📚 Improved admin UI layout & design consistency 🎨
This commit is contained in:
parent
52d0bad64f
commit
7de193d4b2
363
backend/app.py
363
backend/app.py
@ -533,7 +533,7 @@ def job_owner_required(f):
|
|||||||
@wraps(f)
|
@wraps(f)
|
||||||
def decorated_function(job_id, *args, **kwargs):
|
def decorated_function(job_id, *args, **kwargs):
|
||||||
db_session = get_db_session()
|
db_session = get_db_session()
|
||||||
job = db_session.query(Job).filter(Job.id == job_id).first()
|
job = db_session.get(Job, job_id) # Modernized from query().get()
|
||||||
|
|
||||||
if not job:
|
if not job:
|
||||||
db_session.close()
|
db_session.close()
|
||||||
@ -1463,7 +1463,7 @@ def user_export_data():
|
|||||||
"""Exportiert alle Benutzerdaten als JSON für DSGVO-Konformität"""
|
"""Exportiert alle Benutzerdaten als JSON für DSGVO-Konformität"""
|
||||||
try:
|
try:
|
||||||
db_session = get_db_session()
|
db_session = get_db_session()
|
||||||
user = db_session.query(User).filter(User.id == int(current_user.id)).first()
|
user = db_session.get(User, int(current_user.id)) # Modernized from query().get()
|
||||||
|
|
||||||
if not user:
|
if not user:
|
||||||
db_session.close()
|
db_session.close()
|
||||||
@ -1513,7 +1513,7 @@ def user_update_profile_api():
|
|||||||
|
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
db_session = get_db_session()
|
db_session = get_db_session()
|
||||||
user = db_session.query(User).filter(User.id == int(current_user.id)).first()
|
user = db_session.get(User, int(current_user.id)) # Modernized from query().get()
|
||||||
|
|
||||||
if not user:
|
if not user:
|
||||||
db_session.close()
|
db_session.close()
|
||||||
@ -2730,7 +2730,7 @@ def test_printer_tapo_connection(printer_id):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
db_session = get_db_session()
|
db_session = get_db_session()
|
||||||
printer = db_session.query(Printer).get(printer_id)
|
printer = db_session.get(Printer, printer_id) # Modernized from query().get()
|
||||||
|
|
||||||
if not printer:
|
if not printer:
|
||||||
db_session.close()
|
db_session.close()
|
||||||
@ -3103,7 +3103,7 @@ def admin_update_user_form(user_id):
|
|||||||
|
|
||||||
db_session = get_db_session()
|
db_session = get_db_session()
|
||||||
|
|
||||||
user = db_session.query(User).get(user_id)
|
user = db_session.get(User, user_id)
|
||||||
if not user:
|
if not user:
|
||||||
db_session.close()
|
db_session.close()
|
||||||
flash("Benutzer nicht gefunden.", "error")
|
flash("Benutzer nicht gefunden.", "error")
|
||||||
@ -3174,7 +3174,7 @@ def admin_update_printer_form(printer_id):
|
|||||||
|
|
||||||
db_session = get_db_session()
|
db_session = get_db_session()
|
||||||
|
|
||||||
printer = db_session.query(Printer).get(printer_id)
|
printer = db_session.get(Printer, printer_id)
|
||||||
if not printer:
|
if not printer:
|
||||||
db_session.close()
|
db_session.close()
|
||||||
flash("Drucker nicht gefunden.", "error")
|
flash("Drucker nicht gefunden.", "error")
|
||||||
@ -3373,7 +3373,7 @@ def upload_avatar():
|
|||||||
|
|
||||||
# Alte Avatar-Datei löschen falls vorhanden
|
# Alte Avatar-Datei löschen falls vorhanden
|
||||||
db_session = get_db_session()
|
db_session = get_db_session()
|
||||||
user = db_session.query(User).get(current_user.id)
|
user = db_session.get(User, current_user.id) # Modernized from query().get()
|
||||||
if user and user.avatar_path:
|
if user and user.avatar_path:
|
||||||
delete_file_safe(user.avatar_path)
|
delete_file_safe(user.avatar_path)
|
||||||
|
|
||||||
@ -3810,7 +3810,7 @@ def get_job_detail(job_id):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# Eagerly load the user and printer relationships
|
# Eagerly load the user and printer relationships
|
||||||
job = db_session.query(Job).options(joinedload(Job.user), joinedload(Job.printer)).filter(Job.id == job_id).first()
|
job = db_session.get(Job, job_id) # Modernized from query().get()
|
||||||
|
|
||||||
if not job:
|
if not job:
|
||||||
db_session.close()
|
db_session.close()
|
||||||
@ -3833,7 +3833,7 @@ def delete_job(job_id):
|
|||||||
"""Löscht einen Job."""
|
"""Löscht einen Job."""
|
||||||
try:
|
try:
|
||||||
db_session = get_db_session()
|
db_session = get_db_session()
|
||||||
job = db_session.query(Job).get(job_id)
|
job = db_session.get(Job, job_id) # Modernized from query().get()
|
||||||
|
|
||||||
if not job:
|
if not job:
|
||||||
db_session.close()
|
db_session.close()
|
||||||
@ -3963,7 +3963,7 @@ def create_job():
|
|||||||
db_session = get_db_session()
|
db_session = get_db_session()
|
||||||
|
|
||||||
# Prüfen, ob der Drucker existiert
|
# Prüfen, ob der Drucker existiert
|
||||||
printer = db_session.query(Printer).get(printer_id)
|
printer = db_session.get(Printer, printer_id) # Modernized from query().get()
|
||||||
if not printer:
|
if not printer:
|
||||||
db_session.close()
|
db_session.close()
|
||||||
return jsonify({"error": "Drucker nicht gefunden"}), 404
|
return jsonify({"error": "Drucker nicht gefunden"}), 404
|
||||||
@ -4016,7 +4016,7 @@ def update_job(job_id):
|
|||||||
data = request.json
|
data = request.json
|
||||||
|
|
||||||
db_session = get_db_session()
|
db_session = get_db_session()
|
||||||
job = db_session.query(Job).get(job_id)
|
job = db_session.get(Job, job_id) # Modernized from query().get()
|
||||||
|
|
||||||
if not job:
|
if not job:
|
||||||
db_session.close()
|
db_session.close()
|
||||||
@ -4626,7 +4626,7 @@ def approve_guest_request(request_id):
|
|||||||
try:
|
try:
|
||||||
db_session = get_db_session()
|
db_session = get_db_session()
|
||||||
|
|
||||||
guest_request = db_session.query(GuestRequest).filter(GuestRequest.id == request_id).first()
|
guest_request = db_session.get(GuestRequest, request_id)
|
||||||
|
|
||||||
if not guest_request:
|
if not guest_request:
|
||||||
db_session.close()
|
db_session.close()
|
||||||
@ -4656,7 +4656,7 @@ def approve_guest_request(request_id):
|
|||||||
|
|
||||||
# Falls Drucker zugewiesen werden soll
|
# Falls Drucker zugewiesen werden soll
|
||||||
if printer_id:
|
if printer_id:
|
||||||
printer = db_session.query(Printer).filter(Printer.id == printer_id).first()
|
printer = db_session.get(Printer, printer_id)
|
||||||
if printer:
|
if printer:
|
||||||
guest_request.assigned_printer_id = printer_id
|
guest_request.assigned_printer_id = printer_id
|
||||||
|
|
||||||
@ -4706,7 +4706,7 @@ def reject_guest_request(request_id):
|
|||||||
try:
|
try:
|
||||||
db_session = get_db_session()
|
db_session = get_db_session()
|
||||||
|
|
||||||
guest_request = db_session.query(GuestRequest).filter(GuestRequest.id == request_id).first()
|
guest_request = db_session.get(GuestRequest, request_id)
|
||||||
|
|
||||||
if not guest_request:
|
if not guest_request:
|
||||||
db_session.close()
|
db_session.close()
|
||||||
@ -4773,7 +4773,7 @@ def delete_guest_request(request_id):
|
|||||||
try:
|
try:
|
||||||
db_session = get_db_session()
|
db_session = get_db_session()
|
||||||
|
|
||||||
guest_request = db_session.query(GuestRequest).filter(GuestRequest.id == request_id).first()
|
guest_request = db_session.get(GuestRequest, request_id)
|
||||||
|
|
||||||
if not guest_request:
|
if not guest_request:
|
||||||
db_session.close()
|
db_session.close()
|
||||||
@ -4817,7 +4817,7 @@ def get_guest_request_detail(request_id):
|
|||||||
try:
|
try:
|
||||||
db_session = get_db_session()
|
db_session = get_db_session()
|
||||||
|
|
||||||
guest_request = db_session.query(GuestRequest).filter(GuestRequest.id == request_id).first()
|
guest_request = db_session.get(GuestRequest, request_id)
|
||||||
|
|
||||||
if not guest_request:
|
if not guest_request:
|
||||||
db_session.close()
|
db_session.close()
|
||||||
@ -4860,18 +4860,18 @@ def get_guest_request_detail(request_id):
|
|||||||
|
|
||||||
# Bearbeiter-Informationen hinzufügen
|
# Bearbeiter-Informationen hinzufügen
|
||||||
if guest_request.approved_by:
|
if guest_request.approved_by:
|
||||||
approved_by_user = db_session.query(User).filter(User.id == guest_request.approved_by).first()
|
approved_by_user = db_session.get(User, guest_request.approved_by)
|
||||||
if approved_by_user:
|
if approved_by_user:
|
||||||
request_data['approved_by_name'] = approved_by_user.name or approved_by_user.username
|
request_data['approved_by_name'] = approved_by_user.name or approved_by_user.username
|
||||||
|
|
||||||
if guest_request.rejected_by:
|
if guest_request.rejected_by:
|
||||||
rejected_by_user = db_session.query(User).filter(User.id == guest_request.rejected_by).first()
|
rejected_by_user = db_session.get(User, guest_request.rejected_by)
|
||||||
if rejected_by_user:
|
if rejected_by_user:
|
||||||
request_data['rejected_by_name'] = rejected_by_user.name or rejected_by_user.username
|
request_data['rejected_by_name'] = rejected_by_user.name or rejected_by_user.username
|
||||||
|
|
||||||
# Zugewiesener Drucker
|
# Zugewiesener Drucker
|
||||||
if hasattr(guest_request, 'assigned_printer_id') and guest_request.assigned_printer_id:
|
if hasattr(guest_request, 'assigned_printer_id') and guest_request.assigned_printer_id:
|
||||||
assigned_printer = db_session.query(Printer).filter(Printer.id == guest_request.assigned_printer_id).first()
|
assigned_printer = db_session.get(Printer, guest_request.assigned_printer_id)
|
||||||
if assigned_printer:
|
if assigned_printer:
|
||||||
request_data['assigned_printer'] = {
|
request_data['assigned_printer'] = {
|
||||||
'id': assigned_printer.id,
|
'id': assigned_printer.id,
|
||||||
@ -7200,331 +7200,6 @@ def export_admin_logs():
|
|||||||
"message": f"Fehler beim Exportieren: {str(e)}"
|
"message": f"Fehler beim Exportieren: {str(e)}"
|
||||||
}), 500
|
}), 500
|
||||||
|
|
||||||
# ===== FEHLENDE ADMIN API-ENDPUNKTE =====
|
|
||||||
|
|
||||||
@app.route("/api/admin/database/status", methods=['GET'])
|
|
||||||
@login_required
|
|
||||||
@admin_required
|
|
||||||
def api_admin_database_status():
|
|
||||||
"""
|
|
||||||
API-Endpunkt für erweiterten Datenbank-Gesundheitsstatus.
|
|
||||||
|
|
||||||
Führt umfassende Datenbank-Diagnose durch und liefert detaillierte
|
|
||||||
Statusinformationen für den Admin-Bereich.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
JSON: Detaillierter Datenbank-Gesundheitsstatus
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
app_logger.info(f"Datenbank-Gesundheitscheck gestartet von Admin-User {current_user.id}")
|
|
||||||
|
|
||||||
# Datenbankverbindung mit Timeout
|
|
||||||
db_session = get_db_session()
|
|
||||||
start_time = time.time()
|
|
||||||
|
|
||||||
# 1. Basis-Datenbankverbindung testen mit Timeout
|
|
||||||
connection_status = "OK"
|
|
||||||
connection_time_ms = 0
|
|
||||||
try:
|
|
||||||
query_start = time.time()
|
|
||||||
result = db_session.execute(text("SELECT 1 as test_connection")).fetchone()
|
|
||||||
connection_time_ms = round((time.time() - query_start) * 1000, 2)
|
|
||||||
|
|
||||||
if connection_time_ms > 5000: # 5 Sekunden
|
|
||||||
connection_status = f"LANGSAM: {connection_time_ms}ms"
|
|
||||||
elif not result:
|
|
||||||
connection_status = "FEHLER: Keine Antwort"
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
connection_status = f"FEHLER: {str(e)[:100]}"
|
|
||||||
app_logger.error(f"Datenbankverbindungsfehler: {str(e)}")
|
|
||||||
|
|
||||||
# 2. Erweiterte Schema-Integrität prüfen
|
|
||||||
schema_status = {"status": "OK", "details": {}, "missing_tables": [], "table_counts": {}}
|
|
||||||
try:
|
|
||||||
required_tables = {
|
|
||||||
'users': 'Benutzer-Verwaltung',
|
|
||||||
'printers': 'Drucker-Verwaltung',
|
|
||||||
'jobs': 'Druck-Aufträge',
|
|
||||||
'guest_requests': 'Gast-Anfragen',
|
|
||||||
'settings': 'System-Einstellungen'
|
|
||||||
}
|
|
||||||
|
|
||||||
existing_tables = []
|
|
||||||
table_counts = {}
|
|
||||||
|
|
||||||
for table_name, description in required_tables.items():
|
|
||||||
try:
|
|
||||||
count_result = db_session.execute(text(f"SELECT COUNT(*) as count FROM {table_name}")).fetchone()
|
|
||||||
table_count = count_result[0] if count_result else 0
|
|
||||||
|
|
||||||
existing_tables.append(table_name)
|
|
||||||
table_counts[table_name] = table_count
|
|
||||||
schema_status["details"][table_name] = {
|
|
||||||
"exists": True,
|
|
||||||
"count": table_count,
|
|
||||||
"description": description
|
|
||||||
}
|
|
||||||
|
|
||||||
except Exception as table_error:
|
|
||||||
schema_status["missing_tables"].append(table_name)
|
|
||||||
schema_status["details"][table_name] = {
|
|
||||||
"exists": False,
|
|
||||||
"error": str(table_error)[:50],
|
|
||||||
"description": description
|
|
||||||
}
|
|
||||||
app_logger.warning(f"Tabelle {table_name} nicht verfügbar: {str(table_error)}")
|
|
||||||
|
|
||||||
schema_status["table_counts"] = table_counts
|
|
||||||
|
|
||||||
if len(schema_status["missing_tables"]) > 0:
|
|
||||||
schema_status["status"] = f"WARNUNG: {len(schema_status['missing_tables'])} fehlende Tabellen"
|
|
||||||
elif len(existing_tables) != len(required_tables):
|
|
||||||
schema_status["status"] = f"UNVOLLSTÄNDIG: {len(existing_tables)}/{len(required_tables)} Tabellen"
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
schema_status["status"] = f"FEHLER: {str(e)[:100]}"
|
|
||||||
app_logger.error(f"Schema-Integritätsprüfung fehlgeschlagen: {str(e)}")
|
|
||||||
|
|
||||||
# 3. Migrations-Status und Versionsinformationen
|
|
||||||
migration_info = {"status": "Unbekannt", "version": None, "details": {}}
|
|
||||||
try:
|
|
||||||
# Alembic-Version prüfen
|
|
||||||
try:
|
|
||||||
result = db_session.execute(text("SELECT version_num FROM alembic_version ORDER BY version_num DESC LIMIT 1")).fetchone()
|
|
||||||
if result:
|
|
||||||
migration_info["version"] = result[0]
|
|
||||||
migration_info["status"] = "Alembic-Migration aktiv"
|
|
||||||
migration_info["details"]["alembic"] = True
|
|
||||||
else:
|
|
||||||
migration_info["status"] = "Keine Alembic-Migration gefunden"
|
|
||||||
migration_info["details"]["alembic"] = False
|
|
||||||
except Exception:
|
|
||||||
# Fallback: Schema-Informationen sammeln
|
|
||||||
try:
|
|
||||||
# SQLite-spezifische Abfrage
|
|
||||||
tables_result = db_session.execute(text("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")).fetchall()
|
|
||||||
if tables_result:
|
|
||||||
table_list = [row[0] for row in tables_result]
|
|
||||||
migration_info["status"] = f"Schema mit {len(table_list)} Tabellen erkannt"
|
|
||||||
migration_info["details"]["detected_tables"] = table_list
|
|
||||||
migration_info["details"]["alembic"] = False
|
|
||||||
else:
|
|
||||||
migration_info["status"] = "Keine Tabellen erkannt"
|
|
||||||
except Exception:
|
|
||||||
# Weitere Datenbank-Engines
|
|
||||||
migration_info["status"] = "Schema-Erkennung nicht möglich"
|
|
||||||
migration_info["details"]["alembic"] = False
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
migration_info["status"] = f"FEHLER: {str(e)[:100]}"
|
|
||||||
app_logger.error(f"Migrations-Statusprüfung fehlgeschlagen: {str(e)}")
|
|
||||||
|
|
||||||
# 4. Performance-Benchmarks
|
|
||||||
performance_info = {"status": "OK", "benchmarks": {}, "overall_score": 100}
|
|
||||||
try:
|
|
||||||
benchmarks = {}
|
|
||||||
|
|
||||||
# Einfache Select-Query
|
|
||||||
start = time.time()
|
|
||||||
db_session.execute(text("SELECT COUNT(*) FROM users")).fetchone()
|
|
||||||
benchmarks["simple_select"] = round((time.time() - start) * 1000, 2)
|
|
||||||
|
|
||||||
# Join-Query (falls möglich)
|
|
||||||
try:
|
|
||||||
start = time.time()
|
|
||||||
db_session.execute(text("SELECT u.username, COUNT(j.id) FROM users u LEFT JOIN jobs j ON u.id = j.user_id GROUP BY u.id LIMIT 5")).fetchall()
|
|
||||||
benchmarks["join_query"] = round((time.time() - start) * 1000, 2)
|
|
||||||
except Exception:
|
|
||||||
benchmarks["join_query"] = None
|
|
||||||
|
|
||||||
# Insert/Update-Performance simulieren
|
|
||||||
try:
|
|
||||||
start = time.time()
|
|
||||||
db_session.execute(text("SELECT 1 WHERE EXISTS (SELECT 1 FROM users LIMIT 1)")).fetchone()
|
|
||||||
benchmarks["exists_check"] = round((time.time() - start) * 1000, 2)
|
|
||||||
except Exception:
|
|
||||||
benchmarks["exists_check"] = None
|
|
||||||
|
|
||||||
performance_info["benchmarks"] = benchmarks
|
|
||||||
|
|
||||||
# Performance-Score berechnen
|
|
||||||
avg_time = sum(t for t in benchmarks.values() if t is not None) / len([t for t in benchmarks.values() if t is not None])
|
|
||||||
|
|
||||||
if avg_time < 10:
|
|
||||||
performance_info["status"] = "AUSGEZEICHNET"
|
|
||||||
performance_info["overall_score"] = 100
|
|
||||||
elif avg_time < 50:
|
|
||||||
performance_info["status"] = "GUT"
|
|
||||||
performance_info["overall_score"] = 85
|
|
||||||
elif avg_time < 200:
|
|
||||||
performance_info["status"] = "AKZEPTABEL"
|
|
||||||
performance_info["overall_score"] = 70
|
|
||||||
elif avg_time < 1000:
|
|
||||||
performance_info["status"] = "LANGSAM"
|
|
||||||
performance_info["overall_score"] = 50
|
|
||||||
else:
|
|
||||||
performance_info["status"] = "SEHR LANGSAM"
|
|
||||||
performance_info["overall_score"] = 25
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
performance_info["status"] = f"FEHLER: {str(e)[:100]}"
|
|
||||||
performance_info["overall_score"] = 0
|
|
||||||
app_logger.error(f"Performance-Benchmark fehlgeschlagen: {str(e)}")
|
|
||||||
|
|
||||||
# 5. Datenbankgröße und Speicher-Informationen
|
|
||||||
storage_info = {"size": "Unbekannt", "details": {}}
|
|
||||||
try:
|
|
||||||
# SQLite-Datei-Größe
|
|
||||||
db_uri = current_app.config.get('SQLALCHEMY_DATABASE_URI', '')
|
|
||||||
if 'sqlite:///' in db_uri:
|
|
||||||
db_file_path = db_uri.replace('sqlite:///', '')
|
|
||||||
if os.path.exists(db_file_path):
|
|
||||||
file_size = os.path.getsize(db_file_path)
|
|
||||||
storage_info["size"] = f"{file_size / (1024 * 1024):.2f} MB"
|
|
||||||
storage_info["details"]["file_path"] = db_file_path
|
|
||||||
storage_info["details"]["last_modified"] = datetime.fromtimestamp(os.path.getmtime(db_file_path)).isoformat()
|
|
||||||
|
|
||||||
# Speicherplatz-Warnung
|
|
||||||
try:
|
|
||||||
import shutil
|
|
||||||
total, used, free = shutil.disk_usage(os.path.dirname(db_file_path))
|
|
||||||
free_gb = free / (1024**3)
|
|
||||||
storage_info["details"]["disk_free_gb"] = round(free_gb, 2)
|
|
||||||
|
|
||||||
if free_gb < 1:
|
|
||||||
storage_info["warning"] = "Kritisch wenig Speicherplatz"
|
|
||||||
elif free_gb < 5:
|
|
||||||
storage_info["warning"] = "Wenig Speicherplatz verfügbar"
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
# Für andere Datenbanken: Versuche Größe über Metadaten zu ermitteln
|
|
||||||
storage_info["size"] = "Externe Datenbank"
|
|
||||||
storage_info["details"]["database_type"] = "Nicht-SQLite"
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
storage_info["size"] = f"FEHLER: {str(e)[:50]}"
|
|
||||||
app_logger.warning(f"Speicher-Informationen nicht verfügbar: {str(e)}")
|
|
||||||
|
|
||||||
# 6. Aktuelle Verbindungs-Pool-Informationen
|
|
||||||
connection_pool_info = {"status": "Nicht verfügbar", "details": {}}
|
|
||||||
try:
|
|
||||||
# SQLAlchemy Pool-Status (falls verfügbar)
|
|
||||||
engine = db_session.get_bind()
|
|
||||||
if hasattr(engine, 'pool'):
|
|
||||||
pool = engine.pool
|
|
||||||
connection_pool_info["details"]["pool_size"] = getattr(pool, 'size', lambda: 'N/A')()
|
|
||||||
connection_pool_info["details"]["checked_in"] = getattr(pool, 'checkedin', lambda: 'N/A')()
|
|
||||||
connection_pool_info["details"]["checked_out"] = getattr(pool, 'checkedout', lambda: 'N/A')()
|
|
||||||
connection_pool_info["status"] = "Pool aktiv"
|
|
||||||
else:
|
|
||||||
connection_pool_info["status"] = "Kein Pool konfiguriert"
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
connection_pool_info["status"] = f"Pool-Status nicht verfügbar: {str(e)[:50]}"
|
|
||||||
|
|
||||||
db_session.close()
|
|
||||||
|
|
||||||
# Gesamtstatus ermitteln
|
|
||||||
overall_status = "healthy"
|
|
||||||
health_score = 100
|
|
||||||
critical_issues = []
|
|
||||||
warnings = []
|
|
||||||
|
|
||||||
# Kritische Probleme
|
|
||||||
if "FEHLER" in connection_status:
|
|
||||||
overall_status = "critical"
|
|
||||||
health_score -= 50
|
|
||||||
critical_issues.append("Datenbankverbindung fehlgeschlagen")
|
|
||||||
|
|
||||||
if "FEHLER" in schema_status["status"]:
|
|
||||||
overall_status = "critical"
|
|
||||||
health_score -= 30
|
|
||||||
critical_issues.append("Schema-Integrität kompromittiert")
|
|
||||||
|
|
||||||
if performance_info["overall_score"] < 25:
|
|
||||||
overall_status = "critical" if overall_status != "critical" else overall_status
|
|
||||||
health_score -= 25
|
|
||||||
critical_issues.append("Extreme Performance-Probleme")
|
|
||||||
|
|
||||||
# Warnungen
|
|
||||||
if "WARNUNG" in schema_status["status"] or len(schema_status["missing_tables"]) > 0:
|
|
||||||
if overall_status == "healthy":
|
|
||||||
overall_status = "warning"
|
|
||||||
health_score -= 15
|
|
||||||
warnings.append(f"Schema-Probleme: {len(schema_status['missing_tables'])} fehlende Tabellen")
|
|
||||||
|
|
||||||
if "LANGSAM" in connection_status:
|
|
||||||
if overall_status == "healthy":
|
|
||||||
overall_status = "warning"
|
|
||||||
health_score -= 10
|
|
||||||
warnings.append("Langsame Datenbankverbindung")
|
|
||||||
|
|
||||||
if "warning" in storage_info:
|
|
||||||
if overall_status == "healthy":
|
|
||||||
overall_status = "warning"
|
|
||||||
health_score -= 15
|
|
||||||
warnings.append(storage_info["warning"])
|
|
||||||
|
|
||||||
health_score = max(0, health_score) # Nicht unter 0
|
|
||||||
|
|
||||||
total_time = round((time.time() - start_time) * 1000, 2)
|
|
||||||
|
|
||||||
result = {
|
|
||||||
"success": True,
|
|
||||||
"status": overall_status,
|
|
||||||
"health_score": health_score,
|
|
||||||
"critical_issues": critical_issues,
|
|
||||||
"warnings": warnings,
|
|
||||||
"connection": {
|
|
||||||
"status": connection_status,
|
|
||||||
"response_time_ms": connection_time_ms
|
|
||||||
},
|
|
||||||
"schema": schema_status,
|
|
||||||
"migration": migration_info,
|
|
||||||
"performance": performance_info,
|
|
||||||
"storage": storage_info,
|
|
||||||
"connection_pool": connection_pool_info,
|
|
||||||
"timestamp": datetime.now().isoformat(),
|
|
||||||
"check_duration_ms": total_time,
|
|
||||||
"summary": {
|
|
||||||
"database_responsive": "FEHLER" not in connection_status,
|
|
||||||
"schema_complete": len(schema_status["missing_tables"]) == 0,
|
|
||||||
"performance_acceptable": performance_info["overall_score"] >= 50,
|
|
||||||
"storage_adequate": "warning" not in storage_info,
|
|
||||||
"overall_healthy": overall_status == "healthy"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
app_logger.info(f"Datenbank-Gesundheitscheck abgeschlossen: Status={overall_status}, Score={health_score}, Dauer={total_time}ms")
|
|
||||||
|
|
||||||
return jsonify(result)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
app_logger.error(f"Kritischer Fehler beim Datenbank-Gesundheitscheck: {str(e)}")
|
|
||||||
return jsonify({
|
|
||||||
"success": False,
|
|
||||||
"error": f"Kritischer Systemfehler: {str(e)}",
|
|
||||||
"status": "critical",
|
|
||||||
"health_score": 0,
|
|
||||||
"critical_issues": ["System-Gesundheitscheck fehlgeschlagen"],
|
|
||||||
"warnings": [],
|
|
||||||
"connection": {"status": "FEHLER bei der Prüfung"},
|
|
||||||
"schema": {"status": "FEHLER bei der Prüfung"},
|
|
||||||
"migration": {"status": "FEHLER bei der Prüfung"},
|
|
||||||
"performance": {"status": "FEHLER bei der Prüfung"},
|
|
||||||
"storage": {"size": "FEHLER bei der Prüfung"},
|
|
||||||
"timestamp": datetime.now().isoformat(),
|
|
||||||
"summary": {
|
|
||||||
"database_responsive": False,
|
|
||||||
"schema_complete": False,
|
|
||||||
"performance_acceptable": False,
|
|
||||||
"storage_adequate": False,
|
|
||||||
"overall_healthy": False
|
|
||||||
}
|
|
||||||
}), 500
|
|
||||||
|
|
||||||
@app.route("/api/admin/system/status", methods=['GET'])
|
@app.route("/api/admin/system/status", methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
@admin_required
|
@admin_required
|
||||||
|
@ -273,7 +273,32 @@
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
width: 80%;
|
width: 80%;
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
|
max-height: 90vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: rgba(0, 115, 206, 0.2) transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Modal Scrollbar Styling */
|
||||||
|
.modal-content::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(0, 115, 206, 0.2);
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(0, 115, 206, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
.close {
|
.close {
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
float: right;
|
float: right;
|
||||||
|
@ -575,20 +575,71 @@
|
|||||||
/* Premium Modal Styling */
|
/* Premium Modal Styling */
|
||||||
.mercedes-modal {
|
.mercedes-modal {
|
||||||
background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
|
background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
|
||||||
border: 2px solid #e2e8f0;
|
border: 1px solid #e2e8f0;
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0 50px 100px -20px rgba(0, 0, 0, 0.25),
|
0 25px 50px -12px rgba(0, 0, 0, 0.25),
|
||||||
0 20px 40px -10px rgba(0, 0, 0, 0.1);
|
0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||||
border-radius: 20px;
|
border-radius: 16px;
|
||||||
backdrop-filter: blur(20px);
|
backdrop-filter: blur(10px);
|
||||||
|
max-height: 90vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: rgba(0, 115, 206, 0.2) transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .mercedes-modal {
|
.dark .mercedes-modal {
|
||||||
background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
|
background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
|
||||||
border-color: #334155;
|
border-color: #334155;
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0 50px 100px -20px rgba(0, 0, 0, 0.5),
|
0 25px 50px -12px rgba(0, 0, 0, 0.5),
|
||||||
0 20px 40px -10px rgba(0, 0, 0, 0.3);
|
0 4px 6px -1px rgba(0, 0, 0, 0.3);
|
||||||
|
scrollbar-color: rgba(59, 130, 246, 0.3) transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dashboard card als Modal */
|
||||||
|
.dashboard-card {
|
||||||
|
max-height: 90vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: rgba(0, 115, 206, 0.2) transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .dashboard-card {
|
||||||
|
scrollbar-color: rgba(59, 130, 246, 0.3) transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal Scrollbar Styling */
|
||||||
|
.mercedes-modal::-webkit-scrollbar,
|
||||||
|
.dashboard-card::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mercedes-modal::-webkit-scrollbar-track,
|
||||||
|
.dashboard-card::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mercedes-modal::-webkit-scrollbar-thumb,
|
||||||
|
.dashboard-card::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(0, 115, 206, 0.2);
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mercedes-modal::-webkit-scrollbar-thumb:hover,
|
||||||
|
.dashboard-card::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(0, 115, 206, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .mercedes-modal::-webkit-scrollbar-thumb,
|
||||||
|
.dark .dashboard-card::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(59, 130, 246, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .mercedes-modal::-webkit-scrollbar-thumb:hover,
|
||||||
|
.dark .dashboard-card::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(59, 130, 246, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Smooth Scrolling for Calendar */
|
/* Smooth Scrolling for Calendar */
|
||||||
|
@ -270,6 +270,10 @@
|
|||||||
0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
|
max-height: 90vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: rgba(0, 115, 206, 0.2) transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .mercedes-modal {
|
.dark .mercedes-modal {
|
||||||
@ -278,6 +282,35 @@
|
|||||||
box-shadow:
|
box-shadow:
|
||||||
0 25px 50px -12px rgba(0, 0, 0, 0.5),
|
0 25px 50px -12px rgba(0, 0, 0, 0.5),
|
||||||
0 4px 6px -1px rgba(0, 0, 0, 0.3);
|
0 4px 6px -1px rgba(0, 0, 0, 0.3);
|
||||||
|
scrollbar-color: rgba(59, 130, 246, 0.3) transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Improved Modal Scrollbar Styling */
|
||||||
|
.mercedes-modal::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mercedes-modal::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mercedes-modal::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(0, 115, 206, 0.2);
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mercedes-modal::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(0, 115, 206, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .mercedes-modal::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(59, 130, 246, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .mercedes-modal::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(59, 130, 246, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Loading States */
|
/* Loading States */
|
||||||
@ -436,19 +469,39 @@
|
|||||||
.mercedes-modal {
|
.mercedes-modal {
|
||||||
max-height: 90vh;
|
max-height: 90vh;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: rgba(0, 115, 206, 0.2) transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark .mercedes-modal {
|
||||||
|
scrollbar-color: rgba(59, 130, 246, 0.3) transparent;
|
||||||
|
}
|
||||||
|
|
||||||
.mercedes-modal::-webkit-scrollbar {
|
.mercedes-modal::-webkit-scrollbar {
|
||||||
width: 6px;
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mercedes-modal::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mercedes-modal::-webkit-scrollbar-thumb {
|
.mercedes-modal::-webkit-scrollbar-thumb {
|
||||||
background: rgba(0, 115, 206, 0.2);
|
background: rgba(0, 115, 206, 0.2);
|
||||||
border-radius: 3px;
|
border-radius: 4px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mercedes-modal::-webkit-scrollbar-thumb:hover {
|
.mercedes-modal::-webkit-scrollbar-thumb:hover {
|
||||||
background: rgba(0, 115, 206, 0.3);
|
background: rgba(0, 115, 206, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .mercedes-modal::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(59, 130, 246, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .mercedes-modal::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(59, 130, 246, 0.5);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -229,6 +229,10 @@
|
|||||||
0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
|
max-height: 90vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: rgba(0, 115, 206, 0.2) transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .mercedes-modal {
|
.dark .mercedes-modal {
|
||||||
@ -237,6 +241,46 @@
|
|||||||
box-shadow:
|
box-shadow:
|
||||||
0 25px 50px -12px rgba(0, 0, 0, 0.5),
|
0 25px 50px -12px rgba(0, 0, 0, 0.5),
|
||||||
0 4px 6px -1px rgba(0, 0, 0, 0.3);
|
0 4px 6px -1px rgba(0, 0, 0, 0.3);
|
||||||
|
scrollbar-color: rgba(59, 130, 246, 0.3) transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal Scrollbar Styling */
|
||||||
|
.mercedes-modal::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mercedes-modal::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mercedes-modal::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(0, 115, 206, 0.2);
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mercedes-modal::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(0, 115, 206, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .mercedes-modal::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(59, 130, 246, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .mercedes-modal::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(59, 130, 246, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Smooth scrolling for modals */
|
||||||
|
.mercedes-modal {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure modal containers allow scrolling */
|
||||||
|
.modal-container {
|
||||||
|
max-height: 90vh;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Action Buttons */
|
/* Action Buttons */
|
||||||
|
Loading…
x
Reference in New Issue
Block a user