"feat: Update admin UI layout in backend"

This commit is contained in:
Till Tomczak 2025-05-29 18:25:29 +02:00
parent deda6d6c38
commit d2a9a42651
2 changed files with 269 additions and 0 deletions

View File

@ -1656,6 +1656,219 @@ def admin_page():
logs=logs
)
# ===== ERROR MONITORING SYSTEM =====
@app.route("/api/admin/system-health", methods=['GET'])
@login_required
def api_admin_system_health():
"""API-Endpunkt für System-Gesundheitscheck."""
if not current_user.is_admin:
return jsonify({"error": "Berechtigung verweigert"}), 403
db_session = get_db_session()
critical_errors = []
warnings = []
try:
# 1. Datenbank-Schema-Integrität prüfen
try:
# Test verschiedene kritische Tabellen und Spalten
db_session.execute("SELECT COUNT(*) FROM guest_requests WHERE duration_minutes IS NOT NULL")
schema_integrity = "OK"
except Exception as e:
critical_errors.append({
"type": "database_schema",
"message": f"Datenbank-Schema-Fehler: {str(e)}",
"severity": "critical",
"suggested_fix": "Datenbank-Migration ausführen",
"timestamp": datetime.now().isoformat()
})
schema_integrity = "FEHLER"
# 2. Prüfe kritische Spalten in wichtigen Tabellen
schema_checks = [
("guest_requests", "duration_minutes"),
("guest_requests", "file_name"),
("guest_requests", "processed_by"),
("users", "updated_at"),
("jobs", "duration_minutes")
]
missing_columns = []
for table, column in schema_checks:
try:
db_session.execute(f"SELECT {column} FROM {table} LIMIT 1")
except Exception:
missing_columns.append(f"{table}.{column}")
if missing_columns:
critical_errors.append({
"type": "missing_columns",
"message": f"Fehlende Datenbank-Spalten: {', '.join(missing_columns)}",
"severity": "critical",
"suggested_fix": "python utils/database_schema_migration.py ausführen",
"timestamp": datetime.now().isoformat(),
"details": missing_columns
})
# 3. Prüfe auf wiederkehrende Datenbankfehler in den Logs
import os
log_file = os.path.join("logs", "app", f"myp_app_{datetime.now().strftime('%Y_%m_%d')}.log")
recent_db_errors = 0
if os.path.exists(log_file):
try:
with open(log_file, 'r', encoding='utf-8') as f:
last_lines = f.readlines()[-100:] # Letzte 100 Zeilen
for line in last_lines:
if "OperationalError" in line or "no such column" in line:
recent_db_errors += 1
except Exception:
pass
if recent_db_errors > 5:
critical_errors.append({
"type": "frequent_db_errors",
"message": f"{recent_db_errors} Datenbankfehler in letzter Zeit erkannt",
"severity": "high",
"suggested_fix": "System-Logs überprüfen und Migration ausführen",
"timestamp": datetime.now().isoformat()
})
# 4. Prüfe Drucker-Konnektivität
offline_printers = db_session.query(Printer).filter(
Printer.status == "offline",
Printer.active == True
).count()
if offline_printers > 0:
warnings.append({
"type": "printer_offline",
"message": f"{offline_printers} aktive Drucker sind offline",
"severity": "warning",
"suggested_fix": "Drucker-Status überprüfen",
"timestamp": datetime.now().isoformat()
})
# 5. System-Performance Metriken
import psutil
cpu_usage = psutil.cpu_percent(interval=1)
memory_usage = psutil.virtual_memory().percent
disk_usage = psutil.disk_usage('/').percent
if cpu_usage > 90:
warnings.append({
"type": "high_cpu",
"message": f"Hohe CPU-Auslastung: {cpu_usage:.1f}%",
"severity": "warning",
"suggested_fix": "System-Ressourcen überprüfen",
"timestamp": datetime.now().isoformat()
})
if memory_usage > 85:
warnings.append({
"type": "high_memory",
"message": f"Hohe Speicher-Auslastung: {memory_usage:.1f}%",
"severity": "warning",
"suggested_fix": "Speicher-Verbrauch optimieren",
"timestamp": datetime.now().isoformat()
})
# 6. Letzte Migration info
try:
backup_dir = os.path.join("database", "backups")
if os.path.exists(backup_dir):
backup_files = [f for f in os.listdir(backup_dir) if f.endswith('.backup')]
if backup_files:
latest_backup = max(backup_files, key=lambda x: os.path.getctime(os.path.join(backup_dir, x)))
last_migration = latest_backup.replace('.backup', '').replace('myp.db.backup_', '')
else:
last_migration = "Keine Backups gefunden"
else:
last_migration = "Backup-Verzeichnis nicht gefunden"
except Exception:
last_migration = "Unbekannt"
return jsonify({
"success": True,
"health_status": "critical" if critical_errors else ("warning" if warnings else "healthy"),
"critical_errors": critical_errors,
"warnings": warnings,
"schema_integrity": schema_integrity,
"last_migration": last_migration,
"recent_errors_count": recent_db_errors,
"system_metrics": {
"cpu_usage": cpu_usage,
"memory_usage": memory_usage,
"disk_usage": disk_usage
},
"timestamp": datetime.now().isoformat()
})
except Exception as e:
app_logger.error(f"Fehler beim System-Gesundheitscheck: {str(e)}")
return jsonify({
"success": False,
"error": "Fehler beim System-Gesundheitscheck",
"critical_errors": [{
"type": "system_check_failed",
"message": f"System-Check fehlgeschlagen: {str(e)}",
"severity": "critical",
"suggested_fix": "System-Logs überprüfen",
"timestamp": datetime.now().isoformat()
}]
}), 500
finally:
db_session.close()
@app.route("/api/admin/fix-errors", methods=['POST'])
@login_required
def api_admin_fix_errors():
"""API-Endpunkt um automatische Fehler-Reparatur auszuführen."""
if not current_user.is_admin:
return jsonify({"error": "Berechtigung verweigert"}), 403
try:
# Automatische Migration ausführen
import subprocess
import sys
# Migration in separatem Prozess ausführen
result = subprocess.run(
[sys.executable, "utils/database_schema_migration.py"],
cwd=os.path.dirname(os.path.abspath(__file__)),
capture_output=True,
text=True,
timeout=60
)
if result.returncode == 0:
app_logger.info(f"Automatische Migration erfolgreich ausgeführt von Admin {current_user.email}")
return jsonify({
"success": True,
"message": "Automatische Reparatur erfolgreich durchgeführt",
"details": result.stdout
})
else:
app_logger.error(f"Automatische Migration fehlgeschlagen: {result.stderr}")
return jsonify({
"success": False,
"error": "Automatische Reparatur fehlgeschlagen",
"details": result.stderr
}), 500
except subprocess.TimeoutExpired:
return jsonify({
"success": False,
"error": "Migration-Timeout - Vorgang dauerte zu lange"
}), 500
except Exception as e:
app_logger.error(f"Fehler bei automatischer Reparatur: {str(e)}")
return jsonify({
"success": False,
"error": f"Fehler bei automatischer Reparatur: {str(e)}"
}), 500
# Direkter Zugriff auf Logout-Route (für Fallback)
@app.route("/logout", methods=["GET", "POST"])
def logout_redirect():

View File

@ -264,6 +264,62 @@
</nav>
</div>
<!-- Tab Content -->
<!-- Critical Errors Alert System -->
<div id="critical-errors-alert" class="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-xl p-6 mb-8 hidden">
<div class="flex items-start space-x-4">
<div class="flex-shrink-0">
<svg class="w-8 h-8 text-red-500 animate-pulse" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.464 0L4.35 16.5c-.77.833.192 2.5 1.732 2.5z"/>
</svg>
</div>
<div class="flex-1">
<h3 class="text-lg font-semibold text-red-800 dark:text-red-200 mb-2">
🚨 Kritische Systemfehler erkannt
</h3>
<div id="error-list" class="space-y-2">
<!-- Error items will be populated here -->
</div>
<div class="mt-4 flex space-x-3">
<button id="fix-errors-btn" class="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors text-sm font-medium">
🔧 Automatisch reparieren
</button>
<button id="dismiss-errors-btn" class="px-4 py-2 bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors text-sm font-medium">
❌ Verwerfen
</button>
<button id="view-error-details-btn" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors text-sm font-medium">
📊 Details anzeigen
</button>
</div>
</div>
</div>
</div>
<!-- Database Health Status -->
<div id="db-health-status" class="bg-white/60 dark:bg-slate-800/60 backdrop-blur-sm rounded-xl border border-slate-200 dark:border-slate-600 p-6 mb-8 shadow-lg">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white">🗄️ Datenbank-Gesundheitsstatus</h3>
<div class="flex items-center space-x-2">
<div id="db-status-indicator" class="w-3 h-3 bg-green-400 rounded-full animate-pulse"></div>
<span id="db-status-text" class="text-sm font-medium text-slate-600 dark:text-slate-400">Gesund</span>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="bg-white/40 dark:bg-slate-700/40 rounded-lg p-4">
<div class="text-sm text-slate-500 dark:text-slate-400">Letzte Migration</div>
<div id="last-migration" class="text-lg font-semibold text-slate-900 dark:text-white">Lädt...</div>
</div>
<div class="bg-white/40 dark:bg-slate-700/40 rounded-lg p-4">
<div class="text-sm text-slate-500 dark:text-slate-400">Schema-Integrität</div>
<div id="schema-integrity" class="text-lg font-semibold text-green-600 dark:text-green-400">Lädt...</div>
</div>
<div class="bg-white/40 dark:bg-slate-700/40 rounded-lg p-4">
<div class="text-sm text-slate-500 dark:text-slate-400">Letzte Fehler</div>
<div id="recent-errors-count" class="text-lg font-semibold text-slate-900 dark:text-white">Lädt...</div>
</div>
</div>
</div>
<!-- Tab Content -->
<div class="bg-white/80 dark:bg-slate-800/80 backdrop-blur-xl rounded-3xl border border-white/20 dark:border-slate-700/50 shadow-xl overflow-hidden">