🎉 Refactor and optimize database files, enhance error handling with new utility scripts 📚, and update documentation on fault tolerance and unattended operation. 🚀

This commit is contained in:
Till Tomczak 2025-06-02 14:57:58 +02:00
parent 7bea427bd6
commit 6ff407a895
29 changed files with 3148 additions and 450 deletions

View File

@ -1241,6 +1241,239 @@ def kiosk_restart_system():
kiosk_logger.error(f"Fehler beim System-Neustart: {str(e)}") kiosk_logger.error(f"Fehler beim System-Neustart: {str(e)}")
return jsonify({"error": "Fehler beim Neustart"}), 500 return jsonify({"error": "Fehler beim Neustart"}), 500
# ===== ERWEITERTE SYSTEM-CONTROL API-ENDPUNKTE =====
@app.route('/api/admin/system/restart', methods=['POST'])
@login_required
@admin_required
def api_admin_system_restart():
"""Robuster System-Neustart mit Sicherheitsprüfungen."""
try:
from utils.system_control import schedule_system_restart
data = request.get_json() or {}
delay_seconds = data.get('delay_seconds', 60)
reason = data.get('reason', 'Manueller Admin-Neustart')
force = data.get('force', False)
# Begrenze Verzögerung auf sinnvolle Werte
delay_seconds = max(10, min(3600, delay_seconds)) # 10s bis 1h
result = schedule_system_restart(
delay_seconds=delay_seconds,
user_id=str(current_user.id),
reason=reason,
force=force
)
if result.get('success'):
app_logger.warning(f"System-Neustart geplant von Admin {current_user.username}: {reason}")
return jsonify(result)
else:
return jsonify(result), 400
except Exception as e:
app_logger.error(f"Fehler bei System-Neustart-Planung: {e}")
return jsonify({"success": False, "error": str(e)}), 500
@app.route('/api/admin/system/shutdown', methods=['POST'])
@login_required
@admin_required
def api_admin_system_shutdown():
"""Robuster System-Shutdown mit Sicherheitsprüfungen."""
try:
from utils.system_control import schedule_system_shutdown
data = request.get_json() or {}
delay_seconds = data.get('delay_seconds', 30)
reason = data.get('reason', 'Manueller Admin-Shutdown')
force = data.get('force', False)
# Begrenze Verzögerung auf sinnvolle Werte
delay_seconds = max(10, min(3600, delay_seconds)) # 10s bis 1h
result = schedule_system_shutdown(
delay_seconds=delay_seconds,
user_id=str(current_user.id),
reason=reason,
force=force
)
if result.get('success'):
app_logger.warning(f"System-Shutdown geplant von Admin {current_user.username}: {reason}")
return jsonify(result)
else:
return jsonify(result), 400
except Exception as e:
app_logger.error(f"Fehler bei System-Shutdown-Planung: {e}")
return jsonify({"success": False, "error": str(e)}), 500
@app.route('/api/admin/kiosk/restart', methods=['POST'])
@login_required
@admin_required
def api_admin_kiosk_restart():
"""Kiosk-Display neustarten ohne System-Neustart."""
try:
from utils.system_control import restart_kiosk
data = request.get_json() or {}
delay_seconds = data.get('delay_seconds', 10)
reason = data.get('reason', 'Manueller Kiosk-Neustart')
# Begrenze Verzögerung
delay_seconds = max(0, min(300, delay_seconds)) # 0s bis 5min
result = restart_kiosk(
delay_seconds=delay_seconds,
user_id=str(current_user.id),
reason=reason
)
if result.get('success'):
app_logger.info(f"Kiosk-Neustart geplant von Admin {current_user.username}: {reason}")
return jsonify(result)
else:
return jsonify(result), 400
except Exception as e:
app_logger.error(f"Fehler bei Kiosk-Neustart-Planung: {e}")
return jsonify({"success": False, "error": str(e)}), 500
@app.route('/api/admin/system/status', methods=['GET'])
@login_required
@admin_required
def api_admin_system_status_extended():
"""Erweiterte System-Status-Informationen."""
try:
from utils.system_control import get_system_status
from utils.error_recovery import get_error_recovery_manager
# System-Control-Status
system_status = get_system_status()
# Error-Recovery-Status
error_manager = get_error_recovery_manager()
error_stats = error_manager.get_error_statistics()
# Kombiniere alle Informationen
combined_status = {
**system_status,
"error_recovery": error_stats,
"resilience_features": {
"auto_recovery_enabled": error_stats.get('auto_recovery_enabled', False),
"monitoring_active": error_stats.get('monitoring_active', False),
"recovery_success_rate": error_stats.get('recovery_success_rate', 0)
}
}
return jsonify(combined_status)
except Exception as e:
app_logger.error(f"Fehler bei System-Status-Abfrage: {e}")
return jsonify({"success": False, "error": str(e)}), 500
@app.route('/api/admin/system/operations', methods=['GET'])
@login_required
@admin_required
def api_admin_system_operations():
"""Gibt geplante und vergangene System-Operationen zurück."""
try:
from utils.system_control import get_system_control_manager
manager = get_system_control_manager()
return jsonify({
"success": True,
"pending_operations": manager.get_pending_operations(),
"operation_history": manager.get_operation_history(limit=50)
})
except Exception as e:
app_logger.error(f"Fehler bei Operations-Abfrage: {e}")
return jsonify({"success": False, "error": str(e)}), 500
@app.route('/api/admin/system/operations/<operation_id>/cancel', methods=['POST'])
@login_required
@admin_required
def api_admin_cancel_operation(operation_id):
"""Bricht geplante System-Operation ab."""
try:
from utils.system_control import get_system_control_manager
manager = get_system_control_manager()
result = manager.cancel_operation(operation_id)
if result.get('success'):
app_logger.info(f"Operation {operation_id} abgebrochen von Admin {current_user.username}")
return jsonify(result)
else:
return jsonify(result), 400
except Exception as e:
app_logger.error(f"Fehler beim Abbrechen von Operation {operation_id}: {e}")
return jsonify({"success": False, "error": str(e)}), 500
@app.route('/api/admin/error-recovery/status', methods=['GET'])
@login_required
@admin_required
def api_admin_error_recovery_status():
"""Gibt Error-Recovery-Status und -Statistiken zurück."""
try:
from utils.error_recovery import get_error_recovery_manager
manager = get_error_recovery_manager()
return jsonify({
"success": True,
"statistics": manager.get_error_statistics(),
"recent_errors": manager.get_recent_errors(limit=20)
})
except Exception as e:
app_logger.error(f"Fehler bei Error-Recovery-Status-Abfrage: {e}")
return jsonify({"success": False, "error": str(e)}), 500
@app.route('/api/admin/error-recovery/toggle', methods=['POST'])
@login_required
@admin_required
def api_admin_toggle_error_recovery():
"""Aktiviert/Deaktiviert Error-Recovery-Monitoring."""
try:
from utils.error_recovery import get_error_recovery_manager
data = request.get_json() or {}
enable = data.get('enable', True)
manager = get_error_recovery_manager()
if enable:
manager.start_monitoring()
message = "Error-Recovery-Monitoring aktiviert"
else:
manager.stop_monitoring()
message = "Error-Recovery-Monitoring deaktiviert"
app_logger.info(f"{message} von Admin {current_user.username}")
return jsonify({
"success": True,
"message": message,
"monitoring_active": manager.is_active
})
except Exception as e:
app_logger.error(f"Fehler beim Toggle von Error-Recovery: {e}")
return jsonify({"success": False, "error": str(e)}), 500
# ===== BENUTZER-ROUTEN (ehemals user.py) ===== # ===== BENUTZER-ROUTEN (ehemals user.py) =====
@app.route("/user/profile", methods=["GET"]) @app.route("/user/profile", methods=["GET"])
@ -8719,6 +8952,43 @@ if __name__ == "__main__":
# Fallback auf die alte Methode # Fallback auf die alte Methode
shutdown_manager = None shutdown_manager = None
# ===== INITIALISIERE FEHLERRESILIENZ-SYSTEME =====
try:
from utils.error_recovery import start_error_monitoring, stop_error_monitoring
from utils.system_control import get_system_control_manager
# Error-Recovery-Monitoring starten
start_error_monitoring()
app_logger.info("✅ Error-Recovery-Monitoring gestartet")
# System-Control-Manager initialisieren
system_control_manager = get_system_control_manager()
app_logger.info("✅ System-Control-Manager initialisiert")
# Integriere in Shutdown-Manager
if shutdown_manager:
shutdown_manager.register_cleanup_function(
func=stop_error_monitoring,
name="Error Recovery Monitoring",
priority=2,
timeout=10
)
except Exception as e:
app_logger.error(f"❌ Fehlerresilienz-Systeme konnten nicht initialisiert werden: {e}")
# ===== KIOSK-SERVICE-OPTIMIERUNG =====
try:
# Stelle sicher, dass der Kiosk-Service korrekt konfiguriert ist
kiosk_service_exists = os.path.exists('/etc/systemd/system/myp-kiosk.service')
if not kiosk_service_exists:
app_logger.warning("⚠️ Kiosk-Service nicht gefunden - Kiosk-Funktionen eventuell eingeschränkt")
else:
app_logger.info("✅ Kiosk-Service-Konfiguration gefunden")
except Exception as e:
app_logger.error(f"❌ Kiosk-Service-Check fehlgeschlagen: {e}")
# Windows-spezifisches Signal-Handling als Fallback # Windows-spezifisches Signal-Handling als Fallback
def fallback_signal_handler(sig, frame): def fallback_signal_handler(sig, frame):
"""Fallback Signal-Handler für ordnungsgemäßes Shutdown.""" """Fallback Signal-Handler für ordnungsgemäßes Shutdown."""

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@

View File

@ -112,3 +112,5 @@
2025-06-02 14:30:43 - [analytics] analytics - [INFO] INFO - 📈 Analytics Engine initialisiert 2025-06-02 14:30:43 - [analytics] analytics - [INFO] INFO - 📈 Analytics Engine initialisiert
2025-06-02 14:34:09 - [analytics] analytics - [INFO] INFO - 📈 Analytics Engine initialisiert 2025-06-02 14:34:09 - [analytics] analytics - [INFO] INFO - 📈 Analytics Engine initialisiert
2025-06-02 14:43:36 - [analytics] analytics - [INFO] INFO - 📈 Analytics Engine initialisiert 2025-06-02 14:43:36 - [analytics] analytics - [INFO] INFO - 📈 Analytics Engine initialisiert
2025-06-02 14:50:27 - [analytics] analytics - [INFO] INFO - 📈 Analytics Engine initialisiert
2025-06-02 14:51:08 - [analytics] analytics - [INFO] INFO - 📈 Analytics Engine initialisiert

View File

@ -2925,3 +2925,56 @@ WHERE jobs.status = ?) AS anon_1]
2025-06-02 14:43:49 - [app] app - [ERROR] ERROR - Datenbank-Transaktion fehlgeschlagen: name 'func' is not defined 2025-06-02 14:43:49 - [app] app - [ERROR] ERROR - Datenbank-Transaktion fehlgeschlagen: name 'func' is not defined
2025-06-02 14:43:49 - [app] app - [ERROR] ERROR - Fehler beim Erstellen der Steckdosen-Statistiken: name 'func' is not defined 2025-06-02 14:43:49 - [app] app - [ERROR] ERROR - Fehler beim Erstellen der Steckdosen-Statistiken: name 'func' is not defined
2025-06-02 14:43:51 - [app] app - [INFO] INFO - Admin-Check für Funktion api_admin_plug_schedules_calendar: User authenticated: True, User ID: 1, Is Admin: True 2025-06-02 14:43:51 - [app] app - [INFO] INFO - Admin-Check für Funktion api_admin_plug_schedules_calendar: User authenticated: True, User ID: 1, Is Admin: True
2025-06-02 14:50:27 - [app] app - [INFO] INFO - Optimierte SQLite-Engine erstellt: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\database\myp.db
2025-06-02 14:50:28 - [app] app - [INFO] INFO - SQLite für Raspberry Pi optimiert (reduzierte Cache-Größe, SD-Karten I/O)
2025-06-02 14:50:28 - [app] app - [INFO] INFO - ✅ Timeout Force-Quit Manager geladen
2025-06-02 14:50:28 - [app] app - [INFO] INFO - ✅ Zentraler Shutdown-Manager initialisiert
2025-06-02 14:50:28 - [app] app - [INFO] INFO - 🔄 Starte Datenbank-Setup und Migrationen...
2025-06-02 14:50:28 - [app] app - [INFO] INFO - Datenbank mit Optimierungen initialisiert
2025-06-02 14:50:28 - [app] app - [INFO] INFO - ✅ JobOrder-Tabelle bereits vorhanden
2025-06-02 14:50:28 - [app] app - [INFO] INFO - Admin-Benutzer admin (admin@mercedes-benz.com) existiert bereits. Passwort wurde zurückgesetzt.
2025-06-02 14:50:28 - [app] app - [INFO] INFO - ✅ Datenbank-Setup und Migrationen erfolgreich abgeschlossen
2025-06-02 14:50:28 - [app] app - [INFO] INFO - 🖨️ Starte automatische Steckdosen-Initialisierung...
2025-06-02 14:50:30 - [app] app - [INFO] INFO - Steckdosen-Status geloggt: Drucker 4, Status: disconnected, Quelle: system
2025-06-02 14:50:32 - [app] app - [INFO] INFO - Steckdosen-Status geloggt: Drucker 5, Status: disconnected, Quelle: system
2025-06-02 14:50:32 - [app] app - [INFO] INFO - ✅ Steckdosen-Initialisierung: 0/2 Drucker erfolgreich
2025-06-02 14:50:32 - [app] app - [WARNING] WARNING - ⚠️ 2 Drucker konnten nicht initialisiert werden
2025-06-02 14:50:32 - [app] app - [INFO] INFO - 🔄 Debug-Modus: Queue Manager deaktiviert für Entwicklung
2025-06-02 14:50:32 - [app] app - [INFO] INFO - Job-Scheduler gestartet
2025-06-02 14:50:32 - [app] app - [INFO] INFO - Starte Debug-Server auf 0.0.0.0:5000 (HTTP)
2025-06-02 14:50:32 - [app] app - [INFO] INFO - Windows-Debug-Modus: Auto-Reload deaktiviert
2025-06-02 14:50:33 - [app] app - [INFO] INFO - Benutzer admin@mercedes-benz.com hat sich abgemeldet
2025-06-02 14:50:44 - [app] app - [INFO] INFO - Admin-Check für Funktion admin_plug_schedules: User authenticated: True, User ID: 1, Is Admin: True
2025-06-02 14:50:44 - [app] app - [INFO] INFO - Admin Administrator (ID: 1) öffnet Steckdosenschaltzeiten
2025-06-02 14:50:44 - [app] app - [ERROR] ERROR - Datenbank-Transaktion fehlgeschlagen: name 'func' is not defined
2025-06-02 14:50:44 - [app] app - [ERROR] ERROR - Fehler beim Erstellen der Steckdosen-Statistiken: name 'func' is not defined
2025-06-02 14:50:46 - [app] app - [INFO] INFO - Admin-Check für Funktion api_admin_plug_schedules_calendar: User authenticated: True, User ID: 1, Is Admin: True
2025-06-02 14:50:46 - [app] app - [INFO] INFO - Admin-Check für Funktion api_admin_plug_schedules_statistics: User authenticated: True, User ID: 1, Is Admin: True
2025-06-02 14:50:46 - [app] app - [ERROR] ERROR - Datenbank-Transaktion fehlgeschlagen: name 'func' is not defined
2025-06-02 14:50:46 - [app] app - [ERROR] ERROR - Fehler beim Erstellen der Steckdosen-Statistiken: name 'func' is not defined
2025-06-02 14:51:08 - [app] app - [INFO] INFO - Optimierte SQLite-Engine erstellt: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend\database\myp.db
2025-06-02 14:51:09 - [app] app - [INFO] INFO - SQLite für Raspberry Pi optimiert (reduzierte Cache-Größe, SD-Karten I/O)
2025-06-02 14:51:09 - [app] app - [INFO] INFO - ✅ Timeout Force-Quit Manager geladen
2025-06-02 14:51:09 - [app] app - [INFO] INFO - ✅ Zentraler Shutdown-Manager initialisiert
2025-06-02 14:51:09 - [app] app - [INFO] INFO - 🔄 Starte Datenbank-Setup und Migrationen...
2025-06-02 14:51:09 - [app] app - [INFO] INFO - Datenbank mit Optimierungen initialisiert
2025-06-02 14:51:09 - [app] app - [INFO] INFO - ✅ JobOrder-Tabelle bereits vorhanden
2025-06-02 14:51:09 - [app] app - [INFO] INFO - Admin-Benutzer admin (admin@mercedes-benz.com) existiert bereits. Passwort wurde zurückgesetzt.
2025-06-02 14:51:09 - [app] app - [INFO] INFO - ✅ Datenbank-Setup und Migrationen erfolgreich abgeschlossen
2025-06-02 14:51:09 - [app] app - [INFO] INFO - 🖨️ Starte automatische Steckdosen-Initialisierung...
2025-06-02 14:51:12 - [app] app - [INFO] INFO - Steckdosen-Status geloggt: Drucker 4, Status: disconnected, Quelle: system
2025-06-02 14:51:14 - [app] app - [INFO] INFO - Steckdosen-Status geloggt: Drucker 5, Status: disconnected, Quelle: system
2025-06-02 14:51:14 - [app] app - [INFO] INFO - ✅ Steckdosen-Initialisierung: 0/2 Drucker erfolgreich
2025-06-02 14:51:14 - [app] app - [WARNING] WARNING - ⚠️ 2 Drucker konnten nicht initialisiert werden
2025-06-02 14:51:14 - [app] app - [INFO] INFO - 🔄 Debug-Modus: Queue Manager deaktiviert für Entwicklung
2025-06-02 14:51:14 - [app] app - [INFO] INFO - Job-Scheduler gestartet
2025-06-02 14:51:14 - [app] app - [INFO] INFO - Starte Debug-Server auf 0.0.0.0:5000 (HTTP)
2025-06-02 14:51:14 - [app] app - [INFO] INFO - Windows-Debug-Modus: Auto-Reload deaktiviert
2025-06-02 14:51:14 - [app] app - [INFO] INFO - Admin-Check für Funktion admin_plug_schedules: User authenticated: True, User ID: 1, Is Admin: True
2025-06-02 14:51:14 - [app] app - [INFO] INFO - Admin Administrator (ID: 1) öffnet Steckdosenschaltzeiten
2025-06-02 14:51:14 - [app] app - [ERROR] ERROR - Datenbank-Transaktion fehlgeschlagen: name 'func' is not defined
2025-06-02 14:51:14 - [app] app - [ERROR] ERROR - Fehler beim Erstellen der Steckdosen-Statistiken: name 'func' is not defined
2025-06-02 14:51:15 - [app] app - [INFO] INFO - Admin-Check für Funktion api_admin_plug_schedules_calendar: User authenticated: True, User ID: 1, Is Admin: True
2025-06-02 14:51:15 - [app] app - [INFO] INFO - Admin-Check für Funktion api_admin_plug_schedules_statistics: User authenticated: True, User ID: 1, Is Admin: True
2025-06-02 14:51:15 - [app] app - [ERROR] ERROR - Datenbank-Transaktion fehlgeschlagen: name 'func' is not defined
2025-06-02 14:51:15 - [app] app - [ERROR] ERROR - Fehler beim Erstellen der Steckdosen-Statistiken: name 'func' is not defined

View File

@ -71,3 +71,5 @@
2025-06-02 14:27:04 - [auth] auth - [WARNING] WARNING - JSON-Parsing fehlgeschlagen: 400 Bad Request: Failed to decode JSON object: Expecting value: line 1 column 1 (char 0) 2025-06-02 14:27:04 - [auth] auth - [WARNING] WARNING - JSON-Parsing fehlgeschlagen: 400 Bad Request: Failed to decode JSON object: Expecting value: line 1 column 1 (char 0)
2025-06-02 14:27:05 - [auth] auth - [INFO] INFO - Benutzer admin@mercedes-benz.com hat sich erfolgreich angemeldet 2025-06-02 14:27:05 - [auth] auth - [INFO] INFO - Benutzer admin@mercedes-benz.com hat sich erfolgreich angemeldet
2025-06-02 14:27:06 - [auth] auth - [INFO] INFO - 🔐 Neue Session erstellt für Benutzer admin@mercedes-benz.com von IP 127.0.0.1 2025-06-02 14:27:06 - [auth] auth - [INFO] INFO - 🔐 Neue Session erstellt für Benutzer admin@mercedes-benz.com von IP 127.0.0.1
2025-06-02 14:50:38 - [auth] auth - [WARNING] WARNING - JSON-Parsing fehlgeschlagen: 400 Bad Request: Failed to decode JSON object: Expecting value: line 1 column 1 (char 0)
2025-06-02 14:50:38 - [auth] auth - [INFO] INFO - Benutzer admin@mercedes-benz.com hat sich erfolgreich angemeldet

View File

@ -116,3 +116,5 @@
2025-06-02 14:30:43 - [backup] backup - [INFO] INFO - BackupManager initialisiert (minimal implementation) 2025-06-02 14:30:43 - [backup] backup - [INFO] INFO - BackupManager initialisiert (minimal implementation)
2025-06-02 14:34:08 - [backup] backup - [INFO] INFO - BackupManager initialisiert (minimal implementation) 2025-06-02 14:34:08 - [backup] backup - [INFO] INFO - BackupManager initialisiert (minimal implementation)
2025-06-02 14:43:36 - [backup] backup - [INFO] INFO - BackupManager initialisiert (minimal implementation) 2025-06-02 14:43:36 - [backup] backup - [INFO] INFO - BackupManager initialisiert (minimal implementation)
2025-06-02 14:50:27 - [backup] backup - [INFO] INFO - BackupManager initialisiert (minimal implementation)
2025-06-02 14:51:08 - [backup] backup - [INFO] INFO - BackupManager initialisiert (minimal implementation)

View File

@ -449,3 +449,11 @@
2025-06-02 14:43:37 - [dashboard] dashboard - [INFO] INFO - Dashboard-Background-Worker gestartet 2025-06-02 14:43:37 - [dashboard] dashboard - [INFO] INFO - Dashboard-Background-Worker gestartet
2025-06-02 14:43:37 - [dashboard] dashboard - [INFO] INFO - Dashboard WebSocket-Server wird mit threading initialisiert (eventlet-Fallback) 2025-06-02 14:43:37 - [dashboard] dashboard - [INFO] INFO - Dashboard WebSocket-Server wird mit threading initialisiert (eventlet-Fallback)
2025-06-02 14:43:37 - [dashboard] dashboard - [INFO] INFO - Dashboard WebSocket-Server initialisiert (async_mode: threading) 2025-06-02 14:43:37 - [dashboard] dashboard - [INFO] INFO - Dashboard WebSocket-Server initialisiert (async_mode: threading)
2025-06-02 14:50:28 - [dashboard] dashboard - [INFO] INFO - Dashboard-Background-Worker gestartet
2025-06-02 14:50:28 - [dashboard] dashboard - [INFO] INFO - Dashboard-Background-Worker gestartet
2025-06-02 14:50:28 - [dashboard] dashboard - [INFO] INFO - Dashboard WebSocket-Server wird mit threading initialisiert (eventlet-Fallback)
2025-06-02 14:50:28 - [dashboard] dashboard - [INFO] INFO - Dashboard WebSocket-Server initialisiert (async_mode: threading)
2025-06-02 14:51:09 - [dashboard] dashboard - [INFO] INFO - Dashboard-Background-Worker gestartet
2025-06-02 14:51:09 - [dashboard] dashboard - [INFO] INFO - Dashboard-Background-Worker gestartet
2025-06-02 14:51:09 - [dashboard] dashboard - [INFO] INFO - Dashboard WebSocket-Server wird mit threading initialisiert (eventlet-Fallback)
2025-06-02 14:51:09 - [dashboard] dashboard - [INFO] INFO - Dashboard WebSocket-Server initialisiert (async_mode: threading)

View File

@ -112,3 +112,5 @@
2025-06-02 14:30:43 - [database] database - [INFO] INFO - Datenbank-Wartungs-Scheduler gestartet 2025-06-02 14:30:43 - [database] database - [INFO] INFO - Datenbank-Wartungs-Scheduler gestartet
2025-06-02 14:34:08 - [database] database - [INFO] INFO - Datenbank-Wartungs-Scheduler gestartet 2025-06-02 14:34:08 - [database] database - [INFO] INFO - Datenbank-Wartungs-Scheduler gestartet
2025-06-02 14:43:36 - [database] database - [INFO] INFO - Datenbank-Wartungs-Scheduler gestartet 2025-06-02 14:43:36 - [database] database - [INFO] INFO - Datenbank-Wartungs-Scheduler gestartet
2025-06-02 14:50:27 - [database] database - [INFO] INFO - Datenbank-Wartungs-Scheduler gestartet
2025-06-02 14:51:08 - [database] database - [INFO] INFO - Datenbank-Wartungs-Scheduler gestartet

View File

@ -109,3 +109,5 @@
2025-06-02 14:30:44 - [email_notification] email_notification - [INFO] INFO - 📧 Offline-E-Mail-Benachrichtigung initialisiert (kein echter E-Mail-Versand) 2025-06-02 14:30:44 - [email_notification] email_notification - [INFO] INFO - 📧 Offline-E-Mail-Benachrichtigung initialisiert (kein echter E-Mail-Versand)
2025-06-02 14:34:09 - [email_notification] email_notification - [INFO] INFO - 📧 Offline-E-Mail-Benachrichtigung initialisiert (kein echter E-Mail-Versand) 2025-06-02 14:34:09 - [email_notification] email_notification - [INFO] INFO - 📧 Offline-E-Mail-Benachrichtigung initialisiert (kein echter E-Mail-Versand)
2025-06-02 14:43:37 - [email_notification] email_notification - [INFO] INFO - 📧 Offline-E-Mail-Benachrichtigung initialisiert (kein echter E-Mail-Versand) 2025-06-02 14:43:37 - [email_notification] email_notification - [INFO] INFO - 📧 Offline-E-Mail-Benachrichtigung initialisiert (kein echter E-Mail-Versand)
2025-06-02 14:50:28 - [email_notification] email_notification - [INFO] INFO - 📧 Offline-E-Mail-Benachrichtigung initialisiert (kein echter E-Mail-Versand)
2025-06-02 14:51:09 - [email_notification] email_notification - [INFO] INFO - 📧 Offline-E-Mail-Benachrichtigung initialisiert (kein echter E-Mail-Versand)

View File

@ -131,3 +131,8 @@ WHERE printers.id = ?]
2025-06-02 10:03:40 - [jobs] jobs - [INFO] INFO - Jobs abgerufen: 16 von 16 (Seite 1) 2025-06-02 10:03:40 - [jobs] jobs - [INFO] INFO - Jobs abgerufen: 16 von 16 (Seite 1)
2025-06-02 10:03:47 - [jobs] jobs - [INFO] INFO - Jobs abgerufen: 16 von 16 (Seite 1) 2025-06-02 10:03:47 - [jobs] jobs - [INFO] INFO - Jobs abgerufen: 16 von 16 (Seite 1)
2025-06-02 10:26:53 - [jobs] jobs - [INFO] INFO - Jobs abgerufen: 16 von 16 (Seite 1) 2025-06-02 10:26:53 - [jobs] jobs - [INFO] INFO - Jobs abgerufen: 16 von 16 (Seite 1)
2025-06-02 14:51:31 - [jobs] jobs - [INFO] INFO - Jobs abgerufen: 16 von 16 (Seite 1)
2025-06-02 14:51:46 - [jobs] jobs - [INFO] INFO - Jobs abgerufen: 16 von 16 (Seite 1)
2025-06-02 14:52:01 - [jobs] jobs - [ERROR] ERROR - Fehler beim Abrufen von Jobs: tuple index out of range
2025-06-02 14:52:01 - [jobs] jobs - [INFO] INFO - Jobs abgerufen: 16 von 16 (Seite 1)
2025-06-02 14:52:16 - [jobs] jobs - [INFO] INFO - Jobs abgerufen: 16 von 16 (Seite 1)

View File

@ -224,3 +224,7 @@
2025-06-02 14:34:09 - [maintenance] maintenance - [INFO] INFO - Wartungs-Scheduler gestartet 2025-06-02 14:34:09 - [maintenance] maintenance - [INFO] INFO - Wartungs-Scheduler gestartet
2025-06-02 14:43:37 - [maintenance] maintenance - [INFO] INFO - Wartungs-Scheduler gestartet 2025-06-02 14:43:37 - [maintenance] maintenance - [INFO] INFO - Wartungs-Scheduler gestartet
2025-06-02 14:43:37 - [maintenance] maintenance - [INFO] INFO - Wartungs-Scheduler gestartet 2025-06-02 14:43:37 - [maintenance] maintenance - [INFO] INFO - Wartungs-Scheduler gestartet
2025-06-02 14:50:28 - [maintenance] maintenance - [INFO] INFO - Wartungs-Scheduler gestartet
2025-06-02 14:50:28 - [maintenance] maintenance - [INFO] INFO - Wartungs-Scheduler gestartet
2025-06-02 14:51:09 - [maintenance] maintenance - [INFO] INFO - Wartungs-Scheduler gestartet
2025-06-02 14:51:09 - [maintenance] maintenance - [INFO] INFO - Wartungs-Scheduler gestartet

View File

@ -222,3 +222,7 @@
2025-06-02 14:34:09 - [multi_location] multi_location - [INFO] INFO - Standard-Standort erstellt 2025-06-02 14:34:09 - [multi_location] multi_location - [INFO] INFO - Standard-Standort erstellt
2025-06-02 14:43:37 - [multi_location] multi_location - [INFO] INFO - Standard-Standort erstellt 2025-06-02 14:43:37 - [multi_location] multi_location - [INFO] INFO - Standard-Standort erstellt
2025-06-02 14:43:37 - [multi_location] multi_location - [INFO] INFO - Standard-Standort erstellt 2025-06-02 14:43:37 - [multi_location] multi_location - [INFO] INFO - Standard-Standort erstellt
2025-06-02 14:50:28 - [multi_location] multi_location - [INFO] INFO - Standard-Standort erstellt
2025-06-02 14:50:28 - [multi_location] multi_location - [INFO] INFO - Standard-Standort erstellt
2025-06-02 14:51:09 - [multi_location] multi_location - [INFO] INFO - Standard-Standort erstellt
2025-06-02 14:51:09 - [multi_location] multi_location - [INFO] INFO - Standard-Standort erstellt

View File

@ -111,3 +111,5 @@
2025-06-02 14:30:44 - [permissions] permissions - [INFO] INFO - 🔐 Permission Template Helpers registriert 2025-06-02 14:30:44 - [permissions] permissions - [INFO] INFO - 🔐 Permission Template Helpers registriert
2025-06-02 14:34:09 - [permissions] permissions - [INFO] INFO - 🔐 Permission Template Helpers registriert 2025-06-02 14:34:09 - [permissions] permissions - [INFO] INFO - 🔐 Permission Template Helpers registriert
2025-06-02 14:43:37 - [permissions] permissions - [INFO] INFO - 🔐 Permission Template Helpers registriert 2025-06-02 14:43:37 - [permissions] permissions - [INFO] INFO - 🔐 Permission Template Helpers registriert
2025-06-02 14:50:28 - [permissions] permissions - [INFO] INFO - 🔐 Permission Template Helpers registriert
2025-06-02 14:51:09 - [permissions] permissions - [INFO] INFO - 🔐 Permission Template Helpers registriert

View File

@ -3200,3 +3200,37 @@
2025-06-02 14:44:02 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Teste IP 5/6: 192.168.0.102 2025-06-02 14:44:02 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Teste IP 5/6: 192.168.0.102
2025-06-02 14:44:08 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Teste IP 6/6: 192.168.0.105 2025-06-02 14:44:08 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Teste IP 6/6: 192.168.0.105
2025-06-02 14:44:14 - [printer_monitor] printer_monitor - [INFO] INFO - ✅ Steckdosen-Erkennung abgeschlossen: 0/6 Steckdosen gefunden in 36.0s 2025-06-02 14:44:14 - [printer_monitor] printer_monitor - [INFO] INFO - ✅ Steckdosen-Erkennung abgeschlossen: 0/6 Steckdosen gefunden in 36.0s
2025-06-02 14:50:27 - [printer_monitor] printer_monitor - [INFO] INFO - 🖨️ Drucker-Monitor initialisiert
2025-06-02 14:50:27 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Automatische Tapo-Erkennung in separatem Thread gestartet
2025-06-02 14:50:28 - [printer_monitor] printer_monitor - [INFO] INFO - 🚀 Starte Steckdosen-Initialisierung beim Programmstart...
2025-06-02 14:50:29 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Starte automatische Tapo-Steckdosenerkennung...
2025-06-02 14:50:29 - [printer_monitor] printer_monitor - [INFO] INFO - 🔄 Teste 6 Standard-IPs aus der Konfiguration
2025-06-02 14:50:29 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Teste IP 1/6: 192.168.0.103
2025-06-02 14:50:30 - [printer_monitor] printer_monitor - [WARNING] WARNING - ❌ Tapo P110 (192.168.0.103): Steckdose konnte nicht ausgeschaltet werden
2025-06-02 14:50:32 - [printer_monitor] printer_monitor - [WARNING] WARNING - ❌ Tapo P110 (192.168.0.104): Steckdose konnte nicht ausgeschaltet werden
2025-06-02 14:50:32 - [printer_monitor] printer_monitor - [INFO] INFO - 🎯 Steckdosen-Initialisierung abgeschlossen: 0/2 erfolgreich
2025-06-02 14:50:35 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Teste IP 2/6: 192.168.0.104
2025-06-02 14:50:41 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Teste IP 3/6: 192.168.0.100
2025-06-02 14:50:46 - [printer_monitor] printer_monitor - [INFO] INFO - 🔄 Aktualisiere Live-Druckerstatus...
2025-06-02 14:50:46 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Prüfe Status von 2 aktiven Druckern...
2025-06-02 14:50:47 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Teste IP 4/6: 192.168.0.101
2025-06-02 14:51:08 - [printer_monitor] printer_monitor - [INFO] INFO - 🖨️ Drucker-Monitor initialisiert
2025-06-02 14:51:08 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Automatische Tapo-Erkennung in separatem Thread gestartet
2025-06-02 14:51:09 - [printer_monitor] printer_monitor - [INFO] INFO - 🚀 Starte Steckdosen-Initialisierung beim Programmstart...
2025-06-02 14:51:10 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Starte automatische Tapo-Steckdosenerkennung...
2025-06-02 14:51:10 - [printer_monitor] printer_monitor - [INFO] INFO - 🔄 Teste 6 Standard-IPs aus der Konfiguration
2025-06-02 14:51:10 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Teste IP 1/6: 192.168.0.103
2025-06-02 14:51:12 - [printer_monitor] printer_monitor - [WARNING] WARNING - ❌ Tapo P110 (192.168.0.103): Steckdose konnte nicht ausgeschaltet werden
2025-06-02 14:51:14 - [printer_monitor] printer_monitor - [WARNING] WARNING - ❌ Tapo P110 (192.168.0.104): Steckdose konnte nicht ausgeschaltet werden
2025-06-02 14:51:14 - [printer_monitor] printer_monitor - [INFO] INFO - 🎯 Steckdosen-Initialisierung abgeschlossen: 0/2 erfolgreich
2025-06-02 14:51:15 - [printer_monitor] printer_monitor - [INFO] INFO - 🔄 Aktualisiere Live-Druckerstatus...
2025-06-02 14:51:15 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Prüfe Status von 2 aktiven Druckern...
2025-06-02 14:51:16 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Teste IP 2/6: 192.168.0.104
2025-06-02 14:51:22 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Teste IP 3/6: 192.168.0.100
2025-06-02 14:51:24 - [printer_monitor] printer_monitor - [WARNING] WARNING - 🔌 Tapo P110 (192.168.0.103): UNREACHABLE (Ping fehlgeschlagen)
2025-06-02 14:51:24 - [printer_monitor] printer_monitor - [WARNING] WARNING - 🔌 Tapo P110 (192.168.0.104): UNREACHABLE (Ping fehlgeschlagen)
2025-06-02 14:51:24 - [printer_monitor] printer_monitor - [INFO] INFO - ✅ Status-Update abgeschlossen für 2 Drucker
2025-06-02 14:51:28 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Teste IP 4/6: 192.168.0.101
2025-06-02 14:51:34 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Teste IP 5/6: 192.168.0.102
2025-06-02 14:51:40 - [printer_monitor] printer_monitor - [INFO] INFO - 🔍 Teste IP 6/6: 192.168.0.105
2025-06-02 14:51:46 - [printer_monitor] printer_monitor - [INFO] INFO - ✅ Steckdosen-Erkennung abgeschlossen: 0/6 Steckdosen gefunden in 36.0s

View File

@ -6604,3 +6604,38 @@
2025-06-02 14:43:53 - [printers] printers - [INFO] INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1) 2025-06-02 14:43:53 - [printers] printers - [INFO] INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
2025-06-02 14:43:53 - [printers] printers - [INFO] INFO - ✅ Live-Status-Abfrage erfolgreich: 2 Drucker 2025-06-02 14:43:53 - [printers] printers - [INFO] INFO - ✅ Live-Status-Abfrage erfolgreich: 2 Drucker
2025-06-02 14:43:53 - [printers] printers - [INFO] INFO - ✅ API-Live-Drucker-Status-Abfrage 'get_live_printer_status' erfolgreich in 0.52ms 2025-06-02 14:43:53 - [printers] printers - [INFO] INFO - ✅ API-Live-Drucker-Status-Abfrage 'get_live_printer_status' erfolgreich in 0.52ms
2025-06-02 14:44:21 - [printers] printers - [INFO] INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
2025-06-02 14:44:21 - [printers] printers - [INFO] INFO - ✅ Live-Status-Abfrage erfolgreich: 2 Drucker
2025-06-02 14:44:21 - [printers] printers - [INFO] INFO - ✅ API-Live-Drucker-Status-Abfrage 'get_live_printer_status' erfolgreich in 0.32ms
2025-06-02 14:44:51 - [printers] printers - [INFO] INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
2025-06-02 14:44:51 - [printers] printers - [INFO] INFO - ✅ Live-Status-Abfrage erfolgreich: 2 Drucker
2025-06-02 14:44:51 - [printers] printers - [INFO] INFO - ✅ API-Live-Drucker-Status-Abfrage 'get_live_printer_status' erfolgreich in 0.32ms
2025-06-02 14:45:21 - [printers] printers - [INFO] INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
2025-06-02 14:45:21 - [printers] printers - [INFO] INFO - ✅ Live-Status-Abfrage erfolgreich: 2 Drucker
2025-06-02 14:45:21 - [printers] printers - [INFO] INFO - ✅ API-Live-Drucker-Status-Abfrage 'get_live_printer_status' erfolgreich in 0.30ms
2025-06-02 14:45:51 - [printers] printers - [INFO] INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
2025-06-02 14:45:51 - [printers] printers - [INFO] INFO - ✅ Live-Status-Abfrage erfolgreich: 2 Drucker
2025-06-02 14:45:51 - [printers] printers - [INFO] INFO - ✅ API-Live-Drucker-Status-Abfrage 'get_live_printer_status' erfolgreich in 0.27ms
2025-06-02 14:46:21 - [printers] printers - [INFO] INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
2025-06-02 14:46:21 - [printers] printers - [INFO] INFO - ✅ Live-Status-Abfrage erfolgreich: 2 Drucker
2025-06-02 14:46:21 - [printers] printers - [INFO] INFO - ✅ API-Live-Drucker-Status-Abfrage 'get_live_printer_status' erfolgreich in 0.29ms
2025-06-02 14:46:51 - [printers] printers - [INFO] INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
2025-06-02 14:46:51 - [printers] printers - [INFO] INFO - ✅ Live-Status-Abfrage erfolgreich: 2 Drucker
2025-06-02 14:46:51 - [printers] printers - [INFO] INFO - ✅ API-Live-Drucker-Status-Abfrage 'get_live_printer_status' erfolgreich in 0.33ms
2025-06-02 14:47:21 - [printers] printers - [INFO] INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
2025-06-02 14:47:21 - [printers] printers - [INFO] INFO - ✅ Live-Status-Abfrage erfolgreich: 2 Drucker
2025-06-02 14:47:21 - [printers] printers - [INFO] INFO - ✅ API-Live-Drucker-Status-Abfrage 'get_live_printer_status' erfolgreich in 0.31ms
2025-06-02 14:47:51 - [printers] printers - [INFO] INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
2025-06-02 14:47:51 - [printers] printers - [INFO] INFO - ✅ Live-Status-Abfrage erfolgreich: 2 Drucker
2025-06-02 14:47:51 - [printers] printers - [INFO] INFO - ✅ API-Live-Drucker-Status-Abfrage 'get_live_printer_status' erfolgreich in 0.29ms
2025-06-02 14:50:46 - [printers] printers - [INFO] INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
2025-06-02 14:51:15 - [printers] printers - [INFO] INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
2025-06-02 14:51:24 - [printers] printers - [INFO] INFO - ✅ Live-Status-Abfrage erfolgreich: 2 Drucker
2025-06-02 14:51:24 - [printers] printers - [INFO] INFO - ✅ API-Live-Drucker-Status-Abfrage 'get_live_printer_status' erfolgreich in 9010.43ms
2025-06-02 14:51:27 - [printers] printers - [INFO] INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
2025-06-02 14:51:27 - [printers] printers - [INFO] INFO - ✅ Live-Status-Abfrage erfolgreich: 2 Drucker
2025-06-02 14:51:27 - [printers] printers - [INFO] INFO - ✅ API-Live-Drucker-Status-Abfrage 'get_live_printer_status' erfolgreich in 1.68ms
2025-06-02 14:51:32 - [printers] printers - [INFO] INFO - Schnelles Laden abgeschlossen: 6 Drucker geladen (ohne Status-Check)
2025-06-02 14:52:02 - [printers] printers - [INFO] INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
2025-06-02 14:52:02 - [printers] printers - [INFO] INFO - ✅ Live-Status-Abfrage erfolgreich: 2 Drucker
2025-06-02 14:52:02 - [printers] printers - [INFO] INFO - ✅ API-Live-Drucker-Status-Abfrage 'get_live_printer_status' erfolgreich in 0.66ms

View File

@ -27601,3 +27601,522 @@
2025-06-02 14:34:31 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x00000149DF324C00>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)')) 2025-06-02 14:34:31 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x00000149DF324C00>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:34:31 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 6 nicht einschalten 2025-06-02 14:34:31 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 6 nicht einschalten
2025-06-02 14:34:31 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 9: zi 2025-06-02 14:34:31 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 9: zi
2025-06-02 14:43:36 - [scheduler] scheduler - [INFO] INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
2025-06-02 14:43:42 - [scheduler] scheduler - [INFO] INFO - Scheduler-Thread gestartet
2025-06-02 14:43:42 - [scheduler] scheduler - [INFO] INFO - Scheduler gestartet
2025-06-02 14:43:42 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 7: test
2025-06-02 14:43:44 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0777610>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:43:44 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 7 nicht einschalten
2025-06-02 14:43:44 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 8: test
2025-06-02 14:43:46 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C24640>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:43:46 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 8 nicht einschalten
2025-06-02 14:43:46 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 1: test
2025-06-02 14:43:48 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0796C40>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:43:48 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 1 nicht einschalten
2025-06-02 14:43:48 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 2: test
2025-06-02 14:43:50 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA07F5490>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:43:50 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 2 nicht einschalten
2025-06-02 14:43:50 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 3: test
2025-06-02 14:43:52 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079ACF0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:43:52 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 3 nicht einschalten
2025-06-02 14:43:52 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 4: test
2025-06-02 14:43:55 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0717680>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:43:55 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 4 nicht einschalten
2025-06-02 14:43:55 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 5: test
2025-06-02 14:43:57 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0717240>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:43:57 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 5 nicht einschalten
2025-06-02 14:43:57 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 6: test
2025-06-02 14:43:59 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C78160>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:43:59 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 6 nicht einschalten
2025-06-02 14:43:59 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 9: zi
2025-06-02 14:44:01 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C78380>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:44:01 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 9 nicht einschalten
2025-06-02 14:44:01 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 10: zi
2025-06-02 14:44:03 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C787C0>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:44:03 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 10 nicht einschalten
2025-06-02 14:44:03 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 11: fee
2025-06-02 14:44:05 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C78AF0>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:44:05 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 11 nicht einschalten
2025-06-02 14:44:05 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 12: fee
2025-06-02 14:44:07 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C78E20>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:44:07 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 12 nicht einschalten
2025-06-02 14:44:07 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 13: e2
2025-06-02 14:44:09 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C79150>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:44:09 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 13 nicht einschalten
2025-06-02 14:44:09 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 14: e2
2025-06-02 14:44:11 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C79480>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:44:11 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 14 nicht einschalten
2025-06-02 14:44:11 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 15: test
2025-06-02 14:44:13 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0717240>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:44:13 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 15 nicht einschalten
2025-06-02 14:44:13 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 16: test
2025-06-02 14:44:15 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079B570>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:44:15 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 16 nicht einschalten
2025-06-02 14:44:16 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 7: test
2025-06-02 14:44:19 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079A690>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:44:19 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 7 nicht einschalten
2025-06-02 14:44:19 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 8: test
2025-06-02 14:44:21 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079B680>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:44:21 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 8 nicht einschalten
2025-06-02 14:44:21 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 1: test
2025-06-02 14:44:23 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079B460>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:44:23 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 1 nicht einschalten
2025-06-02 14:44:23 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 2: test
2025-06-02 14:44:25 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079ACF0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:44:25 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 2 nicht einschalten
2025-06-02 14:44:25 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 3: test
2025-06-02 14:44:27 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C79590>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:44:27 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 3 nicht einschalten
2025-06-02 14:44:27 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 4: test
2025-06-02 14:44:29 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C79260>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:44:29 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 4 nicht einschalten
2025-06-02 14:44:29 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 5: test
2025-06-02 14:44:31 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C78D10>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:44:31 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 5 nicht einschalten
2025-06-02 14:44:31 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 6: test
2025-06-02 14:44:33 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C78C00>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:44:33 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 6 nicht einschalten
2025-06-02 14:44:33 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 9: zi
2025-06-02 14:44:35 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079ACF0>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:44:35 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 9 nicht einschalten
2025-06-02 14:44:35 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 10: zi
2025-06-02 14:44:37 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079A8B0>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:44:37 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 10 nicht einschalten
2025-06-02 14:44:37 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 11: fee
2025-06-02 14:44:40 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079BAC0>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:44:40 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 11 nicht einschalten
2025-06-02 14:44:40 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 12: fee
2025-06-02 14:44:42 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079AF10>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:44:42 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 12 nicht einschalten
2025-06-02 14:44:42 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 13: e2
2025-06-02 14:44:44 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0799BF0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:44:44 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 13 nicht einschalten
2025-06-02 14:44:44 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 14: e2
2025-06-02 14:44:46 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0717680>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:44:46 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 14 nicht einschalten
2025-06-02 14:44:46 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 15: test
2025-06-02 14:44:48 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C98270>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:44:48 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 15 nicht einschalten
2025-06-02 14:44:48 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 16: test
2025-06-02 14:44:50 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C985A0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:44:50 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 16 nicht einschalten
2025-06-02 14:44:51 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 7: test
2025-06-02 14:44:53 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C988D0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:44:53 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 7 nicht einschalten
2025-06-02 14:44:53 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 8: test
2025-06-02 14:44:55 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C98AF0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:44:55 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 8 nicht einschalten
2025-06-02 14:44:55 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 1: test
2025-06-02 14:44:57 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C98F30>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:44:57 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 1 nicht einschalten
2025-06-02 14:44:57 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 2: test
2025-06-02 14:44:59 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0799BF0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:44:59 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 2 nicht einschalten
2025-06-02 14:44:59 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 3: test
2025-06-02 14:45:01 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079AF10>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:45:01 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 3 nicht einschalten
2025-06-02 14:45:01 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 4: test
2025-06-02 14:45:03 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079BAC0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:45:03 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 4 nicht einschalten
2025-06-02 14:45:03 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 5: test
2025-06-02 14:45:05 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079A8B0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:45:05 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 5 nicht einschalten
2025-06-02 14:45:05 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 6: test
2025-06-02 14:45:08 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079ACF0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:45:08 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 6 nicht einschalten
2025-06-02 14:45:08 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 9: zi
2025-06-02 14:45:10 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C99040>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:45:10 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 9 nicht einschalten
2025-06-02 14:45:10 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 10: zi
2025-06-02 14:45:12 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C98D10>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:45:12 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 10 nicht einschalten
2025-06-02 14:45:12 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 11: fee
2025-06-02 14:45:14 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C989E0>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:45:14 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 11 nicht einschalten
2025-06-02 14:45:14 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 12: fee
2025-06-02 14:45:16 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C986B0>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:45:16 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 12 nicht einschalten
2025-06-02 14:45:16 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 13: e2
2025-06-02 14:45:18 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C98380>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:45:18 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 13 nicht einschalten
2025-06-02 14:45:18 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 14: e2
2025-06-02 14:45:20 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C99260>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:45:20 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 14 nicht einschalten
2025-06-02 14:45:20 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 15: test
2025-06-02 14:45:22 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079B020>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:45:22 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 15 nicht einschalten
2025-06-02 14:45:22 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 16: test
2025-06-02 14:45:25 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079A250>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:45:25 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 16 nicht einschalten
2025-06-02 14:45:26 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 7: test
2025-06-02 14:45:28 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079B680>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:45:28 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 7 nicht einschalten
2025-06-02 14:45:28 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 8: test
2025-06-02 14:45:30 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079AF10>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:45:30 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 8 nicht einschalten
2025-06-02 14:45:30 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 1: test
2025-06-02 14:45:32 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0799BF0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:45:32 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 1 nicht einschalten
2025-06-02 14:45:32 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 2: test
2025-06-02 14:45:34 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0717680>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:45:34 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 2 nicht einschalten
2025-06-02 14:45:34 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 3: test
2025-06-02 14:45:36 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C99150>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:45:36 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 3 nicht einschalten
2025-06-02 14:45:36 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 4: test
2025-06-02 14:45:38 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C98270>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:45:38 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 4 nicht einschalten
2025-06-02 14:45:38 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 5: test
2025-06-02 14:45:41 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C985A0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:45:41 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 5 nicht einschalten
2025-06-02 14:45:41 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 6: test
2025-06-02 14:45:43 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C988D0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:45:43 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 6 nicht einschalten
2025-06-02 14:45:43 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 9: zi
2025-06-02 14:45:45 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C98490>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:45:45 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 9 nicht einschalten
2025-06-02 14:45:45 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 10: zi
2025-06-02 14:45:47 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C98E20>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:45:47 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 10 nicht einschalten
2025-06-02 14:45:47 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 11: fee
2025-06-02 14:45:49 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0717680>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:45:49 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 11 nicht einschalten
2025-06-02 14:45:49 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 12: fee
2025-06-02 14:45:51 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079BBD0>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:45:51 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 12 nicht einschalten
2025-06-02 14:45:51 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 13: e2
2025-06-02 14:45:53 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079A690>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:45:53 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 13 nicht einschalten
2025-06-02 14:45:53 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 14: e2
2025-06-02 14:45:55 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0799D00>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:45:55 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 14 nicht einschalten
2025-06-02 14:45:55 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 15: test
2025-06-02 14:45:57 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079B460>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:45:57 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 15 nicht einschalten
2025-06-02 14:45:57 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 16: test
2025-06-02 14:45:59 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079ABE0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:45:59 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 16 nicht einschalten
2025-06-02 14:46:00 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 7: test
2025-06-02 14:46:02 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C94160>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:46:02 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 7 nicht einschalten
2025-06-02 14:46:02 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 8: test
2025-06-02 14:46:04 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C945A0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:46:04 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 8 nicht einschalten
2025-06-02 14:46:04 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 1: test
2025-06-02 14:46:06 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C948D0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:46:06 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 1 nicht einschalten
2025-06-02 14:46:06 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 2: test
2025-06-02 14:46:08 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C94C00>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:46:08 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 2 nicht einschalten
2025-06-02 14:46:08 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 3: test
2025-06-02 14:46:11 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C94F30>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:46:11 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 3 nicht einschalten
2025-06-02 14:46:11 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 4: test
2025-06-02 14:46:13 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C95260>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:46:13 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 4 nicht einschalten
2025-06-02 14:46:13 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 5: test
2025-06-02 14:46:15 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079B020>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:46:15 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 5 nicht einschalten
2025-06-02 14:46:15 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 6: test
2025-06-02 14:46:17 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079ACF0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:46:17 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 6 nicht einschalten
2025-06-02 14:46:17 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 9: zi
2025-06-02 14:46:19 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0799D00>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:46:19 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 9 nicht einschalten
2025-06-02 14:46:19 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 10: zi
2025-06-02 14:46:21 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079BAC0>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:46:21 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 10 nicht einschalten
2025-06-02 14:46:21 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 11: fee
2025-06-02 14:46:23 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079AF10>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:46:23 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 11 nicht einschalten
2025-06-02 14:46:23 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 12: fee
2025-06-02 14:46:25 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0717680>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:46:25 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 12 nicht einschalten
2025-06-02 14:46:25 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 13: e2
2025-06-02 14:46:27 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C94F30>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:46:27 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 13 nicht einschalten
2025-06-02 14:46:27 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 14: e2
2025-06-02 14:46:29 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C94C00>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:46:29 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 14 nicht einschalten
2025-06-02 14:46:29 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 15: test
2025-06-02 14:46:31 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C948D0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:46:31 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 15 nicht einschalten
2025-06-02 14:46:31 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 16: test
2025-06-02 14:46:33 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C945A0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:46:33 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 16 nicht einschalten
2025-06-02 14:46:34 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 7: test
2025-06-02 14:46:36 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C94270>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:46:36 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 7 nicht einschalten
2025-06-02 14:46:36 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 8: test
2025-06-02 14:46:38 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0717680>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:46:38 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 8 nicht einschalten
2025-06-02 14:46:38 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 1: test
2025-06-02 14:46:40 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0799BF0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:46:40 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 1 nicht einschalten
2025-06-02 14:46:40 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 2: test
2025-06-02 14:46:43 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079A690>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:46:43 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 2 nicht einschalten
2025-06-02 14:46:43 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 3: test
2025-06-02 14:46:45 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079AAD0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:46:45 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 3 nicht einschalten
2025-06-02 14:46:45 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 4: test
2025-06-02 14:46:47 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079B680>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:46:47 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 4 nicht einschalten
2025-06-02 14:46:47 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 5: test
2025-06-02 14:46:49 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079A250>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:46:49 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 5 nicht einschalten
2025-06-02 14:46:49 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 6: test
2025-06-02 14:46:51 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C94160>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:46:51 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 6 nicht einschalten
2025-06-02 14:46:51 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 9: zi
2025-06-02 14:46:53 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C94270>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:46:53 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 9 nicht einschalten
2025-06-02 14:46:53 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 10: zi
2025-06-02 14:46:55 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C946B0>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:46:55 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 10 nicht einschalten
2025-06-02 14:46:55 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 11: fee
2025-06-02 14:46:57 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C94AF0>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:46:57 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 11 nicht einschalten
2025-06-02 14:46:57 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 12: fee
2025-06-02 14:46:59 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C94E20>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:46:59 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 12 nicht einschalten
2025-06-02 14:46:59 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 13: e2
2025-06-02 14:47:01 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C95480>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:47:01 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 13 nicht einschalten
2025-06-02 14:47:01 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 14: e2
2025-06-02 14:47:03 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079A250>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:47:03 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 14 nicht einschalten
2025-06-02 14:47:03 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 15: test
2025-06-02 14:47:05 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079ABE0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:47:05 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 15 nicht einschalten
2025-06-02 14:47:05 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 16: test
2025-06-02 14:47:08 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079B460>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:47:08 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 16 nicht einschalten
2025-06-02 14:47:09 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 7: test
2025-06-02 14:47:11 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079A690>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:47:11 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 7 nicht einschalten
2025-06-02 14:47:11 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 8: test
2025-06-02 14:47:13 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079BBD0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:47:13 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 8 nicht einschalten
2025-06-02 14:47:13 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 1: test
2025-06-02 14:47:15 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0717240>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:47:15 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 1 nicht einschalten
2025-06-02 14:47:15 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 2: test
2025-06-02 14:47:17 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C957B0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:47:17 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 2 nicht einschalten
2025-06-02 14:47:17 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 3: test
2025-06-02 14:47:19 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C956A0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:47:19 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 3 nicht einschalten
2025-06-02 14:47:19 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 4: test
2025-06-02 14:47:22 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C94F30>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:47:22 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 4 nicht einschalten
2025-06-02 14:47:22 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 5: test
2025-06-02 14:47:24 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C949E0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:47:24 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 5 nicht einschalten
2025-06-02 14:47:24 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 6: test
2025-06-02 14:47:26 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C948D0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:47:26 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 6 nicht einschalten
2025-06-02 14:47:26 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 9: zi
2025-06-02 14:47:28 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0717680>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:47:28 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 9 nicht einschalten
2025-06-02 14:47:28 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 10: zi
2025-06-02 14:47:30 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079B570>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:47:30 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 10 nicht einschalten
2025-06-02 14:47:30 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 11: fee
2025-06-02 14:47:32 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079AF10>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:47:32 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 11 nicht einschalten
2025-06-02 14:47:32 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 12: fee
2025-06-02 14:47:34 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079BAC0>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:47:34 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 12 nicht einschalten
2025-06-02 14:47:34 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 13: e2
2025-06-02 14:47:36 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0799D00>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:47:36 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 13 nicht einschalten
2025-06-02 14:47:36 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 14: e2
2025-06-02 14:47:38 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079B020>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:47:38 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 14 nicht einschalten
2025-06-02 14:47:38 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 15: test
2025-06-02 14:47:41 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C946B0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:47:41 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 15 nicht einschalten
2025-06-02 14:47:41 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 16: test
2025-06-02 14:47:43 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C94C00>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:47:43 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 16 nicht einschalten
2025-06-02 14:47:44 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 7: test
2025-06-02 14:47:46 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C947C0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:47:46 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 7 nicht einschalten
2025-06-02 14:47:46 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 8: test
2025-06-02 14:47:48 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C95480>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:47:48 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 8 nicht einschalten
2025-06-02 14:47:48 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 1: test
2025-06-02 14:47:50 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C959D0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:47:50 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 1 nicht einschalten
2025-06-02 14:47:50 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 2: test
2025-06-02 14:47:52 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C958C0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:47:52 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 2 nicht einschalten
2025-06-02 14:47:52 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 3: test
2025-06-02 14:47:54 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079B020>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:47:54 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 3 nicht einschalten
2025-06-02 14:47:54 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 4: test
2025-06-02 14:47:56 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079ACF0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:47:56 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 4 nicht einschalten
2025-06-02 14:47:56 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 5: test
2025-06-02 14:47:58 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079B680>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:47:58 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 5 nicht einschalten
2025-06-02 14:47:58 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 6: test
2025-06-02 14:48:01 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079AAD0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:48:01 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 6 nicht einschalten
2025-06-02 14:48:01 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 9: zi
2025-06-02 14:48:03 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079B570>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:48:03 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 9 nicht einschalten
2025-06-02 14:48:03 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 10: zi
2025-06-02 14:48:05 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0717240>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:48:05 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 10 nicht einschalten
2025-06-02 14:48:05 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 11: fee
2025-06-02 14:48:07 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C94050>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:48:07 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 11 nicht einschalten
2025-06-02 14:48:07 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 12: fee
2025-06-02 14:48:09 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C94160>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:48:09 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 12 nicht einschalten
2025-06-02 14:48:09 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 13: e2
2025-06-02 14:48:11 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C957B0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:48:11 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 13 nicht einschalten
2025-06-02 14:48:11 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 14: e2
2025-06-02 14:48:13 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C956A0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:48:13 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 14 nicht einschalten
2025-06-02 14:48:13 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 15: test
2025-06-02 14:48:15 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C94F30>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:48:15 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 15 nicht einschalten
2025-06-02 14:48:15 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 16: test
2025-06-02 14:48:17 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA0C949E0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:48:17 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 16 nicht einschalten
2025-06-02 14:48:18 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 7: test
2025-06-02 14:48:20 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001ECA079A8B0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:48:20 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 7 nicht einschalten
2025-06-02 14:48:20 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 8: test
2025-06-02 14:50:27 - [scheduler] scheduler - [INFO] INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
2025-06-02 14:50:32 - [scheduler] scheduler - [INFO] INFO - Scheduler-Thread gestartet
2025-06-02 14:50:32 - [scheduler] scheduler - [INFO] INFO - Scheduler gestartet
2025-06-02 14:50:32 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 7: test
2025-06-02 14:50:34 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001E8F1A27610>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:50:34 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 7 nicht einschalten
2025-06-02 14:50:34 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 8: test
2025-06-02 14:50:37 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001E8F1EB8510>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:50:37 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 8 nicht einschalten
2025-06-02 14:50:37 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 1: test
2025-06-02 14:50:39 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001E8F1EB9350>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:50:39 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 1 nicht einschalten
2025-06-02 14:50:39 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 2: test
2025-06-02 14:50:41 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001E8F1AA6330>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:50:41 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 2 nicht einschalten
2025-06-02 14:50:41 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 3: test
2025-06-02 14:50:43 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001E8F1A4A7A0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:50:43 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 3 nicht einschalten
2025-06-02 14:50:43 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 4: test
2025-06-02 14:50:45 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001E8F1F5C6B0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:50:45 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 4 nicht einschalten
2025-06-02 14:50:45 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 5: test
2025-06-02 14:50:47 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001E8F1A4B9B0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:50:47 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 5 nicht einschalten
2025-06-02 14:50:47 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 6: test
2025-06-02 14:50:49 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001E8F1A4A690>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:50:49 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 6 nicht einschalten
2025-06-02 14:50:49 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 9: zi
2025-06-02 14:50:51 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001E8F1F5C050>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:50:51 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 9 nicht einschalten
2025-06-02 14:50:51 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 10: zi
2025-06-02 14:51:08 - [scheduler] scheduler - [INFO] INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
2025-06-02 14:51:14 - [scheduler] scheduler - [INFO] INFO - Scheduler-Thread gestartet
2025-06-02 14:51:14 - [scheduler] scheduler - [INFO] INFO - Scheduler gestartet
2025-06-02 14:51:14 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 7: test
2025-06-02 14:51:16 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x0000014034447610>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:51:16 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 7 nicht einschalten
2025-06-02 14:51:16 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 8: test
2025-06-02 14:51:18 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x00000140344E9A70>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:51:18 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 8 nicht einschalten
2025-06-02 14:51:18 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 1: test
2025-06-02 14:51:20 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x00000140344E9940>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:51:20 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 1 nicht einschalten
2025-06-02 14:51:20 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 2: test
2025-06-02 14:51:22 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x00000140344C69F0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:51:22 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 2 nicht einschalten
2025-06-02 14:51:22 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 3: test
2025-06-02 14:51:24 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x0000014034940050>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:51:24 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 3 nicht einschalten
2025-06-02 14:51:24 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 4: test
2025-06-02 14:51:26 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x00000140349409E0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:51:26 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 4 nicht einschalten
2025-06-02 14:51:26 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 5: test
2025-06-02 14:51:28 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x0000014034940AF0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:51:28 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 5 nicht einschalten
2025-06-02 14:51:28 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 6: test
2025-06-02 14:51:30 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001403446B240>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:51:30 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 6 nicht einschalten
2025-06-02 14:51:30 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 9: zi
2025-06-02 14:51:32 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001403446ABE0>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:51:32 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 9 nicht einschalten
2025-06-02 14:51:32 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 10: zi
2025-06-02 14:51:34 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001403446B9B0>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:51:34 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 10 nicht einschalten
2025-06-02 14:51:34 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 11: fee
2025-06-02 14:51:36 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001403446A9C0>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:51:36 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 11 nicht einschalten
2025-06-02 14:51:36 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 12: fee
2025-06-02 14:51:38 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x00000140343E7240>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:51:38 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 12 nicht einschalten
2025-06-02 14:51:38 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 13: e2
2025-06-02 14:51:41 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x00000140349A4270>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:51:41 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 13 nicht einschalten
2025-06-02 14:51:41 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 14: e2
2025-06-02 14:51:43 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x00000140349A4380>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:51:43 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 14 nicht einschalten
2025-06-02 14:51:43 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 15: test
2025-06-02 14:51:45 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x00000140349A48D0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:51:45 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 15 nicht einschalten
2025-06-02 14:51:45 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 16: test
2025-06-02 14:51:47 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x00000140343E7240>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:51:47 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 16 nicht einschalten
2025-06-02 14:51:48 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 7: test
2025-06-02 14:51:50 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001403446BF00>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:51:50 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 7 nicht einschalten
2025-06-02 14:51:50 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 8: test
2025-06-02 14:51:52 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x0000014034469E10>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:51:52 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 8 nicht einschalten
2025-06-02 14:51:52 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 1: test
2025-06-02 14:51:54 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x00000140349A8380>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:51:54 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 1 nicht einschalten
2025-06-02 14:51:54 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 2: test
2025-06-02 14:51:56 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x00000140349A86B0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:51:56 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 2 nicht einschalten
2025-06-02 14:51:56 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 3: test
2025-06-02 14:51:59 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x00000140349A89E0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:51:59 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 3 nicht einschalten
2025-06-02 14:51:59 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 4: test
2025-06-02 14:52:01 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x00000140349A8D10>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:52:01 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 4 nicht einschalten
2025-06-02 14:52:01 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 5: test
2025-06-02 14:52:03 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x00000140349A9040>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:52:03 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 5 nicht einschalten
2025-06-02 14:52:03 - [scheduler] scheduler - [INFO] INFO - 🚀 Starte geplanten Job 6: test
2025-06-02 14:52:05 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x0000014034469E10>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:52:05 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Job 6 nicht einschalten
2025-06-02 14:52:05 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 9: zi
2025-06-02 14:52:07 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x000001403446B680>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:52:07 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 9 nicht einschalten
2025-06-02 14:52:07 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 10: zi
2025-06-02 14:52:09 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x00000140349A9480>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:52:09 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 10 nicht einschalten
2025-06-02 14:52:09 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 11: fee
2025-06-02 14:52:11 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x00000140349A9370>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:52:11 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 11 nicht einschalten
2025-06-02 14:52:11 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 12: fee
2025-06-02 14:52:13 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.104: HTTPConnectionPool(host='192.168.0.104', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x00000140349A8D10>, 'Connection to 192.168.0.104 timed out. (connect timeout=2)'))
2025-06-02 14:52:13 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 12 nicht einschalten
2025-06-02 14:52:13 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 13: e2
2025-06-02 14:52:16 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x00000140349A89E0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:52:16 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 13 nicht einschalten
2025-06-02 14:52:16 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 14: e2
2025-06-02 14:52:18 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x00000140349A86B0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:52:18 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 14 nicht einschalten
2025-06-02 14:52:18 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 15: test
2025-06-02 14:52:20 - [scheduler] scheduler - [ERROR] ERROR - ❌ Fehler beim einschalten der Tapo-Steckdose 192.168.0.103: HTTPConnectionPool(host='192.168.0.103', port=80): Max retries exceeded with url: /app (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x00000140349A85A0>, 'Connection to 192.168.0.103 timed out. (connect timeout=2)'))
2025-06-02 14:52:20 - [scheduler] scheduler - [ERROR] ERROR - ❌ Konnte Steckdose für Sofort-Job 15 nicht einschalten
2025-06-02 14:52:20 - [scheduler] scheduler - [INFO] INFO - ⚡ Starte Sofort-Job 16: test

View File

@ -111,3 +111,5 @@
2025-06-02 14:30:44 - [security] security - [INFO] INFO - 🔒 Security System initialisiert 2025-06-02 14:30:44 - [security] security - [INFO] INFO - 🔒 Security System initialisiert
2025-06-02 14:34:09 - [security] security - [INFO] INFO - 🔒 Security System initialisiert 2025-06-02 14:34:09 - [security] security - [INFO] INFO - 🔒 Security System initialisiert
2025-06-02 14:43:37 - [security] security - [INFO] INFO - 🔒 Security System initialisiert 2025-06-02 14:43:37 - [security] security - [INFO] INFO - 🔒 Security System initialisiert
2025-06-02 14:50:28 - [security] security - [INFO] INFO - 🔒 Security System initialisiert
2025-06-02 14:51:09 - [security] security - [INFO] INFO - 🔒 Security System initialisiert

View File

@ -197,3 +197,5 @@
2025-06-02 14:30:44 - [shutdown_manager] shutdown_manager - [INFO] INFO - 🔧 Shutdown-Manager initialisiert 2025-06-02 14:30:44 - [shutdown_manager] shutdown_manager - [INFO] INFO - 🔧 Shutdown-Manager initialisiert
2025-06-02 14:34:09 - [shutdown_manager] shutdown_manager - [INFO] INFO - 🔧 Shutdown-Manager initialisiert 2025-06-02 14:34:09 - [shutdown_manager] shutdown_manager - [INFO] INFO - 🔧 Shutdown-Manager initialisiert
2025-06-02 14:43:37 - [shutdown_manager] shutdown_manager - [INFO] INFO - 🔧 Shutdown-Manager initialisiert 2025-06-02 14:43:37 - [shutdown_manager] shutdown_manager - [INFO] INFO - 🔧 Shutdown-Manager initialisiert
2025-06-02 14:50:28 - [shutdown_manager] shutdown_manager - [INFO] INFO - 🔧 Shutdown-Manager initialisiert
2025-06-02 14:51:09 - [shutdown_manager] shutdown_manager - [INFO] INFO - 🔧 Shutdown-Manager initialisiert

View File

@ -1007,3 +1007,21 @@
2025-06-02 14:43:37 - [startup] startup - [INFO] INFO - 🪟 Windows-Modus: Aktiviert 2025-06-02 14:43:37 - [startup] startup - [INFO] INFO - 🪟 Windows-Modus: Aktiviert
2025-06-02 14:43:37 - [startup] startup - [INFO] INFO - 🔒 Windows-sichere Log-Rotation: Aktiviert 2025-06-02 14:43:37 - [startup] startup - [INFO] INFO - 🔒 Windows-sichere Log-Rotation: Aktiviert
2025-06-02 14:43:37 - [startup] startup - [INFO] INFO - ================================================== 2025-06-02 14:43:37 - [startup] startup - [INFO] INFO - ==================================================
2025-06-02 14:50:28 - [startup] startup - [INFO] INFO - ==================================================
2025-06-02 14:50:28 - [startup] startup - [INFO] INFO - 🚀 MYP Platform Backend wird gestartet...
2025-06-02 14:50:28 - [startup] startup - [INFO] INFO - 🐍 Python Version: 3.13.3 (tags/v3.13.3:6280bb5, Apr 8 2025, 14:47:33) [MSC v.1943 64 bit (AMD64)]
2025-06-02 14:50:28 - [startup] startup - [INFO] INFO - 💻 Betriebssystem: nt (win32)
2025-06-02 14:50:28 - [startup] startup - [INFO] INFO - 📁 Arbeitsverzeichnis: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend
2025-06-02 14:50:28 - [startup] startup - [INFO] INFO - ⏰ Startzeit: 2025-06-02T14:50:28.305696
2025-06-02 14:50:28 - [startup] startup - [INFO] INFO - 🪟 Windows-Modus: Aktiviert
2025-06-02 14:50:28 - [startup] startup - [INFO] INFO - 🔒 Windows-sichere Log-Rotation: Aktiviert
2025-06-02 14:50:28 - [startup] startup - [INFO] INFO - ==================================================
2025-06-02 14:51:09 - [startup] startup - [INFO] INFO - ==================================================
2025-06-02 14:51:09 - [startup] startup - [INFO] INFO - 🚀 MYP Platform Backend wird gestartet...
2025-06-02 14:51:09 - [startup] startup - [INFO] INFO - 🐍 Python Version: 3.13.3 (tags/v3.13.3:6280bb5, Apr 8 2025, 14:47:33) [MSC v.1943 64 bit (AMD64)]
2025-06-02 14:51:09 - [startup] startup - [INFO] INFO - 💻 Betriebssystem: nt (win32)
2025-06-02 14:51:09 - [startup] startup - [INFO] INFO - 📁 Arbeitsverzeichnis: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend
2025-06-02 14:51:09 - [startup] startup - [INFO] INFO - ⏰ Startzeit: 2025-06-02T14:51:09.300938
2025-06-02 14:51:09 - [startup] startup - [INFO] INFO - 🪟 Windows-Modus: Aktiviert
2025-06-02 14:51:09 - [startup] startup - [INFO] INFO - 🔒 Windows-sichere Log-Rotation: Aktiviert
2025-06-02 14:51:09 - [startup] startup - [INFO] INFO - ==================================================

View File

@ -479,3 +479,11 @@
2025-06-02 14:43:36 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Subprocess automatisch gepatcht für UTF-8 Encoding (run + Popen) 2025-06-02 14:43:36 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Subprocess automatisch gepatcht für UTF-8 Encoding (run + Popen)
2025-06-02 14:43:36 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Globaler subprocess-Patch angewendet 2025-06-02 14:43:36 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Globaler subprocess-Patch angewendet
2025-06-02 14:43:36 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Alle Windows-Fixes erfolgreich angewendet 2025-06-02 14:43:36 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Alle Windows-Fixes erfolgreich angewendet
2025-06-02 14:50:27 - [windows_fixes] windows_fixes - [INFO] INFO - 🔧 Wende Windows-spezifische Fixes an...
2025-06-02 14:50:27 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Subprocess automatisch gepatcht für UTF-8 Encoding (run + Popen)
2025-06-02 14:50:27 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Globaler subprocess-Patch angewendet
2025-06-02 14:50:27 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Alle Windows-Fixes erfolgreich angewendet
2025-06-02 14:51:08 - [windows_fixes] windows_fixes - [INFO] INFO - 🔧 Wende Windows-spezifische Fixes an...
2025-06-02 14:51:08 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Subprocess automatisch gepatcht für UTF-8 Encoding (run + Popen)
2025-06-02 14:51:08 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Globaler subprocess-Patch angewendet
2025-06-02 14:51:08 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Alle Windows-Fixes erfolgreich angewendet

File diff suppressed because one or more lines are too long

View File

@ -1,9 +1,11 @@
[Unit] [Unit]
Description=MYP Kiosk Browser Autostart (Chromium HTTPS) Description=MYP Kiosk Browser Autostart (Chromium HTTPS) - Wartungsfreier Produktionsbetrieb
Documentation=https://github.com/MYP-Druckerverwaltung Documentation=https://github.com/MYP-Druckerverwaltung
After=graphical-session.target myp-https.service After=graphical-session.target myp-https.service network-online.target
Wants=myp-https.service Wants=myp-https.service network-online.target
Requires=graphical-session.target Requires=graphical-session.target
StartLimitBurst=5
StartLimitInterval=600
[Service] [Service]
Type=simple Type=simple
@ -11,45 +13,118 @@ User=kiosk
Group=kiosk Group=kiosk
Environment=DISPLAY=:0 Environment=DISPLAY=:0
Environment=XAUTHORITY=/home/kiosk/.Xauthority Environment=XAUTHORITY=/home/kiosk/.Xauthority
Environment=HOME=/home/kiosk
Environment=XDG_RUNTIME_DIR=/run/user/1001
Environment=WAYLAND_DISPLAY=
Environment=GDK_BACKEND=x11
WorkingDirectory=/home/kiosk WorkingDirectory=/home/kiosk
# Warte auf HTTPS-Backend und starte dann Chromium # Robuste Backend-Wartung mit verbesserter Fehlererkennung
ExecStartPre=/bin/bash -c 'echo "Warte auf HTTPS Backend..."; for i in {1..60}; do if curl -k -s https://localhost:443 >/dev/null 2>&1; then echo "HTTPS Backend erreichbar"; break; fi; echo "Warte... ($i/60)"; sleep 2; done' ExecStartPre=/bin/bash -c '\
echo "=== MYP Kiosk-Service startet $(date) ==="; \
\
# Prüfe ob X11 läuft \
for i in {1..30}; do \
if DISPLAY=:0 xset q >/dev/null 2>&1; then \
echo " X11 Display verfügbar"; \
break; \
fi; \
echo " Warte auf X11 Display... ($i/30)"; \
sleep 2; \
done; \
\
# Warte auf HTTPS-Backend mit verbesserter Erkennung \
echo "🔍 Warte auf HTTPS Backend..."; \
for i in {1..120}; do \
if curl -k -s --connect-timeout 3 --max-time 5 https://localhost:443/api/kiosk/status >/dev/null 2>&1; then \
echo " HTTPS Backend erreichbar und API verfügbar"; \
break; \
elif curl -k -s --connect-timeout 3 --max-time 5 https://localhost:443 >/dev/null 2>&1; then \
echo " HTTPS Backend erreichbar"; \
break; \
fi; \
echo " Warte auf Backend... ($i/120)"; \
sleep 3; \
done; \
\
# Räume alte Browser-Prozesse auf \
pkill -f "chromium.*kiosk" 2>/dev/null || true; \
pkill -f "firefox.*kiosk" 2>/dev/null || true; \
sleep 2; \
'
# Robuster Kiosk-Start mit Fehlerresilienz
ExecStart=/bin/bash -c '\ ExecStart=/bin/bash -c '\
# Bildschirmauflösung ermitteln \ set -e; \
RESOLUTION=$(DISPLAY=:0 xrandr 2>/dev/null | grep "*" | head -1 | awk "{print \$1}" || echo "1920x1080"); \ \
# Logging-Setup \
LOG_FILE="/var/log/myp-kiosk.log"; \
exec 1> >(tee -a "$LOG_FILE"); \
exec 2>&1; \
\
echo "🚀 Starte Kiosk-Modus $(date)"; \
\
# Bildschirmauflösung robust ermitteln \
RESOLUTION=$(DISPLAY=:0 xrandr 2>/dev/null | grep -E "\*|\+" | head -1 | awk "{print \$1}" || echo "1920x1080"); \
WIDTH=$(echo $RESOLUTION | cut -d"x" -f1); \ WIDTH=$(echo $RESOLUTION | cut -d"x" -f1); \
HEIGHT=$(echo $RESOLUTION | cut -d"x" -f2); \ HEIGHT=$(echo $RESOLUTION | cut -d"x" -f2); \
echo "Erkannte Auflösung: ${WIDTH}x${HEIGHT}"; \ echo "📺 Bildschirmauflösung: ${WIDTH}x${HEIGHT}"; \
\ \
# Bildschirmschoner deaktivieren \ # Display-Konfiguration optimieren \
DISPLAY=:0 xset s off; \ DISPLAY=:0 xset s off 2>/dev/null || true; \
DISPLAY=:0 xset s noblank; \ DISPLAY=:0 xset s noblank 2>/dev/null || true; \
DISPLAY=:0 xset -dpms; \ DISPLAY=:0 xset -dpms 2>/dev/null || true; \
DISPLAY=:0 xset r rate 250 30 2>/dev/null || true; \
echo " Display-Energieverwaltung deaktiviert"; \
\ \
# Mauszeiger verstecken \ # Mauszeiger verstecken \
DISPLAY=:0 unclutter -idle 0.1 -root -noevents & \ if command -v unclutter >/dev/null 2>&1; then \
DISPLAY=:0 unclutter -idle 0.5 -root -noevents & \
echo "🖱 Mauszeiger-Versteckung aktiviert"; \
fi; \
\
# Browser-Auswahl mit Prioritäten \
BROWSER=""; \
BROWSER_ARGS=""; \
\ \
# Chromium Kiosk-Modus starten \
if command -v chromium >/dev/null 2>&1; then \ if command -v chromium >/dev/null 2>&1; then \
BROWSER="chromium"; \ BROWSER="chromium"; \
elif command -v chromium-browser >/dev/null 2>&1; then \ elif command -v chromium-browser >/dev/null 2>&1; then \
BROWSER="chromium-browser"; \ BROWSER="chromium-browser"; \
else \ elif command -v google-chrome >/dev/null 2>&1; then \
echo "Kein Chromium gefunden - verwende Firefox"; \ BROWSER="google-chrome"; \
elif command -v firefox-esr >/dev/null 2>&1; then \
BROWSER="firefox-esr"; \ BROWSER="firefox-esr"; \
elif command -v firefox >/dev/null 2>&1; then \
BROWSER="firefox"; \
else \
echo " Kein unterstützter Browser gefunden"; \
exit 1; \
fi; \ fi; \
\ \
echo "Starte $BROWSER im Kiosk-Modus..."; \ echo "🌐 Verwende Browser: $BROWSER"; \
\ \
if [[ "$BROWSER" == "chromium"* ]]; then \ # Browser-spezifische Argumente \
exec $BROWSER \ if [[ "$BROWSER" == "chromium"* ]] || [[ "$BROWSER" == "google-chrome"* ]]; then \
BROWSER_ARGS=" \
--kiosk \ --kiosk \
--no-sandbox \ --no-sandbox \
--disable-dev-shm-usage \
--disable-gpu-sandbox \
--disable-software-rasterizer \
--disable-background-timer-throttling \
--disable-backgrounding-occluded-windows \
--disable-renderer-backgrounding \
--disable-field-trial-config \
--disable-features=TranslateUI,VizDisplayCompositor,AudioServiceOutOfProcess \
--enable-features=OverlayScrollbar,VaapiVideoDecoder \
--force-device-scale-factor=1.0 \
--window-size=${WIDTH},${HEIGHT} \
--window-position=0,0 \
--user-data-dir=/home/kiosk/.chromium-kiosk \
--disable-infobars \ --disable-infobars \
--disable-session-crashed-bubble \ --disable-session-crashed-bubble \
--disable-restore-session-state \ --disable-restore-session-state \
--disable-features=TranslateUI \
--disable-extensions \ --disable-extensions \
--disable-plugins \ --disable-plugins \
--disable-popup-blocking \ --disable-popup-blocking \
@ -59,24 +134,15 @@ ExecStart=/bin/bash -c '\
--noerrdialogs \ --noerrdialogs \
--no-first-run \ --no-first-run \
--no-default-browser-check \ --no-default-browser-check \
--no-crash-upload \
--disable-crash-reporter \
--disable-logging \
--autoplay-policy=no-user-gesture-required \ --autoplay-policy=no-user-gesture-required \
--start-fullscreen \
--start-maximized \
--window-size=${WIDTH},${HEIGHT} \
--window-position=0,0 \
--user-data-dir=/home/kiosk/.chromium-kiosk \
--disable-background-mode \ --disable-background-mode \
--force-device-scale-factor=1.0 \
--disable-pinch \ --disable-pinch \
--overscroll-history-navigation=0 \ --overscroll-history-navigation=0 \
--disable-dev-shm-usage \
--memory-pressure-off \ --memory-pressure-off \
--max_old_space_size=512 \ --max_old_space_size=512 \
--disable-background-timer-throttling \
--disable-backgrounding-occluded-windows \
--disable-renderer-backgrounding \
--disable-features=VizDisplayCompositor \
--enable-features=OverlayScrollbar \
--hide-scrollbars \ --hide-scrollbars \
--ignore-certificate-errors \ --ignore-certificate-errors \
--ignore-ssl-errors \ --ignore-ssl-errors \
@ -84,24 +150,77 @@ ExecStart=/bin/bash -c '\
--disable-web-security \ --disable-web-security \
--allow-running-insecure-content \ --allow-running-insecure-content \
--unsafely-treat-insecure-origin-as-secure=https://localhost:443 \ --unsafely-treat-insecure-origin-as-secure=https://localhost:443 \
https://localhost:443; \ --disable-blink-features=AutomationControlled \
--disable-ipc-flooding-protection"; \
else \ else \
exec firefox-esr \ # Firefox-Argumente \
BROWSER_ARGS=" \
--kiosk \ --kiosk \
--width=${WIDTH} \ --width=${WIDTH} \
--height=${HEIGHT} \ --height=${HEIGHT} \
https://localhost:443; \ --no-remote \
fi' --new-instance"; \
fi; \
\
# URL mit Fallback \
TARGET_URL="https://localhost:443"; \
\
# Browser starten mit Fehlerbehandlung \
echo "🖥 Starte $BROWSER im Kiosk-Modus..."; \
echo "🔗 URL: $TARGET_URL"; \
\
# Umgebungsvariablen setzen \
export DISPLAY=:0; \
export HOME=/home/kiosk; \
export XDG_RUNTIME_DIR=/run/user/1001; \
export LIBGL_ALWAYS_SOFTWARE=1; \
export MOZ_DISABLE_RDD_SANDBOX=1; \
export MOZ_DISABLE_CONTENT_SANDBOX=1; \
\
# Browser-Start mit exec für korrekte Signal-Behandlung \
exec $BROWSER $BROWSER_ARGS "$TARGET_URL" 2>&1; \
'
# Robuste Restart-Konfiguration für wartungsfreien Betrieb
Restart=always Restart=always
RestartSec=15 RestartSec=15
StartLimitBurst=3 StartLimitBurst=5
StartLimitInterval=300 StartLimitInterval=600
TimeoutStartSec=300
TimeoutStopSec=30
KillMode=mixed
KillSignal=SIGTERM
# Logging # Ressourcen-Management für Stabilität
LimitNOFILE=65536
LimitNPROC=4096
MemoryHigh=1G
MemoryMax=1.5G
CPUQuota=80%
# Erweiterte Service-Überwachung
WatchdogSec=60
NotifyAccess=all
# Fehlerresilienz-Features
PrivateNetwork=false
PrivateTmp=true
ProtectHome=false
ProtectSystem=strict
ReadWritePaths=/home/kiosk /var/log /tmp
NoNewPrivileges=false
# Logging-Konfiguration
StandardOutput=journal StandardOutput=journal
StandardError=journal StandardError=journal
SyslogIdentifier=myp-kiosk SyslogIdentifier=myp-kiosk
LogRateLimitBurst=1000
LogRateLimitIntervalSec=30
# Service-Abhängigkeiten für robuste Startsequenz
Requisite=myp-https.service
BindsTo=graphical-session.target
[Install] [Install]
WantedBy=graphical-session.target WantedBy=graphical-session.target
Also=myp-https.service

View File

@ -524,6 +524,36 @@ document.addEventListener('DOMContentLoaded', function() {
</div> </div>
</div> </div>
<div class="bg-white/60 dark:bg-slate-700/60 backdrop-blur-sm rounded-xl border border-slate-200 dark:border-slate-600 p-6 shadow-lg">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white mb-4">System-Steuerung</h3>
<div class="space-y-3">
<button id="kiosk-restart-btn" class="w-full px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors text-sm font-medium flex items-center justify-center space-x-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
</svg>
<span>Kiosk neustarten</span>
</button>
<button id="restart-system-btn" class="w-full px-4 py-2 bg-orange-500 text-white rounded-lg hover:bg-orange-600 transition-colors text-sm font-medium flex items-center justify-center space-x-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
</svg>
<span>System neustarten</span>
</button>
<button id="shutdown-system-btn" class="w-full px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors text-sm font-medium flex items-center justify-center space-x-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728L5.636 5.636m12.728 12.728L18.364 5.636M5.636 18.364l12.728-12.728"/>
</svg>
<span>System herunterfahren</span>
</button>
<button id="system-status-btn" class="w-full px-4 py-2 bg-slate-500 text-white rounded-lg hover:bg-slate-600 transition-colors text-sm font-medium flex items-center justify-center space-x-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
</svg>
<span>System-Status</span>
</button>
</div>
</div>
<div class="bg-white/60 dark:bg-slate-700/60 backdrop-blur-sm rounded-xl border border-slate-200 dark:border-slate-600 p-6 shadow-lg"> <div class="bg-white/60 dark:bg-slate-700/60 backdrop-blur-sm rounded-xl border border-slate-200 dark:border-slate-600 p-6 shadow-lg">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white mb-4">Konfiguration</h3> <h3 class="text-lg font-semibold text-slate-900 dark:text-white mb-4">Konfiguration</h3>
<div class="space-y-3"> <div class="space-y-3">
@ -533,8 +563,11 @@ document.addEventListener('DOMContentLoaded', function() {
<button id="update-printers-btn" class="w-full px-4 py-2 bg-indigo-500 text-white rounded-lg hover:bg-indigo-600 transition-colors text-sm font-medium"> <button id="update-printers-btn" class="w-full px-4 py-2 bg-indigo-500 text-white rounded-lg hover:bg-indigo-600 transition-colors text-sm font-medium">
Drucker aktualisieren Drucker aktualisieren
</button> </button>
<button id="restart-system-btn" class="w-full px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors text-sm font-medium"> <button id="error-recovery-toggle-btn" class="w-full px-4 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors text-sm font-medium flex items-center justify-center space-x-2">
System neustarten <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<span>Fehlerresilienz</span>
</button> </button>
</div> </div>
</div> </div>
@ -976,5 +1009,532 @@ function getCsrfToken() {
const token = document.querySelector('meta[name="csrf-token"]'); const token = document.querySelector('meta[name="csrf-token"]');
return token ? token.getAttribute('content') : ''; return token ? token.getAttribute('content') : '';
} }
// ===== ERWEITERTE SYSTEM-CONTROL-FUNKTIONALITÄT =====
class SystemControlManager {
constructor() {
this.pendingOperations = new Map();
this.lastStatusUpdate = null;
this.isInitialized = false;
this.initializeEventListeners();
this.startStatusPolling();
this.isInitialized = true;
console.log('🔧 System-Control-Manager initialisiert');
}
initializeEventListeners() {
// Kiosk-Neustart
const kioskRestartBtn = document.getElementById('kiosk-restart-btn');
if (kioskRestartBtn) {
kioskRestartBtn.addEventListener('click', (e) => {
e.preventDefault();
this.handleKioskRestart();
});
}
// System-Neustart
const restartBtn = document.getElementById('restart-system-btn');
if (restartBtn) {
restartBtn.addEventListener('click', (e) => {
e.preventDefault();
this.handleSystemRestart();
});
}
// System-Shutdown
const shutdownBtn = document.getElementById('shutdown-system-btn');
if (shutdownBtn) {
shutdownBtn.addEventListener('click', (e) => {
e.preventDefault();
this.handleSystemShutdown();
});
}
// System-Status
const statusBtn = document.getElementById('system-status-btn');
if (statusBtn) {
statusBtn.addEventListener('click', (e) => {
e.preventDefault();
this.showSystemStatus();
});
}
// Error-Recovery Toggle
const errorRecoveryBtn = document.getElementById('error-recovery-toggle-btn');
if (errorRecoveryBtn) {
errorRecoveryBtn.addEventListener('click', (e) => {
e.preventDefault();
this.toggleErrorRecovery();
});
}
}
async handleKioskRestart() {
const confirmed = await this.confirmOperation(
'Kiosk-Neustart',
'Möchten Sie das Kiosk-Display neustarten? Dies dauert ca. 10-30 Sekunden.',
'Der Kiosk wird neugestartet...'
);
if (!confirmed) return;
try {
const response = await fetch('/api/admin/kiosk/restart', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCsrfToken()
},
body: JSON.stringify({
delay_seconds: 10,
reason: 'Manueller Kiosk-Neustart über Admin-Panel'
})
});
const result = await response.json();
if (response.ok && result.success) {
showNotification('Kiosk-Neustart geplant', 'success');
this.trackOperation(result.operation_id, 'kiosk_restart');
} else {
showNotification(result.error || 'Fehler beim Kiosk-Neustart', 'error');
}
} catch (error) {
console.error('Kiosk-Neustart Fehler:', error);
showNotification('Fehler beim Kiosk-Neustart: ' + error.message, 'error');
}
}
async handleSystemRestart() {
const confirmed = await this.confirmOperation(
'System-Neustart',
'WARNUNG: Das gesamte System wird neu gestartet! Dies dauert ca. 2-5 Minuten.',
'Das System wird neu gestartet...',
true
);
if (!confirmed) return;
// Zusätzliche Verzögerungsabfrage
const delayInput = prompt('Verzögerung in Sekunden (10-3600):', '60');
if (!delayInput) return;
const delay = Math.max(10, Math.min(3600, parseInt(delayInput) || 60));
try {
const response = await fetch('/api/admin/system/restart', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCsrfToken()
},
body: JSON.stringify({
delay_seconds: delay,
reason: 'Manueller System-Neustart über Admin-Panel'
})
});
const result = await response.json();
if (response.ok && result.success) {
showNotification(`System-Neustart in ${delay} Sekunden geplant`, 'success');
this.trackOperation(result.operation_id, 'system_restart');
this.showCountdown(delay, 'System-Neustart');
} else {
showNotification(result.error || 'Fehler beim System-Neustart', 'error');
}
} catch (error) {
console.error('System-Neustart Fehler:', error);
showNotification('Fehler beim System-Neustart: ' + error.message, 'error');
}
}
async handleSystemShutdown() {
const confirmed = await this.confirmOperation(
'System-Shutdown',
'KRITISCHE WARNUNG: Das System wird komplett heruntergefahren!',
'Das System wird heruntergefahren...',
true
);
if (!confirmed) return;
// Doppelte Bestätigung
const doubleConfirm = confirm('LETZTE WARNUNG: System wirklich herunterfahren?\nDas System muss danach manuell neu gestartet werden!');
if (!doubleConfirm) return;
// Verzögerungsabfrage
const delayInput = prompt('Verzögerung in Sekunden (10-3600):', '30');
if (!delayInput) return;
const delay = Math.max(10, Math.min(3600, parseInt(delayInput) || 30));
try {
const response = await fetch('/api/admin/system/shutdown', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCsrfToken()
},
body: JSON.stringify({
delay_seconds: delay,
reason: 'Manueller System-Shutdown über Admin-Panel'
})
});
const result = await response.json();
if (response.ok && result.success) {
showNotification(`System-Shutdown in ${delay} Sekunden geplant`, 'error');
this.trackOperation(result.operation_id, 'system_shutdown');
this.showCountdown(delay, 'System-Shutdown');
} else {
showNotification(result.error || 'Fehler beim System-Shutdown', 'error');
}
} catch (error) {
console.error('System-Shutdown Fehler:', error);
showNotification('Fehler beim System-Shutdown: ' + error.message, 'error');
}
}
async showSystemStatus() {
try {
showNotification('System-Status wird geladen...', 'info');
const response = await fetch('/api/admin/system/status', {
method: 'GET',
headers: {
'X-CSRFToken': getCsrfToken()
}
});
const status = await response.json();
if (response.ok && status.success) {
this.displaySystemStatusModal(status);
} else {
showNotification('Fehler beim Laden des System-Status', 'error');
}
} catch (error) {
console.error('System-Status Fehler:', error);
showNotification('Fehler beim System-Status: ' + error.message, 'error');
}
}
async toggleErrorRecovery() {
try {
// Aktuellen Status abrufen
const statusResponse = await fetch('/api/admin/error-recovery/status', {
method: 'GET',
headers: {
'X-CSRFToken': getCsrfToken()
}
});
const statusData = await statusResponse.json();
const currentlyActive = statusData?.statistics?.monitoring_active || false;
// Toggle-Operation
const response = await fetch('/api/admin/error-recovery/toggle', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCsrfToken()
},
body: JSON.stringify({
enable: !currentlyActive
})
});
const result = await response.json();
if (response.ok && result.success) {
showNotification(result.message, 'success');
this.updateErrorRecoveryButton(result.monitoring_active);
} else {
showNotification(result.error || 'Fehler beim Error-Recovery Toggle', 'error');
}
} catch (error) {
console.error('Error-Recovery Toggle Fehler:', error);
showNotification('Fehler beim Error-Recovery Toggle: ' + error.message, 'error');
}
}
updateErrorRecoveryButton(isActive) {
const btn = document.getElementById('error-recovery-toggle-btn');
if (btn) {
if (isActive) {
btn.classList.remove('bg-green-500', 'hover:bg-green-600');
btn.classList.add('bg-yellow-500', 'hover:bg-yellow-600');
btn.querySelector('span').textContent = 'Fehlerresilienz AN';
} else {
btn.classList.remove('bg-yellow-500', 'hover:bg-yellow-600');
btn.classList.add('bg-green-500', 'hover:bg-green-600');
btn.querySelector('span').textContent = 'Fehlerresilienz AUS';
}
}
}
async confirmOperation(title, message, processingMessage, isDestructive = false) {
const bgColor = isDestructive ? 'bg-red-100 dark:bg-red-900' : 'bg-blue-100 dark:bg-blue-900';
const iconColor = isDestructive ? 'text-red-600 dark:text-red-400' : 'text-blue-600 dark:text-blue-400';
return new Promise((resolve) => {
const modal = document.createElement('div');
modal.className = 'fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center p-4';
modal.innerHTML = `
<div class="bg-white dark:bg-slate-800 rounded-2xl p-6 shadow-2xl max-w-md w-full">
<div class="text-center mb-6">
<div class="mx-auto flex items-center justify-center h-12 w-12 rounded-full ${bgColor} mb-4">
<svg class="h-6 w-6 ${iconColor}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 15.5c-.77.833.192 2.5 1.732 2.5z"/>
</svg>
</div>
<h3 class="text-lg font-medium text-slate-900 dark:text-white mb-2">${title}</h3>
<p class="text-sm text-slate-500 dark:text-slate-400">${message}</p>
</div>
<div class="flex space-x-3">
<button id="modal-cancel" class="flex-1 px-4 py-2 bg-slate-200 dark:bg-slate-700 text-slate-700 dark:text-slate-300 rounded-xl hover:bg-slate-300 dark:hover:bg-slate-600 transition-colors">
Abbrechen
</button>
<button id="modal-confirm" class="flex-1 px-4 py-2 ${isDestructive ? 'bg-red-500 hover:bg-red-600' : 'bg-blue-500 hover:bg-blue-600'} text-white rounded-xl transition-colors">
Bestätigen
</button>
</div>
</div>
`;
document.body.appendChild(modal);
const cancelBtn = modal.querySelector('#modal-cancel');
const confirmBtn = modal.querySelector('#modal-confirm');
cancelBtn.addEventListener('click', () => {
document.body.removeChild(modal);
resolve(false);
});
confirmBtn.addEventListener('click', () => {
document.body.removeChild(modal);
resolve(true);
});
// ESC-Key
const handleEsc = (e) => {
if (e.key === 'Escape') {
document.body.removeChild(modal);
document.removeEventListener('keydown', handleEsc);
resolve(false);
}
};
document.addEventListener('keydown', handleEsc);
});
}
showCountdown(seconds, operationType) {
const modal = document.createElement('div');
modal.className = 'fixed inset-0 bg-black/75 backdrop-blur-sm z-50 flex items-center justify-center p-4';
modal.innerHTML = `
<div class="bg-white dark:bg-slate-800 rounded-2xl p-8 shadow-2xl max-w-sm w-full text-center">
<div class="mx-auto flex items-center justify-center h-16 w-16 rounded-full bg-orange-100 dark:bg-orange-900 mb-6">
<svg class="h-8 w-8 text-orange-600 dark:text-orange-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</div>
<h3 class="text-xl font-bold text-slate-900 dark:text-white mb-2">${operationType}</h3>
<p class="text-slate-600 dark:text-slate-400 mb-6">Wird ausgeführt in:</p>
<div id="countdown-timer" class="text-4xl font-bold text-orange-600 dark:text-orange-400 mb-6">${seconds}</div>
<button id="countdown-cancel" class="px-6 py-2 bg-red-500 text-white rounded-xl hover:bg-red-600 transition-colors">
Abbrechen
</button>
</div>
`;
document.body.appendChild(modal);
const timerEl = modal.querySelector('#countdown-timer');
const cancelBtn = modal.querySelector('#countdown-cancel');
let remainingSeconds = seconds;
const interval = setInterval(() => {
remainingSeconds--;
timerEl.textContent = remainingSeconds;
if (remainingSeconds <= 0) {
clearInterval(interval);
document.body.removeChild(modal);
}
}, 1000);
cancelBtn.addEventListener('click', async () => {
clearInterval(interval);
document.body.removeChild(modal);
// Versuche Operation zu stornieren
try {
// Hier könnte der API-Call zum Stornieren implementiert werden
showNotification('Operation wird storniert...', 'info');
} catch (error) {
console.error('Fehler beim Stornieren:', error);
}
});
}
displaySystemStatusModal(status) {
const modal = document.createElement('div');
modal.className = 'fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center p-4';
const servicesHtml = Object.entries(status.services || {}).map(([name, state]) => {
const isActive = state === 'active';
return `
<div class="flex items-center justify-between py-2 px-3 rounded-lg ${isActive ? 'bg-green-50 dark:bg-green-900/20' : 'bg-red-50 dark:bg-red-900/20'}">
<span class="font-medium">${name}</span>
<span class="px-2 py-1 rounded-full text-xs ${isActive ? 'bg-green-100 text-green-800 dark:bg-green-800 dark:text-green-100' : 'bg-red-100 text-red-800 dark:bg-red-800 dark:text-red-100'}">${state}</span>
</div>
`;
}).join('');
const metrics = status.system_metrics || {};
const errorRecovery = status.error_recovery || {};
modal.innerHTML = `
<div class="bg-white dark:bg-slate-800 rounded-2xl p-6 shadow-2xl max-w-2xl w-full max-h-90vh overflow-y-auto">
<div class="flex items-center justify-between mb-6">
<h3 class="text-xl font-bold text-slate-900 dark:text-white">System-Status</h3>
<button id="close-status-modal" class="text-slate-400 hover:text-slate-600 dark:hover:text-slate-300">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Services -->
<div>
<h4 class="font-semibold text-slate-900 dark:text-white mb-3">Services</h4>
<div class="space-y-2">
${servicesHtml}
</div>
</div>
<!-- System-Metriken -->
<div>
<h4 class="font-semibold text-slate-900 dark:text-white mb-3">System-Metriken</h4>
<div class="space-y-2">
<div class="flex justify-between">
<span>Speicher:</span>
<span class="font-medium ${metrics.memory_percent > 80 ? 'text-red-600' : 'text-green-600'}">${metrics.memory_percent?.toFixed(1) || 0}%</span>
</div>
<div class="flex justify-between">
<span>Festplatte:</span>
<span class="font-medium ${metrics.disk_percent > 90 ? 'text-red-600' : 'text-green-600'}">${metrics.disk_percent?.toFixed(1) || 0}%</span>
</div>
<div class="flex justify-between">
<span>System-Last:</span>
<span class="font-medium">${metrics.load_average?.toFixed(2) || 0}</span>
</div>
</div>
</div>
<!-- Fehlerresilienz -->
<div class="md:col-span-2">
<h4 class="font-semibold text-slate-900 dark:text-white mb-3">Fehlerresilienz</h4>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
<div class="text-center p-3 bg-slate-50 dark:bg-slate-700 rounded-lg">
<div class="text-2xl font-bold text-blue-600">${errorRecovery.total_errors || 0}</div>
<div class="text-xs text-slate-600 dark:text-slate-400">Gesamt-Fehler</div>
</div>
<div class="text-center p-3 bg-slate-50 dark:bg-slate-700 rounded-lg">
<div class="text-2xl font-bold text-orange-600">${errorRecovery.errors_last_24h || 0}</div>
<div class="text-xs text-slate-600 dark:text-slate-400">Letzten 24h</div>
</div>
<div class="text-center p-3 bg-slate-50 dark:bg-slate-700 rounded-lg">
<div class="text-2xl font-bold text-green-600">${errorRecovery.recovery_success_rate || 0}%</div>
<div class="text-xs text-slate-600 dark:text-slate-400">Erfolgsrate</div>
</div>
<div class="text-center p-3 bg-slate-50 dark:bg-slate-700 rounded-lg">
<div class="text-2xl font-bold ${status.is_safe ? 'text-green-600' : 'text-red-600'}">${status.is_safe ? 'SICHER' : 'UNSICHER'}</div>
<div class="text-xs text-slate-600 dark:text-slate-400">Status</div>
</div>
</div>
</div>
</div>
<div class="mt-6 pt-4 border-t border-slate-200 dark:border-slate-600">
<div class="text-sm text-slate-500 dark:text-slate-400">
Letztes Update: ${new Date().toLocaleString('de-DE')}
</div>
</div>
</div>
`;
document.body.appendChild(modal);
const closeBtn = modal.querySelector('#close-status-modal');
closeBtn.addEventListener('click', () => {
document.body.removeChild(modal);
});
// ESC zum Schließen
const handleEsc = (e) => {
if (e.key === 'Escape') {
document.body.removeChild(modal);
document.removeEventListener('keydown', handleEsc);
}
};
document.addEventListener('keydown', handleEsc);
}
trackOperation(operationId, type) {
this.pendingOperations.set(operationId, {
id: operationId,
type: type,
timestamp: Date.now()
});
}
async startStatusPolling() {
// Überwache Error-Recovery-Status
setInterval(async () => {
try {
const response = await fetch('/api/admin/error-recovery/status', {
method: 'GET',
headers: {
'X-CSRFToken': getCsrfToken()
}
});
if (response.ok) {
const data = await response.json();
this.updateErrorRecoveryButton(data?.statistics?.monitoring_active || false);
}
} catch (error) {
console.debug('Status-Polling Fehler:', error);
}
}, 30000); // Alle 30 Sekunden
}
}
// Globaler System-Control-Manager
let systemControlManager = null;
// Initialisierung beim DOM-Laden
document.addEventListener('DOMContentLoaded', function() {
if (!systemControlManager) {
systemControlManager = new SystemControlManager();
}
});
</script> </script>
{% endblock %} {% endblock %}

View File

@ -13,207 +13,124 @@
<style> <style>
.calendar-container { .calendar-container {
@apply bg-white dark:bg-gray-800 rounded-xl shadow-lg border border-gray-200 dark:border-gray-700 p-6 mb-6 transition-all duration-300; background: white;
border-radius: 12px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
padding: 20px;
margin-bottom: 20px;
} }
.fc-event { .fc-event {
font-size: 11px; font-size: 11px;
border-radius: 6px; border-radius: 4px;
padding: 2px 6px; padding: 2px 4px;
border: none !important;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
} }
.fc-event-title { .fc-event-title {
font-weight: 500; font-weight: 500;
} }
/* FullCalendar Dark Mode */
.dark .fc-theme-standard td,
.dark .fc-theme-standard th {
border-color: #374151;
}
.dark .fc-theme-standard .fc-scrollgrid {
border-color: #374151;
}
.dark .fc-col-header-cell {
background: #1f2937;
color: #e5e7eb;
}
.dark .fc-daygrid-day {
background: #1f2937;
color: #e5e7eb;
}
.dark .fc-day-today {
background-color: #065f46 !important;
}
.dark .fc-button-primary {
background: #3b82f6;
border-color: #3b82f6;
}
.dark .fc-button-primary:hover {
background: #2563eb;
border-color: #2563eb;
}
.dark .fc-toolbar-title {
color: #e5e7eb;
}
.stats-card { .stats-card {
@apply bg-gradient-to-br from-blue-500 via-blue-600 to-indigo-700 dark:from-blue-600 dark:via-blue-700 dark:to-indigo-800 rounded-xl p-6 text-white shadow-lg border border-blue-200 dark:border-blue-800 transition-all duration-300 hover:shadow-xl hover:scale-105; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
} border-radius: 12px;
padding: 20px;
.stats-card-success { color: white;
@apply bg-gradient-to-br from-emerald-500 via-emerald-600 to-green-700 dark:from-emerald-600 dark:via-emerald-700 dark:to-green-800; margin-bottom: 20px;
}
.stats-card-warning {
@apply bg-gradient-to-br from-amber-500 via-orange-500 to-orange-600 dark:from-amber-600 dark:via-orange-600 dark:to-orange-700;
}
.stats-card-danger {
@apply bg-gradient-to-br from-red-500 via-red-600 to-rose-700 dark:from-red-600 dark:via-red-700 dark:to-rose-800;
} }
.control-panel { .control-panel {
@apply bg-white dark:bg-gray-800 rounded-xl shadow-lg border border-gray-200 dark:border-gray-700 p-6 mb-6 transition-all duration-300; background: white;
border-radius: 12px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding: 20px;
margin-bottom: 20px;
} }
.filter-group { .filter-group {
@apply flex gap-4 items-center flex-wrap; display: flex;
gap: 15px;
align-items: center;
flex-wrap: wrap;
} }
.btn-calendar { .btn-calendar {
@apply bg-blue-600 hover:bg-blue-700 dark:bg-blue-700 dark:hover:bg-blue-600 text-white border-none px-4 py-2 rounded-lg cursor-pointer transition-all duration-200 font-medium shadow-md hover:shadow-lg; background: #3b82f6;
color: white;
border: none;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
transition: background-color 0.2s;
}
.btn-calendar:hover {
background: #2563eb;
} }
.btn-calendar.active { .btn-calendar.active {
@apply bg-blue-800 dark:bg-blue-500 shadow-lg scale-105; background: #1d4ed8;
}
.btn-danger {
@apply bg-red-600 hover:bg-red-700 dark:bg-red-700 dark:hover:bg-red-600 text-white px-4 py-2 rounded-lg transition-all duration-200 font-medium shadow-md hover:shadow-lg;
}
.btn-primary {
@apply bg-blue-600 hover:bg-blue-700 dark:bg-blue-700 dark:hover:bg-blue-600 text-white px-4 py-2 rounded-lg transition-all duration-200 font-medium shadow-md hover:shadow-lg;
} }
.legend { .legend {
@apply flex gap-6 flex-wrap mt-4 pt-4 border-t border-gray-200 dark:border-gray-600; display: flex;
gap: 20px;
flex-wrap: wrap;
margin-top: 15px;
} }
.legend-item { .legend-item {
@apply flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300; display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
} }
.legend-color { .legend-color {
@apply w-4 h-4 rounded; width: 16px;
} height: 16px;
border-radius: 4px;
.select-custom {
@apply border border-gray-300 dark:border-gray-600 rounded-lg px-3 py-2 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200;
}
.page-header {
@apply bg-gradient-to-r from-blue-50 via-indigo-50 to-purple-50 dark:from-gray-900 dark:via-gray-800 dark:to-gray-900 border-b border-gray-200 dark:border-gray-700;
}
.modal-overlay {
@apply fixed inset-0 bg-black bg-opacity-50 dark:bg-opacity-70 z-50 transition-opacity duration-300;
}
.modal-content {
@apply bg-white dark:bg-gray-800 rounded-xl max-w-2xl w-full p-6 shadow-2xl border border-gray-200 dark:border-gray-700 transition-all duration-300;
}
.modal-header {
@apply flex justify-between items-center mb-6 pb-4 border-b border-gray-200 dark:border-gray-600;
}
.modal-title {
@apply text-xl font-semibold text-gray-900 dark:text-gray-100;
}
.close-button {
@apply text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300 transition-colors duration-200 text-xl;
}
.detail-grid {
@apply grid grid-cols-1 md:grid-cols-2 gap-4 text-sm;
}
.detail-item {
@apply space-y-1;
}
.detail-label {
@apply font-medium text-gray-600 dark:text-gray-400;
}
.detail-value {
@apply text-gray-900 dark:text-gray-100 bg-gray-50 dark:bg-gray-700 px-3 py-2 rounded-lg;
}
.error-message {
@apply bg-red-50 dark:bg-red-900/20 text-red-700 dark:text-red-400 p-3 rounded-lg border border-red-200 dark:border-red-800;
}
.notes-section {
@apply bg-blue-50 dark:bg-blue-900/20 text-blue-800 dark:text-blue-300 p-3 rounded-lg border border-blue-200 dark:border-blue-800;
} }
</style> </style>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="min-h-screen bg-gray-50 dark:bg-gray-900 transition-colors duration-300"> <div class="min-h-screen">
<!-- Header mit Breadcrumb --> <!-- Header mit Breadcrumb -->
<div class="page-header"> <div>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="py-8"> <div class="py-6">
<nav class="text-sm font-medium text-gray-600 dark:text-gray-400 mb-6"> <nav class="text-sm font-medium text-gray-600 mb-4">
<ol class="list-none p-0 inline-flex"> <ol class="list-none p-0 inline-flex">
<li class="flex items-center"> <li class="flex items-center">
<a href="{{ url_for('admin_page') }}" class="hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200"> <a href="{{ url_for('admin_page') }}" class="hover:text-blue-600">Admin-Dashboard</a>
<i class="fas fa-tachometer-alt mr-2"></i> <svg class="fill-current w-3 h-3 mx-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
Admin-Dashboard <path d="m285.476 272.971c4.686 4.686 4.686 12.284 0 16.97l-133.952 133.954c-4.686 4.686-12.284 4.686-16.97 0l-133.952-133.954c-4.686-4.686-4.686-12.284 0-16.97 4.686-4.686 12.284-4.686 16.97 0l125.462 125.463 125.462-125.463c4.686-4.686 12.284-4.686 16.97 0z"/>
</a>
<svg class="fill-current w-4 h-4 mx-3 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
<path d="M285.476 272.971c4.686 4.686 4.686 12.284 0 16.97l-133.952 133.954c-4.686 4.686-12.284 4.686-16.97 0l-133.952-133.954c-4.686-4.686-4.686-12.284 0-16.97 4.686-4.686 12.284-4.686 16.97 0l125.462 125.463 125.462-125.463c4.686-4.686 12.284-4.686 16.97 0z"/>
</svg> </svg>
</li> </li>
<li class="text-gray-900 dark:text-gray-100 font-semibold"> <li class="text-gray-900 font-semibold">
<i class="fas fa-plug mr-2"></i>
Steckdosenschaltzeiten Steckdosenschaltzeiten
</li> </li>
</ol> </ol>
</nav> </nav>
<div class="flex flex-col lg:flex-row lg:justify-between lg:items-center gap-4"> <div class="flex justify-between items-center">
<div> <div>
<h1 class="text-4xl font-bold text-gray-900 dark:text-gray-100 mb-2"> <h1 class="text-3xl font-bold text-gray-900">
<i class="fas fa-plug text-blue-600 dark:text-blue-400 mr-4"></i> <i class="fas fa-plug text-blue-600 mr-3"></i>
Steckdosenschaltzeiten Steckdosenschaltzeiten
</h1> </h1>
<p class="text-lg text-gray-600 dark:text-gray-400"> <p class="mt-2 text-gray-600">
Kalenderübersicht aller Drucker-Steckdosenschaltungen mit detaillierter Analyse Kalenderübersicht aller Drucker-Steckdosenschaltungen mit detaillierter Analyse
</p> </p>
</div> </div>
<div class="flex gap-3"> <div class="flex gap-3">
<button id="refreshData" class="btn-primary"> <button id="refreshData" class="btn-calendar">
<i class="fas fa-sync-alt mr-2"></i> <i class="fas fa-sync-alt mr-2"></i>
Aktualisieren Aktualisieren
</button> </button>
<button id="cleanupLogs" class="btn-danger"> <button id="cleanupLogs" class="bg-red-600 text-white px-4 py-2 rounded-lg hover:bg-red-700">
<i class="fas fa-trash mr-2"></i> <i class="fas fa-trash mr-2"></i>
Alte Logs löschen Alte Logs löschen
</button> </button>
@ -224,64 +141,52 @@
</div> </div>
<!-- Haupt-Container --> <!-- Haupt-Container -->
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pb-8">
<!-- Statistik-Karten --> <!-- Statistik-Karten -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8"> <div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-6">
<div class="stats-card"> <div class="stats-card">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div> <div>
<p class="text-sm opacity-90 mb-1">Schaltungen (24h)</p> <p class="text-sm opacity-80">Schaltungen (24h)</p>
<p class="text-3xl font-bold" id="totalLogs">{{ stats.total_logs or 0 }}</p> <p class="text-2xl font-bold" id="totalLogs">{{ stats.total_logs or 0 }}</p>
<p class="text-xs opacity-80 mt-1">Gesamt-Events</p>
</div>
<div class="p-3 bg-white/20 rounded-xl">
<i class="fas fa-chart-line text-2xl"></i>
</div> </div>
<i class="fas fa-chart-line text-2xl opacity-60"></i>
</div> </div>
</div> </div>
<div class="stats-card stats-card-success"> <div class="stats-card">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div> <div>
<p class="text-sm opacity-90 mb-1">Erfolgsrate</p> <p class="text-sm opacity-80">Erfolgsrate</p>
<p class="text-3xl font-bold" id="successRate">{{ "%.1f"|format(100 - stats.error_rate) }}%</p> <p class="text-2xl font-bold" id="successRate">{{ "%.1f"|format(100 - stats.error_rate) }}%</p>
<p class="text-xs opacity-80 mt-1">Erfolgreiche Schaltungen</p>
</div>
<div class="p-3 bg-white/20 rounded-xl">
<i class="fas fa-check-circle text-2xl"></i>
</div> </div>
<i class="fas fa-check-circle text-2xl opacity-60"></i>
</div> </div>
</div> </div>
<div class="stats-card stats-card-warning"> <div class="stats-card">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div> <div>
<p class="text-sm opacity-90 mb-1">Ø Antwortzeit</p> <p class="text-sm opacity-80">Ø Antwortzeit</p>
<p class="text-3xl font-bold" id="avgResponseTime"> <p class="text-2xl font-bold" id="avgResponseTime">
{% if stats.average_response_time_ms %} {% if stats.average_response_time_ms %}
{{ "%.0f"|format(stats.average_response_time_ms) }}ms {{ "%.0f"|format(stats.average_response_time_ms) }}ms
{% else %} {% else %}
N/A N/A
{% endif %} {% endif %}
</p> </p>
<p class="text-xs opacity-80 mt-1">Durchschnittlich</p>
</div>
<div class="p-3 bg-white/20 rounded-xl">
<i class="fas fa-stopwatch text-2xl"></i>
</div> </div>
<i class="fas fa-stopwatch text-2xl opacity-60"></i>
</div> </div>
</div> </div>
<div class="stats-card stats-card-danger"> <div class="stats-card">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div> <div>
<p class="text-sm opacity-90 mb-1">Fehlerzahl</p> <p class="text-sm opacity-80">Fehlerzahl</p>
<p class="text-3xl font-bold" id="errorCount">{{ stats.error_count or 0 }}</p> <p class="text-2xl font-bold" id="errorCount">{{ stats.error_count or 0 }}</p>
<p class="text-xs opacity-80 mt-1">Gescheiterte Versuche</p>
</div>
<div class="p-3 bg-white/20 rounded-xl">
<i class="fas fa-exclamation-triangle text-2xl"></i>
</div> </div>
<i class="fas fa-exclamation-triangle text-2xl opacity-60"></i>
</div> </div>
</div> </div>
</div> </div>
@ -289,62 +194,39 @@
<!-- Filter und Steuerung --> <!-- Filter und Steuerung -->
<div class="control-panel"> <div class="control-panel">
<div class="filter-group"> <div class="filter-group">
<div class="flex items-center gap-3"> <label for="printerFilter" class="font-medium text-gray-700">Drucker filtern:</label>
<label for="printerFilter" class="font-medium text-gray-700 dark:text-gray-300"> <select id="printerFilter" class="border border-gray-300 rounded-lg px-3 py-2 bg-white">
<i class="fas fa-filter mr-2"></i> <option value="">Alle Drucker</option>
Drucker filtern:
</label>
<select id="printerFilter" class="select-custom">
<option value="">Alle Drucker anzeigen</option>
{% for printer in printers %} {% for printer in printers %}
<option value="{{ printer.id }}">{{ printer.name }}</option> <option value="{{ printer.id }}">{{ printer.name }}</option>
{% endfor %} {% endfor %}
</select> </select>
</div>
<div class="flex items-center gap-3 border-l border-gray-300 dark:border-gray-600 pl-6 ml-6"> <div class="border-l border-gray-300 pl-4 ml-4">
<label class="font-medium text-gray-700 dark:text-gray-300"> <label class="font-medium text-gray-700 mr-3">Ansicht:</label>
<i class="fas fa-eye mr-2"></i> <button id="monthView" class="btn-calendar active">Monat</button>
Ansicht: <button id="weekView" class="btn-calendar">Woche</button>
</label> <button id="dayView" class="btn-calendar">Tag</button>
<div class="flex gap-2">
<button id="monthView" class="btn-calendar active">
<i class="fas fa-calendar-alt mr-1"></i>
Monat
</button>
<button id="weekView" class="btn-calendar">
<i class="fas fa-calendar-week mr-1"></i>
Woche
</button>
<button id="dayView" class="btn-calendar">
<i class="fas fa-calendar-day mr-1"></i>
Tag
</button>
</div>
</div> </div>
</div> </div>
<!-- Legende --> <!-- Legende -->
<div class="legend"> <div class="legend">
<h4 class="font-medium text-gray-700 dark:text-gray-300 w-full mb-2">
<i class="fas fa-info-circle mr-2"></i>
Status-Legende:
</h4>
<div class="legend-item"> <div class="legend-item">
<div class="legend-color bg-green-500"></div> <div class="legend-color" style="background-color: #10b981;"></div>
<span><i class="fas fa-toggle-on mr-1"></i>Steckdose EIN</span> <span>Steckdose EIN</span>
</div> </div>
<div class="legend-item"> <div class="legend-item">
<div class="legend-color bg-orange-500"></div> <div class="legend-color" style="background-color: #f59e0b;"></div>
<span><i class="fas fa-toggle-off mr-1"></i>Steckdose AUS</span> <span>Steckdose AUS</span>
</div> </div>
<div class="legend-item"> <div class="legend-item">
<div class="legend-color bg-blue-500"></div> <div class="legend-color" style="background-color: #3b82f6;"></div>
<span><i class="fas fa-plug mr-1"></i>Verbunden</span> <span>Verbunden</span>
</div> </div>
<div class="legend-item"> <div class="legend-item">
<div class="legend-color bg-red-500"></div> <div class="legend-color" style="background-color: #ef4444;"></div>
<span><i class="fas fa-unlink mr-1"></i>Getrennt</span> <span>Getrennt</span>
</div> </div>
</div> </div>
</div> </div>
@ -355,15 +237,12 @@
</div> </div>
<!-- Detailansicht Modal --> <!-- Detailansicht Modal -->
<div id="eventDetailModal" class="modal-overlay hidden"> <div id="eventDetailModal" class="fixed inset-0 bg-black bg-opacity-50 hidden z-50">
<div class="flex items-center justify-center min-h-screen p-4"> <div class="flex items-center justify-center min-h-screen p-4">
<div class="modal-content"> <div class="bg-white rounded-lg max-w-lg w-full p-6">
<div class="modal-header"> <div class="flex justify-between items-center mb-4">
<h3 class="modal-title"> <h3 class="text-lg font-semibold">Schaltung Details</h3>
<i class="fas fa-info-circle mr-2 text-blue-600 dark:text-blue-400"></i> <button id="closeModal" class="text-gray-400 hover:text-gray-600">
Schaltung Details
</h3>
<button id="closeModal" class="close-button">
<i class="fas fa-times"></i> <i class="fas fa-times"></i>
</button> </button>
</div> </div>
@ -372,9 +251,8 @@
<!-- Wird dynamisch gefüllt --> <!-- Wird dynamisch gefüllt -->
</div> </div>
<div class="mt-6 flex justify-end gap-3"> <div class="mt-6 flex justify-end">
<button id="closeModalBtn" class="px-6 py-2 bg-gray-300 dark:bg-gray-600 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-400 dark:hover:bg-gray-500 transition-all duration-200 font-medium"> <button id="closeModalBtn" class="px-4 py-2 bg-gray-300 text-gray-700 rounded-lg hover:bg-gray-400">
<i class="fas fa-times mr-2"></i>
Schließen Schließen
</button> </button>
</div> </div>
@ -414,17 +292,6 @@ document.addEventListener('DOMContentLoaded', function() {
eventDidMount: function(info) { eventDidMount: function(info) {
// Tooltip hinzufügen // Tooltip hinzufügen
info.el.title = info.event.title + '\nKlicken für Details'; info.el.title = info.event.title + '\nKlicken für Details';
// Hover-Effekt
info.el.addEventListener('mouseenter', function() {
this.style.transform = 'scale(1.05)';
this.style.zIndex = '10';
});
info.el.addEventListener('mouseleave', function() {
this.style.transform = 'scale(1)';
this.style.zIndex = '1';
});
} }
}); });
@ -459,108 +326,78 @@ document.addEventListener('DOMContentLoaded', function() {
const modal = document.getElementById('eventDetailModal'); const modal = document.getElementById('eventDetailModal');
const content = document.getElementById('modalContent'); const content = document.getElementById('modalContent');
const startTime = new Date(event.start).toLocaleString('de-DE', { const startTime = new Date(event.start).toLocaleString('de-DE');
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
let detailsHtml = ` let detailsHtml = `
<div class="space-y-6"> <div class="space-y-3">
<div class="flex items-center p-4 bg-gray-50 dark:bg-gray-700 rounded-xl"> <div class="flex items-center">
<span class="w-6 h-6 rounded-lg mr-4 shadow-md" style="background-color: ${event.backgroundColor}"></span> <span class="w-4 h-4 rounded mr-3" style="background-color: ${event.backgroundColor}"></span>
<span class="font-medium">${event.title}</span>
</div>
<div class="grid grid-cols-2 gap-4 text-sm">
<div> <div>
<h4 class="font-semibold text-lg text-gray-900 dark:text-gray-100">${event.title}</h4> <span class="font-medium text-gray-600">Zeitpunkt:</span>
<p class="text-sm text-gray-600 dark:text-gray-400">${startTime}</p> <p>${startTime}</p>
</div>
</div> </div>
<div class="detail-grid"> <div>
<div class="detail-item"> <span class="font-medium text-gray-600">Drucker:</span>
<div class="detail-label"> <p>${props.printer_name}</p>
<i class="fas fa-printer mr-1"></i>
Drucker:
</div>
<div class="detail-value">${props.printer_name}</div>
</div> </div>
<div class="detail-item"> <div>
<div class="detail-label"> <span class="font-medium text-gray-600">Status:</span>
<i class="fas fa-info-circle mr-1"></i> <p class="capitalize">${props.status}</p>
Status:
</div>
<div class="detail-value capitalize">${props.status}</div>
</div> </div>
<div class="detail-item"> <div>
<div class="detail-label"> <span class="font-medium text-gray-600">Quelle:</span>
<i class="fas fa-source mr-1"></i> <p class="capitalize">${props.source}</p>
Quelle:
</div>
<div class="detail-value capitalize">${props.source}</div>
</div> </div>
`; `;
if (props.user_name) { if (props.user_name) {
detailsHtml += ` detailsHtml += `
<div class="detail-item"> <div>
<div class="detail-label"> <span class="font-medium text-gray-600">Benutzer:</span>
<i class="fas fa-user mr-1"></i> <p>${props.user_name}</p>
Benutzer:
</div>
<div class="detail-value">${props.user_name}</div>
</div> </div>
`; `;
} }
if (props.response_time_ms) { if (props.response_time_ms) {
detailsHtml += ` detailsHtml += `
<div class="detail-item"> <div>
<div class="detail-label"> <span class="font-medium text-gray-600">Antwortzeit:</span>
<i class="fas fa-clock mr-1"></i> <p>${props.response_time_ms}ms</p>
Antwortzeit:
</div>
<div class="detail-value">${props.response_time_ms}ms</div>
</div> </div>
`; `;
} }
if (props.power_consumption) { if (props.power_consumption) {
detailsHtml += ` detailsHtml += `
<div class="detail-item"> <div>
<div class="detail-label"> <span class="font-medium text-gray-600">Verbrauch:</span>
<i class="fas fa-bolt mr-1"></i> <p>${props.power_consumption}W</p>
Verbrauch:
</div>
<div class="detail-value">${props.power_consumption}W</div>
</div> </div>
`; `;
} }
if (props.voltage) { if (props.voltage) {
detailsHtml += ` detailsHtml += `
<div class="detail-item"> <div>
<div class="detail-label"> <span class="font-medium text-gray-600">Spannung:</span>
<i class="fas fa-tachometer-alt mr-1"></i> <p>${props.voltage}V</p>
Spannung:
</div>
<div class="detail-value">${props.voltage}V</div>
</div> </div>
`; `;
} }
if (props.current) { if (props.current) {
detailsHtml += ` detailsHtml += `
<div class="detail-item"> <div>
<div class="detail-label"> <span class="font-medium text-gray-600">Strom:</span>
<i class="fas fa-wave-square mr-1"></i> <p>${props.current}A</p>
Strom:
</div>
<div class="detail-value">${props.current}A</div>
</div> </div>
`; `;
} }
@ -569,78 +406,39 @@ document.addEventListener('DOMContentLoaded', function() {
if (props.notes) { if (props.notes) {
detailsHtml += ` detailsHtml += `
<div class="mt-6"> <div class="mt-4">
<div class="notes-section"> <span class="font-medium text-gray-600">Notizen:</span>
<div class="flex items-start gap-2"> <p class="text-sm bg-gray-50 p-2 rounded mt-1">${props.notes}</p>
<i class="fas fa-sticky-note mt-0.5"></i>
<div>
<span class="font-medium">Notizen:</span>
<p class="mt-1">${props.notes}</p>
</div>
</div>
</div>
</div> </div>
`; `;
} }
if (props.error_message) { if (props.error_message) {
detailsHtml += ` detailsHtml += `
<div class="mt-6"> <div class="mt-4">
<div class="error-message"> <span class="font-medium text-red-600">Fehlermeldung:</span>
<div class="flex items-start gap-2"> <p class="text-sm bg-red-50 text-red-700 p-2 rounded mt-1">${props.error_message}</p>
<i class="fas fa-exclamation-triangle mt-0.5"></i>
<div>
<span class="font-medium">Fehlermeldung:</span>
<p class="mt-1">${props.error_message}</p>
</div>
</div>
</div>
</div> </div>
`; `;
} }
content.innerHTML = detailsHtml; content.innerHTML = detailsHtml;
modal.classList.remove('hidden'); modal.classList.remove('hidden');
// Animation
setTimeout(() => {
modal.style.opacity = '1';
modal.querySelector('.modal-content').style.transform = 'scale(1)';
}, 10);
} }
// Modal schließen // Modal schließen
function closeModal() { function closeModal() {
const modal = document.getElementById('eventDetailModal'); document.getElementById('eventDetailModal').classList.add('hidden');
modal.style.opacity = '0';
modal.querySelector('.modal-content').style.transform = 'scale(0.95)';
setTimeout(() => {
modal.classList.add('hidden');
}, 300);
} }
// Event-Listener // Event-Listener
document.getElementById('closeModal').addEventListener('click', closeModal); document.getElementById('closeModal').addEventListener('click', closeModal);
document.getElementById('closeModalBtn').addEventListener('click', closeModal); document.getElementById('closeModalBtn').addEventListener('click', closeModal);
// Modal schließen bei Klick außerhalb
document.getElementById('eventDetailModal').addEventListener('click', function(e) {
if (e.target === this) {
closeModal();
}
});
// Drucker-Filter // Drucker-Filter
document.getElementById('printerFilter').addEventListener('change', function() { document.getElementById('printerFilter').addEventListener('change', function() {
currentPrinterFilter = this.value; currentPrinterFilter = this.value;
calendar.refetchEvents(); calendar.refetchEvents();
// Visual Feedback
this.style.transform = 'scale(1.02)';
setTimeout(() => {
this.style.transform = 'scale(1)';
}, 150);
}); });
// Ansicht-Buttons // Ansicht-Buttons
@ -668,29 +466,13 @@ document.addEventListener('DOMContentLoaded', function() {
// Aktualisieren-Button // Aktualisieren-Button
document.getElementById('refreshData').addEventListener('click', function() { document.getElementById('refreshData').addEventListener('click', function() {
// Loading-Animation
const originalContent = this.innerHTML;
this.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i>Lädt...';
this.disabled = true;
calendar.refetchEvents(); calendar.refetchEvents();
loadStatistics(); loadStatistics();
// Reset nach 2 Sekunden
setTimeout(() => {
this.innerHTML = originalContent;
this.disabled = false;
}, 2000);
}); });
// Cleanup-Button // Cleanup-Button
document.getElementById('cleanupLogs').addEventListener('click', function() { document.getElementById('cleanupLogs').addEventListener('click', function() {
if (confirm('Möchten Sie wirklich alte Logs löschen? (älter als 30 Tage)\n\nDieser Vorgang kann nicht rückgängig gemacht werden.')) { if (confirm('Möchten Sie wirklich alte Logs löschen? (älter als 30 Tage)')) {
// Loading-Animation
const originalContent = this.innerHTML;
this.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i>Lösche...';
this.disabled = true;
fetch('/api/admin/plug-schedules/cleanup', { fetch('/api/admin/plug-schedules/cleanup', {
method: 'POST', method: 'POST',
headers: { headers: {
@ -702,20 +484,16 @@ document.addEventListener('DOMContentLoaded', function() {
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.success) { if (data.success) {
alert(`Erfolgreich ${data.deleted_count} alte Einträge gelöscht`); alert(`Erfolgreich ${data.deleted_count} alte Einträge gelöscht`);
calendar.refetchEvents(); calendar.refetchEvents();
loadStatistics(); loadStatistics();
} else { } else {
alert('Fehler beim Löschen: ' + data.error); alert('Fehler beim Löschen: ' + data.error);
} }
}) })
.catch(error => { .catch(error => {
console.error('Fehler:', error); console.error('Fehler:', error);
alert('❌ Fehler beim Löschen der Logs'); alert('Fehler beim Löschen der Logs');
})
.finally(() => {
this.innerHTML = originalContent;
this.disabled = false;
}); });
} }
}); });
@ -727,15 +505,11 @@ document.addEventListener('DOMContentLoaded', function() {
.then(data => { .then(data => {
if (data.success) { if (data.success) {
const stats = data.statistics; const stats = data.statistics;
document.getElementById('totalLogs').textContent = stats.total_logs || 0;
// Animierte Zahlen-Updates document.getElementById('successRate').textContent = (100 - (stats.error_rate || 0)).toFixed(1) + '%';
animateValue('totalLogs', stats.total_logs || 0);
animateValue('successRate', (100 - (stats.error_rate || 0)), '%');
animateValue('errorCount', stats.error_count || 0);
const avgTime = stats.average_response_time_ms;
document.getElementById('avgResponseTime').textContent = document.getElementById('avgResponseTime').textContent =
avgTime ? Math.round(avgTime) + 'ms' : 'N/A'; stats.average_response_time_ms ? Math.round(stats.average_response_time_ms) + 'ms' : 'N/A';
document.getElementById('errorCount').textContent = stats.error_count || 0;
} }
}) })
.catch(error => { .catch(error => {
@ -743,37 +517,8 @@ document.addEventListener('DOMContentLoaded', function() {
}); });
} }
// Animierte Werte-Updates
function animateValue(elementId, endValue, suffix = '') {
const element = document.getElementById(elementId);
const startValue = parseInt(element.textContent) || 0;
const difference = endValue - startValue;
const duration = 1000; // 1 Sekunde
const steps = 30;
const stepValue = difference / steps;
const stepDuration = duration / steps;
let currentValue = startValue;
let currentStep = 0;
const timer = setInterval(() => {
currentStep++;
currentValue += stepValue;
if (currentStep >= steps) {
currentValue = endValue;
clearInterval(timer);
}
element.textContent = Math.round(currentValue) + suffix;
}, stepDuration);
}
// Kalender initialisieren // Kalender initialisieren
initCalendar(); initCalendar();
// Initiales Laden der Statistiken
loadStatistics();
}); });
</script> </script>
{% endblock %} {% endblock %}

View File

@ -0,0 +1,641 @@
#!/usr/bin/env python3
"""
Robustes Error-Recovery-System für wartungsfreien Produktionsbetrieb
Automatische Fehlererkennung, -behebung und -prävention
"""
import os
import sys
import time
import threading
import traceback
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Callable, Any
from dataclasses import dataclass, field
from enum import Enum
import logging
import json
import subprocess
import psutil
from contextlib import contextmanager
import signal
# Logging-Setup
try:
from utils.logging_config import get_logger
recovery_logger = get_logger("error_recovery")
except ImportError:
logging.basicConfig(level=logging.INFO)
recovery_logger = logging.getLogger("error_recovery")
class ErrorSeverity(Enum):
"""Schweregrade von Fehlern"""
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
CRITICAL = "critical"
class RecoveryAction(Enum):
"""Verfügbare Recovery-Aktionen"""
LOG_ONLY = "log_only"
RESTART_SERVICE = "restart_service"
RESTART_COMPONENT = "restart_component"
CLEAR_CACHE = "clear_cache"
RESET_DATABASE = "reset_database"
RESTART_SYSTEM = "restart_system"
EMERGENCY_STOP = "emergency_stop"
@dataclass
class ErrorPattern:
"""Definiert ein Fehlermuster und zugehörige Recovery-Aktionen"""
name: str
patterns: List[str] # Regex-Patterns für Fehlererkennung
severity: ErrorSeverity
actions: List[RecoveryAction]
max_occurrences: int = 3 # Maximale Anzahl vor Eskalation
time_window: int = 300 # Zeitfenster in Sekunden
escalation_actions: List[RecoveryAction] = field(default_factory=list)
description: str = ""
@dataclass
class ErrorOccurrence:
"""Einzelnes Auftreten eines Fehlers"""
timestamp: datetime
pattern_name: str
error_message: str
severity: ErrorSeverity
context: Dict[str, Any] = field(default_factory=dict)
recovery_attempted: List[RecoveryAction] = field(default_factory=list)
recovery_successful: bool = False
class ErrorRecoveryManager:
"""
Zentraler Manager für automatische Fehlererkennung und -behebung.
Überwacht kontinuierlich das System und führt automatische Recovery durch.
"""
def __init__(self):
self.is_active = False
self.error_patterns: Dict[str, ErrorPattern] = {}
self.error_history: List[ErrorOccurrence] = []
self.recovery_handlers: Dict[RecoveryAction, Callable] = {}
self.monitoring_thread: Optional[threading.Thread] = None
self.lock = threading.Lock()
# Konfiguration
self.config = {
"check_interval": 30, # Sekunden
"max_history_size": 1000,
"auto_recovery_enabled": True,
"critical_error_threshold": 5,
"system_restart_threshold": 10,
"log_file_paths": [
"logs/app/app.log",
"logs/errors/errors.log",
"logs/database/database.log"
]
}
# Initialisiere Standard-Fehlermuster
self._init_default_patterns()
# Initialisiere Recovery-Handler
self._init_recovery_handlers()
recovery_logger.info("🛡️ Error-Recovery-Manager initialisiert")
def _init_default_patterns(self):
"""Initialisiert Standard-Fehlermuster für häufige Probleme"""
patterns = [
# Datenbank-Fehler
ErrorPattern(
name="database_lock",
patterns=[
r"database is locked",
r"SQLite.*locked",
r"OperationalError.*locked"
],
severity=ErrorSeverity.HIGH,
actions=[RecoveryAction.RESET_DATABASE],
max_occurrences=3,
escalation_actions=[RecoveryAction.RESTART_SERVICE],
description="Datenbank-Sperrung"
),
# Memory-Fehler
ErrorPattern(
name="memory_exhausted",
patterns=[
r"MemoryError",
r"Out of memory",
r"Cannot allocate memory"
],
severity=ErrorSeverity.CRITICAL,
actions=[RecoveryAction.CLEAR_CACHE, RecoveryAction.RESTART_SERVICE],
max_occurrences=2,
escalation_actions=[RecoveryAction.RESTART_SYSTEM],
description="Speicher erschöpft"
),
# Network-Fehler
ErrorPattern(
name="connection_error",
patterns=[
r"ConnectionError",
r"Network is unreachable",
r"Connection refused"
],
severity=ErrorSeverity.MEDIUM,
actions=[RecoveryAction.RESTART_COMPONENT],
max_occurrences=5,
escalation_actions=[RecoveryAction.RESTART_SERVICE],
description="Netzwerk-Verbindungsfehler"
),
# Kiosk-Fehler
ErrorPattern(
name="kiosk_crash",
patterns=[
r"chromium.*crashed",
r"firefox.*crashed",
r"X11.*error",
r"Display.*not found"
],
severity=ErrorSeverity.HIGH,
actions=[RecoveryAction.RESTART_COMPONENT],
max_occurrences=3,
escalation_actions=[RecoveryAction.RESTART_SYSTEM],
description="Kiosk-Display Fehler"
),
# Service-Fehler
ErrorPattern(
name="service_failure",
patterns=[
r"systemctl.*failed",
r"Service.*not found",
r"Failed to start"
],
severity=ErrorSeverity.HIGH,
actions=[RecoveryAction.RESTART_SERVICE],
max_occurrences=3,
escalation_actions=[RecoveryAction.RESTART_SYSTEM],
description="System-Service Fehler"
),
# Disk-Fehler
ErrorPattern(
name="disk_full",
patterns=[
r"No space left on device",
r"Disk full",
r"OSError.*28"
],
severity=ErrorSeverity.CRITICAL,
actions=[RecoveryAction.CLEAR_CACHE],
max_occurrences=1,
escalation_actions=[RecoveryAction.EMERGENCY_STOP],
description="Festplatte voll"
),
# Flask-Fehler
ErrorPattern(
name="flask_error",
patterns=[
r"Internal Server Error",
r"500 Internal Server Error",
r"Application failed to start"
],
severity=ErrorSeverity.HIGH,
actions=[RecoveryAction.RESTART_SERVICE],
max_occurrences=3,
escalation_actions=[RecoveryAction.RESTART_SYSTEM],
description="Flask-Anwendungsfehler"
)
]
for pattern in patterns:
self.error_patterns[pattern.name] = pattern
def _init_recovery_handlers(self):
"""Initialisiert Handler für Recovery-Aktionen"""
self.recovery_handlers = {
RecoveryAction.LOG_ONLY: self._handle_log_only,
RecoveryAction.RESTART_SERVICE: self._handle_restart_service,
RecoveryAction.RESTART_COMPONENT: self._handle_restart_component,
RecoveryAction.CLEAR_CACHE: self._handle_clear_cache,
RecoveryAction.RESET_DATABASE: self._handle_reset_database,
RecoveryAction.RESTART_SYSTEM: self._handle_restart_system,
RecoveryAction.EMERGENCY_STOP: self._handle_emergency_stop
}
def start_monitoring(self):
"""Startet kontinuierliche Überwachung"""
if self.is_active:
recovery_logger.warning("Monitoring bereits aktiv")
return
self.is_active = True
self.monitoring_thread = threading.Thread(
target=self._monitor_loop,
daemon=True,
name="ErrorRecoveryMonitor"
)
self.monitoring_thread.start()
recovery_logger.info("🔍 Error-Monitoring gestartet")
def stop_monitoring(self):
"""Stoppt Überwachung"""
self.is_active = False
if self.monitoring_thread and self.monitoring_thread.is_alive():
self.monitoring_thread.join(timeout=5)
recovery_logger.info("🛑 Error-Monitoring gestoppt")
def _monitor_loop(self):
"""Hauptschleife für kontinuierliche Überwachung"""
while self.is_active:
try:
# Log-Dateien prüfen
self._check_log_files()
# System-Metriken prüfen
self._check_system_metrics()
# Service-Status prüfen
self._check_service_status()
# Alte Einträge bereinigen
self._cleanup_old_entries()
time.sleep(self.config["check_interval"])
except Exception as e:
recovery_logger.error(f"Fehler in Monitor-Loop: {e}")
time.sleep(5) # Kurze Pause bei Fehlern
def _check_log_files(self):
"""Prüft Log-Dateien auf Fehlermuster"""
for log_path in self.config["log_file_paths"]:
try:
if not os.path.exists(log_path):
continue
# Lese nur neue Zeilen (vereinfacht)
with open(log_path, 'r', encoding='utf-8') as f:
# Gehe zu den letzten 1000 Zeilen
lines = f.readlines()
recent_lines = lines[-1000:] if len(lines) > 1000 else lines
for line in recent_lines:
self._analyze_log_line(line, log_path)
except Exception as e:
recovery_logger.debug(f"Fehler beim Lesen von {log_path}: {e}")
def _analyze_log_line(self, line: str, source: str):
"""Analysiert einzelne Log-Zeile auf Fehlermuster"""
import re
for pattern_name, pattern in self.error_patterns.items():
for regex in pattern.patterns:
try:
if re.search(regex, line, re.IGNORECASE):
self._handle_error_detection(
pattern_name=pattern_name,
error_message=line.strip(),
context={"source": source, "pattern": regex}
)
break
except Exception as e:
recovery_logger.debug(f"Regex-Fehler für {regex}: {e}")
def _check_system_metrics(self):
"""Prüft System-Metriken auf kritische Werte"""
try:
# Memory-Check
memory = psutil.virtual_memory()
if memory.percent > 95:
self._handle_error_detection(
pattern_name="memory_exhausted",
error_message=f"Speicherverbrauch kritisch: {memory.percent:.1f}%",
context={"memory_percent": memory.percent}
)
# Disk-Check
disk = psutil.disk_usage('/')
if disk.percent > 98:
self._handle_error_detection(
pattern_name="disk_full",
error_message=f"Festplatte fast voll: {disk.percent:.1f}%",
context={"disk_percent": disk.percent}
)
# Load-Check
if hasattr(psutil, 'getloadavg'):
load_avg = psutil.getloadavg()[0]
if load_avg > 5.0: # Sehr hohe Last
self._handle_error_detection(
pattern_name="system_overload",
error_message=f"System-Last kritisch: {load_avg:.2f}",
context={"load_average": load_avg}
)
except Exception as e:
recovery_logger.debug(f"System-Metrics-Check fehlgeschlagen: {e}")
def _check_service_status(self):
"""Prüft Status wichtiger Services"""
services = ["myp-https.service", "myp-kiosk.service"]
for service in services:
try:
result = subprocess.run(
["sudo", "systemctl", "is-active", service],
capture_output=True, text=True, timeout=10
)
if result.returncode != 0:
self._handle_error_detection(
pattern_name="service_failure",
error_message=f"Service {service} nicht aktiv: {result.stdout.strip()}",
context={"service": service, "status": result.stdout.strip()}
)
except Exception as e:
recovery_logger.debug(f"Service-Check für {service} fehlgeschlagen: {e}")
def _handle_error_detection(self, pattern_name: str, error_message: str, context: Dict[str, Any] = None):
"""Behandelt erkannten Fehler und startet Recovery"""
with self.lock:
if pattern_name not in self.error_patterns:
recovery_logger.warning(f"Unbekanntes Fehlermuster: {pattern_name}")
return
pattern = self.error_patterns[pattern_name]
# Prüfe ob bereits kürzlich aufgetreten
recent_occurrences = self._count_recent_occurrences(pattern_name, pattern.time_window)
# Erstelle Error-Occurrence
occurrence = ErrorOccurrence(
timestamp=datetime.now(),
pattern_name=pattern_name,
error_message=error_message,
severity=pattern.severity,
context=context or {}
)
self.error_history.append(occurrence)
recovery_logger.warning(f"🚨 Fehler erkannt: {pattern_name} - {error_message}")
# Entscheide über Recovery-Aktionen
if recent_occurrences >= pattern.max_occurrences:
# Eskalation
actions = pattern.escalation_actions
recovery_logger.error(f"🔥 Eskalation für {pattern_name}: {recent_occurrences} Vorkommen in {pattern.time_window}s")
else:
# Normale Recovery
actions = pattern.actions
# Führe Recovery-Aktionen aus
if self.config["auto_recovery_enabled"]:
self._execute_recovery_actions(occurrence, actions)
def _count_recent_occurrences(self, pattern_name: str, time_window: int) -> int:
"""Zählt kürzliche Vorkommen eines Fehlermusters"""
cutoff_time = datetime.now() - timedelta(seconds=time_window)
return sum(1 for err in self.error_history
if err.pattern_name == pattern_name and err.timestamp > cutoff_time)
def _execute_recovery_actions(self, occurrence: ErrorOccurrence, actions: List[RecoveryAction]):
"""Führt Recovery-Aktionen aus"""
for action in actions:
try:
recovery_logger.info(f"🔧 Führe Recovery-Aktion aus: {action.value}")
handler = self.recovery_handlers.get(action)
if handler:
success = handler(occurrence)
occurrence.recovery_attempted.append(action)
if success:
occurrence.recovery_successful = True
recovery_logger.info(f"✅ Recovery erfolgreich: {action.value}")
break # Stoppe bei erfolgreicher Recovery
else:
recovery_logger.warning(f"❌ Recovery fehlgeschlagen: {action.value}")
else:
recovery_logger.error(f"Kein Handler für Recovery-Aktion: {action.value}")
except Exception as e:
recovery_logger.error(f"Fehler bei Recovery-Aktion {action.value}: {e}")
def _handle_log_only(self, occurrence: ErrorOccurrence) -> bool:
"""Handler: Nur Logging, keine weitere Aktion"""
recovery_logger.info(f"📝 Log-Only für: {occurrence.error_message}")
return True
def _handle_restart_service(self, occurrence: ErrorOccurrence) -> bool:
"""Handler: Service-Neustart"""
try:
from utils.system_control import get_system_control_manager, SystemOperation
manager = get_system_control_manager()
result = manager.schedule_operation(
SystemOperation.SERVICE_RESTART,
delay_seconds=5,
reason=f"Automatische Recovery für: {occurrence.pattern_name}"
)
return result.get("success", False)
except Exception as e:
recovery_logger.error(f"Service-Neustart fehlgeschlagen: {e}")
return False
def _handle_restart_component(self, occurrence: ErrorOccurrence) -> bool:
"""Handler: Komponenten-Neustart (z.B. Kiosk)"""
try:
from utils.system_control import get_system_control_manager, SystemOperation
manager = get_system_control_manager()
result = manager.schedule_operation(
SystemOperation.KIOSK_RESTART,
delay_seconds=5,
reason=f"Automatische Recovery für: {occurrence.pattern_name}"
)
return result.get("success", False)
except Exception as e:
recovery_logger.error(f"Komponenten-Neustart fehlgeschlagen: {e}")
return False
def _handle_clear_cache(self, occurrence: ErrorOccurrence) -> bool:
"""Handler: Cache leeren"""
try:
# App-Caches leeren
from app import clear_user_cache, clear_printer_status_cache
clear_user_cache()
clear_printer_status_cache()
# System-Cache leeren
if os.name != 'nt':
subprocess.run(["sudo", "sync"], timeout=10)
return True
except Exception as e:
recovery_logger.error(f"Cache-Clearing fehlgeschlagen: {e}")
return False
def _handle_reset_database(self, occurrence: ErrorOccurrence) -> bool:
"""Handler: Datenbank-Reset"""
try:
from utils.database_cleanup import safe_database_cleanup
result = safe_database_cleanup(force_mode_switch=True)
return result.get("success", False)
except Exception as e:
recovery_logger.error(f"Database-Reset fehlgeschlagen: {e}")
return False
def _handle_restart_system(self, occurrence: ErrorOccurrence) -> bool:
"""Handler: System-Neustart"""
try:
from utils.system_control import schedule_system_restart
result = schedule_system_restart(
delay_seconds=60,
reason=f"Automatische Recovery für kritischen Fehler: {occurrence.pattern_name}",
force=True
)
return result.get("success", False)
except Exception as e:
recovery_logger.error(f"System-Neustart fehlgeschlagen: {e}")
return False
def _handle_emergency_stop(self, occurrence: ErrorOccurrence) -> bool:
"""Handler: Notfall-Stopp"""
try:
recovery_logger.critical(f"🚨 NOTFALL-STOPP: {occurrence.error_message}")
# Führe sofortigen Shutdown durch
from utils.shutdown_manager import get_shutdown_manager
shutdown_manager = get_shutdown_manager()
shutdown_manager.force_shutdown(1)
return True
except Exception as e:
recovery_logger.error(f"Notfall-Stopp fehlgeschlagen: {e}")
return False
def _cleanup_old_entries(self):
"""Bereinigt alte Error-History-Einträge"""
with self.lock:
if len(self.error_history) > self.config["max_history_size"]:
self.error_history = self.error_history[-self.config["max_history_size"]:]
def get_error_statistics(self) -> Dict[str, Any]:
"""Gibt Fehler-Statistiken zurück"""
with self.lock:
total_errors = len(self.error_history)
# Fehler nach Schweregrad
by_severity = {}
for severity in ErrorSeverity:
by_severity[severity.value] = sum(1 for err in self.error_history
if err.severity == severity)
# Fehler nach Pattern
by_pattern = {}
for pattern_name in self.error_patterns.keys():
by_pattern[pattern_name] = sum(1 for err in self.error_history
if err.pattern_name == pattern_name)
# Letzten 24h
last_24h = datetime.now() - timedelta(hours=24)
recent_errors = sum(1 for err in self.error_history
if err.timestamp > last_24h)
# Recovery-Erfolgsrate
attempted_recoveries = sum(1 for err in self.error_history
if err.recovery_attempted)
successful_recoveries = sum(1 for err in self.error_history
if err.recovery_successful)
success_rate = (successful_recoveries / attempted_recoveries * 100) if attempted_recoveries > 0 else 0
return {
"total_errors": total_errors,
"errors_last_24h": recent_errors,
"by_severity": by_severity,
"by_pattern": by_pattern,
"recovery_success_rate": round(success_rate, 1),
"monitoring_active": self.is_active,
"auto_recovery_enabled": self.config["auto_recovery_enabled"]
}
def get_recent_errors(self, limit: int = 50) -> List[Dict[str, Any]]:
"""Gibt kürzliche Fehler zurück"""
with self.lock:
recent = self.error_history[-limit:] if limit else self.error_history
return [{
"timestamp": err.timestamp.isoformat(),
"pattern_name": err.pattern_name,
"error_message": err.error_message,
"severity": err.severity.value,
"context": err.context,
"recovery_attempted": [action.value for action in err.recovery_attempted],
"recovery_successful": err.recovery_successful
} for err in recent]
# Globaler Error-Recovery-Manager
_error_recovery_manager: Optional[ErrorRecoveryManager] = None
_recovery_lock = threading.Lock()
def get_error_recovery_manager() -> ErrorRecoveryManager:
"""
Singleton-Pattern für globalen Error-Recovery-Manager.
Returns:
ErrorRecoveryManager: Globaler Error-Recovery-Manager
"""
global _error_recovery_manager
with _recovery_lock:
if _error_recovery_manager is None:
_error_recovery_manager = ErrorRecoveryManager()
return _error_recovery_manager
def start_error_monitoring():
"""Startet Error-Monitoring"""
manager = get_error_recovery_manager()
manager.start_monitoring()
def stop_error_monitoring():
"""Stoppt Error-Monitoring"""
manager = get_error_recovery_manager()
manager.stop_monitoring()
def force_error_check(log_message: str = None):
"""Erzwingt manuelle Fehlerprüfung"""
if log_message:
manager = get_error_recovery_manager()
manager._analyze_log_line(log_message, "manual_check")

View File

@ -0,0 +1,658 @@
#!/usr/bin/env python3
"""
Robuste System-Control-Funktionen für wartungsfreien Produktionsbetrieb
Bietet sichere Restart-, Shutdown- und Kiosk-Verwaltungsfunktionen
"""
import os
import sys
import subprocess
import time
import signal
import psutil
import threading
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Tuple, Any
from pathlib import Path
import logging
import json
from contextlib import contextmanager
from enum import Enum
# Logging-Setup
try:
from utils.logging_config import get_logger
system_logger = get_logger("system_control")
except ImportError:
logging.basicConfig(level=logging.INFO)
system_logger = logging.getLogger("system_control")
class SystemOperation(Enum):
"""Verfügbare System-Operationen"""
RESTART = "restart"
SHUTDOWN = "shutdown"
KIOSK_RESTART = "kiosk_restart"
KIOSK_ENABLE = "kiosk_enable"
KIOSK_DISABLE = "kiosk_disable"
SERVICE_RESTART = "service_restart"
EMERGENCY_STOP = "emergency_stop"
class SystemControlManager:
"""
Zentraler Manager für alle System-Control-Operationen.
Bietet sichere und robuste Funktionen für wartungsfreien Betrieb.
"""
def __init__(self):
self.is_windows = os.name == 'nt'
self.pending_operations: Dict[str, Dict] = {}
self.operation_history: List[Dict] = []
self.lock = threading.Lock()
# Konfiguration
self.config = {
"restart_delay": 60, # Sekunden
"shutdown_delay": 30, # Sekunden
"kiosk_restart_delay": 10, # Sekunden
"max_operation_history": 100,
"safety_checks": True,
"require_confirmation": True
}
# Service-Namen für verschiedene Plattformen
self.services = {
"https": "myp-https.service",
"kiosk": "myp-kiosk.service",
"watchdog": "kiosk-watchdog.service"
}
system_logger.info("🔧 System-Control-Manager initialisiert")
def is_safe_to_operate(self) -> Tuple[bool, str]:
"""
Prüft ob System-Operationen sicher ausgeführt werden können.
Returns:
Tuple[bool, str]: (is_safe, reason)
"""
try:
# Prüfe Systemlast
load_avg = psutil.getloadavg()[0] if hasattr(psutil, 'getloadavg') else 0
if load_avg > 2.0:
return False, f"Hohe Systemlast: {load_avg:.2f}"
# Prüfe verfügbaren Speicher
memory = psutil.virtual_memory()
if memory.percent > 90:
return False, f"Wenig verfügbarer Speicher: {memory.percent:.1f}% belegt"
# Prüfe aktive Drucker-Jobs
try:
from models import get_db_session, Job
db_session = get_db_session()
active_jobs = db_session.query(Job).filter(
Job.status.in_(["printing", "queued", "preparing"])
).count()
db_session.close()
if active_jobs > 0:
return False, f"Aktive Druckjobs: {active_jobs}"
except Exception as e:
system_logger.warning(f"Job-Prüfung fehlgeschlagen: {e}")
# Prüfe kritische Prozesse
critical_processes = ["chromium", "firefox", "python"]
for proc in psutil.process_iter(['pid', 'name', 'cpu_percent']):
try:
if any(crit in proc.info['name'].lower() for crit in critical_processes):
if proc.info['cpu_percent'] > 80:
return False, f"Kritischer Prozess unter hoher Last: {proc.info['name']}"
except (psutil.NoSuchProcess, psutil.AccessDenied):
continue
return True, "System ist sicher für Operationen"
except Exception as e:
system_logger.error(f"Fehler bei Sicherheitsprüfung: {e}")
return False, f"Sicherheitsprüfung fehlgeschlagen: {e}"
def schedule_operation(self,
operation: SystemOperation,
delay_seconds: int = None,
user_id: str = None,
reason: str = None,
force: bool = False) -> Dict[str, Any]:
"""
Plant eine System-Operation mit Verzögerung.
Args:
operation: Art der Operation
delay_seconds: Verzögerung in Sekunden (None = Standard)
user_id: ID des anfragenden Benutzers
reason: Grund für die Operation
force: Sicherheitsprüfungen überspringen
Returns:
Dict mit Operation-Details
"""
with self.lock:
# Sicherheitsprüfung (außer bei Force)
if not force and self.config["safety_checks"]:
is_safe, safety_reason = self.is_safe_to_operate()
if not is_safe:
return {
"success": False,
"error": f"Operation abgelehnt: {safety_reason}",
"safety_check": False
}
# Standard-Verzögerung setzen
if delay_seconds is None:
delay_seconds = {
SystemOperation.RESTART: self.config["restart_delay"],
SystemOperation.SHUTDOWN: self.config["shutdown_delay"],
SystemOperation.KIOSK_RESTART: self.config["kiosk_restart_delay"],
SystemOperation.KIOSK_ENABLE: 5,
SystemOperation.KIOSK_DISABLE: 5,
SystemOperation.SERVICE_RESTART: 10,
SystemOperation.EMERGENCY_STOP: 0
}.get(operation, 30)
# Operations-ID generieren
operation_id = f"{operation.value}_{int(time.time())}"
scheduled_time = datetime.now() + timedelta(seconds=delay_seconds)
# Operation speichern
operation_data = {
"id": operation_id,
"operation": operation.value,
"scheduled_time": scheduled_time,
"delay_seconds": delay_seconds,
"user_id": user_id,
"reason": reason or "Keine Begründung angegeben",
"force": force,
"created_at": datetime.now(),
"status": "scheduled"
}
self.pending_operations[operation_id] = operation_data
# Operation in separatem Thread ausführen
thread = threading.Thread(
target=self._execute_delayed_operation,
args=(operation_id,),
daemon=True
)
thread.start()
system_logger.info(f"🕐 Operation geplant: {operation.value} in {delay_seconds}s")
return {
"success": True,
"operation_id": operation_id,
"scheduled_time": scheduled_time.isoformat(),
"delay_seconds": delay_seconds,
"message": f"Operation '{operation.value}' geplant für {scheduled_time.strftime('%H:%M:%S')}"
}
def _execute_delayed_operation(self, operation_id: str):
"""
Führt geplante Operation nach Verzögerung aus.
Args:
operation_id: ID der auszuführenden Operation
"""
try:
operation_data = self.pending_operations.get(operation_id)
if not operation_data:
return
# Warten bis zur geplanten Zeit
scheduled_time = operation_data["scheduled_time"]
wait_time = (scheduled_time - datetime.now()).total_seconds()
if wait_time > 0:
time.sleep(wait_time)
# Status aktualisieren
operation_data["status"] = "executing"
operation_data["executed_at"] = datetime.now()
# Operation ausführen
operation = SystemOperation(operation_data["operation"])
result = self._execute_operation(operation, operation_data)
# Ergebnis speichern
operation_data["result"] = result
operation_data["status"] = "completed" if result.get("success") else "failed"
operation_data["completed_at"] = datetime.now()
# In Historie verschieben
self._move_to_history(operation_id)
except Exception as e:
system_logger.error(f"Fehler bei verzögerter Operation {operation_id}: {e}")
if operation_id in self.pending_operations:
self.pending_operations[operation_id]["status"] = "error"
self.pending_operations[operation_id]["error"] = str(e)
self._move_to_history(operation_id)
def _execute_operation(self, operation: SystemOperation, operation_data: Dict) -> Dict[str, Any]:
"""
Führt die eigentliche System-Operation aus.
Args:
operation: Art der Operation
operation_data: Operation-Daten
Returns:
Dict mit Ergebnis
"""
try:
system_logger.info(f"▶️ Führe Operation aus: {operation.value}")
if operation == SystemOperation.RESTART:
return self._restart_system(operation_data)
elif operation == SystemOperation.SHUTDOWN:
return self._shutdown_system(operation_data)
elif operation == SystemOperation.KIOSK_RESTART:
return self._restart_kiosk(operation_data)
elif operation == SystemOperation.KIOSK_ENABLE:
return self._enable_kiosk(operation_data)
elif operation == SystemOperation.KIOSK_DISABLE:
return self._disable_kiosk(operation_data)
elif operation == SystemOperation.SERVICE_RESTART:
return self._restart_services(operation_data)
elif operation == SystemOperation.EMERGENCY_STOP:
return self._emergency_stop(operation_data)
else:
return {"success": False, "error": f"Unbekannte Operation: {operation.value}"}
except Exception as e:
system_logger.error(f"Fehler bei Operation {operation.value}: {e}")
return {"success": False, "error": str(e)}
def _restart_system(self, operation_data: Dict) -> Dict[str, Any]:
"""Startet das System neu."""
try:
system_logger.warning("🔄 System-Neustart wird ausgeführt...")
# Cleanup vor Neustart
self._cleanup_before_restart()
# System-Neustart je nach Plattform
if self.is_windows:
subprocess.run(["shutdown", "/r", "/t", "0"], check=True)
else:
subprocess.run(["sudo", "systemctl", "reboot"], check=True)
return {"success": True, "message": "System-Neustart initiiert"}
except subprocess.CalledProcessError as e:
return {"success": False, "error": f"Neustart fehlgeschlagen: {e}"}
except Exception as e:
return {"success": False, "error": f"Unerwarteter Fehler: {e}"}
def _shutdown_system(self, operation_data: Dict) -> Dict[str, Any]:
"""Fährt das System herunter."""
try:
system_logger.warning("🛑 System-Shutdown wird ausgeführt...")
# Cleanup vor Shutdown
self._cleanup_before_restart()
# System-Shutdown je nach Plattform
if self.is_windows:
subprocess.run(["shutdown", "/s", "/t", "0"], check=True)
else:
subprocess.run(["sudo", "systemctl", "poweroff"], check=True)
return {"success": True, "message": "System-Shutdown initiiert"}
except subprocess.CalledProcessError as e:
return {"success": False, "error": f"Shutdown fehlgeschlagen: {e}"}
except Exception as e:
return {"success": False, "error": f"Unerwarteter Fehler: {e}"}
def _restart_kiosk(self, operation_data: Dict) -> Dict[str, Any]:
"""Startet nur den Kiosk-Modus neu."""
try:
system_logger.info("🖥️ Kiosk-Neustart wird ausgeführt...")
success_count = 0
errors = []
# Kiosk-Service neustarten
try:
subprocess.run(["sudo", "systemctl", "restart", self.services["kiosk"]],
check=True, timeout=30)
success_count += 1
system_logger.info("✅ Kiosk-Service neugestartet")
except Exception as e:
errors.append(f"Kiosk-Service: {e}")
# Watchdog-Service neustarten (falls vorhanden)
try:
subprocess.run(["sudo", "systemctl", "restart", self.services["watchdog"]],
check=True, timeout=30)
success_count += 1
system_logger.info("✅ Watchdog-Service neugestartet")
except Exception as e:
errors.append(f"Watchdog-Service: {e}")
# X11-Session neustarten
try:
subprocess.run(["sudo", "systemctl", "restart", "getty@tty1.service"],
check=True, timeout=30)
success_count += 1
system_logger.info("✅ X11-Session neugestartet")
except Exception as e:
errors.append(f"X11-Session: {e}")
if success_count > 0:
return {
"success": True,
"message": f"Kiosk neugestartet ({success_count} Services)",
"errors": errors if errors else None
}
else:
return {
"success": False,
"error": "Alle Kiosk-Neustarts fehlgeschlagen",
"details": errors
}
except Exception as e:
return {"success": False, "error": f"Kiosk-Neustart fehlgeschlagen: {e}"}
def _enable_kiosk(self, operation_data: Dict) -> Dict[str, Any]:
"""Aktiviert den Kiosk-Modus."""
try:
system_logger.info("🖥️ Kiosk-Modus wird aktiviert...")
# Kiosk-Service aktivieren und starten
subprocess.run(["sudo", "systemctl", "enable", self.services["kiosk"]],
check=True, timeout=30)
subprocess.run(["sudo", "systemctl", "start", self.services["kiosk"]],
check=True, timeout=30)
# Watchdog aktivieren
try:
subprocess.run(["sudo", "systemctl", "enable", self.services["watchdog"]],
check=True, timeout=30)
subprocess.run(["sudo", "systemctl", "start", self.services["watchdog"]],
check=True, timeout=30)
except Exception as e:
system_logger.warning(f"Watchdog-Aktivierung fehlgeschlagen: {e}")
return {"success": True, "message": "Kiosk-Modus aktiviert"}
except subprocess.CalledProcessError as e:
return {"success": False, "error": f"Kiosk-Aktivierung fehlgeschlagen: {e}"}
except Exception as e:
return {"success": False, "error": f"Unerwarteter Fehler: {e}"}
def _disable_kiosk(self, operation_data: Dict) -> Dict[str, Any]:
"""Deaktiviert den Kiosk-Modus."""
try:
system_logger.info("🖥️ Kiosk-Modus wird deaktiviert...")
# Kiosk-Service stoppen und deaktivieren
subprocess.run(["sudo", "systemctl", "stop", self.services["kiosk"]],
check=True, timeout=30)
subprocess.run(["sudo", "systemctl", "disable", self.services["kiosk"]],
check=True, timeout=30)
# Watchdog stoppen
try:
subprocess.run(["sudo", "systemctl", "stop", self.services["watchdog"]],
check=True, timeout=30)
subprocess.run(["sudo", "systemctl", "disable", self.services["watchdog"]],
check=True, timeout=30)
except Exception as e:
system_logger.warning(f"Watchdog-Deaktivierung fehlgeschlagen: {e}")
return {"success": True, "message": "Kiosk-Modus deaktiviert"}
except subprocess.CalledProcessError as e:
return {"success": False, "error": f"Kiosk-Deaktivierung fehlgeschlagen: {e}"}
except Exception as e:
return {"success": False, "error": f"Unerwarteter Fehler: {e}"}
def _restart_services(self, operation_data: Dict) -> Dict[str, Any]:
"""Startet wichtige Services neu."""
try:
system_logger.info("🔄 Services werden neugestartet...")
success_count = 0
errors = []
# HTTPS-Service neustarten
try:
subprocess.run(["sudo", "systemctl", "restart", self.services["https"]],
check=True, timeout=60)
success_count += 1
system_logger.info("✅ HTTPS-Service neugestartet")
except Exception as e:
errors.append(f"HTTPS-Service: {e}")
# NetworkManager neustarten (falls nötig)
try:
subprocess.run(["sudo", "systemctl", "restart", "NetworkManager"],
check=True, timeout=30)
success_count += 1
system_logger.info("✅ NetworkManager neugestartet")
except Exception as e:
errors.append(f"NetworkManager: {e}")
if success_count > 0:
return {
"success": True,
"message": f"Services neugestartet ({success_count})",
"errors": errors if errors else None
}
else:
return {
"success": False,
"error": "Alle Service-Neustarts fehlgeschlagen",
"details": errors
}
except Exception as e:
return {"success": False, "error": f"Service-Neustart fehlgeschlagen: {e}"}
def _emergency_stop(self, operation_data: Dict) -> Dict[str, Any]:
"""Notfall-Stopp aller Services."""
try:
system_logger.warning("🚨 Notfall-Stopp wird ausgeführt...")
# Flask-App stoppen
try:
os.kill(os.getpid(), signal.SIGTERM)
except Exception as e:
system_logger.error(f"Flask-Stopp fehlgeschlagen: {e}")
return {"success": True, "message": "Notfall-Stopp initiiert"}
except Exception as e:
return {"success": False, "error": f"Notfall-Stopp fehlgeschlagen: {e}"}
def _cleanup_before_restart(self):
"""Führt Cleanup-Operationen vor Neustart/Shutdown aus."""
try:
system_logger.info("🧹 Cleanup vor Neustart/Shutdown...")
# Shutdown-Manager verwenden falls verfügbar
try:
from utils.shutdown_manager import get_shutdown_manager
shutdown_manager = get_shutdown_manager()
shutdown_manager.shutdown(exit_code=0)
except ImportError:
system_logger.warning("Shutdown-Manager nicht verfügbar")
# Datenbank-Cleanup
try:
from utils.database_cleanup import safe_database_cleanup
safe_database_cleanup(force_mode_switch=False)
except ImportError:
system_logger.warning("Database-Cleanup nicht verfügbar")
# Cache leeren
self._clear_caches()
except Exception as e:
system_logger.error(f"Cleanup fehlgeschlagen: {e}")
def _clear_caches(self):
"""Leert alle Caches."""
try:
# User-Cache leeren
from app import clear_user_cache, clear_printer_status_cache
clear_user_cache()
clear_printer_status_cache()
# System-Cache leeren
if not self.is_windows:
subprocess.run(["sudo", "sync"], timeout=10)
subprocess.run(["sudo", "echo", "3", ">", "/proc/sys/vm/drop_caches"],
shell=True, timeout=10)
except Exception as e:
system_logger.warning(f"Cache-Clearing fehlgeschlagen: {e}")
def _move_to_history(self, operation_id: str):
"""Verschiebt abgeschlossene Operation in Historie."""
with self.lock:
if operation_id in self.pending_operations:
operation_data = self.pending_operations.pop(operation_id)
self.operation_history.append(operation_data)
# Historie begrenzen
if len(self.operation_history) > self.config["max_operation_history"]:
self.operation_history = self.operation_history[-self.config["max_operation_history"]:]
def cancel_operation(self, operation_id: str) -> Dict[str, Any]:
"""
Bricht geplante Operation ab.
Args:
operation_id: ID der abzubrechenden Operation
Returns:
Dict mit Ergebnis
"""
with self.lock:
if operation_id not in self.pending_operations:
return {"success": False, "error": "Operation nicht gefunden"}
operation_data = self.pending_operations[operation_id]
if operation_data["status"] == "executing":
return {"success": False, "error": "Operation bereits in Ausführung"}
operation_data["status"] = "cancelled"
operation_data["cancelled_at"] = datetime.now()
self._move_to_history(operation_id)
system_logger.info(f"❌ Operation abgebrochen: {operation_id}")
return {"success": True, "message": "Operation erfolgreich abgebrochen"}
def get_pending_operations(self) -> List[Dict]:
"""Gibt alle geplanten Operationen zurück."""
with self.lock:
return list(self.pending_operations.values())
def get_operation_history(self, limit: int = 20) -> List[Dict]:
"""Gibt Operation-Historie zurück."""
with self.lock:
return self.operation_history[-limit:] if limit else self.operation_history
def get_system_status(self) -> Dict[str, Any]:
"""Gibt aktuellen System-Status zurück."""
try:
# Service-Status prüfen
service_status = {}
for name, service in self.services.items():
try:
result = subprocess.run(
["sudo", "systemctl", "is-active", service],
capture_output=True, text=True, timeout=10
)
service_status[name] = result.stdout.strip()
except Exception as e:
service_status[name] = f"error: {e}"
# System-Metriken
memory = psutil.virtual_memory()
disk = psutil.disk_usage('/')
# Aktive Operations
pending_ops = len(self.pending_operations)
return {
"success": True,
"timestamp": datetime.now().isoformat(),
"services": service_status,
"system_metrics": {
"memory_percent": memory.percent,
"memory_available_gb": memory.available / (1024**3),
"disk_percent": disk.percent,
"disk_free_gb": disk.free / (1024**3),
"load_average": psutil.getloadavg()[0] if hasattr(psutil, 'getloadavg') else 0
},
"operations": {
"pending": pending_ops,
"history_count": len(self.operation_history)
},
"is_safe": self.is_safe_to_operate()[0]
}
except Exception as e:
return {"success": False, "error": str(e)}
# Globaler System-Control-Manager
_system_control_manager: Optional[SystemControlManager] = None
_control_lock = threading.Lock()
def get_system_control_manager() -> SystemControlManager:
"""
Singleton-Pattern für globalen System-Control-Manager.
Returns:
SystemControlManager: Globaler System-Control-Manager
"""
global _system_control_manager
with _control_lock:
if _system_control_manager is None:
_system_control_manager = SystemControlManager()
return _system_control_manager
# Convenience-Funktionen
def schedule_system_restart(delay_seconds: int = 60, user_id: str = None, reason: str = None, force: bool = False) -> Dict[str, Any]:
"""Plant System-Neustart."""
manager = get_system_control_manager()
return manager.schedule_operation(SystemOperation.RESTART, delay_seconds, user_id, reason, force)
def schedule_system_shutdown(delay_seconds: int = 30, user_id: str = None, reason: str = None, force: bool = False) -> Dict[str, Any]:
"""Plant System-Shutdown."""
manager = get_system_control_manager()
return manager.schedule_operation(SystemOperation.SHUTDOWN, delay_seconds, user_id, reason, force)
def restart_kiosk(delay_seconds: int = 10, user_id: str = None, reason: str = None) -> Dict[str, Any]:
"""Plant Kiosk-Neustart."""
manager = get_system_control_manager()
return manager.schedule_operation(SystemOperation.KIOSK_RESTART, delay_seconds, user_id, reason)
def get_system_status() -> Dict[str, Any]:
"""Gibt System-Status zurück."""
manager = get_system_control_manager()
return manager.get_system_status()