🎉 Feature: Implement MASSIVE KONSOLIDIERUNG PLAN in backend utils
This commit is contained in:
1
MASSIVE_KONSOLIDIERUNG_PLAN.md
Normal file
1
MASSIVE_KONSOLIDIERUNG_PLAN.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
822
backend/utils/core_system.py
Normal file
822
backend/utils/core_system.py
Normal file
@@ -0,0 +1,822 @@
|
|||||||
|
#!/usr/bin/env python3.11
|
||||||
|
"""
|
||||||
|
Core System Management - Massive Konsolidierung
|
||||||
|
==============================================
|
||||||
|
|
||||||
|
Konsolidiert alle System-Management-Funktionalitäten in einer Datei:
|
||||||
|
- System Control (system_control.py)
|
||||||
|
- Shutdown Manager (shutdown_manager.py)
|
||||||
|
- Watchdog Manager (watchdog_manager.py)
|
||||||
|
- Windows Fixes (windows_fixes.py)
|
||||||
|
- Error Recovery (error_recovery.py)
|
||||||
|
- Timeout Force Quit Manager (timeout_force_quit_manager.py)
|
||||||
|
|
||||||
|
Migration: 6 Dateien → 1 Datei
|
||||||
|
Autor: MYP Team - Massive Konsolidierung für IHK-Projektarbeit
|
||||||
|
Datum: 2025-06-09
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import signal
|
||||||
|
import threading
|
||||||
|
import subprocess
|
||||||
|
import platform
|
||||||
|
import traceback
|
||||||
|
import shutil
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from typing import Dict, List, Any, Optional, Tuple, Union, Callable
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from enum import Enum
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from utils.logging_config import get_logger
|
||||||
|
|
||||||
|
# ===== UNIFIED LOGGER =====
|
||||||
|
core_logger = get_logger("core_system")
|
||||||
|
|
||||||
|
# ===== ENUMS =====
|
||||||
|
|
||||||
|
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 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"
|
||||||
|
|
||||||
|
# ===== DATA CLASSES =====
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ErrorPattern:
|
||||||
|
"""Definiert ein Fehlermuster und zugehörige Recovery-Aktionen"""
|
||||||
|
name: str
|
||||||
|
patterns: List[str] # Regex-Patterns
|
||||||
|
severity: ErrorSeverity
|
||||||
|
actions: List[RecoveryAction]
|
||||||
|
max_occurrences: int = 3
|
||||||
|
time_window: int = 300 # Sekunden
|
||||||
|
escalation_actions: List[RecoveryAction] = None
|
||||||
|
description: str = ""
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ErrorOccurrence:
|
||||||
|
"""Einzelnes Auftreten eines Fehlers"""
|
||||||
|
timestamp: datetime
|
||||||
|
pattern_name: str
|
||||||
|
error_message: str
|
||||||
|
severity: ErrorSeverity
|
||||||
|
context: Dict[str, Any] = None
|
||||||
|
recovery_attempted: List[RecoveryAction] = None
|
||||||
|
recovery_successful: bool = False
|
||||||
|
|
||||||
|
# ===== CORE SYSTEM MANAGER =====
|
||||||
|
|
||||||
|
class CoreSystemManager:
|
||||||
|
"""
|
||||||
|
Zentraler System-Manager für alle kritischen System-Operationen.
|
||||||
|
Konsolidiert System Control, Shutdown Management, Error Recovery und Windows-Fixes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, timeout: int = 30):
|
||||||
|
self.timeout = timeout
|
||||||
|
self.shutdown_requested = False
|
||||||
|
self.shutdown_time = None
|
||||||
|
self.components = {}
|
||||||
|
self.cleanup_functions = []
|
||||||
|
self.pending_operations = {}
|
||||||
|
self.operation_history = []
|
||||||
|
|
||||||
|
# Error Recovery
|
||||||
|
self.error_patterns = {}
|
||||||
|
self.error_occurrences = []
|
||||||
|
self.recovery_handlers = {}
|
||||||
|
self.monitoring_active = False
|
||||||
|
self.monitoring_thread = None
|
||||||
|
|
||||||
|
# Windows Thread Management
|
||||||
|
self.registered_threads = []
|
||||||
|
self.cleanup_callbacks = []
|
||||||
|
|
||||||
|
self._init_default_patterns()
|
||||||
|
self._init_recovery_handlers()
|
||||||
|
self._register_signal_handlers()
|
||||||
|
self._apply_platform_fixes()
|
||||||
|
|
||||||
|
core_logger.info("🚀 Core System Manager initialisiert")
|
||||||
|
|
||||||
|
# ===== SYSTEM CONTROL =====
|
||||||
|
|
||||||
|
def is_safe_to_operate(self) -> Tuple[bool, str]:
|
||||||
|
"""Prüft, ob es sicher ist, System-Operationen durchzuführen"""
|
||||||
|
try:
|
||||||
|
from models import get_cached_session, Job
|
||||||
|
|
||||||
|
with get_cached_session() as session:
|
||||||
|
# Aktive Jobs prüfen
|
||||||
|
active_jobs = session.query(Job).filter(
|
||||||
|
Job.status.in_(['printing', 'paused'])
|
||||||
|
).count()
|
||||||
|
|
||||||
|
if active_jobs > 0:
|
||||||
|
return False, f"{active_jobs} aktive Jobs laufen noch"
|
||||||
|
|
||||||
|
# System-Load prüfen
|
||||||
|
if hasattr(os, 'getloadavg'):
|
||||||
|
load = os.getloadavg()[0]
|
||||||
|
if load > 2.0:
|
||||||
|
return False, f"System-Load zu hoch: {load:.1f}"
|
||||||
|
|
||||||
|
return True, "System bereit für Operationen"
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return False, f"Sicherheitsprüfung fehlgeschlagen: {str(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"""
|
||||||
|
operation_id = f"{operation.value}_{int(time.time())}"
|
||||||
|
|
||||||
|
if not force:
|
||||||
|
safe, message = self.is_safe_to_operate()
|
||||||
|
if not safe:
|
||||||
|
return {
|
||||||
|
'success': False,
|
||||||
|
'message': f"Operation nicht sicher: {message}",
|
||||||
|
'operation_id': None
|
||||||
|
}
|
||||||
|
|
||||||
|
operation_data = {
|
||||||
|
'id': operation_id,
|
||||||
|
'operation': operation,
|
||||||
|
'scheduled_at': datetime.now(),
|
||||||
|
'execute_at': datetime.now() + timedelta(seconds=delay_seconds or 5),
|
||||||
|
'user_id': user_id,
|
||||||
|
'reason': reason or f"Geplante {operation.value}",
|
||||||
|
'force': force,
|
||||||
|
'status': 'scheduled'
|
||||||
|
}
|
||||||
|
|
||||||
|
self.pending_operations[operation_id] = operation_data
|
||||||
|
|
||||||
|
if delay_seconds and delay_seconds > 0:
|
||||||
|
# Verzögerte Ausführung
|
||||||
|
timer = threading.Timer(delay_seconds, self._execute_delayed_operation, [operation_id])
|
||||||
|
timer.daemon = True
|
||||||
|
timer.start()
|
||||||
|
operation_data['timer'] = timer
|
||||||
|
else:
|
||||||
|
# Sofortige Ausführung
|
||||||
|
self._execute_delayed_operation(operation_id)
|
||||||
|
|
||||||
|
core_logger.info(f"System-Operation geplant: {operation.value} (ID: {operation_id})")
|
||||||
|
return {
|
||||||
|
'success': True,
|
||||||
|
'message': f"Operation {operation.value} geplant",
|
||||||
|
'operation_id': operation_id,
|
||||||
|
'execute_at': operation_data['execute_at'].isoformat()
|
||||||
|
}
|
||||||
|
|
||||||
|
def _execute_delayed_operation(self, operation_id: str):
|
||||||
|
"""Führt eine verzögerte Operation aus"""
|
||||||
|
if operation_id not in self.pending_operations:
|
||||||
|
return
|
||||||
|
|
||||||
|
operation_data = self.pending_operations[operation_id]
|
||||||
|
operation = operation_data['operation']
|
||||||
|
|
||||||
|
try:
|
||||||
|
operation_data['status'] = 'executing'
|
||||||
|
operation_data['started_at'] = datetime.now()
|
||||||
|
|
||||||
|
core_logger.info(f"Führe System-Operation aus: {operation.value}")
|
||||||
|
|
||||||
|
result = self._execute_operation(operation, operation_data)
|
||||||
|
operation_data.update(result)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
operation_data['status'] = 'failed'
|
||||||
|
operation_data['error'] = str(e)
|
||||||
|
core_logger.error(f"System-Operation fehlgeschlagen: {operation.value} - {str(e)}")
|
||||||
|
finally:
|
||||||
|
operation_data['completed_at'] = datetime.now()
|
||||||
|
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"""
|
||||||
|
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.EMERGENCY_STOP:
|
||||||
|
return self._emergency_stop(operation_data)
|
||||||
|
else:
|
||||||
|
return {'status': 'failed', 'message': f'Unbekannte Operation: {operation.value}'}
|
||||||
|
|
||||||
|
def _restart_system(self, operation_data: Dict) -> Dict[str, Any]:
|
||||||
|
"""Startet das System neu"""
|
||||||
|
try:
|
||||||
|
self._cleanup_before_restart()
|
||||||
|
core_logger.info("System-Neustart wird eingeleitet...")
|
||||||
|
|
||||||
|
if platform.system() == "Windows":
|
||||||
|
subprocess.run(['shutdown', '/r', '/t', '5'], check=True)
|
||||||
|
else:
|
||||||
|
subprocess.run(['sudo', 'reboot'], check=True)
|
||||||
|
|
||||||
|
return {'status': 'success', 'message': 'System-Neustart eingeleitet'}
|
||||||
|
except Exception as e:
|
||||||
|
return {'status': 'failed', 'message': f'Neustart fehlgeschlagen: {str(e)}'}
|
||||||
|
|
||||||
|
def _shutdown_system(self, operation_data: Dict) -> Dict[str, Any]:
|
||||||
|
"""Fährt das System herunter"""
|
||||||
|
try:
|
||||||
|
self._cleanup_before_restart()
|
||||||
|
core_logger.info("System-Herunterfahren wird eingeleitet...")
|
||||||
|
|
||||||
|
if platform.system() == "Windows":
|
||||||
|
subprocess.run(['shutdown', '/s', '/t', '5'], check=True)
|
||||||
|
else:
|
||||||
|
subprocess.run(['sudo', 'shutdown', 'now'], check=True)
|
||||||
|
|
||||||
|
return {'status': 'success', 'message': 'System-Herunterfahren eingeleitet'}
|
||||||
|
except Exception as e:
|
||||||
|
return {'status': 'failed', 'message': f'Herunterfahren fehlgeschlagen: {str(e)}'}
|
||||||
|
|
||||||
|
def _emergency_stop(self, operation_data: Dict) -> Dict[str, Any]:
|
||||||
|
"""Führt einen Notfall-Stop durch"""
|
||||||
|
try:
|
||||||
|
core_logger.critical("NOTFALL-STOP eingeleitet!")
|
||||||
|
self._force_shutdown_all_threads()
|
||||||
|
self._cleanup_before_restart()
|
||||||
|
os._exit(1)
|
||||||
|
except Exception as e:
|
||||||
|
return {'status': 'failed', 'message': f'Notfall-Stop fehlgeschlagen: {str(e)}'}
|
||||||
|
|
||||||
|
def _cleanup_before_restart(self):
|
||||||
|
"""Bereinigung vor Neustart/Herunterfahren"""
|
||||||
|
try:
|
||||||
|
# Database WAL cleanup
|
||||||
|
from utils.database_core import database_service
|
||||||
|
database_service.cleanup.perform_wal_checkpoint()
|
||||||
|
|
||||||
|
# Clear caches
|
||||||
|
self._clear_caches()
|
||||||
|
|
||||||
|
# Stop all registered components
|
||||||
|
self._shutdown_components()
|
||||||
|
|
||||||
|
core_logger.info("Bereinigung vor Neustart abgeschlossen")
|
||||||
|
except Exception as e:
|
||||||
|
core_logger.error(f"Fehler bei Bereinigung: {str(e)}")
|
||||||
|
|
||||||
|
def _clear_caches(self):
|
||||||
|
"""Löscht System-Caches"""
|
||||||
|
try:
|
||||||
|
cache_dirs = ['/tmp/myp_cache', '/var/cache/myp']
|
||||||
|
for cache_dir in cache_dirs:
|
||||||
|
if os.path.exists(cache_dir):
|
||||||
|
shutil.rmtree(cache_dir, ignore_errors=True)
|
||||||
|
except Exception as e:
|
||||||
|
core_logger.warning(f"Cache-Bereinigung teilweise fehlgeschlagen: {str(e)}")
|
||||||
|
|
||||||
|
# ===== SHUTDOWN MANAGEMENT =====
|
||||||
|
|
||||||
|
def register_component(self, name: str, component: Any, stop_method: str = "stop"):
|
||||||
|
"""Registriert eine Komponente für ordnungsgemäßes Herunterfahren"""
|
||||||
|
self.components[name] = {
|
||||||
|
'component': component,
|
||||||
|
'stop_method': stop_method,
|
||||||
|
'priority': 1
|
||||||
|
}
|
||||||
|
core_logger.debug(f"Komponente '{name}' für Shutdown registriert")
|
||||||
|
|
||||||
|
def register_cleanup_function(self, func: Callable, name: str, priority: int = 1,
|
||||||
|
timeout: int = 10, args: tuple = (), kwargs: dict = None):
|
||||||
|
"""Registriert eine Bereinigungsfunktion"""
|
||||||
|
self.cleanup_functions.append({
|
||||||
|
'function': func,
|
||||||
|
'name': name,
|
||||||
|
'priority': priority,
|
||||||
|
'timeout': timeout,
|
||||||
|
'args': args or (),
|
||||||
|
'kwargs': kwargs or {}
|
||||||
|
})
|
||||||
|
core_logger.debug(f"Cleanup-Funktion '{name}' registriert")
|
||||||
|
|
||||||
|
def shutdown(self, exit_code: int = 0):
|
||||||
|
"""Führt ordnungsgemäßes Herunterfahren durch"""
|
||||||
|
if self.shutdown_requested:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.shutdown_requested = True
|
||||||
|
self.shutdown_time = datetime.now()
|
||||||
|
|
||||||
|
core_logger.info("🔄 Ordnungsgemäßes Herunterfahren wird eingeleitet...")
|
||||||
|
|
||||||
|
self._shutdown_components()
|
||||||
|
self._execute_cleanup_functions()
|
||||||
|
|
||||||
|
core_logger.info("✅ Herunterfahren abgeschlossen")
|
||||||
|
sys.exit(exit_code)
|
||||||
|
|
||||||
|
def _shutdown_components(self):
|
||||||
|
"""Fährt alle registrierten Komponenten herunter"""
|
||||||
|
for name, component_info in self.components.items():
|
||||||
|
try:
|
||||||
|
component = component_info['component']
|
||||||
|
stop_method = component_info['stop_method']
|
||||||
|
|
||||||
|
if hasattr(component, stop_method):
|
||||||
|
getattr(component, stop_method)()
|
||||||
|
core_logger.debug(f"Komponente '{name}' erfolgreich heruntergefahren")
|
||||||
|
else:
|
||||||
|
core_logger.warning(f"Komponente '{name}' hat keine '{stop_method}' Methode")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
core_logger.error(f"Fehler beim Herunterfahren von '{name}': {str(e)}")
|
||||||
|
|
||||||
|
def _execute_cleanup_functions(self):
|
||||||
|
"""Führt alle Cleanup-Funktionen aus"""
|
||||||
|
# Nach Priorität sortieren
|
||||||
|
sorted_functions = sorted(self.cleanup_functions, key=lambda x: x['priority'])
|
||||||
|
|
||||||
|
for cleanup_info in sorted_functions:
|
||||||
|
try:
|
||||||
|
with self.cleanup_timeout(cleanup_info['timeout'], cleanup_info['name']):
|
||||||
|
cleanup_info['function'](*cleanup_info['args'], **cleanup_info['kwargs'])
|
||||||
|
core_logger.debug(f"Cleanup '{cleanup_info['name']}' erfolgreich")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
core_logger.error(f"Cleanup '{cleanup_info['name']}' fehlgeschlagen: {str(e)}")
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def cleanup_timeout(self, timeout: int, operation_name: str):
|
||||||
|
"""Context Manager für Timeout bei Cleanup-Operationen"""
|
||||||
|
def timeout_handler(signum, frame):
|
||||||
|
raise TimeoutError(f"Timeout bei {operation_name}")
|
||||||
|
|
||||||
|
if platform.system() != "Windows":
|
||||||
|
old_handler = signal.signal(signal.SIGALRM, timeout_handler)
|
||||||
|
signal.alarm(timeout)
|
||||||
|
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
if platform.system() != "Windows":
|
||||||
|
signal.alarm(0)
|
||||||
|
signal.signal(signal.SIGALRM, old_handler)
|
||||||
|
|
||||||
|
# ===== ERROR RECOVERY =====
|
||||||
|
|
||||||
|
def _init_default_patterns(self):
|
||||||
|
"""Initialisiert Standard-Fehlermuster"""
|
||||||
|
patterns = [
|
||||||
|
ErrorPattern(
|
||||||
|
name="database_lock",
|
||||||
|
patterns=[r"database.*locked", r"sqlite.*busy"],
|
||||||
|
severity=ErrorSeverity.HIGH,
|
||||||
|
actions=[RecoveryAction.CLEAR_CACHE, RecoveryAction.RESTART_COMPONENT],
|
||||||
|
description="Datenbank-Sperren"
|
||||||
|
),
|
||||||
|
ErrorPattern(
|
||||||
|
name="memory_exhaustion",
|
||||||
|
patterns=[r"out of memory", r"memory.*exhausted"],
|
||||||
|
severity=ErrorSeverity.CRITICAL,
|
||||||
|
actions=[RecoveryAction.RESTART_SYSTEM],
|
||||||
|
description="Speicher-Erschöpfung"
|
||||||
|
),
|
||||||
|
ErrorPattern(
|
||||||
|
name="network_timeout",
|
||||||
|
patterns=[r"timeout.*network", r"connection.*timed out"],
|
||||||
|
severity=ErrorSeverity.MEDIUM,
|
||||||
|
actions=[RecoveryAction.RESTART_SERVICE],
|
||||||
|
description="Netzwerk-Timeouts"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
for pattern in patterns:
|
||||||
|
self.error_patterns[pattern.name] = pattern
|
||||||
|
|
||||||
|
def _init_recovery_handlers(self):
|
||||||
|
"""Initialisiert Recovery-Handler"""
|
||||||
|
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_error_monitoring(self):
|
||||||
|
"""Startet Error-Monitoring"""
|
||||||
|
if self.monitoring_active:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.monitoring_active = True
|
||||||
|
self.monitoring_thread = threading.Thread(target=self._monitor_loop, daemon=True)
|
||||||
|
self.monitoring_thread.start()
|
||||||
|
core_logger.info("Error-Monitoring gestartet")
|
||||||
|
|
||||||
|
def stop_error_monitoring(self):
|
||||||
|
"""Stoppt Error-Monitoring"""
|
||||||
|
self.monitoring_active = False
|
||||||
|
if self.monitoring_thread:
|
||||||
|
self.monitoring_thread.join(timeout=5)
|
||||||
|
core_logger.info("Error-Monitoring gestoppt")
|
||||||
|
|
||||||
|
def _monitor_loop(self):
|
||||||
|
"""Haupt-Monitoring-Schleife"""
|
||||||
|
while self.monitoring_active:
|
||||||
|
try:
|
||||||
|
self._check_log_files()
|
||||||
|
self._check_system_metrics()
|
||||||
|
self._cleanup_old_entries()
|
||||||
|
time.sleep(30) # Alle 30 Sekunden prüfen
|
||||||
|
except Exception as e:
|
||||||
|
core_logger.error(f"Monitoring-Fehler: {str(e)}")
|
||||||
|
time.sleep(60) # Bei Fehlern länger warten
|
||||||
|
|
||||||
|
def _check_log_files(self):
|
||||||
|
"""Überprüft Log-Dateien auf Fehlermuster"""
|
||||||
|
try:
|
||||||
|
from utils.settings import LOG_DIR
|
||||||
|
|
||||||
|
if not os.path.exists(LOG_DIR):
|
||||||
|
return
|
||||||
|
|
||||||
|
for log_subdir in os.listdir(LOG_DIR):
|
||||||
|
log_file = os.path.join(LOG_DIR, log_subdir, f"{log_subdir}.log")
|
||||||
|
|
||||||
|
if os.path.exists(log_file):
|
||||||
|
# Nur die letzten 100 Zeilen lesen
|
||||||
|
with open(log_file, 'r', encoding='utf-8', errors='ignore') as f:
|
||||||
|
lines = f.readlines()[-100:]
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
self._analyze_log_line(line, log_subdir)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
core_logger.debug(f"Log-Datei-Prüfung fehlgeschlagen: {str(e)}")
|
||||||
|
|
||||||
|
def _analyze_log_line(self, line: str, source: str):
|
||||||
|
"""Analysiert eine Log-Zeile auf Fehlermuster"""
|
||||||
|
import re
|
||||||
|
|
||||||
|
for pattern_name, pattern in self.error_patterns.items():
|
||||||
|
for regex in pattern.patterns:
|
||||||
|
if re.search(regex, line, re.IGNORECASE):
|
||||||
|
self._handle_error_detection(pattern_name, line, {'source': source})
|
||||||
|
return
|
||||||
|
|
||||||
|
def _handle_error_detection(self, pattern_name: str, error_message: str, context: Dict[str, Any] = None):
|
||||||
|
"""Behandelt erkannte Fehler"""
|
||||||
|
pattern = self.error_patterns[pattern_name]
|
||||||
|
|
||||||
|
# Prüfen, ob zu viele Fehler in kurzer Zeit aufgetreten sind
|
||||||
|
recent_count = self._count_recent_occurrences(pattern_name, pattern.time_window)
|
||||||
|
|
||||||
|
occurrence = ErrorOccurrence(
|
||||||
|
timestamp=datetime.now(),
|
||||||
|
pattern_name=pattern_name,
|
||||||
|
error_message=error_message,
|
||||||
|
severity=pattern.severity,
|
||||||
|
context=context or {}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.error_occurrences.append(occurrence)
|
||||||
|
|
||||||
|
if recent_count >= pattern.max_occurrences:
|
||||||
|
# Eskalation
|
||||||
|
actions = pattern.escalation_actions or [RecoveryAction.EMERGENCY_STOP]
|
||||||
|
core_logger.critical(f"Fehler-Eskalation für {pattern_name}: {recent_count} Vorkommen")
|
||||||
|
else:
|
||||||
|
actions = pattern.actions
|
||||||
|
|
||||||
|
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 len([occ for occ in self.error_occurrences
|
||||||
|
if occ.pattern_name == pattern_name and occ.timestamp > cutoff_time])
|
||||||
|
|
||||||
|
def _execute_recovery_actions(self, occurrence: ErrorOccurrence, actions: List[RecoveryAction]):
|
||||||
|
"""Führt Recovery-Aktionen aus"""
|
||||||
|
for action in actions:
|
||||||
|
if action in self.recovery_handlers:
|
||||||
|
try:
|
||||||
|
success = self.recovery_handlers[action](occurrence)
|
||||||
|
occurrence.recovery_attempted.append(action)
|
||||||
|
if success:
|
||||||
|
occurrence.recovery_successful = True
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
core_logger.error(f"Recovery-Aktion {action.value} fehlgeschlagen: {str(e)}")
|
||||||
|
|
||||||
|
def _handle_log_only(self, occurrence: ErrorOccurrence) -> bool:
|
||||||
|
"""Recovery: Nur Logging"""
|
||||||
|
core_logger.warning(f"Fehler erkannt ({occurrence.pattern_name}): {occurrence.error_message}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _handle_restart_service(self, occurrence: ErrorOccurrence) -> bool:
|
||||||
|
"""Recovery: Service-Neustart"""
|
||||||
|
try:
|
||||||
|
service_name = occurrence.context.get('service_name', 'myp-https')
|
||||||
|
subprocess.run(['sudo', 'systemctl', 'restart', service_name], check=True)
|
||||||
|
core_logger.info(f"Service {service_name} neu gestartet")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
core_logger.error(f"Service-Neustart fehlgeschlagen: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _handle_clear_cache(self, occurrence: ErrorOccurrence) -> bool:
|
||||||
|
"""Recovery: Cache leeren"""
|
||||||
|
try:
|
||||||
|
self._clear_caches()
|
||||||
|
core_logger.info("Cache erfolgreich geleert")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
core_logger.error(f"Cache-Bereinigung fehlgeschlagen: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _handle_restart_system(self, occurrence: ErrorOccurrence) -> bool:
|
||||||
|
"""Recovery: System-Neustart"""
|
||||||
|
try:
|
||||||
|
self.schedule_operation(SystemOperation.RESTART, delay_seconds=30,
|
||||||
|
reason=f"Error Recovery: {occurrence.pattern_name}", force=True)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
core_logger.error(f"System-Neustart fehlgeschlagen: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _handle_emergency_stop(self, occurrence: ErrorOccurrence) -> bool:
|
||||||
|
"""Recovery: Notfall-Stop"""
|
||||||
|
core_logger.critical(f"NOTFALL-STOP wegen: {occurrence.pattern_name}")
|
||||||
|
self._emergency_stop({})
|
||||||
|
return True
|
||||||
|
|
||||||
|
# ===== WINDOWS-SPEZIFISCHE FIXES =====
|
||||||
|
|
||||||
|
def _apply_platform_fixes(self):
|
||||||
|
"""Wendet plattformspezifische Fixes an"""
|
||||||
|
if platform.system() == "Windows":
|
||||||
|
self._apply_windows_fixes()
|
||||||
|
else:
|
||||||
|
self._apply_unix_fixes()
|
||||||
|
|
||||||
|
def _apply_windows_fixes(self):
|
||||||
|
"""Windows-spezifische Fixes"""
|
||||||
|
try:
|
||||||
|
# UTF-8 Encoding
|
||||||
|
import locale
|
||||||
|
try:
|
||||||
|
locale.setlocale(locale.LC_ALL, 'German_Germany.1252')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Socket-Fixes
|
||||||
|
import socket
|
||||||
|
if hasattr(socket, 'SO_REUSEADDR'):
|
||||||
|
original_bind = socket.socket.bind
|
||||||
|
def windows_bind_with_reuse(self, address):
|
||||||
|
self.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
return original_bind(self, address)
|
||||||
|
socket.socket.bind = windows_bind_with_reuse
|
||||||
|
|
||||||
|
core_logger.info("Windows-Fixes angewendet")
|
||||||
|
except Exception as e:
|
||||||
|
core_logger.warning(f"Windows-Fixes teilweise fehlgeschlagen: {str(e)}")
|
||||||
|
|
||||||
|
def _apply_unix_fixes(self):
|
||||||
|
"""Unix-spezifische Optimierungen"""
|
||||||
|
try:
|
||||||
|
# Umgebungsvariablen setzen
|
||||||
|
os.environ.setdefault('PYTHONIOENCODING', 'utf-8')
|
||||||
|
core_logger.debug("Unix-Optimierungen angewendet")
|
||||||
|
except Exception as e:
|
||||||
|
core_logger.warning(f"Unix-Optimierungen fehlgeschlagen: {str(e)}")
|
||||||
|
|
||||||
|
# ===== THREAD MANAGEMENT =====
|
||||||
|
|
||||||
|
def register_thread(self, thread: threading.Thread):
|
||||||
|
"""Registriert einen Thread für sauberes Shutdown"""
|
||||||
|
self.registered_threads.append(thread)
|
||||||
|
core_logger.debug(f"Thread registriert: {thread.name}")
|
||||||
|
|
||||||
|
def register_cleanup_callback(self, func: Callable):
|
||||||
|
"""Registriert Callback für Cleanup"""
|
||||||
|
self.cleanup_callbacks.append(func)
|
||||||
|
|
||||||
|
def _force_shutdown_all_threads(self):
|
||||||
|
"""Erzwingt Shutdown aller registrierten Threads"""
|
||||||
|
for thread in self.registered_threads:
|
||||||
|
if thread.is_alive():
|
||||||
|
try:
|
||||||
|
# Versuche graceful shutdown
|
||||||
|
if hasattr(thread, 'stop'):
|
||||||
|
thread.stop()
|
||||||
|
|
||||||
|
thread.join(timeout=2)
|
||||||
|
|
||||||
|
if thread.is_alive():
|
||||||
|
core_logger.warning(f"Thread {thread.name} reagiert nicht - erzwinge Beendigung")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
core_logger.error(f"Fehler beim Thread-Shutdown: {str(e)}")
|
||||||
|
|
||||||
|
# Cleanup-Callbacks ausführen
|
||||||
|
for callback in self.cleanup_callbacks:
|
||||||
|
try:
|
||||||
|
callback()
|
||||||
|
except Exception as e:
|
||||||
|
core_logger.error(f"Cleanup-Callback fehlgeschlagen: {str(e)}")
|
||||||
|
|
||||||
|
# ===== SIGNAL HANDLING =====
|
||||||
|
|
||||||
|
def _register_signal_handlers(self):
|
||||||
|
"""Registriert Signal-Handler für verschiedene Signale"""
|
||||||
|
if platform.system() != "Windows":
|
||||||
|
signal.signal(signal.SIGTERM, self._signal_handler)
|
||||||
|
signal.signal(signal.SIGINT, self._signal_handler)
|
||||||
|
signal.signal(signal.SIGHUP, self._signal_handler)
|
||||||
|
else:
|
||||||
|
signal.signal(signal.SIGTERM, self._signal_handler)
|
||||||
|
signal.signal(signal.SIGINT, self._signal_handler)
|
||||||
|
|
||||||
|
def _signal_handler(self, signum, frame):
|
||||||
|
"""Handler für System-Signale"""
|
||||||
|
signal_names = {2: 'SIGINT', 15: 'SIGTERM', 1: 'SIGHUP'}
|
||||||
|
signal_name = signal_names.get(signum, f'Signal {signum}')
|
||||||
|
|
||||||
|
core_logger.info(f"📡 {signal_name} empfangen - leite ordnungsgemäßes Herunterfahren ein")
|
||||||
|
self.shutdown(0)
|
||||||
|
|
||||||
|
def _cleanup_old_entries(self):
|
||||||
|
"""Bereinigt alte Error-Occurrences"""
|
||||||
|
cutoff_time = datetime.now() - timedelta(hours=24)
|
||||||
|
self.error_occurrences = [
|
||||||
|
occ for occ in self.error_occurrences
|
||||||
|
if occ.timestamp > cutoff_time
|
||||||
|
]
|
||||||
|
|
||||||
|
def _move_to_history(self, operation_id: str):
|
||||||
|
"""Verschiebt Operation in Historie"""
|
||||||
|
if operation_id in self.pending_operations:
|
||||||
|
operation = self.pending_operations.pop(operation_id)
|
||||||
|
self.operation_history.append(operation)
|
||||||
|
|
||||||
|
# Nur die letzten 50 Operationen behalten
|
||||||
|
self.operation_history = self.operation_history[-50:]
|
||||||
|
|
||||||
|
# ===== STATUS & MONITORING =====
|
||||||
|
|
||||||
|
def get_system_status(self) -> Dict[str, Any]:
|
||||||
|
"""Gibt umfassenden System-Status zurück"""
|
||||||
|
return {
|
||||||
|
'shutdown_requested': self.shutdown_requested,
|
||||||
|
'shutdown_time': self.shutdown_time.isoformat() if self.shutdown_time else None,
|
||||||
|
'registered_components': len(self.components),
|
||||||
|
'cleanup_functions': len(self.cleanup_functions),
|
||||||
|
'pending_operations': len(self.pending_operations),
|
||||||
|
'error_monitoring_active': self.monitoring_active,
|
||||||
|
'error_patterns': len(self.error_patterns),
|
||||||
|
'recent_errors': len([occ for occ in self.error_occurrences
|
||||||
|
if occ.timestamp > datetime.now() - timedelta(hours=1)]),
|
||||||
|
'registered_threads': len(self.registered_threads),
|
||||||
|
'platform': platform.system(),
|
||||||
|
'platform_fixes_applied': True
|
||||||
|
}
|
||||||
|
|
||||||
|
# ===== GLOBALE INSTANZ =====
|
||||||
|
|
||||||
|
# Singleton-Pattern für Core System Manager
|
||||||
|
_core_system_manager = None
|
||||||
|
|
||||||
|
def get_core_system_manager(timeout: int = 30) -> CoreSystemManager:
|
||||||
|
"""Gibt die globale CoreSystemManager-Instanz zurück"""
|
||||||
|
global _core_system_manager
|
||||||
|
if _core_system_manager is None:
|
||||||
|
_core_system_manager = CoreSystemManager(timeout)
|
||||||
|
return _core_system_manager
|
||||||
|
|
||||||
|
# ===== CONVENIENCE FUNCTIONS =====
|
||||||
|
|
||||||
|
def schedule_system_restart(delay_seconds: int = 60, user_id: str = None,
|
||||||
|
reason: str = None, force: bool = False) -> Dict[str, Any]:
|
||||||
|
"""Convenience-Funktion für System-Neustart"""
|
||||||
|
manager = get_core_system_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]:
|
||||||
|
"""Convenience-Funktion für System-Herunterfahren"""
|
||||||
|
manager = get_core_system_manager()
|
||||||
|
return manager.schedule_operation(SystemOperation.SHUTDOWN, delay_seconds, user_id, reason, force)
|
||||||
|
|
||||||
|
def register_for_shutdown(component_or_function, name: str,
|
||||||
|
component_stop_method: str = "stop", priority: int = 1,
|
||||||
|
timeout: int = 10):
|
||||||
|
"""Registriert Komponente oder Funktion für Shutdown"""
|
||||||
|
manager = get_core_system_manager()
|
||||||
|
|
||||||
|
if callable(component_or_function):
|
||||||
|
manager.register_cleanup_function(
|
||||||
|
component_or_function, name, priority, timeout
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
manager.register_component(name, component_or_function, component_stop_method)
|
||||||
|
|
||||||
|
def shutdown_application(exit_code: int = 0):
|
||||||
|
"""Fährt die Anwendung ordnungsgemäß herunter"""
|
||||||
|
manager = get_core_system_manager()
|
||||||
|
manager.shutdown(exit_code)
|
||||||
|
|
||||||
|
def start_error_monitoring():
|
||||||
|
"""Startet Error-Monitoring"""
|
||||||
|
manager = get_core_system_manager()
|
||||||
|
manager.start_error_monitoring()
|
||||||
|
|
||||||
|
def stop_error_monitoring():
|
||||||
|
"""Stoppt Error-Monitoring"""
|
||||||
|
manager = get_core_system_manager()
|
||||||
|
manager.stop_error_monitoring()
|
||||||
|
|
||||||
|
def get_system_status() -> Dict[str, Any]:
|
||||||
|
"""Gibt System-Status zurück"""
|
||||||
|
manager = get_core_system_manager()
|
||||||
|
return manager.get_system_status()
|
||||||
|
|
||||||
|
def is_shutdown_requested() -> bool:
|
||||||
|
"""Prüft, ob Shutdown angefordert wurde"""
|
||||||
|
manager = get_core_system_manager()
|
||||||
|
return manager.shutdown_requested
|
||||||
|
|
||||||
|
# ===== INITIALIZATION =====
|
||||||
|
|
||||||
|
# Auto-Initialisierung beim Import
|
||||||
|
core_logger.info("🔧 Core System Manager wird initialisiert...")
|
||||||
|
|
||||||
|
# Legacy-Kompatibilität: Alte Funktionen automatisch verfügbar machen
|
||||||
|
def apply_all_windows_fixes():
|
||||||
|
"""Legacy-Kompatibilität für Windows-Fixes"""
|
||||||
|
manager = get_core_system_manager()
|
||||||
|
if platform.system() == "Windows":
|
||||||
|
manager._apply_windows_fixes()
|
||||||
|
|
||||||
|
def fix_windows_socket_issues():
|
||||||
|
"""Legacy-Kompatibilität für Socket-Fixes"""
|
||||||
|
apply_all_windows_fixes()
|
||||||
|
|
||||||
|
# CLI Interface
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
command = sys.argv[1]
|
||||||
|
manager = get_core_system_manager()
|
||||||
|
|
||||||
|
if command == "status":
|
||||||
|
status = manager.get_system_status()
|
||||||
|
print("=== Core System Status ===")
|
||||||
|
for key, value in status.items():
|
||||||
|
print(f"{key}: {value}")
|
||||||
|
elif command == "restart":
|
||||||
|
result = schedule_system_restart(delay_seconds=10)
|
||||||
|
print(f"Restart scheduled: {result}")
|
||||||
|
elif command == "monitor":
|
||||||
|
print("Starting error monitoring...")
|
||||||
|
start_error_monitoring()
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
time.sleep(1)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("Stopping monitoring...")
|
||||||
|
stop_error_monitoring()
|
||||||
|
else:
|
||||||
|
print("Available commands: status, restart, monitor")
|
||||||
|
else:
|
||||||
|
print("Core System Manager - Available commands: status, restart, monitor")
|
509
backend/utils/hardware_integration.py
Normal file
509
backend/utils/hardware_integration.py
Normal file
@@ -0,0 +1,509 @@
|
|||||||
|
#!/usr/bin/env python3.11
|
||||||
|
"""
|
||||||
|
Hardware Integration - Konsolidierte Hardware-Steuerung
|
||||||
|
====================================================
|
||||||
|
|
||||||
|
Konsolidiert alle Hardware-Integration-Funktionalitäten:
|
||||||
|
- TP-Link Tapo Controller (tapo_controller.py)
|
||||||
|
- Printer Monitor (printer_monitor.py)
|
||||||
|
|
||||||
|
Migration: 2 Dateien → 1 Datei
|
||||||
|
Autor: MYP Team - Konsolidierung für IHK-Projektarbeit
|
||||||
|
Datum: 2025-06-09
|
||||||
|
"""
|
||||||
|
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import socket
|
||||||
|
import subprocess
|
||||||
|
import platform
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from typing import Dict, List, Any, Optional, Tuple
|
||||||
|
from utils.logging_config import get_logger
|
||||||
|
|
||||||
|
# Logger
|
||||||
|
hardware_logger = get_logger("hardware_integration")
|
||||||
|
|
||||||
|
# ===== TAPO SMART PLUG CONTROLLER =====
|
||||||
|
|
||||||
|
class TapoController:
|
||||||
|
"""TP-Link Tapo Smart Plug Controller"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.default_username = "till.tomczak@mercedes-benz.com"
|
||||||
|
self.default_password = "744563017196A"
|
||||||
|
hardware_logger.info("🔌 Tapo Controller initialisiert")
|
||||||
|
|
||||||
|
def toggle_plug(self, ip: str, state: bool, username: str = None, password: str = None) -> bool:
|
||||||
|
"""Schaltet eine Tapo-Steckdose ein oder aus"""
|
||||||
|
try:
|
||||||
|
from PyP100 import PyP110
|
||||||
|
|
||||||
|
username = username or self.default_username
|
||||||
|
password = password or self.default_password
|
||||||
|
|
||||||
|
p110 = PyP110.P110(ip, username, password)
|
||||||
|
p110.handshake()
|
||||||
|
p110.login()
|
||||||
|
|
||||||
|
if state:
|
||||||
|
p110.turnOn()
|
||||||
|
hardware_logger.info(f"✅ Tapo {ip} eingeschaltet")
|
||||||
|
else:
|
||||||
|
p110.turnOff()
|
||||||
|
hardware_logger.info(f"❌ Tapo {ip} ausgeschaltet")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
hardware_logger.error(f"Tapo-Steuerung fehlgeschlagen {ip}: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def check_outlet_status(self, ip: str, username: str = None, password: str = None,
|
||||||
|
printer_id: int = None) -> Tuple[bool, str]:
|
||||||
|
"""Prüft den Status einer Tapo-Steckdose"""
|
||||||
|
try:
|
||||||
|
from PyP100 import PyP110
|
||||||
|
|
||||||
|
username = username or self.default_username
|
||||||
|
password = password or self.default_password
|
||||||
|
|
||||||
|
p110 = PyP110.P110(ip, username, password)
|
||||||
|
p110.handshake()
|
||||||
|
p110.login()
|
||||||
|
|
||||||
|
device_info = p110.getDeviceInfo()
|
||||||
|
is_on = device_info.get('device_on', False)
|
||||||
|
status = "Ein" if is_on else "Aus"
|
||||||
|
|
||||||
|
return is_on, status
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
hardware_logger.error(f"Tapo-Status-Prüfung fehlgeschlagen {ip}: {str(e)}")
|
||||||
|
return False, "Fehler"
|
||||||
|
|
||||||
|
# ===== PRINTER MONITOR =====
|
||||||
|
|
||||||
|
class PrinterMonitor:
|
||||||
|
"""3D-Drucker Monitoring System"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.tapo_controller = TapoController()
|
||||||
|
self.status_cache = {}
|
||||||
|
self.cache_timeout = timedelta(minutes=2)
|
||||||
|
self.last_cache_update = None
|
||||||
|
hardware_logger.info("🖨️ Printer Monitor initialisiert")
|
||||||
|
|
||||||
|
def get_live_printer_status(self, use_session_cache: bool = True) -> Dict[int, Dict]:
|
||||||
|
"""Holt Live-Status aller Drucker"""
|
||||||
|
try:
|
||||||
|
# Cache-Prüfung
|
||||||
|
if (use_session_cache and self.last_cache_update and
|
||||||
|
datetime.now() - self.last_cache_update < self.cache_timeout):
|
||||||
|
return self.status_cache
|
||||||
|
|
||||||
|
# Fresh status fetch
|
||||||
|
status = self._fetch_live_printer_status()
|
||||||
|
|
||||||
|
# Cache aktualisieren
|
||||||
|
self.status_cache = status
|
||||||
|
self.last_cache_update = datetime.now()
|
||||||
|
|
||||||
|
return status
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
hardware_logger.error(f"Live-Status-Abfrage fehlgeschlagen: {str(e)}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def _fetch_live_printer_status(self) -> Dict[int, Dict]:
|
||||||
|
"""Holt tatsächlichen Live-Status"""
|
||||||
|
try:
|
||||||
|
from models import get_cached_session, Printer
|
||||||
|
|
||||||
|
status_dict = {}
|
||||||
|
|
||||||
|
with get_cached_session() as session:
|
||||||
|
printers = session.query(Printer).filter(Printer.active == True).all()
|
||||||
|
|
||||||
|
for printer in printers:
|
||||||
|
status = self._check_single_printer_status(printer)
|
||||||
|
status_dict[printer.id] = status
|
||||||
|
|
||||||
|
hardware_logger.debug(f"Status für {len(status_dict)} Drucker abgerufen")
|
||||||
|
return status_dict
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
hardware_logger.error(f"Drucker-Status-Abfrage fehlgeschlagen: {str(e)}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def _check_single_printer_status(self, printer) -> Dict:
|
||||||
|
"""Prüft den Status eines einzelnen Druckers"""
|
||||||
|
status = {
|
||||||
|
'printer_id': printer.id,
|
||||||
|
'name': printer.name,
|
||||||
|
'ip_address': printer.ip_address,
|
||||||
|
'plug_ip': printer.plug_ip,
|
||||||
|
'ping_status': 'unknown',
|
||||||
|
'outlet_status': 'unknown',
|
||||||
|
'outlet_on': False,
|
||||||
|
'last_checked': datetime.now(),
|
||||||
|
'error_message': None
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Ping-Test
|
||||||
|
if printer.ip_address:
|
||||||
|
ping_ok = self._ping_address(printer.ip_address)
|
||||||
|
status['ping_status'] = 'online' if ping_ok else 'offline'
|
||||||
|
|
||||||
|
# Outlet-Status prüfen
|
||||||
|
if printer.plug_ip:
|
||||||
|
outlet_on, outlet_status = self.tapo_controller.check_outlet_status(
|
||||||
|
printer.plug_ip, printer.plug_username, printer.plug_password, printer.id
|
||||||
|
)
|
||||||
|
status['outlet_on'] = outlet_on
|
||||||
|
status['outlet_status'] = outlet_status
|
||||||
|
|
||||||
|
# Gesamtstatus bestimmen
|
||||||
|
if status['ping_status'] == 'online' and status['outlet_on']:
|
||||||
|
status['overall_status'] = 'online'
|
||||||
|
elif status['outlet_on']:
|
||||||
|
status['overall_status'] = 'booting'
|
||||||
|
else:
|
||||||
|
status['overall_status'] = 'offline'
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
status['error_message'] = str(e)
|
||||||
|
status['overall_status'] = 'error'
|
||||||
|
hardware_logger.error(f"Drucker-Status-Prüfung fehlgeschlagen {printer.name}: {str(e)}")
|
||||||
|
|
||||||
|
return status
|
||||||
|
|
||||||
|
def _ping_address(self, ip_address: str, timeout: int = 3) -> bool:
|
||||||
|
"""Pingt eine IP-Adresse an"""
|
||||||
|
try:
|
||||||
|
if platform.system().lower() == "windows":
|
||||||
|
result = subprocess.run(
|
||||||
|
['ping', '-n', '1', '-w', str(timeout * 1000), ip_address],
|
||||||
|
capture_output=True, text=True, timeout=timeout + 2,
|
||||||
|
encoding='utf-8', errors='replace'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
result = subprocess.run(
|
||||||
|
['ping', '-c', '1', '-W', str(timeout), ip_address],
|
||||||
|
capture_output=True, text=True, timeout=timeout + 2,
|
||||||
|
encoding='utf-8', errors='replace'
|
||||||
|
)
|
||||||
|
|
||||||
|
return result.returncode == 0
|
||||||
|
|
||||||
|
except (subprocess.TimeoutExpired, FileNotFoundError):
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
hardware_logger.debug(f"Ping fehlgeschlagen {ip_address}: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def initialize_all_outlets_on_startup(self) -> Dict[str, bool]:
|
||||||
|
"""Initialisiert alle Outlets beim Systemstart"""
|
||||||
|
try:
|
||||||
|
from models import get_cached_session, Printer
|
||||||
|
|
||||||
|
results = {}
|
||||||
|
|
||||||
|
with get_cached_session() as session:
|
||||||
|
printers = session.query(Printer).filter(
|
||||||
|
Printer.active == True,
|
||||||
|
Printer.plug_ip.isnot(None)
|
||||||
|
).all()
|
||||||
|
|
||||||
|
hardware_logger.info(f"Initialisiere {len(printers)} Drucker-Outlets...")
|
||||||
|
|
||||||
|
for printer in printers:
|
||||||
|
try:
|
||||||
|
# Outlet ausschalten als Standardzustand
|
||||||
|
success = self.tapo_controller.toggle_plug(
|
||||||
|
printer.plug_ip, False,
|
||||||
|
printer.plug_username, printer.plug_password
|
||||||
|
)
|
||||||
|
results[printer.name] = success
|
||||||
|
|
||||||
|
if success:
|
||||||
|
hardware_logger.info(f"✅ {printer.name} Outlet initialisiert (AUS)")
|
||||||
|
else:
|
||||||
|
hardware_logger.warning(f"⚠️ {printer.name} Outlet-Initialisierung fehlgeschlagen")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
results[printer.name] = False
|
||||||
|
hardware_logger.error(f"❌ {printer.name} Outlet-Fehler: {str(e)}")
|
||||||
|
|
||||||
|
success_count = sum(1 for success in results.values() if success)
|
||||||
|
hardware_logger.info(f"Outlet-Initialisierung: {success_count}/{len(results)} erfolgreich")
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
hardware_logger.error(f"Outlet-Initialisierung fehlgeschlagen: {str(e)}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def get_printer_summary(self) -> Dict[str, int]:
|
||||||
|
"""Gibt eine Zusammenfassung des Drucker-Status zurück"""
|
||||||
|
try:
|
||||||
|
status_dict = self.get_live_printer_status()
|
||||||
|
|
||||||
|
summary = {
|
||||||
|
'total': len(status_dict),
|
||||||
|
'online': 0,
|
||||||
|
'offline': 0,
|
||||||
|
'booting': 0,
|
||||||
|
'error': 0,
|
||||||
|
'outlets_on': 0,
|
||||||
|
'outlets_off': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
for printer_status in status_dict.values():
|
||||||
|
overall_status = printer_status.get('overall_status', 'unknown')
|
||||||
|
|
||||||
|
if overall_status in summary:
|
||||||
|
summary[overall_status] += 1
|
||||||
|
|
||||||
|
if printer_status.get('outlet_on'):
|
||||||
|
summary['outlets_on'] += 1
|
||||||
|
else:
|
||||||
|
summary['outlets_off'] += 1
|
||||||
|
|
||||||
|
return summary
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
hardware_logger.error(f"Drucker-Zusammenfassung fehlgeschlagen: {str(e)}")
|
||||||
|
return {'total': 0, 'online': 0, 'offline': 0, 'error': 1}
|
||||||
|
|
||||||
|
def auto_discover_tapo_outlets(self) -> Dict[str, bool]:
|
||||||
|
"""Automatische Erkennung von Tapo-Outlets im Netzwerk"""
|
||||||
|
try:
|
||||||
|
discovered = {}
|
||||||
|
|
||||||
|
# Standard-IP-Bereiche scannen
|
||||||
|
base_ips = ["192.168.0.", "192.168.1.", "192.168.100."]
|
||||||
|
|
||||||
|
for base_ip in base_ips:
|
||||||
|
for i in range(100, 110): # Scan 100-109
|
||||||
|
ip = f"{base_ip}{i}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Schneller TCP-Port-Test
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
sock.settimeout(1)
|
||||||
|
result = sock.connect_ex((ip, 80))
|
||||||
|
sock.close()
|
||||||
|
|
||||||
|
if result == 0:
|
||||||
|
# Versuche Tapo-Verbindung
|
||||||
|
is_on, status = self.tapo_controller.check_outlet_status(ip)
|
||||||
|
if status != "Fehler":
|
||||||
|
discovered[ip] = True
|
||||||
|
hardware_logger.info(f"🔍 Tapo-Outlet entdeckt: {ip}")
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
pass # Ignoriere Fehler beim Scannen
|
||||||
|
|
||||||
|
hardware_logger.info(f"Auto-Discovery: {len(discovered)} Tapo-Outlets gefunden")
|
||||||
|
return discovered
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
hardware_logger.error(f"Auto-Discovery fehlgeschlagen: {str(e)}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def clear_all_caches(self):
|
||||||
|
"""Löscht alle Caches"""
|
||||||
|
self.status_cache.clear()
|
||||||
|
self.last_cache_update = None
|
||||||
|
hardware_logger.info("Alle Hardware-Caches geleert")
|
||||||
|
|
||||||
|
# ===== HARDWARE INTEGRATION MANAGER =====
|
||||||
|
|
||||||
|
class HardwareIntegrationManager:
|
||||||
|
"""Zentraler Manager für alle Hardware-Integrationen"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.tapo_controller = TapoController()
|
||||||
|
self.printer_monitor = PrinterMonitor()
|
||||||
|
self.monitoring_active = False
|
||||||
|
self.monitoring_thread = None
|
||||||
|
hardware_logger.info("🔧 Hardware Integration Manager initialisiert")
|
||||||
|
|
||||||
|
def start_monitoring(self):
|
||||||
|
"""Startet Hardware-Monitoring"""
|
||||||
|
if self.monitoring_active:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.monitoring_active = True
|
||||||
|
self.monitoring_thread = threading.Thread(target=self._monitoring_loop, daemon=True)
|
||||||
|
self.monitoring_thread.start()
|
||||||
|
hardware_logger.info("Hardware-Monitoring gestartet")
|
||||||
|
|
||||||
|
def stop_monitoring(self):
|
||||||
|
"""Stoppt Hardware-Monitoring"""
|
||||||
|
self.monitoring_active = False
|
||||||
|
if self.monitoring_thread:
|
||||||
|
self.monitoring_thread.join(timeout=5)
|
||||||
|
hardware_logger.info("Hardware-Monitoring gestoppt")
|
||||||
|
|
||||||
|
def _monitoring_loop(self):
|
||||||
|
"""Hauptschleife für Hardware-Monitoring"""
|
||||||
|
while self.monitoring_active:
|
||||||
|
try:
|
||||||
|
# Drucker-Status aktualisieren
|
||||||
|
self.printer_monitor.get_live_printer_status(use_session_cache=False)
|
||||||
|
|
||||||
|
# 60 Sekunden warten
|
||||||
|
for _ in range(60):
|
||||||
|
if not self.monitoring_active:
|
||||||
|
break
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
hardware_logger.error(f"Hardware-Monitoring-Fehler: {str(e)}")
|
||||||
|
time.sleep(30) # Bei Fehlern länger warten
|
||||||
|
|
||||||
|
def get_hardware_status(self) -> Dict[str, Any]:
|
||||||
|
"""Gibt umfassenden Hardware-Status zurück"""
|
||||||
|
try:
|
||||||
|
printer_summary = self.printer_monitor.get_printer_summary()
|
||||||
|
|
||||||
|
return {
|
||||||
|
'timestamp': datetime.now().isoformat(),
|
||||||
|
'monitoring_active': self.monitoring_active,
|
||||||
|
'printer_summary': printer_summary,
|
||||||
|
'tapo_controller_active': True,
|
||||||
|
'cache_status': {
|
||||||
|
'last_update': self.printer_monitor.last_cache_update.isoformat()
|
||||||
|
if self.printer_monitor.last_cache_update else None,
|
||||||
|
'cached_printers': len(self.printer_monitor.status_cache)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
hardware_logger.error(f"Hardware-Status-Abfrage fehlgeschlagen: {str(e)}")
|
||||||
|
return {'error': str(e), 'timestamp': datetime.now().isoformat()}
|
||||||
|
|
||||||
|
def initialize_system(self) -> Dict[str, Any]:
|
||||||
|
"""Initialisiert das gesamte Hardware-System"""
|
||||||
|
results = {
|
||||||
|
'outlet_initialization': {},
|
||||||
|
'auto_discovery': {},
|
||||||
|
'monitoring_started': False
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Outlets initialisieren
|
||||||
|
results['outlet_initialization'] = self.printer_monitor.initialize_all_outlets_on_startup()
|
||||||
|
|
||||||
|
# Auto-Discovery
|
||||||
|
results['auto_discovery'] = self.printer_monitor.auto_discover_tapo_outlets()
|
||||||
|
|
||||||
|
# Monitoring starten
|
||||||
|
self.start_monitoring()
|
||||||
|
results['monitoring_started'] = True
|
||||||
|
|
||||||
|
hardware_logger.info("Hardware-System erfolgreich initialisiert")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
hardware_logger.error(f"Hardware-System-Initialisierung fehlgeschlagen: {str(e)}")
|
||||||
|
results['error'] = str(e)
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
# ===== GLOBALE INSTANZ =====
|
||||||
|
|
||||||
|
# Singleton für Hardware Integration Manager
|
||||||
|
_hardware_manager = None
|
||||||
|
|
||||||
|
def get_hardware_manager() -> HardwareIntegrationManager:
|
||||||
|
"""Gibt die globale Hardware-Manager-Instanz zurück"""
|
||||||
|
global _hardware_manager
|
||||||
|
if _hardware_manager is None:
|
||||||
|
_hardware_manager = HardwareIntegrationManager()
|
||||||
|
return _hardware_manager
|
||||||
|
|
||||||
|
# ===== CONVENIENCE FUNCTIONS =====
|
||||||
|
|
||||||
|
def toggle_plug(ip: str, state: bool, username: str = None, password: str = None) -> bool:
|
||||||
|
"""Convenience-Funktion für Tapo-Steuerung"""
|
||||||
|
manager = get_hardware_manager()
|
||||||
|
return manager.tapo_controller.toggle_plug(ip, state, username, password)
|
||||||
|
|
||||||
|
def get_printer_status(printer_id: int = None) -> Dict:
|
||||||
|
"""Convenience-Funktion für Drucker-Status"""
|
||||||
|
manager = get_hardware_manager()
|
||||||
|
all_status = manager.printer_monitor.get_live_printer_status()
|
||||||
|
|
||||||
|
if printer_id:
|
||||||
|
return all_status.get(printer_id, {})
|
||||||
|
return all_status
|
||||||
|
|
||||||
|
def get_hardware_summary() -> Dict[str, Any]:
|
||||||
|
"""Convenience-Funktion für Hardware-Zusammenfassung"""
|
||||||
|
manager = get_hardware_manager()
|
||||||
|
return manager.get_hardware_status()
|
||||||
|
|
||||||
|
def initialize_hardware_system() -> Dict[str, Any]:
|
||||||
|
"""Convenience-Funktion für Hardware-Initialisierung"""
|
||||||
|
manager = get_hardware_manager()
|
||||||
|
return manager.initialize_system()
|
||||||
|
|
||||||
|
# Legacy-Kompatibilität
|
||||||
|
def check_outlet_status(ip: str, username: str = None, password: str = None,
|
||||||
|
printer_id: int = None) -> Tuple[bool, str]:
|
||||||
|
"""Legacy-Kompatibilität für Outlet-Status"""
|
||||||
|
manager = get_hardware_manager()
|
||||||
|
return manager.tapo_controller.check_outlet_status(ip, username, password, printer_id)
|
||||||
|
|
||||||
|
def auto_discover_tapo_outlets() -> Dict[str, bool]:
|
||||||
|
"""Legacy-Kompatibilität für Auto-Discovery"""
|
||||||
|
manager = get_hardware_manager()
|
||||||
|
return manager.printer_monitor.auto_discover_tapo_outlets()
|
||||||
|
|
||||||
|
def initialize_all_outlets() -> Dict[str, bool]:
|
||||||
|
"""Legacy-Kompatibilität für Outlet-Initialisierung"""
|
||||||
|
manager = get_hardware_manager()
|
||||||
|
return manager.printer_monitor.initialize_all_outlets_on_startup()
|
||||||
|
|
||||||
|
# CLI Interface
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
command = sys.argv[1]
|
||||||
|
manager = get_hardware_manager()
|
||||||
|
|
||||||
|
if command == "status":
|
||||||
|
status = manager.get_hardware_status()
|
||||||
|
print("=== Hardware Status ===")
|
||||||
|
for key, value in status.items():
|
||||||
|
print(f"{key}: {value}")
|
||||||
|
|
||||||
|
elif command == "discover":
|
||||||
|
print("Starte Auto-Discovery...")
|
||||||
|
discovered = manager.printer_monitor.auto_discover_tapo_outlets()
|
||||||
|
print(f"Gefundene Outlets: {discovered}")
|
||||||
|
|
||||||
|
elif command == "init":
|
||||||
|
print("Initialisiere Hardware-System...")
|
||||||
|
results = manager.initialize_system()
|
||||||
|
print(f"Initialisierung: {results}")
|
||||||
|
|
||||||
|
elif command == "monitor":
|
||||||
|
print("Starte Hardware-Monitoring...")
|
||||||
|
manager.start_monitoring()
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
time.sleep(10)
|
||||||
|
status = manager.get_hardware_status()
|
||||||
|
print(f"Status: {status['printer_summary']}")
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("Stoppe Monitoring...")
|
||||||
|
manager.stop_monitoring()
|
||||||
|
else:
|
||||||
|
print("Available commands: status, discover, init, monitor")
|
||||||
|
else:
|
||||||
|
print("Hardware Integration Manager - Available commands: status, discover, init, monitor")
|
@@ -1,442 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Zentraler Shutdown-Manager für robuste Anwendungsbeendigung
|
|
||||||
Koordiniert alle Cleanup-Operationen mit Timeouts und Fehlerbehandlung
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import signal
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
import atexit
|
|
||||||
from typing import List, Callable, Dict, Any, Optional
|
|
||||||
from datetime import datetime
|
|
||||||
from contextlib import contextmanager
|
|
||||||
import logging
|
|
||||||
|
|
||||||
# Logging-Setup mit Fallback
|
|
||||||
try:
|
|
||||||
from utils.logging_config import get_logger
|
|
||||||
shutdown_logger = get_logger("shutdown_manager")
|
|
||||||
except ImportError:
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
|
||||||
shutdown_logger = logging.getLogger("shutdown_manager")
|
|
||||||
|
|
||||||
|
|
||||||
class ShutdownManager:
|
|
||||||
"""
|
|
||||||
Zentraler Manager für ordnungsgemäße Anwendungsbeendigung.
|
|
||||||
Koordiniert alle Cleanup-Operationen mit Timeouts und Prioritäten.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, timeout: int = 30):
|
|
||||||
self.timeout = timeout
|
|
||||||
self.is_shutting_down = False
|
|
||||||
self.shutdown_started = None
|
|
||||||
self.cleanup_functions: List[Dict[str, Any]] = []
|
|
||||||
self.components: Dict[str, Any] = {}
|
|
||||||
self._lock = threading.Lock()
|
|
||||||
self._signal_handlers_registered = False
|
|
||||||
|
|
||||||
# Shutdown-Ereignis für Thread-Koordination
|
|
||||||
self.shutdown_event = threading.Event()
|
|
||||||
|
|
||||||
# Registriere grundlegende Signal-Handler
|
|
||||||
self._register_signal_handlers()
|
|
||||||
|
|
||||||
shutdown_logger.info("🔧 Shutdown-Manager initialisiert")
|
|
||||||
|
|
||||||
def _register_signal_handlers(self):
|
|
||||||
"""Registriert plattformspezifische Signal-Handler"""
|
|
||||||
if self._signal_handlers_registered:
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
if os.name == 'nt': # Windows
|
|
||||||
signal.signal(signal.SIGINT, self._signal_handler)
|
|
||||||
signal.signal(signal.SIGTERM, self._signal_handler)
|
|
||||||
if hasattr(signal, 'SIGBREAK'):
|
|
||||||
signal.signal(signal.SIGBREAK, self._signal_handler)
|
|
||||||
else: # Unix/Linux
|
|
||||||
signal.signal(signal.SIGINT, self._signal_handler)
|
|
||||||
signal.signal(signal.SIGTERM, self._signal_handler)
|
|
||||||
signal.signal(signal.SIGHUP, self._signal_handler)
|
|
||||||
|
|
||||||
self._signal_handlers_registered = True
|
|
||||||
shutdown_logger.debug("✅ Signal-Handler registriert")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
shutdown_logger.warning(f"⚠️ Signal-Handler konnten nicht registriert werden: {e}")
|
|
||||||
|
|
||||||
def _signal_handler(self, signum, frame):
|
|
||||||
"""Zentraler Signal-Handler für ordnungsgemäße Beendigung"""
|
|
||||||
if not self.is_shutting_down:
|
|
||||||
shutdown_logger.warning(f"🛑 Signal {signum} empfangen - starte koordiniertes Shutdown")
|
|
||||||
self.shutdown()
|
|
||||||
|
|
||||||
def register_component(self, name: str, component: Any, stop_method: str = "stop"):
|
|
||||||
"""
|
|
||||||
Registriert eine Komponente für ordnungsgemäße Beendigung
|
|
||||||
|
|
||||||
Args:
|
|
||||||
name: Name der Komponente
|
|
||||||
component: Komponenten-Objekt
|
|
||||||
stop_method: Name der Stopp-Methode (Standard: "stop")
|
|
||||||
"""
|
|
||||||
with self._lock:
|
|
||||||
self.components[name] = {
|
|
||||||
'component': component,
|
|
||||||
'stop_method': stop_method,
|
|
||||||
'priority': 1 # Standard-Priorität
|
|
||||||
}
|
|
||||||
shutdown_logger.debug(f"📝 Komponente '{name}' registriert")
|
|
||||||
|
|
||||||
def register_cleanup_function(self,
|
|
||||||
func: Callable,
|
|
||||||
name: str,
|
|
||||||
priority: int = 1,
|
|
||||||
timeout: int = 10,
|
|
||||||
args: tuple = (),
|
|
||||||
kwargs: dict = None):
|
|
||||||
"""
|
|
||||||
Registriert eine Cleanup-Funktion
|
|
||||||
|
|
||||||
Args:
|
|
||||||
func: Cleanup-Funktion
|
|
||||||
name: Beschreibender Name
|
|
||||||
priority: Priorität (1=hoch, 3=niedrig)
|
|
||||||
timeout: Timeout in Sekunden
|
|
||||||
args: Funktions-Argumente
|
|
||||||
kwargs: Funktions-Keyword-Argumente
|
|
||||||
"""
|
|
||||||
if kwargs is None:
|
|
||||||
kwargs = {}
|
|
||||||
|
|
||||||
cleanup_item = {
|
|
||||||
'function': func,
|
|
||||||
'name': name,
|
|
||||||
'priority': priority,
|
|
||||||
'timeout': timeout,
|
|
||||||
'args': args,
|
|
||||||
'kwargs': kwargs
|
|
||||||
}
|
|
||||||
|
|
||||||
with self._lock:
|
|
||||||
self.cleanup_functions.append(cleanup_item)
|
|
||||||
# Nach Priorität sortieren (1 = höchste Priorität zuerst)
|
|
||||||
self.cleanup_functions.sort(key=lambda x: x['priority'])
|
|
||||||
|
|
||||||
shutdown_logger.debug(f"📝 Cleanup-Funktion '{name}' registriert (Priorität: {priority})")
|
|
||||||
|
|
||||||
def register_queue_manager(self, queue_manager_module):
|
|
||||||
"""Registriert den Queue Manager für ordnungsgemäße Beendigung"""
|
|
||||||
try:
|
|
||||||
stop_func = getattr(queue_manager_module, 'stop_queue_manager', None)
|
|
||||||
if stop_func:
|
|
||||||
self.register_cleanup_function(
|
|
||||||
func=stop_func,
|
|
||||||
name="Queue Manager",
|
|
||||||
priority=1, # Hohe Priorität
|
|
||||||
timeout=15
|
|
||||||
)
|
|
||||||
shutdown_logger.debug("✅ Queue Manager registriert")
|
|
||||||
else:
|
|
||||||
shutdown_logger.warning("⚠️ Queue Manager stop_queue_manager Funktion nicht gefunden")
|
|
||||||
except Exception as e:
|
|
||||||
shutdown_logger.error(f"❌ Fehler beim Registrieren des Queue Managers: {e}")
|
|
||||||
|
|
||||||
def register_scheduler(self, scheduler, scheduler_enabled: bool = True):
|
|
||||||
"""Registriert den Job-Scheduler für ordnungsgemäße Beendigung"""
|
|
||||||
if not scheduler_enabled or not scheduler:
|
|
||||||
shutdown_logger.debug("Scheduler nicht registriert (deaktiviert oder nicht vorhanden)")
|
|
||||||
return
|
|
||||||
|
|
||||||
def stop_scheduler():
|
|
||||||
try:
|
|
||||||
if hasattr(scheduler, 'shutdown'):
|
|
||||||
shutdown_logger.info("🔄 Beende Scheduler mit shutdown()...")
|
|
||||||
scheduler.shutdown(wait=True)
|
|
||||||
elif hasattr(scheduler, 'stop'):
|
|
||||||
shutdown_logger.info("🔄 Beende Scheduler mit stop()...")
|
|
||||||
scheduler.stop()
|
|
||||||
else:
|
|
||||||
shutdown_logger.warning("⚠️ Scheduler hat keine bekannte Stop-Methode")
|
|
||||||
|
|
||||||
shutdown_logger.info("✅ Scheduler erfolgreich gestoppt")
|
|
||||||
except Exception as e:
|
|
||||||
shutdown_logger.error(f"❌ Fehler beim Stoppen des Schedulers: {e}")
|
|
||||||
|
|
||||||
self.register_cleanup_function(
|
|
||||||
func=stop_scheduler,
|
|
||||||
name="Job Scheduler",
|
|
||||||
priority=1, # Hohe Priorität
|
|
||||||
timeout=10
|
|
||||||
)
|
|
||||||
|
|
||||||
def register_database_cleanup(self):
|
|
||||||
"""Registriert Datenbank-Cleanup mit robuster Fehlerbehandlung"""
|
|
||||||
def safe_database_cleanup():
|
|
||||||
try:
|
|
||||||
shutdown_logger.info("💾 Führe sicheres Datenbank-Cleanup durch...")
|
|
||||||
|
|
||||||
# Versuche den neuen DatabaseCleanupManager zu verwenden
|
|
||||||
try:
|
|
||||||
from utils.database_cleanup import safe_database_cleanup
|
|
||||||
|
|
||||||
result = safe_database_cleanup(force_mode_switch=False) # Kein riskantes Mode-Switching
|
|
||||||
|
|
||||||
if result.get("success", False):
|
|
||||||
shutdown_logger.info(f"✅ Datenbank-Cleanup erfolgreich: {', '.join(result.get('operations', []))}")
|
|
||||||
else:
|
|
||||||
shutdown_logger.warning(f"⚠️ Datenbank-Cleanup mit Problemen: {', '.join(result.get('errors', []))}")
|
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
shutdown_logger.info("Fallback: Verwende einfaches WAL-Checkpoint...")
|
|
||||||
|
|
||||||
# Einfacher Fallback nur mit WAL-Checkpoint
|
|
||||||
try:
|
|
||||||
from models import create_optimized_engine
|
|
||||||
from sqlalchemy import text
|
|
||||||
|
|
||||||
engine = create_optimized_engine()
|
|
||||||
|
|
||||||
with engine.connect() as conn:
|
|
||||||
result = conn.execute(text("PRAGMA wal_checkpoint(PASSIVE)")).fetchone()
|
|
||||||
if result and result[1] > 0:
|
|
||||||
shutdown_logger.info(f"WAL-Checkpoint: {result[1]} Seiten übertragen")
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
engine.dispose()
|
|
||||||
shutdown_logger.info("✅ Einfaches Datenbank-Cleanup abgeschlossen")
|
|
||||||
|
|
||||||
except Exception as db_error:
|
|
||||||
shutdown_logger.error(f"❌ Fehler beim einfachen Datenbank-Cleanup: {db_error}")
|
|
||||||
|
|
||||||
except Exception as cleanup_error:
|
|
||||||
shutdown_logger.error(f"❌ Fehler beim Datenbank-Cleanup: {cleanup_error}")
|
|
||||||
|
|
||||||
self.register_cleanup_function(
|
|
||||||
func=safe_database_cleanup,
|
|
||||||
name="Datenbank Cleanup",
|
|
||||||
priority=3, # Niedrige Priorität (am Ende)
|
|
||||||
timeout=20
|
|
||||||
)
|
|
||||||
|
|
||||||
def register_windows_thread_manager(self):
|
|
||||||
"""Registriert Windows Thread Manager falls verfügbar"""
|
|
||||||
try:
|
|
||||||
if os.name == 'nt':
|
|
||||||
from utils.windows_fixes import get_windows_thread_manager
|
|
||||||
|
|
||||||
thread_manager = get_windows_thread_manager()
|
|
||||||
if thread_manager:
|
|
||||||
self.register_cleanup_function(
|
|
||||||
func=thread_manager.shutdown_all,
|
|
||||||
name="Windows Thread Manager",
|
|
||||||
priority=2,
|
|
||||||
timeout=15
|
|
||||||
)
|
|
||||||
shutdown_logger.debug("✅ Windows Thread Manager registriert")
|
|
||||||
except Exception as e:
|
|
||||||
shutdown_logger.debug(f"Windows Thread Manager nicht verfügbar: {e}")
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def cleanup_timeout(self, timeout: int, operation_name: str):
|
|
||||||
"""Context Manager für Timeout-überwachte Operationen"""
|
|
||||||
start_time = time.time()
|
|
||||||
success = False
|
|
||||||
|
|
||||||
try:
|
|
||||||
yield
|
|
||||||
success = True
|
|
||||||
except Exception as e:
|
|
||||||
elapsed = time.time() - start_time
|
|
||||||
shutdown_logger.error(f"❌ Fehler bei {operation_name} nach {elapsed:.1f}s: {e}")
|
|
||||||
raise
|
|
||||||
finally:
|
|
||||||
elapsed = time.time() - start_time
|
|
||||||
if success:
|
|
||||||
shutdown_logger.debug(f"✅ {operation_name} abgeschlossen in {elapsed:.1f}s")
|
|
||||||
elif elapsed >= timeout:
|
|
||||||
shutdown_logger.warning(f"⏱️ {operation_name} Timeout nach {elapsed:.1f}s")
|
|
||||||
|
|
||||||
def shutdown(self, exit_code: int = 0):
|
|
||||||
"""
|
|
||||||
Führt koordiniertes Shutdown aller registrierten Komponenten durch
|
|
||||||
|
|
||||||
Args:
|
|
||||||
exit_code: Exit-Code für sys.exit()
|
|
||||||
"""
|
|
||||||
if self.is_shutting_down:
|
|
||||||
shutdown_logger.debug("Shutdown bereits in Bearbeitung")
|
|
||||||
return
|
|
||||||
|
|
||||||
with self._lock:
|
|
||||||
if self.is_shutting_down:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.is_shutting_down = True
|
|
||||||
self.shutdown_started = datetime.now()
|
|
||||||
|
|
||||||
shutdown_logger.info("🔄 Starte koordiniertes System-Shutdown...")
|
|
||||||
self.shutdown_event.set()
|
|
||||||
|
|
||||||
total_start = time.time()
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 1. Komponenten stoppen (nach Priorität)
|
|
||||||
self._shutdown_components()
|
|
||||||
|
|
||||||
# 2. Cleanup-Funktionen ausführen (nach Priorität)
|
|
||||||
self._execute_cleanup_functions()
|
|
||||||
|
|
||||||
total_elapsed = time.time() - total_start
|
|
||||||
shutdown_logger.info(f"✅ Koordiniertes Shutdown abgeschlossen in {total_elapsed:.1f}s")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
shutdown_logger.error(f"❌ Fehler beim koordinierten Shutdown: {e}")
|
|
||||||
finally:
|
|
||||||
shutdown_logger.info("🏁 System wird beendet...")
|
|
||||||
sys.exit(exit_code)
|
|
||||||
|
|
||||||
def _shutdown_components(self):
|
|
||||||
"""Stoppt alle registrierten Komponenten"""
|
|
||||||
if not self.components:
|
|
||||||
return
|
|
||||||
|
|
||||||
shutdown_logger.info(f"🔄 Stoppe {len(self.components)} registrierte Komponenten...")
|
|
||||||
|
|
||||||
for name, config in self.components.items():
|
|
||||||
try:
|
|
||||||
component = config['component']
|
|
||||||
stop_method = config['stop_method']
|
|
||||||
|
|
||||||
if hasattr(component, stop_method):
|
|
||||||
with self.cleanup_timeout(10, f"Komponente {name}"):
|
|
||||||
method = getattr(component, stop_method)
|
|
||||||
method()
|
|
||||||
shutdown_logger.debug(f"✅ Komponente '{name}' gestoppt")
|
|
||||||
else:
|
|
||||||
shutdown_logger.warning(f"⚠️ Komponente '{name}' hat keine '{stop_method}' Methode")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
shutdown_logger.error(f"❌ Fehler beim Stoppen der Komponente '{name}': {e}")
|
|
||||||
|
|
||||||
def _execute_cleanup_functions(self):
|
|
||||||
"""Führt alle registrierten Cleanup-Funktionen aus"""
|
|
||||||
if not self.cleanup_functions:
|
|
||||||
return
|
|
||||||
|
|
||||||
shutdown_logger.info(f"🧹 Führe {len(self.cleanup_functions)} Cleanup-Funktionen aus...")
|
|
||||||
|
|
||||||
for cleanup_item in self.cleanup_functions:
|
|
||||||
try:
|
|
||||||
func = cleanup_item['function']
|
|
||||||
name = cleanup_item['name']
|
|
||||||
timeout = cleanup_item['timeout']
|
|
||||||
args = cleanup_item['args']
|
|
||||||
kwargs = cleanup_item['kwargs']
|
|
||||||
|
|
||||||
shutdown_logger.debug(f"🔄 Führe Cleanup aus: {name}")
|
|
||||||
|
|
||||||
with self.cleanup_timeout(timeout, f"Cleanup {name}"):
|
|
||||||
func(*args, **kwargs)
|
|
||||||
shutdown_logger.debug(f"✅ Cleanup '{name}' abgeschlossen")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
shutdown_logger.error(f"❌ Fehler bei Cleanup '{cleanup_item['name']}': {e}")
|
|
||||||
# Weiter mit nächstem Cleanup
|
|
||||||
continue
|
|
||||||
|
|
||||||
def is_shutdown_requested(self) -> bool:
|
|
||||||
"""Prüft ob Shutdown angefordert wurde"""
|
|
||||||
return self.shutdown_event.is_set()
|
|
||||||
|
|
||||||
def get_shutdown_time(self) -> Optional[datetime]:
|
|
||||||
"""Gibt den Zeitpunkt des Shutdown-Starts zurück"""
|
|
||||||
return self.shutdown_started
|
|
||||||
|
|
||||||
def force_shutdown(self, exit_code: int = 1):
|
|
||||||
"""Erzwingt sofortiges Shutdown ohne Cleanup"""
|
|
||||||
shutdown_logger.warning("🚨 Erzwinge sofortiges Shutdown ohne Cleanup!")
|
|
||||||
sys.exit(exit_code)
|
|
||||||
|
|
||||||
|
|
||||||
# Globaler Shutdown-Manager
|
|
||||||
_shutdown_manager: Optional[ShutdownManager] = None
|
|
||||||
_manager_lock = threading.Lock()
|
|
||||||
|
|
||||||
|
|
||||||
def get_shutdown_manager(timeout: int = 30) -> ShutdownManager:
|
|
||||||
"""
|
|
||||||
Singleton-Pattern für globalen Shutdown-Manager
|
|
||||||
|
|
||||||
Args:
|
|
||||||
timeout: Gesamttimeout für Shutdown-Operationen
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
ShutdownManager: Globaler Shutdown-Manager
|
|
||||||
"""
|
|
||||||
global _shutdown_manager
|
|
||||||
|
|
||||||
with _manager_lock:
|
|
||||||
if _shutdown_manager is None:
|
|
||||||
_shutdown_manager = ShutdownManager(timeout=timeout)
|
|
||||||
|
|
||||||
# Registriere atexit-Handler als Fallback
|
|
||||||
atexit.register(_shutdown_manager.shutdown)
|
|
||||||
|
|
||||||
return _shutdown_manager
|
|
||||||
|
|
||||||
|
|
||||||
def register_for_shutdown(component_or_function,
|
|
||||||
name: str,
|
|
||||||
component_stop_method: str = "stop",
|
|
||||||
priority: int = 1,
|
|
||||||
timeout: int = 10):
|
|
||||||
"""
|
|
||||||
Vereinfachte Registrierung für Shutdown
|
|
||||||
|
|
||||||
Args:
|
|
||||||
component_or_function: Komponente mit Stop-Methode oder Cleanup-Funktion
|
|
||||||
name: Beschreibender Name
|
|
||||||
component_stop_method: Name der Stop-Methode für Komponenten
|
|
||||||
priority: Priorität (1=hoch, 3=niedrig)
|
|
||||||
timeout: Timeout in Sekunden
|
|
||||||
"""
|
|
||||||
manager = get_shutdown_manager()
|
|
||||||
|
|
||||||
if callable(component_or_function):
|
|
||||||
# Es ist eine Funktion
|
|
||||||
manager.register_cleanup_function(
|
|
||||||
func=component_or_function,
|
|
||||||
name=name,
|
|
||||||
priority=priority,
|
|
||||||
timeout=timeout
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# Es ist eine Komponente
|
|
||||||
manager.register_component(
|
|
||||||
name=name,
|
|
||||||
component=component_or_function,
|
|
||||||
stop_method=component_stop_method
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def shutdown_application(exit_code: int = 0):
|
|
||||||
"""
|
|
||||||
Startet koordiniertes Shutdown der Anwendung
|
|
||||||
|
|
||||||
Args:
|
|
||||||
exit_code: Exit-Code für sys.exit()
|
|
||||||
"""
|
|
||||||
manager = get_shutdown_manager()
|
|
||||||
manager.shutdown(exit_code)
|
|
||||||
|
|
||||||
|
|
||||||
def is_shutdown_requested() -> bool:
|
|
||||||
"""Prüft ob Shutdown angefordert wurde"""
|
|
||||||
if _shutdown_manager:
|
|
||||||
return _shutdown_manager.is_shutdown_requested()
|
|
||||||
return False
|
|
@@ -1,658 +0,0 @@
|
|||||||
#!/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()
|
|
Reference in New Issue
Block a user