🎉 Feature: Vollständige granulare Benutzerverwaltung implementiert
• ✅ CRUD-Operationen für Benutzer (Erstellen, Bearbeiten, Löschen, Status-Toggle) • ✅ Granulare Berechtigungsstufen (4 Ebenen: Restricted, Standard, Advanced, Admin) • ✅ Detaillierte Berechtigungseinstellungen pro Benutzer • ✅ Vollständige API-Endpunkte für Benutzerverwaltung und Berechtigungen • ✅ Responsive Frontend-Modals mit Mercedes-Benz Corporate Design • ✅ Schutzmaßnahmen (Admin kann sich nicht selbst degradieren/löschen) • 🔧 Database Path Configuration korrigiert (backend/database/myp.db) • 🔧 Template-Fixes (user.name statt user.first_name/last_name) • 🔧 User-Loading-Errors behoben durch korrigierte Pfad-Konfiguration Das Admin Panel verfügt jetzt über eine vollständig funktionale und granulare Benutzerverwaltung mit detaillierten Berechtigungskontrollen für das MYP 3D-Druck-Management-System. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -223,6 +223,14 @@ class AdminDashboard {
|
||||
const userName = e.target.closest('button').dataset.userName;
|
||||
this.deleteUser(userId, userName);
|
||||
}
|
||||
|
||||
if (e.target.closest('.permissions-user-btn')) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const userId = e.target.closest('button').dataset.userId;
|
||||
const userName = e.target.closest('button').dataset.userName;
|
||||
this.showPermissionsModal(userId, userName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -665,6 +673,118 @@ class AdminDashboard {
|
||||
}, 100);
|
||||
}
|
||||
|
||||
async createUser(formData) {
|
||||
try {
|
||||
const submitBtn = document.getElementById('user-submit-btn');
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = 'Wird erstellt...';
|
||||
|
||||
// FormData zu JSON konvertieren
|
||||
const userData = {};
|
||||
for (let [key, value] of formData.entries()) {
|
||||
userData[key] = value;
|
||||
}
|
||||
|
||||
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 "${userData.username}" erfolgreich erstellt!`, 'success');
|
||||
|
||||
// Modal schließen
|
||||
document.getElementById('user-modal').remove();
|
||||
|
||||
// Seite nach 1 Sekunde neu laden
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
} else {
|
||||
this.showNotification(`❌ Fehler beim Erstellen: ${data.error || 'Unbekannter Fehler'}`, 'error');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Erstellen des Benutzers:', error);
|
||||
this.showNotification('❌ Fehler beim Erstellen des Benutzers', 'error');
|
||||
} finally {
|
||||
const submitBtn = document.getElementById('user-submit-btn');
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = 'Erstellen';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async updateUser(userId, formData) {
|
||||
try {
|
||||
const submitBtn = document.getElementById('user-submit-btn');
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = 'Wird aktualisiert...';
|
||||
|
||||
// FormData zu JSON konvertieren
|
||||
const userData = {};
|
||||
for (let [key, value] of formData.entries()) {
|
||||
if (key === 'is_active') {
|
||||
userData['active'] = value === 'on';
|
||||
} else if (key === 'password' && !value) {
|
||||
// Leeres Passwort nicht senden
|
||||
continue;
|
||||
} else {
|
||||
userData[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Rolle zu is_admin konvertieren
|
||||
if (userData.role === 'admin') {
|
||||
userData.role = 'admin';
|
||||
} else {
|
||||
userData.role = 'user';
|
||||
}
|
||||
|
||||
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');
|
||||
|
||||
// Modal schließen
|
||||
document.getElementById('user-modal').remove();
|
||||
|
||||
// Seite nach 1 Sekunde neu laden
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
} else {
|
||||
this.showNotification(`❌ Fehler beim Aktualisieren: ${data.error || 'Unbekannter Fehler'}`, 'error');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Aktualisieren des Benutzers:', error);
|
||||
this.showNotification('❌ Fehler beim Aktualisieren des Benutzers', 'error');
|
||||
} finally {
|
||||
const submitBtn = document.getElementById('user-submit-btn');
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = 'Aktualisieren';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async loadUserData(userId) {
|
||||
try {
|
||||
const response = await fetch(`${this.apiBaseUrl}/api/admin/users/${userId}`);
|
||||
@ -804,6 +924,164 @@ class AdminDashboard {
|
||||
submitBtn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
async showPermissionsModal(userId, userName) {
|
||||
try {
|
||||
// Erst die aktuellen Benutzer-Daten laden
|
||||
const response = await fetch(`${this.apiBaseUrl}/api/admin/users/${userId}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.success) {
|
||||
this.showNotification('❌ Fehler beim Laden der Benutzerdaten', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const user = data.user;
|
||||
const permissions = user.permissions || {
|
||||
can_start_jobs: false,
|
||||
needs_approval: true,
|
||||
can_approve_jobs: false
|
||||
};
|
||||
|
||||
const modalHtml = `
|
||||
<div id="permissions-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-lg 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">Berechtigungen für ${userName}</h3>
|
||||
<button onclick="this.closest('#permissions-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>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-xl p-4">
|
||||
<div class="flex items-center mb-2">
|
||||
<svg class="w-5 h-5 text-blue-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<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>
|
||||
<h4 class="font-semibold text-blue-800 dark:text-blue-300">Berechtigungsebene</h4>
|
||||
</div>
|
||||
<p class="text-sm text-blue-700 dark:text-blue-400">Aktuelle Ebene: <span class="font-medium">${user.permission_level || 'restricted'}</span></p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center justify-between p-4 bg-slate-50 dark:bg-slate-700 rounded-xl">
|
||||
<div>
|
||||
<h5 class="font-medium text-slate-900 dark:text-white">Jobs starten</h5>
|
||||
<p class="text-sm text-slate-500 dark:text-slate-400">Kann 3D-Druck-Jobs eigenständig starten</p>
|
||||
</div>
|
||||
<label class="relative inline-flex items-center cursor-pointer">
|
||||
<input type="checkbox" id="can-start-jobs" ${permissions.can_start_jobs ? 'checked' : ''} class="sr-only peer">
|
||||
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between p-4 bg-slate-50 dark:bg-slate-700 rounded-xl">
|
||||
<div>
|
||||
<h5 class="font-medium text-slate-900 dark:text-white">Genehmigung erforderlich</h5>
|
||||
<p class="text-sm text-slate-500 dark:text-slate-400">Jobs müssen vor Start genehmigt werden</p>
|
||||
</div>
|
||||
<label class="relative inline-flex items-center cursor-pointer">
|
||||
<input type="checkbox" id="needs-approval" ${permissions.needs_approval ? 'checked' : ''} class="sr-only peer">
|
||||
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between p-4 bg-slate-50 dark:bg-slate-700 rounded-xl">
|
||||
<div>
|
||||
<h5 class="font-medium text-slate-900 dark:text-white">Jobs genehmigen</h5>
|
||||
<p class="text-sm text-slate-500 dark:text-slate-400">Kann fremde Jobs genehmigen (Ausbilder-Funktion)</p>
|
||||
</div>
|
||||
<label class="relative inline-flex items-center cursor-pointer">
|
||||
<input type="checkbox" id="can-approve-jobs" ${permissions.can_approve_jobs ? 'checked' : ''} class="sr-only peer">
|
||||
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
|
||||
</label>
|
||||
</div>
|
||||
</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('#permissions-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="button" id="save-permissions-btn" data-user-id="${userId}"
|
||||
class="px-6 py-3 bg-gradient-to-r from-green-500 to-green-600 text-white
|
||||
rounded-xl hover:from-green-600 hover:to-green-700 transition-all duration-300
|
||||
shadow-lg hover:shadow-xl font-medium">
|
||||
Berechtigungen speichern
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Modal zum DOM hinzufügen
|
||||
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
||||
|
||||
// Event-Listener für Speichern-Button
|
||||
document.getElementById('save-permissions-btn').addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
this.saveUserPermissions(userId, userName);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden des Permissions-Modals:', error);
|
||||
this.showNotification('❌ Fehler beim Laden der Berechtigungen', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async saveUserPermissions(userId, userName) {
|
||||
try {
|
||||
const saveBtn = document.getElementById('save-permissions-btn');
|
||||
saveBtn.disabled = true;
|
||||
saveBtn.textContent = 'Wird gespeichert...';
|
||||
|
||||
const permissionsData = {
|
||||
can_start_jobs: document.getElementById('can-start-jobs').checked,
|
||||
needs_approval: document.getElementById('needs-approval').checked,
|
||||
can_approve_jobs: document.getElementById('can-approve-jobs').checked
|
||||
};
|
||||
|
||||
const response = await fetch(`${this.apiBaseUrl}/api/admin/users/${userId}/permissions`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': this.csrfToken
|
||||
},
|
||||
body: JSON.stringify(permissionsData)
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
this.showNotification(`✅ Berechtigungen für ${userName} erfolgreich aktualisiert!`, 'success');
|
||||
|
||||
// Modal schließen
|
||||
document.getElementById('permissions-modal').remove();
|
||||
|
||||
// Seite nach 1 Sekunde neu laden
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
} else {
|
||||
this.showNotification(`❌ Fehler beim Speichern: ${data.error || 'Unbekannter Fehler'}`, 'error');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Speichern der Berechtigungen:', error);
|
||||
this.showNotification('❌ Fehler beim Speichern der Berechtigungen', 'error');
|
||||
} finally {
|
||||
const saveBtn = document.getElementById('save-permissions-btn');
|
||||
if (saveBtn) {
|
||||
saveBtn.disabled = false;
|
||||
saveBtn.textContent = 'Berechtigungen speichern';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
editUser(userId) {
|
||||
console.log(`✏️ Benutzer ${userId} wird bearbeitet`);
|
||||
|
Reference in New Issue
Block a user