Spaces:
Runtime error
Runtime error
task-tracking-system / TaskTrackingSystem.WebApp /Components /Pages /Features /Audit /AuditLogs.razor
Download TaskTrackingSystem.WebApp/Components/Pages/Features/Audit/AuditLogs.razor from Yuruyuruu/task-tracking-system: direct link, hf CLI and curl.
- Browser
- Download file 11.4 kB
-
https://huggingface.co/spaces/Yuruyuruu/task-tracking-system/resolve/main/TaskTrackingSystem.WebApp/Components/Pages/Features/Audit/AuditLogs.razor
- Command line
-
hf download hf://spaces/Yuruyuruu/task-tracking-system/TaskTrackingSystem.WebApp/Components/Pages/Features/Audit/AuditLogs.razor
-
curl -L -o AuditLogs.razor https://huggingface.co/spaces/Yuruyuruu/task-tracking-system/resolve/main/TaskTrackingSystem.WebApp/Components/Pages/Features/Audit/AuditLogs.razor
11.4 kB
| @page "/audit-logs" | |
| @using Microsoft.AspNetCore.Authorization | |
| @using Microsoft.AspNetCore.Components.Authorization | |
| @using TaskTrackingSystem.Shared.Models.AuditLog | |
| @using TaskTrackingSystem.WebApp.Components.Partial | |
| @rendermode @(new InteractiveServerRenderMode(prerender: false)) | |
| @attribute [Authorize] | |
| @inject ApiClientService ApiClient | |
| @inject IJSRuntime JS | |
| @inject AuthenticationStateProvider AuthStateProvider | |
| <PageTitle>@AppLocalization.PageTitle("auditLogs", "Audit Logs")</PageTitle> | |
| <div class="space-y-6"> | |
| <!-- Page Header --> | |
| <div class="flex items-center justify-between"> | |
| <div> | |
| <h2 class="text-3xl font-bold tracking-tight text-slate-900">@AppLocalization.PageTitle("auditLogs", "Audit Logs")</h2> | |
| <p class="text-slate-500 mt-1">@AppLocalization.PageDescription("auditLogs", "Review system activity and changes.")</p> | |
| </div> | |
| </div> | |
| <!-- Search Panel --> | |
| <div class="rounded-xl border border-slate-200 bg-white p-4 shadow-sm mb-4"> | |
| <div class="flex items-center gap-3"> | |
| <div class="relative flex-1 max-w-md"> | |
| <input @bind="searchInput" @bind:event="oninput" @onkeyup="HandleKeyUp" type="text" placeholder="@AppLocalization.Text("common.searchByUser", "Search by user, action, section, or details...")" | |
| class="w-full rounded-lg border border-slate-200 px-3 py-2 text-sm text-slate-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-violet-600 focus:border-transparent transition-all" /> | |
| </div> | |
| <button @onclick="ApplySearch" class="inline-flex items-center rounded-lg bg-violet-600 px-4 py-2.5 text-sm font-medium text-white hover:bg-violet-700 transition-colors shadow-sm"> | |
| <i data-lucide="search" class="h-4 w-4 mr-2"></i> | |
| @AppLocalization.Text("action.search", "Search") | |
| </button> | |
| <button @onclick="ResetSearch" title="@AppLocalization.Text("action.reset", "Reset")" aria-label="@AppLocalization.Text("action.reset", "Reset")" class="inline-flex items-center justify-center rounded-lg border border-slate-200 bg-white p-2.5 text-slate-700 hover:bg-slate-50 transition-colors shadow-sm"> | |
| <i data-lucide="rotate-ccw" class="h-4 w-4 text-slate-500"></i> | |
| </button> | |
| </div> | |
| </div> | |
| @if (isLoading) | |
| { | |
| <LoadingSpinner Message="@AppLocalization.Text("common.loadingAuditLogs", "Loading audit logs...")" /> | |
| } | |
| else | |
| { | |
| <!-- Logs Table --> | |
| <div class="rounded-xl border border-slate-200 bg-white shadow-sm overflow-hidden"> | |
| <table class="w-full"> | |
| <thead> | |
| <tr class="border-b border-slate-100 bg-slate-50/60"> | |
| <th class="px-6 py-3 text-left text-xs font-semibold text-slate-500 uppercase tracking-wider">@AppLocalization.Text("common.index", "No.")</th> | |
| <th class="px-6 py-3 text-left text-xs font-semibold text-slate-500 uppercase tracking-wider">@AppLocalization.Text("common.date", "Timestamp")</th> | |
| <th class="px-6 py-3 text-left text-xs font-semibold text-slate-500 uppercase tracking-wider">@AppLocalization.Text("common.user", "User")</th> | |
| <th class="px-6 py-3 text-left text-xs font-semibold text-slate-500 uppercase tracking-wider">@AppLocalization.Text("common.module", "Module")</th> | |
| <th class="px-6 py-3 text-left text-xs font-semibold text-slate-500 uppercase tracking-wider">@AppLocalization.Text("common.actions", "Action")</th> | |
| <th class="px-6 py-3 text-left text-xs font-semibold text-slate-500 uppercase tracking-wider">@AppLocalization.Text("common.description", "Description")</th> | |
| <th class="px-6 py-3 text-left text-xs font-semibold text-slate-500 uppercase tracking-wider">@AppLocalization.Text("field.ipAddress", "IP Address")</th> | |
| </tr> | |
| </thead> | |
| <tbody class="divide-y divide-slate-100 font-sans"> | |
| @if (PagedLogs.Any()) | |
| { | |
| @foreach (var entry in PagedLogs.Select((log, index) => (log, index))) | |
| { | |
| var log = entry.log; | |
| <tr class="hover:bg-slate-50/50 transition-colors"> | |
| <td class="px-6 py-4 text-sm font-semibold text-slate-500 align-top">@AppLocalization.Number((currentPage - 1) * pageSize + entry.index + 1)</td> | |
| <td class="px-6 py-4 text-xs font-mono text-slate-500 whitespace-nowrap align-top"> | |
| @AppLocalization.LocalizeDigits(log.CreatedAt.ToString("dd-MM-yyyy HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture)) | |
| </td> | |
| <td class="px-6 py-4 align-top"> | |
| <div class="flex items-center gap-2"> | |
| <span class="flex h-6 w-6 items-center justify-center rounded-full bg-violet-100 text-violet-700 text-[10px] font-bold shrink-0"> | |
| @(GetAuditUserName(log).Length > 0 ? GetAuditUserName(log)[0].ToString().ToUpper() : "S") | |
| </span> | |
| <div> | |
| <p class="text-xs font-semibold text-slate-800">@GetAuditUserName(log)</p> | |
| <p class="text-[10px] text-slate-400">@@@log.Username</p> | |
| </div> | |
| </div> | |
| </td> | |
| <td class="px-6 py-4 align-top"> | |
| <span class="inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-bold bg-slate-100 text-slate-700 border border-slate-200"> | |
| @AppLocalization.ModuleName(log.Module) | |
| </span> | |
| </td> | |
| <td class="px-6 py-4 align-top"> | |
| <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-[10px] font-bold @GetActionBadge(log.Action)"> | |
| @AppLocalization.ActionName(log.Action) | |
| </span> | |
| </td> | |
| <td class="px-6 py-4 text-xs text-slate-700 whitespace-normal break-words align-top"> | |
| @AppLocalization.AuditDescription(log.Description) | |
| </td> | |
| <td class="px-6 py-4 text-xs font-mono text-slate-500 whitespace-nowrap align-top"> | |
| @(string.IsNullOrWhiteSpace(log.IpAddress) ? AppLocalization.Text("common.na", "N/A") : AppLocalization.LocalizeDigits(log.IpAddress)) | |
| </td> | |
| </tr> | |
| } | |
| } | |
| else | |
| { | |
| <EmptyState ColSpan="7" Icon="history" Title="@AppLocalization.Text("common.noResults", "No audit logs")" Subtitle="@AppLocalization.Text("common.noResults", "No security events or administrator operations were found.")" /> | |
| } | |
| </tbody> | |
| </table> | |
| <!-- Pagination --> | |
| <Pagination CurrentPage="currentPage" | |
| TotalPages="TotalPages" | |
| PageSize="pageSize" | |
| TotalCount="TotalCount" | |
| OnPageChanged="HandlePageChanged" | |
| OnPageSizeChanged="HandlePageSizeChanged" /> | |
| </div> | |
| } | |
| </div> | |
| @code { | |
| private bool isLoading = true; | |
| private List<AuditLogDto> auditLogs = new(); | |
| private string searchInput = ""; | |
| // Pagination state | |
| private int currentPage = 1; | |
| private int pageSize = 15; | |
| private int totalCount = 0; | |
| private int totalPages = 0; | |
| private int TotalPages => totalPages; | |
| private int TotalCount => totalCount; | |
| private IEnumerable<AuditLogDto> PagedLogs => auditLogs; | |
| protected override async Task OnInitializedAsync() | |
| { | |
| await LoadLogs(); | |
| } | |
| protected override async Task OnAfterRenderAsync(bool firstRender) | |
| { | |
| await JS.InvokeVoidAsync("initIcons"); | |
| } | |
| private async Task LoadLogs() | |
| { | |
| isLoading = true; | |
| var client = ApiClient.CreateClient(); | |
| try | |
| { | |
| var url = BuildAuditLogsUrl(); | |
| var result = await client.GetFromJsonAsync<PagedResult<AuditLogDto>>(url, Serialization.CaseInsensitive); | |
| auditLogs = result?.Items ?? new(); | |
| totalCount = result?.TotalCount ?? 0; | |
| totalPages = result?.TotalPages ?? 0; | |
| currentPage = result?.Page ?? currentPage; | |
| pageSize = result?.PageSize ?? pageSize; | |
| } | |
| catch (Exception ex) | |
| { | |
| Console.WriteLine($"Load audit logs error: {ex.Message}"); | |
| } | |
| finally | |
| { | |
| isLoading = false; | |
| } | |
| } | |
| private async Task ApplySearch() | |
| { | |
| currentPage = 1; | |
| await LoadLogs(); | |
| } | |
| private async Task ResetSearch() | |
| { | |
| searchInput = ""; | |
| currentPage = 1; | |
| await LoadLogs(); | |
| } | |
| private async Task HandleKeyUp(KeyboardEventArgs e) | |
| { | |
| if (e.Key == "Enter") | |
| { | |
| await ApplySearch(); | |
| } | |
| } | |
| private string GetAuditUserName(AuditLogDto log) | |
| { | |
| if (string.Equals(log.UserFullName, "System Admin", StringComparison.OrdinalIgnoreCase)) | |
| { | |
| return AppLocalization.Text("common.systemAdmin", "System Admin"); | |
| } | |
| if (string.Equals(log.UserFullName, "System", StringComparison.OrdinalIgnoreCase)) | |
| { | |
| return AppLocalization.Text("notification.system", "System"); | |
| } | |
| return Content.GetText(log.UserFullName, log.UserFullNameMy); | |
| } | |
| private string BuildAuditLogsUrl() | |
| { | |
| var query = new List<string> | |
| { | |
| $"page={currentPage}", | |
| $"limit={pageSize}" | |
| }; | |
| if (!string.IsNullOrWhiteSpace(searchInput)) | |
| { | |
| query.Add($"search={Uri.EscapeDataString(searchInput.Trim())}"); | |
| } | |
| return $"AuditLog?{string.Join("&", query)}"; | |
| } | |
| private async Task HandlePageChanged(int page) | |
| { | |
| currentPage = page; | |
| await LoadLogs(); | |
| } | |
| private async Task HandlePageSizeChanged(int size) | |
| { | |
| pageSize = size; | |
| currentPage = 1; | |
| await LoadLogs(); | |
| } | |
| private string GetActionBadge(string action) | |
| { | |
| return action.ToLower() switch | |
| { | |
| "create" => "bg-emerald-50 text-emerald-700 border border-emerald-200", | |
| "update" => "bg-amber-50 text-amber-700 border border-amber-200", | |
| "delete" => "bg-rose-50 text-rose-700 border border-rose-200", | |
| "assignaccess" => "bg-violet-50 text-violet-700 border border-violet-200", | |
| _ => "bg-blue-50 text-blue-700 border border-blue-200" | |
| }; | |
| } | |
| } | |