/** * Mercedes-Benz MYP Admin Live Dashboard * Echtzeit-Updates für das Admin Panel mit echten Daten */ class AdminLiveDashboard { constructor() { this.isLive = false; this.updateInterval = null; this.retryCount = 0; this.maxRetries = 3; // Dynamische API-Base-URL-Erkennung this.apiBaseUrl = this.detectApiBaseUrl(); console.log('🔗 API Base URL erkannt:', this.apiBaseUrl); this.init(); } detectApiBaseUrl() { const currentHost = window.location.hostname; const currentProtocol = window.location.protocol; const currentPort = window.location.port; console.log('🔍 Live Dashboard API URL Detection:', { currentHost, currentProtocol, currentPort }); // Wenn wir bereits auf dem richtigen Port sind, verwende relative URLs if (currentPort === '443' || !currentPort) { console.log('✅ Verwende relative URLs (HTTPS Port 443)'); return ''; } // Für alle anderen Fälle, verwende HTTPS auf Port 443 const fallbackUrl = `https://${currentHost}`; console.log('🔄 Fallback zu HTTPS:443:', fallbackUrl); return fallbackUrl; } init() { console.log('🚀 Mercedes-Benz MYP Admin Live Dashboard gestartet'); // Live-Status anzeigen this.updateLiveTime(); this.startLiveUpdates(); // Event Listeners this.bindEvents(); // Initial Load this.loadLiveStats(); // Error Monitoring System this.initErrorMonitoring(); } bindEvents() { // Quick Action Buttons const systemStatusBtn = document.getElementById('system-status-btn'); const analyticsBtn = document.getElementById('analytics-btn'); const maintenanceBtn = document.getElementById('maintenance-btn'); if (systemStatusBtn) { systemStatusBtn.addEventListener('click', () => this.showSystemStatus()); } if (analyticsBtn) { analyticsBtn.addEventListener('click', () => this.showAnalytics()); } if (maintenanceBtn) { maintenanceBtn.addEventListener('click', () => this.showMaintenance()); } // Page Visibility API für optimierte Updates document.addEventListener('visibilitychange', () => { if (document.hidden) { this.pauseLiveUpdates(); } else { this.resumeLiveUpdates(); } }); } startLiveUpdates() { this.isLive = true; this.updateLiveIndicator(true); // Live Stats alle 30 Sekunden aktualisieren this.updateInterval = setInterval(() => { this.loadLiveStats(); }, 30000); // Zeit jede Sekunde aktualisieren setInterval(() => { this.updateLiveTime(); }, 1000); } pauseLiveUpdates() { this.isLive = false; this.updateLiveIndicator(false); if (this.updateInterval) { clearInterval(this.updateInterval); } } resumeLiveUpdates() { if (!this.isLive) { this.startLiveUpdates(); this.loadLiveStats(); // Sofortiges Update beim Fortsetzen } } updateLiveIndicator(isLive) { const indicator = document.getElementById('live-indicator'); if (indicator) { if (isLive) { indicator.className = 'w-2 h-2 bg-green-400 rounded-full animate-pulse'; } else { indicator.className = 'w-2 h-2 bg-gray-400 rounded-full'; } } } updateLiveTime() { const timeElement = document.getElementById('live-time'); if (timeElement) { const now = new Date(); timeElement.textContent = now.toLocaleTimeString('de-DE'); } } async loadLiveStats() { try { const url = `${this.apiBaseUrl}/api/admin/stats/live`; console.log('🔄 Lade Live-Statistiken von:', url); const response = await fetch(url, { method: 'GET', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': this.getCSRFToken() } }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data = await response.json(); if (data.success) { this.updateStatsDisplay(data); this.retryCount = 0; // Reset retry count on success // Success notification (optional) this.showQuietNotification('Live-Daten aktualisiert', 'success'); } else { throw new Error(data.error || 'Unbekannter Fehler beim Laden der Live-Statistiken'); } } catch (error) { console.error('Fehler beim Laden der Live-Statistiken:', error); this.retryCount++; if (this.retryCount <= this.maxRetries) { console.log(`Versuche erneut... (${this.retryCount}/${this.maxRetries})`); setTimeout(() => this.loadLiveStats(), 5000); // Retry nach 5 Sekunden } else { this.handleConnectionError(); } } } updateStatsDisplay(data) { // Benutzer Stats this.updateCounter('live-users-count', data.total_users); this.updateProgress('users-progress', Math.min((data.total_users / 20) * 100, 100)); // Max 20 users = 100% // Drucker Stats this.updateCounter('live-printers-count', data.total_printers); this.updateElement('live-printers-online', `${data.online_printers} online`); if (data.total_printers > 0) { this.updateProgress('printers-progress', (data.online_printers / data.total_printers) * 100); } // Jobs Stats this.updateCounter('live-jobs-active', data.active_jobs); this.updateElement('live-jobs-queued', `${data.queued_jobs} in Warteschlange`); this.updateProgress('jobs-progress', Math.min(data.active_jobs * 20, 100)); // Max 5 jobs = 100% // Erfolgsrate Stats this.updateCounter('live-success-rate', `${data.success_rate}%`); this.updateProgress('success-progress', data.success_rate); // Trend Analysis this.updateSuccessTrend(data.success_rate); console.log('📊 Live-Statistiken aktualisiert:', data); } updateCounter(elementId, newValue) { const element = document.getElementById(elementId); if (element) { const currentValue = parseInt(element.textContent) || 0; if (currentValue !== newValue) { this.animateCounter(element, currentValue, newValue); } } } animateCounter(element, from, to) { const duration = 1000; // 1 Sekunde const increment = (to - from) / (duration / 16); // 60 FPS let current = from; const timer = setInterval(() => { current += increment; if ((increment > 0 && current >= to) || (increment < 0 && current <= to)) { current = to; clearInterval(timer); } element.textContent = Math.round(current); }, 16); } updateElement(elementId, newValue) { const element = document.getElementById(elementId); if (element && element.textContent !== newValue) { element.textContent = newValue; } } updateProgress(elementId, percentage) { const element = document.getElementById(elementId); if (element) { element.style.width = `${Math.max(0, Math.min(100, percentage))}%`; } } updateSuccessTrend(successRate) { const trendElement = document.getElementById('success-trend'); if (trendElement) { let trendText = 'Stabil'; let trendClass = 'text-green-500'; let trendIcon = 'M5 10l7-7m0 0l7 7m-7-7v18'; // Up arrow if (successRate >= 95) { trendText = 'Excellent'; trendClass = 'text-green-600'; } else if (successRate >= 80) { trendText = 'Gut'; trendClass = 'text-green-500'; } else if (successRate >= 60) { trendText = 'Mittel'; trendClass = 'text-yellow-500'; trendIcon = 'M5 12h14'; // Horizontal line } else { trendText = 'Niedrig'; trendClass = 'text-red-500'; trendIcon = 'M19 14l-7 7m0 0l-7-7m7 7V3'; // Down arrow } trendElement.className = `text-sm ${trendClass}`; trendElement.innerHTML = ` ${trendText} `; } } showSystemStatus() { // System Status Modal oder Navigation console.log('🔧 System Status angezeigt'); this.showNotification('System Status wird geladen...', 'info'); // Hier könnten weitere System-Details geladen werden const url = `${this.apiBaseUrl}/api/admin/system/status`; fetch(url) .then(response => response.json()) .then(data => { // System Status anzeigen console.log('System Status:', data); }) .catch(error => { console.error('Fehler beim Laden des System Status:', error); }); } showAnalytics() { console.log('📈 Live Analytics angezeigt'); this.showNotification('Analytics werden geladen...', 'info'); // Analytics Tab aktivieren oder Modal öffnen const analyticsTab = document.querySelector('a[href*="tab=system"]'); if (analyticsTab) { analyticsTab.click(); } } showMaintenance() { console.log('🛠️ Wartung angezeigt'); this.showNotification('Wartungsoptionen werden geladen...', 'info'); // Wartungs-Tab aktivieren oder Modal öffnen const systemTab = document.querySelector('a[href*="tab=system"]'); if (systemTab) { systemTab.click(); } } handleConnectionError() { console.error('🔴 Verbindung zu Live-Updates verloren'); this.updateLiveIndicator(false); this.showNotification('Verbindung zu Live-Updates verloren. Versuche erneut...', 'error'); // Auto-Recovery nach 30 Sekunden setTimeout(() => { this.retryCount = 0; this.loadLiveStats(); }, 30000); } showNotification(message, type = 'info') { // Erstelle oder aktualisiere Notification let notification = document.getElementById('live-notification'); if (!notification) { notification = document.createElement('div'); notification.id = 'live-notification'; notification.className = 'fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg max-w-sm'; document.body.appendChild(notification); } const colors = { success: 'bg-green-500 text-white', error: 'bg-red-500 text-white', info: 'bg-blue-500 text-white', warning: 'bg-yellow-500 text-white' }; notification.className = `fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg max-w-sm ${colors[type]} transform transition-all duration-300 translate-x-0`; notification.textContent = message; // Auto-Hide nach 3 Sekunden setTimeout(() => { if (notification) { notification.style.transform = 'translateX(100%)'; setTimeout(() => { if (notification && notification.parentNode) { notification.parentNode.removeChild(notification); } }, 300); } }, 3000); } showQuietNotification(message, type) { // Nur in der Konsole loggen für nicht-störende Updates const emoji = type === 'success' ? '✅' : type === 'error' ? '❌' : 'ℹ️'; console.log(`${emoji} ${message}`); } getCSRFToken() { const meta = document.querySelector('meta[name="csrf-token"]'); return meta ? meta.getAttribute('content') : ''; } // Error Monitoring System initErrorMonitoring() { // Check system health every 30 seconds this.checkSystemHealth(); setInterval(() => this.checkSystemHealth(), 30000); // Setup error alert event handlers this.setupErrorAlertHandlers(); } async checkSystemHealth() { try { const response = await fetch('/api/admin/system-health'); const data = await response.json(); if (data.success) { this.updateHealthDisplay(data); this.updateErrorAlerts(data); } else { console.error('System health check failed:', data.error); } } catch (error) { console.error('Error checking system health:', error); } } updateHealthDisplay(data) { // Update database health status const statusIndicator = document.getElementById('db-status-indicator'); const statusText = document.getElementById('db-status-text'); const lastMigration = document.getElementById('last-migration'); const schemaIntegrity = document.getElementById('schema-integrity'); const recentErrorsCount = document.getElementById('recent-errors-count'); if (statusIndicator && statusText) { if (data.health_status === 'critical') { statusIndicator.className = 'w-3 h-3 bg-red-500 rounded-full animate-pulse'; statusText.textContent = 'Kritisch'; statusText.className = 'text-sm font-medium text-red-600 dark:text-red-400'; } else if (data.health_status === 'warning') { statusIndicator.className = 'w-3 h-3 bg-yellow-500 rounded-full animate-pulse'; statusText.textContent = 'Warnung'; statusText.className = 'text-sm font-medium text-yellow-600 dark:text-yellow-400'; } else { statusIndicator.className = 'w-3 h-3 bg-green-400 rounded-full animate-pulse'; statusText.textContent = 'Gesund'; statusText.className = 'text-sm font-medium text-green-600 dark:text-green-400'; } } if (lastMigration) { lastMigration.textContent = data.last_migration || 'Unbekannt'; } if (schemaIntegrity) { schemaIntegrity.textContent = data.schema_integrity || 'Prüfung'; if (data.schema_integrity === 'FEHLER') { schemaIntegrity.className = 'text-lg font-semibold text-red-600 dark:text-red-400'; } else { schemaIntegrity.className = 'text-lg font-semibold text-green-600 dark:text-green-400'; } } if (recentErrorsCount) { const errorCount = data.recent_errors_count || 0; recentErrorsCount.textContent = errorCount; if (errorCount > 0) { recentErrorsCount.className = 'text-lg font-semibold text-red-600 dark:text-red-400'; } else { recentErrorsCount.className = 'text-lg font-semibold text-green-600 dark:text-green-400'; } } } updateErrorAlerts(data) { const alertContainer = document.getElementById('critical-errors-alert'); const errorList = document.getElementById('error-list'); if (!alertContainer || !errorList) return; const allErrors = [...(data.critical_errors || []), ...(data.warnings || [])]; if (allErrors.length > 0) { // Show alert container alertContainer.classList.remove('hidden'); // Clear previous errors errorList.innerHTML = ''; // Add each error allErrors.forEach(error => { const errorElement = document.createElement('div'); errorElement.className = `p-3 rounded-lg border-l-4 ${ error.severity === 'critical' ? 'bg-red-50 dark:bg-red-900/30 border-red-500' : error.severity === 'high' ? 'bg-orange-50 dark:bg-orange-900/30 border-orange-500' : 'bg-yellow-50 dark:bg-yellow-900/30 border-yellow-500' }`; errorElement.innerHTML = `
💡 ${error.suggested_fix}
📅 ${new Date(error.timestamp).toLocaleString('de-DE')}