🎉 Improved backend structure & logs organization 🎉
This commit is contained in:
@ -92,17 +92,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
// User-Dropdown-Funktionalität initialisieren
|
||||
initializeUserDropdown();
|
||||
|
||||
// Mobile Menu Toggle initialisieren
|
||||
initializeMobileMenu();
|
||||
|
||||
// MYP App für Offline-Funktionalität initialisieren
|
||||
if (typeof MYPApp !== 'undefined') {
|
||||
window.mypApp = new MYPApp();
|
||||
}
|
||||
|
||||
// User-Dropdown-Funktionalität initialisieren
|
||||
initUserDropdown();
|
||||
|
||||
// Flask Flash Messages über das Glassmorphism-System anzeigen
|
||||
const flashContainer = document.getElementById('flask-flash-messages');
|
||||
if (flashContainer) {
|
||||
@ -733,7 +730,7 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- DND Status und Counter -->
|
||||
<!-- DND Status -->
|
||||
<div id="dndStatus" class="text-xs text-slate-500 dark:text-slate-400">
|
||||
<span class="dnd-status-text">Alle Benachrichtigungen aktiv</span>
|
||||
</div>
|
||||
@ -812,17 +809,14 @@
|
||||
* Initialisierung aller UI-Komponenten nach DOM-Load
|
||||
*/
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// User-Dropdown-Funktionalität initialisieren
|
||||
initializeUserDropdown();
|
||||
|
||||
// Mobile Menu Toggle initialisieren
|
||||
initializeMobileMenu();
|
||||
|
||||
// MYP App für Offline-Funktionalität initialisieren
|
||||
if (typeof MYPApp !== 'undefined') {
|
||||
window.mypApp = new MYPApp();
|
||||
}
|
||||
|
||||
// User-Dropdown-Funktionalität initialisieren
|
||||
initUserDropdown();
|
||||
|
||||
// Flask Flash Messages über das Glassmorphism-System anzeigen
|
||||
const flashContainer = document.getElementById('flask-flash-messages');
|
||||
if (flashContainer) {
|
||||
@ -857,154 +851,139 @@
|
||||
/**
|
||||
* User-Dropdown-Funktionalität
|
||||
*/
|
||||
function initializeUserDropdown() {
|
||||
function initUserDropdown() {
|
||||
const userMenuButton = document.getElementById('user-menu-button');
|
||||
const userDropdown = document.getElementById('user-dropdown');
|
||||
const userMenuContainer = document.getElementById('user-menu-container');
|
||||
|
||||
if (!userMenuButton || !userDropdown) return;
|
||||
console.log('🔍 User-Dropdown Init:', { userMenuButton, userDropdown });
|
||||
|
||||
if (!userMenuButton || !userDropdown) {
|
||||
console.warn('⚠️ User-Dropdown-Elemente nicht gefunden:', {
|
||||
button: !!userMenuButton,
|
||||
dropdown: !!userDropdown
|
||||
});
|
||||
return; // Nicht angemeldet oder Elemente nicht gefunden
|
||||
}
|
||||
|
||||
let isDropdownOpen = false;
|
||||
|
||||
// Toggle-Funktion
|
||||
function toggleDropdown(e) {
|
||||
e.stopPropagation();
|
||||
const isExpanded = userMenuButton.getAttribute('aria-expanded') === 'true';
|
||||
function toggleDropdown(event) {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
if (isExpanded) {
|
||||
closeDropdown();
|
||||
} else {
|
||||
isDropdownOpen = !isDropdownOpen;
|
||||
console.log('🔄 Toggle Dropdown:', isDropdownOpen);
|
||||
|
||||
if (isDropdownOpen) {
|
||||
openDropdown();
|
||||
} else {
|
||||
closeDropdown();
|
||||
}
|
||||
}
|
||||
|
||||
// Dropdown öffnen
|
||||
function openDropdown() {
|
||||
userDropdown.classList.remove('hidden');
|
||||
userMenuButton.setAttribute('aria-expanded', 'true');
|
||||
|
||||
// Animation für bessere UX
|
||||
// Reset any existing styles
|
||||
userDropdown.style.transition = '';
|
||||
userDropdown.style.opacity = '0';
|
||||
userDropdown.style.transform = 'scale(0.95) translateY(-5px)';
|
||||
userDropdown.style.transform = 'translateY(-10px) scale(0.95)';
|
||||
|
||||
// Kleine Verzögerung für Animation
|
||||
// Force reflow
|
||||
userDropdown.offsetHeight;
|
||||
|
||||
// Apply opening animation
|
||||
requestAnimationFrame(() => {
|
||||
userDropdown.style.transition = 'all 0.15s ease-out';
|
||||
userDropdown.style.transition = 'all 0.2s ease-out';
|
||||
userDropdown.style.opacity = '1';
|
||||
userDropdown.style.transform = 'scale(1) translateY(0)';
|
||||
userDropdown.style.transform = 'translateY(0) scale(1)';
|
||||
});
|
||||
|
||||
console.log('✅ Dropdown geöffnet');
|
||||
}
|
||||
|
||||
// Dropdown schließen
|
||||
function closeDropdown() {
|
||||
userDropdown.style.transition = 'all 0.15s ease-in';
|
||||
userDropdown.style.opacity = '0';
|
||||
userDropdown.style.transform = 'scale(0.95) translateY(-5px)';
|
||||
userDropdown.style.transform = 'translateY(-10px) scale(0.95)';
|
||||
|
||||
setTimeout(() => {
|
||||
userDropdown.classList.add('hidden');
|
||||
userMenuButton.setAttribute('aria-expanded', 'false');
|
||||
// Reset inline styles
|
||||
userDropdown.style.transition = '';
|
||||
userDropdown.style.opacity = '';
|
||||
userDropdown.style.transform = '';
|
||||
}, 150);
|
||||
|
||||
isDropdownOpen = false;
|
||||
console.log('❌ Dropdown geschlossen');
|
||||
}
|
||||
|
||||
// Event-Listener
|
||||
// Event-Listener für Button-Click
|
||||
userMenuButton.addEventListener('click', toggleDropdown);
|
||||
|
||||
// Außerhalb des Dropdowns klicken schließt es
|
||||
// Alternative: Auch auf touchstart für mobile Geräte
|
||||
userMenuButton.addEventListener('touchstart', function(e) {
|
||||
e.preventDefault();
|
||||
toggleDropdown(e);
|
||||
}, { passive: false });
|
||||
|
||||
// Event-Listener für Klicks außerhalb des Dropdowns
|
||||
document.addEventListener('click', function(e) {
|
||||
if (!userMenuContainer.contains(e.target)) {
|
||||
closeDropdown();
|
||||
if (!userMenuButton.contains(e.target) && !userDropdown.contains(e.target)) {
|
||||
if (isDropdownOpen) {
|
||||
closeDropdown();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Escape-Taste schließt das Dropdown
|
||||
// Touch-Events für mobile Geräte
|
||||
document.addEventListener('touchstart', function(e) {
|
||||
if (!userMenuButton.contains(e.target) && !userDropdown.contains(e.target)) {
|
||||
if (isDropdownOpen) {
|
||||
closeDropdown();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Escape-Taste zum Schließen
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape' && userMenuButton.getAttribute('aria-expanded') === 'true') {
|
||||
if (e.key === 'Escape' && isDropdownOpen) {
|
||||
closeDropdown();
|
||||
userMenuButton.focus();
|
||||
}
|
||||
});
|
||||
|
||||
// Keyboard-Navigation im Dropdown
|
||||
// Fokus-Management für bessere Accessibility
|
||||
userDropdown.addEventListener('keydown', function(e) {
|
||||
const focusableElements = userDropdown.querySelectorAll('a, button');
|
||||
const currentIndex = Array.from(focusableElements).indexOf(document.activeElement);
|
||||
|
||||
switch(e.key) {
|
||||
case 'ArrowDown':
|
||||
e.preventDefault();
|
||||
const nextIndex = (currentIndex + 1) % focusableElements.length;
|
||||
focusableElements[nextIndex].focus();
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
e.preventDefault();
|
||||
const prevIndex = currentIndex === 0 ? focusableElements.length - 1 : currentIndex - 1;
|
||||
focusableElements[prevIndex].focus();
|
||||
break;
|
||||
case 'Tab':
|
||||
// Tab schließt das Dropdown
|
||||
if (!e.shiftKey && currentIndex === focusableElements.length - 1) {
|
||||
closeDropdown();
|
||||
} else if (e.shiftKey && currentIndex === 0) {
|
||||
closeDropdown();
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Mobile Menu Toggle Funktionalität
|
||||
*/
|
||||
function initializeMobileMenu() {
|
||||
const mobileMenuToggle = document.getElementById('mobileMenuToggle');
|
||||
const mobileMenu = document.getElementById('mobileMenu');
|
||||
|
||||
if (!mobileMenuToggle || !mobileMenu) return;
|
||||
|
||||
mobileMenuToggle.addEventListener('click', function() {
|
||||
const isExpanded = mobileMenuToggle.getAttribute('aria-expanded') === 'true';
|
||||
|
||||
if (isExpanded) {
|
||||
// Menü schließen
|
||||
mobileMenu.classList.add('hidden');
|
||||
mobileMenuToggle.setAttribute('aria-expanded', 'false');
|
||||
mobileMenuToggle.setAttribute('aria-label', 'Menü öffnen');
|
||||
if (e.key === 'Tab') {
|
||||
const focusableElements = userDropdown.querySelectorAll('a, button');
|
||||
const firstElement = focusableElements[0];
|
||||
const lastElement = focusableElements[focusableElements.length - 1];
|
||||
|
||||
// Icon zu Hamburger ändern
|
||||
mobileMenuToggle.innerHTML = `
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
|
||||
</svg>
|
||||
`;
|
||||
} else {
|
||||
// Menü öffnen
|
||||
mobileMenu.classList.remove('hidden');
|
||||
mobileMenuToggle.setAttribute('aria-expanded', 'true');
|
||||
mobileMenuToggle.setAttribute('aria-label', 'Menü schließen');
|
||||
|
||||
// Icon zu X ändern
|
||||
mobileMenuToggle.innerHTML = `
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
`;
|
||||
if (e.shiftKey && document.activeElement === firstElement) {
|
||||
e.preventDefault();
|
||||
lastElement.focus();
|
||||
} else if (!e.shiftKey && document.activeElement === lastElement) {
|
||||
e.preventDefault();
|
||||
firstElement.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Mobile Menu bei Resize auf Desktop schließen
|
||||
// Window resize handler
|
||||
window.addEventListener('resize', function() {
|
||||
if (window.innerWidth >= 1024) { // lg breakpoint
|
||||
mobileMenu.classList.add('hidden');
|
||||
mobileMenuToggle.setAttribute('aria-expanded', 'false');
|
||||
mobileMenuToggle.setAttribute('aria-label', 'Menü öffnen');
|
||||
|
||||
// Icon zurück zu Hamburger
|
||||
mobileMenuToggle.innerHTML = `
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
|
||||
</svg>
|
||||
`;
|
||||
if (isDropdownOpen) {
|
||||
closeDropdown();
|
||||
}
|
||||
});
|
||||
|
||||
console.log('✅ User-Dropdown erfolgreich initialisiert');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -357,6 +357,155 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Timer Management Section -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||
<!-- Session Timer -->
|
||||
<div class="dashboard-card p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="section-title mb-0">Session-Timer</h2>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="mb-status-indicator mb-status-idle" id="session-timer-status"></div>
|
||||
<span class="text-sm text-slate-500 dark:text-slate-400">Gestoppt</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Timer Container -->
|
||||
<div id="session-countdown-timer" class="mb-6"></div>
|
||||
|
||||
<!-- Timer Configuration -->
|
||||
<div class="bg-gray-50 dark:bg-slate-700/30 rounded-lg p-4 mb-4">
|
||||
<h4 class="text-sm font-medium text-slate-900 dark:text-white mb-3">Timer-Einstellungen</h4>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-xs text-slate-500 dark:text-slate-400 mb-1">Dauer (Minuten)</label>
|
||||
<select id="session-duration" class="w-full px-3 py-2 text-sm border border-gray-300 dark:border-slate-600 rounded-md bg-white dark:bg-slate-800 text-slate-900 dark:text-white">
|
||||
<option value="30">30 Minuten</option>
|
||||
<option value="60">1 Stunde</option>
|
||||
<option value="120" selected>2 Stunden</option>
|
||||
<option value="240">4 Stunden</option>
|
||||
<option value="480">8 Stunden</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-slate-500 dark:text-slate-400 mb-1">Force-Quit Aktion</label>
|
||||
<select id="session-force-quit" class="w-full px-3 py-2 text-sm border border-gray-300 dark:border-slate-600 rounded-md bg-white dark:bg-slate-800 text-slate-900 dark:text-white">
|
||||
<option value="logout" selected>Automatisch abmelden</option>
|
||||
<option value="warning">Nur warnen</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<label class="block text-xs text-slate-500 dark:text-slate-400 mb-1">Warnung (Sekunden vor Ablauf)</label>
|
||||
<input type="number" id="session-warning" value="60" min="10" max="300" class="w-full px-3 py-2 text-sm border border-gray-300 dark:border-slate-600 rounded-md bg-white dark:bg-slate-800 text-slate-900 dark:text-white">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button id="create-session-timer" class="btn-primary text-sm px-4 py-2">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
|
||||
</svg>
|
||||
Timer erstellen
|
||||
</button>
|
||||
<button id="extend-session-5min" class="btn-secondary text-sm px-4 py-2">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
+5 Min
|
||||
</button>
|
||||
<button id="extend-session-15min" class="btn-secondary text-sm px-4 py-2">
|
||||
+15 Min
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Kiosk Timer (Admin Only) -->
|
||||
{% if current_user.is_admin %}
|
||||
<div class="dashboard-card p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="section-title mb-0">Kiosk-Timer</h2>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="mb-status-indicator mb-status-idle" id="kiosk-timer-status"></div>
|
||||
<span class="text-sm text-slate-500 dark:text-slate-400">Inaktiv</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Timer Container -->
|
||||
<div id="kiosk-countdown-timer" class="mb-6"></div>
|
||||
|
||||
<!-- Admin Configuration -->
|
||||
<div class="bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-lg p-4 mb-4">
|
||||
<div class="flex items-center mb-3">
|
||||
<svg class="w-5 h-5 text-amber-600 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<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-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z" />
|
||||
</svg>
|
||||
<h4 class="text-sm font-medium text-amber-800 dark:text-amber-200">Administrator-Funktion</h4>
|
||||
</div>
|
||||
<p class="text-xs text-amber-700 dark:text-amber-300 mb-3">Kiosk-Timer gelten systemweit für alle Benutzer und führen bei Ablauf automatische Aktionen aus.</p>
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-xs text-amber-700 dark:text-amber-300 mb-1">Dauer (Minuten)</label>
|
||||
<select id="kiosk-duration" class="w-full px-3 py-2 text-sm border border-amber-300 dark:border-amber-700 rounded-md bg-white dark:bg-slate-800 text-slate-900 dark:text-white">
|
||||
<option value="15">15 Minuten</option>
|
||||
<option value="30" selected>30 Minuten</option>
|
||||
<option value="60">1 Stunde</option>
|
||||
<option value="120">2 Stunden</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-amber-700 dark:text-amber-300 mb-1">System-Aktion</label>
|
||||
<select id="kiosk-action" class="w-full px-3 py-2 text-sm border border-amber-300 dark:border-amber-700 rounded-md bg-white dark:bg-slate-800 text-slate-900 dark:text-white">
|
||||
<option value="logout" selected>Alle Benutzer abmelden</option>
|
||||
<option value="restart">System neustarten</option>
|
||||
<option value="shutdown">System herunterfahren</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Admin Actions -->
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button id="create-kiosk-timer" class="btn-warning text-sm px-4 py-2">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2zm10-10V7a4 4 0 00-8 0v4h8z" />
|
||||
</svg>
|
||||
Kiosk-Timer starten
|
||||
</button>
|
||||
<button id="stop-kiosk-timer" class="btn-danger text-sm px-4 py-2">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 10a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z" />
|
||||
</svg>
|
||||
Stoppen
|
||||
</button>
|
||||
<button id="force-quit-now" class="btn-danger text-sm px-4 py-2">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<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-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z" />
|
||||
</svg>
|
||||
Force-Quit jetzt
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<!-- Timer Overview for Non-Admin -->
|
||||
<div class="dashboard-card p-6">
|
||||
<h2 class="section-title">Timer-Übersicht</h2>
|
||||
<div id="timer-overview" class="space-y-4">
|
||||
<!-- Dynamic timer list will be populated here -->
|
||||
<div class="text-center py-8">
|
||||
<svg class="w-12 h-12 mx-auto mb-3 text-slate-400 dark:text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<p class="text-slate-700 dark:text-slate-300 font-medium mb-1">Keine aktiven Timer</p>
|
||||
<p class="text-slate-500 dark:text-slate-400 text-sm">Erstellen Sie einen Session-Timer für automatische Verwaltung.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Active Jobs Section -->
|
||||
<div class="dashboard-card p-6">
|
||||
<h2 class="section-title">Aktuelle Druckaufträge</h2>
|
||||
@ -498,6 +647,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script src="{{ url_for('static', filename='js/countdown-timer.js') }}"></script>
|
||||
<script>
|
||||
class DashboardManager {
|
||||
constructor() {
|
||||
@ -941,12 +1091,512 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
window.dashboardManager = new DashboardManager();
|
||||
|
||||
// Timer-Funktionalität initialisieren
|
||||
initializeTimerControls();
|
||||
|
||||
// Cleanup beim Verlassen
|
||||
window.addEventListener('beforeunload', () => {
|
||||
if (window.dashboardManager) {
|
||||
window.dashboardManager.cleanup();
|
||||
}
|
||||
// Timer cleanup
|
||||
if (window.TimerManager) {
|
||||
window.TimerManager.destroyAll();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Initialisiert die Timer-Steuerung im Dashboard
|
||||
*/
|
||||
function initializeTimerControls() {
|
||||
console.log('🕒 Timer-Steuerung wird initialisiert...');
|
||||
|
||||
// Session-Timer Ereignisse
|
||||
setupSessionTimerEvents();
|
||||
|
||||
// Kiosk-Timer Ereignisse (nur für Admins)
|
||||
if (document.getElementById('create-kiosk-timer')) {
|
||||
setupKioskTimerEvents();
|
||||
}
|
||||
|
||||
// Bestehende Timer laden
|
||||
loadExistingTimers();
|
||||
|
||||
// Timer-Status regelmäßig aktualisieren
|
||||
setInterval(updateTimerStatus, 5000);
|
||||
|
||||
console.log('✅ Timer-Steuerung erfolgreich initialisiert');
|
||||
}
|
||||
|
||||
/**
|
||||
* Konfiguriert Session-Timer Ereignisse
|
||||
*/
|
||||
function setupSessionTimerEvents() {
|
||||
// Timer erstellen
|
||||
const createBtn = document.getElementById('create-session-timer');
|
||||
if (createBtn) {
|
||||
createBtn.addEventListener('click', async function() {
|
||||
const duration = parseInt(document.getElementById('session-duration').value);
|
||||
const forceQuitAction = document.getElementById('session-force-quit').value;
|
||||
const warningThreshold = parseInt(document.getElementById('session-warning').value);
|
||||
|
||||
await createSessionTimer(duration, forceQuitAction, warningThreshold);
|
||||
});
|
||||
}
|
||||
|
||||
// Timer verlängern - 5 Minuten
|
||||
const extend5Btn = document.getElementById('extend-session-5min');
|
||||
if (extend5Btn) {
|
||||
extend5Btn.addEventListener('click', async function() {
|
||||
await extendSessionTimer(300); // 5 Minuten = 300 Sekunden
|
||||
});
|
||||
}
|
||||
|
||||
// Timer verlängern - 15 Minuten
|
||||
const extend15Btn = document.getElementById('extend-session-15min');
|
||||
if (extend15Btn) {
|
||||
extend15Btn.addEventListener('click', async function() {
|
||||
await extendSessionTimer(900); // 15 Minuten = 900 Sekunden
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Konfiguriert Kiosk-Timer Ereignisse (Admin)
|
||||
*/
|
||||
function setupKioskTimerEvents() {
|
||||
// Kiosk-Timer erstellen
|
||||
const createKioskBtn = document.getElementById('create-kiosk-timer');
|
||||
if (createKioskBtn) {
|
||||
createKioskBtn.addEventListener('click', async function() {
|
||||
const duration = parseInt(document.getElementById('kiosk-duration').value);
|
||||
const action = document.getElementById('kiosk-action').value;
|
||||
|
||||
await createKioskTimer(duration, action);
|
||||
});
|
||||
}
|
||||
|
||||
// Kiosk-Timer stoppen
|
||||
const stopKioskBtn = document.getElementById('stop-kiosk-timer');
|
||||
if (stopKioskBtn) {
|
||||
stopKioskBtn.addEventListener('click', async function() {
|
||||
await stopKioskTimer();
|
||||
});
|
||||
}
|
||||
|
||||
// Force-Quit sofort ausführen
|
||||
const forceQuitBtn = document.getElementById('force-quit-now');
|
||||
if (forceQuitBtn) {
|
||||
forceQuitBtn.addEventListener('click', async function() {
|
||||
if (confirm('⚠️ Force-Quit wird sofort ausgeführt. Alle Benutzer werden abgemeldet. Fortfahren?')) {
|
||||
await executeForceQuitNow();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt einen Session-Timer
|
||||
*/
|
||||
async function createSessionTimer(durationMinutes, forceQuitAction, warningThreshold) {
|
||||
try {
|
||||
showTimerLoading('session');
|
||||
|
||||
// API-Aufruf zur Timer-Erstellung
|
||||
const response = await fetch('/api/timers/session/create', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': getCSRFToken()
|
||||
},
|
||||
body: JSON.stringify({
|
||||
duration_minutes: durationMinutes
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
// Timer-UI erstellen
|
||||
const timer = window.TimerManager.create('session_timer', {
|
||||
container: 'session-countdown-timer',
|
||||
duration: durationMinutes * 60,
|
||||
forceQuitAction: forceQuitAction,
|
||||
warningThreshold: warningThreshold,
|
||||
syncWithServer: true,
|
||||
apiBase: '/api/timers/session_timer_' + getCurrentUserId(),
|
||||
size: 'medium',
|
||||
theme: 'primary',
|
||||
warningMessage: 'Ihre Session läuft ab! Speichern Sie Ihre Arbeit.',
|
||||
autoStart: true,
|
||||
|
||||
// Callbacks
|
||||
onTick: (remaining, total) => {
|
||||
updateTimerStatusIndicator('session', 'running');
|
||||
},
|
||||
onWarning: (remaining) => {
|
||||
showTimerWarning('Session läuft in ' + Math.floor(remaining / 60) + ' Minuten ab!');
|
||||
updateTimerStatusIndicator('session', 'warning');
|
||||
},
|
||||
onExpired: () => {
|
||||
updateTimerStatusIndicator('session', 'expired');
|
||||
if (forceQuitAction === 'logout') {
|
||||
showLogoutWarning();
|
||||
}
|
||||
},
|
||||
onForceQuit: () => {
|
||||
handleSessionForceQuit(forceQuitAction);
|
||||
}
|
||||
});
|
||||
|
||||
showTimerSuccess('Session-Timer erfolgreich erstellt und gestartet');
|
||||
updateSessionTimerControls(true);
|
||||
|
||||
} else {
|
||||
throw new Error(result.error || 'Timer konnte nicht erstellt werden');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Erstellen des Session-Timers:', error);
|
||||
showTimerError('Session-Timer konnte nicht erstellt werden: ' + error.message);
|
||||
} finally {
|
||||
hideTimerLoading('session');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt einen Kiosk-Timer (Admin)
|
||||
*/
|
||||
async function createKioskTimer(durationMinutes, action) {
|
||||
try {
|
||||
showTimerLoading('kiosk');
|
||||
|
||||
const response = await fetch('/api/timers/kiosk/create', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': getCSRFToken()
|
||||
},
|
||||
body: JSON.stringify({
|
||||
duration_minutes: durationMinutes,
|
||||
auto_start: true
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
// Kiosk-Timer UI erstellen
|
||||
const timer = window.TimerManager.create('kiosk_timer', {
|
||||
container: 'kiosk-countdown-timer',
|
||||
duration: durationMinutes * 60,
|
||||
forceQuitAction: action,
|
||||
warningThreshold: 30,
|
||||
syncWithServer: true,
|
||||
apiBase: '/api/timers/kiosk_session',
|
||||
size: 'medium',
|
||||
theme: 'warning',
|
||||
warningMessage: '⚠️ Kiosk-Session läuft ab! System wird automatisch ' + getActionText(action) + '.',
|
||||
autoStart: true,
|
||||
showControls: true,
|
||||
|
||||
onTick: (remaining, total) => {
|
||||
updateTimerStatusIndicator('kiosk', 'running');
|
||||
broadcastKioskTimerUpdate(remaining, total);
|
||||
},
|
||||
onWarning: (remaining) => {
|
||||
showKioskWarning('Kiosk-Timer läuft in ' + Math.floor(remaining / 60) + ' Minuten ab!');
|
||||
updateTimerStatusIndicator('kiosk', 'warning');
|
||||
},
|
||||
onExpired: () => {
|
||||
updateTimerStatusIndicator('kiosk', 'expired');
|
||||
executeKioskAction(action);
|
||||
}
|
||||
});
|
||||
|
||||
showTimerSuccess('Kiosk-Timer erfolgreich gestartet');
|
||||
updateKioskTimerControls(true);
|
||||
|
||||
} else {
|
||||
throw new Error(result.error || 'Kiosk-Timer konnte nicht erstellt werden');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Erstellen des Kiosk-Timers:', error);
|
||||
showTimerError('Kiosk-Timer konnte nicht erstellt werden: ' + error.message);
|
||||
} finally {
|
||||
hideTimerLoading('kiosk');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verlängert Session-Timer
|
||||
*/
|
||||
async function extendSessionTimer(additionalSeconds) {
|
||||
try {
|
||||
const timer = window.TimerManager.get('session_timer');
|
||||
if (!timer) {
|
||||
showTimerError('Kein aktiver Session-Timer gefunden');
|
||||
return;
|
||||
}
|
||||
|
||||
const success = await timer.extend(additionalSeconds);
|
||||
if (success) {
|
||||
const minutes = Math.floor(additionalSeconds / 60);
|
||||
showTimerSuccess(`Session-Timer um ${minutes} Minuten verlängert`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Verlängern des Session-Timers:', error);
|
||||
showTimerError('Timer konnte nicht verlängert werden');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stoppt Kiosk-Timer
|
||||
*/
|
||||
async function stopKioskTimer() {
|
||||
try {
|
||||
const timer = window.TimerManager.get('kiosk_timer');
|
||||
if (!timer) {
|
||||
showTimerError('Kein aktiver Kiosk-Timer gefunden');
|
||||
return;
|
||||
}
|
||||
|
||||
const success = await timer.stop();
|
||||
if (success) {
|
||||
showTimerSuccess('Kiosk-Timer gestoppt');
|
||||
updateKioskTimerControls(false);
|
||||
updateTimerStatusIndicator('kiosk', 'stopped');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Stoppen des Kiosk-Timers:', error);
|
||||
showTimerError('Kiosk-Timer konnte nicht gestoppt werden');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Führt Force-Quit sofort aus
|
||||
*/
|
||||
async function executeForceQuitNow() {
|
||||
try {
|
||||
const response = await fetch('/api/timers/kiosk_session/force-quit', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': getCSRFToken()
|
||||
}
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
showTimerSuccess('Force-Quit wird ausgeführt...');
|
||||
|
||||
// Je nach Aktion unterschiedlich behandeln
|
||||
if (result.action === 'logout') {
|
||||
setTimeout(() => {
|
||||
window.location.href = result.redirect_url || '/login';
|
||||
}, 2000);
|
||||
} else {
|
||||
showTimerSuccess('Force-Quit-Aktion ausgeführt: ' + result.action);
|
||||
}
|
||||
} else {
|
||||
throw new Error(result.error || 'Force-Quit fehlgeschlagen');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Force-Quit:', error);
|
||||
showTimerError('Force-Quit konnte nicht ausgeführt werden');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt bestehende Timer beim Seitenaufruf
|
||||
*/
|
||||
async function loadExistingTimers() {
|
||||
try {
|
||||
const response = await fetch('/api/timers');
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success && result.data) {
|
||||
result.data.forEach(timerData => {
|
||||
if (timerData.status === 'running') {
|
||||
recreateTimerFromData(timerData);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden bestehender Timer:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recreiert Timer aus Server-Daten
|
||||
*/
|
||||
function recreateTimerFromData(timerData) {
|
||||
const isSessionTimer = timerData.timer_type === 'session';
|
||||
const isKioskTimer = timerData.timer_type === 'kiosk';
|
||||
|
||||
if (isSessionTimer && timerData.context_id === getCurrentUserId()) {
|
||||
// Session-Timer für aktuellen Benutzer recreieren
|
||||
const timer = window.TimerManager.create('session_timer', {
|
||||
container: 'session-countdown-timer',
|
||||
duration: timerData.duration_seconds,
|
||||
remaining: timerData.remaining_seconds,
|
||||
forceQuitAction: timerData.force_quit_action,
|
||||
warningThreshold: timerData.force_quit_warning_seconds,
|
||||
syncWithServer: true,
|
||||
autoStart: false, // Läuft bereits
|
||||
size: 'medium',
|
||||
theme: 'primary'
|
||||
});
|
||||
|
||||
updateTimerStatusIndicator('session', 'running');
|
||||
updateSessionTimerControls(true);
|
||||
|
||||
} else if (isKioskTimer && isCurrentUserAdmin()) {
|
||||
// Kiosk-Timer für Admin recreieren
|
||||
const timer = window.TimerManager.create('kiosk_timer', {
|
||||
container: 'kiosk-countdown-timer',
|
||||
duration: timerData.duration_seconds,
|
||||
remaining: timerData.remaining_seconds,
|
||||
forceQuitAction: timerData.force_quit_action,
|
||||
warningThreshold: timerData.force_quit_warning_seconds,
|
||||
syncWithServer: true,
|
||||
autoStart: false, // Läuft bereits
|
||||
size: 'medium',
|
||||
theme: 'warning'
|
||||
});
|
||||
|
||||
updateTimerStatusIndicator('kiosk', 'running');
|
||||
updateKioskTimerControls(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktualisiert Timer-Status regelmäßig
|
||||
*/
|
||||
async function updateTimerStatus() {
|
||||
// Implementierung für regelmäßige Status-Updates
|
||||
const sessionTimer = window.TimerManager.get('session_timer');
|
||||
const kioskTimer = window.TimerManager.get('kiosk_timer');
|
||||
|
||||
if (sessionTimer && sessionTimer.config.syncWithServer) {
|
||||
sessionTimer.syncWithServer();
|
||||
}
|
||||
|
||||
if (kioskTimer && kioskTimer.config.syncWithServer) {
|
||||
kioskTimer.syncWithServer();
|
||||
}
|
||||
}
|
||||
|
||||
// Hilfsfunktionen
|
||||
function updateTimerStatusIndicator(timerType, status) {
|
||||
const indicator = document.getElementById(`${timerType}-timer-status`);
|
||||
const statusText = indicator?.nextElementSibling;
|
||||
|
||||
if (indicator) {
|
||||
indicator.className = `mb-status-indicator ${getTimerStatusClass(status)}`;
|
||||
}
|
||||
|
||||
if (statusText) {
|
||||
statusText.textContent = getTimerStatusText(status);
|
||||
}
|
||||
}
|
||||
|
||||
function getTimerStatusClass(status) {
|
||||
const classes = {
|
||||
'running': 'mb-status-busy',
|
||||
'stopped': 'mb-status-idle',
|
||||
'warning': 'mb-status-busy',
|
||||
'expired': 'mb-status-offline'
|
||||
};
|
||||
return classes[status] || 'mb-status-idle';
|
||||
}
|
||||
|
||||
function getTimerStatusText(status) {
|
||||
const texts = {
|
||||
'running': 'Läuft',
|
||||
'stopped': 'Gestoppt',
|
||||
'warning': 'Warnung',
|
||||
'expired': 'Abgelaufen'
|
||||
};
|
||||
return texts[status] || 'Unbekannt';
|
||||
}
|
||||
|
||||
function updateSessionTimerControls(active) {
|
||||
const createBtn = document.getElementById('create-session-timer');
|
||||
const extendBtns = [
|
||||
document.getElementById('extend-session-5min'),
|
||||
document.getElementById('extend-session-15min')
|
||||
];
|
||||
|
||||
if (createBtn) createBtn.disabled = active;
|
||||
extendBtns.forEach(btn => {
|
||||
if (btn) btn.disabled = !active;
|
||||
});
|
||||
}
|
||||
|
||||
function updateKioskTimerControls(active) {
|
||||
const createBtn = document.getElementById('create-kiosk-timer');
|
||||
const stopBtn = document.getElementById('stop-kiosk-timer');
|
||||
const forceQuitBtn = document.getElementById('force-quit-now');
|
||||
|
||||
if (createBtn) createBtn.disabled = active;
|
||||
if (stopBtn) stopBtn.disabled = !active;
|
||||
if (forceQuitBtn) forceQuitBtn.disabled = !active;
|
||||
}
|
||||
|
||||
function showTimerLoading(timerType) {
|
||||
// Implementierung für Loading-Anzeige
|
||||
}
|
||||
|
||||
function hideTimerLoading(timerType) {
|
||||
// Implementierung für Loading-Anzeige ausblenden
|
||||
}
|
||||
|
||||
function showTimerSuccess(message) {
|
||||
if (window.dashboardManager) {
|
||||
window.dashboardManager.showToast(message, 'success');
|
||||
}
|
||||
}
|
||||
|
||||
function showTimerError(message) {
|
||||
if (window.dashboardManager) {
|
||||
window.dashboardManager.showToast(message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function showTimerWarning(message) {
|
||||
if (window.dashboardManager) {
|
||||
window.dashboardManager.showToast(message, 'warning');
|
||||
}
|
||||
}
|
||||
|
||||
function getActionText(action) {
|
||||
const actions = {
|
||||
'logout': 'abgemeldet',
|
||||
'restart': 'neugestartet',
|
||||
'shutdown': 'heruntergefahren'
|
||||
};
|
||||
return actions[action] || action;
|
||||
}
|
||||
|
||||
function getCurrentUserId() {
|
||||
return {{ current_user.id }};
|
||||
}
|
||||
|
||||
function isCurrentUserAdmin() {
|
||||
return {{ 'true' if current_user.is_admin else 'false' }};
|
||||
}
|
||||
|
||||
function getCSRFToken() {
|
||||
const token = document.querySelector('meta[name="csrf-token"]');
|
||||
return token ? token.getAttribute('content') : '';
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
Reference in New Issue
Block a user