Files
Projektarbeit-MYP/backend/static/js/admin-unified.js
Till Tomczak 0fcf04833f Title: Enhanced System Logs and UI Updates
🎉 New system logs have been implemented for improved monitoring and debugging capabilities. These include:

- admin.log
- admin_api.log
- app.log
- data_management.log
- drucker_steuerung.log
- energy_monitoring.log
- hardware_integration.log
- job_queue_system.log
- monitoring_analytics.log
- permissions.
2025-06-20 00:41:55 +02:00

2012 lines
86 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Mercedes-Benz MYP Admin Dashboard - Unified JavaScript
* Konsolidierte Admin-Funktionalitäten ohne Event-Handler-Konflikte
*/
// Globale Variablen und State-Management
class AdminDashboard {
constructor() {
this.csrfToken = null;
this.updateInterval = null;
this.eventListenersAttached = false;
this.apiBaseUrl = this.detectApiBaseUrl();
this.retryCount = 0;
this.maxRetries = 3;
this.isInitialized = false;
this.init();
}
detectApiBaseUrl() {
const currentHost = window.location.hostname;
const currentPort = window.location.port;
if (currentPort === '5000') {
return '';
}
return `http://${currentHost}:5000`;
}
init() {
if (this.isInitialized) {
console.log('🔄 Admin Dashboard bereits initialisiert, überspringe...');
return;
}
console.log('🚀 Initialisiere Mercedes-Benz MYP Admin Dashboard');
// CSRF Token mit verbesserter Extraktion
this.csrfToken = this.extractCSRFToken();
console.log('🔒 CSRF Token:', this.csrfToken ? 'verfügbar' : 'FEHLT!');
// Event-Listener nur einmal registrieren
this.attachEventListeners();
// Live-Updates starten
this.startLiveUpdates();
// Initiale Daten laden
this.loadInitialData();
this.isInitialized = true;
console.log('✅ Admin Dashboard erfolgreich initialisiert');
}
extractCSRFToken() {
// Methode 1: Meta Tag
const metaToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
if (metaToken) {
console.log('🔒 CSRF Token aus meta Tag geladen');
return metaToken;
}
// Methode 2: Hidden Input
const hiddenInput = document.querySelector('input[name="csrf_token"]')?.value;
if (hiddenInput) {
console.log('🔒 CSRF Token aus hidden input geladen');
return hiddenInput;
}
// Methode 3: Cookie (falls verfügbar)
const cookieToken = document.cookie.split('; ').find(row => row.startsWith('csrf_token='))?.split('=')[1];
if (cookieToken) {
console.log('🔒 CSRF Token aus Cookie geladen');
return cookieToken;
}
// Methode 4: Flask-WTF Standard
const flaskToken = document.querySelector('meta[name="csrf-token"]')?.content;
if (flaskToken) {
console.log('🔒 CSRF Token aus Flask-WTF Meta geladen');
return flaskToken;
}
console.error('❌ CSRF Token konnte nicht gefunden werden!');
return null;
}
async loadInitialData() {
console.log('📊 Lade initiale Dashboard-Daten...');
// Live-Statistiken laden
await this.loadLiveStats();
// System-Gesundheitscheck durchführen
await this.checkSystemHealth();
// Aktuelle Zeit anzeigen
this.updateLiveTime();
}
updateLiveTime() {
const liveTimeElement = document.getElementById('live-time');
if (liveTimeElement) {
liveTimeElement.textContent = new Date().toLocaleTimeString('de-DE');
}
}
startLiveUpdates() {
console.log('🔄 Starte Live-Update-Timer...');
// Clear existing interval if any
if (this.updateInterval) {
clearInterval(this.updateInterval);
}
// Update live time every second
this.updateInterval = setInterval(() => {
this.updateLiveTime();
}, 1000);
// Update statistics every 30 seconds
setInterval(() => {
this.loadLiveStats();
}, 30000);
// System health check every 60 seconds
setInterval(() => {
this.checkSystemHealth();
}, 60000);
}
attachEventListeners() {
if (this.eventListenersAttached) {
console.log('⚠️ Event-Listener bereits registriert, überspringe...');
return;
}
// System-Action-Buttons mit Event-Delegation
this.attachSystemButtons();
this.attachUserManagement();
this.attachPrinterManagement();
this.attachJobManagement();
this.attachModalEvents();
this.eventListenersAttached = true;
console.log('📌 Event-Listener erfolgreich registriert');
}
attachSystemButtons() {
// System Status Button
this.addEventListenerSafe('#system-status-btn', 'click', (e) => {
e.preventDefault();
e.stopPropagation();
this.showSystemStatus();
});
// Analytics Button
this.addEventListenerSafe('#analytics-btn', 'click', (e) => {
e.preventDefault();
e.stopPropagation();
this.showAnalytics();
});
// Maintenance Button - DEAKTIVIERT wegen Konflikt mit MaintenanceModal
// Das Wartungs-Modal wird jetzt direkt in admin.html verwaltet
// this.addEventListenerSafe('#maintenance-btn', 'click', (e) => {
// e.preventDefault();
// e.stopPropagation();
// this.showMaintenance();
// });
// Cache leeren
this.addEventListenerSafe('#clear-cache-btn', 'click', (e) => {
e.preventDefault();
e.stopPropagation();
this.clearSystemCache();
});
// Datenbank optimieren
this.addEventListenerSafe('#optimize-db-btn', 'click', (e) => {
e.preventDefault();
e.stopPropagation();
this.optimizeDatabase();
});
// Backup erstellen
this.addEventListenerSafe('#create-backup-btn', 'click', (e) => {
e.preventDefault();
e.stopPropagation();
this.createSystemBackup();
});
// Drucker-Initialisierung erzwingen
this.addEventListenerSafe('#force-init-printers-btn', 'click', (e) => {
e.preventDefault();
e.stopPropagation();
this.forceInitializePrinters();
});
}
attachUserManagement() {
// Neuer Benutzer Button
this.addEventListenerSafe('#add-user-btn', 'click', (e) => {
e.preventDefault();
e.stopPropagation();
this.showUserModal();
});
// Event-Delegation für Benutzer-Aktionen
document.addEventListener('click', (e) => {
if (e.target.closest('.edit-user-btn')) {
e.preventDefault();
e.stopPropagation();
const userId = e.target.closest('button').dataset.userId;
this.editUser(userId);
}
if (e.target.closest('.delete-user-btn')) {
e.preventDefault();
e.stopPropagation();
const userId = e.target.closest('button').dataset.userId;
const userName = e.target.closest('button').dataset.userName;
this.deleteUser(userId, userName);
}
if (e.target.closest('.permissions-user-btn')) {
e.preventDefault();
e.stopPropagation();
const userId = e.target.closest('button').dataset.userId;
const userName = e.target.closest('button').dataset.userName;
this.showPermissionsModal(userId, userName);
}
});
}
attachPrinterManagement() {
// Drucker hinzufügen Button
this.addEventListenerSafe('#add-printer-btn', 'click', (e) => {
e.preventDefault();
e.stopPropagation();
this.showPrinterModal();
});
// Event-Delegation für Drucker-Aktionen
document.addEventListener('click', (e) => {
if (e.target.closest('.manage-printer-btn')) {
e.preventDefault();
e.stopPropagation();
const printerId = e.target.closest('button').dataset.printerId;
this.managePrinter(printerId);
}
if (e.target.closest('.settings-printer-btn')) {
e.preventDefault();
e.stopPropagation();
const printerId = e.target.closest('button').dataset.printerId;
this.showPrinterSettings(printerId);
}
// Smart-Plug Ein/Aus Toggle für Drucker
if (e.target.closest('.toggle-printer-power-btn')) {
e.preventDefault();
e.stopPropagation();
const button = e.target.closest('button');
const printerId = button.dataset.printerId;
const printerName = button.dataset.printerName;
this.togglePrinterPower(printerId, printerName, button);
}
});
}
attachJobManagement() {
// Event-Delegation für Job-Aktionen
document.addEventListener('click', (e) => {
if (e.target.closest('.job-action-btn')) {
e.preventDefault();
e.stopPropagation();
const action = e.target.closest('button').dataset.action;
const jobId = e.target.closest('button').dataset.jobId;
this.handleJobAction(action, jobId);
}
});
}
attachModalEvents() {
// Logs-Funktionalität Event-Listener
this.addEventListenerSafe('#refresh-logs-btn', 'click', (e) => {
e.preventDefault();
e.stopPropagation();
this.loadLogs();
});
this.addEventListenerSafe('#export-logs-btn', 'click', (e) => {
e.preventDefault();
e.stopPropagation();
this.exportLogs();
});
this.addEventListenerSafe('#log-level-filter', 'change', (e) => {
e.preventDefault();
const selectedLevel = e.target.value;
this.loadLogs(selectedLevel);
});
// Modal-bezogene Event-Listener (existierende)
document.addEventListener('click', (e) => {
// User-Modal schließen bei Klick außerhalb
if (e.target.id === 'user-modal') {
this.closeUserModal();
}
// Printer-Modal schließen bei Klick außerhalb
if (e.target.id === 'printer-modal') {
this.closePrinterModal();
}
});
// ESC-Taste für Modal-Schließen
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
this.closeUserModal();
this.closePrinterModal();
}
});
}
addEventListenerSafe(selector, event, handler) {
const element = document.querySelector(selector);
if (element && !element.dataset.listenerAttached) {
element.addEventListener(event, handler);
element.dataset.listenerAttached = 'true';
}
}
startLiveUpdates() {
if (this.updateInterval) {
clearInterval(this.updateInterval);
}
// Statistiken alle 30 Sekunden aktualisieren
this.updateInterval = setInterval(() => {
this.loadLiveStats();
}, 30000);
// Live-Zeit jede Sekunde aktualisieren
setInterval(() => {
this.updateLiveTime();
}, 1000);
// System-Health alle 30 Sekunden prüfen
setInterval(() => {
this.checkSystemHealth();
}, 30000);
console.log('🔄 Live-Updates gestartet');
}
async loadInitialData() {
try {
console.log('📋 Lade initiale Admin-Daten...');
// Live-Statistiken laden
await this.loadLiveStats();
// System-Health prüfen
await this.checkSystemHealth();
// Falls wir auf der Logs-Seite sind, Logs laden
const currentPath = window.location.pathname;
if (currentPath.includes('/admin/logs') || document.querySelector('.admin-logs-tab')) {
await this.loadLogs();
}
console.log('✅ Initiale Admin-Daten geladen');
} catch (error) {
console.error('❌ Fehler beim Laden der initialen Daten:', error);
this.showNotification('Fehler beim Laden der Admin-Daten', 'error');
}
}
async loadLiveStats() {
try {
const url = `${this.apiBaseUrl}/api/stats`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
this.updateStatsDisplay(data);
this.retryCount = 0;
} catch (error) {
console.error('Fehler beim Laden der Live-Statistiken:', error);
this.retryCount++;
if (this.retryCount <= this.maxRetries) {
setTimeout(() => this.loadLiveStats(), 5000);
}
}
}
updateStatsDisplay(data) {
// Sichere Updates mit null-Checks
this.updateElement('live-users-count', data.total_users || 0);
this.updateElement('live-printers-count', data.total_printers || 0);
this.updateElement('live-printers-online', `${data.online_printers || 0} online`);
this.updateElement('live-jobs-active', data.active_jobs || 0);
this.updateElement('live-jobs-queued', `${data.queued_jobs || 0} in Warteschlange`);
this.updateElement('live-success-rate', `${data.success_rate || 0}%`);
// Progress Bars aktualisieren
this.updateProgressBar('users-progress', data.total_users, 20);
this.updateProgressBar('printers-progress', data.online_printers, data.total_printers);
this.updateProgressBar('jobs-progress', data.active_jobs, 10);
this.updateProgressBar('success-progress', data.success_rate, 100);
console.log('📊 Live-Statistiken aktualisiert');
}
updateElement(elementId, value) {
const element = document.getElementById(elementId);
if (element) {
element.textContent = value;
}
}
updateProgressBar(progressId, currentValue, maxValue) {
const progressEl = document.getElementById(progressId);
if (progressEl && currentValue !== undefined && maxValue > 0) {
const percentage = Math.min(100, Math.max(0, (currentValue / maxValue) * 100));
progressEl.style.width = `${percentage}%`;
}
}
updateLiveTime() {
const timeElement = document.getElementById('live-time');
if (timeElement) {
const now = new Date();
timeElement.textContent = now.toLocaleTimeString('de-DE');
}
}
// System-Funktionen
async showSystemStatus() {
console.log('🔧 System Status wird angezeigt');
this.showNotification('System Status wird geladen...', 'info');
}
async showAnalytics() {
console.log('📈 Analytics wird angezeigt');
this.showNotification('Analytics werden geladen...', 'info');
}
async showMaintenance() {
console.log('🛠️ Wartung wird angezeigt');
const systemTab = document.querySelector('a[href*="tab=system"]');
if (systemTab) {
systemTab.click();
}
}
async clearSystemCache() {
if (!confirm('🗑️ Möchten Sie wirklich den System-Cache leeren?')) return;
try {
const response = await fetch(`${this.apiBaseUrl}/api/admin/cache/clear`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': this.csrfToken
}
});
const data = await response.json();
if (data.success) {
this.showNotification('✅ Cache erfolgreich geleert!', 'success');
setTimeout(() => window.location.reload(), 2000);
} else {
this.showNotification('❌ Fehler beim Leeren des Cache', 'error');
}
} catch (error) {
this.showNotification('❌ Fehler beim Leeren des Cache', 'error');
}
}
async optimizeDatabase() {
if (!confirm('🔧 Möchten Sie die Datenbank optimieren?')) return;
this.showNotification('🔄 Datenbank wird optimiert...', 'info');
try {
const response = await fetch(`${this.apiBaseUrl}/api/admin/database/optimize`, {
method: 'POST',
headers: { 'X-CSRFToken': this.csrfToken }
});
const data = await response.json();
if (data.success) {
this.showNotification('✅ Datenbank erfolgreich optimiert!', 'success');
} else {
this.showNotification('❌ Fehler bei der Datenbank-Optimierung', 'error');
}
} catch (error) {
this.showNotification('❌ Fehler bei der Datenbank-Optimierung', 'error');
}
}
async createSystemBackup() {
if (!confirm('💾 Möchten Sie ein System-Backup erstellen?')) return;
this.showNotification('🔄 Backup wird erstellt...', 'info');
try {
const response = await fetch(`${this.apiBaseUrl}/api/admin/backup/create`, {
method: 'POST',
headers: { 'X-CSRFToken': this.csrfToken }
});
const data = await response.json();
if (data.success) {
this.showNotification('✅ Backup erfolgreich erstellt!', 'success');
} else {
this.showNotification('❌ Fehler beim Erstellen des Backups', 'error');
}
} catch (error) {
this.showNotification('❌ Fehler beim Erstellen des Backups', 'error');
}
}
async forceInitializePrinters() {
if (!confirm('🔄 Möchten Sie die Drucker-Initialisierung erzwingen?')) return;
this.showNotification('🔄 Drucker werden initialisiert...', 'info');
try {
const response = await fetch(`${this.apiBaseUrl}/api/admin/printers/force-init`, {
method: 'POST',
headers: { 'X-CSRFToken': this.csrfToken }
});
const data = await response.json();
if (data.success) {
this.showNotification('✅ Drucker erfolgreich initialisiert!', 'success');
setTimeout(() => window.location.reload(), 2000);
} else {
this.showNotification('❌ Fehler bei der Drucker-Initialisierung', 'error');
}
} catch (error) {
this.showNotification('❌ Fehler bei der Drucker-Initialisierung', 'error');
}
}
// User-Management
showUserModal(userId = null) {
const isEdit = userId !== null;
const title = isEdit ? 'Benutzer bearbeiten' : 'Neuer Benutzer';
// Modal HTML erstellen
const modalHtml = `
<div id="user-modal" class="fixed inset-0 bg-black/60 backdrop-blur-sm z-50 flex items-center justify-center p-4">
<div class="bg-white dark:bg-slate-800 rounded-2xl p-8 max-w-md w-full shadow-2xl transform scale-100 transition-all duration-300">
<div class="flex justify-between items-center mb-6">
<h3 class="text-2xl font-bold text-slate-900 dark:text-white">${title}</h3>
<button onclick="this.closest('#user-modal').remove()" class="p-2 hover:bg-gray-100 dark:hover:bg-slate-700 rounded-lg transition-colors">
<svg class="w-6 h-6 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
<form id="user-form" class="space-y-4">
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">E-Mail-Adresse *</label>
<input type="email" name="email" id="user-email" required
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl
focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all
dark:bg-slate-700 dark:text-white bg-slate-50">
</div>
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">Benutzername</label>
<input type="text" name="username" id="user-username"
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl
focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all
dark:bg-slate-700 dark:text-white bg-slate-50">
</div>
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">Name</label>
<input type="text" name="name" id="user-name"
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl
focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all
dark:bg-slate-700 dark:text-white bg-slate-50">
</div>
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">Passwort ${isEdit ? '(leer lassen für keine Änderung)' : '*'}</label>
<input type="password" name="password" id="user-password" ${!isEdit ? 'required' : ''}
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl
focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all
dark:bg-slate-700 dark:text-white bg-slate-50">
</div>
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">Rolle</label>
<select name="role" id="user-role"
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl
focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all
dark:bg-slate-700 dark:text-white bg-slate-50">
<option value="user">Benutzer</option>
<option value="admin">Administrator</option>
</select>
</div>
${isEdit ? `
<div class="flex items-center space-x-2">
<input type="checkbox" name="is_active" id="user-active"
class="w-4 h-4 text-blue-600 bg-slate-100 border-slate-300 rounded focus:ring-blue-500">
<label for="user-active" class="text-sm font-medium text-slate-700 dark:text-slate-300">Aktiv</label>
</div>
` : ''}
<div class="flex justify-end space-x-3 mt-8 pt-6 border-t border-slate-200 dark:border-slate-600">
<button type="button" onclick="this.closest('#user-modal').remove()"
class="px-6 py-3 bg-slate-200 dark:bg-slate-600 text-slate-700 dark:text-slate-300
rounded-xl hover:bg-slate-300 dark:hover:bg-slate-500 transition-colors font-medium">
Abbrechen
</button>
<button type="submit" id="user-submit-btn"
class="px-6 py-3 bg-gradient-to-r from-blue-500 to-blue-600 text-white
rounded-xl hover:from-blue-600 hover:to-blue-700 transition-all duration-300
shadow-lg hover:shadow-xl font-medium">
${isEdit ? 'Aktualisieren' : 'Erstellen'}
</button>
</div>
</form>
</div>
</div>
`;
// Modal zum DOM hinzufügen
document.body.insertAdjacentHTML('beforeend', modalHtml);
// Event-Listener für das Formular
const form = document.getElementById('user-form');
form.addEventListener('submit', (e) => {
e.preventDefault();
e.stopPropagation();
if (isEdit) {
this.updateUser(userId, new FormData(form));
} else {
this.createUser(new FormData(form));
}
});
// Bei Bearbeitung: Benutzer-Daten laden
if (isEdit) {
this.loadUserData(userId);
}
// Fokus auf erstes Eingabefeld
setTimeout(() => {
document.getElementById('user-email').focus();
}, 100);
}
async createUser(formData) {
try {
const submitBtn = document.getElementById('user-submit-btn');
submitBtn.disabled = true;
submitBtn.textContent = 'Wird erstellt...';
// FormData zu JSON konvertieren
const userData = {};
for (let [key, value] of formData.entries()) {
userData[key] = value;
}
const response = await fetch(`${this.apiBaseUrl}/api/admin/users`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': this.csrfToken
},
body: JSON.stringify(userData)
});
const data = await response.json();
if (data.success) {
this.showNotification(`✅ Benutzer "${userData.username}" erfolgreich erstellt!`, 'success');
// Modal schließen
document.getElementById('user-modal').remove();
// Seite nach 1 Sekunde neu laden
setTimeout(() => {
window.location.reload();
}, 1000);
} else {
this.showNotification(`❌ Fehler beim Erstellen: ${data.error || 'Unbekannter Fehler'}`, 'error');
}
} catch (error) {
console.error('Fehler beim Erstellen des Benutzers:', error);
this.showNotification('❌ Fehler beim Erstellen des Benutzers', 'error');
} finally {
const submitBtn = document.getElementById('user-submit-btn');
if (submitBtn) {
submitBtn.disabled = false;
submitBtn.textContent = 'Erstellen';
}
}
}
async updateUser(userId, formData) {
try {
const submitBtn = document.getElementById('user-submit-btn');
submitBtn.disabled = true;
submitBtn.textContent = 'Wird aktualisiert...';
// FormData zu JSON konvertieren
const userData = {};
for (let [key, value] of formData.entries()) {
if (key === 'is_active') {
userData['active'] = value === 'on';
} else if (key === 'password' && !value) {
// Leeres Passwort nicht senden
continue;
} else {
userData[key] = value;
}
}
// Rolle zu is_admin konvertieren
if (userData.role === 'admin') {
userData.role = 'admin';
} else {
userData.role = 'user';
}
const response = await fetch(`${this.apiBaseUrl}/api/admin/users/${userId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': this.csrfToken
},
body: JSON.stringify(userData)
});
const data = await response.json();
if (data.success) {
this.showNotification(`✅ Benutzer erfolgreich aktualisiert!`, 'success');
// Modal schließen
document.getElementById('user-modal').remove();
// Seite nach 1 Sekunde neu laden
setTimeout(() => {
window.location.reload();
}, 1000);
} else {
this.showNotification(`❌ Fehler beim Aktualisieren: ${data.error || 'Unbekannter Fehler'}`, 'error');
}
} catch (error) {
console.error('Fehler beim Aktualisieren des Benutzers:', error);
this.showNotification('❌ Fehler beim Aktualisieren des Benutzers', 'error');
} finally {
const submitBtn = document.getElementById('user-submit-btn');
if (submitBtn) {
submitBtn.disabled = false;
submitBtn.textContent = 'Aktualisieren';
}
}
}
async loadUserData(userId) {
try {
const response = await fetch(`${this.apiBaseUrl}/api/admin/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (data.success) {
const user = data.user;
// Formular mit Benutzerdaten füllen
document.getElementById('user-email').value = user.email || '';
document.getElementById('user-username').value = user.username || '';
document.getElementById('user-name').value = user.name || '';
document.getElementById('user-role').value = user.is_admin ? 'admin' : 'user';
const activeCheckbox = document.getElementById('user-active');
if (activeCheckbox) {
activeCheckbox.checked = user.is_active !== false;
}
} else {
this.showNotification('❌ Fehler beim Laden der Benutzerdaten', 'error');
}
} catch (error) {
console.error('Fehler beim Laden der Benutzerdaten:', error);
this.showNotification('❌ Fehler beim Laden der Benutzerdaten', 'error');
}
}
async createUser(formData) {
const submitBtn = document.getElementById('user-submit-btn');
const originalText = submitBtn.innerHTML;
try {
// Loading-Zustand
submitBtn.innerHTML = '<div class="animate-spin rounded-full h-5 w-5 border-b-2 border-white mx-auto"></div>';
submitBtn.disabled = true;
// FormData zu JSON konvertieren
const userData = {
email: formData.get('email'),
username: formData.get('username') || formData.get('email').split('@')[0],
name: formData.get('name'),
password: formData.get('password'),
is_admin: formData.get('role') === 'admin'
};
const response = await fetch(`${this.apiBaseUrl}/api/admin/users`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': this.csrfToken
},
body: JSON.stringify(userData)
});
const data = await response.json();
if (data.success) {
this.showNotification('✅ Benutzer erfolgreich erstellt!', 'success');
document.getElementById('user-modal').remove();
// Seite nach 1 Sekunde neu laden um neue Benutzerliste zu zeigen
setTimeout(() => {
window.location.reload();
}, 1000);
} else {
this.showNotification(`❌ Fehler: ${data && data.error ? data.error : 'Unbekannter Fehler'}`, 'error');
}
} catch (error) {
console.error('Fehler beim Erstellen des Benutzers:', error);
this.showNotification('❌ Fehler beim Erstellen des Benutzers', 'error');
} finally {
// Button zurücksetzen
submitBtn.innerHTML = originalText;
submitBtn.disabled = false;
}
}
async updateUser(userId, formData) {
const submitBtn = document.getElementById('user-submit-btn');
const originalText = submitBtn.innerHTML;
try {
// Loading-Zustand
submitBtn.innerHTML = '<div class="animate-spin rounded-full h-5 w-5 border-b-2 border-white mx-auto"></div>';
submitBtn.disabled = true;
// FormData zu JSON konvertieren
const userData = {
email: formData.get('email'),
username: formData.get('username'),
name: formData.get('name'),
is_admin: formData.get('role') === 'admin',
is_active: formData.get('is_active') === 'on'
};
// Passwort nur hinzufügen wenn es gesetzt wurde
const password = formData.get('password');
if (password && password.trim()) {
userData.password = password;
}
const response = await fetch(`${this.apiBaseUrl}/api/admin/users/${userId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': this.csrfToken
},
body: JSON.stringify(userData)
});
const data = await response.json();
if (data.success) {
this.showNotification('✅ Benutzer erfolgreich aktualisiert!', 'success');
document.getElementById('user-modal').remove();
// Seite nach 1 Sekunde neu laden um aktualisierte Benutzerliste zu zeigen
setTimeout(() => {
window.location.reload();
}, 1000);
} else {
this.showNotification(`❌ Fehler: ${data && data.error ? data.error : 'Unbekannter Fehler'}`, 'error');
}
} catch (error) {
console.error('Fehler beim Aktualisieren des Benutzers:', error);
this.showNotification('❌ Fehler beim Aktualisieren des Benutzers', 'error');
} finally {
// Button zurücksetzen
submitBtn.innerHTML = originalText;
submitBtn.disabled = false;
}
}
async showPermissionsModal(userId, userName) {
try {
// Erst die aktuellen Benutzer-Daten laden
const response = await fetch(`${this.apiBaseUrl}/api/admin/users/${userId}`);
const data = await response.json();
if (!data.success) {
this.showNotification('❌ Fehler beim Laden der Benutzerdaten', 'error');
return;
}
const user = data.user;
const permissions = user.permissions || {
can_start_jobs: false,
needs_approval: true,
can_approve_jobs: false
};
const modalHtml = `
<div id="permissions-modal" class="fixed inset-0 bg-black/60 backdrop-blur-sm z-50 flex items-center justify-center p-4">
<div class="bg-white dark:bg-slate-800 rounded-2xl p-8 max-w-lg w-full shadow-2xl transform scale-100 transition-all duration-300">
<div class="flex justify-between items-center mb-6">
<h3 class="text-2xl font-bold text-slate-900 dark:text-white">Berechtigungen für ${userName}</h3>
<button onclick="this.closest('#permissions-modal').remove()" class="p-2 hover:bg-gray-100 dark:hover:bg-slate-700 rounded-lg transition-colors">
<svg class="w-6 h-6 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
<div class="space-y-6">
<div class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-xl p-4">
<div class="flex items-center mb-2">
<svg class="w-5 h-5 text-blue-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<h4 class="font-semibold text-blue-800 dark:text-blue-300">Berechtigungsebene</h4>
</div>
<p class="text-sm text-blue-700 dark:text-blue-400">Aktuelle Ebene: <span class="font-medium">${user.permission_level || 'restricted'}</span></p>
</div>
<div class="space-y-4">
<div class="flex items-center justify-between p-4 bg-slate-50 dark:bg-slate-700 rounded-xl">
<div>
<h5 class="font-medium text-slate-900 dark:text-white">Jobs starten</h5>
<p class="text-sm text-slate-500 dark:text-slate-400">Kann 3D-Druck-Jobs eigenständig starten</p>
</div>
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" id="can-start-jobs" ${permissions.can_start_jobs ? 'checked' : ''} class="sr-only peer">
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
</label>
</div>
<div class="flex items-center justify-between p-4 bg-slate-50 dark:bg-slate-700 rounded-xl">
<div>
<h5 class="font-medium text-slate-900 dark:text-white">Genehmigung erforderlich</h5>
<p class="text-sm text-slate-500 dark:text-slate-400">Jobs müssen vor Start genehmigt werden</p>
</div>
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" id="needs-approval" ${permissions.needs_approval ? 'checked' : ''} class="sr-only peer">
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
</label>
</div>
<div class="flex items-center justify-between p-4 bg-slate-50 dark:bg-slate-700 rounded-xl">
<div>
<h5 class="font-medium text-slate-900 dark:text-white">Jobs genehmigen</h5>
<p class="text-sm text-slate-500 dark:text-slate-400">Kann fremde Jobs genehmigen (Ausbilder-Funktion)</p>
</div>
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" id="can-approve-jobs" ${permissions.can_approve_jobs ? 'checked' : ''} class="sr-only peer">
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
</label>
</div>
</div>
<div class="flex justify-end space-x-3 mt-8 pt-6 border-t border-slate-200 dark:border-slate-600">
<button type="button" onclick="this.closest('#permissions-modal').remove()"
class="px-6 py-3 bg-slate-200 dark:bg-slate-600 text-slate-700 dark:text-slate-300
rounded-xl hover:bg-slate-300 dark:hover:bg-slate-500 transition-colors font-medium">
Abbrechen
</button>
<button type="button" id="save-permissions-btn" data-user-id="${userId}"
class="px-6 py-3 bg-gradient-to-r from-green-500 to-green-600 text-white
rounded-xl hover:from-green-600 hover:to-green-700 transition-all duration-300
shadow-lg hover:shadow-xl font-medium">
Berechtigungen speichern
</button>
</div>
</div>
</div>
</div>
`;
// Modal zum DOM hinzufügen
document.body.insertAdjacentHTML('beforeend', modalHtml);
// Event-Listener für Speichern-Button
document.getElementById('save-permissions-btn').addEventListener('click', (e) => {
e.preventDefault();
this.saveUserPermissions(userId, userName);
});
} catch (error) {
console.error('Fehler beim Laden des Permissions-Modals:', error);
this.showNotification('❌ Fehler beim Laden der Berechtigungen', 'error');
}
}
async saveUserPermissions(userId, userName) {
try {
const saveBtn = document.getElementById('save-permissions-btn');
saveBtn.disabled = true;
saveBtn.textContent = 'Wird gespeichert...';
const permissionsData = {
can_start_jobs: document.getElementById('can-start-jobs').checked,
needs_approval: document.getElementById('needs-approval').checked,
can_approve_jobs: document.getElementById('can-approve-jobs').checked
};
const response = await fetch(`${this.apiBaseUrl}/api/admin/users/${userId}/permissions`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': this.csrfToken
},
body: JSON.stringify(permissionsData)
});
const data = await response.json();
if (data.success) {
this.showNotification(`✅ Berechtigungen für ${userName} erfolgreich aktualisiert!`, 'success');
// Modal schließen
document.getElementById('permissions-modal').remove();
// Seite nach 1 Sekunde neu laden
setTimeout(() => {
window.location.reload();
}, 1000);
} else {
this.showNotification(`❌ Fehler beim Speichern: ${data.error || 'Unbekannter Fehler'}`, 'error');
}
} catch (error) {
console.error('Fehler beim Speichern der Berechtigungen:', error);
this.showNotification('❌ Fehler beim Speichern der Berechtigungen', 'error');
} finally {
const saveBtn = document.getElementById('save-permissions-btn');
if (saveBtn) {
saveBtn.disabled = false;
saveBtn.textContent = 'Berechtigungen speichern';
}
}
}
// ===== DIREKTE BENUTZERVERWALTUNGS-FUNKTIONEN =====
async updateUserRole(userId, newRole) {
try {
const response = await fetch(`${this.apiBaseUrl}/api/admin/users/${userId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': this.csrfToken
},
body: JSON.stringify({ role: newRole })
});
const data = await response.json();
if (data.success) {
this.showNotification(`✅ Rolle erfolgreich aktualisiert`, 'success');
// Seite nach kurzer Zeit neu laden
setTimeout(() => window.location.reload(), 1000);
} else {
this.showNotification(`❌ Fehler: ${data.error}`, 'error');
// Rolle zurücksetzen bei Fehler
const selectElement = document.querySelector(`select[data-user-id="${userId}"]`);
if (selectElement) {
selectElement.value = selectElement.value === 'admin' ? 'user' : 'admin';
}
}
} catch (error) {
console.error('Fehler beim Aktualisieren der Rolle:', error);
this.showNotification('❌ Fehler beim Aktualisieren der Rolle', 'error');
}
}
async toggleUserStatus(userId, isActive) {
try {
const response = await fetch(`${this.apiBaseUrl}/api/admin/users/${userId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': this.csrfToken
},
body: JSON.stringify({ active: isActive })
});
const data = await response.json();
if (data.success) {
const statusText = isActive ? 'aktiviert' : 'deaktiviert';
this.showNotification(`✅ Benutzer erfolgreich ${statusText}`, 'success');
// Status-Text visuell aktualisieren
const row = document.querySelector(`tr[data-user-id="${userId}"]`);
const statusSpan = row.querySelector('.status-toggle + span');
if (statusSpan) {
statusSpan.textContent = isActive ? 'Aktiv' : 'Inaktiv';
statusSpan.className = `ml-2 text-xs ${isActive ? 'text-green-700 dark:text-green-400' : 'text-red-700 dark:text-red-400'}`;
}
} else {
this.showNotification(`❌ Fehler: ${data.error}`, 'error');
// Checkbox zurücksetzen
const checkbox = document.querySelector(`input[data-user-id="${userId}"].status-toggle`);
if (checkbox) checkbox.checked = !isActive;
}
} catch (error) {
console.error('Fehler beim Ändern des Status:', error);
this.showNotification('❌ Fehler beim Ändern des Status', 'error');
}
}
async updateUserPermission(userId, permission, isEnabled) {
try {
const permissionData = {
[permission]: isEnabled
};
const response = await fetch(`${this.apiBaseUrl}/api/admin/users/${userId}/permissions`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': this.csrfToken
},
body: JSON.stringify(permissionData)
});
const data = await response.json();
if (data.success) {
const permissionNames = {
'can_start_jobs': 'Jobs starten',
'needs_approval': 'Genehmigung erforderlich',
'can_approve_jobs': 'Jobs genehmigen'
};
const actionText = isEnabled ? 'aktiviert' : 'deaktiviert';
this.showNotification(`${permissionNames[permission]} ${actionText}`, 'success');
} else {
this.showNotification(`❌ Fehler: ${data.error}`, 'error');
// Checkbox zurücksetzen
const checkbox = document.querySelector(`input[data-user-id="${userId}"][data-permission="${permission}"]`);
if (checkbox) checkbox.checked = !isEnabled;
}
} catch (error) {
console.error('Fehler beim Aktualisieren der Berechtigung:', error);
this.showNotification('❌ Fehler beim Aktualisieren der Berechtigung', 'error');
}
}
async resetUserPassword(userId, userName) {
if (!confirm(`🔑 Möchten Sie das Passwort für "${userName}" wirklich zurücksetzen?\n\nDem Benutzer wird ein neues temporäres Passwort zugewiesen.`)) {
return;
}
try {
// Temporäres Passwort generieren
const tempPassword = this.generateTempPassword();
const response = await fetch(`${this.apiBaseUrl}/api/admin/users/${userId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': this.csrfToken
},
body: JSON.stringify({ password: tempPassword })
});
const data = await response.json();
if (data.success) {
// Passwort-Modal anzeigen
this.showPasswordResetModal(userName, tempPassword);
} else {
this.showNotification(`❌ Fehler: ${data.error}`, 'error');
}
} catch (error) {
console.error('Fehler beim Zurücksetzen des Passworts:', error);
this.showNotification('❌ Fehler beim Zurücksetzen des Passworts', 'error');
}
}
generateTempPassword() {
const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789';
let password = '';
for (let i = 0; i < 12; i++) {
password += chars.charAt(Math.floor(Math.random() * chars.length));
}
return password;
}
showPasswordResetModal(userName, tempPassword) {
const modalHtml = `
<div id="password-reset-modal" class="fixed inset-0 bg-black/60 backdrop-blur-sm z-50 flex items-center justify-center p-4">
<div class="bg-white dark:bg-slate-800 rounded-2xl p-8 max-w-md w-full shadow-2xl">
<div class="text-center mb-6">
<div class="w-16 h-16 bg-green-100 dark:bg-green-900/30 rounded-full flex items-center justify-center mx-auto mb-4">
<svg class="w-8 h-8 text-green-600 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"/>
</svg>
</div>
<h3 class="text-2xl font-bold text-slate-900 dark:text-white">Passwort zurückgesetzt</h3>
<p class="text-slate-600 dark:text-slate-400 mt-2">Neues temporäres Passwort für <strong>${userName}</strong></p>
</div>
<div class="bg-slate-50 dark:bg-slate-700 rounded-xl p-4 mb-6">
<div class="flex items-center justify-between">
<div class="font-mono text-lg font-bold text-slate-900 dark:text-white">${tempPassword}</div>
<button onclick="navigator.clipboard.writeText('${tempPassword}'); this.innerHTML='✓ Kopiert'"
class="px-3 py-1 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors text-sm">
Kopieren
</button>
</div>
</div>
<div class="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-xl p-4 mb-6">
<p class="text-sm text-yellow-800 dark:text-yellow-200">
<strong>Wichtig:</strong> Teilen Sie dieses Passwort sicher mit dem Benutzer mit und bitten Sie ihn, es sofort zu ändern.
</p>
</div>
<button onclick="document.getElementById('password-reset-modal').remove()"
class="w-full px-6 py-3 bg-green-500 text-white rounded-xl hover:bg-green-600 transition-colors font-medium">
Verstanden
</button>
</div>
</div>
`;
document.body.insertAdjacentHTML('beforeend', modalHtml);
}
async impersonateUser(userId, userName) {
if (!confirm(`👤 Möchten Sie sich als "${userName}" anmelden?\n\nSie können jederzeit zur Admin-Ansicht zurückkehren.`)) {
return;
}
try {
// Impersonation-API implementieren falls gewünscht
this.showNotification('🚧 Impersonation-Feature wird implementiert...', 'info');
} catch (error) {
console.error('Fehler bei der Benutzer-Impersonation:', error);
this.showNotification('❌ Fehler bei der Benutzer-Impersonation', 'error');
}
}
editUser(userId) {
console.log(`✏️ Benutzer ${userId} wird bearbeitet`);
this.showUserModal(userId);
}
async deleteUser(userId, userName) {
if (!confirm(`🗑️ Möchten Sie den Benutzer "${userName}" wirklich löschen?\n\nDiese Aktion kann nicht rückgängig gemacht werden!`)) {
return;
}
try {
this.showNotification(`🔄 Benutzer "${userName}" wird gelöscht...`, 'info');
const response = await fetch(`${this.apiBaseUrl}/api/admin/users/${userId}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': this.csrfToken
}
});
const data = await response.json();
if (data.success) {
this.showNotification(`✅ Benutzer "${userName}" erfolgreich gelöscht!`, 'success');
// Seite nach 1 Sekunde neu laden um aktualisierte Benutzerliste zu zeigen
setTimeout(() => {
window.location.reload();
}, 1000);
} else {
this.showNotification(`❌ Fehler beim Löschen: ${data && data.error ? data.error : 'Unbekannter Fehler'}`, 'error');
}
} catch (error) {
console.error('Fehler beim Löschen des Benutzers:', error);
this.showNotification('❌ Fehler beim Löschen des Benutzers', 'error');
}
}
// Printer-Management
/**
* Zeigt Drucker-Hinzufügen-Seite an - Backend-Only-Architektur
* Entspricht der ursprünglichen showPrinterModal() JavaScript-Funktion
*/
showPrinterModal() {
console.log('🖨️ Drucker-Hinzufügen-Seite wird geladen (Backend-Only)');
this.showNotification('Weiterleitung zu Drucker hinzufügen...', 'info');
// Backend-Redirect anstatt JavaScript-Modal
window.location.href = '/admin/printers/add';
}
/**
* Drucker-Verwaltungsseite laden - Backend-Only-Architektur
* Entspricht der ursprünglichen managePrinter() JavaScript-Funktion
*/
managePrinter(printerId) {
console.log(`🔧 Drucker ${printerId} Verwaltungsseite wird geladen (Backend-Only)`);
this.showNotification(`Drucker ${printerId} Verwaltung wird geladen...`, 'info');
// Validierung
if (!printerId || isNaN(printerId)) {
this.showNotification('Ungültige Drucker-ID', 'error');
return;
}
// Backend-Redirect zur vollständigen Verwaltungsseite
window.location.href = `/admin/printers/${printerId}/manage`;
}
/**
* Drucker-Einstellungsseite laden - Backend-Only-Architektur
* Entspricht der ursprünglichen showPrinterSettings() JavaScript-Funktion
*/
showPrinterSettings(printerId) {
console.log(`⚙️ Drucker-Einstellungen ${printerId} Seite wird geladen (Backend-Only)`);
this.showNotification(`Drucker-Einstellungen werden geladen...`, 'info');
// Validierung
if (!printerId || isNaN(printerId)) {
this.showNotification('Ungültige Drucker-ID', 'error');
return;
}
// Backend-Redirect zur Einstellungsseite
window.location.href = `/admin/printers/${printerId}/settings`;
}
// Smart-Plug Ein/Aus Toggle für Drucker
async togglePrinterPower(printerId, printerName, button) {
console.log(`🔌 Smart-Plug Toggle für Drucker ${printerId} (${printerName})`);
// Validierung der Parameter
if (!button || !button.classList) {
console.error('❌ Ungültiger Button-Parameter:', button);
this.showNotification('❌ Fehler: Ungültiger Button-Parameter', 'error');
return;
}
// Bestätigungsdialog
const confirmMessage = `Möchten Sie die Steckdose für "${printerName}" umschalten?\n\nDies schaltet den Drucker ein/aus.`;
if (!confirm(confirmMessage)) return;
// Button-Zustand während der Anfrage anzeigen
const originalContent = button.innerHTML;
button.disabled = true;
button.innerHTML = `
<div class="animate-spin rounded-full h-4 w-4 border-2 border-white border-t-transparent"></div>
<span class="hidden lg:inline">Schaltet...</span>
`;
try {
const response = await fetch(`${this.apiBaseUrl}/api/admin/printers/${printerId}/toggle`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': this.csrfToken
},
body: JSON.stringify({
reason: 'Admin-Panel Smart-Plug Toggle'
})
});
const data = await response.json();
if (response.ok && data.success) {
const action = data.action || 'umgeschaltet';
this.showNotification(`✅ Steckdose für "${printerName}" erfolgreich ${action}`, 'success');
// Button-Visual-Feedback
button.classList.remove('from-orange-500', 'to-red-500');
button.classList.add('from-green-500', 'to-green-600');
setTimeout(() => {
button.classList.remove('from-green-500', 'to-green-600');
button.classList.add('from-orange-500', 'to-red-500');
}, 2000);
// Live-Statistiken nach kurzer Verzögerung aktualisieren
setTimeout(() => {
this.loadLiveStats();
}, 1000);
} else {
const errorMsg = data.error || 'Unbekannter Fehler beim Schalten der Steckdose';
this.showNotification(`❌ Fehler: ${errorMsg}`, 'error');
console.error('Smart-Plug Toggle Fehler:', data);
}
} catch (error) {
console.error('Netzwerkfehler beim Smart-Plug Toggle:', error);
this.showNotification(`❌ Netzwerkfehler beim Schalten der Steckdose für "${printerName}"`, 'error');
} finally {
// Button-Zustand zurücksetzen
button.disabled = false;
button.innerHTML = originalContent;
}
}
// Job-Management
handleJobAction(action, jobId) {
console.log(`📋 Job-Aktion "${action}" für Job ${jobId}`);
this.showNotification(`Job-Aktion "${action}" wird ausgeführt...`, 'info');
}
// Error-Management
async checkSystemHealth() {
try {
const response = await fetch(`${this.apiBaseUrl}/api/admin/system/status`);
if (!response.ok) {
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) {
this.updateHealthDisplay(data);
this.updateErrorAlerts(data);
}
} catch (error) {
console.error('Fehler bei System-Health-Check:', error);
}
}
updateHealthDisplay(data) {
const statusIndicator = document.getElementById('db-status-indicator');
const statusText = document.getElementById('db-status-text');
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';
}
}
this.updateElement('last-migration', data.last_migration || 'Unbekannt');
this.updateElement('schema-integrity', data.schema_integrity || 'Prüfung');
this.updateElement('recent-errors-count', data.recent_errors_count || 0);
}
updateErrorAlerts(data) {
const alertContainer = document.getElementById('critical-errors-alert');
if (!alertContainer) return;
const allErrors = [...(data.critical_errors || []), ...(data.warnings || [])];
if (allErrors.length > 0) {
alertContainer.classList.remove('hidden');
} else {
alertContainer.classList.add('hidden');
}
}
async fixErrors() {
if (!confirm('🔧 Möchten Sie die automatische Fehlerkorrektur durchführen?')) return;
this.showNotification('🔄 Fehler werden automatisch behoben...', 'info');
// Debug: CSRF Token prüfen
if (!this.csrfToken) {
console.error('❌ CSRF Token fehlt! Versuche Token neu zu laden...');
this.csrfToken = this.extractCSRFToken();
if (!this.csrfToken) {
this.showNotification('❌ Sicherheitsfehler: CSRF Token nicht verfügbar', 'error');
return;
}
}
console.log('🔧 Starte automatische Fehlerkorrektur...');
console.log('🔒 CSRF Token für Request:', this.csrfToken ? this.csrfToken.substring(0, 10) + '...' : 'NICHT VERFÜGBAR');
try {
const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': this.csrfToken
}
};
console.log('📡 Sende Request an:', `${this.apiBaseUrl}/api/admin/fix-errors`);
console.log('📝 Request Headers:', requestOptions.headers);
const response = await fetch(`${this.apiBaseUrl}/api/admin/fix-errors`, requestOptions);
console.log('📡 Response Status:', response.status);
console.log('📡 Response Headers:', Object.fromEntries(response.headers.entries()));
if (!response.ok) {
const errorText = await response.text();
console.error('❌ Response Error:', errorText);
throw new Error(`HTTP ${response.status}: ${response.statusText} - ${errorText}`);
}
const data = await response.json();
console.log('✅ Response Data:', data);
if (data.success) {
this.showNotification('✅ Automatische Reparatur erfolgreich!', 'success');
setTimeout(() => this.checkSystemHealth(), 2000);
} else {
this.showNotification(`❌ Automatische Reparatur fehlgeschlagen: ${data.message || 'Unbekannter Fehler'}`, 'error');
}
} catch (error) {
console.error('❌ Fehler bei automatischer Reparatur:', error);
this.showNotification(`❌ Fehler bei der automatischen Reparatur: ${error.message}`, 'error');
}
}
dismissErrors() {
const alertContainer = document.getElementById('critical-errors-alert');
if (alertContainer) {
alertContainer.classList.add('hidden');
}
}
// Notification System
showNotification(message, type = 'info') {
let notification = document.getElementById('admin-notification');
if (!notification) {
notification = document.createElement('div');
notification.id = 'admin-notification';
notification.className = 'fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg max-w-sm transition-all duration-300';
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 transition-all duration-300 ${colors[type]}`;
notification.textContent = message;
notification.style.transform = 'translateX(0)';
// Auto-Hide nach 3 Sekunden
setTimeout(() => {
if (notification) {
notification.style.transform = 'translateX(100%)';
setTimeout(() => {
if (notification && notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, 300);
}
}, 3000);
}
/**
* Logs-Management Funktionen
*/
async loadLogs(level = null) {
const logsContainer = document.getElementById('logs-container');
if (!logsContainer) return;
// Loading-Indikator anzeigen
logsContainer.innerHTML = `
<div class="flex justify-center items-center py-12">
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 dark:border-blue-400"></div>
<span class="ml-3 text-slate-600 dark:text-slate-400">Logs werden geladen...</span>
</div>
`;
try {
const filter = level || document.getElementById('log-level-filter')?.value || 'all';
const url = `${this.apiBaseUrl}/api/admin/logs?level=${filter}&limit=100`;
const response = await fetch(url, {
headers: {
'X-CSRFToken': this.csrfToken
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
this.displayLogs(data.logs || []);
console.log('📋 Logs erfolgreich geladen');
} catch (error) {
console.error('Fehler beim Laden der Logs:', error);
logsContainer.innerHTML = `
<div class="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-xl p-6 text-center">
<svg class="w-12 h-12 text-red-500 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.464 0L4.35 16.5c-.77.833.192 2.5 1.732 2.5z"/>
</svg>
<h3 class="text-lg font-semibold text-red-800 dark:text-red-200 mb-2">Fehler beim Laden der Logs</h3>
<p class="text-red-600 dark:text-red-400 mb-4">${error.message}</p>
<button onclick="adminDashboard.loadLogs()" class="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors">
🔄 Erneut versuchen
</button>
</div>
`;
}
}
displayLogs(logs) {
const logsContainer = document.getElementById('logs-container');
if (!logsContainer) return;
if (!logs || logs.length === 0) {
logsContainer.innerHTML = `
<div class="text-center py-12">
<svg class="w-16 h-16 mx-auto text-slate-400 dark:text-slate-500 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
</svg>
<h3 class="text-lg font-medium text-slate-900 dark:text-white mb-2">Keine Logs gefunden</h3>
<p class="text-slate-500 dark:text-slate-400">Es sind keine Logs für die ausgewählten Kriterien vorhanden.</p>
</div>
`;
return;
}
const logsHtml = logs.map(log => {
const levelColors = {
'error': 'bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800 text-red-800 dark:text-red-200',
'warning': 'bg-yellow-50 dark:bg-yellow-900/20 border-yellow-200 dark:border-yellow-800 text-yellow-800 dark:text-yellow-200',
'info': 'bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800 text-blue-800 dark:text-blue-200',
'debug': 'bg-gray-50 dark:bg-gray-900/20 border-gray-200 dark:border-gray-800 text-gray-800 dark:text-gray-200',
'critical': 'bg-red-100 dark:bg-red-900/40 border-red-300 dark:border-red-700 text-red-900 dark:text-red-100'
};
const levelIcons = {
'error': '❌',
'warning': '⚠️',
'info': '',
'debug': '🔍',
'critical': '🚨'
};
const levelClass = levelColors[log.level] || levelColors['info'];
const levelIcon = levelIcons[log.level] || '';
return `
<div class="border rounded-xl p-4 transition-all duration-200 hover:shadow-md ${levelClass}">
<div class="flex items-start justify-between mb-2">
<div class="flex items-center space-x-2">
<span class="text-lg">${levelIcon}</span>
<span class="font-semibold text-sm uppercase tracking-wide">${log.level}</span>
<span class="text-xs opacity-75">${log.component || 'System'}</span>
</div>
<div class="text-xs opacity-75">
${this.formatLogTimestamp(log.timestamp)}
</div>
</div>
<div class="mb-2">
<p class="font-medium">${this.escapeHtml(log.message)}</p>
${log.details ? `<p class="text-sm opacity-75 mt-1">${this.escapeHtml(log.details)}</p>` : ''}
</div>
${log.user ? `
<div class="text-xs opacity-75">
<span class="font-medium">Benutzer:</span> ${this.escapeHtml(log.user)}
</div>
` : ''}
${log.ip_address ? `
<div class="text-xs opacity-75">
<span class="font-medium">IP:</span> ${this.escapeHtml(log.ip_address)}
</div>
` : ''}
${log.request_id ? `
<div class="text-xs opacity-75 mt-2 font-mono">
<span class="font-medium">Request-ID:</span> ${this.escapeHtml(log.request_id)}
</div>
` : ''}
</div>
`;
}).join('');
logsContainer.innerHTML = logsHtml;
}
formatLogTimestamp(timestamp) {
if (!timestamp) return 'Unbekannt';
try {
const date = new Date(timestamp);
return date.toLocaleString('de-DE', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
} catch (error) {
return timestamp;
}
}
escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
async exportLogs() {
try {
this.showNotification('📥 Logs werden exportiert...', 'info');
const filter = document.getElementById('log-level-filter')?.value || 'all';
const url = `${this.apiBaseUrl}/api/admin/logs/export`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': this.csrfToken
},
body: JSON.stringify({
level: filter,
format: 'csv'
})
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (data.success && data.content) {
// Download als Datei
const blob = new Blob([data.content], { type: data.content_type });
const downloadUrl = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = downloadUrl;
a.download = data.filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(downloadUrl);
this.showNotification('✅ Logs erfolgreich exportiert!', 'success');
} else {
throw new Error(data.error || 'Unbekannter Fehler beim Export');
}
} catch (error) {
console.error('Fehler beim Exportieren der Logs:', error);
this.showNotification('❌ Fehler beim Exportieren der Logs: ' + error.message, 'error');
}
}
// ===== BUTTON-FUNKTIONALITÄT-TEST =====
testButtons() {
console.log('🧪 Teste Button-Funktionalität...');
// Teste Fix-Errors Button
const fixBtn = document.querySelector('#fix-errors-btn');
if (fixBtn) {
console.log('✅ Fix-Errors Button gefunden:', fixBtn);
console.log('🔗 Event-Listener-Status:', fixBtn.dataset.listenerAttached);
// Manueller Test-Click
fixBtn.addEventListener('click', (e) => {
console.log('🖱️ Fix-Errors Button wurde geklickt (manueller Listener)');
e.preventDefault();
e.stopPropagation();
this.testFixErrors();
});
} else {
console.error('❌ Fix-Errors Button NICHT gefunden!');
}
// Teste View-Details Button
const viewBtn = document.querySelector('#view-error-details-btn');
if (viewBtn) {
console.log('✅ View-Details Button gefunden:', viewBtn);
console.log('🔗 Event-Listener-Status:', viewBtn.dataset.listenerAttached);
// Manueller Test-Click
viewBtn.addEventListener('click', (e) => {
console.log('🖱️ View-Details Button wurde geklickt (manueller Listener)');
e.preventDefault();
e.stopPropagation();
console.log('🔄 Weiterleitung zu Logs-Tab...');
window.location.href = '/admin-dashboard?tab=logs';
});
} else {
console.error('❌ View-Details Button NICHT gefunden!');
}
}
/**
* Live-Statistiken laden für Dashboard-Updates
*/
async loadLiveStats() {
try {
const response = await fetch(`${this.apiBaseUrl}/api/admin/live-stats`, {
headers: {
'X-CSRFToken': this.csrfToken
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (data.success) {
this.updateDashboardStats(data.stats);
console.log('📊 Live-Statistiken erfolgreich geladen');
}
} catch (error) {
console.error('Fehler beim Laden der Live-Statistiken:', error);
}
}
/**
* Dashboard-Statistiken in der UI aktualisieren
*/
updateDashboardStats(stats) {
if (!stats) return;
// Benutzer-Statistiken aktualisieren
this.updateElement('total-users-count', stats.total_users || 0);
this.updateElement('active-users-count', stats.active_users || 0);
// Drucker-Statistiken aktualisieren
this.updateElement('total-printers-count', stats.total_printers || 0);
this.updateElement('online-printers-count', stats.online_printers || 0);
// Job-Statistiken aktualisieren
this.updateElement('total-jobs-count', stats.total_jobs || 0);
this.updateElement('active-jobs-count', stats.active_jobs || 0);
this.updateElement('completed-jobs-count', stats.completed_jobs || 0);
// Gast-Anfragen aktualisieren
this.updateElement('pending-guests-count', stats.pending_guests || 0);
// Zeitstempel aktualisieren
const timestampEl = document.getElementById('stats-timestamp');
if (timestampEl) {
timestampEl.textContent = new Date().toLocaleTimeString('de-DE');
}
}
/**
* Hilfsfunktion zum sicheren Aktualisieren von DOM-Elementen
*/
updateElement(id, value) {
const element = document.getElementById(id);
if (element) {
element.textContent = value;
}
}
// Test-Version der Fix-Errors Funktion
async testFixErrors() {
console.log('🧪 TEST: Fix-Errors wird ausgeführt...');
// Token-Test
console.log('🔒 Aktueller CSRF Token:', this.csrfToken);
if (!this.csrfToken) {
console.error('❌ CSRF Token fehlt - versuche neu zu laden...');
this.csrfToken = this.extractCSRFToken();
console.log('🔒 Neu geladener Token:', this.csrfToken);
}
this.showNotification('🧪 TEST: Starte automatische Fehlerkorrektur...', 'info');
try {
const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': this.csrfToken,
'X-Requested-With': 'XMLHttpRequest'
}
};
console.log('📡 TEST Request an:', `${this.apiBaseUrl}/api/admin/fix-errors`);
console.log('📝 TEST Headers:', requestOptions.headers);
const response = await fetch(`${this.apiBaseUrl}/api/admin/fix-errors`, requestOptions);
console.log('📡 TEST Response Status:', response.status);
console.log('📡 TEST Response Headers:', Object.fromEntries(response.headers.entries()));
if (!response.ok) {
const errorText = await response.text();
console.error('❌ TEST Response Error:', errorText);
this.showNotification(`❌ TEST Fehler: ${response.status} - ${errorText}`, 'error');
return;
}
const data = await response.json();
console.log('✅ TEST Response Data:', data);
if (data.success) {
this.showNotification('✅ TEST: Automatische Reparatur erfolgreich!', 'success');
} else {
this.showNotification(`❌ TEST: Reparatur fehlgeschlagen - ${data.message}`, 'error');
}
} catch (error) {
console.error('❌ TEST Fehler:', error);
this.showNotification(`❌ TEST Netzwerk-Fehler: ${error.message}`, 'error');
}
}
}
// Sichere Initialisierung - nur einmal ausführen
let adminDashboardInstance = null;
document.addEventListener('DOMContentLoaded', function() {
// Verhindere doppelte Initialisierung
if (window.adminDashboard) {
console.log('⚠️ Admin Dashboard bereits initialisiert, überspringe...');
return;
}
console.log('🚀 Starte Mercedes-Benz MYP Admin Dashboard...');
// Dashboard erstellen
window.adminDashboard = new AdminDashboard();
// Überprüfe, ob wir auf dem Logs-Tab sind und lade Logs
setTimeout(() => {
const currentUrl = window.location.pathname;
const isLogsTab = currentUrl.includes('/admin/logs') ||
document.querySelector('[href*="logs"]')?.closest('.bg-gradient-to-r') ||
document.getElementById('logs-container');
if (isLogsTab) {
console.log('📋 Logs-Tab erkannt, lade Logs...');
window.adminDashboard.loadLogs();
}
}, 1000);
console.log('✅ Admin Dashboard Initialisierung abgeschlossen');
});
// Export für globalen Zugriff
window.AdminDashboard = AdminDashboard;