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

@@ -18,7 +18,7 @@ from typing import Dict, List, Tuple, Any, Optional
from models import Printer, User, Job, get_db_session
from utils.logging_config import get_logger, measure_execution_time
from utils.security_suite import require_permission, Permission, check_permission
from utils.hardware_integration import printer_monitor
from utils.hardware_integration import printer_monitor, tapo_controller
from utils.drag_drop_system import drag_drop_manager
# Logger initialisieren
@@ -1047,4 +1047,527 @@ def get_drag_drop_config():
# =============================================================================
# ENDE DRAG & DROP API
# =============================================================================
# =============================================================================
@printers_blueprint.route("/tapo/status-check", methods=["POST"])
@login_required
@require_permission(Permission.CONTROL_PRINTER)
@measure_execution_time(logger=printers_logger, task_name="API-Massenhafte-Tapo-Status-Prüfung")
def mass_tapo_status_check():
"""
Führt eine vollständige Tapo-Status-Überprüfung für alle Drucker durch.
Returns:
JSON mit detailliertem Status aller Tapo-Steckdosen
"""
printers_logger.info(f"Massenhafte Tapo-Status-Prüfung von Benutzer {current_user.name}")
try:
db_session = get_db_session()
# Alle Drucker laden
all_printers = db_session.query(Printer).all()
# Tapo-Controller laden
try:
from utils.hardware_integration import tapo_controller
tapo_available = True
except Exception as e:
db_session.close()
return jsonify({
"success": False,
"error": f"Tapo-Controller nicht verfügbar: {str(e)}",
"tapo_available": False
}), 500
printer_status = []
summary = {
"total_printers": len(all_printers),
"printers_with_tapo": 0,
"printers_without_tapo": 0,
"tapo_online": 0,
"tapo_offline": 0,
"tapo_unreachable": 0,
"configuration_issues": 0
}
for printer in all_printers:
printer_info = {
"id": printer.id,
"name": printer.name,
"model": printer.model,
"location": printer.location,
"active": printer.active,
"has_tapo_config": bool(printer.plug_ip),
"plug_ip": printer.plug_ip,
"last_checked": datetime.now()
}
if not printer.plug_ip:
# Drucker ohne Tapo-Konfiguration
summary["printers_without_tapo"] += 1
printer_info.update({
"tapo_status": "not_configured",
"tapo_reachable": False,
"power_status": None,
"recommendations": ["Tapo-Steckdose konfigurieren für automatische Steuerung"]
})
else:
# Drucker mit Tapo-Konfiguration
summary["printers_with_tapo"] += 1
# Konfigurationsprüfung
config_issues = []
if not printer.plug_username:
config_issues.append("Tapo-Benutzername fehlt")
if not printer.plug_password:
config_issues.append("Tapo-Passwort fehlt")
if config_issues:
summary["configuration_issues"] += 1
printer_info.update({
"tapo_status": "configuration_error",
"tapo_reachable": False,
"power_status": None,
"config_issues": config_issues,
"recommendations": ["Tapo-Anmeldedaten vervollständigen"]
})
else:
# Vollständige Konfiguration - Status prüfen
try:
reachable, status = tapo_controller.check_outlet_status(
printer.plug_ip,
printer_id=printer.id
)
if reachable:
if status == "on":
summary["tapo_online"] += 1
status_type = "online"
recommendations = []
else:
summary["tapo_offline"] += 1
status_type = "offline"
recommendations = ["Steckdose kann bei Bedarf eingeschaltet werden"]
else:
summary["tapo_unreachable"] += 1
status_type = "unreachable"
recommendations = ["Netzwerkverbindung prüfen", "IP-Adresse überprüfen"]
printer_info.update({
"tapo_status": status_type,
"tapo_reachable": reachable,
"power_status": status,
"recommendations": recommendations
})
# Drucker-Status in DB aktualisieren
if reachable:
printer.last_checked = datetime.now()
if status == "on":
printer.status = "online"
else:
printer.status = "offline"
else:
printer.status = "unreachable"
except Exception as tapo_error:
summary["tapo_unreachable"] += 1
printer_info.update({
"tapo_status": "error",
"tapo_reachable": False,
"power_status": None,
"error": str(tapo_error),
"recommendations": ["Tapo-Verbindung prüfen", "Anmeldedaten überprüfen"]
})
printers_logger.warning(f"Tapo-Fehler für {printer.name}: {str(tapo_error)}")
# Aktuelle Jobs für zusätzliche Info
active_jobs = db_session.query(Job).filter(
Job.printer_id == printer.id,
Job.status.in_(["running", "printing", "active", "scheduled"])
).count()
printer_info["active_jobs"] = active_jobs
if active_jobs > 0:
printer_info.setdefault("recommendations", []).append(
f"Vorsicht: {active_jobs} aktive Job(s) bei Steckdosen-Änderungen"
)
printer_status.append(printer_info)
# Änderungen in DB speichern
db_session.commit()
db_session.close()
# Übersicht der Ergebnisse
coverage_percentage = (summary["printers_with_tapo"] / summary["total_printers"] * 100) if summary["total_printers"] > 0 else 0
health_score = ((summary["tapo_online"] + summary["tapo_offline"]) / summary["printers_with_tapo"] * 100) if summary["printers_with_tapo"] > 0 else 0
printers_logger.info(f"Tapo-Status-Check abgeschlossen: {summary['printers_with_tapo']} konfiguriert, "
f"{summary['tapo_online']} online, {summary['tapo_unreachable']} nicht erreichbar")
return jsonify({
"success": True,
"tapo_available": tapo_available,
"printers": printer_status,
"summary": summary,
"metrics": {
"coverage_percentage": round(coverage_percentage, 1),
"health_score": round(health_score, 1),
"needs_attention": summary["configuration_issues"] + summary["tapo_unreachable"]
},
"timestamp": datetime.now().isoformat()
})
except Exception as e:
printers_logger.error(f"Unerwarteter Fehler bei Massenhafte-Tapo-Status-Prüfung: {str(e)}")
if 'db_session' in locals():
db_session.close()
return jsonify({
"success": False,
"error": f"Systemfehler: {str(e)}"
}), 500
@printers_blueprint.route("/tapo/configuration-wizard", methods=["POST"])
@login_required
@require_permission(Permission.ADMIN)
@measure_execution_time(logger=printers_logger, task_name="API-Tapo-Konfigurationsassistent")
def tapo_configuration_wizard():
"""
Automatischer Konfigurationsassistent für Tapo-Steckdosen.
Versucht automatisch verfügbare Steckdosen zu erkennen und zu konfigurieren.
"""
printers_logger.info(f"Tapo-Konfigurationsassistent von Admin {current_user.name}")
try:
data = request.get_json()
auto_configure = data.get('auto_configure', True)
test_ips = data.get('test_ips', [])
# 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
db_session = get_db_session()
# Standard-IP-Bereich für Mercedes-Benz TBA (normalerweise 192.168.1.201-206)
if not test_ips:
test_ips = [f"192.168.1.{i}" for i in range(201, 207)] # 6 Standard-Arbeitsplätze
discovery_results = {
"tested_ips": test_ips,
"discovered_devices": [],
"configured_printers": [],
"errors": []
}
printers_logger.info(f"Teste {len(test_ips)} IP-Adressen auf Tapo-Geräte...")
# Discovery für jede IP-Adresse
for ip in test_ips:
try:
printers_logger.debug(f"Teste IP: {ip}")
# Ping-Test
if not tapo_controller.ping_address(ip, timeout=3):
discovery_results["errors"].append(f"{ip}: Nicht erreichbar (Ping fehlgeschlagen)")
continue
# Tapo-Verbindungstest
test_result = tapo_controller.test_connection(ip)
if test_result["success"]:
device_info = test_result.get("device_info", {})
discovered_device = {
"ip": ip,
"device_info": device_info,
"nickname": device_info.get("nickname", f"Tapo Device {ip}"),
"model": device_info.get("model", "Unknown"),
"device_on": device_info.get("device_on", False)
}
discovery_results["discovered_devices"].append(discovered_device)
printers_logger.info(f"✅ Tapo-Gerät gefunden: {ip} - {discovered_device['nickname']}")
# Auto-Konfiguration wenn gewünscht
if auto_configure:
# Suche nach Drucker ohne Tapo-Konfiguration
unconfigured_printer = db_session.query(Printer).filter(
Printer.plug_ip.is_(None),
Printer.active == True
).first()
if unconfigured_printer:
# Konfiguriere den ersten verfügbaren Drucker
unconfigured_printer.plug_ip = ip
unconfigured_printer.plug_username = "admin" # Standard für Tapo
unconfigured_printer.plug_password = "admin" # Standard für Tapo
unconfigured_printer.last_checked = datetime.now()
configured_info = {
"printer_id": unconfigured_printer.id,
"printer_name": unconfigured_printer.name,
"tapo_ip": ip,
"tapo_nickname": discovered_device['nickname']
}
discovery_results["configured_printers"].append(configured_info)
printers_logger.info(f"✅ Drucker '{unconfigured_printer.name}' automatisch mit {ip} verknüpft")
else:
discovery_results["errors"].append(f"{ip}: Tapo-Gerät gefunden, aber kein unkonfigurierter Drucker verfügbar")
else:
discovery_results["errors"].append(f"{ip}: Erreichbar, aber kein Tapo-Gerät oder Authentifizierung fehlgeschlagen")
except Exception as ip_error:
discovery_results["errors"].append(f"{ip}: Fehler beim Test - {str(ip_error)}")
printers_logger.warning(f"Fehler beim Testen von {ip}: {str(ip_error)}")
# Änderungen speichern
db_session.commit()
db_session.close()
# Zusammenfassung
summary = {
"tested_ips": len(test_ips),
"discovered_devices": len(discovery_results["discovered_devices"]),
"configured_printers": len(discovery_results["configured_printers"]),
"errors": len(discovery_results["errors"])
}
printers_logger.info(f"Tapo-Konfigurationsassistent abgeschlossen: {summary}")
return jsonify({
"success": True,
"message": f"Discovery abgeschlossen: {summary['discovered_devices']} Geräte gefunden, "
f"{summary['configured_printers']} Drucker konfiguriert",
"results": discovery_results,
"summary": summary,
"timestamp": datetime.now().isoformat()
})
except Exception as e:
printers_logger.error(f"Fehler beim Tapo-Konfigurationsassistent: {str(e)}")
if 'db_session' in locals():
db_session.close()
return jsonify({
"success": False,
"error": f"Systemfehler: {str(e)}"
}), 500
@printers_blueprint.route("/tapo/validate-configuration/<int:printer_id>", methods=["POST"])
@login_required
@require_permission(Permission.ADMIN)
@measure_execution_time(logger=printers_logger, task_name="API-Tapo-Konfigurationsvalidierung")
def validate_tapo_configuration(printer_id):
"""
Validiert die Tapo-Konfiguration eines spezifischen Druckers.
Führt umfassende Tests durch: Ping, Authentifizierung, Funktionalität.
"""
printers_logger.info(f"Tapo-Konfigurationsvalidierung für Drucker {printer_id} von Admin {current_user.name}")
try:
db_session = get_db_session()
printer = db_session.query(Printer).filter(Printer.id == printer_id).first()
if not printer:
db_session.close()
return jsonify({
"success": False,
"error": "Drucker nicht gefunden"
}), 404
# Tapo-Controller laden
try:
from utils.hardware_integration import tapo_controller
except Exception as e:
db_session.close()
return jsonify({
"success": False,
"error": f"Tapo-Controller nicht verfügbar: {str(e)}"
}), 500
validation_results = {
"printer": {
"id": printer.id,
"name": printer.name,
"model": printer.model,
"location": printer.location
},
"configuration": {
"has_ip": bool(printer.plug_ip),
"has_username": bool(printer.plug_username),
"has_password": bool(printer.plug_password),
"ip_address": printer.plug_ip
},
"tests": {
"ping": {"status": "not_run", "message": ""},
"authentication": {"status": "not_run", "message": ""},
"functionality": {"status": "not_run", "message": ""},
"device_info": {"status": "not_run", "message": ""}
},
"overall_status": "unknown",
"recommendations": []
}
# Konfigurationsprüfung
if not printer.plug_ip:
validation_results["overall_status"] = "not_configured"
validation_results["recommendations"].append("IP-Adresse der Tapo-Steckdose eintragen")
elif not printer.plug_username or not printer.plug_password:
validation_results["overall_status"] = "incomplete_config"
validation_results["recommendations"].append("Benutzername und Passwort für Tapo-Steckdose eintragen")
else:
# Umfassende Tests durchführen
all_tests_passed = True
# Test 1: Ping/Erreichbarkeit
try:
ping_success = tapo_controller.ping_address(printer.plug_ip, timeout=5)
if ping_success:
validation_results["tests"]["ping"] = {
"status": "passed",
"message": "Steckdose ist im Netzwerk erreichbar"
}
else:
validation_results["tests"]["ping"] = {
"status": "failed",
"message": "Steckdose nicht erreichbar - Netzwerkproblem oder falsche IP"
}
all_tests_passed = False
validation_results["recommendations"].append("IP-Adresse überprüfen")
validation_results["recommendations"].append("Netzwerkverbindung der Steckdose prüfen")
except Exception as ping_error:
validation_results["tests"]["ping"] = {
"status": "error",
"message": f"Ping-Test fehlgeschlagen: {str(ping_error)}"
}
all_tests_passed = False
# Test 2: Authentifizierung (nur wenn Ping erfolgreich)
if validation_results["tests"]["ping"]["status"] == "passed":
try:
auth_result = tapo_controller.test_connection(
printer.plug_ip,
username=printer.plug_username,
password=printer.plug_password
)
if auth_result["success"]:
validation_results["tests"]["authentication"] = {
"status": "passed",
"message": "Authentifizierung erfolgreich"
}
# Geräteinformationen extrahieren
device_info = auth_result.get("device_info", {})
validation_results["tests"]["device_info"] = {
"status": "passed",
"message": "Geräteinformationen abgerufen",
"data": {
"nickname": device_info.get("nickname", "Unbekannt"),
"model": device_info.get("model", "Unbekannt"),
"device_on": device_info.get("device_on", False),
"signal_level": device_info.get("signal_level", 0)
}
}
else:
validation_results["tests"]["authentication"] = {
"status": "failed",
"message": f"Authentifizierung fehlgeschlagen: {auth_result.get('error', 'Unbekannt')}"
}
all_tests_passed = False
validation_results["recommendations"].append("Benutzername und Passwort überprüfen")
except Exception as auth_error:
validation_results["tests"]["authentication"] = {
"status": "error",
"message": f"Authentifizierungstest fehlgeschlagen: {str(auth_error)}"
}
all_tests_passed = False
# Test 3: Funktionalität (nur wenn Authentifizierung erfolgreich)
if validation_results["tests"]["authentication"]["status"] == "passed":
try:
reachable, status = tapo_controller.check_outlet_status(
printer.plug_ip,
printer_id=printer_id
)
if reachable:
validation_results["tests"]["functionality"] = {
"status": "passed",
"message": f"Status erfolgreich abgerufen: {status}",
"current_status": status
}
# Drucker-Status in DB aktualisieren
printer.last_checked = datetime.now()
printer.status = "online" if status == "on" else "offline"
else:
validation_results["tests"]["functionality"] = {
"status": "failed",
"message": "Status konnte nicht abgerufen werden"
}
all_tests_passed = False
except Exception as func_error:
validation_results["tests"]["functionality"] = {
"status": "error",
"message": f"Funktionalitätstest fehlgeschlagen: {str(func_error)}"
}
all_tests_passed = False
# Gesamtstatus bestimmen
if all_tests_passed:
validation_results["overall_status"] = "fully_functional"
validation_results["recommendations"].append("Konfiguration ist vollständig und funktional")
else:
failed_tests = [test for test, result in validation_results["tests"].items()
if result["status"] in ["failed", "error"]]
if "ping" in failed_tests:
validation_results["overall_status"] = "network_issue"
elif "authentication" in failed_tests:
validation_results["overall_status"] = "auth_issue"
elif "functionality" in failed_tests:
validation_results["overall_status"] = "functionality_issue"
else:
validation_results["overall_status"] = "partial_failure"
# Aktuelle Jobs als Sicherheitshinweis
active_jobs = db_session.query(Job).filter(
Job.printer_id == printer_id,
Job.status.in_(["running", "printing", "active"])
).count()
if active_jobs > 0:
validation_results["safety_warning"] = f"{active_jobs} aktive Job(s) - Vorsicht bei Steckdosen-Tests"
db_session.commit()
db_session.close()
printers_logger.info(f"Tapo-Validierung für {printer.name} abgeschlossen: {validation_results['overall_status']}")
return jsonify({
"success": True,
"validation": validation_results,
"timestamp": datetime.now().isoformat()
})
except Exception as e:
printers_logger.error(f"Fehler bei Tapo-Konfigurationsvalidierung: {str(e)}")
if 'db_session' in locals():
db_session.close()
return jsonify({
"success": False,
"error": f"Systemfehler: {str(e)}"
}), 500