// Dashboard JavaScript - Externe Datei für CSP-Konformität // Globale Variablen let dashboardData = {}; let updateInterval; // API Base URL Detection function detectApiBaseUrl() { const currentPort = window.location.port; const currentProtocol = window.location.protocol; const currentHost = window.location.hostname; // Development-Umgebung (Port 5000) if (currentPort === '5000') { return `${currentProtocol}//${currentHost}:${currentPort}`; } // Production-Umgebung (Port 443 oder kein Port) if (currentPort === '443' || currentPort === '') { return `${currentProtocol}//${currentHost}`; } // Fallback für andere Ports return window.location.origin; } const API_BASE_URL = detectApiBaseUrl(); /** * Zentrale API-Response-Validierung mit umfassendem Error-Handling * @param {Response} response - Fetch Response-Objekt * @param {string} context - Kontext der API-Anfrage für bessere Fehlermeldungen * @returns {Promise} - Validierte JSON-Daten * @throws {Error} - Bei Validierungsfehlern */ async function validateApiResponse(response, context = 'API-Anfrage') { try { // 1. HTTP Status Code prüfen if (!response.ok) { // Spezielle Behandlung für bekannte Fehler-Codes switch (response.status) { case 401: throw new Error(`Authentifizierung fehlgeschlagen (${context})`); case 403: throw new Error(`Zugriff verweigert (${context})`); case 404: throw new Error(`Ressource nicht gefunden (${context})`); case 429: throw new Error(`Zu viele Anfragen (${context})`); case 500: throw new Error(`Serverfehler (${context})`); case 503: throw new Error(`Service nicht verfügbar (${context})`); default: throw new Error(`HTTP ${response.status}: ${response.statusText} (${context})`); } } // 2. Content-Type prüfen (muss application/json enthalten) const contentType = response.headers.get('content-type'); if (!contentType || !contentType.includes('application/json')) { // Versuche Response-Text zu lesen für bessere Fehlermeldung const responseText = await response.text(); // Prüfe auf HTML-Fehlerseiten (typisch für 404/500 Seiten) if (responseText.includes('') || responseText.includes('Fehler beim Laden: ${error.message}`; } } } // Jobs-Liste aktualisieren function updateRecentJobsList(jobs) { if (!elements.recentJobsList) return; if (!jobs || jobs.length === 0) { elements.recentJobsList.innerHTML = '
  • Keine aktuellen Jobs
  • '; return; } const jobsHtml = jobs.map(job => { const statusClass = getStatusClass(job.status); const timeAgo = formatTimeAgo(job.created_at); return `
  • ${escapeHtml(job.name)}
    ${escapeHtml(job.printer_name)} • ${timeAgo}
    ${getStatusText(job.status)}
  • `; }).join(''); elements.recentJobsList.innerHTML = jobsHtml; } // Aktuelle Aktivitäten laden async function loadRecentActivities() { try { const response = await fetch(`${API_BASE_URL}/api/activity/recent`); const data = await validateApiResponse(response, 'Aktivitäten'); updateRecentActivitiesList(data.activities); } catch (error) { console.error('Fehler beim Laden der Aktivitäten:', error); if (elements.recentActivitiesList) { elements.recentActivitiesList.innerHTML = `
  • Fehler beim Laden: ${error.message}
  • `; } } } // Aktivitäten-Liste aktualisieren function updateRecentActivitiesList(activities) { if (!elements.recentActivitiesList) return; if (!activities || activities.length === 0) { elements.recentActivitiesList.innerHTML = '
  • Keine aktuellen Aktivitäten
  • '; return; } const activitiesHtml = activities.map(activity => { const timeAgo = formatTimeAgo(activity.timestamp); return `
  • ${escapeHtml(activity.description)}
    ${timeAgo}
  • `; }).join(''); elements.recentActivitiesList.innerHTML = activitiesHtml; } // Scheduler-Status laden async function loadSchedulerStatus() { try { const response = await fetch(`${API_BASE_URL}/api/scheduler/status`); const data = await validateApiResponse(response, 'Scheduler-Status'); updateSchedulerStatus(data.running); } catch (error) { console.error('Fehler beim Laden des Scheduler-Status:', error); if (elements.schedulerStatus) { elements.schedulerStatus.innerHTML = 'Unbekannt'; } } } // Scheduler-Status aktualisieren function updateSchedulerStatus(isRunning) { if (!elements.schedulerStatus) return; const statusClass = isRunning ? 'bg-success' : 'bg-danger'; const statusText = isRunning ? 'Aktiv' : 'Gestoppt'; elements.schedulerStatus.innerHTML = `${statusText}`; if (elements.schedulerToggleBtn) { elements.schedulerToggleBtn.textContent = isRunning ? 'Scheduler stoppen' : 'Scheduler starten'; elements.schedulerToggleBtn.className = isRunning ? 'btn btn-danger btn-sm' : 'btn btn-success btn-sm'; } } // Scheduler umschalten async function toggleScheduler() { try { const isRunning = dashboardData.scheduler_running; const endpoint = isRunning ? '/api/scheduler/stop' : '/api/scheduler/start'; const response = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' } }); const result = await validateApiResponse(response, 'Scheduler umschalten'); if (result.success) { showSuccess(result.message); // Status sofort neu laden setTimeout(loadSchedulerStatus, 1000); } else { showError(result.error || 'Unbekannter Fehler'); } } catch (error) { console.error('Fehler beim Umschalten des Schedulers:', error); showError('Fehler beim Umschalten des Schedulers'); } } // Dashboard manuell aktualisieren function refreshDashboard() { if (elements.refreshBtn) { elements.refreshBtn.disabled = true; elements.refreshBtn.innerHTML = ' Aktualisiere...'; } Promise.all([ loadDashboardData(), loadRecentJobs(), loadRecentActivities(), loadSchedulerStatus() ]).finally(() => { if (elements.refreshBtn) { elements.refreshBtn.disabled = false; elements.refreshBtn.innerHTML = ' Aktualisieren'; } }); } // Hilfsfunktionen function getStatusClass(status) { const statusClasses = { 'pending': 'bg-warning', 'printing': 'bg-primary', 'completed': 'bg-success', 'failed': 'bg-danger', 'cancelled': 'bg-secondary', 'scheduled': 'bg-info' }; return statusClasses[status] || 'bg-secondary'; } function getStatusText(status) { const statusTexts = { 'pending': 'Wartend', 'printing': 'Druckt', 'completed': 'Abgeschlossen', 'failed': 'Fehlgeschlagen', 'cancelled': 'Abgebrochen', 'scheduled': 'Geplant' }; return statusTexts[status] || status; } function formatTimeAgo(timestamp) { const now = new Date(); const time = new Date(timestamp); const diffMs = now - time; const diffMins = Math.floor(diffMs / 60000); const diffHours = Math.floor(diffMins / 60); const diffDays = Math.floor(diffHours / 24); if (diffMins < 1) return 'Gerade eben'; if (diffMins < 60) return `vor ${diffMins} Min`; if (diffHours < 24) return `vor ${diffHours} Std`; return `vor ${diffDays} Tag${diffDays > 1 ? 'en' : ''}`; } function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } function showSuccess(message) { showNotification(message, 'success'); } function showError(message) { showNotification(message, 'danger'); } function showNotification(message, type) { // Einfache Notification - kann später durch Toast-System ersetzt werden const alertDiv = document.createElement('div'); alertDiv.className = `alert alert-${type} alert-dismissible fade show position-fixed`; alertDiv.style.top = '20px'; alertDiv.style.right = '20px'; alertDiv.style.zIndex = '9999'; alertDiv.innerHTML = ` ${escapeHtml(message)} `; document.body.appendChild(alertDiv); // Automatisch nach 5 Sekunden entfernen setTimeout(() => { if (alertDiv.parentNode) { alertDiv.parentNode.removeChild(alertDiv); } }, 5000); } // Cleanup beim Verlassen der Seite window.addEventListener('beforeunload', function() { if (updateInterval) { clearInterval(updateInterval); } });