feat: implement admin dashboard with authentication, session monitoring, and user management UI
Build and Push Docker Image to Gitea Container Registry / build-and-push (push) Successful in 1m27s
Build and Push Docker Image to Gitea Container Registry / build-and-push (pull_request) Successful in 1m19s

This commit is contained in:
Tygozwolle
2026-07-24 12:09:46 +02:00
parent 4b8396b6a6
commit cd2ee7bddd
3 changed files with 104 additions and 34 deletions
@@ -45,10 +45,18 @@
</div>
@code {
protected override async Task OnInitializedAsync()
protected override void OnInitialized()
{
AuthService.OnAuthStateChanged += StateHasChanged;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await AuthService.InitializeAsync();
StateHasChanged();
}
}
private async Task LogoutAdmin()
@@ -23,9 +23,14 @@
Manage guest bandwidth caps, session durations, and monitor live PostgreSQL accounting.
</p>
</div>
<div style="display: flex; gap: 0.5rem; align-items: center;">
<button class="btn-sm btn-secondary" @onclick="RefreshData" disabled="@isLoading">
<i class="bi bi-arrow-clockwise"></i> Refresh Data
</button>
<button class="btn-sm btn-danger" @onclick="LogoutAdmin">
<i class="bi bi-box-arrow-right"></i> Logout (@AuthService.CurrentAdminUsername)
</button>
</div>
</div>
@if (notificationMessage != null)
@@ -196,7 +201,7 @@
<i class="bi bi-slash-circle"></i>
</button>
}
<button class="btn-sm btn-secondary" style="color: var(--accent-rose);" title="Delete User" @onclick="() => DeleteUser(user.ProfileId)">
<button class="btn-sm btn-secondary" style="color: var(--accent-rose);" title="Delete User" @onclick="() => OpenDeleteModal(user)">
<i class="bi bi-trash"></i>
</button>
</div>
@@ -364,6 +369,37 @@
</div>
</div>
}
<!-- Delete Confirmation Modal Overlay -->
@if (selectedUserForDelete != null)
{
<div style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.75); backdrop-filter: blur(8px); display: flex; align-items: center; justify-content: center; z-index: 1000; padding: 1rem;">
<div class="glass-card" style="max-width: 480px; width: 100%; border-color: rgba(244, 63, 94, 0.4);">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem;">
<h3 style="font-size: 1.25rem; font-weight: 700; color: var(--accent-rose);">
<i class="bi bi-exclamation-triangle-fill"></i> Delete Guest Account
</h3>
<button class="btn-sm btn-secondary" @onclick="() => selectedUserForDelete = null">
<i class="bi bi-x-lg"></i>
</button>
</div>
<p style="color: var(--text-muted); font-size: 0.95rem; margin-bottom: 1rem;">
Are you sure you want to permanently delete guest account <strong style="color: var(--text-primary);">@selectedUserForDelete.GuestName</strong> (<code style="color: var(--accent-teal);">@selectedUserForDelete.Username</code>)?
</p>
<p style="color: var(--text-dim); font-size: 0.825rem; margin-bottom: 1.5rem;">
This will remove their RADIUS credentials from <code style="color: var(--accent-teal);">radcheck</code> and <code style="color: var(--accent-teal);">radreply</code> tables. This action cannot be undone.
</p>
<div style="display: flex; gap: 0.75rem; justify-content: flex-end;">
<button class="btn-sm btn-secondary" @onclick="() => selectedUserForDelete = null">Cancel</button>
<button class="btn-sm btn-danger" style="padding: 0.5rem 1.25rem;" @onclick="ConfirmDeleteUser">
<i class="bi bi-trash-fill"></i> Delete User
</button>
</div>
</div>
</div>
}
}
</div>
@@ -385,7 +421,11 @@
private long editModalDataLimit;
private string editModalNewPassword = string.Empty;
protected override async Task OnInitializedAsync()
private UserUsageDto? selectedUserForDelete;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await AuthService.InitializeAsync();
if (!AuthService.IsAuthenticated)
@@ -395,6 +435,14 @@
}
await LoadDataAsync();
StateHasChanged();
}
}
private async Task LogoutAdmin()
{
await AuthService.LogoutAsync();
NavManager.NavigateTo("/admin/login");
}
private async Task LoadDataAsync()
@@ -495,13 +543,23 @@
}
}
private async Task DeleteUser(int profileId)
private void OpenDeleteModal(UserUsageDto user)
{
selectedUserForDelete = user;
}
private async Task ConfirmDeleteUser()
{
if (selectedUserForDelete == null) return;
int profileId = selectedUserForDelete.ProfileId;
selectedUserForDelete = null;
try
{
await RadiusService.DeleteUserAsync(profileId);
await LoadDataAsync();
ShowNotification("User deleted.");
ShowNotification("User account deleted successfully.");
}
catch (Exception ex)
{
@@ -143,7 +143,11 @@
private bool isSubmitting = false;
private string errorMessage = string.Empty;
protected override async Task OnInitializedAsync()
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
try
{
await AuthService.InitializeAsync();
if (AuthService.IsAuthenticated)
@@ -152,8 +156,6 @@
return;
}
try
{
isFirstRun = !await AuthService.HasAnyAdminAsync();
}
catch
@@ -163,6 +165,8 @@
finally
{
isCheckingFirstRun = false;
StateHasChanged();
}
}
}