Die Dateien wurden geändert oder hinzugefügt:

This commit is contained in:
2025-06-13 07:17:00 +02:00
parent ae95d82afc
commit 691a4f2d41
137 changed files with 2354 additions and 29 deletions

View File

@ -1823,4 +1823,656 @@ def get_status_color(status):
'disconnected': '#ef4444', # Rot
'unknown': '#6b7280' # Grau
}
return status_colors.get(status, '#6b7280')
return status_colors.get(status, '#6b7280')
# ===== FEHLENDE API-ROUTEN HINZUFÜGEN =====
@admin_api_blueprint.route('/system-health', methods=['GET'])
@admin_required
def api_admin_system_health_alias():
"""
Alias-Route für system-health (Kompatibilität mit Frontend).
Leitet Anfragen an die bestehende system/health Route weiter.
"""
return api_admin_system_health()
@admin_api_blueprint.route('/error-recovery/status', methods=['GET'])
@admin_required
def api_admin_error_recovery_status():
"""
API-Endpunkt für Error-Recovery-Status.
Gibt Informationen über das Error-Recovery-System zurück,
einschließlich Status, Statistiken und letzter Aktionen.
"""
try:
admin_api_logger.info(f"Error-Recovery-Status angefordert von {current_user.username}")
# Error-Recovery-Basis-Status sammeln
recovery_status = {
'enabled': True, # Error-Recovery ist standardmäßig aktiviert
'last_check': datetime.now().isoformat(),
'status': 'active',
'errors_detected': 0,
'errors_recovered': 0,
'last_recovery_action': None,
'monitoring_active': True,
'recovery_methods': [
'automatic_restart',
'service_health_check',
'database_recovery',
'cache_cleanup'
]
}
# Versuche Log-Informationen zu sammeln
try:
# Prüfe auf kürzliche Fehler in System-Logs
with get_cached_session() as db_session:
# Letzte Stunde nach Error-Logs suchen
last_hour = datetime.now() - timedelta(hours=1)
error_logs = db_session.query(SystemLog).filter(
SystemLog.level == 'ERROR',
SystemLog.timestamp >= last_hour
).count()
recovery_logs = db_session.query(SystemLog).filter(
SystemLog.message.like('%Recovery%'),
SystemLog.timestamp >= last_hour
).count()
recovery_status['errors_detected'] = error_logs
recovery_status['errors_recovered'] = recovery_logs
# Letzten Recovery-Eintrag finden
last_recovery = db_session.query(SystemLog).filter(
SystemLog.message.like('%Recovery%')
).order_by(SystemLog.timestamp.desc()).first()
if last_recovery:
recovery_status['last_recovery_action'] = {
'timestamp': last_recovery.timestamp.isoformat(),
'action': 'system_log_recovery',
'message': last_recovery.message,
'module': last_recovery.module
}
except Exception as log_error:
admin_api_logger.warning(f"Log-Analyse für Error-Recovery fehlgeschlagen: {str(log_error)}")
recovery_status['errors_detected'] = 0
recovery_status['errors_recovered'] = 0
# System-Load als Indikator für potenzielle Probleme
try:
import psutil
cpu_percent = psutil.cpu_percent(interval=1)
memory_percent = psutil.virtual_memory().percent
# Hohe System-Last kann auf Probleme hindeuten
if cpu_percent > 80 or memory_percent > 85:
recovery_status['status'] = 'warning'
recovery_status['last_recovery_action'] = {
'timestamp': datetime.now().isoformat(),
'action': 'system_load_warning',
'details': {
'cpu_percent': cpu_percent,
'memory_percent': memory_percent
}
}
# System-Performance-Daten hinzufügen
recovery_status['system_performance'] = {
'cpu_percent': cpu_percent,
'memory_percent': memory_percent,
'status': 'normal' if cpu_percent < 80 and memory_percent < 85 else 'high_load'
}
except ImportError:
admin_api_logger.info("psutil nicht verfügbar für Error-Recovery-Monitoring")
recovery_status['system_performance'] = {
'available': False,
'message': 'psutil-Bibliothek nicht installiert'
}
except Exception as system_error:
admin_api_logger.warning(f"System-Load-Check für Error-Recovery fehlgeschlagen: {str(system_error)}")
recovery_status['system_performance'] = {
'available': False,
'error': str(system_error)
}
# Datenbank-Gesundheit als Recovery-Indikator
try:
with get_cached_session() as db_session:
# Einfacher DB-Test
db_session.execute("SELECT 1")
recovery_status['database_health'] = 'healthy'
except Exception as db_error:
recovery_status['database_health'] = 'unhealthy'
recovery_status['status'] = 'critical'
admin_api_logger.error(f"Datenbank-Health-Check für Error-Recovery fehlgeschlagen: {str(db_error)}")
admin_api_logger.info(f"Error-Recovery-Status abgerufen: {recovery_status['status']}")
return jsonify({
'success': True,
'error_recovery': recovery_status,
'message': f"Error-Recovery-Status: {recovery_status['status']}"
})
except Exception as e:
admin_api_logger.error(f"Fehler beim Abrufen des Error-Recovery-Status: {str(e)}")
return jsonify({
'success': False,
'error': 'Error-Recovery-Status nicht verfügbar',
'details': str(e),
'error_recovery': {
'status': 'error',
'enabled': False,
'last_check': datetime.now().isoformat()
}
}), 500
# ===== ERWEITERTE TAPO-STECKDOSEN-VERWALTUNG =====
@admin_blueprint.route("/tapo-monitoring")
@admin_required
def tapo_monitoring():
"""
Erweiterte Tapo-Steckdosen-Überwachung für Administratoren.
Bietet Real-Time-Monitoring aller Drucker-Steckdosen mit automatischer Überprüfung.
"""
admin_logger.info(f"Tapo-Monitoring aufgerufen von {current_user.username}")
try:
with get_cached_session() as db_session:
# Alle Drucker mit konfigurierten Steckdosen laden
printers_with_plugs = db_session.query(Printer).filter(
Printer.plug_ip.isnot(None),
Printer.active == True
).all()
# Grundlegende Statistiken
total_printers = db_session.query(Printer).count()
printers_with_tapo = len(printers_with_plugs)
# Aktueller Status aller Tapo-Steckdosen abrufen
try:
from utils.hardware_integration import tapo_controller
tapo_available = True
# Status für jeden Drucker mit Tapo-Steckdose abrufen
printer_status = []
online_count = 0
offline_count = 0
error_count = 0
for printer in printers_with_plugs:
try:
reachable, status = tapo_controller.check_outlet_status(
printer.plug_ip,
printer_id=printer.id
)
if reachable:
if status == 'on':
online_count += 1
status_class = 'success'
else:
offline_count += 1
status_class = 'secondary'
else:
error_count += 1
status_class = 'danger'
status = 'unreachable'
# Aktuelle Jobs für diesen Drucker prüfen
active_jobs = db_session.query(Job).filter(
Job.printer_id == printer.id,
Job.status.in_(['running', 'printing', 'active', 'scheduled'])
).count()
printer_info = {
'id': printer.id,
'name': printer.name,
'model': printer.model,
'location': printer.location,
'plug_ip': printer.plug_ip,
'plug_status': status,
'plug_reachable': reachable,
'status_class': status_class,
'active_jobs': active_jobs,
'last_checked': datetime.now(),
'has_issues': not reachable or active_jobs > 0
}
printer_status.append(printer_info)
except Exception as e:
admin_logger.error(f"Fehler beim Status-Check für {printer.name}: {str(e)}")
error_count += 1
printer_status.append({
'id': printer.id,
'name': printer.name,
'model': printer.model,
'location': printer.location,
'plug_ip': printer.plug_ip,
'plug_status': 'error',
'plug_reachable': False,
'status_class': 'danger',
'active_jobs': 0,
'last_checked': datetime.now(),
'has_issues': True,
'error': str(e)
})
except Exception as e:
admin_logger.error(f"Tapo-Controller nicht verfügbar: {str(e)}")
tapo_available = False
printer_status = []
online_count = offline_count = error_count = 0
# Statistiken zusammenstellen
monitoring_stats = {
'total_printers': total_printers,
'printers_with_tapo': printers_with_tapo,
'tapo_available': tapo_available,
'online_count': online_count,
'offline_count': offline_count,
'error_count': error_count,
'coverage_percentage': round((printers_with_tapo / total_printers * 100), 1) if total_printers > 0 else 0
}
admin_logger.info(f"Tapo-Monitoring geladen: {printers_with_tapo} Steckdosen, {online_count} online")
return render_template('admin_tapo_monitoring.html',
printer_status=printer_status,
stats=monitoring_stats,
page_title="Tapo-Steckdosen-Monitoring",
breadcrumb=[
{"name": "Admin-Dashboard", "url": url_for("admin.admin_dashboard")},
{"name": "Tapo-Monitoring", "url": "#"}
])
except Exception as e:
admin_logger.error(f"Fehler beim Laden des Tapo-Monitorings: {str(e)}")
flash("Fehler beim Laden der Tapo-Monitoring-Daten.", "error")
return redirect(url_for("admin.admin_dashboard"))
@admin_api_blueprint.route('/tapo/bulk-control', methods=['POST'])
@admin_required
def api_admin_bulk_tapo_control():
"""
API-Endpunkt für Massensteuerung von Tapo-Steckdosen.
Ermöglicht das gleichzeitige Ein-/Ausschalten mehrerer Steckdosen.
"""
admin_api_logger.info(f"Bulk-Tapo-Steuerung von {current_user.username}")
try:
data = request.get_json()
action = data.get('action') # 'on', 'off', 'status'
printer_ids = data.get('printer_ids', [])
if not action or not printer_ids:
return jsonify({
'success': False,
'error': 'Aktion und Drucker-IDs sind erforderlich'
}), 400
if action not in ['on', 'off', 'status']:
return jsonify({
'success': False,
'error': 'Ungültige Aktion. Erlaubt: on, off, status'
}), 400
# Tapo-Controller laden
try:
from utils.hardware_integration import tapo_controller
except Exception as e:
return jsonify({
'success': False,
'error': f'Tapo-Controller nicht verfügbar: {str(e)}'
}), 500
results = []
success_count = 0
error_count = 0
with get_cached_session() as db_session:
for printer_id in printer_ids:
try:
printer = db_session.query(Printer).filter(Printer.id == printer_id).first()
if not printer:
results.append({
'printer_id': printer_id,
'success': False,
'error': 'Drucker nicht gefunden'
})
error_count += 1
continue
if not printer.plug_ip:
results.append({
'printer_id': printer_id,
'printer_name': printer.name,
'success': False,
'error': 'Keine Steckdose konfiguriert'
})
error_count += 1
continue
# Aktion ausführen
if action == 'status':
reachable, status = tapo_controller.check_outlet_status(
printer.plug_ip,
printer_id=printer_id
)
results.append({
'printer_id': printer_id,
'printer_name': printer.name,
'success': True,
'status': status,
'reachable': reachable
})
success_count += 1
else:
# Ein- oder Ausschalten
state = action == 'on'
success = tapo_controller.toggle_plug(printer.plug_ip, state)
if success:
# Drucker-Status in DB aktualisieren
printer.status = 'starting' if state else 'offline'
printer.last_checked = datetime.now()
results.append({
'printer_id': printer_id,
'printer_name': printer.name,
'success': True,
'action': action,
'message': f'Steckdose erfolgreich {"ein" if state else "aus"}geschaltet'
})
success_count += 1
else:
results.append({
'printer_id': printer_id,
'printer_name': printer.name,
'success': False,
'error': f'Steckdose konnte nicht {"ein" if state else "aus"}geschaltet werden'
})
error_count += 1
except Exception as e:
admin_api_logger.error(f"Fehler bei Bulk-Steuerung für Drucker {printer_id}: {str(e)}")
results.append({
'printer_id': printer_id,
'success': False,
'error': str(e)
})
error_count += 1
# Änderungen speichern
if action in ['on', 'off']:
db_session.commit()
admin_api_logger.info(f"Bulk-Tapo-Steuerung abgeschlossen: {success_count} erfolgreich, {error_count} Fehler")
return jsonify({
'success': True,
'results': results,
'summary': {
'total': len(printer_ids),
'success': success_count,
'errors': error_count
},
'timestamp': datetime.now().isoformat()
})
except Exception as e:
admin_api_logger.error(f"Unerwarteter Fehler bei Bulk-Tapo-Steuerung: {str(e)}")
return jsonify({
'success': False,
'error': f'Systemfehler: {str(e)}'
}), 500
@admin_api_blueprint.route('/tapo/health-check', methods=['POST'])
@admin_required
def api_admin_tapo_health_check():
"""
Führt eine umfassende Gesundheitsüberprüfung aller Tapo-Steckdosen durch.
Testet Konnektivität, Authentifizierung und Funktionsfähigkeit.
"""
admin_api_logger.info(f"Tapo-Gesundheitscheck von {current_user.username}")
try:
# Tapo-Controller laden
try:
from utils.hardware_integration import tapo_controller
tapo_available = True
except Exception as e:
return jsonify({
'success': False,
'error': f'Tapo-Controller nicht verfügbar: {str(e)}',
'tapo_available': False
}), 500
health_results = {
'overall_status': 'healthy',
'tapo_available': tapo_available,
'timestamp': datetime.now().isoformat(),
'printers': [],
'summary': {
'total': 0,
'healthy': 0,
'warning': 0,
'critical': 0
},
'recommendations': []
}
with get_cached_session() as db_session:
# Alle Drucker mit Steckdosen laden
printers_with_plugs = db_session.query(Printer).filter(
Printer.plug_ip.isnot(None)
).all()
health_results['summary']['total'] = len(printers_with_plugs)
for printer in printers_with_plugs:
printer_health = {
'id': printer.id,
'name': printer.name,
'plug_ip': printer.plug_ip,
'status': 'unknown',
'issues': [],
'checks': {
'connectivity': False,
'authentication': False,
'functionality': False
}
}
try:
# Check 1: Konnektivität (Ping)
ping_success = tapo_controller.ping_address(printer.plug_ip, timeout=3)
printer_health['checks']['connectivity'] = ping_success
if not ping_success:
printer_health['issues'].append('Netzwerkverbindung fehlgeschlagen')
# Check 2: Authentifizierung und Geräteinformationen
if ping_success:
try:
test_result = tapo_controller.test_connection(printer.plug_ip)
printer_health['checks']['authentication'] = test_result['success']
if not test_result['success']:
printer_health['issues'].append(f'Authentifizierung fehlgeschlagen: {test_result.get("error", "Unbekannt")}')
except Exception as auth_error:
printer_health['issues'].append(f'Authentifizierungstest fehlgeschlagen: {str(auth_error)}')
# Check 3: Funktionalität (Status abrufen)
if printer_health['checks']['authentication']:
try:
reachable, status = tapo_controller.check_outlet_status(
printer.plug_ip,
printer_id=printer.id
)
printer_health['checks']['functionality'] = reachable
printer_health['current_status'] = status
if not reachable:
printer_health['issues'].append('Status-Abfrage fehlgeschlagen')
except Exception as func_error:
printer_health['issues'].append(f'Funktionstest fehlgeschlagen: {str(func_error)}')
# Gesamtstatus bewerten
if len(printer_health['issues']) == 0:
printer_health['status'] = 'healthy'
health_results['summary']['healthy'] += 1
elif len(printer_health['issues']) <= 1:
printer_health['status'] = 'warning'
health_results['summary']['warning'] += 1
else:
printer_health['status'] = 'critical'
health_results['summary']['critical'] += 1
# Aktuelle Jobs prüfen (für Sicherheitswarnungen)
active_jobs = db_session.query(Job).filter(
Job.printer_id == printer.id,
Job.status.in_(['running', 'printing', 'active'])
).count()
if active_jobs > 0:
printer_health['active_jobs'] = active_jobs
printer_health['issues'].append(f'{active_jobs} aktive(r) Job(s) - Vorsicht bei Steckdosen-Änderungen')
except Exception as e:
admin_api_logger.error(f"Fehler beim Gesundheitscheck für {printer.name}: {str(e)}")
printer_health['status'] = 'critical'
printer_health['issues'].append(f'Systemfehler: {str(e)}')
health_results['summary']['critical'] += 1
health_results['printers'].append(printer_health)
# Gesamtstatus und Empfehlungen bestimmen
if health_results['summary']['critical'] > 0:
health_results['overall_status'] = 'critical'
health_results['recommendations'].append('Kritische Probleme bei Tapo-Steckdosen beheben')
elif health_results['summary']['warning'] > 0:
health_results['overall_status'] = 'warning'
health_results['recommendations'].append('Warnungen bei Tapo-Steckdosen überprüfen')
# Zusätzliche Empfehlungen
coverage = (len(printers_with_plugs) / db_session.query(Printer).count()) * 100 if db_session.query(Printer).count() > 0 else 0
if coverage < 80:
health_results['recommendations'].append(f'Tapo-Abdeckung nur {coverage:.1f}% - weitere Steckdosen konfigurieren')
admin_api_logger.info(f"Tapo-Gesundheitscheck abgeschlossen: {health_results['summary']}")
return jsonify(health_results)
except Exception as e:
admin_api_logger.error(f"Unerwarteter Fehler beim Tapo-Gesundheitscheck: {str(e)}")
return jsonify({
'success': False,
'error': 'Fehler beim Health-Check',
'message': str(e),
'health': {
'overall': 'error',
'timestamp': datetime.now().isoformat()
}
}), 500
@admin_api_blueprint.route('/printers/tapo-configure', methods=['POST'])
@admin_required
def api_admin_configure_printer_tapo():
"""
Konfiguriert oder aktualisiert die Tapo-Steckdosen-Einstellungen für einen Drucker.
"""
admin_api_logger.info(f"Tapo-Konfiguration von {current_user.username}")
try:
data = request.get_json()
printer_id = data.get('printer_id')
plug_ip = data.get('plug_ip')
plug_username = data.get('plug_username')
plug_password = data.get('plug_password')
test_connection = data.get('test_connection', True)
if not printer_id:
return jsonify({
'success': False,
'error': 'Drucker-ID ist erforderlich'
}), 400
with get_cached_session() as db_session:
printer = db_session.query(Printer).filter(Printer.id == printer_id).first()
if not printer:
return jsonify({
'success': False,
'error': 'Drucker nicht gefunden'
}), 404
# Tapo-Einstellungen aktualisieren
if plug_ip:
try:
import ipaddress
ipaddress.ip_address(plug_ip)
printer.plug_ip = plug_ip
except ValueError:
return jsonify({
'success': False,
'error': 'Ungültige IP-Adresse'
}), 400
if plug_username:
printer.plug_username = plug_username
if plug_password:
printer.plug_password = plug_password
# Verbindung testen falls gewünscht
test_result = None
if test_connection and printer.plug_ip:
try:
from utils.hardware_integration import tapo_controller
test_result = tapo_controller.test_connection(
printer.plug_ip,
username=printer.plug_username,
password=printer.plug_password
)
if test_result['success']:
printer.last_checked = datetime.now()
printer.status = 'online'
else:
admin_api_logger.warning(f"Tapo-Test für {printer.name} fehlgeschlagen: {test_result.get('error')}")
except Exception as e:
test_result = {
'success': False,
'error': f'Test fehlgeschlagen: {str(e)}'
}
db_session.commit()
admin_api_logger.info(f"Tapo-Konfiguration für {printer.name} aktualisiert")
return jsonify({
'success': True,
'message': f'Tapo-Einstellungen für {printer.name} erfolgreich aktualisiert',
'printer_id': printer_id,
'test_result': test_result,
'timestamp': datetime.now().isoformat()
})
except Exception as e:
admin_api_logger.error(f"Fehler bei Tapo-Konfiguration: {str(e)}")
return jsonify({
'success': False,
'error': f'Systemfehler: {str(e)}'
}), 500