584 lines
20 KiB
JavaScript
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>
|
|
`;
|
|
});
|
|
} |