1580 lines
58 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 JavaScript
* Moderne Administration mit Echtzeit-Updates und eleganter UI
*/
// Globale Variablen
let statsUpdateInterval;
let systemStatusInterval;
let realTimeChart;
let csrfToken;
// Dynamische API-Base-URL-Erkennung
function detectApiBaseUrl() {
const currentHost = window.location.hostname;
const currentProtocol = window.location.protocol;
const currentPort = window.location.port;
console.log('🔍 Admin API URL Detection:', { currentHost, currentProtocol, currentPort });
// Wenn wir bereits auf dem richtigen Port sind, verwende relative URLs
if (currentPort === '443' || !currentPort) {
console.log('✅ Verwende relative URLs (HTTPS Port 443)');
return '';
}
// Für alle anderen Fälle, verwende HTTPS auf Port 443
const fallbackUrl = `https://${currentHost}`;
console.log('🔄 Admin Fallback zu HTTPS:443:', fallbackUrl);
return fallbackUrl;
}
// Globale API-Base-URL
const API_BASE_URL = detectApiBaseUrl();
console.log('🔗 Admin API Base URL erkannt:', API_BASE_URL);
// Initialisierung beim Laden der Seite
document.addEventListener('DOMContentLoaded', function() {
// CSRF Token für AJAX-Anfragen
csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
// Basis-Initialisierung
initProgressBars();
initConfirmDialogs();
initFlashMessages();
// Erweiterte Admin-Funktionalitäten
initAdminButtons();
initRealTimeUpdates();
initSystemMonitoring();
initModernAnimations();
initSearchAndFilters();
initDataExport();
// Auto-Refresh für Statistiken
startStatsUpdate();
console.log('🚀 Mercedes-Benz MYP Admin Dashboard loaded successfully');
});
/**
* Initialisiert alle Admin-Button-Funktionalitäten
*/
function initAdminButtons() {
// Benutzer-Management Buttons
initUserManagement();
// Drucker-Management Buttons
initPrinterManagement();
// System-Management Buttons
initSystemManagement();
// Job-Management Buttons
initJobManagement();
}
/**
* Benutzer-Management Funktionalitäten
*/
function initUserManagement() {
// Neuer Benutzer Button
const addUserBtn = document.getElementById('add-user-btn');
if (addUserBtn) {
addUserBtn.addEventListener('click', () => showUserModal());
}
// Benutzer bearbeiten Buttons
document.querySelectorAll('.edit-user-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const userId = e.target.closest('button').dataset.userId;
editUser(userId);
});
});
// Benutzer löschen Buttons
document.querySelectorAll('.delete-user-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const userId = e.target.closest('button').dataset.userId;
const userName = e.target.closest('button').dataset.userName;
deleteUser(userId, userName);
});
});
}
/**
* Drucker-Management Funktionalitäten
*/
function initPrinterManagement() {
// Drucker hinzufügen Button
const addPrinterBtn = document.getElementById('add-printer-btn');
if (addPrinterBtn) {
addPrinterBtn.addEventListener('click', () => showPrinterModal());
}
// Drucker verwalten Buttons
document.querySelectorAll('.manage-printer-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const printerId = e.target.closest('button').dataset.printerId;
managePrinter(printerId);
});
});
// Drucker-Einstellungen Buttons
document.querySelectorAll('.settings-printer-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const printerId = e.target.closest('button').dataset.printerId;
showPrinterSettings(printerId);
});
});
}
/**
* System-Management Funktionalitäten
*/
function initSystemManagement() {
// System Status Buttons
const systemStatusBtn = document.querySelector('button[onclick*="System Status"]');
if (systemStatusBtn) {
systemStatusBtn.onclick = null;
systemStatusBtn.addEventListener('click', () => showSystemStatus());
}
const analyticsBtn = document.querySelector('button[onclick*="Analytics"]');
if (analyticsBtn) {
analyticsBtn.onclick = null;
analyticsBtn.addEventListener('click', () => showAnalytics());
}
// Cache leeren
const clearCacheBtn = document.getElementById('clear-cache-btn');
if (clearCacheBtn) {
clearCacheBtn.addEventListener('click', () => clearSystemCache());
}
// Datenbank optimieren
const optimizeDbBtn = document.getElementById('optimize-db-btn');
if (optimizeDbBtn) {
optimizeDbBtn.addEventListener('click', () => optimizeDatabase());
}
// Backup erstellen
const createBackupBtn = document.getElementById('create-backup-btn');
if (createBackupBtn) {
createBackupBtn.addEventListener('click', () => createSystemBackup());
}
// Einstellungen bearbeiten
const editSettingsBtn = document.getElementById('edit-settings-btn');
if (editSettingsBtn) {
editSettingsBtn.addEventListener('click', () => showSystemSettings());
}
// Drucker aktualisieren
const updatePrintersBtn = document.getElementById('update-printers-btn');
if (updatePrintersBtn) {
updatePrintersBtn.addEventListener('click', () => updateAllPrinters());
}
// System neustarten
const restartSystemBtn = document.getElementById('restart-system-btn');
if (restartSystemBtn) {
restartSystemBtn.addEventListener('click', () => restartSystem());
}
// Drucker-Initialisierung erzwingen
const forceInitPrintersBtn = document.getElementById('force-init-printers-btn');
if (forceInitPrintersBtn) {
forceInitPrintersBtn.addEventListener('click', () => forceInitializePrinters());
}
}
/**
* Job-Management Funktionalitäten
*/
function initJobManagement() {
// Job-Aktionen
document.querySelectorAll('.job-action-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const action = e.target.closest('button').dataset.action;
const jobId = e.target.closest('button').dataset.jobId;
handleJobAction(action, jobId);
});
});
}
/**
* Echtzeit-Updates für Dashboard
*/
function initRealTimeUpdates() {
// Statistiken alle 30 Sekunden aktualisieren
statsUpdateInterval = setInterval(() => {
updateDashboardStats();
}, 30000);
// System-Status alle 10 Sekunden aktualisieren
systemStatusInterval = setInterval(() => {
updateSystemStatus();
}, 10000);
}
/**
* System-Monitoring initialisieren
*/
function initSystemMonitoring() {
// CPU/RAM Monitoring in Echtzeit
if (document.querySelector('.server-status')) {
monitorSystemResources();
}
}
/**
* Moderne Animationen initialisieren
*/
function initModernAnimations() {
// Animierte Counters für Statistiken
animateCounters();
// Progress Bars mit Animation
animateProgressBars();
// Hover-Effekte
addHoverEffects();
}
/**
* Such- und Filter-Funktionalitäten
*/
function initSearchAndFilters() {
// Benutzer-Suche
const userSearch = document.querySelector('#user-search');
if (userSearch) {
userSearch.addEventListener('input', (e) => {
filterUsers(e.target.value);
});
}
// Job-Filter
const jobFilter = document.querySelector('select[onchange*="filter"]');
if (jobFilter) {
jobFilter.onchange = null;
jobFilter.addEventListener('change', (e) => {
filterJobs(e.target.value);
});
}
}
/**
* Daten-Export Funktionalitäten
*/
function initDataExport() {
// Export-Buttons
document.querySelectorAll('button[onclick*="export"]').forEach(btn => {
btn.onclick = null;
btn.addEventListener('click', (e) => {
const exportType = btn.textContent.includes('Export') ? 'csv' : 'json';
exportData(exportType);
});
});
}
/**
* Benutzer Modal anzeigen
*/
function showUserModal(userId = null) {
const modal = createModal('Benutzer ' + (userId ? 'bearbeiten' : 'hinzufügen'), `
<form id="user-form" class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">E-Mail</label>
<input type="email" name="email" required
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg
focus:ring-2 focus:ring-blue-500 focus:border-transparent
dark:bg-gray-700 dark:text-white">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Name</label>
<input type="text" name="name"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg
focus:ring-2 focus:ring-blue-500 focus:border-transparent
dark:bg-gray-700 dark:text-white">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Passwort</label>
<input type="password" name="password" ${userId ? '' : 'required'}
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg
focus:ring-2 focus:ring-blue-500 focus:border-transparent
dark:bg-gray-700 dark:text-white">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Rolle</label>
<select name="role"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg
focus:ring-2 focus:ring-blue-500 focus:border-transparent
dark:bg-gray-700 dark:text-white">
<option value="user">Benutzer</option>
<option value="admin">Administrator</option>
</select>
</div>
<div class="flex justify-end space-x-3 mt-6">
<button type="button" onclick="closeModal()"
class="px-4 py-2 bg-gray-300 dark:bg-gray-600 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-400 dark:hover:bg-gray-500 transition-colors">
Abbrechen
</button>
<button type="submit"
class="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors">
${userId ? 'Aktualisieren' : 'Erstellen'}
</button>
</div>
</form>
`);
// Form-Handler mit preventDefault
const form = document.getElementById('user-form');
form.addEventListener('submit', (e) => {
e.preventDefault(); // Verhindert automatische Weiterleitung
const formData = new FormData(e.target);
if (userId) {
updateUser(userId, formData);
} else {
createUser(formData);
}
});
}
/**
* System Status anzeigen
*/
function showSystemStatus() {
showLoadingOverlay();
const url = `${API_BASE_URL}/api/admin/system/status`;
fetch(url)
.then(response => response.json())
.then(data => {
hideLoadingOverlay();
const modal = createModal('🚀 System Status - Live Monitoring', `
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="bg-gradient-to-br from-blue-50 to-indigo-50 dark:from-blue-900/20 dark:to-indigo-900/20 rounded-xl p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">Server Resources</h3>
<div class="space-y-3">
<div class="flex justify-between items-center">
<span class="text-gray-600 dark:text-gray-400">CPU:</span>
<div class="flex items-center space-x-2">
<div class="w-24 bg-gray-200 dark:bg-gray-700 rounded-full h-2">
<div class="bg-gradient-to-r from-green-400 to-green-600 h-2 rounded-full transition-all duration-500"
style="width: ${data.cpu_usage || 0}%"></div>
</div>
<span class="text-sm font-medium text-gray-900 dark:text-white">${data.cpu_usage || 0}%</span>
</div>
</div>
<div class="flex justify-between items-center">
<span class="text-gray-600 dark:text-gray-400">RAM:</span>
<div class="flex items-center space-x-2">
<div class="w-24 bg-gray-200 dark:bg-gray-700 rounded-full h-2">
<div class="bg-gradient-to-r from-blue-400 to-blue-600 h-2 rounded-full transition-all duration-500"
style="width: ${data.memory_usage || 0}%"></div>
</div>
<span class="text-sm font-medium text-gray-900 dark:text-white">${data.memory_usage || 0}%</span>
</div>
</div>
<div class="flex justify-between items-center">
<span class="text-gray-600 dark:text-gray-400">Uptime:</span>
<span class="text-sm font-medium text-gray-900 dark:text-white">${data.uptime || 'Unbekannt'}</span>
</div>
</div>
</div>
<div class="bg-gradient-to-br from-green-50 to-emerald-50 dark:from-green-900/20 dark:to-emerald-900/20 rounded-xl p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">Services</h3>
<div class="space-y-3">
<div class="flex justify-between items-center">
<span class="text-gray-600 dark:text-gray-400">Database:</span>
<span class="inline-flex items-center px-2 py-1 text-xs font-medium rounded-full
${data.database_status === 'connected' ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200' : 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'}">
<span class="w-2 h-2 mr-1 rounded-full ${data.database_status === 'connected' ? 'bg-green-400' : 'bg-red-400'}"></span>
${data.database_status === 'connected' ? 'Connected' : 'Disconnected'}
</span>
</div>
<div class="flex justify-between items-center">
<span class="text-gray-600 dark:text-gray-400">Scheduler:</span>
<span class="inline-flex items-center px-2 py-1 text-xs font-medium rounded-full
${data.scheduler_running ? 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200' : 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200'}">
<span class="w-2 h-2 mr-1 rounded-full ${data.scheduler_running ? 'bg-blue-400' : 'bg-yellow-400'}"></span>
${data.scheduler_running ? 'Running' : 'Stopped'}
</span>
</div>
</div>
</div>
</div>
<div class="mt-6 text-center">
<button onclick="refreshSystemStatus()"
class="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors">
🔄 Aktualisieren
</button>
</div>
`);
})
.catch(error => {
hideLoadingOverlay();
showNotification('Fehler beim Laden des System Status', 'error');
console.error('System status error:', error);
});
}
/**
* Analytics anzeigen
*/
function showAnalytics() {
const modal = createModal('📊 Live Analytics Dashboard', `
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
<div class="bg-gradient-to-br from-purple-50 to-pink-50 dark:from-purple-900/20 dark:to-pink-900/20 rounded-xl p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">Drucker Auslastung</h3>
<canvas id="printerUsageChart" width="400" height="200"></canvas>
</div>
<div class="bg-gradient-to-br from-orange-50 to-red-50 dark:from-orange-900/20 dark:to-red-900/20 rounded-xl p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">Erfolgsrate</h3>
<canvas id="successRateChart" width="400" height="200"></canvas>
</div>
</div>
<div class="bg-gradient-to-r from-blue-50 to-cyan-50 dark:from-blue-900/20 dark:to-cyan-900/20 rounded-xl p-6">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">Echtzeit Monitoring</h3>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
<div class="text-center">
<div class="text-2xl font-bold text-blue-600 dark:text-blue-400" id="live-jobs">-</div>
<div class="text-sm text-gray-600 dark:text-gray-400">Aktive Jobs</div>
</div>
<div class="text-center">
<div class="text-2xl font-bold text-green-600 dark:text-green-400" id="live-printers">-</div>
<div class="text-sm text-gray-600 dark:text-gray-400">Online Drucker</div>
</div>
<div class="text-center">
<div class="text-2xl font-bold text-orange-600 dark:text-orange-400" id="live-queue">-</div>
<div class="text-sm text-gray-600 dark:text-gray-400">Warteschlange</div>
</div>
<div class="text-center">
<div class="text-2xl font-bold text-purple-600 dark:text-purple-400" id="live-success">-</div>
<div class="text-sm text-gray-600 dark:text-gray-400">Erfolg %</div>
</div>
</div>
</div>
`);
// Analytics-Daten laden und Charts initialisieren
loadAnalyticsData();
startLiveAnalytics();
}
/**
* System Cache leeren
*/
async function clearSystemCache() {
if (!confirm('🗑️ Möchten Sie wirklich den System-Cache leeren?')) return;
showLoadingOverlay();
try {
const url = `${API_BASE_URL}/api/admin/cache/clear`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
}
});
const data = await response.json();
hideLoadingOverlay();
if (data.success) {
showNotification('✅ Cache erfolgreich geleert!', 'success');
// Seite nach 2 Sekunden neu laden
setTimeout(() => window.location.reload(), 2000);
} else {
showNotification('❌ Fehler beim Leeren des Cache: ' + data.message, 'error');
}
} catch (error) {
hideLoadingOverlay();
showNotification('❌ Netzwerkfehler beim Leeren des Cache', 'error');
console.error('Cache clear error:', error);
}
}
/**
* Datenbank optimieren
*/
async function optimizeDatabase() {
if (!confirm('🔧 Möchten Sie die Datenbank optimieren? Dies kann einige Minuten dauern.')) return;
showLoadingOverlay();
try {
const url = `${API_BASE_URL}/api/admin/database/optimize`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
}
});
const data = await response.json();
hideLoadingOverlay();
if (data.success) {
showNotification('✅ Datenbank erfolgreich optimiert!', 'success');
} else {
showNotification('❌ Fehler bei der Datenbank-Optimierung: ' + data.message, 'error');
}
} catch (error) {
hideLoadingOverlay();
showNotification('❌ Netzwerkfehler bei der Datenbank-Optimierung', 'error');
console.error('Database optimization error:', error);
}
}
/**
* System Backup erstellen
*/
async function createSystemBackup() {
if (!confirm('💾 Möchten Sie ein System-Backup erstellen?')) return;
showLoadingOverlay();
try {
const url = `${API_BASE_URL}/api/admin/backup/create`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
}
});
if (response.ok) {
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `myp-backup-${new Date().toISOString().split('T')[0]}.zip`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
hideLoadingOverlay();
showNotification('✅ Backup erfolgreich erstellt und heruntergeladen!', 'success');
} else {
throw new Error('Backup failed');
}
} catch (error) {
hideLoadingOverlay();
showNotification('❌ Fehler beim Erstellen des Backups', 'error');
console.error('Backup error:', error);
}
}
/**
* Utility-Funktionen für UI
*/
function createModal(title, content) {
const modal = document.createElement('div');
modal.className = 'fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center p-4';
modal.innerHTML = `
<div class="bg-white dark:bg-slate-800 rounded-2xl shadow-2xl max-w-2xl w-full max-h-[90vh] overflow-y-auto">
<div class="p-6 border-b border-gray-200 dark:border-gray-700">
<div class="flex justify-between items-center">
<h2 class="text-xl font-bold text-gray-900 dark:text-white">${title}</h2>
<button onclick="closeModal()" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
<svg class="w-6 h-6" 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>
<div class="p-6">
${content}
</div>
</div>
`;
document.body.appendChild(modal);
// ESC-Key zum Schließen
modal.addEventListener('keydown', (e) => {
if (e.key === 'Escape') closeModal();
});
return modal;
}
function closeModal() {
const modal = document.querySelector('.fixed.inset-0.bg-black\\/50');
if (modal) {
modal.remove();
}
}
function showLoadingOverlay() {
const overlay = document.getElementById('loading-overlay');
if (overlay) {
overlay.classList.remove('hidden');
}
}
function hideLoadingOverlay() {
const overlay = document.getElementById('loading-overlay');
if (overlay) {
overlay.classList.add('hidden');
}
}
function showNotification(message, type = 'info') {
const notification = document.createElement('div');
notification.className = `fixed top-4 right-4 px-6 py-4 rounded-xl shadow-2xl z-50 transform transition-all duration-500 translate-x-full ${
type === 'success' ? 'bg-green-500 text-white' :
type === 'error' ? 'bg-red-500 text-white' :
type === 'warning' ? 'bg-yellow-500 text-black' :
'bg-blue-500 text-white'
}`;
notification.innerHTML = `
<div class="flex items-center space-x-3">
<span class="text-lg">
${type === 'success' ? '✅' :
type === 'error' ? '❌' :
type === 'warning' ? '⚠️' : ''}
</span>
<span class="font-medium">${message}</span>
</div>
`;
document.body.appendChild(notification);
// Animation einblenden
setTimeout(() => {
notification.classList.remove('translate-x-full');
}, 100);
// Nach 5 Sekunden entfernen
setTimeout(() => {
notification.classList.add('translate-x-full');
setTimeout(() => notification.remove(), 500);
}, 5000);
}
/**
* Original Basis-Funktionen (erweitert)
*/
function initProgressBars() {
const progressBars = document.querySelectorAll('.progress-bar-fill, [style*="width:"]');
progressBars.forEach((bar, index) => {
const width = bar.style.width || bar.getAttribute('data-width') || '0%';
bar.style.width = '0%';
// Animierte Progress Bars
setTimeout(() => {
bar.style.transition = 'width 1.5s cubic-bezier(0.4, 0, 0.2, 1)';
bar.style.width = width;
}, index * 200);
});
}
function initConfirmDialogs() {
// Erweiterte Confirm-Dialoge mit modernem Design
document.querySelectorAll('[data-confirm]').forEach(element => {
element.addEventListener('click', (e) => {
e.preventDefault();
const message = element.dataset.confirm;
if (confirm(message)) {
// Ursprüngliche Aktion ausführen
if (element.tagName === 'FORM') {
element.submit();
} else if (element.href) {
window.location.href = element.href;
}
}
});
});
}
function initFlashMessages() {
const flashMessages = document.querySelectorAll('.flash-messages .alert, [class*="alert"]');
flashMessages.forEach((message, index) => {
// Einblende-Animation
message.style.opacity = '0';
message.style.transform = 'translateY(-20px)';
setTimeout(() => {
message.style.transition = 'all 0.5s ease';
message.style.opacity = '1';
message.style.transform = 'translateY(0)';
}, index * 100);
// Auto-hide nach 7 Sekunden
setTimeout(() => {
message.style.transition = 'all 0.5s ease';
message.style.opacity = '0';
message.style.transform = 'translateY(-20px)';
setTimeout(() => message.remove(), 500);
}, 7000);
});
}
/**
* Dashboard-Updates
*/
function startStatsUpdate() {
updateDashboardStats();
setInterval(updateDashboardStats, 30000); // Alle 30 Sekunden
}
async function updateDashboardStats() {
try {
const url = `${API_BASE_URL}/api/admin/stats/live`;
const response = await fetch(url);
const data = await response.json();
// Stats animiert aktualisieren
updateAnimatedCounter('total-users', data.total_users);
updateAnimatedCounter('total-printers', data.total_printers);
updateAnimatedCounter('active-jobs', data.active_jobs);
updateAnimatedCounter('success-rate', data.success_rate);
} catch (error) {
console.error('Stats update error:', error);
}
}
function updateAnimatedCounter(elementId, newValue) {
const element = document.getElementById(elementId) ||
document.querySelector(`[data-stat="${elementId}"]`);
if (!element) return;
const currentValue = parseInt(element.textContent) || 0;
const difference = newValue - currentValue;
const steps = 20;
const stepValue = difference / steps;
let step = 0;
const interval = setInterval(() => {
step++;
const value = Math.round(currentValue + (stepValue * step));
element.textContent = value + (elementId === 'success-rate' ? '%' : '');
if (step >= steps) {
clearInterval(interval);
element.textContent = newValue + (elementId === 'success-rate' ? '%' : '');
}
}, 50);
}
// Global verfügbare Funktionen für Modal-Callbacks
window.closeModal = closeModal;
window.refreshSystemStatus = () => {
closeModal();
showSystemStatus();
};
/**
* Animierte Counters für Statistiken
*/
function animateCounters() {
const counters = document.querySelectorAll('.counter, .stat-number, [data-counter]');
counters.forEach(counter => {
const target = parseInt(counter.textContent.replace(/[^\d]/g, '')) || 0;
const duration = 2000; // 2 Sekunden
const increment = target / (duration / 16); // 60 FPS
let current = 0;
const updateCounter = () => {
if (current < target) {
current += increment;
counter.textContent = Math.floor(current).toLocaleString('de-DE');
requestAnimationFrame(updateCounter);
} else {
counter.textContent = target.toLocaleString('de-DE');
}
};
// Intersection Observer für bessere Performance
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
updateCounter();
observer.unobserve(entry.target);
}
});
});
observer.observe(counter);
});
}
/**
* Progress Bars mit Animation
*/
function animateProgressBars() {
const progressBars = document.querySelectorAll('.progress-bar, [data-progress]');
progressBars.forEach(bar => {
const targetWidth = bar.dataset.progress || bar.style.width || '0%';
const targetValue = parseInt(targetWidth);
// Reset width
bar.style.width = '0%';
// Animate to target
setTimeout(() => {
bar.style.transition = 'width 1.5s ease-out';
bar.style.width = targetWidth;
}, 100);
});
}
/**
* Hover-Effekte hinzufügen
*/
function addHoverEffects() {
// Card hover effects
const cards = document.querySelectorAll('.card, .stat-card, .dashboard-card');
cards.forEach(card => {
card.addEventListener('mouseenter', () => {
card.style.transform = 'translateY(-2px)';
card.style.boxShadow = '0 10px 25px rgba(0,0,0,0.1)';
});
card.addEventListener('mouseleave', () => {
card.style.transform = 'translateY(0)';
card.style.boxShadow = '';
});
});
// Button hover effects
const buttons = document.querySelectorAll('.btn, button:not(.no-hover)');
buttons.forEach(button => {
button.addEventListener('mouseenter', () => {
button.style.transform = 'scale(1.02)';
});
button.addEventListener('mouseleave', () => {
button.style.transform = 'scale(1)';
});
});
}
/**
* Drucker Modal anzeigen
*/
function showPrinterModal(printerId = null) {
const modal = createModal('Drucker ' + (printerId ? 'bearbeiten' : 'hinzufügen'), `
<form id="printer-form" class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Name</label>
<input type="text" name="name" required
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg
focus:ring-2 focus:ring-blue-500 focus:border-transparent
dark:bg-gray-700 dark:text-white"
placeholder="z.B. Prusa i3 MK3S+">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">IP-Adresse</label>
<input type="text" name="ip_address" required
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg
focus:ring-2 focus:ring-blue-500 focus:border-transparent
dark:bg-gray-700 dark:text-white"
placeholder="192.168.1.100"
pattern="^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Standort</label>
<input type="text" name="location"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg
focus:ring-2 focus:ring-blue-500 focus:border-transparent
dark:bg-gray-700 dark:text-white"
placeholder="z.B. Werkstatt, Büro">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Beschreibung</label>
<textarea name="description" rows="3"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg
focus:ring-2 focus:ring-blue-500 focus:border-transparent
dark:bg-gray-700 dark:text-white"
placeholder="Zusätzliche Informationen zum Drucker"></textarea>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Typ</label>
<select name="printer_type"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg
focus:ring-2 focus:ring-blue-500 focus:border-transparent
dark:bg-gray-700 dark:text-white">
<option value="FDM">FDM (Fused Deposition Modeling)</option>
<option value="SLA">SLA (Stereolithography)</option>
<option value="SLS">SLS (Selective Laser Sintering)</option>
<option value="Other">Sonstiges</option>
</select>
</div>
<div class="flex items-center">
<input type="checkbox" name="is_active" id="printer-active" checked
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
<label for="printer-active" class="ml-2 block text-sm text-gray-700 dark:text-gray-300">
Drucker ist aktiv
</label>
</div>
<div class="flex justify-end space-x-3 mt-6">
<button type="button" onclick="closeModal()"
class="px-4 py-2 bg-gray-300 dark:bg-gray-600 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-400 dark:hover:bg-gray-500 transition-colors">
Abbrechen
</button>
<button type="submit"
class="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors">
${printerId ? 'Aktualisieren' : 'Hinzufügen'}
</button>
</div>
</form>
`);
// Form-Handler mit preventDefault
const form = document.getElementById('printer-form');
form.addEventListener('submit', (e) => {
e.preventDefault(); // Verhindert automatische Weiterleitung
const formData = new FormData(e.target);
if (printerId) {
updatePrinter(printerId, formData);
} else {
createPrinter(formData);
}
});
// Wenn Drucker bearbeitet wird, Daten laden
if (printerId) {
loadPrinterData(printerId);
}
}
/**
* Drucker erstellen
*/
async function createPrinter(formData) {
try {
showLoadingOverlay();
const url = `${API_BASE_URL}/api/admin/printers/create`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCSRFToken()
},
body: JSON.stringify({
name: formData.get('name'),
ip_address: formData.get('ip_address'),
location: formData.get('location'),
description: formData.get('description'),
printer_type: formData.get('printer_type'),
is_active: formData.get('is_active') === 'on'
})
});
const result = await response.json();
if (response.ok) {
showNotification('Drucker erfolgreich hinzugefügt', 'success');
closeModal();
location.reload(); // Seite neu laden um neue Daten anzuzeigen
} else {
showNotification(result.error || 'Fehler beim Hinzufügen des Druckers', 'error');
}
} catch (error) {
console.error('Error creating printer:', error);
showNotification('Fehler beim Hinzufügen des Druckers', 'error');
} finally {
hideLoadingOverlay();
}
}
/**
* Fehlende Admin-Funktionen
*/
// System-Einstellungen anzeigen
function showSystemSettings() {
const modal = createModal('⚙️ System-Einstellungen', `
<form id="settings-form" class="space-y-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">Server-Konfiguration</h3>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Host</label>
<input type="text" name="host" value="0.0.0.0"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg
focus:ring-2 focus:ring-blue-500 focus:border-transparent
dark:bg-gray-700 dark:text-white">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Port</label>
<input type="number" name="port" value="443" min="1" max="65535"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg
focus:ring-2 focus:ring-blue-500 focus:border-transparent
dark:bg-gray-700 dark:text-white">
</div>
<div class="flex items-center">
<input type="checkbox" name="ssl_enabled" id="ssl-enabled" checked
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
<label for="ssl-enabled" class="ml-2 block text-sm text-gray-700 dark:text-gray-300">
SSL aktiviert
</label>
</div>
</div>
</div>
<div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">Scheduler-Einstellungen</h3>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Intervall (Sekunden)</label>
<input type="number" name="scheduler_interval" value="60" min="10" max="3600"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg
focus:ring-2 focus:ring-blue-500 focus:border-transparent
dark:bg-gray-700 dark:text-white">
</div>
<div class="flex items-center">
<input type="checkbox" name="scheduler_enabled" id="scheduler-enabled" checked
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
<label for="scheduler-enabled" class="ml-2 block text-sm text-gray-700 dark:text-gray-300">
Scheduler aktiviert
</label>
</div>
</div>
</div>
</div>
<div class="flex justify-end space-x-3 mt-6">
<button type="button" onclick="closeModal()"
class="px-4 py-2 bg-gray-300 dark:bg-gray-600 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-400 dark:hover:bg-gray-500 transition-colors">
Abbrechen
</button>
<button type="submit"
class="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors">
Einstellungen speichern
</button>
</div>
</form>
`);
// Form-Handler
const form = document.getElementById('settings-form');
form.addEventListener('submit', async (e) => {
e.preventDefault();
await saveSystemSettings(new FormData(e.target));
});
}
// System-Einstellungen speichern
async function saveSystemSettings(formData) {
try {
showLoadingOverlay();
const settings = {
server: {
host: formData.get('host'),
port: parseInt(formData.get('port')),
ssl_enabled: formData.get('ssl_enabled') === 'on'
},
scheduler: {
interval_seconds: parseInt(formData.get('scheduler_interval')),
enabled: formData.get('scheduler_enabled') === 'on'
}
};
const url = `${API_BASE_URL}/api/admin/settings`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCSRFToken()
},
body: JSON.stringify(settings)
});
const result = await response.json();
if (result.success) {
showNotification('✅ Einstellungen erfolgreich gespeichert!', 'success');
closeModal();
} else {
showNotification('❌ Fehler beim Speichern: ' + result.message, 'error');
}
} catch (error) {
console.error('Settings save error:', error);
showNotification('❌ Fehler beim Speichern der Einstellungen', 'error');
} finally {
hideLoadingOverlay();
}
}
// Alle Drucker aktualisieren
async function updateAllPrinters() {
if (!confirm('🔄 Möchten Sie den Status aller Drucker aktualisieren?')) return;
showLoadingOverlay();
try {
const url = `${API_BASE_URL}/api/admin/printers/update-all`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCSRFToken()
}
});
const result = await response.json();
if (result.success) {
showNotification(`${result.message}`, 'success');
// Seite nach 2 Sekunden neu laden
setTimeout(() => location.reload(), 2000);
} else {
showNotification('❌ Fehler beim Aktualisieren: ' + result.message, 'error');
}
} catch (error) {
console.error('Printer update error:', error);
showNotification('❌ Fehler beim Aktualisieren der Drucker', 'error');
} finally {
hideLoadingOverlay();
}
}
/**
* System neustarten
*/
async function restartSystem() {
if (!confirm('🔄 Möchten Sie das System wirklich neustarten?\n\nDies wird alle aktiven Verbindungen trennen.')) return;
showLoadingOverlay();
try {
const url = `${API_BASE_URL}/api/admin/system/restart`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
}
});
const data = await response.json();
hideLoadingOverlay();
if (data.success) {
showNotification('🔄 System wird neugestartet...', 'info');
// Nach 5 Sekunden zur Login-Seite weiterleiten
setTimeout(() => {
window.location.href = '/auth/login';
}, 5000);
} else {
showNotification('❌ Fehler beim Neustart: ' + data.message, 'error');
}
} catch (error) {
hideLoadingOverlay();
showNotification('❌ Netzwerkfehler beim Neustart', 'error');
console.error('System restart error:', error);
}
}
/**
* Robuste Drucker-Initialisierung erzwingen
*/
async function forceInitializePrinters() {
if (!confirm('🔄 Möchten Sie eine robuste Drucker-Initialisierung starten?\n\nDies überprüft alle Drucker um jeden Preis und markiert sie als online/offline.')) return;
showLoadingOverlay();
try {
const url = `${API_BASE_URL}/api/admin/printers/force-initialize`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
}
});
const data = await response.json();
hideLoadingOverlay();
if (data.success) {
showNotification('🚀 Drucker-Initialisierung gestartet!', 'success');
showNotification(' ' + data.info, 'info');
// Nach 3 Sekunden die Drucker-Statistiken aktualisieren
setTimeout(() => {
updateDashboardStats();
}, 3000);
// Nach 10 Sekunden eine weitere Aktualisierung für vollständige Ergebnisse
setTimeout(() => {
updateDashboardStats();
showNotification('✅ Drucker-Status wurde aktualisiert', 'success');
}, 10000);
} else {
showNotification('❌ Fehler bei der Drucker-Initialisierung: ' + data.message, 'error');
}
} catch (error) {
hideLoadingOverlay();
showNotification('❌ Netzwerkfehler bei der Drucker-Initialisierung', 'error');
console.error('Force printer initialization error:', error);
}
}
// Drucker verwalten
function managePrinter(printerId) {
window.location.href = `/admin/printers/${printerId}/manage`;
}
// Drucker-Einstellungen anzeigen
function showPrinterSettings(printerId) {
window.location.href = `/admin/printers/${printerId}/settings`;
}
// Benutzer bearbeiten
function editUser(userId) {
window.location.href = `/admin/users/${userId}/edit`;
}
// Benutzer löschen
async function deleteUser(userId, userName) {
if (!confirm(`⚠️ Möchten Sie den Benutzer "${userName}" wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.`)) return;
showLoadingOverlay();
try {
const url = `${API_BASE_URL}/api/admin/users/${userId}`;
const response = await fetch(url, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCSRFToken()
}
});
const result = await response.json();
if (response.ok) {
showNotification(`✅ Benutzer "${userName}" erfolgreich gelöscht`, 'success');
// Seite nach 2 Sekunden neu laden
setTimeout(() => location.reload(), 2000);
} else {
showNotification('❌ Fehler beim Löschen: ' + result.error, 'error');
}
} catch (error) {
console.error('User delete error:', error);
showNotification('❌ Fehler beim Löschen des Benutzers', 'error');
} finally {
hideLoadingOverlay();
}
}
// Job-Aktionen verarbeiten
async function handleJobAction(action, jobId) {
let confirmMessage = '';
let url = '';
let method = 'POST';
switch (action) {
case 'cancel':
confirmMessage = 'Möchten Sie diesen Job wirklich abbrechen?';
url = `${API_BASE_URL}/api/jobs/${jobId}/cancel`;
break;
case 'delete':
confirmMessage = 'Möchten Sie diesen Job wirklich löschen?';
url = `${API_BASE_URL}/api/jobs/${jobId}`;
method = 'DELETE';
break;
case 'finish':
confirmMessage = 'Möchten Sie diesen Job als beendet markieren?';
url = `${API_BASE_URL}/api/jobs/${jobId}/finish`;
break;
default:
showNotification('❌ Unbekannte Aktion', 'error');
return;
}
if (!confirm(confirmMessage)) return;
showLoadingOverlay();
try {
const response = await fetch(url, {
method: method,
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCSRFToken()
}
});
const result = await response.json();
if (response.ok) {
showNotification('✅ Aktion erfolgreich ausgeführt', 'success');
// Seite nach 2 Sekunden neu laden
setTimeout(() => location.reload(), 2000);
} else {
showNotification('❌ Fehler: ' + result.error, 'error');
}
} catch (error) {
console.error('Job action error:', error);
showNotification('❌ Fehler beim Ausführen der Aktion', 'error');
} finally {
hideLoadingOverlay();
}
}
// Such- und Filter-Funktionen
function filterUsers(searchTerm) {
const userRows = document.querySelectorAll('tbody tr');
userRows.forEach(row => {
const text = row.textContent.toLowerCase();
const matches = text.includes(searchTerm.toLowerCase());
row.style.display = matches ? '' : 'none';
});
}
function filterJobs(status) {
const jobRows = document.querySelectorAll('.job-row, [data-job-status]');
jobRows.forEach(row => {
if (status === 'all') {
row.style.display = '';
} else {
const jobStatus = row.dataset.jobStatus || row.querySelector('.status')?.textContent?.toLowerCase();
const matches = jobStatus === status.toLowerCase();
row.style.display = matches ? '' : 'none';
}
});
}
// Daten-Export
function exportData(type) {
const url = `${API_BASE_URL}/api/admin/export/${type}`;
window.open(url, '_blank');
}
// Analytics-Daten laden
async function loadAnalyticsData() {
try {
const response = await fetch(`${API_BASE_URL}/api/admin/stats/live`);
const data = await response.json();
// Charts mit Chart.js erstellen (falls verfügbar)
if (typeof Chart !== 'undefined') {
createPrinterUsageChart(data);
createSuccessRateChart(data);
}
} catch (error) {
console.error('Analytics data error:', error);
}
}
// Live-Analytics starten
function startLiveAnalytics() {
setInterval(async () => {
try {
const response = await fetch(`${API_BASE_URL}/api/admin/stats/live`);
const data = await response.json();
// Live-Werte aktualisieren
document.getElementById('live-jobs').textContent = data.jobs?.active || 0;
document.getElementById('live-printers').textContent = data.printers?.online || 0;
document.getElementById('live-queue').textContent = data.jobs?.queued || 0;
document.getElementById('live-success').textContent = (data.jobs?.success_rate || 0) + '%';
} catch (error) {
console.error('Live analytics error:', error);
}
}, 5000); // Alle 5 Sekunden
}
// System-Status aktualisieren
async function updateSystemStatus() {
try {
const response = await fetch(`${API_BASE_URL}/api/admin/system/status`);
const data = await response.json();
// Status-Indikatoren aktualisieren
const indicators = document.querySelectorAll('.status-indicator');
indicators.forEach(indicator => {
// Aktualisiere basierend auf data
});
} catch (error) {
console.error('System status update error:', error);
}
}
/**
* Drucker aktualisieren
*/
async function updatePrinter(printerId, formData) {
try {
showLoadingOverlay();
const url = `${API_BASE_URL}/api/admin/printers/${printerId}/edit`;
const response = await fetch(url, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCSRFToken()
},
body: JSON.stringify({
name: formData.get('name'),
ip_address: formData.get('ip_address'),
location: formData.get('location'),
description: formData.get('description'),
printer_type: formData.get('printer_type'),
is_active: formData.get('is_active') === 'on'
})
});
const result = await response.json();
if (response.ok) {
showNotification('Drucker erfolgreich aktualisiert', 'success');
closeModal();
location.reload();
} else {
showNotification(result.error || 'Fehler beim Aktualisieren des Druckers', 'error');
}
} catch (error) {
console.error('Error updating printer:', error);
showNotification('Fehler beim Aktualisieren des Druckers', 'error');
} finally {
hideLoadingOverlay();
}
}
/**
* Drucker-Daten laden
*/
async function loadPrinterData(printerId) {
try {
const url = `${API_BASE_URL}/api/printers`;
const response = await fetch(url);
const printers = await response.json();
const printer = printers.find(p => p.id == printerId);
if (printer) {
const form = document.getElementById('printer-form');
form.name.value = printer.name || '';
form.ip_address.value = printer.ip_address || '';
form.location.value = printer.location || '';
form.description.value = printer.description || '';
form.printer_type.value = printer.printer_type || 'FDM';
form.is_active.checked = printer.is_active;
}
} catch (error) {
console.error('Error loading printer data:', error);
showNotification('Fehler beim Laden der Drucker-Daten', 'error');
}
}
/**
* CSRF Token abrufen
*/
function getCSRFToken() {
const token = document.querySelector('meta[name=csrf-token]');
return token ? token.getAttribute('content') : '';
}
/**
* Benutzer erstellen
*/
async function createUser(formData) {
try {
showLoadingOverlay();
const url = `${API_BASE_URL}/api/admin/users/create`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCSRFToken()
},
body: JSON.stringify({
email: formData.get('email'),
name: formData.get('name'),
password: formData.get('password'),
role: formData.get('role'),
username: formData.get('email').split('@')[0] // Username aus E-Mail ableiten
})
});
const result = await response.json();
if (response.ok) {
showNotification('Benutzer erfolgreich erstellt', 'success');
closeModal();
location.reload(); // Seite neu laden um neue Daten anzuzeigen
} else {
showNotification(result.error || 'Fehler beim Erstellen des Benutzers', 'error');
}
} catch (error) {
console.error('Error creating user:', error);
showNotification('Fehler beim Erstellen des Benutzers', 'error');
} finally {
hideLoadingOverlay();
}
}
/**
* Benutzer aktualisieren
*/
async function updateUser(userId, formData) {
try {
showLoadingOverlay();
const url = `${API_BASE_URL}/api/admin/users/${userId}/edit`;
const response = await fetch(url, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCSRFToken()
},
body: JSON.stringify({
email: formData.get('email'),
name: formData.get('name'),
password: formData.get('password'),
role: formData.get('role')
})
});
const result = await response.json();
if (response.ok) {
showNotification('Benutzer erfolgreich aktualisiert', 'success');
closeModal();
location.reload();
} else {
showNotification(result.error || 'Fehler beim Aktualisieren des Benutzers', 'error');
}
} catch (error) {
console.error('Error updating user:', error);
showNotification('Fehler beim Aktualisieren des Benutzers', 'error');
} finally {
hideLoadingOverlay();
}
}
console.log('🎉 Mercedes-Benz MYP Admin Dashboard JavaScript vollständig geladen!');