AuthorBuddy/Components/Pages/UserManagement.razor

375 lines
12 KiB
Text
Raw Permalink Normal View History

2026-05-25 06:38:16 +02:00
@page "/user-management"
2026-06-21 07:52:03 +02:00
@attribute [Authorize(Roles = "Administrator")]
2026-05-25 06:38:16 +02:00
@inject IAuthService AuthService
2026-06-21 07:52:03 +02:00
@inject IEmailService EmailService
2026-05-25 06:38:16 +02:00
@inject ILocalizationService Loc
@inject NavigationManager Navigation
2026-06-21 07:52:03 +02:00
@rendermode InteractiveServer
2026-05-25 06:38:16 +02:00
<PageTitle>@Loc["usermanagement.title"] - Author Buddy</PageTitle>
<div class="usermanagement-page">
2026-06-21 07:52:03 +02:00
<div class="usermanagement-header">
<h3>@Loc["usermanagement.title"]</h3>
<button class="btn btn-primary" @onclick="ShowCreateDialog">+ Neuen Benutzer</button>
</div>
2026-05-25 06:38:16 +02:00
<table class="usermanagement-table">
<thead>
<tr>
2026-06-21 07:52:03 +02:00
<th>Name</th>
<th>E-Mail</th>
<th>Rolle</th>
<th>Status</th>
<th>Aktionen</th>
2026-05-25 06:38:16 +02:00
</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>
2026-06-21 07:52:03 +02:00
<td>@(!string.IsNullOrEmpty(u.Email) ? u.Email : "—")</td>
<td>@string.Join(", ", u.Roles.Select(RoleName))</td>
2026-05-25 06:38:16 +02:00
<td>
@if (u.IsActive)
{
2026-06-21 07:52:03 +02:00
<span class="usermanagement-status-active">Aktiv</span>
2026-05-25 06:38:16 +02:00
}
else
{
2026-06-21 07:52:03 +02:00
<span class="usermanagement-status-inactive">Inaktiv</span>
2026-05-25 06:38:16 +02:00
}
</td>
<td class="usermanagement-actions">
2026-06-21 07:52:03 +02:00
<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>
}
2026-05-25 06:38:16 +02:00
@if (u.Id != currentUserId)
{
@if (u.IsActive)
{
2026-06-21 07:52:03 +02:00
<button class="btn btn-sm" @onclick="() => DeactivateUser(u.Id)" title="Deaktivieren">🔴</button>
2026-05-25 06:38:16 +02:00
}
else
{
2026-06-21 07:52:03 +02:00
<button class="btn btn-sm btn-primary" @onclick="() => ActivateUser(u.Id)" title="Aktivieren">🟢</button>
2026-05-25 06:38:16 +02:00
}
2026-06-21 07:52:03 +02:00
<button class="btn btn-sm btn-danger" @onclick="() => BeginDelete(u)" title="Löschen">🗑</button>
2026-05-25 06:38:16 +02:00
}
</td>
</tr>
}
</tbody>
</table>
@if (!string.IsNullOrEmpty(statusMessage))
{
<div class="usermanagement-status">@statusMessage</div>
}
</div>
2026-06-21 07:52:03 +02:00
@* 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>
2026-07-04 10:32:35 +02:00
<button @onclick="CreateUser" class="primary" disabled="@_createUserDisabled">Anlegen</button>
2026-06-21 07:52:03 +02:00
</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>
}
2026-05-25 06:38:16 +02:00
@code {
private List<UserEntity> users = new();
private int currentUserId;
private string statusMessage = string.Empty;
2026-06-21 07:52:03 +02:00
private bool showCreateDialog;
private string _newUsername = string.Empty;
private string _newEmail = string.Empty;
private List<string> _newRoles = new() { "Author" };
private string _newPassword = string.Empty;
2026-07-04 10:32:35 +02:00
private bool _createUserDisabled => string.IsNullOrWhiteSpace(_newUsername) || string.IsNullOrWhiteSpace(_newPassword);
2026-06-21 07:52:03 +02:00
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"
};
2026-05-25 06:38:16 +02:00
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);
2026-06-21 07:52:03 +02:00
statusMessage = ok ? "Benutzer aktiviert." : "Fehler";
2026-05-25 06:38:16 +02:00
await LoadUsers();
}
private async Task DeactivateUser(int userId)
{
var ok = await AuthService.SetUserActiveAsync(userId, false);
2026-06-21 07:52:03 +02:00
statusMessage = ok ? "Benutzer deaktiviert." : "Fehler";
2026-05-25 06:38:16 +02:00
await LoadUsers();
}
2026-06-21 07:52:03 +02:00
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()
2026-05-25 06:38:16 +02:00
{
2026-06-21 07:52:03 +02:00
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;
2026-05-25 06:38:16 +02:00
await LoadUsers();
}
2026-06-21 07:52:03 +02:00
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).";
}
2026-05-25 06:38:16 +02:00
}