🎉 Feat/Fix: Log Rotation & Integration Improvement 🎉
📚 Added htmx-integration.js for enhanced interactivity 🌟 💄 Refactored backend/logs/dark-light-unified.css for better theming consistency 📝 🐛 Optimized logging files (app.log, data_management.log, etc.) for performance & error tracking 🛡️
This commit is contained in:
481
backend/static/js/htmx-integration.js
Normal file
481
backend/static/js/htmx-integration.js
Normal file
@@ -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 = '<i class="fas fa-spinner fa-spin"></i>';
|
||||
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);
|
||||
}
|
Reference in New Issue
Block a user