🎯 Fix: Vollständige Behebung der JavaScript exportStats-Funktion und Admin-System-Optimierungen

 **Stats Export API implementiert**:
- Neuer /api/stats/export Endpunkt für CSV-Download
- Umfassende Systemstatistiken mit Drucker-Details
- Zeitbasierte Metriken und Erfolgsraten-Berechnung
- Sichere Authentifizierung und Fehlerbehandlung

 **API-Datenkompatibilität verbessert**:
- Frontend-Aliases hinzugefügt: online_printers, active_jobs, success_rate
- Einheitliche Datenstruktur für Stats-Anzeige
- Korrekte Erfolgsraten-Berechnung mit Null-Division-Schutz

 **Admin-System erweitert**:
- Erweiterte CRUD-Funktionalität für Benutzerverwaltung
- Verbesserte Template-Integration und Formular-Validierung
- Optimierte Datenbankabfragen und Session-Management

🔧 **Technische Details**:
- CSV-Export mit strukturierten Headers und Zeitstempel
- Defensive Programmierung mit umfassender Fehlerbehandlung
- Performance-optimierte Datenbankabfragen
- Vollständige API-Kompatibilität zu bestehender Frontend-Logik

Das MYP-System ist jetzt vollständig funktionsfähig mit korrekter Statistik-Export-Funktionalität.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-06-20 01:32:01 +02:00
parent 321626e9d3
commit 02d18f7f1e
890 changed files with 3592 additions and 31 deletions

View File

@ -1697,11 +1697,324 @@ class SystemControlManager {
// Globaler System-Control-Manager
let systemControlManager = null;
// ===== BENUTZER-CRUD-FUNKTIONALITÄT =====
class UserManagement {
constructor() {
this.initializeEventListeners();
console.log('👥 User-Management initialisiert');
}
initializeEventListeners() {
// Benutzer hinzufügen Button
const addUserBtn = document.getElementById('add-user-btn');
if (addUserBtn) {
addUserBtn.addEventListener('click', () => {
window.location.href = '/admin/users/add';
});
}
// Benutzer bearbeiten Buttons
document.addEventListener('click', (e) => {
if (e.target.closest('.edit-user-btn')) {
const userId = e.target.closest('.edit-user-btn').dataset.userId;
this.editUser(userId);
}
// Benutzer löschen Buttons
if (e.target.closest('.delete-user-btn')) {
const userId = e.target.closest('.delete-user-btn').dataset.userId;
const userName = e.target.closest('.delete-user-btn').dataset.userName;
this.deleteUser(userId, userName);
}
// Passwort zurücksetzen Buttons
if (e.target.closest('.reset-password-btn')) {
const userId = e.target.closest('.reset-password-btn').dataset.userId;
const userName = e.target.closest('.reset-password-btn').dataset.userName;
this.resetPassword(userId, userName);
}
});
// Rolle/Status Updates durch Inline-Editing
document.addEventListener('change', (e) => {
if (e.target.classList.contains('role-select')) {
const userId = e.target.dataset.userId;
const newRole = e.target.value;
this.updateUserRole(userId, newRole);
}
if (e.target.classList.contains('status-toggle')) {
const userId = e.target.dataset.userId;
const isActive = e.target.checked;
this.updateUserStatus(userId, isActive);
}
});
}
editUser(userId) {
window.location.href = `/admin/users/${userId}/edit`;
}
async deleteUser(userId, userName) {
const confirmed = await this.showConfirmDialog(
'Benutzer löschen',
`Möchten Sie den Benutzer "${userName}" wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.`,
'Löschen',
'danger'
);
if (!confirmed) return;
try {
const form = document.createElement('form');
form.method = 'POST';
form.action = `/admin/users/${userId}/delete`;
const csrfToken = document.createElement('input');
csrfToken.type = 'hidden';
csrfToken.name = 'csrf_token';
csrfToken.value = getCsrfToken();
form.appendChild(csrfToken);
document.body.appendChild(form);
form.submit();
} catch (error) {
console.error('Fehler beim Löschen des Benutzers:', error);
showNotification('Fehler beim Löschen des Benutzers', 'error');
}
}
async resetPassword(userId, userName) {
const newPassword = await this.showPasswordDialog(
'Passwort zurücksetzen',
`Neues Passwort für Benutzer "${userName}"`
);
if (!newPassword) return;
try {
const response = await fetch(`/api/admin/users/${userId}/reset-password`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCsrfToken()
},
body: JSON.stringify({
new_password: newPassword
})
});
const data = await response.json();
if (data.success) {
showNotification(`Passwort für "${userName}" erfolgreich zurückgesetzt`, 'success');
} else {
showNotification(data.error || 'Fehler beim Zurücksetzen des Passworts', 'error');
}
} catch (error) {
console.error('Fehler beim Zurücksetzen des Passworts:', error);
showNotification('Fehler beim Zurücksetzen des Passworts', 'error');
}
}
async updateUserRole(userId, newRole) {
try {
const response = await fetch(`/api/admin/users/${userId}/role`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCsrfToken()
},
body: JSON.stringify({
role: newRole
})
});
const data = await response.json();
if (data.success) {
showNotification('Benutzerrolle erfolgreich aktualisiert', 'success');
// Seite nach kurzer Verzögerung neu laden für UI-Update
setTimeout(() => window.location.reload(), 1500);
} else {
showNotification(data.error || 'Fehler beim Aktualisieren der Rolle', 'error');
// Revert auf alte Auswahl
window.location.reload();
}
} catch (error) {
console.error('Fehler beim Aktualisieren der Rolle:', error);
showNotification('Fehler beim Aktualisieren der Rolle', 'error');
window.location.reload();
}
}
async updateUserStatus(userId, isActive) {
try {
const response = await fetch(`/api/admin/users/${userId}/status`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCsrfToken()
},
body: JSON.stringify({
active: isActive
})
});
const data = await response.json();
if (data.success) {
showNotification(
`Benutzer ${isActive ? 'aktiviert' : 'deaktiviert'}`,
'success'
);
// UI-Update für Status-Text
const statusText = document.querySelector(`[data-user-id="${userId}"] .status-toggle`).nextElementSibling;
if (statusText) {
statusText.textContent = isActive ? 'Aktiv' : 'Inaktiv';
statusText.className = isActive ?
'ml-2 text-xs text-green-700 dark:text-green-400' :
'ml-2 text-xs text-red-700 dark:text-red-400';
}
} else {
showNotification(data.error || 'Fehler beim Aktualisieren des Status', 'error');
// Revert Checkbox
document.querySelector(`[data-user-id="${userId}"] .status-toggle`).checked = !isActive;
}
} catch (error) {
console.error('Fehler beim Aktualisieren des Status:', error);
showNotification('Fehler beim Aktualisieren des Status', 'error');
// Revert Checkbox
document.querySelector(`[data-user-id="${userId}"] .status-toggle`).checked = !isActive;
}
}
showConfirmDialog(title, message, confirmText = 'Bestätigen', type = 'warning') {
return new Promise((resolve) => {
const modal = document.createElement('div');
modal.className = 'fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50';
modal.innerHTML = `
<div class="bg-white dark:bg-slate-800 rounded-xl p-6 max-w-md mx-4 shadow-2xl">
<div class="flex items-center mb-4">
<div class="flex-shrink-0 w-10 h-10 rounded-full flex items-center justify-center ${
type === 'danger' ? 'bg-red-100 dark:bg-red-900' : 'bg-yellow-100 dark:bg-yellow-900'
}">
<svg class="w-6 h-6 ${type === 'danger' ? 'text-red-600' : 'text-yellow-600'}" 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 0L4.268 15.5c-.77.833.192 2.5 1.732 2.5z"/>
</svg>
</div>
<div class="ml-4">
<h3 class="text-lg font-medium text-slate-900 dark:text-white">${title}</h3>
</div>
</div>
<p class="text-slate-600 dark:text-slate-400 mb-6">${message}</p>
<div class="flex space-x-3">
<button id="confirm-btn" class="flex-1 px-4 py-2 ${
type === 'danger' ? 'bg-red-600 hover:bg-red-700' : 'bg-blue-600 hover:bg-blue-700'
} text-white rounded-lg transition-colors">${confirmText}</button>
<button id="cancel-btn" class="flex-1 px-4 py-2 bg-slate-300 dark:bg-slate-600 text-slate-700 dark:text-slate-300 rounded-lg hover:bg-slate-400 dark:hover:bg-slate-500 transition-colors">Abbrechen</button>
</div>
</div>
`;
document.body.appendChild(modal);
modal.querySelector('#confirm-btn').addEventListener('click', () => {
document.body.removeChild(modal);
resolve(true);
});
modal.querySelector('#cancel-btn').addEventListener('click', () => {
document.body.removeChild(modal);
resolve(false);
});
// ESC zum Abbrechen
const handleEsc = (e) => {
if (e.key === 'Escape') {
document.body.removeChild(modal);
document.removeEventListener('keydown', handleEsc);
resolve(false);
}
};
document.addEventListener('keydown', handleEsc);
});
}
showPasswordDialog(title, description) {
return new Promise((resolve) => {
const modal = document.createElement('div');
modal.className = 'fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50';
modal.innerHTML = `
<div class="bg-white dark:bg-slate-800 rounded-xl p-6 max-w-md mx-4 shadow-2xl">
<h3 class="text-lg font-medium text-slate-900 dark:text-white mb-2">${title}</h3>
<p class="text-slate-600 dark:text-slate-400 mb-4">${description}</p>
<input type="password" id="password-input" placeholder="Neues Passwort"
class="w-full px-3 py-2 border border-slate-300 dark:border-slate-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-slate-700 dark:text-white mb-4">
<div class="flex space-x-3">
<button id="set-password-btn" class="flex-1 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors">Setzen</button>
<button id="cancel-password-btn" class="flex-1 px-4 py-2 bg-slate-300 dark:bg-slate-600 text-slate-700 dark:text-slate-300 rounded-lg hover:bg-slate-400 dark:hover:bg-slate-500 transition-colors">Abbrechen</button>
</div>
</div>
`;
document.body.appendChild(modal);
const passwordInput = modal.querySelector('#password-input');
passwordInput.focus();
modal.querySelector('#set-password-btn').addEventListener('click', () => {
const password = passwordInput.value.trim();
document.body.removeChild(modal);
resolve(password || null);
});
modal.querySelector('#cancel-password-btn').addEventListener('click', () => {
document.body.removeChild(modal);
resolve(null);
});
// Enter zum Bestätigen
passwordInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
const password = passwordInput.value.trim();
document.body.removeChild(modal);
resolve(password || null);
}
});
// ESC zum Abbrechen
const handleEsc = (e) => {
if (e.key === 'Escape') {
document.body.removeChild(modal);
document.removeEventListener('keydown', handleEsc);
resolve(null);
}
};
document.addEventListener('keydown', handleEsc);
});
}
}
// Globaler User-Manager
let userManagement = null;
// Initialisierung beim DOM-Laden
document.addEventListener('DOMContentLoaded', function() {
if (!systemControlManager) {
systemControlManager = new SystemControlManager();
}
if (!userManagement) {
userManagement = new UserManagement();
}
});
</script>
{% endblock %}