AuthorBuddy/Components/Pages/UserManagement.razor
2026-06-21 07:52:03 +02:00

373 lines
12 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@page "/user-management"
@attribute [Authorize(Roles = "Administrator")]
@inject IAuthService AuthService
@inject IEmailService EmailService
@inject ILocalizationService Loc
@inject NavigationManager Navigation
@rendermode InteractiveServer
<PageTitle>@Loc["usermanagement.title"] - Author Buddy</PageTitle>
<div class="usermanagement-page">
<div class="usermanagement-header">
<h3>@Loc["usermanagement.title"]</h3>
<button class="btn btn-primary" @onclick="ShowCreateDialog">+ Neuen Benutzer</button>
</div>
<table class="usermanagement-table">
<thead>
<tr>
<th>Name</th>
<th>E-Mail</th>
<th>Rolle</th>
<th>Status</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
@foreach (var u in users)
{
<tr class="@(u.Id == currentUserId ? "usermanagement-current" : "") @(!u.IsActive ? "usermanagement-inactive" : "")">
<td>@u.Name @(u.Id == currentUserId ? "(Du)" : "")</td>
<td>@(!string.IsNullOrEmpty(u.Email) ? u.Email : "—")</td>
<td>@string.Join(", ", u.Roles.Select(RoleName))</td>
<td>
@if (u.IsActive)
{
<span class="usermanagement-status-active">Aktiv</span>
}
else
{
<span class="usermanagement-status-inactive">Inaktiv</span>
}
</td>
<td class="usermanagement-actions">
<button class="btn btn-sm" @onclick="() => BeginEdit(u)" title="Bearbeiten">✎</button>
@if (!string.IsNullOrEmpty(u.Email) && EmailService.IsConfigured)
{
<button class="btn btn-sm" @onclick="() => SendRecovery(u)" title="Passwort zurücksetzen per E-Mail">📧</button>
}
@if (u.Id != currentUserId)
{
@if (u.IsActive)
{
<button class="btn btn-sm" @onclick="() => DeactivateUser(u.Id)" title="Deaktivieren">🔴</button>
}
else
{
<button class="btn btn-sm btn-primary" @onclick="() => ActivateUser(u.Id)" title="Aktivieren">🟢</button>
}
<button class="btn btn-sm btn-danger" @onclick="() => BeginDelete(u)" title="Löschen">🗑</button>
}
</td>
</tr>
}
</tbody>
</table>
@if (!string.IsNullOrEmpty(statusMessage))
{
<div class="usermanagement-status">@statusMessage</div>
}
</div>
@* Create User Dialog *@
@if (showCreateDialog)
{
<div class="modal-overlay">
<div class="modal-dialog">
<div class="modal-title">Neuen Benutzer anlegen</div>
<div class="modal-body">
<div class="form-group">
<label>Benutzername</label>
<input @bind="_newUsername" />
</div>
<div class="form-group">
<label>E-Mail</label>
<input @bind="_newEmail" type="email" />
</div>
<div class="form-group">
<label>Rollen</label>
<div class="um-role-checkboxes">
@foreach (var (role, label) in UmRoleOptions)
{
<label><input type="checkbox" checked="@_newRoles.Contains(role)" @onchange="() => ToggleNewRole(role)" /> @label</label>
}
</div>
</div>
<div class="form-group">
<label>Passwort</label>
<div style="display:flex;gap:8px;">
<input @bind="_newPassword" type="password" style="flex:1;" />
<button class="btn btn-sm" @onclick="GeneratePassword" type="button">Generieren</button>
</div>
@if (!string.IsNullOrEmpty(_generatedPassword))
{
<small style="color:#c00;font-family:monospace;">@_generatedPassword</small>
}
</div>
</div>
<div class="modal-actions">
<button @onclick="CancelCreate">Abbrechen</button>
<button @onclick="CreateUser" class="primary" disabled="@(string.IsNullOrWhiteSpace(_newUsername) || string.IsNullOrWhiteSpace(_newPassword))">Anlegen</button>
</div>
</div>
</div>
}
@* Edit User Dialog *@
@if (showEditDialog && _editUser != null)
{
<div class="modal-overlay">
<div class="modal-dialog">
<div class="modal-title">Benutzer bearbeiten @_editUser.Name</div>
<div class="modal-body">
<div class="form-group">
<label>E-Mail</label>
<input @bind="_editEmail" type="email" />
</div>
<div class="form-group">
<label>Rollen</label>
<div class="um-role-checkboxes">
@foreach (var (role, label) in UmRoleOptions)
{
<label><input type="checkbox" checked="@_editRoles.Contains(role)" @onchange="() => ToggleEditRole(role)" /> @label</label>
}
</div>
</div>
</div>
<div class="modal-actions">
<button @onclick="CancelEdit">Abbrechen</button>
<button @onclick="SaveEdit" class="primary">Speichern</button>
</div>
</div>
</div>
}
@* Delete Confirmation Dialog *@
@if (showDeleteDialog && _deleteUser != null)
{
<div class="modal-overlay">
<div class="modal-dialog">
<div class="modal-title">Benutzer löschen</div>
<div class="modal-body">
<p>Soll der Benutzer <strong>@_deleteUser.Name</strong> wirklich gelöscht werden?</p>
</div>
<div class="modal-actions">
<button @onclick="CancelDelete">Abbrechen</button>
<button @onclick="ConfirmDelete" class="btn-danger">Löschen</button>
</div>
</div>
</div>
}
@code {
private List<UserEntity> users = new();
private int currentUserId;
private string statusMessage = string.Empty;
private bool showCreateDialog;
private string _newUsername = string.Empty;
private string _newEmail = string.Empty;
private List<string> _newRoles = new() { "Author" };
private string _newPassword = string.Empty;
private string? _generatedPassword;
private bool showEditDialog;
private UserEntity? _editUser;
private string _editEmail = string.Empty;
private List<string> _editRoles = new();
private bool showDeleteDialog;
private UserEntity? _deleteUser;
private static (string role, string label)[] UmRoleOptions => new[]
{
("Author", "Autor"),
("Blogger", "Blogger"),
("Chat", "Chat"),
("Administrator", "Administrator")
};
private string RoleName(UserRole role) => role switch
{
UserRole.Administrator => "Administrator",
UserRole.Blogger => "Blogger",
UserRole.Chat => "Chat",
_ => "Autor"
};
protected override async Task OnInitializedAsync()
{
await LoadUsers();
}
private async Task LoadUsers()
{
users = await AuthService.GetAllUsersAsync();
currentUserId = AuthService.CurrentUser?.Id ?? 0;
}
private async Task ActivateUser(int userId)
{
var ok = await AuthService.SetUserActiveAsync(userId, true);
statusMessage = ok ? "Benutzer aktiviert." : "Fehler";
await LoadUsers();
}
private async Task DeactivateUser(int userId)
{
var ok = await AuthService.SetUserActiveAsync(userId, false);
statusMessage = ok ? "Benutzer deaktiviert." : "Fehler";
await LoadUsers();
}
private void ShowCreateDialog()
{
_newUsername = string.Empty;
_newEmail = string.Empty;
_newRoles = new List<string> { "Author" };
_newPassword = string.Empty;
_generatedPassword = null;
showCreateDialog = true;
}
private void CancelCreate()
{
showCreateDialog = false;
}
private void ToggleNewRole(string role)
{
if (_newRoles.Contains(role))
_newRoles.Remove(role);
else
_newRoles.Add(role);
}
private void ToggleEditRole(string role)
{
if (_editRoles.Contains(role))
_editRoles.Remove(role);
else
_editRoles.Add(role);
}
private void GeneratePassword()
{
using var rng = System.Security.Cryptography.RandomNumberGenerator.Create();
var bytes = new byte[10];
rng.GetBytes(bytes);
_generatedPassword = Convert.ToBase64String(bytes).TrimEnd('=').Replace('+', 'X').Replace('/', 'Y');
_newPassword = _generatedPassword;
}
private async Task CreateUser()
{
if (string.IsNullOrWhiteSpace(_newUsername) || string.IsNullOrWhiteSpace(_newPassword))
return;
if (_newRoles.Count == 0)
{
statusMessage = "Mindestens eine Rolle erforderlich.";
return;
}
var roles = _newRoles.Select(r => r switch
{
"Administrator" => UserRole.Administrator,
"Blogger" => UserRole.Blogger,
"Chat" => UserRole.Chat,
_ => UserRole.Author
}).Distinct().ToList();
var ok = await AuthService.CreateUserAsync(_newUsername.Trim(), _newPassword, _newEmail.Trim(), roles);
statusMessage = ok
? $"Benutzer '{_newUsername}' angelegt."
: "Benutzername bereits vergeben oder maximal ein Admin erlaubt.";
showCreateDialog = false;
await LoadUsers();
}
private void BeginEdit(UserEntity user)
{
_editUser = user;
_editEmail = user.Email;
_editRoles = user.Roles.Select(r => r.ToString()).ToList();
showEditDialog = true;
}
private void CancelEdit()
{
showEditDialog = false;
_editUser = null;
}
private async Task SaveEdit()
{
if (_editUser == null) return;
if (_editRoles.Count == 0)
{
statusMessage = "Mindestens eine Rolle erforderlich.";
return;
}
var roles = _editRoles.Select(r => r switch
{
"Administrator" => UserRole.Administrator,
"Blogger" => UserRole.Blogger,
"Chat" => UserRole.Chat,
_ => UserRole.Author
}).Distinct().ToList();
string? email = _editEmail?.Trim();
if (string.IsNullOrEmpty(email)) email = null;
var ok = await AuthService.UpdateUserAsync(_editUser.Id, email, roles);
statusMessage = ok ? "Benutzer aktualisiert." : "Fehler (max. ein Admin erlaubt).";
showEditDialog = false;
_editUser = null;
await LoadUsers();
}
private void BeginDelete(UserEntity user)
{
_deleteUser = user;
showDeleteDialog = true;
}
private void CancelDelete()
{
showDeleteDialog = false;
_deleteUser = null;
}
private async Task ConfirmDelete()
{
if (_deleteUser == null) return;
var ok = await AuthService.DeleteUserAsync(_deleteUser.Id);
statusMessage = ok ? "Benutzer gelöscht." : "Fehler";
showDeleteDialog = false;
_deleteUser = null;
await LoadUsers();
}
private async Task SendRecovery(UserEntity user)
{
if (string.IsNullOrEmpty(user.Email)) return;
var newPassword = await AuthService.ResetPasswordAsync(user.Id);
if (string.IsNullOrEmpty(newPassword))
{
statusMessage = "Fehler beim Zurücksetzen des Passworts.";
return;
}
var sent = await EmailService.SendPasswordAsync(user.Email, user.Name, newPassword);
statusMessage = sent
? $"Neues Passwort an {user.Email} gesendet."
: "E-Mail konnte nicht gesendet werden (SMTP-Konfiguration prüfen).";
}
}