🎉 Fix: Address security vulnerability in admin-unified module & related files (#XXXXXX)

This commit is contained in:
2025-06-16 11:17:10 +02:00
parent 4b2ff50f7a
commit 061bae3005
6 changed files with 663 additions and 98 deletions

View File

@ -332,12 +332,8 @@ class AdminDashboard {
try { try {
const url = `${this.apiBaseUrl}/api/stats`; const url = `${this.apiBaseUrl}/api/stats`;
const response = await fetch(url); const response = await fetch(url);
const data = await this.validateApiResponse(response, 'Live-Statistiken');
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
this.updateStatsDisplay(data); this.updateStatsDisplay(data);
this.retryCount = 0; this.retryCount = 0;
@ -347,6 +343,8 @@ class AdminDashboard {
if (this.retryCount <= this.maxRetries) { if (this.retryCount <= this.maxRetries) {
setTimeout(() => this.loadLiveStats(), 5000); setTimeout(() => this.loadLiveStats(), 5000);
} else {
this.showNotification('Live-Statistiken konnten nicht geladen werden', 'error');
} }
} }
} }
@ -624,12 +622,7 @@ class AdminDashboard {
async loadUserData(userId) { async loadUserData(userId) {
try { try {
const response = await fetch(`${this.apiBaseUrl}/api/admin/users/${userId}`); const response = await fetch(`${this.apiBaseUrl}/api/admin/users/${userId}`);
const data = await this.validateApiResponse(response, 'Benutzerdaten laden');
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (data.success) { if (data.success) {
const user = data.user; const user = data.user;
@ -649,7 +642,7 @@ class AdminDashboard {
} }
} catch (error) { } catch (error) {
console.error('Fehler beim Laden der Benutzerdaten:', error); console.error('Fehler beim Laden der Benutzerdaten:', error);
this.showNotification('❌ Fehler beim Laden der Benutzerdaten', 'error'); this.showNotification(`❌ Fehler beim Laden der Benutzerdaten: ${error.message}`, 'error');
} }
} }
@ -680,7 +673,7 @@ class AdminDashboard {
body: JSON.stringify(userData) body: JSON.stringify(userData)
}); });
const data = await response.json(); const data = await this.validateApiResponse(response, 'Benutzer erstellen');
if (data.success) { if (data.success) {
this.showNotification('✅ Benutzer erfolgreich erstellt!', 'success'); this.showNotification('✅ Benutzer erfolgreich erstellt!', 'success');
@ -895,20 +888,101 @@ class AdminDashboard {
} }
// Error-Management // Error-Management
/**
* 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<Object>} - Validierte JSON-Daten
* @throws {Error} - Bei Validierungsfehlern
*/
async 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('<!DOCTYPE html>') || responseText.includes('<html')) {
console.warn(`❌ HTML-Fehlerseite erhalten statt JSON (${context}):`, responseText.substring(0, 200));
throw new Error(`Server-Fehlerseite erhalten statt JSON-Response (${context})`);
}
console.warn(`❌ Ungültiger Content-Type (${context}):`, contentType);
console.warn(`❌ Response-Text (${context}):`, responseText.substring(0, 500));
throw new Error(`Ungültiger Content-Type: ${contentType || 'fehlt'} (${context})`);
}
// 3. JSON parsing mit detailliertem Error-Handling
let data;
try {
data = await response.json();
} catch (jsonError) {
// Versuche rohen Text zu lesen für Debugging
const rawText = await response.text();
console.error(`❌ JSON-Parsing-Fehler (${context}):`, jsonError);
console.error(`❌ Raw Response (${context}):`, rawText.substring(0, 1000));
throw new Error(`Ungültige JSON-Response: ${jsonError.message} (${context})`);
}
// 4. Prüfe auf null/undefined Response
if (data === null || data === undefined) {
throw new Error(`Leere Response erhalten (${context})`);
}
// 5. Validiere Response-Struktur (wenn success-Feld erwartet wird)
if (typeof data === 'object' && data.hasOwnProperty('success')) {
if (!data.success && data.error) {
console.warn(`❌ API-Fehler (${context}):`, data.error);
throw new Error(`API-Fehler: ${data.error} (${context})`);
}
}
// Erfolgreiche Validierung
console.log(`✅ API-Response validiert (${context}):`, data);
return data;
} catch (error) {
// Error-Logging mit Kontext
console.error(`❌ validateApiResponse fehlgeschlagen (${context}):`, error);
console.error(`❌ Response-Details (${context}):`, {
status: response.status,
statusText: response.statusText,
url: response.url,
headers: Object.fromEntries(response.headers.entries())
});
// Re-throw mit erweiterten Informationen
throw error;
}
}
async checkSystemHealth() { async checkSystemHealth() {
try { try {
const response = await fetch(`${this.apiBaseUrl}/api/admin/system/status`); const response = await fetch(`${this.apiBaseUrl}/api/admin/system/status`);
if (!response.ok) { const data = await this.validateApiResponse(response, 'System-Health-Check');
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
// Prüfe Content-Type vor JSON parsing
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes('application/json')) {
throw new Error('Server returned non-JSON response');
}
const data = await response.json();
if (data.success) { if (data.success) {
this.updateHealthDisplay(data); this.updateHealthDisplay(data);
@ -916,6 +990,7 @@ class AdminDashboard {
} }
} catch (error) { } catch (error) {
console.error('Fehler bei System-Health-Check:', error); console.error('Fehler bei System-Health-Check:', error);
this.showNotification(`System-Health-Check fehlgeschlagen: ${error.message}`, 'error');
} }
} }

View File

@ -6,9 +6,122 @@ class AutoLogoutManager {
this.warningTime = 5; // Warnung 5 Minuten vor Logout this.warningTime = 5; // Warnung 5 Minuten vor Logout
this.isWarningShown = false; this.isWarningShown = false;
// API Base URL Detection
this.apiBaseUrl = this.detectApiBaseUrl();
this.init(); this.init();
} }
/**
* 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<Object>} - Validierte JSON-Daten
* @throws {Error} - Bei Validierungsfehlern
*/
async 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('<!DOCTYPE html>') || responseText.includes('<html')) {
console.warn(`❌ HTML-Fehlerseite erhalten statt JSON (${context}):`, responseText.substring(0, 200));
throw new Error(`Server-Fehlerseite erhalten statt JSON-Response (${context})`);
}
console.warn(`❌ Ungültiger Content-Type (${context}):`, contentType);
console.warn(`❌ Response-Text (${context}):`, responseText.substring(0, 500));
throw new Error(`Ungültiger Content-Type: ${contentType || 'fehlt'} (${context})`);
}
// 3. JSON parsing mit detailliertem Error-Handling
let data;
try {
data = await response.json();
} catch (jsonError) {
// Versuche rohen Text zu lesen für Debugging
const rawText = await response.text();
console.error(`❌ JSON-Parsing-Fehler (${context}):`, jsonError);
console.error(`❌ Raw Response (${context}):`, rawText.substring(0, 1000));
throw new Error(`Ungültige JSON-Response: ${jsonError.message} (${context})`);
}
// 4. Prüfe auf null/undefined Response
if (data === null || data === undefined) {
throw new Error(`Leere Response erhalten (${context})`);
}
// 5. Validiere Response-Struktur (wenn success-Feld erwartet wird)
if (typeof data === 'object' && data.hasOwnProperty('success')) {
if (!data.success && data.error) {
console.warn(`❌ API-Fehler (${context}):`, data.error);
throw new Error(`API-Fehler: ${data.error} (${context})`);
}
}
// Erfolgreiche Validierung
console.log(`✅ API-Response validiert (${context}):`, data);
return data;
} catch (error) {
// Error-Logging mit Kontext
console.error(`❌ validateApiResponse fehlgeschlagen (${context}):`, error);
console.error(`❌ Response-Details (${context}):`, {
status: response.status,
statusText: response.statusText,
url: response.url,
headers: Object.fromEntries(response.headers.entries())
});
// Re-throw mit erweiterten Informationen
throw error;
}
}
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;
}
async init() { async init() {
await this.loadSettings(); await this.loadSettings();
this.setupActivityListeners(); this.setupActivityListeners();
@ -17,9 +130,9 @@ class AutoLogoutManager {
async loadSettings() { async loadSettings() {
try { try {
const response = await fetch('/api/user/settings'); const response = await fetch(`${this.apiBaseUrl}/api/user/settings`);
if (response.ok) { const data = await this.validateApiResponse(response, 'Benutzereinstellungen laden');
const data = await response.json();
if (data.success && data.settings.privacy?.auto_logout) { if (data.success && data.settings.privacy?.auto_logout) {
const timeout = parseInt(data.settings.privacy.auto_logout); const timeout = parseInt(data.settings.privacy.auto_logout);
if (timeout > 0 && timeout !== 'never') { if (timeout > 0 && timeout !== 'never') {
@ -28,7 +141,6 @@ class AutoLogoutManager {
this.timeout = 0; // Deaktiviert this.timeout = 0; // Deaktiviert
} }
} }
}
} catch (error) { } catch (error) {
console.warn('Auto-Logout-Einstellungen konnten nicht geladen werden:', error); console.warn('Auto-Logout-Einstellungen konnten nicht geladen werden:', error);
} }
@ -111,13 +223,15 @@ class AutoLogoutManager {
async sendKeepAlive() { async sendKeepAlive() {
try { try {
await fetch('/api/auth/keep-alive', { const response = await fetch(`${this.apiBaseUrl}/api/auth/keep-alive`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'X-CSRFToken': this.getCSRFToken() 'X-CSRFToken': this.getCSRFToken()
} }
}); });
await this.validateApiResponse(response, 'Keep-Alive senden');
} catch (error) { } catch (error) {
console.warn('Keep-Alive fehlgeschlagen:', error); console.warn('Keep-Alive fehlgeschlagen:', error);
} }

View File

@ -8,6 +8,119 @@
// Chart.js Instanzen Global verfügbar machen // Chart.js Instanzen Global verfügbar machen
window.statsCharts = {}; window.statsCharts = {};
// 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<Object>} - 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('<!DOCTYPE html>') || responseText.includes('<html')) {
console.warn(`❌ HTML-Fehlerseite erhalten statt JSON (${context}):`, responseText.substring(0, 200));
throw new Error(`Server-Fehlerseite erhalten statt JSON-Response (${context})`);
}
console.warn(`❌ Ungültiger Content-Type (${context}):`, contentType);
console.warn(`❌ Response-Text (${context}):`, responseText.substring(0, 500));
throw new Error(`Ungültiger Content-Type: ${contentType || 'fehlt'} (${context})`);
}
// 3. JSON parsing mit detailliertem Error-Handling
let data;
try {
data = await response.json();
} catch (jsonError) {
// Versuche rohen Text zu lesen für Debugging
const rawText = await response.text();
console.error(`❌ JSON-Parsing-Fehler (${context}):`, jsonError);
console.error(`❌ Raw Response (${context}):`, rawText.substring(0, 1000));
throw new Error(`Ungültige JSON-Response: ${jsonError.message} (${context})`);
}
// 4. Prüfe auf null/undefined Response
if (data === null || data === undefined) {
throw new Error(`Leere Response erhalten (${context})`);
}
// 5. Validiere Response-Struktur (wenn success-Feld erwartet wird)
if (typeof data === 'object' && data.hasOwnProperty('success')) {
if (!data.success && data.error) {
console.warn(`❌ API-Fehler (${context}):`, data.error);
throw new Error(`API-Fehler: ${data.error} (${context})`);
}
}
// Erfolgreiche Validierung
console.log(`✅ API-Response validiert (${context}):`, data);
return data;
} catch (error) {
// Error-Logging mit Kontext
console.error(`❌ validateApiResponse fehlgeschlagen (${context}):`, error);
console.error(`❌ Response-Details (${context}):`, {
status: response.status,
statusText: response.statusText,
url: response.url,
headers: Object.fromEntries(response.headers.entries())
});
// Re-throw mit erweiterten Informationen
throw error;
}
}
// Chart.js Konfiguration für Dark/Light Theme // Chart.js Konfiguration für Dark/Light Theme
function getChartTheme() { function getChartTheme() {
const isDark = document.documentElement.classList.contains('dark'); const isDark = document.documentElement.classList.contains('dark');
@ -75,7 +188,7 @@ function getDefaultChartOptions() {
// Job Status Doughnut Chart // Job Status Doughnut Chart
async function createJobStatusChart() { async function createJobStatusChart() {
try { try {
const response = await fetch('/api/stats/charts/job-status'); const response = await fetch(`${API_BASE_URL}/api/stats/charts/job-status`);
const data = await response.json(); const data = await response.json();
if (!response.ok) { if (!response.ok) {
@ -140,7 +253,7 @@ async function createJobStatusChart() {
// Drucker-Nutzung Bar Chart // Drucker-Nutzung Bar Chart
async function createPrinterUsageChart() { async function createPrinterUsageChart() {
try { try {
const response = await fetch('/api/stats/charts/printer-usage'); const response = await fetch(`${API_BASE_URL}/api/stats/charts/printer-usage`);
const data = await response.json(); const data = await response.json();
if (!response.ok) { if (!response.ok) {
@ -180,7 +293,7 @@ async function createPrinterUsageChart() {
// Jobs Timeline Line Chart // Jobs Timeline Line Chart
async function createJobsTimelineChart() { async function createJobsTimelineChart() {
try { try {
const response = await fetch('/api/stats/charts/jobs-timeline'); const response = await fetch(`${API_BASE_URL}/api/stats/charts/jobs-timeline`);
const data = await response.json(); const data = await response.json();
if (!response.ok) { if (!response.ok) {

View File

@ -26,6 +26,97 @@ function detectApiBaseUrl() {
const API_BASE_URL = detectApiBaseUrl(); 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<Object>} - 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('<!DOCTYPE html>') || responseText.includes('<html')) {
console.warn(`❌ HTML-Fehlerseite erhalten statt JSON (${context}):`, responseText.substring(0, 200));
throw new Error(`Server-Fehlerseite erhalten statt JSON-Response (${context})`);
}
console.warn(`❌ Ungültiger Content-Type (${context}):`, contentType);
console.warn(`❌ Response-Text (${context}):`, responseText.substring(0, 500));
throw new Error(`Ungültiger Content-Type: ${contentType || 'fehlt'} (${context})`);
}
// 3. JSON parsing mit detailliertem Error-Handling
let data;
try {
data = await response.json();
} catch (jsonError) {
// Versuche rohen Text zu lesen für Debugging
const rawText = await response.text();
console.error(`❌ JSON-Parsing-Fehler (${context}):`, jsonError);
console.error(`❌ Raw Response (${context}):`, rawText.substring(0, 1000));
throw new Error(`Ungültige JSON-Response: ${jsonError.message} (${context})`);
}
// 4. Prüfe auf null/undefined Response
if (data === null || data === undefined) {
throw new Error(`Leere Response erhalten (${context})`);
}
// 5. Validiere Response-Struktur (wenn success-Feld erwartet wird)
if (typeof data === 'object' && data.hasOwnProperty('success')) {
if (!data.success && data.error) {
console.warn(`❌ API-Fehler (${context}):`, data.error);
throw new Error(`API-Fehler: ${data.error} (${context})`);
}
}
// Erfolgreiche Validierung
console.log(`✅ API-Response validiert (${context}):`, data);
return data;
} catch (error) {
// Error-Logging mit Kontext
console.error(`❌ validateApiResponse fehlgeschlagen (${context}):`, error);
console.error(`❌ Response-Details (${context}):`, {
status: response.status,
statusText: response.statusText,
url: response.url,
headers: Object.fromEntries(response.headers.entries())
});
// Re-throw mit erweiterten Informationen
throw error;
}
}
// DOM-Elemente // DOM-Elemente
const elements = { const elements = {
activeJobs: null, activeJobs: null,
@ -83,17 +174,13 @@ function initializeDashboard() {
// Dashboard-Hauptdaten laden // Dashboard-Hauptdaten laden
async function loadDashboardData() { async function loadDashboardData() {
try { try {
const response = await fetch('/api/dashboard'); const response = await fetch(`${API_BASE_URL}/api/dashboard`);
if (!response.ok) { dashboardData = await validateApiResponse(response, 'Dashboard-Daten');
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
dashboardData = await response.json();
updateDashboardUI(); updateDashboardUI();
} catch (error) { } catch (error) {
console.error('Fehler beim Laden der Dashboard-Daten:', error); console.error('Fehler beim Laden der Dashboard-Daten:', error);
showError('Fehler beim Laden der Dashboard-Daten'); showError(`Fehler beim Laden der Dashboard-Daten: ${error.message}`);
} }
} }
@ -121,18 +208,14 @@ function updateDashboardUI() {
// Aktuelle Jobs laden // Aktuelle Jobs laden
async function loadRecentJobs() { async function loadRecentJobs() {
try { try {
const response = await fetch('/api/jobs/recent'); const response = await fetch(`${API_BASE_URL}/api/jobs/recent`);
if (!response.ok) { const data = await validateApiResponse(response, 'Aktuelle Jobs');
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
updateRecentJobsList(data.jobs); updateRecentJobsList(data.jobs);
} catch (error) { } catch (error) {
console.error('Fehler beim Laden der aktuellen Jobs:', error); console.error('Fehler beim Laden der aktuellen Jobs:', error);
if (elements.recentJobsList) { if (elements.recentJobsList) {
elements.recentJobsList.innerHTML = '<li class="list-group-item text-danger">Fehler beim Laden</li>'; elements.recentJobsList.innerHTML = `<li class="list-group-item text-danger">Fehler beim Laden: ${error.message}</li>`;
} }
} }
} }
@ -167,18 +250,14 @@ function updateRecentJobsList(jobs) {
// Aktuelle Aktivitäten laden // Aktuelle Aktivitäten laden
async function loadRecentActivities() { async function loadRecentActivities() {
try { try {
const response = await fetch('/api/activity/recent'); const response = await fetch(`${API_BASE_URL}/api/activity/recent`);
if (!response.ok) { const data = await validateApiResponse(response, 'Aktivitäten');
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
updateRecentActivitiesList(data.activities); updateRecentActivitiesList(data.activities);
} catch (error) { } catch (error) {
console.error('Fehler beim Laden der Aktivitäten:', error); console.error('Fehler beim Laden der Aktivitäten:', error);
if (elements.recentActivitiesList) { if (elements.recentActivitiesList) {
elements.recentActivitiesList.innerHTML = '<li class="list-group-item text-danger">Fehler beim Laden</li>'; elements.recentActivitiesList.innerHTML = `<li class="list-group-item text-danger">Fehler beim Laden: ${error.message}</li>`;
} }
} }
} }
@ -209,12 +288,8 @@ function updateRecentActivitiesList(activities) {
// Scheduler-Status laden // Scheduler-Status laden
async function loadSchedulerStatus() { async function loadSchedulerStatus() {
try { try {
const response = await fetch('/api/scheduler/status'); const response = await fetch(`${API_BASE_URL}/api/scheduler/status`);
if (!response.ok) { const data = await validateApiResponse(response, 'Scheduler-Status');
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
updateSchedulerStatus(data.running); updateSchedulerStatus(data.running);
} catch (error) { } catch (error) {
@ -253,11 +328,7 @@ async function toggleScheduler() {
} }
}); });
if (!response.ok) { const result = await validateApiResponse(response, 'Scheduler umschalten');
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const result = await response.json();
if (result.success) { if (result.success) {
showSuccess(result.message); showSuccess(result.message);

View File

@ -19,7 +19,124 @@
this.refreshInterval = null; this.refreshInterval = null;
this.autoRefreshEnabled = false; this.autoRefreshEnabled = false;
// API Base URL Detection
this.apiBaseUrl = this.detectApiBaseUrl();
console.log('🔧 JobManager wird initialisiert...'); console.log('🔧 JobManager wird initialisiert...');
console.log('🌐 API Base URL:', this.apiBaseUrl);
}
/**
* 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<Object>} - Validierte JSON-Daten
* @throws {Error} - Bei Validierungsfehlern
*/
async 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('<!DOCTYPE html>') || responseText.includes('<html')) {
console.warn(`❌ HTML-Fehlerseite erhalten statt JSON (${context}):`, responseText.substring(0, 200));
throw new Error(`Server-Fehlerseite erhalten statt JSON-Response (${context})`);
}
console.warn(`❌ Ungültiger Content-Type (${context}):`, contentType);
console.warn(`❌ Response-Text (${context}):`, responseText.substring(0, 500));
throw new Error(`Ungültiger Content-Type: ${contentType || 'fehlt'} (${context})`);
}
// 3. JSON parsing mit detailliertem Error-Handling
let data;
try {
data = await response.json();
} catch (jsonError) {
// Versuche rohen Text zu lesen für Debugging
const rawText = await response.text();
console.error(`❌ JSON-Parsing-Fehler (${context}):`, jsonError);
console.error(`❌ Raw Response (${context}):`, rawText.substring(0, 1000));
throw new Error(`Ungültige JSON-Response: ${jsonError.message} (${context})`);
}
// 4. Prüfe auf null/undefined Response
if (data === null || data === undefined) {
throw new Error(`Leere Response erhalten (${context})`);
}
// 5. Validiere Response-Struktur (wenn success-Feld erwartet wird)
if (typeof data === 'object' && data.hasOwnProperty('success')) {
if (!data.success && data.error) {
console.warn(`❌ API-Fehler (${context}):`, data.error);
throw new Error(`API-Fehler: ${data.error} (${context})`);
}
}
// Erfolgreiche Validierung
console.log(`✅ API-Response validiert (${context}):`, data);
return data;
} catch (error) {
// Error-Logging mit Kontext
console.error(`❌ validateApiResponse fehlgeschlagen (${context}):`, error);
console.error(`❌ Response-Details (${context}):`, {
status: response.status,
statusText: response.statusText,
url: response.url,
headers: Object.fromEntries(response.headers.entries())
});
// Re-throw mit erweiterten Informationen
throw error;
}
}
/**
* Erkennt die korrekte API Base URL basierend auf der aktuellen Umgebung
*/
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;
} }
/** /**
@ -163,11 +280,7 @@
} }
}); });
if (!response.ok) { const data = await this.validateApiResponse(response, 'Jobs laden');
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
// VERBESSERTE NULL-CHECKS für jobs-Daten // VERBESSERTE NULL-CHECKS für jobs-Daten
if (data && typeof data === 'object') { if (data && typeof data === 'object') {
@ -220,9 +333,7 @@
} }
}); });
if (!response.ok) { await this.validateApiResponse(response, 'Job starten');
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
this.showToast('Job erfolgreich gestartet', 'success'); this.showToast('Job erfolgreich gestartet', 'success');
await this.loadJobs(); await this.loadJobs();
@ -248,9 +359,7 @@
} }
}); });
if (!response.ok) { await this.validateApiResponse(response, 'Job pausieren');
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
this.showToast('Job erfolgreich pausiert', 'success'); this.showToast('Job erfolgreich pausiert', 'success');
await this.loadJobs(); await this.loadJobs();
@ -276,9 +385,7 @@
} }
}); });
if (!response.ok) { await this.validateApiResponse(response, 'Job fortsetzen');
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
this.showToast('Job erfolgreich fortgesetzt', 'success'); this.showToast('Job erfolgreich fortgesetzt', 'success');
await this.loadJobs(); await this.loadJobs();
@ -308,9 +415,7 @@
} }
}); });
if (!response.ok) { await this.validateApiResponse(response, 'Job stoppen');
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
this.showToast('Job erfolgreich gestoppt', 'success'); this.showToast('Job erfolgreich gestoppt', 'success');
await this.loadJobs(); await this.loadJobs();
@ -340,9 +445,7 @@
} }
}); });
if (!response.ok) { await this.validateApiResponse(response, 'Job löschen');
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
this.showToast('Job erfolgreich gelöscht', 'success'); this.showToast('Job erfolgreich gelöscht', 'success');
await this.loadJobs(); await this.loadJobs();
@ -691,7 +794,7 @@
try { try {
console.log('📝 Erstelle neuen Job...'); console.log('📝 Erstelle neuen Job...');
const response = await fetch('/api/jobs', { const response = await fetch(`${this.apiBaseUrl}/api/jobs`, {
method: 'POST', method: 'POST',
headers: { headers: {
'X-CSRFToken': this.getCSRFToken() 'X-CSRFToken': this.getCSRFToken()

View File

@ -32,6 +32,97 @@ class PrinterMonitor {
console.log('🌐 API Base URL:', this.apiBaseUrl); console.log('🌐 API Base URL:', this.apiBaseUrl);
} }
/**
* 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<Object>} - Validierte JSON-Daten
* @throws {Error} - Bei Validierungsfehlern
*/
async 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('<!DOCTYPE html>') || responseText.includes('<html')) {
console.warn(`❌ HTML-Fehlerseite erhalten statt JSON (${context}):`, responseText.substring(0, 200));
throw new Error(`Server-Fehlerseite erhalten statt JSON-Response (${context})`);
}
console.warn(`❌ Ungültiger Content-Type (${context}):`, contentType);
console.warn(`❌ Response-Text (${context}):`, responseText.substring(0, 500));
throw new Error(`Ungültiger Content-Type: ${contentType || 'fehlt'} (${context})`);
}
// 3. JSON parsing mit detailliertem Error-Handling
let data;
try {
data = await response.json();
} catch (jsonError) {
// Versuche rohen Text zu lesen für Debugging
const rawText = await response.text();
console.error(`❌ JSON-Parsing-Fehler (${context}):`, jsonError);
console.error(`❌ Raw Response (${context}):`, rawText.substring(0, 1000));
throw new Error(`Ungültige JSON-Response: ${jsonError.message} (${context})`);
}
// 4. Prüfe auf null/undefined Response
if (data === null || data === undefined) {
throw new Error(`Leere Response erhalten (${context})`);
}
// 5. Validiere Response-Struktur (wenn success-Feld erwartet wird)
if (typeof data === 'object' && data.hasOwnProperty('success')) {
if (!data.success && data.error) {
console.warn(`❌ API-Fehler (${context}):`, data.error);
throw new Error(`API-Fehler: ${data.error} (${context})`);
}
}
// Erfolgreiche Validierung
console.log(`✅ API-Response validiert (${context}):`, data);
return data;
} catch (error) {
// Error-Logging mit Kontext
console.error(`❌ validateApiResponse fehlgeschlagen (${context}):`, error);
console.error(`❌ Response-Details (${context}):`, {
status: response.status,
statusText: response.statusText,
url: response.url,
headers: Object.fromEntries(response.headers.entries())
});
// Re-throw mit erweiterten Informationen
throw error;
}
}
/** /**
* Erkennt die korrekte API Base URL basierend auf der aktuellen Umgebung * Erkennt die korrekte API Base URL basierend auf der aktuellen Umgebung
*/ */
@ -148,11 +239,7 @@ class PrinterMonitor {
} }
}); });
if (!response.ok) { const data = await this.validateApiResponse(response, 'Drucker-Status');
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (data.success) { if (data.success) {
this.processPrinterData(data); this.processPrinterData(data);
@ -343,11 +430,13 @@ class PrinterMonitor {
} }
}); });
if (response.ok) { const data = await this.validateApiResponse(response, 'Cache leeren');
if (data.success) {
console.log('🧹 Drucker-Cache geleert'); console.log('🧹 Drucker-Cache geleert');
await this.forceUpdate(); await this.forceUpdate();
} else { } else {
throw new Error(`HTTP ${response.status}`); throw new Error(data.error || 'Cache leeren fehlgeschlagen');
} }
} catch (error) { } catch (error) {