manage-your-printer/static/js/admin-unified.js
2025-06-04 10:03:22 +02:00

1345 lines
54 KiB
JavaScript
Raw Permalink 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;
}
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);
}
});
}
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() {
// Error-Alert Buttons
this.addEventListenerSafe('#fix-errors-btn', 'click', (e) => {
e.preventDefault();
e.stopPropagation();
this.fixErrors();
});
this.addEventListenerSafe('#dismiss-errors-btn', 'click', (e) => {
e.preventDefault();
e.stopPropagation();
this.dismissErrors();
});
this.addEventListenerSafe('#view-error-details-btn', 'click', (e) => {
e.preventDefault();
e.stopPropagation();
window.location.href = '/admin-dashboard?tab=logs';
});
// Logs-Funktionalität
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) => {
this.loadLogs();
});
}
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() {
await this.loadLiveStats();
await this.checkSystemHealth();
// Button-Test ausführen
setTimeout(() => {
this.testButtons();
}, 1000);
// Logs laden falls wir auf dem Logs-Tab sind
if (window.location.search.includes('tab=logs') || document.querySelector('.tabs [href*="logs"]')?.classList.contains('active')) {
await this.loadLogs();
}
// Prüfe auch ob der Logs-Tab durch die URL-Parameter aktiv ist
const urlParams = new URLSearchParams(window.location.search);
const activeTab = urlParams.get('tab');
if (activeTab === 'logs') {
await this.loadLogs();
}
// Oder prüfe ob das Logs-Container-Element sichtbar ist
const logsContainer = document.getElementById('logs-container');
if (logsContainer && logsContainer.offsetParent !== null) {
await this.loadLogs();
}
}
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 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;
}
}
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
showPrinterModal() {
console.log('🖨️ Drucker-Modal wird angezeigt');
this.showNotification('Drucker-Funktionen werden geladen...', 'info');
}
managePrinter(printerId) {
console.log(`🔧 Drucker ${printerId} wird verwaltet`);
this.showNotification(`Drucker ${printerId} wird verwaltet...`, 'info');
}
showPrinterSettings(printerId) {
console.log(`⚙️ Drucker-Einstellungen ${printerId} werden angezeigt`);
this.showNotification(`Drucker-Einstellungen werden geladen...`, 'info');
}
// 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('/api/admin/system-health');
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:', '/api/admin/fix-errors');
console.log('📝 Request Headers:', requestOptions.headers);
const response = await fetch('/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?level=${filter}`;
const response = await fetch(url, {
headers: {
'X-CSRFToken': this.csrfToken
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
// Download als Datei
const blob = await response.blob();
const downloadUrl = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = downloadUrl;
a.download = `system-logs-${new Date().toISOString().split('T')[0]}.csv`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(downloadUrl);
this.showNotification('✅ Logs erfolgreich exportiert!', 'success');
} 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!');
}
}
// 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:', '/api/admin/fix-errors');
console.log('📝 TEST Headers:', requestOptions.headers);
const response = await fetch('/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() {
if (!adminDashboardInstance) {
adminDashboardInstance = new AdminDashboard();
window.AdminDashboard = adminDashboardInstance;
console.log('🎯 Admin Dashboard erfolgreich initialisiert (unified)');
}
});
// Export für globalen Zugriff
window.AdminDashboard = AdminDashboard;