Projektarbeit-MYP/backend/static/js/admin-dashboard.js
2025-05-31 22:40:29 +02:00

584 lines
20 KiB
JavaScript

/**
* 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
document.getElementById('total-users-count').textContent = data.total_users || 0;
document.getElementById('total-printers-count').textContent = data.total_printers || 0;
document.getElementById('active-jobs-count').textContent = data.active_jobs || 0;
// Update scheduler status
updateSchedulerStatusIndicator(data.scheduler_status || false);
} catch (error) {
console.error('Error loading stats:', error);
showToast('Fehler beim Laden der Statistiken', 'error');
}
}
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');
const data = await response.json();
// Update system stats
document.getElementById('total-print-time').textContent =
formatPrintTime(data.total_print_time_hours);
document.getElementById('completed-jobs-count').textContent =
data.total_jobs_completed || 0;
document.getElementById('total-material-used').textContent =
formatMaterialUsed(data.total_material_used);
document.getElementById('last-updated-time').textContent =
data.last_updated ? formatDateTime(data.last_updated) : '-';
} catch (error) {
console.error('Error loading system status:', error);
showToast('Fehler beim Laden des Systemstatus', 'error');
}
}
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>
`;
});
}