"feat: Update admin UI layout in backend"
This commit is contained in:
@@ -1656,6 +1656,219 @@ def admin_page():
|
|||||||
logs=logs
|
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)
|
# Direkter Zugriff auf Logout-Route (für Fallback)
|
||||||
@app.route("/logout", methods=["GET", "POST"])
|
@app.route("/logout", methods=["GET", "POST"])
|
||||||
def logout_redirect():
|
def logout_redirect():
|
||||||
|
@@ -264,6 +264,62 @@
|
|||||||
</nav>
|
</nav>
|
||||||
</div>
|
</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 -->
|
<!-- 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">
|
<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">
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user