1580 lines
58 KiB
JavaScript
1580 lines
58 KiB
JavaScript
/**
|
||
* 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!');
|