📚 Improved codebase structure & logging enhancements 🚀
This commit is contained in:
@ -1079,7 +1079,7 @@
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.2),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
|
||||
.notification.show {
|
||||
@apply translate-x-0 opacity-100;
|
||||
}
|
||||
@ -1100,7 +1100,7 @@
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.3),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
|
||||
.notification-success {
|
||||
@apply text-green-100;
|
||||
background: linear-gradient(135deg,
|
||||
@ -1114,7 +1114,7 @@
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.4),
|
||||
0 0 0 1px rgba(34, 197, 94, 0.3);
|
||||
}
|
||||
|
||||
|
||||
.notification-error {
|
||||
@apply text-red-100;
|
||||
background: linear-gradient(135deg,
|
||||
@ -1128,7 +1128,7 @@
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.4),
|
||||
0 0 0 1px rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
|
||||
|
||||
.notification-warning {
|
||||
@apply text-yellow-100;
|
||||
background: linear-gradient(135deg,
|
||||
@ -1142,7 +1142,7 @@
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.4),
|
||||
0 0 0 1px rgba(245, 158, 11, 0.3);
|
||||
}
|
||||
|
||||
|
||||
.notification-info {
|
||||
@apply text-blue-100;
|
||||
background: linear-gradient(135deg,
|
||||
@ -1209,7 +1209,7 @@
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.15),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
|
||||
.alert-success {
|
||||
@apply text-green-900 dark:text-green-100;
|
||||
background: linear-gradient(135deg,
|
||||
@ -1218,7 +1218,7 @@
|
||||
rgba(34, 197, 94, 0.08) 100%);
|
||||
border: 1px solid rgba(34, 197, 94, 0.3);
|
||||
}
|
||||
|
||||
|
||||
.alert-error {
|
||||
@apply text-red-900 dark:text-red-100;
|
||||
background: linear-gradient(135deg,
|
||||
@ -1227,7 +1227,7 @@
|
||||
rgba(239, 68, 68, 0.08) 100%);
|
||||
border: 1px solid rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
|
||||
|
||||
.alert-warning {
|
||||
@apply text-yellow-900 dark:text-yellow-100;
|
||||
background: linear-gradient(135deg,
|
||||
@ -1236,7 +1236,7 @@
|
||||
rgba(245, 158, 11, 0.08) 100%);
|
||||
border: 1px solid rgba(245, 158, 11, 0.3);
|
||||
}
|
||||
|
||||
|
||||
.alert-info {
|
||||
@apply text-blue-900 dark:text-blue-100;
|
||||
background: linear-gradient(135deg,
|
||||
|
2
backend/static/css/tailwind.min.css
vendored
2
backend/static/css/tailwind.min.css
vendored
File diff suppressed because one or more lines are too long
@ -422,20 +422,298 @@ class AdminDashboard {
|
||||
}
|
||||
|
||||
// User-Management
|
||||
showUserModal() {
|
||||
console.log('👤 Benutzer-Modal wird angezeigt');
|
||||
this.showNotification('Benutzer-Funktionen werden geladen...', 'info');
|
||||
showUserModal(userId = null) {
|
||||
const isEdit = userId !== null;
|
||||
const title = isEdit ? 'Benutzer bearbeiten' : 'Neuer Benutzer';
|
||||
|
||||
// Modal HTML erstellen
|
||||
const modalHtml = `
|
||||
<div id="user-modal" class="fixed inset-0 bg-black/60 backdrop-blur-sm z-50 flex items-center justify-center p-4">
|
||||
<div class="bg-white dark:bg-slate-800 rounded-2xl p-8 max-w-md w-full shadow-2xl transform scale-100 transition-all duration-300">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h3 class="text-2xl font-bold text-slate-900 dark:text-white">${title}</h3>
|
||||
<button onclick="this.closest('#user-modal').remove()" class="p-2 hover:bg-gray-100 dark:hover:bg-slate-700 rounded-lg transition-colors">
|
||||
<svg class="w-6 h-6 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form id="user-form" class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">E-Mail-Adresse *</label>
|
||||
<input type="email" name="email" id="user-email" required
|
||||
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl
|
||||
focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all
|
||||
dark:bg-slate-700 dark:text-white bg-slate-50">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">Benutzername</label>
|
||||
<input type="text" name="username" id="user-username"
|
||||
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl
|
||||
focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all
|
||||
dark:bg-slate-700 dark:text-white bg-slate-50">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">Name</label>
|
||||
<input type="text" name="name" id="user-name"
|
||||
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl
|
||||
focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all
|
||||
dark:bg-slate-700 dark:text-white bg-slate-50">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">Passwort ${isEdit ? '(leer lassen für keine Änderung)' : '*'}</label>
|
||||
<input type="password" name="password" id="user-password" ${!isEdit ? 'required' : ''}
|
||||
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl
|
||||
focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all
|
||||
dark:bg-slate-700 dark:text-white bg-slate-50">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">Rolle</label>
|
||||
<select name="role" id="user-role"
|
||||
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl
|
||||
focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all
|
||||
dark:bg-slate-700 dark:text-white bg-slate-50">
|
||||
<option value="user">Benutzer</option>
|
||||
<option value="admin">Administrator</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
${isEdit ? `
|
||||
<div class="flex items-center space-x-2">
|
||||
<input type="checkbox" name="is_active" id="user-active"
|
||||
class="w-4 h-4 text-blue-600 bg-slate-100 border-slate-300 rounded focus:ring-blue-500">
|
||||
<label for="user-active" class="text-sm font-medium text-slate-700 dark:text-slate-300">Aktiv</label>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<div class="flex justify-end space-x-3 mt-8 pt-6 border-t border-slate-200 dark:border-slate-600">
|
||||
<button type="button" onclick="this.closest('#user-modal').remove()"
|
||||
class="px-6 py-3 bg-slate-200 dark:bg-slate-600 text-slate-700 dark:text-slate-300
|
||||
rounded-xl hover:bg-slate-300 dark:hover:bg-slate-500 transition-colors font-medium">
|
||||
Abbrechen
|
||||
</button>
|
||||
<button type="submit" id="user-submit-btn"
|
||||
class="px-6 py-3 bg-gradient-to-r from-blue-500 to-blue-600 text-white
|
||||
rounded-xl hover:from-blue-600 hover:to-blue-700 transition-all duration-300
|
||||
shadow-lg hover:shadow-xl font-medium">
|
||||
${isEdit ? 'Aktualisieren' : 'Erstellen'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Modal zum DOM hinzufügen
|
||||
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
||||
|
||||
// Event-Listener für das Formular
|
||||
const form = document.getElementById('user-form');
|
||||
form.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (isEdit) {
|
||||
this.updateUser(userId, new FormData(form));
|
||||
} else {
|
||||
this.createUser(new FormData(form));
|
||||
}
|
||||
});
|
||||
|
||||
// Bei Bearbeitung: Benutzer-Daten laden
|
||||
if (isEdit) {
|
||||
this.loadUserData(userId);
|
||||
}
|
||||
|
||||
// Fokus auf erstes Eingabefeld
|
||||
setTimeout(() => {
|
||||
document.getElementById('user-email').focus();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
async loadUserData(userId) {
|
||||
try {
|
||||
const response = await fetch(`${this.apiBaseUrl}/api/admin/users/${userId}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
const user = data.user;
|
||||
|
||||
// Formular mit Benutzerdaten füllen
|
||||
document.getElementById('user-email').value = user.email || '';
|
||||
document.getElementById('user-username').value = user.username || '';
|
||||
document.getElementById('user-name').value = user.name || '';
|
||||
document.getElementById('user-role').value = user.is_admin ? 'admin' : 'user';
|
||||
|
||||
const activeCheckbox = document.getElementById('user-active');
|
||||
if (activeCheckbox) {
|
||||
activeCheckbox.checked = user.is_active !== false;
|
||||
}
|
||||
} else {
|
||||
this.showNotification('❌ Fehler beim Laden der Benutzerdaten', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Benutzerdaten:', error);
|
||||
this.showNotification('❌ Fehler beim Laden der Benutzerdaten', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async createUser(formData) {
|
||||
const submitBtn = document.getElementById('user-submit-btn');
|
||||
const originalText = submitBtn.innerHTML;
|
||||
|
||||
try {
|
||||
// Loading-Zustand
|
||||
submitBtn.innerHTML = '<div class="animate-spin rounded-full h-5 w-5 border-b-2 border-white mx-auto"></div>';
|
||||
submitBtn.disabled = true;
|
||||
|
||||
// FormData zu JSON konvertieren
|
||||
const userData = {
|
||||
email: formData.get('email'),
|
||||
username: formData.get('username') || formData.get('email').split('@')[0],
|
||||
name: formData.get('name'),
|
||||
password: formData.get('password'),
|
||||
is_admin: formData.get('role') === 'admin'
|
||||
};
|
||||
|
||||
const response = await fetch(`${this.apiBaseUrl}/api/admin/users`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': this.csrfToken
|
||||
},
|
||||
body: JSON.stringify(userData)
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
this.showNotification('✅ Benutzer erfolgreich erstellt!', 'success');
|
||||
document.getElementById('user-modal').remove();
|
||||
|
||||
// Seite nach 1 Sekunde neu laden um neue Benutzerliste zu zeigen
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
} else {
|
||||
this.showNotification(`❌ Fehler: ${data.error}`, 'error');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Erstellen des Benutzers:', error);
|
||||
this.showNotification('❌ Fehler beim Erstellen des Benutzers', 'error');
|
||||
} finally {
|
||||
// Button zurücksetzen
|
||||
submitBtn.innerHTML = originalText;
|
||||
submitBtn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
async updateUser(userId, formData) {
|
||||
const submitBtn = document.getElementById('user-submit-btn');
|
||||
const originalText = submitBtn.innerHTML;
|
||||
|
||||
try {
|
||||
// Loading-Zustand
|
||||
submitBtn.innerHTML = '<div class="animate-spin rounded-full h-5 w-5 border-b-2 border-white mx-auto"></div>';
|
||||
submitBtn.disabled = true;
|
||||
|
||||
// FormData zu JSON konvertieren
|
||||
const userData = {
|
||||
email: formData.get('email'),
|
||||
username: formData.get('username'),
|
||||
name: formData.get('name'),
|
||||
is_admin: formData.get('role') === 'admin',
|
||||
is_active: formData.get('is_active') === 'on'
|
||||
};
|
||||
|
||||
// Passwort nur hinzufügen wenn es gesetzt wurde
|
||||
const password = formData.get('password');
|
||||
if (password && password.trim()) {
|
||||
userData.password = password;
|
||||
}
|
||||
|
||||
const response = await fetch(`${this.apiBaseUrl}/api/admin/users/${userId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': this.csrfToken
|
||||
},
|
||||
body: JSON.stringify(userData)
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
this.showNotification('✅ Benutzer erfolgreich aktualisiert!', 'success');
|
||||
document.getElementById('user-modal').remove();
|
||||
|
||||
// Seite nach 1 Sekunde neu laden um aktualisierte Benutzerliste zu zeigen
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
} else {
|
||||
this.showNotification(`❌ Fehler: ${data.error}`, 'error');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Aktualisieren des Benutzers:', error);
|
||||
this.showNotification('❌ Fehler beim Aktualisieren des Benutzers', 'error');
|
||||
} finally {
|
||||
// Button zurücksetzen
|
||||
submitBtn.innerHTML = originalText;
|
||||
submitBtn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
editUser(userId) {
|
||||
console.log(`✏️ Benutzer ${userId} wird bearbeitet`);
|
||||
this.showNotification(`Benutzer ${userId} wird bearbeitet...`, 'info');
|
||||
this.showUserModal(userId);
|
||||
}
|
||||
|
||||
|
||||
async deleteUser(userId, userName) {
|
||||
if (!confirm(`🗑️ Möchten Sie den Benutzer "${userName}" wirklich löschen?`)) return;
|
||||
if (!confirm(`🗑️ Möchten Sie den Benutzer "${userName}" wirklich löschen?\n\nDiese Aktion kann nicht rückgängig gemacht werden!`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.showNotification(`🔄 Benutzer "${userName}" wird gelöscht...`, 'info');
|
||||
try {
|
||||
this.showNotification(`🔄 Benutzer "${userName}" wird gelöscht...`, 'info');
|
||||
|
||||
const response = await fetch(`${this.apiBaseUrl}/api/admin/users/${userId}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': this.csrfToken
|
||||
}
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
this.showNotification(`✅ Benutzer "${userName}" erfolgreich gelöscht!`, 'success');
|
||||
|
||||
// Seite nach 1 Sekunde neu laden um aktualisierte Benutzerliste zu zeigen
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
} else {
|
||||
this.showNotification(`❌ Fehler beim Löschen: ${data.error}`, 'error');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Löschen des Benutzers:', error);
|
||||
this.showNotification('❌ Fehler beim Löschen des Benutzers', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Printer-Management
|
||||
|
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user