Files
SharepointToolbox-Web/Components/Pages/Admin/AuditLogs.razor
T
2026-06-02 10:56:03 +02:00

102 lines
4.4 KiB
Plaintext

@page "/admin/audit"
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@inject IAuditService AuditService
@inject IUserContextAccessor UserContext
@inject NavigationManager Nav
@rendermode InteractiveServer
@using SharepointToolbox.Web.Core.Models
@using SharepointToolbox.Web.Services.Audit
@using SharepointToolbox.Web.Services.Session
<h1 class="page-title">Audit Logs</h1>
<p class="page-subtitle">All technician and admin actions within the application.</p>
@if (!UserContext.IsAuthenticated || UserContext.Role != UserRole.Admin)
{
<div class="alert alert-error">Access denied. Admin role required.</div>
return;
}
<div class="flex-row" style="margin-bottom:16px;flex-wrap:wrap;gap:8px">
<input class="form-input" style="width:200px" placeholder="Filter by user..." @bind="_filterUser" @bind:event="oninput" />
<input class="form-input" style="width:200px" placeholder="Filter by client..." @bind="_filterClient" @bind:event="oninput" />
<input class="form-input" style="width:200px" placeholder="Filter by action..." @bind="_filterAction" @bind:event="oninput" />
<a href="/audit/export" class="btn btn-secondary" target="_blank">Export CSV</a>
</div>
@if (_loading)
{
<div class="alert alert-info">Loading audit log...</div>
}
else if (_filtered.Count == 0)
{
<div class="alert alert-info">No audit entries found.</div>
}
else
{
<div class="card" style="overflow-x:auto">
<table style="width:100%;border-collapse:collapse;font-size:13px">
<thead>
<tr style="border-bottom:2px solid var(--border)">
<th style="text-align:left;padding:6px">Timestamp</th>
<th style="text-align:left;padding:6px">User</th>
<th style="text-align:left;padding:6px">Role</th>
<th style="text-align:left;padding:6px">Action</th>
<th style="text-align:left;padding:6px">Client</th>
<th style="text-align:left;padding:6px">Sites</th>
<th style="text-align:left;padding:6px">Details</th>
</tr>
</thead>
<tbody>
@foreach (var e in _filtered)
{
<tr style="border-bottom:1px solid var(--border)">
<td style="padding:6px;white-space:nowrap">@e.Timestamp.ToString("yyyy-MM-dd HH:mm:ss")</td>
<td style="padding:6px">@e.UserDisplay<br /><span class="text-muted" style="font-size:11px">@e.UserEmail</span></td>
<td style="padding:6px"><span class="chip @RoleChipClass(e.UserRole)">@e.UserRole</span></td>
<td style="padding:6px;font-weight:600">@e.Action</td>
<td style="padding:6px">@e.ClientName</td>
<td style="padding:6px">@string.Join(", ", e.Sites)</td>
<td style="padding:6px;color:var(--text-muted)">@e.Details</td>
</tr>
}
</tbody>
</table>
</div>
<p class="text-muted" style="margin-top:8px;font-size:12px">Showing @_filtered.Count of @_entries.Count entries</p>
}
@code {
private List<AuditEntry> _entries = new();
private List<AuditEntry> _filtered = new();
private bool _loading = true;
private string _filterUser = string.Empty;
private string _filterClient = string.Empty;
private string _filterAction = string.Empty;
protected override async Task OnInitializedAsync()
{
_entries = (await AuditService.GetAllAsync())
.OrderByDescending(e => e.Timestamp)
.ToList();
_loading = false;
ApplyFilters();
}
private void ApplyFilters()
{
_filtered = _entries.Where(e =>
(string.IsNullOrEmpty(_filterUser) || e.UserEmail.Contains(_filterUser, StringComparison.OrdinalIgnoreCase) || e.UserDisplay.Contains(_filterUser, StringComparison.OrdinalIgnoreCase)) &&
(string.IsNullOrEmpty(_filterClient) || e.ClientName.Contains(_filterClient, StringComparison.OrdinalIgnoreCase)) &&
(string.IsNullOrEmpty(_filterAction) || e.Action.Contains(_filterAction, StringComparison.OrdinalIgnoreCase))
).ToList();
}
private static string RoleChipClass(UserRole role) => role switch
{
UserRole.Admin => "chip-red",
UserRole.TechN1 => "chip-green",
_ => "chip-blue"
};
}