📚 Improved admin UI layout & design consistency 🎨

This commit is contained in:
Till Tomczak 2025-06-01 17:03:31 +02:00
parent 52d0bad64f
commit 7de193d4b2
5 changed files with 202 additions and 354 deletions

View File

@ -533,7 +533,7 @@ def job_owner_required(f):
@wraps(f)
def decorated_function(job_id, *args, **kwargs):
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:
db_session.close()
@ -1463,7 +1463,7 @@ def user_export_data():
"""Exportiert alle Benutzerdaten als JSON für DSGVO-Konformität"""
try:
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:
db_session.close()
@ -1513,7 +1513,7 @@ def user_update_profile_api():
data = request.get_json()
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:
db_session.close()
@ -2730,7 +2730,7 @@ def test_printer_tapo_connection(printer_id):
"""
try:
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:
db_session.close()
@ -3103,7 +3103,7 @@ def admin_update_user_form(user_id):
db_session = get_db_session()
user = db_session.query(User).get(user_id)
user = db_session.get(User, user_id)
if not user:
db_session.close()
flash("Benutzer nicht gefunden.", "error")
@ -3174,7 +3174,7 @@ def admin_update_printer_form(printer_id):
db_session = get_db_session()
printer = db_session.query(Printer).get(printer_id)
printer = db_session.get(Printer, printer_id)
if not printer:
db_session.close()
flash("Drucker nicht gefunden.", "error")
@ -3373,7 +3373,7 @@ def upload_avatar():
# Alte Avatar-Datei löschen falls vorhanden
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:
delete_file_safe(user.avatar_path)
@ -3810,7 +3810,7 @@ def get_job_detail(job_id):
try:
# 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:
db_session.close()
@ -3833,7 +3833,7 @@ def delete_job(job_id):
"""Löscht einen Job."""
try:
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:
db_session.close()
@ -3963,7 +3963,7 @@ def create_job():
db_session = get_db_session()
# 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:
db_session.close()
return jsonify({"error": "Drucker nicht gefunden"}), 404
@ -4016,7 +4016,7 @@ def update_job(job_id):
data = request.json
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:
db_session.close()
@ -4626,7 +4626,7 @@ def approve_guest_request(request_id):
try:
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:
db_session.close()
@ -4656,7 +4656,7 @@ def approve_guest_request(request_id):
# Falls Drucker zugewiesen werden soll
if printer_id:
printer = db_session.query(Printer).filter(Printer.id == printer_id).first()
printer = db_session.get(Printer, printer_id)
if printer:
guest_request.assigned_printer_id = printer_id
@ -4706,7 +4706,7 @@ def reject_guest_request(request_id):
try:
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:
db_session.close()
@ -4773,7 +4773,7 @@ def delete_guest_request(request_id):
try:
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:
db_session.close()
@ -4817,7 +4817,7 @@ def get_guest_request_detail(request_id):
try:
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:
db_session.close()
@ -4860,18 +4860,18 @@ def get_guest_request_detail(request_id):
# Bearbeiter-Informationen hinzufügen
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:
request_data['approved_by_name'] = approved_by_user.name or approved_by_user.username
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:
request_data['rejected_by_name'] = rejected_by_user.name or rejected_by_user.username
# Zugewiesener Drucker
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:
request_data['assigned_printer'] = {
'id': assigned_printer.id,
@ -7200,331 +7200,6 @@ def export_admin_logs():
"message": f"Fehler beim Exportieren: {str(e)}"
}), 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'])
@login_required
@admin_required

View File

@ -273,7 +273,32 @@
border-radius: 8px;
width: 80%;
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 {
color: #aaa;
float: right;

View File

@ -575,20 +575,71 @@
/* Premium Modal Styling */
.mercedes-modal {
background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
border: 2px solid #e2e8f0;
border: 1px solid #e2e8f0;
box-shadow:
0 50px 100px -20px rgba(0, 0, 0, 0.25),
0 20px 40px -10px rgba(0, 0, 0, 0.1);
border-radius: 20px;
backdrop-filter: blur(20px);
0 25px 50px -12px rgba(0, 0, 0, 0.25),
0 4px 6px -1px rgba(0, 0, 0, 0.1);
border-radius: 16px;
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 {
background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
border-color: #334155;
box-shadow:
0 50px 100px -20px rgba(0, 0, 0, 0.5),
0 20px 40px -10px rgba(0, 0, 0, 0.3);
0 25px 50px -12px rgba(0, 0, 0, 0.5),
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 */

View File

@ -270,6 +270,10 @@
0 4px 6px -1px rgba(0, 0, 0, 0.1);
border-radius: 16px;
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 {
@ -278,6 +282,35 @@
box-shadow:
0 25px 50px -12px rgba(0, 0, 0, 0.5),
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 */
@ -436,19 +469,39 @@
.mercedes-modal {
max-height: 90vh;
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 {
width: 6px;
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: 3px;
border-radius: 4px;
transition: all 0.3s ease;
}
.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>
{% endblock %}

View File

@ -229,6 +229,10 @@
0 4px 6px -1px rgba(0, 0, 0, 0.1);
border-radius: 16px;
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 {
@ -237,6 +241,46 @@
box-shadow:
0 25px 50px -12px rgba(0, 0, 0, 0.5),
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 */