From 7de193d4b20f2bf08ce03f7e5a4979e97592e159 Mon Sep 17 00:00:00 2001 From: Till Tomczak Date: Sun, 1 Jun 2025 17:03:31 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9A=20Improved=20admin=20UI=20layout?= =?UTF-8?q?=20&=20design=20consistency=20=F0=9F=8E=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app.py | 363 +----------------- .../admin_guest_requests_overview.html | 25 ++ backend/templates/calendar.html | 65 +++- backend/templates/jobs.html | 59 ++- backend/templates/printers.html | 44 +++ 5 files changed, 202 insertions(+), 354 deletions(-) diff --git a/backend/app.py b/backend/app.py index eaecbce5..f0bf0270 100644 --- a/backend/app.py +++ b/backend/app.py @@ -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 diff --git a/backend/templates/admin_guest_requests_overview.html b/backend/templates/admin_guest_requests_overview.html index e916ffaa..c505fa10 100644 --- a/backend/templates/admin_guest_requests_overview.html +++ b/backend/templates/admin_guest_requests_overview.html @@ -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; diff --git a/backend/templates/calendar.html b/backend/templates/calendar.html index d468b8df..0a21abf8 100644 --- a/backend/templates/calendar.html +++ b/backend/templates/calendar.html @@ -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 */ diff --git a/backend/templates/jobs.html b/backend/templates/jobs.html index 0ef343e3..907689b9 100644 --- a/backend/templates/jobs.html +++ b/backend/templates/jobs.html @@ -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); } {% endblock %} diff --git a/backend/templates/printers.html b/backend/templates/printers.html index 3fff7c32..57050369 100644 --- a/backend/templates/printers.html +++ b/backend/templates/printers.html @@ -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 */