🎉 Improved documentation and logs for better system understanding & maintenance
This commit is contained in:
2
backend/static/css/tailwind.min.css
vendored
2
backend/static/css/tailwind.min.css
vendored
File diff suppressed because one or more lines are too long
@ -1,670 +0,0 @@
|
||||
/**
|
||||
* MYP Admin Dashboard
|
||||
* Core JavaScript für das Admin-Dashboard
|
||||
*/
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize navigation
|
||||
initNavigation();
|
||||
|
||||
// Initialize modal events
|
||||
initModals();
|
||||
|
||||
// Load initial data
|
||||
loadDashboardData();
|
||||
});
|
||||
|
||||
/**
|
||||
* Navigation Initialization
|
||||
*/
|
||||
function initNavigation() {
|
||||
// Desktop navigation
|
||||
const desktopNavItems = document.querySelectorAll('.admin-nav-item');
|
||||
desktopNavItems.forEach(item => {
|
||||
item.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
const section = this.getAttribute('data-section');
|
||||
activateSection(section);
|
||||
updateActiveNavItem(this, desktopNavItems);
|
||||
});
|
||||
});
|
||||
|
||||
// Mobile navigation
|
||||
const mobileNavItems = document.querySelectorAll('.mobile-nav-item');
|
||||
mobileNavItems.forEach(item => {
|
||||
item.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
const section = this.getAttribute('data-section');
|
||||
activateSection(section);
|
||||
updateActiveNavItem(this, mobileNavItems);
|
||||
closeMobileNav();
|
||||
});
|
||||
});
|
||||
|
||||
// Mobile menu toggle
|
||||
const mobileMenuButton = document.getElementById('mobile-menu-button');
|
||||
const closeMobileNavButton = document.getElementById('close-mobile-nav');
|
||||
|
||||
if (mobileMenuButton) {
|
||||
mobileMenuButton.addEventListener('click', openMobileNav);
|
||||
}
|
||||
|
||||
if (closeMobileNavButton) {
|
||||
closeMobileNavButton.addEventListener('click', closeMobileNav);
|
||||
}
|
||||
|
||||
// Setup hash navigation
|
||||
window.addEventListener('hashchange', handleHashChange);
|
||||
if (window.location.hash) {
|
||||
handleHashChange();
|
||||
}
|
||||
}
|
||||
|
||||
function activateSection(section) {
|
||||
// Hide all sections
|
||||
document.querySelectorAll('.admin-section').forEach(el => {
|
||||
el.classList.remove('active');
|
||||
el.classList.add('hidden');
|
||||
});
|
||||
|
||||
// Show selected section
|
||||
const targetSection = document.getElementById(`${section}-section`);
|
||||
if (targetSection) {
|
||||
targetSection.classList.remove('hidden');
|
||||
targetSection.classList.add('active');
|
||||
|
||||
// Load section data if needed
|
||||
switch(section) {
|
||||
case 'dashboard':
|
||||
loadDashboardData();
|
||||
break;
|
||||
case 'users':
|
||||
loadUsers();
|
||||
break;
|
||||
case 'printers':
|
||||
loadPrinters();
|
||||
break;
|
||||
case 'scheduler':
|
||||
loadSchedulerStatus();
|
||||
break;
|
||||
case 'logs':
|
||||
loadLogs();
|
||||
break;
|
||||
}
|
||||
|
||||
// Update URL hash
|
||||
window.location.hash = section;
|
||||
}
|
||||
}
|
||||
|
||||
function updateActiveNavItem(activeItem, allItems) {
|
||||
// Remove active class from all items
|
||||
allItems.forEach(item => {
|
||||
item.classList.remove('active');
|
||||
});
|
||||
|
||||
// Add active class to selected item
|
||||
activeItem.classList.add('active');
|
||||
}
|
||||
|
||||
function handleHashChange() {
|
||||
const hash = window.location.hash.substring(1);
|
||||
if (hash) {
|
||||
const navItem = document.querySelector(`.admin-nav-item[data-section="${hash}"]`);
|
||||
if (navItem) {
|
||||
activateSection(hash);
|
||||
updateActiveNavItem(navItem, document.querySelectorAll('.admin-nav-item'));
|
||||
|
||||
// Also update mobile nav
|
||||
const mobileNavItem = document.querySelector(`.mobile-nav-item[data-section="${hash}"]`);
|
||||
if (mobileNavItem) {
|
||||
updateActiveNavItem(mobileNavItem, document.querySelectorAll('.mobile-nav-item'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function openMobileNav() {
|
||||
const mobileNav = document.getElementById('mobile-nav');
|
||||
if (mobileNav) {
|
||||
mobileNav.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
function closeMobileNav() {
|
||||
const mobileNav = document.getElementById('mobile-nav');
|
||||
if (mobileNav) {
|
||||
mobileNav.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modal Initialization
|
||||
*/
|
||||
function initModals() {
|
||||
// Delete modal
|
||||
const deleteModal = document.getElementById('delete-modal');
|
||||
const closeDeleteModalBtn = document.getElementById('close-delete-modal');
|
||||
const cancelDeleteBtn = document.getElementById('cancel-delete-btn');
|
||||
|
||||
if (closeDeleteModalBtn) {
|
||||
closeDeleteModalBtn.addEventListener('click', closeDeleteModal);
|
||||
}
|
||||
|
||||
if (cancelDeleteBtn) {
|
||||
cancelDeleteBtn.addEventListener('click', closeDeleteModal);
|
||||
}
|
||||
|
||||
// Toast notification
|
||||
const closeToastBtn = document.getElementById('close-toast');
|
||||
if (closeToastBtn) {
|
||||
closeToastBtn.addEventListener('click', closeToast);
|
||||
}
|
||||
|
||||
// Global refresh button
|
||||
const refreshAllBtn = document.getElementById('refresh-all-btn');
|
||||
if (refreshAllBtn) {
|
||||
refreshAllBtn.addEventListener('click', refreshAllData);
|
||||
}
|
||||
}
|
||||
|
||||
function showDeleteModal(message, onConfirm) {
|
||||
const modal = document.getElementById('delete-modal');
|
||||
const messageEl = document.getElementById('delete-message');
|
||||
const confirmBtn = document.getElementById('confirm-delete-btn');
|
||||
|
||||
if (modal && messageEl && confirmBtn) {
|
||||
messageEl.textContent = message;
|
||||
modal.classList.add('modal-show');
|
||||
|
||||
// Setup confirm button action
|
||||
confirmBtn.onclick = function() {
|
||||
closeDeleteModal();
|
||||
if (typeof onConfirm === 'function') {
|
||||
onConfirm();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function closeDeleteModal() {
|
||||
const modal = document.getElementById('delete-modal');
|
||||
if (modal) {
|
||||
modal.classList.remove('modal-show');
|
||||
}
|
||||
}
|
||||
|
||||
function showToast(message, type = 'info') {
|
||||
const toast = document.getElementById('toast-notification');
|
||||
const messageEl = document.getElementById('toast-message');
|
||||
const iconEl = document.getElementById('toast-icon');
|
||||
|
||||
if (toast && messageEl && iconEl) {
|
||||
messageEl.textContent = message;
|
||||
|
||||
// Set icon based on type
|
||||
const iconSvg = getToastIcon(type);
|
||||
iconEl.innerHTML = iconSvg;
|
||||
|
||||
// Show toast
|
||||
toast.classList.add('toast-show');
|
||||
|
||||
// Auto-hide after 5 seconds
|
||||
setTimeout(closeToast, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
function closeToast() {
|
||||
const toast = document.getElementById('toast-notification');
|
||||
if (toast) {
|
||||
toast.classList.remove('toast-show');
|
||||
}
|
||||
}
|
||||
|
||||
function getToastIcon(type) {
|
||||
switch(type) {
|
||||
case 'success':
|
||||
return '<svg class="h-6 w-6 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /></svg>';
|
||||
case 'error':
|
||||
return '<svg class="h-6 w-6 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /></svg>';
|
||||
case 'warning':
|
||||
return '<svg class="h-6 w-6 text-yellow-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg>';
|
||||
case 'info':
|
||||
default:
|
||||
return '<svg class="h-6 w-6 text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dashboard Data Loading
|
||||
*/
|
||||
function loadDashboardData() {
|
||||
// Load dashboard stats
|
||||
loadStats();
|
||||
|
||||
// Load recent activity
|
||||
loadRecentActivity();
|
||||
|
||||
// Load system status
|
||||
loadSystemStatus();
|
||||
|
||||
// Setup refresh buttons
|
||||
const refreshActivityBtn = document.getElementById('refresh-activity-btn');
|
||||
if (refreshActivityBtn) {
|
||||
refreshActivityBtn.addEventListener('click', loadRecentActivity);
|
||||
}
|
||||
|
||||
const refreshSystemBtn = document.getElementById('refresh-system-btn');
|
||||
if (refreshSystemBtn) {
|
||||
refreshSystemBtn.addEventListener('click', loadSystemStatus);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadStats() {
|
||||
try {
|
||||
const response = await fetch('/api/stats');
|
||||
const data = await response.json();
|
||||
|
||||
// Update dashboard counters mit robusten ID-Checks
|
||||
const userCountEl = document.getElementById('live-users-count');
|
||||
if (userCountEl) {
|
||||
userCountEl.textContent = data.total_users || 0;
|
||||
}
|
||||
|
||||
const printerCountEl = document.getElementById('live-printers-count');
|
||||
if (printerCountEl) {
|
||||
printerCountEl.textContent = data.total_printers || 0;
|
||||
}
|
||||
|
||||
const activeJobsEl = document.getElementById('live-jobs-active');
|
||||
if (activeJobsEl) {
|
||||
activeJobsEl.textContent = data.active_jobs || 0;
|
||||
}
|
||||
|
||||
// Update scheduler status
|
||||
updateSchedulerStatusIndicator(data.scheduler_status || false);
|
||||
|
||||
// Update additional stats if elements exist
|
||||
const onlinePrintersEl = document.getElementById('live-printers-online');
|
||||
if (onlinePrintersEl) {
|
||||
onlinePrintersEl.textContent = `${data.online_printers || 0} online`;
|
||||
}
|
||||
|
||||
const queuedJobsEl = document.getElementById('live-jobs-queued');
|
||||
if (queuedJobsEl) {
|
||||
queuedJobsEl.textContent = `${data.queued_jobs || 0} in Warteschlange`;
|
||||
}
|
||||
|
||||
const successRateEl = document.getElementById('live-success-rate');
|
||||
if (successRateEl) {
|
||||
successRateEl.textContent = `${data.success_rate || 0}%`;
|
||||
}
|
||||
|
||||
// Update progress bars if they exist
|
||||
updateProgressBar('users-progress', data.total_users, 10);
|
||||
updateProgressBar('printers-progress', data.online_printers, data.total_printers);
|
||||
updateProgressBar('jobs-progress', data.active_jobs, 10);
|
||||
updateProgressBar('success-progress', data.success_rate, 100);
|
||||
|
||||
console.log('✅ Dashboard-Statistiken erfolgreich aktualisiert:', data);
|
||||
} catch (error) {
|
||||
console.error('Error loading stats:', error);
|
||||
showToast('Fehler beim Laden der Statistiken', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper-Funktion zum sicheren Aktualisieren von Progress-Bars
|
||||
*/
|
||||
function updateProgressBar(progressId, currentValue, maxValue) {
|
||||
const progressEl = document.getElementById(progressId);
|
||||
if (progressEl && currentValue !== undefined && maxValue > 0) {
|
||||
const percentage = Math.min(100, Math.max(0, (currentValue / maxValue) * 100));
|
||||
progressEl.style.width = `${percentage}%`;
|
||||
}
|
||||
}
|
||||
|
||||
function updateSchedulerStatusIndicator(isRunning) {
|
||||
const statusText = document.getElementById('scheduler-status');
|
||||
const indicator = document.getElementById('scheduler-indicator');
|
||||
|
||||
if (statusText && indicator) {
|
||||
if (isRunning) {
|
||||
statusText.textContent = 'Aktiv';
|
||||
statusText.classList.add('text-green-600', 'dark:text-green-400');
|
||||
statusText.classList.remove('text-red-600', 'dark:text-red-400');
|
||||
|
||||
indicator.classList.add('bg-green-500');
|
||||
indicator.classList.remove('bg-red-500', 'bg-gray-300');
|
||||
} else {
|
||||
statusText.textContent = 'Inaktiv';
|
||||
statusText.classList.add('text-red-600', 'dark:text-red-400');
|
||||
statusText.classList.remove('text-green-600', 'dark:text-green-400');
|
||||
|
||||
indicator.classList.add('bg-red-500');
|
||||
indicator.classList.remove('bg-green-500', 'bg-gray-300');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRecentActivity() {
|
||||
const container = document.getElementById('recent-activity-container');
|
||||
if (!container) return;
|
||||
|
||||
// Show loading state
|
||||
container.innerHTML = `
|
||||
<div class="flex justify-center items-center py-8">
|
||||
<svg class="animate-spin h-8 w-8 text-accent-primary" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
`;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/activity/recent');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.activities && data.activities.length > 0) {
|
||||
const activities = data.activities;
|
||||
const html = activities.map(activity => `
|
||||
<div class="p-3 rounded-lg bg-light-surface dark:bg-dark-surface border border-light-border dark:border-dark-border">
|
||||
<div class="flex items-start">
|
||||
<div class="w-2 h-2 rounded-full bg-accent-primary mt-2 mr-3 flex-shrink-0"></div>
|
||||
<div>
|
||||
<p class="text-sm text-light-text dark:text-dark-text">${activity.description}</p>
|
||||
<p class="text-xs text-light-text-muted dark:text-dark-text-muted mt-1">
|
||||
${formatDateTime(activity.timestamp)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
container.innerHTML = html;
|
||||
} else {
|
||||
container.innerHTML = `
|
||||
<div class="text-center py-8">
|
||||
<p class="text-light-text-muted dark:text-dark-text-muted">Keine Aktivitäten gefunden</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading activities:', error);
|
||||
container.innerHTML = `
|
||||
<div class="text-center py-8">
|
||||
<p class="text-red-600 dark:text-red-400">Fehler beim Laden der Aktivitäten</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadSystemStatus() {
|
||||
try {
|
||||
const response = await fetch('/api/stats');
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// Prüfen ob data gültig ist
|
||||
if (!data || typeof data !== 'object') {
|
||||
throw new Error('Ungültige Antwort vom Server erhalten');
|
||||
}
|
||||
|
||||
// Update system stats mit Fallback-Werten
|
||||
const totalPrintTimeEl = document.getElementById('total-print-time');
|
||||
if (totalPrintTimeEl) {
|
||||
totalPrintTimeEl.textContent = formatPrintTime(data.total_print_time_hours);
|
||||
}
|
||||
|
||||
const completedJobsEl = document.getElementById('completed-jobs-count');
|
||||
if (completedJobsEl) {
|
||||
completedJobsEl.textContent = data.total_jobs_completed || 0;
|
||||
}
|
||||
|
||||
const totalMaterialEl = document.getElementById('total-material-used');
|
||||
if (totalMaterialEl) {
|
||||
totalMaterialEl.textContent = formatMaterialUsed(data.total_material_used);
|
||||
}
|
||||
|
||||
const lastUpdatedEl = document.getElementById('last-updated-time');
|
||||
if (lastUpdatedEl) {
|
||||
lastUpdatedEl.textContent = data.last_updated ? formatDateTime(data.last_updated) : '-';
|
||||
}
|
||||
|
||||
console.log('✅ Systemstatus erfolgreich geladen:', data);
|
||||
} catch (error) {
|
||||
console.error('Error loading system status:', error);
|
||||
const errorMessage = error.message || 'Unbekannter Systemfehler';
|
||||
showToast(`Fehler beim Laden des Systemstatus: ${errorMessage}`, 'error');
|
||||
|
||||
// Fallback-Werte anzeigen
|
||||
const elements = [
|
||||
'total-print-time',
|
||||
'completed-jobs-count',
|
||||
'total-material-used',
|
||||
'last-updated-time'
|
||||
];
|
||||
|
||||
elements.forEach(id => {
|
||||
const el = document.getElementById(id);
|
||||
if (el) {
|
||||
el.textContent = 'Fehler beim Laden';
|
||||
el.classList.add('text-red-500');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function formatPrintTime(hours) {
|
||||
if (!hours) return '-';
|
||||
return `${hours} Stunden`;
|
||||
}
|
||||
|
||||
function formatMaterialUsed(grams) {
|
||||
if (!grams) return '-';
|
||||
return `${grams} g`;
|
||||
}
|
||||
|
||||
function formatDateTime(dateString) {
|
||||
if (!dateString) return '-';
|
||||
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleString('de-DE', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Global refresh function
|
||||
*/
|
||||
function refreshAllData() {
|
||||
// Get active section
|
||||
const activeSection = document.querySelector('.admin-section.active');
|
||||
if (activeSection) {
|
||||
const sectionId = activeSection.id;
|
||||
const section = sectionId.replace('-section', '');
|
||||
|
||||
// Reload data based on active section
|
||||
switch(section) {
|
||||
case 'dashboard':
|
||||
loadDashboardData();
|
||||
break;
|
||||
case 'users':
|
||||
loadUsers();
|
||||
break;
|
||||
case 'printers':
|
||||
loadPrinters();
|
||||
break;
|
||||
case 'scheduler':
|
||||
loadSchedulerStatus();
|
||||
break;
|
||||
case 'logs':
|
||||
loadLogs();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
showToast('Daten aktualisiert', 'success');
|
||||
}
|
||||
/**
|
||||
* Benutzer laden und anzeigen
|
||||
*/
|
||||
function loadUsers() {
|
||||
const usersContainer = document.getElementById('users-container');
|
||||
if (!usersContainer) return;
|
||||
|
||||
// Lade-Animation anzeigen
|
||||
usersContainer.innerHTML = `
|
||||
<div class="flex justify-center items-center py-12">
|
||||
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-indigo-600 dark:border-indigo-400"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Benutzer vom Server laden
|
||||
fetch('/api/users')
|
||||
.then(response => {
|
||||
if (!response.ok) throw new Error('Fehler beim Laden der Benutzer');
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
renderUsers(data.users);
|
||||
updateUserStatistics(data.users);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Fehler beim Laden der Benutzer:', error);
|
||||
usersContainer.innerHTML = `
|
||||
<div class="text-center py-8">
|
||||
<div class="text-red-600 dark:text-red-400 text-xl mb-2">Fehler beim Laden der Benutzer</div>
|
||||
<p class="text-gray-600 dark:text-gray-400">${error.message}</p>
|
||||
<button onclick="loadUsers()" class="mt-4 px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors">
|
||||
Erneut versuchen
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Drucker laden und anzeigen
|
||||
*/
|
||||
function loadPrinters() {
|
||||
const printersContainer = document.getElementById('printers-container');
|
||||
if (!printersContainer) return;
|
||||
|
||||
// Lade-Animation anzeigen
|
||||
printersContainer.innerHTML = `
|
||||
<div class="flex justify-center items-center py-12">
|
||||
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-indigo-600 dark:border-indigo-400"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Drucker vom Server laden
|
||||
fetch('/api/printers')
|
||||
.then(response => {
|
||||
if (!response.ok) throw new Error('Fehler beim Laden der Drucker');
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
renderPrinters(data.printers);
|
||||
updatePrinterStatistics(data.printers);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Fehler beim Laden der Drucker:', error);
|
||||
printersContainer.innerHTML = `
|
||||
<div class="text-center py-8">
|
||||
<div class="text-red-600 dark:text-red-400 text-xl mb-2">Fehler beim Laden der Drucker</div>
|
||||
<p class="text-gray-600 dark:text-gray-400">${error.message}</p>
|
||||
<button onclick="loadPrinters()" class="mt-4 px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors">
|
||||
Erneut versuchen
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Scheduler-Status laden und anzeigen
|
||||
*/
|
||||
function loadSchedulerStatus() {
|
||||
const schedulerContainer = document.getElementById('scheduler-container');
|
||||
if (!schedulerContainer) return;
|
||||
|
||||
// Lade-Animation anzeigen
|
||||
schedulerContainer.innerHTML = `
|
||||
<div class="flex justify-center items-center py-12">
|
||||
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-indigo-600 dark:border-indigo-400"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Scheduler-Status vom Server laden
|
||||
fetch('/api/scheduler/status')
|
||||
.then(response => {
|
||||
if (!response.ok) throw new Error('Fehler beim Laden des Scheduler-Status');
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
renderSchedulerStatus(data);
|
||||
updateSchedulerControls(data.active);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Fehler beim Laden des Scheduler-Status:', error);
|
||||
schedulerContainer.innerHTML = `
|
||||
<div class="text-center py-8">
|
||||
<div class="text-red-600 dark:text-red-400 text-xl mb-2">Fehler beim Laden des Scheduler-Status</div>
|
||||
<p class="text-gray-600 dark:text-gray-400">${error.message}</p>
|
||||
<button onclick="loadSchedulerStatus()" class="mt-4 px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors">
|
||||
Erneut versuchen
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs laden und anzeigen
|
||||
*/
|
||||
function loadLogs() {
|
||||
const logsContainer = document.getElementById('logs-container');
|
||||
if (!logsContainer) return;
|
||||
|
||||
// Lade-Animation anzeigen
|
||||
logsContainer.innerHTML = `
|
||||
<div class="flex justify-center items-center py-12">
|
||||
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-indigo-600 dark:border-indigo-400"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Logs vom Server laden
|
||||
fetch('/api/logs')
|
||||
.then(response => {
|
||||
if (!response.ok) throw new Error('Fehler beim Laden der Logs');
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
window.logsData = data.logs;
|
||||
window.filteredLogs = [...data.logs];
|
||||
renderLogs();
|
||||
updateLogStatistics();
|
||||
scrollLogsToBottom();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Fehler beim Laden der Logs:', error);
|
||||
logsContainer.innerHTML = `
|
||||
<div class="text-center py-8">
|
||||
<div class="text-red-600 dark:text-red-400 text-xl mb-2">Fehler beim Laden der Logs</div>
|
||||
<p class="text-gray-600 dark:text-gray-400">${error.message}</p>
|
||||
<button onclick="loadLogs()" class="mt-4 px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors">
|
||||
Erneut versuchen
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
}
|
@ -1,585 +0,0 @@
|
||||
/**
|
||||
* Mercedes-Benz MYP Admin Live Dashboard
|
||||
* Echtzeit-Updates für das Admin Panel mit echten Daten
|
||||
*/
|
||||
|
||||
class AdminLiveDashboard {
|
||||
constructor() {
|
||||
this.isLive = false;
|
||||
this.updateInterval = null;
|
||||
this.retryCount = 0;
|
||||
this.maxRetries = 3;
|
||||
|
||||
// Dynamische API-Base-URL-Erkennung
|
||||
this.apiBaseUrl = this.detectApiBaseUrl();
|
||||
console.log('🔗 API Base URL erkannt:', this.apiBaseUrl);
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
detectApiBaseUrl() {
|
||||
const currentHost = window.location.hostname;
|
||||
const currentProtocol = window.location.protocol;
|
||||
const currentPort = window.location.port;
|
||||
|
||||
console.log('🔍 Live Dashboard API URL Detection:', { currentHost, currentProtocol, currentPort });
|
||||
|
||||
// Prüfe ob wir bereits auf dem richtigen Port sind
|
||||
if (currentPort === '5000') {
|
||||
console.log('✅ Verwende relative URLs (bereits auf Port 5000)');
|
||||
return '';
|
||||
}
|
||||
|
||||
// Für Entwicklung: Verwende HTTP Port 5000
|
||||
const devUrl = `http://${currentHost}:5000`;
|
||||
console.log('🔄 Fallback zu HTTP:5000:', devUrl);
|
||||
|
||||
return devUrl;
|
||||
}
|
||||
|
||||
init() {
|
||||
console.log('🚀 Mercedes-Benz MYP Admin Live Dashboard gestartet');
|
||||
|
||||
// Live-Status anzeigen
|
||||
this.updateLiveTime();
|
||||
this.startLiveUpdates();
|
||||
|
||||
// Event Listeners
|
||||
this.bindEvents();
|
||||
|
||||
// Initial Load
|
||||
this.loadLiveStats();
|
||||
|
||||
// Error Monitoring System
|
||||
this.initErrorMonitoring();
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
// Quick Action Buttons
|
||||
const systemStatusBtn = document.getElementById('system-status-btn');
|
||||
const analyticsBtn = document.getElementById('analytics-btn');
|
||||
const maintenanceBtn = document.getElementById('maintenance-btn');
|
||||
|
||||
if (systemStatusBtn) {
|
||||
systemStatusBtn.addEventListener('click', () => this.showSystemStatus());
|
||||
}
|
||||
|
||||
if (analyticsBtn) {
|
||||
analyticsBtn.addEventListener('click', () => this.showAnalytics());
|
||||
}
|
||||
|
||||
if (maintenanceBtn) {
|
||||
maintenanceBtn.addEventListener('click', () => this.showMaintenance());
|
||||
}
|
||||
|
||||
// Page Visibility API für optimierte Updates
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.hidden) {
|
||||
this.pauseLiveUpdates();
|
||||
} else {
|
||||
this.resumeLiveUpdates();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
startLiveUpdates() {
|
||||
this.isLive = true;
|
||||
this.updateLiveIndicator(true);
|
||||
|
||||
// Live Stats alle 30 Sekunden aktualisieren
|
||||
this.updateInterval = setInterval(() => {
|
||||
this.loadLiveStats();
|
||||
}, 30000);
|
||||
|
||||
// Zeit jede Sekunde aktualisieren
|
||||
setInterval(() => {
|
||||
this.updateLiveTime();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
pauseLiveUpdates() {
|
||||
this.isLive = false;
|
||||
this.updateLiveIndicator(false);
|
||||
if (this.updateInterval) {
|
||||
clearInterval(this.updateInterval);
|
||||
}
|
||||
}
|
||||
|
||||
resumeLiveUpdates() {
|
||||
if (!this.isLive) {
|
||||
this.startLiveUpdates();
|
||||
this.loadLiveStats(); // Sofortiges Update beim Fortsetzen
|
||||
}
|
||||
}
|
||||
|
||||
updateLiveIndicator(isLive) {
|
||||
const indicator = document.getElementById('live-indicator');
|
||||
if (indicator) {
|
||||
if (isLive) {
|
||||
indicator.className = 'w-2 h-2 bg-green-400 rounded-full animate-pulse';
|
||||
} else {
|
||||
indicator.className = 'w-2 h-2 bg-gray-400 rounded-full';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateLiveTime() {
|
||||
const timeElement = document.getElementById('live-time');
|
||||
if (timeElement) {
|
||||
const now = new Date();
|
||||
timeElement.textContent = now.toLocaleTimeString('de-DE');
|
||||
}
|
||||
}
|
||||
|
||||
async loadLiveStats() {
|
||||
try {
|
||||
const url = `${this.apiBaseUrl}/api/admin/stats/live`;
|
||||
console.log('🔄 Lade Live-Statistiken von:', url);
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': this.getCSRFToken()
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
this.updateStatsDisplay(data);
|
||||
this.retryCount = 0; // Reset retry count on success
|
||||
|
||||
// Success notification (optional)
|
||||
this.showQuietNotification('Live-Daten aktualisiert', 'success');
|
||||
} else {
|
||||
throw new Error(data.error || 'Unbekannter Fehler beim Laden der Live-Statistiken');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Live-Statistiken:', error);
|
||||
|
||||
this.retryCount++;
|
||||
if (this.retryCount <= this.maxRetries) {
|
||||
console.log(`Versuche erneut... (${this.retryCount}/${this.maxRetries})`);
|
||||
setTimeout(() => this.loadLiveStats(), 5000); // Retry nach 5 Sekunden
|
||||
} else {
|
||||
this.handleConnectionError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateStatsDisplay(data) {
|
||||
// Benutzer Stats
|
||||
this.updateCounter('live-users-count', data.total_users);
|
||||
this.updateProgress('users-progress', Math.min((data.total_users / 20) * 100, 100)); // Max 20 users = 100%
|
||||
|
||||
// Drucker Stats
|
||||
this.updateCounter('live-printers-count', data.total_printers);
|
||||
this.updateElement('live-printers-online', `${data.online_printers} online`);
|
||||
if (data.total_printers > 0) {
|
||||
this.updateProgress('printers-progress', (data.online_printers / data.total_printers) * 100);
|
||||
}
|
||||
|
||||
// Jobs Stats
|
||||
this.updateCounter('live-jobs-active', data.active_jobs);
|
||||
this.updateElement('live-jobs-queued', `${data.queued_jobs} in Warteschlange`);
|
||||
this.updateProgress('jobs-progress', Math.min(data.active_jobs * 20, 100)); // Max 5 jobs = 100%
|
||||
|
||||
// Erfolgsrate Stats
|
||||
this.updateCounter('live-success-rate', `${data.success_rate}%`);
|
||||
this.updateProgress('success-progress', data.success_rate);
|
||||
|
||||
// Trend Analysis
|
||||
this.updateSuccessTrend(data.success_rate);
|
||||
|
||||
console.log('📊 Live-Statistiken aktualisiert:', data);
|
||||
}
|
||||
|
||||
updateCounter(elementId, newValue) {
|
||||
const element = document.getElementById(elementId);
|
||||
if (element) {
|
||||
const currentValue = parseInt(element.textContent) || 0;
|
||||
if (currentValue !== newValue) {
|
||||
this.animateCounter(element, currentValue, newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
animateCounter(element, from, to) {
|
||||
const duration = 1000; // 1 Sekunde
|
||||
const increment = (to - from) / (duration / 16); // 60 FPS
|
||||
let current = from;
|
||||
|
||||
const timer = setInterval(() => {
|
||||
current += increment;
|
||||
if ((increment > 0 && current >= to) || (increment < 0 && current <= to)) {
|
||||
current = to;
|
||||
clearInterval(timer);
|
||||
}
|
||||
element.textContent = Math.round(current);
|
||||
}, 16);
|
||||
}
|
||||
|
||||
updateElement(elementId, newValue) {
|
||||
const element = document.getElementById(elementId);
|
||||
if (element && element.textContent !== newValue) {
|
||||
element.textContent = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
updateProgress(elementId, percentage) {
|
||||
const element = document.getElementById(elementId);
|
||||
if (element) {
|
||||
element.style.width = `${Math.max(0, Math.min(100, percentage))}%`;
|
||||
}
|
||||
}
|
||||
|
||||
updateSuccessTrend(successRate) {
|
||||
const trendElement = document.getElementById('success-trend');
|
||||
if (trendElement) {
|
||||
let trendText = 'Stabil';
|
||||
let trendClass = 'text-green-500';
|
||||
let trendIcon = 'M5 10l7-7m0 0l7 7m-7-7v18'; // Up arrow
|
||||
|
||||
if (successRate >= 95) {
|
||||
trendText = 'Excellent';
|
||||
trendClass = 'text-green-600';
|
||||
} else if (successRate >= 80) {
|
||||
trendText = 'Gut';
|
||||
trendClass = 'text-green-500';
|
||||
} else if (successRate >= 60) {
|
||||
trendText = 'Mittel';
|
||||
trendClass = 'text-yellow-500';
|
||||
trendIcon = 'M5 12h14'; // Horizontal line
|
||||
} else {
|
||||
trendText = 'Niedrig';
|
||||
trendClass = 'text-red-500';
|
||||
trendIcon = 'M19 14l-7 7m0 0l-7-7m7 7V3'; // Down arrow
|
||||
}
|
||||
|
||||
trendElement.className = `text-sm ${trendClass}`;
|
||||
trendElement.innerHTML = `
|
||||
<span class="inline-flex items-center">
|
||||
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="${trendIcon}"/>
|
||||
</svg>
|
||||
${trendText}
|
||||
</span>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
showSystemStatus() {
|
||||
// System Status Modal oder Navigation
|
||||
console.log('🔧 System Status angezeigt');
|
||||
this.showNotification('System Status wird geladen...', 'info');
|
||||
|
||||
// Hier könnten weitere System-Details geladen werden
|
||||
const url = `${this.apiBaseUrl}/api/admin/system/status`;
|
||||
fetch(url)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// System Status anzeigen
|
||||
console.log('System Status:', data);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Fehler beim Laden des System Status:', error);
|
||||
});
|
||||
}
|
||||
|
||||
showAnalytics() {
|
||||
console.log('📈 Live Analytics angezeigt');
|
||||
this.showNotification('Analytics werden geladen...', 'info');
|
||||
|
||||
// Analytics Tab aktivieren oder Modal öffnen
|
||||
const analyticsTab = document.querySelector('a[href*="tab=system"]');
|
||||
if (analyticsTab) {
|
||||
analyticsTab.click();
|
||||
}
|
||||
}
|
||||
|
||||
showMaintenance() {
|
||||
console.log('🛠️ Wartung angezeigt');
|
||||
this.showNotification('Wartungsoptionen werden geladen...', 'info');
|
||||
|
||||
// Wartungs-Tab aktivieren oder Modal öffnen
|
||||
const systemTab = document.querySelector('a[href*="tab=system"]');
|
||||
if (systemTab) {
|
||||
systemTab.click();
|
||||
}
|
||||
}
|
||||
|
||||
handleConnectionError() {
|
||||
console.error('🔴 Verbindung zu Live-Updates verloren');
|
||||
this.updateLiveIndicator(false);
|
||||
this.showNotification('Verbindung zu Live-Updates verloren. Versuche erneut...', 'error');
|
||||
|
||||
// Auto-Recovery nach 30 Sekunden
|
||||
setTimeout(() => {
|
||||
this.retryCount = 0;
|
||||
this.loadLiveStats();
|
||||
}, 30000);
|
||||
}
|
||||
|
||||
showNotification(message, type = 'info') {
|
||||
// Erstelle oder aktualisiere Notification
|
||||
let notification = document.getElementById('live-notification');
|
||||
if (!notification) {
|
||||
notification = document.createElement('div');
|
||||
notification.id = 'live-notification';
|
||||
notification.className = 'fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg max-w-sm';
|
||||
document.body.appendChild(notification);
|
||||
}
|
||||
|
||||
const colors = {
|
||||
success: 'bg-green-500 text-white',
|
||||
error: 'bg-red-500 text-white',
|
||||
info: 'bg-blue-500 text-white',
|
||||
warning: 'bg-yellow-500 text-white'
|
||||
};
|
||||
|
||||
notification.className = `fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg max-w-sm ${colors[type]} transform transition-all duration-300 translate-x-0`;
|
||||
notification.textContent = message;
|
||||
|
||||
// Auto-Hide nach 3 Sekunden
|
||||
setTimeout(() => {
|
||||
if (notification) {
|
||||
notification.style.transform = 'translateX(100%)';
|
||||
setTimeout(() => {
|
||||
if (notification && notification.parentNode) {
|
||||
notification.parentNode.removeChild(notification);
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
showQuietNotification(message, type) {
|
||||
// Nur in der Konsole loggen für nicht-störende Updates
|
||||
const emoji = type === 'success' ? '✅' : type === 'error' ? '❌' : 'ℹ️';
|
||||
console.log(`${emoji} ${message}`);
|
||||
}
|
||||
|
||||
getCSRFToken() {
|
||||
const meta = document.querySelector('meta[name="csrf-token"]');
|
||||
return meta ? meta.getAttribute('content') : '';
|
||||
}
|
||||
|
||||
// Error Monitoring System
|
||||
initErrorMonitoring() {
|
||||
// Check system health every 30 seconds
|
||||
this.checkSystemHealth();
|
||||
setInterval(() => this.checkSystemHealth(), 30000);
|
||||
|
||||
// Setup error alert event handlers
|
||||
this.setupErrorAlertHandlers();
|
||||
}
|
||||
|
||||
async checkSystemHealth() {
|
||||
try {
|
||||
const response = await fetch('/api/admin/system-health');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
this.updateHealthDisplay(data);
|
||||
this.updateErrorAlerts(data);
|
||||
} else {
|
||||
console.error('System health check failed:', data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking system health:', error);
|
||||
}
|
||||
}
|
||||
|
||||
updateHealthDisplay(data) {
|
||||
// Update database health status
|
||||
const statusIndicator = document.getElementById('db-status-indicator');
|
||||
const statusText = document.getElementById('db-status-text');
|
||||
const lastMigration = document.getElementById('last-migration');
|
||||
const schemaIntegrity = document.getElementById('schema-integrity');
|
||||
const recentErrorsCount = document.getElementById('recent-errors-count');
|
||||
|
||||
if (statusIndicator && statusText) {
|
||||
if (data.health_status === 'critical') {
|
||||
statusIndicator.className = 'w-3 h-3 bg-red-500 rounded-full animate-pulse';
|
||||
statusText.textContent = 'Kritisch';
|
||||
statusText.className = 'text-sm font-medium text-red-600 dark:text-red-400';
|
||||
} else if (data.health_status === 'warning') {
|
||||
statusIndicator.className = 'w-3 h-3 bg-yellow-500 rounded-full animate-pulse';
|
||||
statusText.textContent = 'Warnung';
|
||||
statusText.className = 'text-sm font-medium text-yellow-600 dark:text-yellow-400';
|
||||
} else {
|
||||
statusIndicator.className = 'w-3 h-3 bg-green-400 rounded-full animate-pulse';
|
||||
statusText.textContent = 'Gesund';
|
||||
statusText.className = 'text-sm font-medium text-green-600 dark:text-green-400';
|
||||
}
|
||||
}
|
||||
|
||||
if (lastMigration) {
|
||||
lastMigration.textContent = data.last_migration || 'Unbekannt';
|
||||
}
|
||||
|
||||
if (schemaIntegrity) {
|
||||
schemaIntegrity.textContent = data.schema_integrity || 'Prüfung';
|
||||
if (data.schema_integrity === 'FEHLER') {
|
||||
schemaIntegrity.className = 'text-lg font-semibold text-red-600 dark:text-red-400';
|
||||
} else {
|
||||
schemaIntegrity.className = 'text-lg font-semibold text-green-600 dark:text-green-400';
|
||||
}
|
||||
}
|
||||
|
||||
if (recentErrorsCount) {
|
||||
const errorCount = data.recent_errors_count || 0;
|
||||
recentErrorsCount.textContent = errorCount;
|
||||
if (errorCount > 0) {
|
||||
recentErrorsCount.className = 'text-lg font-semibold text-red-600 dark:text-red-400';
|
||||
} else {
|
||||
recentErrorsCount.className = 'text-lg font-semibold text-green-600 dark:text-green-400';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateErrorAlerts(data) {
|
||||
const alertContainer = document.getElementById('critical-errors-alert');
|
||||
const errorList = document.getElementById('error-list');
|
||||
|
||||
if (!alertContainer || !errorList) return;
|
||||
|
||||
const allErrors = [...(data.critical_errors || []), ...(data.warnings || [])];
|
||||
|
||||
if (allErrors.length > 0) {
|
||||
// Show alert container
|
||||
alertContainer.classList.remove('hidden');
|
||||
|
||||
// Clear previous errors
|
||||
errorList.innerHTML = '';
|
||||
|
||||
// Add each error
|
||||
allErrors.forEach(error => {
|
||||
const errorElement = document.createElement('div');
|
||||
errorElement.className = `p-3 rounded-lg border-l-4 ${
|
||||
error.severity === 'critical' ? 'bg-red-50 dark:bg-red-900/30 border-red-500' :
|
||||
error.severity === 'high' ? 'bg-orange-50 dark:bg-orange-900/30 border-orange-500' :
|
||||
'bg-yellow-50 dark:bg-yellow-900/30 border-yellow-500'
|
||||
}`;
|
||||
|
||||
errorElement.innerHTML = `
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<h4 class="font-medium ${
|
||||
error.severity === 'critical' ? 'text-red-800 dark:text-red-200' :
|
||||
error.severity === 'high' ? 'text-orange-800 dark:text-orange-200' :
|
||||
'text-yellow-800 dark:text-yellow-200'
|
||||
}">${error.message}</h4>
|
||||
<p class="text-sm mt-1 ${
|
||||
error.severity === 'critical' ? 'text-red-600 dark:text-red-300' :
|
||||
error.severity === 'high' ? 'text-orange-600 dark:text-orange-300' :
|
||||
'text-yellow-600 dark:text-yellow-300'
|
||||
}">💡 ${error.suggested_fix}</p>
|
||||
<p class="text-xs mt-1 text-gray-500 dark:text-gray-400">
|
||||
📅 ${new Date(error.timestamp).toLocaleString('de-DE')}
|
||||
</p>
|
||||
</div>
|
||||
<span class="ml-2 px-2 py-1 text-xs font-medium rounded-full ${
|
||||
error.severity === 'critical' ? 'bg-red-100 text-red-800 dark:bg-red-800 dark:text-red-100' :
|
||||
error.severity === 'high' ? 'bg-orange-100 text-orange-800 dark:bg-orange-800 dark:text-orange-100' :
|
||||
'bg-yellow-100 text-yellow-800 dark:bg-yellow-800 dark:text-yellow-100'
|
||||
}">
|
||||
${error.severity.toUpperCase()}
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
errorList.appendChild(errorElement);
|
||||
});
|
||||
} else {
|
||||
// Hide alert container
|
||||
alertContainer.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
setupErrorAlertHandlers() {
|
||||
// Fix errors button
|
||||
const fixErrorsBtn = document.getElementById('fix-errors-btn');
|
||||
if (fixErrorsBtn) {
|
||||
fixErrorsBtn.addEventListener('click', async () => {
|
||||
await this.fixErrors();
|
||||
});
|
||||
}
|
||||
|
||||
// Dismiss errors button
|
||||
const dismissErrorsBtn = document.getElementById('dismiss-errors-btn');
|
||||
if (dismissErrorsBtn) {
|
||||
dismissErrorsBtn.addEventListener('click', () => {
|
||||
const alertContainer = document.getElementById('critical-errors-alert');
|
||||
if (alertContainer) {
|
||||
alertContainer.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// View details button
|
||||
const viewDetailsBtn = document.getElementById('view-error-details-btn');
|
||||
if (viewDetailsBtn) {
|
||||
viewDetailsBtn.addEventListener('click', () => {
|
||||
// Redirect to logs tab
|
||||
window.location.href = '/admin-dashboard?tab=logs';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async fixErrors() {
|
||||
const fixBtn = document.getElementById('fix-errors-btn');
|
||||
if (!fixBtn) return;
|
||||
|
||||
// Show loading state
|
||||
const originalText = fixBtn.innerHTML;
|
||||
fixBtn.innerHTML = '🔄 Repariere...';
|
||||
fixBtn.disabled = true;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/admin/fix-errors', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
// Show success message
|
||||
this.showNotification('✅ Automatische Reparatur erfolgreich durchgeführt!', 'success');
|
||||
|
||||
// Refresh health check
|
||||
setTimeout(() => {
|
||||
this.checkSystemHealth();
|
||||
}, 2000);
|
||||
} else {
|
||||
// Show error message
|
||||
this.showNotification(`❌ Reparatur fehlgeschlagen: ${data.error}`, 'error');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fixing errors:', error);
|
||||
this.showNotification('❌ Fehler bei der automatischen Reparatur', 'error');
|
||||
} finally {
|
||||
// Restore button
|
||||
fixBtn.innerHTML = originalText;
|
||||
fixBtn.disabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize when DOM is ready
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
new AdminLiveDashboard();
|
||||
});
|
||||
|
||||
// Export for global access
|
||||
window.AdminLiveDashboard = AdminLiveDashboard;
|
@ -1,350 +0,0 @@
|
||||
/**
|
||||
* Admin System Management JavaScript
|
||||
* Funktionen für System-Wartung und -Konfiguration
|
||||
*/
|
||||
|
||||
// CSRF Token für AJAX-Anfragen
|
||||
function getCsrfToken() {
|
||||
const token = document.querySelector('meta[name="csrf-token"]');
|
||||
return token ? token.getAttribute('content') : '';
|
||||
}
|
||||
|
||||
// Hilfsfunktion für API-Aufrufe
|
||||
async function makeApiCall(url, method = 'GET', data = null) {
|
||||
const options = {
|
||||
method: method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': getCsrfToken()
|
||||
}
|
||||
};
|
||||
|
||||
if (data) {
|
||||
options.body = JSON.stringify(data);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(url, options);
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
showNotification(result.message || 'Aktion erfolgreich ausgeführt', 'success');
|
||||
return result;
|
||||
} else {
|
||||
showNotification(result.error || 'Ein Fehler ist aufgetreten', 'error');
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
showNotification('Netzwerkfehler: ' + error.message, 'error');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Logs laden und anzeigen
|
||||
async function loadLogs() {
|
||||
const logsContainer = document.getElementById('logs-container');
|
||||
if (!logsContainer) return;
|
||||
|
||||
// Lade-Animation anzeigen
|
||||
logsContainer.innerHTML = `
|
||||
<div class="flex justify-center items-center py-12">
|
||||
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-indigo-600 dark:border-indigo-400"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/logs');
|
||||
if (!response.ok) throw new Error('Fehler beim Laden der Logs');
|
||||
|
||||
const data = await response.json();
|
||||
window.logsData = data.logs || [];
|
||||
window.filteredLogs = [...window.logsData];
|
||||
renderLogs();
|
||||
updateLogStatistics();
|
||||
scrollLogsToBottom();
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Logs:', error);
|
||||
logsContainer.innerHTML = `
|
||||
<div class="text-center py-8">
|
||||
<div class="text-red-600 dark:text-red-400 text-xl mb-2">Fehler beim Laden der Logs</div>
|
||||
<p class="text-gray-600 dark:text-gray-400">${error.message}</p>
|
||||
<button onclick="loadLogs()" class="mt-4 px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors">
|
||||
Erneut versuchen
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
// Logs rendern
|
||||
function renderLogs() {
|
||||
const logsContainer = document.getElementById('logs-container');
|
||||
if (!logsContainer || !window.filteredLogs) return;
|
||||
|
||||
if (window.filteredLogs.length === 0) {
|
||||
logsContainer.innerHTML = `
|
||||
<div class="text-center py-8">
|
||||
<p class="text-gray-600 dark:text-gray-400">Keine Logs gefunden</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
const logsHtml = window.filteredLogs.map(log => {
|
||||
const levelColor = getLogLevelColor(log.level);
|
||||
return `
|
||||
<div class="bg-white/40 dark:bg-slate-700/40 rounded-lg p-4 border ${levelColor.border}">
|
||||
<div class="flex items-start space-x-3">
|
||||
<span class="inline-block px-2 py-1 text-xs font-semibold rounded-full ${levelColor.bg} ${levelColor.text}">
|
||||
${log.level}
|
||||
</span>
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<span class="text-sm font-medium text-slate-600 dark:text-slate-400">${log.category}</span>
|
||||
<span class="text-xs text-slate-500 dark:text-slate-500">${log.timestamp}</span>
|
||||
</div>
|
||||
<p class="text-sm text-slate-900 dark:text-white break-all">${log.message}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
logsContainer.innerHTML = logsHtml;
|
||||
}
|
||||
|
||||
// Log-Level-Farben bestimmen
|
||||
function getLogLevelColor(level) {
|
||||
const colors = {
|
||||
'ERROR': {
|
||||
bg: 'bg-red-100 dark:bg-red-900/30',
|
||||
text: 'text-red-800 dark:text-red-200',
|
||||
border: 'border-red-200 dark:border-red-700'
|
||||
},
|
||||
'WARNING': {
|
||||
bg: 'bg-yellow-100 dark:bg-yellow-900/30',
|
||||
text: 'text-yellow-800 dark:text-yellow-200',
|
||||
border: 'border-yellow-200 dark:border-yellow-700'
|
||||
},
|
||||
'INFO': {
|
||||
bg: 'bg-blue-100 dark:bg-blue-900/30',
|
||||
text: 'text-blue-800 dark:text-blue-200',
|
||||
border: 'border-blue-200 dark:border-blue-700'
|
||||
},
|
||||
'DEBUG': {
|
||||
bg: 'bg-gray-100 dark:bg-gray-900/30',
|
||||
text: 'text-gray-800 dark:text-gray-200',
|
||||
border: 'border-gray-200 dark:border-gray-700'
|
||||
}
|
||||
};
|
||||
|
||||
return colors[level.toUpperCase()] || colors['INFO'];
|
||||
}
|
||||
|
||||
// Log-Statistiken aktualisieren
|
||||
function updateLogStatistics() {
|
||||
if (!window.logsData) return;
|
||||
|
||||
const stats = {
|
||||
total: window.logsData.length,
|
||||
errors: window.logsData.filter(log => log.level.toUpperCase() === 'ERROR').length,
|
||||
warnings: window.logsData.filter(log => log.level.toUpperCase() === 'WARNING').length,
|
||||
info: window.logsData.filter(log => log.level.toUpperCase() === 'INFO').length
|
||||
};
|
||||
|
||||
// Aktualisiere Statistik-Anzeigen falls vorhanden
|
||||
const totalElement = document.getElementById('log-stats-total');
|
||||
const errorsElement = document.getElementById('log-stats-errors');
|
||||
const warningsElement = document.getElementById('log-stats-warnings');
|
||||
const infoElement = document.getElementById('log-stats-info');
|
||||
|
||||
if (totalElement) totalElement.textContent = stats.total;
|
||||
if (errorsElement) errorsElement.textContent = stats.errors;
|
||||
if (warningsElement) warningsElement.textContent = stats.warnings;
|
||||
if (infoElement) infoElement.textContent = stats.info;
|
||||
}
|
||||
|
||||
// Zum Ende der Logs scrollen
|
||||
function scrollLogsToBottom() {
|
||||
const logsContainer = document.getElementById('logs-container');
|
||||
if (logsContainer) {
|
||||
logsContainer.scrollTop = logsContainer.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
// Notification anzeigen
|
||||
function showNotification(message, type = 'info') {
|
||||
// Erstelle Notification-Element falls nicht vorhanden
|
||||
let notification = document.getElementById('admin-notification');
|
||||
if (!notification) {
|
||||
notification = document.createElement('div');
|
||||
notification.id = 'admin-notification';
|
||||
notification.className = 'fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg max-w-sm transition-all duration-300 transform translate-x-full';
|
||||
document.body.appendChild(notification);
|
||||
}
|
||||
|
||||
// Setze Farbe basierend auf Typ
|
||||
const colors = {
|
||||
success: 'bg-green-500 text-white',
|
||||
error: 'bg-red-500 text-white',
|
||||
warning: 'bg-yellow-500 text-white',
|
||||
info: 'bg-blue-500 text-white'
|
||||
};
|
||||
|
||||
notification.className = `fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg max-w-sm transition-all duration-300 ${colors[type] || colors.info}`;
|
||||
notification.textContent = message;
|
||||
|
||||
// Zeige Notification
|
||||
notification.style.transform = 'translateX(0)';
|
||||
|
||||
// Verstecke nach 5 Sekunden
|
||||
setTimeout(() => {
|
||||
notification.style.transform = 'translateX(100%)';
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// Cache leeren
|
||||
async function clearCache() {
|
||||
if (confirm('Möchten Sie wirklich den Cache leeren?')) {
|
||||
showNotification('Cache wird geleert...', 'info');
|
||||
const result = await makeApiCall('/api/admin/cache/clear', 'POST');
|
||||
if (result) {
|
||||
setTimeout(() => location.reload(), 2000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Datenbank optimieren
|
||||
async function optimizeDatabase() {
|
||||
if (confirm('Möchten Sie wirklich die Datenbank optimieren? Dies kann einige Minuten dauern.')) {
|
||||
showNotification('Datenbank wird optimiert...', 'info');
|
||||
const result = await makeApiCall('/api/admin/database/optimize', 'POST');
|
||||
if (result) {
|
||||
setTimeout(() => location.reload(), 2000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Backup erstellen
|
||||
async function createBackup() {
|
||||
if (confirm('Möchten Sie wirklich ein Backup erstellen?')) {
|
||||
showNotification('Backup wird erstellt...', 'info');
|
||||
const result = await makeApiCall('/api/admin/backup/create', 'POST');
|
||||
}
|
||||
}
|
||||
|
||||
// Drucker aktualisieren
|
||||
async function updatePrinters() {
|
||||
if (confirm('Möchten Sie alle Drucker-Verbindungen aktualisieren?')) {
|
||||
showNotification('Drucker werden aktualisiert...', 'info');
|
||||
const result = await makeApiCall('/api/admin/printers/update', 'POST');
|
||||
if (result) {
|
||||
setTimeout(() => location.reload(), 2000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// System neustarten
|
||||
async function restartSystem() {
|
||||
if (confirm('WARNUNG: Möchten Sie wirklich das System neustarten? Alle aktiven Verbindungen werden getrennt.')) {
|
||||
const result = await makeApiCall('/api/admin/system/restart', 'POST');
|
||||
if (result) {
|
||||
showNotification('System wird neugestartet...', 'warning');
|
||||
setTimeout(() => {
|
||||
window.location.href = '/';
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Einstellungen bearbeiten
|
||||
function editSettings() {
|
||||
window.location.href = '/settings';
|
||||
}
|
||||
|
||||
// Systemstatus automatisch aktualisieren
|
||||
async function updateSystemStatus() {
|
||||
if (window.location.search.includes('tab=system')) {
|
||||
const result = await makeApiCall('/api/admin/system/status');
|
||||
if (result) {
|
||||
// Aktualisiere die Anzeige
|
||||
updateStatusDisplay('cpu_usage', result.cpu_usage + '%');
|
||||
updateStatusDisplay('memory_usage', result.memory_usage + '%');
|
||||
updateStatusDisplay('disk_usage', result.disk_usage + '%');
|
||||
updateStatusDisplay('uptime', result.uptime);
|
||||
updateStatusDisplay('db_size', result.db_size);
|
||||
updateStatusDisplay('scheduler_jobs', result.scheduler_jobs);
|
||||
updateStatusDisplay('next_job', result.next_job);
|
||||
|
||||
// Scheduler-Status aktualisieren
|
||||
const schedulerStatus = document.querySelector('.scheduler-status');
|
||||
if (schedulerStatus) {
|
||||
if (result.scheduler_running) {
|
||||
schedulerStatus.innerHTML = '<span class="w-2 h-2 mr-1 rounded-full bg-blue-400 animate-pulse"></span>Läuft';
|
||||
schedulerStatus.className = 'inline-flex items-center px-2 py-1 text-xs font-semibold rounded-full bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200';
|
||||
} else {
|
||||
schedulerStatus.innerHTML = '<span class="w-2 h-2 mr-1 rounded-full bg-red-400"></span>Gestoppt';
|
||||
schedulerStatus.className = 'inline-flex items-center px-2 py-1 text-xs font-semibold rounded-full bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hilfsfunktion zum Aktualisieren der Status-Anzeige
|
||||
function updateStatusDisplay(key, value) {
|
||||
const element = document.querySelector(`[data-status="${key}"]`);
|
||||
if (element) {
|
||||
element.textContent = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Datenbankstatus aktualisieren
|
||||
async function updateDatabaseStatus() {
|
||||
if (window.location.search.includes('tab=system')) {
|
||||
const result = await makeApiCall('/api/admin/database/status');
|
||||
if (result) {
|
||||
const dbStatus = document.querySelector('.database-status');
|
||||
if (dbStatus) {
|
||||
if (result.connected) {
|
||||
dbStatus.innerHTML = '<span class="w-2 h-2 mr-1 rounded-full bg-green-400 animate-pulse"></span>Verbunden';
|
||||
dbStatus.className = 'inline-flex items-center px-2 py-1 text-xs font-semibold rounded-full bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200';
|
||||
} else {
|
||||
dbStatus.innerHTML = '<span class="w-2 h-2 mr-1 rounded-full bg-red-400"></span>Getrennt';
|
||||
dbStatus.className = 'inline-flex items-center px-2 py-1 text-xs font-semibold rounded-full bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200';
|
||||
}
|
||||
}
|
||||
|
||||
updateStatusDisplay('db_size', result.size);
|
||||
updateStatusDisplay('db_connections', result.connected ? 'Aktiv' : 'Getrennt');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-Update alle 30 Sekunden
|
||||
setInterval(() => {
|
||||
updateSystemStatus();
|
||||
updateDatabaseStatus();
|
||||
}, 30000);
|
||||
|
||||
// Initial load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
updateSystemStatus();
|
||||
updateDatabaseStatus();
|
||||
});
|
||||
|
||||
// Export für globale Verwendung
|
||||
window.adminSystem = {
|
||||
clearCache,
|
||||
optimizeDatabase,
|
||||
createBackup,
|
||||
updatePrinters,
|
||||
restartSystem,
|
||||
editSettings,
|
||||
updateSystemStatus,
|
||||
updateDatabaseStatus,
|
||||
loadLogs,
|
||||
renderLogs,
|
||||
updateLogStatistics,
|
||||
scrollLogsToBottom
|
||||
};
|
594
backend/static/js/admin-unified.js
Normal file
594
backend/static/js/admin-unified.js
Normal file
@ -0,0 +1,594 @@
|
||||
/**
|
||||
* Mercedes-Benz MYP Admin Dashboard - Unified JavaScript
|
||||
* Konsolidierte Admin-Funktionalitäten ohne Event-Handler-Konflikte
|
||||
*/
|
||||
|
||||
// Globale Variablen und State-Management
|
||||
class AdminDashboard {
|
||||
constructor() {
|
||||
this.csrfToken = null;
|
||||
this.updateInterval = null;
|
||||
this.eventListenersAttached = false;
|
||||
this.apiBaseUrl = this.detectApiBaseUrl();
|
||||
this.retryCount = 0;
|
||||
this.maxRetries = 3;
|
||||
this.isInitialized = false;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
detectApiBaseUrl() {
|
||||
const currentHost = window.location.hostname;
|
||||
const currentPort = window.location.port;
|
||||
|
||||
if (currentPort === '5000') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return `http://${currentHost}:5000`;
|
||||
}
|
||||
|
||||
init() {
|
||||
if (this.isInitialized) {
|
||||
console.log('🔄 Admin Dashboard bereits initialisiert, überspringe...');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('🚀 Initialisiere Mercedes-Benz MYP Admin Dashboard');
|
||||
|
||||
// CSRF Token setzen
|
||||
this.csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
|
||||
|
||||
// Event-Listener nur einmal registrieren
|
||||
this.attachEventListeners();
|
||||
|
||||
// Live-Updates starten
|
||||
this.startLiveUpdates();
|
||||
|
||||
// Initiale Daten laden
|
||||
this.loadInitialData();
|
||||
|
||||
this.isInitialized = true;
|
||||
console.log('✅ Admin Dashboard erfolgreich initialisiert');
|
||||
}
|
||||
|
||||
attachEventListeners() {
|
||||
if (this.eventListenersAttached) {
|
||||
console.log('⚠️ Event-Listener bereits registriert, überspringe...');
|
||||
return;
|
||||
}
|
||||
|
||||
// System-Action-Buttons mit Event-Delegation
|
||||
this.attachSystemButtons();
|
||||
this.attachUserManagement();
|
||||
this.attachPrinterManagement();
|
||||
this.attachJobManagement();
|
||||
this.attachModalEvents();
|
||||
|
||||
this.eventListenersAttached = true;
|
||||
console.log('📌 Event-Listener erfolgreich registriert');
|
||||
}
|
||||
|
||||
attachSystemButtons() {
|
||||
// System Status Button
|
||||
this.addEventListenerSafe('#system-status-btn', 'click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.showSystemStatus();
|
||||
});
|
||||
|
||||
// Analytics Button
|
||||
this.addEventListenerSafe('#analytics-btn', 'click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.showAnalytics();
|
||||
});
|
||||
|
||||
// Maintenance Button
|
||||
this.addEventListenerSafe('#maintenance-btn', 'click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.showMaintenance();
|
||||
});
|
||||
|
||||
// Cache leeren
|
||||
this.addEventListenerSafe('#clear-cache-btn', 'click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.clearSystemCache();
|
||||
});
|
||||
|
||||
// Datenbank optimieren
|
||||
this.addEventListenerSafe('#optimize-db-btn', 'click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.optimizeDatabase();
|
||||
});
|
||||
|
||||
// Backup erstellen
|
||||
this.addEventListenerSafe('#create-backup-btn', 'click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.createSystemBackup();
|
||||
});
|
||||
|
||||
// Drucker-Initialisierung erzwingen
|
||||
this.addEventListenerSafe('#force-init-printers-btn', 'click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.forceInitializePrinters();
|
||||
});
|
||||
}
|
||||
|
||||
attachUserManagement() {
|
||||
// Neuer Benutzer Button
|
||||
this.addEventListenerSafe('#add-user-btn', 'click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.showUserModal();
|
||||
});
|
||||
|
||||
// Event-Delegation für Benutzer-Aktionen
|
||||
document.addEventListener('click', (e) => {
|
||||
if (e.target.closest('.edit-user-btn')) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const userId = e.target.closest('button').dataset.userId;
|
||||
this.editUser(userId);
|
||||
}
|
||||
|
||||
if (e.target.closest('.delete-user-btn')) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const userId = e.target.closest('button').dataset.userId;
|
||||
const userName = e.target.closest('button').dataset.userName;
|
||||
this.deleteUser(userId, userName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
attachPrinterManagement() {
|
||||
// Drucker hinzufügen Button
|
||||
this.addEventListenerSafe('#add-printer-btn', 'click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.showPrinterModal();
|
||||
});
|
||||
|
||||
// Event-Delegation für Drucker-Aktionen
|
||||
document.addEventListener('click', (e) => {
|
||||
if (e.target.closest('.manage-printer-btn')) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const printerId = e.target.closest('button').dataset.printerId;
|
||||
this.managePrinter(printerId);
|
||||
}
|
||||
|
||||
if (e.target.closest('.settings-printer-btn')) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const printerId = e.target.closest('button').dataset.printerId;
|
||||
this.showPrinterSettings(printerId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
attachJobManagement() {
|
||||
// Event-Delegation für Job-Aktionen
|
||||
document.addEventListener('click', (e) => {
|
||||
if (e.target.closest('.job-action-btn')) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const action = e.target.closest('button').dataset.action;
|
||||
const jobId = e.target.closest('button').dataset.jobId;
|
||||
this.handleJobAction(action, jobId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
attachModalEvents() {
|
||||
// Error-Alert Buttons
|
||||
this.addEventListenerSafe('#fix-errors-btn', 'click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.fixErrors();
|
||||
});
|
||||
|
||||
this.addEventListenerSafe('#dismiss-errors-btn', 'click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.dismissErrors();
|
||||
});
|
||||
|
||||
this.addEventListenerSafe('#view-error-details-btn', 'click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
window.location.href = '/admin-dashboard?tab=logs';
|
||||
});
|
||||
}
|
||||
|
||||
addEventListenerSafe(selector, event, handler) {
|
||||
const element = document.querySelector(selector);
|
||||
if (element && !element.dataset.listenerAttached) {
|
||||
element.addEventListener(event, handler);
|
||||
element.dataset.listenerAttached = 'true';
|
||||
}
|
||||
}
|
||||
|
||||
startLiveUpdates() {
|
||||
if (this.updateInterval) {
|
||||
clearInterval(this.updateInterval);
|
||||
}
|
||||
|
||||
// Statistiken alle 30 Sekunden aktualisieren
|
||||
this.updateInterval = setInterval(() => {
|
||||
this.loadLiveStats();
|
||||
}, 30000);
|
||||
|
||||
// Live-Zeit jede Sekunde aktualisieren
|
||||
setInterval(() => {
|
||||
this.updateLiveTime();
|
||||
}, 1000);
|
||||
|
||||
// System-Health alle 30 Sekunden prüfen
|
||||
setInterval(() => {
|
||||
this.checkSystemHealth();
|
||||
}, 30000);
|
||||
|
||||
console.log('🔄 Live-Updates gestartet');
|
||||
}
|
||||
|
||||
async loadInitialData() {
|
||||
await this.loadLiveStats();
|
||||
await this.checkSystemHealth();
|
||||
}
|
||||
|
||||
async loadLiveStats() {
|
||||
try {
|
||||
const url = `${this.apiBaseUrl}/api/stats`;
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
this.updateStatsDisplay(data);
|
||||
this.retryCount = 0;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Live-Statistiken:', error);
|
||||
this.retryCount++;
|
||||
|
||||
if (this.retryCount <= this.maxRetries) {
|
||||
setTimeout(() => this.loadLiveStats(), 5000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateStatsDisplay(data) {
|
||||
// Sichere Updates mit null-Checks
|
||||
this.updateElement('live-users-count', data.total_users || 0);
|
||||
this.updateElement('live-printers-count', data.total_printers || 0);
|
||||
this.updateElement('live-printers-online', `${data.online_printers || 0} online`);
|
||||
this.updateElement('live-jobs-active', data.active_jobs || 0);
|
||||
this.updateElement('live-jobs-queued', `${data.queued_jobs || 0} in Warteschlange`);
|
||||
this.updateElement('live-success-rate', `${data.success_rate || 0}%`);
|
||||
|
||||
// Progress Bars aktualisieren
|
||||
this.updateProgressBar('users-progress', data.total_users, 20);
|
||||
this.updateProgressBar('printers-progress', data.online_printers, data.total_printers);
|
||||
this.updateProgressBar('jobs-progress', data.active_jobs, 10);
|
||||
this.updateProgressBar('success-progress', data.success_rate, 100);
|
||||
|
||||
console.log('📊 Live-Statistiken aktualisiert');
|
||||
}
|
||||
|
||||
updateElement(elementId, value) {
|
||||
const element = document.getElementById(elementId);
|
||||
if (element) {
|
||||
element.textContent = value;
|
||||
}
|
||||
}
|
||||
|
||||
updateProgressBar(progressId, currentValue, maxValue) {
|
||||
const progressEl = document.getElementById(progressId);
|
||||
if (progressEl && currentValue !== undefined && maxValue > 0) {
|
||||
const percentage = Math.min(100, Math.max(0, (currentValue / maxValue) * 100));
|
||||
progressEl.style.width = `${percentage}%`;
|
||||
}
|
||||
}
|
||||
|
||||
updateLiveTime() {
|
||||
const timeElement = document.getElementById('live-time');
|
||||
if (timeElement) {
|
||||
const now = new Date();
|
||||
timeElement.textContent = now.toLocaleTimeString('de-DE');
|
||||
}
|
||||
}
|
||||
|
||||
// System-Funktionen
|
||||
async showSystemStatus() {
|
||||
console.log('🔧 System Status wird angezeigt');
|
||||
this.showNotification('System Status wird geladen...', 'info');
|
||||
}
|
||||
|
||||
async showAnalytics() {
|
||||
console.log('📈 Analytics wird angezeigt');
|
||||
this.showNotification('Analytics werden geladen...', 'info');
|
||||
}
|
||||
|
||||
async showMaintenance() {
|
||||
console.log('🛠️ Wartung wird angezeigt');
|
||||
const systemTab = document.querySelector('a[href*="tab=system"]');
|
||||
if (systemTab) {
|
||||
systemTab.click();
|
||||
}
|
||||
}
|
||||
|
||||
async clearSystemCache() {
|
||||
if (!confirm('🗑️ Möchten Sie wirklich den System-Cache leeren?')) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.apiBaseUrl}/api/admin/cache/clear`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': this.csrfToken
|
||||
}
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
this.showNotification('✅ Cache erfolgreich geleert!', 'success');
|
||||
setTimeout(() => window.location.reload(), 2000);
|
||||
} else {
|
||||
this.showNotification('❌ Fehler beim Leeren des Cache', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
this.showNotification('❌ Fehler beim Leeren des Cache', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async optimizeDatabase() {
|
||||
if (!confirm('🔧 Möchten Sie die Datenbank optimieren?')) return;
|
||||
|
||||
this.showNotification('🔄 Datenbank wird optimiert...', 'info');
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.apiBaseUrl}/api/admin/database/optimize`, {
|
||||
method: 'POST',
|
||||
headers: { 'X-CSRFToken': this.csrfToken }
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
this.showNotification('✅ Datenbank erfolgreich optimiert!', 'success');
|
||||
} else {
|
||||
this.showNotification('❌ Fehler bei der Datenbank-Optimierung', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
this.showNotification('❌ Fehler bei der Datenbank-Optimierung', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async createSystemBackup() {
|
||||
if (!confirm('💾 Möchten Sie ein System-Backup erstellen?')) return;
|
||||
|
||||
this.showNotification('🔄 Backup wird erstellt...', 'info');
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.apiBaseUrl}/api/admin/backup/create`, {
|
||||
method: 'POST',
|
||||
headers: { 'X-CSRFToken': this.csrfToken }
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
this.showNotification('✅ Backup erfolgreich erstellt!', 'success');
|
||||
} else {
|
||||
this.showNotification('❌ Fehler beim Erstellen des Backups', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
this.showNotification('❌ Fehler beim Erstellen des Backups', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async forceInitializePrinters() {
|
||||
if (!confirm('🔄 Möchten Sie die Drucker-Initialisierung erzwingen?')) return;
|
||||
|
||||
this.showNotification('🔄 Drucker werden initialisiert...', 'info');
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.apiBaseUrl}/api/admin/printers/force-init`, {
|
||||
method: 'POST',
|
||||
headers: { 'X-CSRFToken': this.csrfToken }
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
this.showNotification('✅ Drucker erfolgreich initialisiert!', 'success');
|
||||
setTimeout(() => window.location.reload(), 2000);
|
||||
} else {
|
||||
this.showNotification('❌ Fehler bei der Drucker-Initialisierung', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
this.showNotification('❌ Fehler bei der Drucker-Initialisierung', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// User-Management
|
||||
showUserModal() {
|
||||
console.log('👤 Benutzer-Modal wird angezeigt');
|
||||
this.showNotification('Benutzer-Funktionen werden geladen...', 'info');
|
||||
}
|
||||
|
||||
editUser(userId) {
|
||||
console.log(`✏️ Benutzer ${userId} wird bearbeitet`);
|
||||
this.showNotification(`Benutzer ${userId} wird bearbeitet...`, 'info');
|
||||
}
|
||||
|
||||
async deleteUser(userId, userName) {
|
||||
if (!confirm(`🗑️ Möchten Sie den Benutzer "${userName}" wirklich löschen?`)) return;
|
||||
|
||||
this.showNotification(`🔄 Benutzer "${userName}" wird gelöscht...`, 'info');
|
||||
}
|
||||
|
||||
// Printer-Management
|
||||
showPrinterModal() {
|
||||
console.log('🖨️ Drucker-Modal wird angezeigt');
|
||||
this.showNotification('Drucker-Funktionen werden geladen...', 'info');
|
||||
}
|
||||
|
||||
managePrinter(printerId) {
|
||||
console.log(`🔧 Drucker ${printerId} wird verwaltet`);
|
||||
this.showNotification(`Drucker ${printerId} wird verwaltet...`, 'info');
|
||||
}
|
||||
|
||||
showPrinterSettings(printerId) {
|
||||
console.log(`⚙️ Drucker-Einstellungen ${printerId} werden angezeigt`);
|
||||
this.showNotification(`Drucker-Einstellungen werden geladen...`, 'info');
|
||||
}
|
||||
|
||||
// Job-Management
|
||||
handleJobAction(action, jobId) {
|
||||
console.log(`📋 Job-Aktion "${action}" für Job ${jobId}`);
|
||||
this.showNotification(`Job-Aktion "${action}" wird ausgeführt...`, 'info');
|
||||
}
|
||||
|
||||
// Error-Management
|
||||
async checkSystemHealth() {
|
||||
try {
|
||||
const response = await fetch('/api/admin/system-health');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
this.updateHealthDisplay(data);
|
||||
this.updateErrorAlerts(data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler bei System-Health-Check:', error);
|
||||
}
|
||||
}
|
||||
|
||||
updateHealthDisplay(data) {
|
||||
const statusIndicator = document.getElementById('db-status-indicator');
|
||||
const statusText = document.getElementById('db-status-text');
|
||||
|
||||
if (statusIndicator && statusText) {
|
||||
if (data.health_status === 'critical') {
|
||||
statusIndicator.className = 'w-3 h-3 bg-red-500 rounded-full animate-pulse';
|
||||
statusText.textContent = 'Kritisch';
|
||||
statusText.className = 'text-sm font-medium text-red-600 dark:text-red-400';
|
||||
} else if (data.health_status === 'warning') {
|
||||
statusIndicator.className = 'w-3 h-3 bg-yellow-500 rounded-full animate-pulse';
|
||||
statusText.textContent = 'Warnung';
|
||||
statusText.className = 'text-sm font-medium text-yellow-600 dark:text-yellow-400';
|
||||
} else {
|
||||
statusIndicator.className = 'w-3 h-3 bg-green-400 rounded-full animate-pulse';
|
||||
statusText.textContent = 'Gesund';
|
||||
statusText.className = 'text-sm font-medium text-green-600 dark:text-green-400';
|
||||
}
|
||||
}
|
||||
|
||||
this.updateElement('last-migration', data.last_migration || 'Unbekannt');
|
||||
this.updateElement('schema-integrity', data.schema_integrity || 'Prüfung');
|
||||
this.updateElement('recent-errors-count', data.recent_errors_count || 0);
|
||||
}
|
||||
|
||||
updateErrorAlerts(data) {
|
||||
const alertContainer = document.getElementById('critical-errors-alert');
|
||||
if (!alertContainer) return;
|
||||
|
||||
const allErrors = [...(data.critical_errors || []), ...(data.warnings || [])];
|
||||
|
||||
if (allErrors.length > 0) {
|
||||
alertContainer.classList.remove('hidden');
|
||||
} else {
|
||||
alertContainer.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
async fixErrors() {
|
||||
if (!confirm('🔧 Möchten Sie die automatische Fehlerkorrektur durchführen?')) return;
|
||||
|
||||
this.showNotification('🔄 Fehler werden automatisch behoben...', 'info');
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/admin/fix-errors', {
|
||||
method: 'POST',
|
||||
headers: { 'X-CSRFToken': this.csrfToken }
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
this.showNotification('✅ Automatische Reparatur erfolgreich!', 'success');
|
||||
setTimeout(() => this.checkSystemHealth(), 2000);
|
||||
} else {
|
||||
this.showNotification('❌ Automatische Reparatur fehlgeschlagen', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
this.showNotification('❌ Fehler bei der automatischen Reparatur', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
dismissErrors() {
|
||||
const alertContainer = document.getElementById('critical-errors-alert');
|
||||
if (alertContainer) {
|
||||
alertContainer.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// Notification System
|
||||
showNotification(message, type = 'info') {
|
||||
let notification = document.getElementById('admin-notification');
|
||||
if (!notification) {
|
||||
notification = document.createElement('div');
|
||||
notification.id = 'admin-notification';
|
||||
notification.className = 'fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg max-w-sm transition-all duration-300';
|
||||
document.body.appendChild(notification);
|
||||
}
|
||||
|
||||
const colors = {
|
||||
success: 'bg-green-500 text-white',
|
||||
error: 'bg-red-500 text-white',
|
||||
info: 'bg-blue-500 text-white',
|
||||
warning: 'bg-yellow-500 text-white'
|
||||
};
|
||||
|
||||
notification.className = `fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg max-w-sm transition-all duration-300 ${colors[type]}`;
|
||||
notification.textContent = message;
|
||||
notification.style.transform = 'translateX(0)';
|
||||
|
||||
// Auto-Hide nach 3 Sekunden
|
||||
setTimeout(() => {
|
||||
if (notification) {
|
||||
notification.style.transform = 'translateX(100%)';
|
||||
setTimeout(() => {
|
||||
if (notification && notification.parentNode) {
|
||||
notification.parentNode.removeChild(notification);
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
// Sichere Initialisierung - nur einmal ausführen
|
||||
let adminDashboardInstance = null;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
if (!adminDashboardInstance) {
|
||||
adminDashboardInstance = new AdminDashboard();
|
||||
window.AdminDashboard = adminDashboardInstance;
|
||||
console.log('🎯 Admin Dashboard erfolgreich initialisiert (unified)');
|
||||
}
|
||||
});
|
||||
|
||||
// Export für globalen Zugriff
|
||||
window.AdminDashboard = AdminDashboard;
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user