🎉 Improved logging system & documentation updates 🎉

This commit is contained in:
2025-06-01 03:31:48 +02:00
parent 09462724e0
commit 941ee21c76
57 changed files with 3743 additions and 668 deletions

View File

@ -357,155 +357,6 @@
</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>
@ -647,7 +498,6 @@
{% endblock %}
{% block extra_js %}
<script src="{{ url_for('static', filename='js/countdown-timer.js') }}"></script>
<script>
class DashboardManager {
constructor() {
@ -1091,512 +941,12 @@
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 %}