Files
certigo/frontend/src/pages/Users.jsx

890 lines
39 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, useEffect } from 'react'
import { useAuth } from '../contexts/AuthContext'
import { usePermissions } from '../contexts/PermissionsContext'
const Users = () => {
const { authFetch } = useAuth()
const { refreshPermissions } = usePermissions()
const [users, setUsers] = useState([])
const [groups, setGroups] = useState([])
const [loading, setLoading] = useState(false)
const [error, setError] = useState('')
const [showForm, setShowForm] = useState(false)
const [editingUser, setEditingUser] = useState(null)
const [showDeleteModal, setShowDeleteModal] = useState(false)
const [userToDelete, setUserToDelete] = useState(null)
const [confirmChecked, setConfirmChecked] = useState(false)
const [showToggleModal, setShowToggleModal] = useState(false)
const [userToToggle, setUserToToggle] = useState(null)
const [confirmToggleChecked, setConfirmToggleChecked] = useState(false)
const [formData, setFormData] = useState({
username: '',
email: '',
oldPassword: '',
password: '',
confirmPassword: '',
isAdmin: false,
enabled: true,
groupIds: []
})
const [showAdminWarning, setShowAdminWarning] = useState(false)
useEffect(() => {
fetchUsers()
fetchGroups()
}, [])
const fetchUsers = async () => {
try {
setError('')
const response = await authFetch('/api/users')
if (response.ok) {
const data = await response.json()
setUsers(Array.isArray(data) ? data : [])
} else {
setError('Fehler beim Abrufen der Benutzer')
}
} catch (err) {
setError('Fehler beim Abrufen der Benutzer')
console.error('Error fetching users:', err)
}
}
const fetchGroups = async () => {
try {
const response = await authFetch('/api/permission-groups')
if (response.ok) {
const data = await response.json()
setGroups(Array.isArray(data) ? data : [])
}
} catch (err) {
console.error('Error fetching permission groups:', err)
}
}
const handleSubmit = async (e) => {
e.preventDefault()
setError('')
setLoading(true)
// Validierung: Passwort-Bestätigung muss übereinstimmen
if (formData.password && formData.password !== formData.confirmPassword) {
setError('Die Passwörter stimmen nicht überein')
setLoading(false)
return
}
// Validierung: Wenn Passwort geändert wird, muss altes Passwort vorhanden sein
if (editingUser && formData.password && !formData.oldPassword) {
setError('Bitte geben Sie das alte Passwort ein, um das Passwort zu ändern')
setLoading(false)
return
}
try {
const url = editingUser
? `/api/users/${editingUser.id}`
: '/api/users'
const method = editingUser ? 'PUT' : 'POST'
const body = editingUser
? {
// Username/Email nur setzen wenn nicht der spezielle Admin-User mit UID 'admin'
...(formData.username && editingUser.id !== 'admin' && { username: formData.username }),
...(formData.email && editingUser.id !== 'admin' && { email: formData.email }),
...(formData.password && {
password: formData.password,
oldPassword: formData.oldPassword
}),
// isAdmin nur setzen wenn nicht UID 'admin' (UID 'admin' ist immer Admin)
...(formData.isAdmin !== undefined && editingUser.id !== 'admin' && { isAdmin: formData.isAdmin }),
// enabled wird nicht über das Bearbeitungsformular geändert, nur über den Button in der Liste
...(formData.groupIds !== undefined && { groupIds: formData.groupIds })
}
: {
username: formData.username,
email: formData.email,
password: formData.password,
isAdmin: formData.isAdmin || false,
enabled: true, // Neue User sind immer aktiviert, enabled kann nur für UID 'admin' geändert werden
groupIds: formData.groupIds || []
}
const response = await authFetch(url, {
method,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
})
if (response.ok) {
await fetchUsers()
setFormData({ username: '', email: '', oldPassword: '', password: '', confirmPassword: '', isAdmin: false, enabled: true, groupIds: [] })
setShowForm(false)
setEditingUser(null)
setShowAdminWarning(false)
// Aktualisiere Berechtigungen nach Änderung an Benutzern (Gruppen-Zuweisungen könnten sich geändert haben)
refreshPermissions()
} else {
const errorData = await response.json()
setError(errorData.error || 'Fehler beim Speichern des Benutzers')
}
} catch (err) {
setError('Fehler beim Speichern des Benutzers')
console.error('Error saving user:', err)
} finally {
setLoading(false)
}
}
const handleEdit = (user) => {
setEditingUser(user)
setFormData({
username: user.username,
email: user.email,
oldPassword: '',
password: '',
confirmPassword: '',
isAdmin: user.isAdmin || false,
enabled: user.enabled !== undefined ? user.enabled : true, // Wird nicht im Formular angezeigt, nur für internen Zustand
groupIds: user.groupIds || []
})
setShowForm(true)
}
const handleDelete = (user) => {
setUserToDelete(user)
setShowDeleteModal(true)
setConfirmChecked(false)
}
const handleToggleEnabled = (user) => {
if (user.id !== 'admin') {
return
}
setUserToToggle(user)
setShowToggleModal(true)
setConfirmToggleChecked(false)
}
const confirmToggle = async () => {
if (!confirmToggleChecked || !userToToggle) {
return
}
const newEnabled = !userToToggle.enabled
const action = newEnabled ? 'aktivieren' : 'deaktivieren'
try {
setLoading(true)
const response = await authFetch(`/api/users/${userToToggle.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: newEnabled
}),
})
if (response.ok) {
await fetchUsers()
setShowToggleModal(false)
setUserToToggle(null)
setConfirmToggleChecked(false)
// Aktualisiere Berechtigungen nach Änderung
refreshPermissions()
} else {
const errorData = await response.json().catch(() => ({ error: `Fehler beim ${action}` }))
const errorMessage = errorData.error || `Fehler beim ${action} des Admin-Users`
setError(errorMessage)
// Schließe Modal bei Fehler
if (response.status === 403) {
setShowToggleModal(false)
setUserToToggle(null)
setConfirmToggleChecked(false)
}
}
} catch (err) {
console.error(`Error toggling enabled for admin user:`, err)
setError(`Fehler beim ${action} des Admin-Users`)
} finally {
setLoading(false)
}
}
const cancelToggle = () => {
setShowToggleModal(false)
setUserToToggle(null)
setConfirmToggleChecked(false)
}
const confirmDelete = async () => {
if (!confirmChecked || !userToDelete) {
return
}
try {
const response = await authFetch(`/api/users/${userToDelete.id}`, {
method: 'DELETE',
})
if (response.ok) {
await fetchUsers()
setShowDeleteModal(false)
setUserToDelete(null)
setConfirmChecked(false)
// Aktualisiere Berechtigungen nach Löschen eines Benutzers
refreshPermissions()
} else {
const errorData = await response.json().catch(() => ({ error: 'Fehler beim Löschen' }))
const errorMessage = errorData.error || 'Fehler beim Löschen des Benutzers'
// Zeige Fehlermeldung
setError(errorMessage)
// Wenn Admin-Löschung verhindert wurde, schließe Modal
if (response.status === 403) {
setShowDeleteModal(false)
setUserToDelete(null)
setConfirmChecked(false)
}
}
} catch (err) {
console.error('Error deleting user:', err)
setError('Fehler beim Löschen des Benutzers')
}
}
const cancelDelete = () => {
setShowDeleteModal(false)
setUserToDelete(null)
setConfirmChecked(false)
}
const handleChange = (e) => {
setFormData({
...formData,
[e.target.name]: e.target.value
})
}
const handleGroupToggle = (groupId) => {
setFormData(prev => {
const groupIds = prev.groupIds || []
if (groupIds.includes(groupId)) {
return { ...prev, groupIds: groupIds.filter(id => id !== groupId) }
} else {
return { ...prev, groupIds: [...groupIds, groupId] }
}
})
}
const handleAdminToggle = (e) => {
const isAdmin = e.target.checked
if (isAdmin && !showAdminWarning) {
setShowAdminWarning(true)
}
setFormData(prev => ({
...prev,
isAdmin,
// Wenn Admin aktiviert wird, entferne alle Gruppen und stelle sicher dass enabled=true
groupIds: isAdmin ? [] : prev.groupIds,
enabled: isAdmin ? true : (prev.enabled !== undefined ? prev.enabled : true) // Admin muss immer enabled sein
}))
}
const getPermissionLabel = (permission) => {
switch (permission) {
case 'READ':
return 'Lesen'
case 'READ_WRITE':
return 'Lesen/Schreiben'
case 'FULL_ACCESS':
return 'Vollzugriff'
default:
return permission
}
}
return (
<div className="p-8 min-h-full bg-gradient-to-r from-slate-700 to-slate-900">
<div className="max-w-4xl mx-auto">
<div className="flex items-center justify-between mb-8">
<div>
<h1 className="text-4xl font-bold text-white mb-2">Benutzerverwaltung</h1>
<p className="text-lg text-slate-200">
Verwalten Sie lokale Benutzer und deren Zugangsdaten.
</p>
</div>
<button
onClick={() => {
setShowForm(!showForm)
setEditingUser(null)
setFormData({ username: '', email: '', oldPassword: '', password: '', confirmPassword: '', isAdmin: false, enabled: true, groupIds: [] })
setShowAdminWarning(false)
}}
className="px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white font-semibold rounded-lg shadow-lg hover:shadow-xl transition-all duration-200"
>
{showForm ? 'Abbrechen' : '+ Neuer Benutzer'}
</button>
</div>
{/* Create/Edit User Form */}
{showForm && (
<div className="bg-slate-800/80 backdrop-blur-sm rounded-lg shadow-xl border border-slate-600/50 p-6 mb-6">
<h2 className="text-2xl font-semibold text-white mb-4">
{editingUser ? 'Benutzer bearbeiten' : 'Neuen Benutzer erstellen'}
</h2>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="username" className="block text-sm font-medium text-slate-200 mb-2">
Benutzername {!editingUser && '*'}
</label>
<input
type="text"
id="username"
name="username"
value={formData.username}
onChange={handleChange}
required={!editingUser}
disabled={editingUser && editingUser.id === 'admin'}
className={`w-full px-4 py-2 bg-slate-700/50 border border-slate-600 rounded-lg text-white placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
editingUser && editingUser.id === 'admin' ? 'opacity-50 cursor-not-allowed' : ''
}`}
placeholder="Geben Sie einen Benutzernamen ein"
/>
{editingUser && editingUser.id === 'admin' && (
<p className="mt-1 text-xs text-slate-400">Der Benutzername des Admin-Users mit UID 'admin' kann nicht geändert werden</p>
)}
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium text-slate-200 mb-2">
E-Mail {!editingUser && '*'}
</label>
<input
type="email"
id="email"
name="email"
value={formData.email}
onChange={handleChange}
required={!editingUser}
disabled={editingUser && editingUser.id === 'admin'}
className={`w-full px-4 py-2 bg-slate-700/50 border border-slate-600 rounded-lg text-white placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
editingUser && editingUser.id === 'admin' ? 'opacity-50 cursor-not-allowed' : ''
}`}
placeholder="Geben Sie eine E-Mail-Adresse ein"
/>
{editingUser && editingUser.id === 'admin' && (
<p className="mt-1 text-xs text-slate-400">Die E-Mail-Adresse des Admin-Users mit UID 'admin' kann nicht geändert werden</p>
)}
</div>
{editingUser && (
<div>
<label htmlFor="oldPassword" className="block text-sm font-medium text-slate-200 mb-2">
Altes Passwort {formData.password && '*'}
</label>
<input
type="password"
id="oldPassword"
name="oldPassword"
value={formData.oldPassword}
onChange={handleChange}
required={!!formData.password}
className="w-full px-4 py-2 bg-slate-700/50 border border-slate-600 rounded-lg text-white placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="Geben Sie Ihr aktuelles Passwort ein"
/>
</div>
)}
<div>
<label htmlFor="password" className="block text-sm font-medium text-slate-200 mb-2">
{editingUser ? 'Neues Passwort' : 'Passwort'} {!editingUser && '*'}
{editingUser && <span className="text-xs text-slate-400 ml-2">(leer lassen, um nicht zu ändern)</span>}
</label>
<input
type="password"
id="password"
name="password"
value={formData.password}
onChange={handleChange}
required={!editingUser}
className="w-full px-4 py-2 bg-slate-700/50 border border-slate-600 rounded-lg text-white placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder={editingUser ? "Geben Sie ein neues Passwort ein" : "Geben Sie ein Passwort ein"}
/>
{/* Passwortrichtlinie - nur anzeigen wenn Passwort eingegeben wird */}
{(formData.password || !editingUser) && (
<div className="mt-2 p-3 bg-slate-700/30 border border-slate-600/50 rounded-lg">
<p className="text-xs font-semibold text-slate-300 mb-2">Passwortrichtlinie:</p>
<ul className="text-xs text-slate-400 space-y-1">
<li className={`flex items-center gap-2 ${formData.password.length >= 8 ? 'text-green-400' : ''}`}>
{formData.password.length >= 8 ? '✓' : '○'} Mindestens 8 Zeichen
</li>
<li className={`flex items-center gap-2 ${/[A-Z]/.test(formData.password) ? 'text-green-400' : ''}`}>
{/[A-Z]/.test(formData.password) ? '✓' : '○'} Mindestens ein Großbuchstabe
</li>
<li className={`flex items-center gap-2 ${/[a-z]/.test(formData.password) ? 'text-green-400' : ''}`}>
{/[a-z]/.test(formData.password) ? '✓' : '○'} Mindestens ein Kleinbuchstabe
</li>
<li className={`flex items-center gap-2 ${/[0-9]/.test(formData.password) ? 'text-green-400' : ''}`}>
{/[0-9]/.test(formData.password) ? '✓' : '○'} Mindestens eine Zahl
</li>
<li className={`flex items-center gap-2 ${/[^A-Za-z0-9]/.test(formData.password) ? 'text-green-400' : ''}`}>
{/[^A-Za-z0-9]/.test(formData.password) ? '✓' : '○'} Mindestens ein Sonderzeichen
</li>
</ul>
</div>
)}
</div>
<div>
<label htmlFor="confirmPassword" className="block text-sm font-medium text-slate-200 mb-2">
{editingUser ? 'Neues Passwort bestätigen' : 'Passwort bestätigen'} {!editingUser && '*'}
</label>
<input
type="password"
id="confirmPassword"
name="confirmPassword"
value={formData.confirmPassword}
onChange={handleChange}
required={!editingUser || !!formData.password}
className={`w-full px-4 py-2 bg-slate-700/50 border rounded-lg text-white placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
formData.confirmPassword && formData.password !== formData.confirmPassword
? 'border-red-500'
: formData.confirmPassword && formData.password === formData.confirmPassword
? 'border-green-500'
: 'border-slate-600'
}`}
placeholder={editingUser ? "Bestätigen Sie das neue Passwort" : "Bestätigen Sie das Passwort"}
/>
{formData.confirmPassword && formData.password !== formData.confirmPassword && (
<p className="mt-1 text-xs text-red-400">Die Passwörter stimmen nicht überein</p>
)}
{formData.confirmPassword && formData.password === formData.confirmPassword && formData.password && (
<p className="mt-1 text-xs text-green-400"> Passwörter stimmen überein</p>
)}
</div>
{/* Admin Checkbox - nicht für UID 'admin' */}
{(!editingUser || editingUser.id !== 'admin') && (
<div className="bg-slate-700/30 border border-slate-600/50 rounded-lg p-4">
<label className="flex items-start cursor-pointer group">
<input
type="checkbox"
checked={formData.isAdmin || false}
onChange={handleAdminToggle}
disabled={editingUser && editingUser.username === 'admin'} // Admin user kann seinen Status nicht ändern
className="mt-1 w-5 h-5 text-blue-600 bg-slate-700 border-slate-600 rounded focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-slate-800 cursor-pointer"
/>
<div className="ml-3 flex-1">
<div className="flex items-center gap-2">
<span className="text-slate-200 font-semibold">Administrator</span>
<span className="px-2 py-0.5 bg-red-600/20 text-red-300 rounded text-xs font-medium">
VOLLZUGRIFF
</span>
</div>
<p className="text-xs text-slate-400 mt-1">
Ein Administrator hat vollständigen Zugriff auf alle Funktionen und kann alle Einstellungen verwalten.
</p>
</div>
</label>
</div>
)}
{/* Berechtigungsgruppen - ausgegraut wenn Admin oder UID 'admin' */}
<div>
<label className="block text-sm font-medium text-slate-200 mb-2">
Berechtigungsgruppen
{(formData.isAdmin || (editingUser && editingUser.id === 'admin')) && <span className="text-xs text-slate-400 ml-2">(nicht verfügbar für Administratoren)</span>}
</label>
<div className={`bg-slate-700/50 border border-slate-600 rounded-lg p-4 max-h-60 overflow-y-auto ${
formData.isAdmin || (editingUser && editingUser.id === 'admin') ? 'opacity-50 pointer-events-none' : ''
}`}>
{groups.length === 0 ? (
<p className="text-slate-400 text-sm">Keine Berechtigungsgruppen vorhanden</p>
) : (
<div className="space-y-2">
{groups.map(group => (
<label key={group.id} className={`flex items-start p-2 rounded ${
formData.isAdmin || (editingUser && editingUser.id === 'admin') ? 'cursor-not-allowed' : 'cursor-pointer hover:bg-slate-600/50'
}`}>
<input
type="checkbox"
checked={formData.groupIds?.includes(group.id) || false}
onChange={() => handleGroupToggle(group.id)}
disabled={formData.isAdmin || (editingUser && editingUser.id === 'admin')}
className="w-4 h-4 text-blue-600 bg-slate-600 border-slate-500 rounded focus:ring-blue-500 mt-1"
/>
<div className="ml-3 flex-1">
<div className="flex items-center gap-2">
<span className="text-slate-300 font-medium">{group.name}</span>
<span className="px-2 py-0.5 bg-blue-600/20 text-blue-300 rounded text-xs">
{getPermissionLabel(group.permission)}
</span>
</div>
{group.description && (
<p className="text-xs text-slate-400 mt-1">{group.description}</p>
)}
</div>
</label>
))}
</div>
)}
</div>
</div>
{error && (
<div className="p-3 bg-red-500/20 border border-red-500/50 rounded-lg text-red-300 text-sm">
{error}
</div>
)}
<div className="flex gap-3">
<button
type="submit"
disabled={loading}
className="px-6 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-blue-800 disabled:cursor-not-allowed text-white font-semibold rounded-lg transition-colors duration-200"
>
{loading ? 'Wird gespeichert...' : editingUser ? 'Aktualisieren' : 'Benutzer erstellen'}
</button>
<button
type="button"
onClick={() => {
setShowForm(false)
setEditingUser(null)
setFormData({ username: '', email: '', oldPassword: '', password: '', confirmPassword: '', isAdmin: false, enabled: true, groupIds: [] })
setShowAdminWarning(false)
setError('')
}}
className="px-6 py-2 bg-slate-600 hover:bg-slate-700 text-white font-semibold rounded-lg transition-colors duration-200"
>
Abbrechen
</button>
</div>
</form>
</div>
)}
{/* Users List */}
<div className="bg-slate-800/80 backdrop-blur-sm rounded-lg shadow-xl border border-slate-600/50 p-6">
<h2 className="text-2xl font-semibold text-white mb-4">
Benutzer
</h2>
{error && !showForm && (
<div className="mb-4 p-4 bg-red-500/20 border border-red-500/50 rounded-lg">
<p className="text-red-300 mb-2">{error}</p>
<button
onClick={fetchUsers}
className="px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg transition-colors"
>
Erneut versuchen
</button>
</div>
)}
{users.length === 0 ? (
<p className="text-slate-300 text-center py-8">
Noch keine Benutzer vorhanden. Erstellen Sie Ihren ersten Benutzer!
</p>
) : (
<div className="space-y-4">
{users.map((user) => (
<div
key={user.id}
className="bg-slate-700/50 rounded-lg p-4 border border-slate-600/50 hover:border-slate-500 transition-colors"
>
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center gap-2 mb-2">
<h3 className="text-xl font-semibold text-white">
{user.username}
</h3>
{user.isAdmin && (
<span className="px-2 py-0.5 bg-red-600/20 text-red-300 rounded text-xs font-medium border border-red-500/30">
ADMIN
</span>
)}
{user.id === 'admin' && user.enabled === false && (
<span className="px-2 py-0.5 bg-red-600/20 text-red-300 rounded text-xs font-medium border border-red-500/30">
DEAKTIVIERT
</span>
)}
</div>
<p className="text-slate-300 mb-2">{user.email}</p>
{!user.isAdmin && user.groupIds && user.groupIds.length > 0 && (
<div className="mb-2">
<p className="text-sm text-slate-300 mb-1">Berechtigungsgruppen:</p>
<div className="flex flex-wrap gap-2">
{user.groupIds.map(groupId => {
const group = groups.find(g => g.id === groupId)
return group ? (
<span key={groupId} className="px-2 py-1 bg-blue-600/20 text-blue-300 rounded text-xs">
{group.name} ({getPermissionLabel(group.permission)})
</span>
) : null
})}
</div>
</div>
)}
<p className="text-xs text-slate-400">
Erstellt: {user.createdAt ? new Date(user.createdAt).toLocaleString('de-DE') : 'Unbekannt'}
</p>
{user.id && (
<p className="text-xs text-slate-500 font-mono mt-1">
ID: {user.id}
</p>
)}
</div>
<div className="flex gap-2 ml-4">
<button
onClick={() => handleEdit(user)}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm rounded-lg transition-colors"
>
Bearbeiten
</button>
{user.id === 'admin' ? (
<button
onClick={() => handleToggleEnabled(user)}
className={`px-4 py-2 text-white text-sm rounded-lg transition-colors ${
user.enabled
? 'bg-yellow-600 hover:bg-yellow-700'
: 'bg-green-600 hover:bg-green-700'
}`}
title={user.enabled ? "Admin-User deaktivieren" : "Admin-User aktivieren"}
>
{user.enabled ? 'Deaktivieren' : 'Aktivieren'}
</button>
) : (
<button
onClick={() => handleDelete(user)}
className="px-4 py-2 bg-red-600 hover:bg-red-700 text-white text-sm rounded-lg transition-colors"
>
Löschen
</button>
)}
</div>
</div>
</div>
))}
</div>
)}
</div>
{/* Admin Warning Modal */}
{showAdminWarning && (
<div className="fixed inset-0 bg-black/60 backdrop-blur-sm z-50 flex items-center justify-center p-4">
<div className="bg-slate-800 rounded-xl shadow-2xl border border-red-600/50 max-w-md w-full p-6">
<div className="flex items-center mb-4">
<div className="flex-shrink-0 w-12 h-12 bg-red-500/20 rounded-full flex items-center justify-center mr-4">
<svg
className="w-6 h-6 text-red-400"
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
</div>
<h3 className="text-xl font-bold text-white">
Administrator-Berechtigung
</h3>
</div>
<div className="mb-6">
<p className="text-slate-300 mb-3">
Sie sind dabei, diesem Benutzer <span className="font-semibold text-red-400">Administrator-Rechte</span> zu gewähren.
</p>
<div className="bg-red-500/10 border border-red-500/30 rounded-lg p-3 mb-4">
<p className="text-sm font-semibold text-red-300 mb-2"> Mögliche Gefahren:</p>
<ul className="text-xs text-slate-300 space-y-1 list-disc list-inside">
<li>Vollständiger Zugriff auf alle Funktionen und Einstellungen</li>
<li>Möglichkeit, andere Benutzer zu erstellen, zu bearbeiten oder zu löschen</li>
<li>Zugriff auf alle Spaces, FQDNs und Zertifikate</li>
<li>Möglichkeit, Berechtigungsgruppen zu verwalten</li>
<li>Keine Einschränkungen durch Berechtigungsgruppen</li>
</ul>
</div>
<p className="text-sm text-slate-400">
Möchten Sie wirklich fortfahren?
</p>
</div>
<div className="flex gap-3">
<button
onClick={() => setShowAdminWarning(false)}
className="flex-1 px-4 py-2 bg-slate-600 hover:bg-slate-700 text-white font-semibold rounded-lg transition-colors duration-200"
>
Abbrechen
</button>
<button
onClick={() => setShowAdminWarning(false)}
className="flex-1 px-4 py-2 bg-red-600 hover:bg-red-700 text-white font-semibold rounded-lg transition-colors duration-200"
>
Verstanden, fortfahren
</button>
</div>
</div>
</div>
)}
{/* Delete Confirmation Modal */}
{showDeleteModal && userToDelete && (
<div className="fixed inset-0 bg-black/60 backdrop-blur-sm z-50 flex items-center justify-center p-4">
<div className="bg-slate-800 rounded-xl shadow-2xl border border-slate-600/50 max-w-md w-full p-6">
<div className="flex items-center mb-4">
<div className="flex-shrink-0 w-12 h-12 bg-red-500/20 rounded-full flex items-center justify-center mr-4">
<svg
className="w-6 h-6 text-red-400"
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
</div>
<h3 className="text-xl font-bold text-white">
Benutzer löschen
</h3>
</div>
<div className="mb-6">
<p className="text-slate-300 mb-4">
Möchten Sie den Benutzer <span className="font-semibold text-white">{userToDelete.username}</span> wirklich löschen?
</p>
<p className="text-sm text-red-400 mb-4">
Diese Aktion kann nicht rückgängig gemacht werden.
</p>
<label className="flex items-start cursor-pointer group">
<input
type="checkbox"
checked={confirmChecked}
onChange={(e) => setConfirmChecked(e.target.checked)}
className="mt-1 w-5 h-5 text-red-600 bg-slate-700 border-slate-600 rounded focus:ring-2 focus:ring-red-500 focus:ring-offset-2 focus:ring-offset-slate-800 cursor-pointer"
/>
<span className="ml-3 text-sm text-slate-300 group-hover:text-white transition-colors">
Ich bestätige, dass ich diesen Benutzer unwiderruflich löschen möchte
</span>
</label>
</div>
<div className="flex gap-3">
<button
onClick={confirmDelete}
disabled={!confirmChecked}
className="flex-1 px-4 py-2 bg-red-600 hover:bg-red-700 disabled:bg-slate-700 disabled:text-slate-500 disabled:cursor-not-allowed text-white font-semibold rounded-lg transition-colors duration-200"
>
Löschen
</button>
<button
onClick={cancelDelete}
className="flex-1 px-4 py-2 bg-slate-600 hover:bg-slate-700 text-white font-semibold rounded-lg transition-colors duration-200"
>
Abbrechen
</button>
</div>
</div>
</div>
)}
{/* Toggle Enabled Confirmation Modal */}
{showToggleModal && userToToggle && (
<div className="fixed inset-0 bg-black/60 backdrop-blur-sm z-50 flex items-center justify-center p-4">
<div className={`bg-slate-800 rounded-xl shadow-2xl border max-w-md w-full p-6 ${
userToToggle.enabled ? 'border-yellow-600/50' : 'border-green-600/50'
}`}>
<div className="flex items-center mb-4">
<div className={`flex-shrink-0 w-12 h-12 rounded-full flex items-center justify-center mr-4 ${
userToToggle.enabled ? 'bg-yellow-500/20' : 'bg-green-500/20'
}`}>
{userToToggle.enabled ? (
<svg
className="w-6 h-6 text-yellow-400"
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636" />
</svg>
) : (
<svg
className="w-6 h-6 text-green-400"
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
)}
</div>
<h3 className="text-xl font-bold text-white">
Admin-User {userToToggle.enabled ? 'deaktivieren' : 'aktivieren'}
</h3>
</div>
<div className="mb-6">
<p className="text-slate-300 mb-4">
Möchten Sie den Admin-User <span className="font-semibold text-white">{userToToggle.username}</span> (UID: {userToToggle.id}) wirklich {userToToggle.enabled ? 'deaktivieren' : 'aktivieren'}?
</p>
<p className={`text-sm mb-4 ${
userToToggle.enabled ? 'text-yellow-400' : 'text-green-400'
}`}>
{userToToggle.enabled
? 'Der Admin-User kann sich nach der Deaktivierung nicht mehr anmelden und keine API-Calls durchführen.'
: 'Der Admin-User kann sich nach der Aktivierung wieder anmelden und API-Calls durchführen.'}
</p>
<label className="flex items-start cursor-pointer group">
<input
type="checkbox"
checked={confirmToggleChecked}
onChange={(e) => setConfirmToggleChecked(e.target.checked)}
className={`mt-1 w-5 h-5 bg-slate-700 border-slate-600 rounded focus:ring-2 focus:ring-offset-2 focus:ring-offset-slate-800 cursor-pointer ${
userToToggle.enabled
? 'text-yellow-600 focus:ring-yellow-500'
: 'text-green-600 focus:ring-green-500'
}`}
/>
<span className="ml-3 text-sm text-slate-300 group-hover:text-white transition-colors">
Ich bestätige, dass ich den Admin-User {userToToggle.enabled ? 'deaktivieren' : 'aktivieren'} möchte
</span>
</label>
</div>
<div className="flex gap-3">
<button
onClick={confirmToggle}
disabled={!confirmToggleChecked}
className={`flex-1 px-4 py-2 disabled:bg-slate-700 disabled:text-slate-500 disabled:cursor-not-allowed text-white font-semibold rounded-lg transition-colors duration-200 ${
userToToggle.enabled
? 'bg-yellow-600 hover:bg-yellow-700'
: 'bg-green-600 hover:bg-green-700'
}`}
>
{userToToggle.enabled ? 'Deaktivieren' : 'Aktivieren'}
</button>
<button
onClick={cancelToggle}
className="flex-1 px-4 py-2 bg-slate-600 hover:bg-slate-700 text-white font-semibold rounded-lg transition-colors duration-200"
>
Abbrechen
</button>
</div>
</div>
</div>
)}
</div>
</div>
)
}
export default Users