diff --git a/backend/logs/app/app.log b/backend/logs/app/app.log index adf714fe7..15247f122 100644 --- a/backend/logs/app/app.log +++ b/backend/logs/app/app.log @@ -32824,3 +32824,4 @@ NameError: name 'send_from_directory' is not defined 2025-06-15 23:57:41 - [app] app - [DEBUG] DEBUG - Response: 200 2025-06-15 23:57:42 - [app] app - [DEBUG] DEBUG - Request: GET /sw.js 2025-06-15 23:57:42 - [app] app - [DEBUG] DEBUG - Response: 304 +2025-06-16 00:06:52 - [app] app - [INFO] INFO - Optimierte SQLite-Engine erstellt: ./database/myp.db diff --git a/backend/logs/data_management/data_management.log b/backend/logs/data_management/data_management.log index dd264892c..bb794f9ec 100644 --- a/backend/logs/data_management/data_management.log +++ b/backend/logs/data_management/data_management.log @@ -437,3 +437,5 @@ 2025-06-15 23:55:05 - [data_management] data_management - [INFO] INFO - 📊 Massive Konsolidierung: 3 Dateien → 1 Datei (67% Reduktion) 2025-06-15 23:55:07 - [data_management] data_management - [INFO] INFO - ✅ Data Management Module initialisiert 2025-06-15 23:55:07 - [data_management] data_management - [INFO] INFO - 📊 Massive Konsolidierung: 3 Dateien → 1 Datei (67% Reduktion) +2025-06-16 00:06:52 - [data_management] data_management - [INFO] INFO - ✅ Data Management Module initialisiert +2025-06-16 00:06:52 - [data_management] data_management - [INFO] INFO - 📊 Massive Konsolidierung: 3 Dateien → 1 Datei (67% Reduktion) diff --git a/backend/logs/hardware_integration/hardware_integration.log b/backend/logs/hardware_integration/hardware_integration.log index 2fe7854eb..a22b5ed84 100644 --- a/backend/logs/hardware_integration/hardware_integration.log +++ b/backend/logs/hardware_integration/hardware_integration.log @@ -1293,3 +1293,7 @@ 2025-06-15 23:57:41 - [hardware_integration] hardware_integration - [WARNING] WARNING - ⚠️ Konnte Energiedaten für Drucker 6 nicht abrufen: module 'PyP100.PyP100' has no attribute 'P110' 2025-06-15 23:57:41 - [hardware_integration] hardware_integration - [INFO] INFO - ✅ Energiestatistiken erfolgreich gesammelt: 0/7 Geräte online 2025-06-15 23:57:41 - [hardware_integration] hardware_integration - [INFO] INFO - 📊 Gesamtverbrauch: 0.0W aktuell, 0.0Wh heute +2025-06-16 00:06:52 - [hardware_integration] hardware_integration - [INFO] INFO - ✅ PyP100 (TP-Link Tapo) verfügbar +2025-06-16 00:06:52 - [hardware_integration] hardware_integration - [INFO] INFO - ✅ Printer Monitor initialisiert +2025-06-16 00:06:52 - [hardware_integration] hardware_integration - [INFO] INFO - ✅ Hardware Integration Module initialisiert +2025-06-16 00:06:52 - [hardware_integration] hardware_integration - [INFO] INFO - 📊 Massive Konsolidierung: 2 Dateien → 1 Datei (50% Reduktion) diff --git a/backend/logs/job_queue_system/job_queue_system.log b/backend/logs/job_queue_system/job_queue_system.log index 53c429e9b..78eb8f413 100644 --- a/backend/logs/job_queue_system/job_queue_system.log +++ b/backend/logs/job_queue_system/job_queue_system.log @@ -857,3 +857,5 @@ 2025-06-15 23:55:08 - [job_queue_system] job_queue_system - [INFO] INFO - Queue Manager gestartet (Legacy-Kompatibilität) 2025-06-15 23:57:55 - [job_queue_system] job_queue_system - [INFO] INFO - Queue Manager gestoppt (Legacy-Kompatibilität) 2025-06-15 23:57:55 - [job_queue_system] job_queue_system - [INFO] INFO - Queue Manager gestoppt (Legacy-Kompatibilität) +2025-06-16 00:06:52 - [job_queue_system] job_queue_system - [INFO] INFO - ✅ Job & Queue System Module initialisiert +2025-06-16 00:06:52 - [job_queue_system] job_queue_system - [INFO] INFO - 📊 MASSIVE Konsolidierung: 4 Dateien → 1 Datei (75% Reduktion) diff --git a/backend/logs/scheduler/scheduler.log b/backend/logs/scheduler/scheduler.log index b9a466e68..01f226a7c 100644 --- a/backend/logs/scheduler/scheduler.log +++ b/backend/logs/scheduler/scheduler.log @@ -924,3 +924,4 @@ 2025-06-15 23:55:07 - [scheduler] scheduler - [INFO] INFO - Task check_jobs registriert: Intervall 30s, Enabled: True 2025-06-15 23:55:08 - [scheduler] scheduler - [INFO] INFO - Scheduler-Thread gestartet 2025-06-15 23:55:08 - [scheduler] scheduler - [INFO] INFO - Scheduler gestartet +2025-06-16 00:06:52 - [scheduler] scheduler - [INFO] INFO - Task check_jobs registriert: Intervall 30s, Enabled: True diff --git a/backend/logs/security_suite/security_suite.log b/backend/logs/security_suite/security_suite.log index aa875bc3c..bce0a3b70 100644 --- a/backend/logs/security_suite/security_suite.log +++ b/backend/logs/security_suite/security_suite.log @@ -656,3 +656,5 @@ 2025-06-15 23:55:07 - [security_suite] security_suite - [INFO] INFO - ✅ Security Suite Module initialisiert 2025-06-15 23:55:07 - [security_suite] security_suite - [INFO] INFO - 📊 Massive Konsolidierung: 3 Dateien → 1 Datei (67% Reduktion) 2025-06-15 23:55:08 - [security_suite] security_suite - [INFO] INFO - 🔒 Security Suite initialisiert +2025-06-16 00:06:52 - [security_suite] security_suite - [INFO] INFO - ✅ Security Suite Module initialisiert +2025-06-16 00:06:52 - [security_suite] security_suite - [INFO] INFO - 📊 Massive Konsolidierung: 3 Dateien → 1 Datei (67% Reduktion) diff --git a/backend/logs/tapo_controller/tapo_controller.log b/backend/logs/tapo_controller/tapo_controller.log index 608e1f3d3..a603431ed 100644 --- a/backend/logs/tapo_controller/tapo_controller.log +++ b/backend/logs/tapo_controller/tapo_controller.log @@ -910,3 +910,4 @@ 2025-06-15 23:56:27 - [tapo_controller] tapo_controller - [WARNING] WARNING - ⚠️ Versuch 2/3 fehlgeschlagen beim einschalten von 192.168.0.100: HTTPConnectionPool(host='192.168.1.101', port=3128): Read timed out. (read timeout=2) 2025-06-15 23:56:30 - [tapo_controller] tapo_controller - [WARNING] WARNING - ⚠️ Versuch 3/3 fehlgeschlagen beim einschalten von 192.168.0.100: HTTPConnectionPool(host='192.168.1.101', port=3128): Read timed out. (read timeout=2) 2025-06-15 23:56:30 - [tapo_controller] tapo_controller - [ERROR] ERROR - ❌ Alle 3 Versuche fehlgeschlagen beim einschalten der Tapo-Steckdose 192.168.0.100 +2025-06-16 00:06:52 - [tapo_controller] tapo_controller - [INFO] INFO - ✅ tapo controller initialisiert diff --git a/backend/logs/tapo_status_manager/tapo_status_manager.log b/backend/logs/tapo_status_manager/tapo_status_manager.log index 17d90927c..d32e446a6 100644 --- a/backend/logs/tapo_status_manager/tapo_status_manager.log +++ b/backend/logs/tapo_status_manager/tapo_status_manager.log @@ -185,3 +185,4 @@ 2025-06-15 23:54:04 - [tapo_status_manager] tapo_status_manager - [INFO] INFO - TapoStatusManager mit Session-Caching initialisiert 2025-06-15 23:55:05 - [tapo_status_manager] tapo_status_manager - [INFO] INFO - TapoStatusManager mit Session-Caching initialisiert 2025-06-15 23:55:07 - [tapo_status_manager] tapo_status_manager - [INFO] INFO - TapoStatusManager mit Session-Caching initialisiert +2025-06-16 00:06:52 - [tapo_status_manager] tapo_status_manager - [INFO] INFO - TapoStatusManager mit Session-Caching initialisiert diff --git a/backend/logs/utilities_collection/utilities_collection.log b/backend/logs/utilities_collection/utilities_collection.log index e0a785e06..cd3db4f6b 100644 --- a/backend/logs/utilities_collection/utilities_collection.log +++ b/backend/logs/utilities_collection/utilities_collection.log @@ -599,3 +599,5 @@ 2025-06-15 23:55:05 - [utilities_collection] utilities_collection - [INFO] INFO - 🚨 ALLERLETZTE MEGA-Konsolidierung: 12+ Dateien → 1 Datei (90%+ Reduktion) 2025-06-15 23:55:07 - [utilities_collection] utilities_collection - [INFO] INFO - ✅ Utilities Collection initialisiert 2025-06-15 23:55:07 - [utilities_collection] utilities_collection - [INFO] INFO - 🚨 ALLERLETZTE MEGA-Konsolidierung: 12+ Dateien → 1 Datei (90%+ Reduktion) +2025-06-16 00:06:52 - [utilities_collection] utilities_collection - [INFO] INFO - ✅ Utilities Collection initialisiert +2025-06-16 00:06:52 - [utilities_collection] utilities_collection - [INFO] INFO - 🚨 ALLERLETZTE MEGA-Konsolidierung: 12+ Dateien → 1 Datei (90%+ Reduktion) diff --git a/backend/static/css/dark-light-unified.css b/backend/static/css/dark-light-unified.css index 209a0b8ad..5bc23b5d2 100644 --- a/backend/static/css/dark-light-unified.css +++ b/backend/static/css/dark-light-unified.css @@ -77,30 +77,30 @@ .dark { /* === DARK MODE FOUNDATION === */ --bg-primary: #000000; - --bg-secondary: #0a0a0a; - --bg-tertiary: #1a1a1a; - --bg-card: #111111; - --bg-surface: #0d0d0d; + --bg-secondary: #0f0f0f; + --bg-tertiary: #1f1f1f; + --bg-card: #1a1a1a; + --bg-surface: #151515; --bg-overlay: rgba(0, 0, 0, 0.95); - --bg-modal: rgba(17, 17, 17, 0.98); + --bg-modal: rgba(26, 26, 26, 0.98); /* === TEXT COLORS DARK === */ --text-primary: #ffffff; - --text-secondary: #e2e8f0; - --text-tertiary: #cbd5e1; - --text-muted: #94a3b8; - --text-accent: var(--mb-white); - --text-on-primary: var(--mb-black); - --text-success: #10b981; - --text-warning: #f59e0b; - --text-error: #ef4444; + --text-secondary: #f1f5f9; + --text-tertiary: #e2e8f0; + --text-muted: #cbd5e1; + --text-accent: #ffffff; + --text-on-primary: #000000; + --text-success: #22c55e; + --text-warning: #fbbf24; + --text-error: #f87171; /* === BORDER COLORS DARK === */ - --border-primary: #1f2937; - --border-secondary: #374151; - --border-focus: var(--mb-white); - --border-error: #ef4444; - --border-success: #10b981; + --border-primary: #2d2d2d; + --border-secondary: #404040; + --border-focus: #ffffff; + --border-error: #f87171; + --border-success: #22c55e; /* === SHADOWS DARK === */ --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3); @@ -110,32 +110,63 @@ --shadow-modal: 0 25px 50px -12px rgba(0, 0, 0, 0.7); /* === GLASSMORPHISM DARK === */ - --glass-bg: rgba(17, 17, 17, 0.8); - --glass-border: rgba(255, 255, 255, 0.1); - --glass-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); + --glass-bg: rgba(26, 26, 26, 0.85); + --glass-border: rgba(255, 255, 255, 0.15); + --glass-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); --glass-blur: blur(16px); /* === INTERACTIVE STATES DARK === */ - --hover-bg: rgba(255, 255, 255, 0.04); - --active-bg: rgba(255, 255, 255, 0.08); - --focus-ring: 0 0 0 3px rgba(255, 255, 255, 0.1); + --hover-bg: rgba(255, 255, 255, 0.08); + --active-bg: rgba(255, 255, 255, 0.12); + --focus-ring: 0 0 0 3px rgba(255, 255, 255, 0.2); /* === GRADIENTS DARK === */ - --gradient-primary: linear-gradient(135deg, #000000 0%, #0a0a0a 100%); - --gradient-card: linear-gradient(135deg, #111111 0%, #0d0d0d 100%); - --gradient-modal: linear-gradient(135deg, #1a1a1a 0%, #111111 100%); - --gradient-button: linear-gradient(135deg, var(--mb-white) 0%, #f0f0f0 100%); + --gradient-primary: linear-gradient(135deg, #000000 0%, #0f0f0f 100%); + --gradient-card: linear-gradient(135deg, #1a1a1a 0%, #151515 100%); + --gradient-modal: linear-gradient(135deg, #1f1f1f 0%, #1a1a1a 100%); + --gradient-button: linear-gradient(135deg, #ffffff 0%, #f5f5f5 100%); } /* ===== UNIFIED COMPONENT STYLES ===== */ +/* === GLOBAL ROUNDED BORDERS === */ +* { + border-radius: 8px !important; +} + +input, textarea, select, button { + border-radius: 12px !important; +} + +.card, .modal, .glass-card, .mercedes-modal { + border-radius: 20px !important; +} + +.badge, .btn-sm { + border-radius: 16px !important; +} + +.avatar, .profile-image { + border-radius: 50% !important; +} + +/* === ENHANCED SPACING SYSTEM === */ +.spacing-xs { margin: 0.25rem; padding: 0.25rem; } +.spacing-sm { margin: 0.5rem; padding: 0.5rem; } +.spacing-md { margin: 1rem; padding: 1rem; } +.spacing-lg { margin: 1.5rem; padding: 1.5rem; } +.spacing-xl { margin: 2rem; padding: 2rem; } +.spacing-2xl { margin: 3rem; padding: 3rem; } + /* === BUTTONS === */ .btn { - @apply inline-flex items-center justify-center px-4 py-2 text-sm font-medium rounded-lg transition-all duration-200; + @apply inline-flex items-center justify-center px-6 py-3 text-sm font-medium transition-all duration-200; background: var(--bg-card); color: var(--text-primary); border: 1px solid var(--border-primary); box-shadow: var(--shadow-sm); + border-radius: 12px !important; + margin: 0.25rem; } .btn:hover { @@ -173,10 +204,12 @@ .card { background: var(--gradient-card); border: 1px solid var(--border-primary); - border-radius: 16px; + border-radius: 20px !important; box-shadow: var(--shadow-lg); backdrop-filter: var(--glass-blur); transition: all 0.3s ease; + margin: 0.75rem; + padding: 1.5rem; } .card:hover { @@ -187,9 +220,11 @@ .glass-card { background: var(--glass-bg); border: 1px solid var(--glass-border); - border-radius: 16px; + border-radius: 20px !important; box-shadow: var(--glass-shadow); backdrop-filter: var(--glass-blur); + margin: 0.75rem; + padding: 1.5rem; } /* === MODALS === */ @@ -327,10 +362,13 @@ /* === FORM ELEMENTS === */ .form-input { - @apply w-full px-4 py-3 rounded-lg transition-all duration-200; + @apply w-full transition-all duration-200; background: var(--bg-card); border: 1px solid var(--border-primary); color: var(--text-primary); + border-radius: 12px !important; + padding: 1rem 1.25rem; + margin: 0.5rem 0; } .form-input:focus { @@ -344,14 +382,19 @@ } .form-label { - @apply block text-sm font-medium mb-2; + @apply block text-sm font-medium; color: var(--text-secondary); + margin-bottom: 0.5rem; + margin-top: 1rem; } /* === NAVIGATION === */ .nav-item { - @apply px-4 py-2 rounded-lg transition-all duration-200; + @apply transition-all duration-200; color: var(--text-secondary); + border-radius: 12px !important; + padding: 0.75rem 1rem; + margin: 0.25rem; } .nav-item:hover { @@ -366,9 +409,12 @@ /* === ALERTS === */ .alert { - @apply p-4 rounded-lg border; + @apply border; background: var(--bg-card); border-color: var(--border-primary); + border-radius: 16px !important; + padding: 1.25rem; + margin: 1rem 0; } .alert-info { @@ -403,19 +449,24 @@ /* === TABLES === */ .table { @apply w-full border-collapse; + border-radius: 16px !important; + overflow: hidden; + margin: 1rem 0; } .table th { - @apply px-6 py-4 text-left text-sm font-medium uppercase tracking-wider; + @apply text-left text-sm font-medium uppercase tracking-wider; background: var(--bg-tertiary); color: var(--text-tertiary); border-bottom: 1px solid var(--border-primary); + padding: 1rem 1.5rem; } .table td { - @apply px-6 py-4 text-sm; + @apply text-sm; color: var(--text-secondary); border-bottom: 1px solid var(--border-primary); + padding: 1rem 1.5rem; } .table tbody tr:hover { @@ -424,18 +475,24 @@ /* === TOOLTIPS === */ .tooltip { - @apply absolute z-50 px-3 py-2 text-sm rounded-lg shadow-lg; + @apply absolute z-50 text-sm shadow-lg; background: var(--bg-modal); color: var(--text-primary); border: 1px solid var(--border-primary); backdrop-filter: var(--glass-blur); + border-radius: 12px !important; + padding: 0.75rem 1rem; + margin: 0.25rem; } /* === BADGES & STATUS === */ .badge { - @apply inline-flex items-center px-3 py-1 rounded-full text-xs font-medium; + @apply inline-flex items-center text-xs font-medium; background: var(--bg-tertiary); color: var(--text-secondary); + border-radius: 16px !important; + padding: 0.5rem 0.75rem; + margin: 0.25rem; } .badge-primary { @@ -475,14 +532,29 @@ .modal { margin: 1rem; max-width: calc(100% - 2rem); + border-radius: 16px !important; } .card { - border-radius: 12px; + border-radius: 16px !important; + margin: 0.5rem; + padding: 1rem; } .btn { - @apply px-3 py-2 text-xs; + @apply text-xs; + padding: 0.5rem 1rem; + margin: 0.125rem; + } + + .form-input { + padding: 0.75rem 1rem; + margin: 0.25rem 0; + } + + .nav-item { + padding: 0.5rem 0.75rem; + margin: 0.125rem; } } @@ -558,6 +630,57 @@ transition-duration: 200ms; } +/* === ENHANCED DARK MODE CONTRAST === */ +.dark .btn-primary { + background: var(--gradient-button); + color: var(--text-on-primary); + box-shadow: 0 4px 14px 0 rgba(255, 255, 255, 0.1); +} + +.dark .btn-primary:hover { + background: #f5f5f5; + box-shadow: 0 6px 20px 0 rgba(255, 255, 255, 0.15); +} + +.dark .card { + border: 1px solid var(--border-primary); + box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.8), 0 0 0 1px rgba(255, 255, 255, 0.05); +} + +.dark .form-input { + border: 1px solid var(--border-primary); + background: var(--bg-card); +} + +.dark .form-input:focus { + border-color: var(--border-focus); + box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.1); +} + +.dark .alert-info { + background: rgba(0, 115, 206, 0.15); + border-color: var(--mb-primary); + color: #60a5fa; +} + +.dark .alert-success { + background: rgba(34, 197, 94, 0.15); + border-color: var(--text-success); + color: #4ade80; +} + +.dark .alert-warning { + background: rgba(251, 191, 36, 0.15); + border-color: var(--text-warning); + color: #fbbf24; +} + +.dark .alert-error { + background: rgba(248, 113, 113, 0.15); + border-color: var(--text-error); + color: #f87171; +} + /* === UTILITY CLASSES === */ .theme-transition { transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); diff --git a/backend/static/js/htmx-integration.js b/backend/static/js/htmx-integration.js new file mode 100644 index 000000000..a4c67e44c --- /dev/null +++ b/backend/static/js/htmx-integration.js @@ -0,0 +1,481 @@ +/** + * HTMX Non-Invasive Integration für MYP Platform + * ============================================= + * + * Diese Implementierung stellt sicher, dass HTMX vollständig non-invasiv + * geladen wird, ohne das Seitenladen oder bestehende JavaScript-Funktionalität + * zu blockieren oder zu beeinträchtigen. + * + * Features: + * - Lazy Loading von HTMX nur wenn benötigt + * - Progressive Enhancement Pattern + * - Keine Blockierung von kritischen Pfaden + * - Graceful Fallback ohne HTMX + * + * Author: Till Tomczak - MYP Team + * Zweck: Non-invasive HTMX Integration für dynamische UI-Updates + */ + +(function() { + 'use strict'; + + // Configuration + const HTMX_CONFIG = { + cdnUrl: 'https://unpkg.com/htmx.org@1.9.12/dist/htmx.min.js', + localFallback: '/static/js/lib/htmx.min.js', + loadTimeout: 5000, + enableLogging: true, + retryAttempts: 2 + }; + + // State Management + let htmxLoaded = false; + let htmxLoading = false; + let loadPromise = null; + let pendingRequests = []; + + /** + * Logger mit Namespace + */ + function log(level, message, data = null) { + if (!HTMX_CONFIG.enableLogging) return; + + const prefix = '[HTMX-Integration]'; + const styles = { + info: 'color: #0073ce; font-weight: bold;', + warn: 'color: #f59e0b; font-weight: bold;', + error: 'color: #ef4444; font-weight: bold;', + success: 'color: #10b981; font-weight: bold;' + }; + + console[level === 'success' ? 'log' : level]( + `%c${prefix} ${message}`, + styles[level] || styles.info, + data + ); + } + + /** + * Prüft ob HTMX bereits verfügbar ist + */ + function isHtmxAvailable() { + return typeof window.htmx !== 'undefined' && window.htmx; + } + + /** + * Lädt HTMX dynamisch und non-invasiv + */ + async function loadHtmx() { + if (htmxLoaded || isHtmxAvailable()) { + log('info', 'HTMX bereits verfügbar'); + return Promise.resolve(); + } + + if (htmxLoading) { + log('info', 'HTMX wird bereits geladen, verwende existierendes Promise'); + return loadPromise; + } + + htmxLoading = true; + log('info', 'Beginne HTMX-Laden...'); + + loadPromise = new Promise((resolve, reject) => { + const script = document.createElement('script'); + script.type = 'text/javascript'; + script.async = true; + script.defer = true; + + // Timeout Handler + const timeout = setTimeout(() => { + script.remove(); + htmxLoading = false; + reject(new Error('HTMX laden timeout')); + }, HTMX_CONFIG.loadTimeout); + + // Success Handler + script.onload = () => { + clearTimeout(timeout); + htmxLoaded = true; + htmxLoading = false; + log('success', 'HTMX erfolgreich geladen'); + + // HTMX konfigurieren + configureHtmx(); + + // Pending Requests verarbeiten + processPendingRequests(); + + resolve(); + }; + + // Error Handler mit Fallback + script.onerror = () => { + clearTimeout(timeout); + log('warn', 'CDN-Laden fehlgeschlagen, versuche lokalen Fallback...'); + + // Lokaler Fallback + const fallbackScript = document.createElement('script'); + fallbackScript.src = HTMX_CONFIG.localFallback; + fallbackScript.async = true; + fallbackScript.defer = true; + + fallbackScript.onload = () => { + htmxLoaded = true; + htmxLoading = false; + log('success', 'HTMX über lokalen Fallback geladen'); + configureHtmx(); + processPendingRequests(); + resolve(); + }; + + fallbackScript.onerror = () => { + htmxLoading = false; + log('error', 'HTMX konnte nicht geladen werden (CDN + Fallback fehlgeschlagen)'); + reject(new Error('HTMX nicht verfügbar')); + }; + + document.head.appendChild(fallbackScript); + script.remove(); + }; + + // CDN laden + script.src = HTMX_CONFIG.cdnUrl; + document.head.appendChild(script); + }); + + return loadPromise; + } + + /** + * Konfiguriert HTMX nach dem Laden + */ + function configureHtmx() { + if (!isHtmxAvailable()) { + log('warn', 'HTMX nicht verfügbar für Konfiguration'); + return; + } + + try { + // HTMX Konfiguration + htmx.config.selfRequestsOnly = true; + htmx.config.allowEval = false; + htmx.config.allowScriptTags = false; + htmx.config.historyCacheSize = 10; + htmx.config.timeout = 30000; + htmx.config.withCredentials = true; + htmx.config.defaultSwapStyle = 'outerHTML'; + htmx.config.defaultSwapDelay = 0; + htmx.config.defaultSettleDelay = 20; + + // CSRF Token Integration + const csrfToken = document.querySelector('meta[name="csrf-token"]'); + if (csrfToken) { + htmx.config.requestClass = 'htmx-request'; + + // Event Listener für CSRF Token + document.body.addEventListener('htmx:configRequest', function(evt) { + const token = document.querySelector('meta[name="csrf-token"]'); + if (token) { + evt.detail.headers['X-CSRFToken'] = token.getAttribute('content'); + } + }); + } + + // Error Handling + document.body.addEventListener('htmx:responseError', function(evt) { + log('error', 'HTMX Response Error', { + status: evt.detail.xhr.status, + response: evt.detail.xhr.responseText + }); + + // Fallback zu Standard-Navigation bei schweren Fehlern + if (evt.detail.xhr.status >= 500) { + window.location.reload(); + } + }); + + // Success Handling + document.body.addEventListener('htmx:afterRequest', function(evt) { + log('info', 'HTMX Request completed', { + method: evt.detail.requestConfig.verb, + url: evt.detail.requestConfig.path, + status: evt.detail.xhr.status + }); + }); + + // Loading States + document.body.addEventListener('htmx:beforeRequest', function(evt) { + addLoadingState(evt.detail.elt); + }); + + document.body.addEventListener('htmx:afterRequest', function(evt) { + removeLoadingState(evt.detail.elt); + }); + + log('success', 'HTMX konfiguration abgeschlossen'); + + } catch (error) { + log('error', 'Fehler bei HTMX-Konfiguration:', error); + } + } + + /** + * Fügt Loading State zu Elementen hinzu + */ + function addLoadingState(element) { + if (!element) return; + + element.classList.add('htmx-loading'); + element.style.opacity = '0.7'; + element.style.pointerEvents = 'none'; + + // Spinner hinzufügen wenn noch nicht vorhanden + if (!element.querySelector('.htmx-spinner')) { + const spinner = document.createElement('div'); + spinner.className = 'htmx-spinner inline-block ml-2'; + spinner.innerHTML = ''; + element.appendChild(spinner); + } + } + + /** + * Entfernt Loading State von Elementen + */ + function removeLoadingState(element) { + if (!element) return; + + element.classList.remove('htmx-loading'); + element.style.opacity = ''; + element.style.pointerEvents = ''; + + // Spinner entfernen + const spinner = element.querySelector('.htmx-spinner'); + if (spinner) { + spinner.remove(); + } + } + + /** + * Verarbeitet wartende HTMX-Requests + */ + function processPendingRequests() { + if (!isHtmxAvailable() || pendingRequests.length === 0) { + return; + } + + log('info', `Verarbeite ${pendingRequests.length} wartende HTMX-Requests`); + + pendingRequests.forEach(request => { + try { + request(); + } catch (error) { + log('error', 'Fehler beim Verarbeiten wartender Request:', error); + } + }); + + pendingRequests = []; + } + + /** + * Fügt HTMX-Attribute zu einem Element hinzu + */ + function enhanceElement(element, config) { + if (!element || typeof config !== 'object') { + log('warn', 'Ungültige Parameter für enhanceElement'); + return; + } + + const request = () => { + Object.keys(config).forEach(attr => { + const htmxAttr = `hx-${attr}`; + element.setAttribute(htmxAttr, config[attr]); + }); + + // HTMX Element verarbeiten lassen + if (isHtmxAvailable()) { + htmx.process(element); + } + }; + + if (isHtmxAvailable()) { + request(); + } else { + pendingRequests.push(request); + loadHtmx().catch(error => { + log('error', 'HTMX-Laden für Element-Enhancement fehlgeschlagen:', error); + }); + } + } + + /** + * Erstellt ein HTMX-Enhanced Element + */ + function createEnhancedElement(tagName, config, content = '') { + const element = document.createElement(tagName); + element.innerHTML = content; + + enhanceElement(element, config); + return element; + } + + /** + * Lazy Loading Trigger für HTMX + */ + function initializeLazyLoading() { + // Intersection Observer für lazy loading + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + const element = entry.target; + if (element.hasAttribute('data-htmx-lazy')) { + loadHtmx().then(() => { + log('info', 'HTMX lazy-loaded für sichtbares Element'); + }).catch(error => { + log('error', 'HTMX lazy-loading fehlgeschlagen:', error); + }); + observer.unobserve(element); + } + } + }); + }, { threshold: 0.1 }); + + // Alle lazy-loading Elemente beobachten + document.querySelectorAll('[data-htmx-lazy]').forEach(el => { + observer.observe(el); + }); + } + + /** + * Event-basiertes HTMX-Laden + */ + function initializeEventTriggers() { + document.addEventListener('click', (event) => { + const target = event.target.closest('[data-htmx-trigger]'); + if (target) { + event.preventDefault(); + loadHtmx().then(() => { + // Nach dem Laden HTMX-Attribute anwenden + const config = JSON.parse(target.getAttribute('data-htmx-config') || '{}'); + enhanceElement(target, config); + + // Original Click Event simulieren + target.click(); + }).catch(error => { + log('error', 'HTMX-Laden über Event-Trigger fehlgeschlagen:', error); + // Fallback zu Standard-Verhalten + window.location.href = target.href || target.getAttribute('data-fallback-url'); + }); + } + }); + } + + /** + * Auto-Detection für HTMX-Attribute + */ + function autoDetectHtmxElements() { + const htmxAttributes = [ + 'hx-get', 'hx-post', 'hx-put', 'hx-patch', 'hx-delete', + 'hx-trigger', 'hx-target', 'hx-swap', 'hx-boost' + ]; + + const hasHtmxElements = htmxAttributes.some(attr => + document.querySelector(`[${attr}]`) + ); + + if (hasHtmxElements) { + log('info', 'HTMX-Elemente detectiert, lade HTMX...'); + loadHtmx().catch(error => { + log('error', 'Auto-Detection HTMX-Laden fehlgeschlagen:', error); + }); + } + } + + /** + * Public API + */ + window.MYP_HTMX = { + load: loadHtmx, + enhance: enhanceElement, + create: createEnhancedElement, + isLoaded: () => htmxLoaded, + isAvailable: isHtmxAvailable, + config: HTMX_CONFIG + }; + + /** + * Initialization + */ + function initialize() { + log('info', 'Initialisiere HTMX Non-Invasive Integration...'); + + // Event Listeners registrieren + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => { + initializeLazyLoading(); + initializeEventTriggers(); + autoDetectHtmxElements(); + }); + } else { + initializeLazyLoading(); + initializeEventTriggers(); + autoDetectHtmxElements(); + } + + log('success', 'HTMX Integration bereit'); + } + + // Initialize immediately + initialize(); + +})(); + +/** + * CSS für HTMX Loading States + */ +const htmxStyles = ` +.htmx-loading { + position: relative; + transition: opacity 0.2s ease; +} + +.htmx-spinner { + display: inline-block; + animation: htmx-spin 1s linear infinite; +} + +@keyframes htmx-spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +.htmx-indicator { + opacity: 0; + transition: opacity 200ms ease-in; +} + +.htmx-request .htmx-indicator { + opacity: 1; +} + +.htmx-request.htmx-indicator { + opacity: 1; +} + +.htmx-swapping { + opacity: 0; + transition: opacity 200ms ease-out; +} + +.htmx-settling { + opacity: 1; + transition: opacity 200ms ease-in; +} +`; + +// CSS dynamisch injizieren +if (!document.getElementById('htmx-integration-styles')) { + const style = document.createElement('style'); + style.id = 'htmx-integration-styles'; + style.textContent = htmxStyles; + document.head.appendChild(style); +} \ No newline at end of file diff --git a/backend/templates/base.html b/backend/templates/base.html index 4378fda79..64586a9d9 100644 --- a/backend/templates/base.html +++ b/backend/templates/base.html @@ -903,6 +903,9 @@ }); + + +