diff --git a/src/Pages/AuditTrail.razor b/src/Pages/AuditTrail.razor
new file mode 100644
index 00000000..91ff5461
--- /dev/null
+++ b/src/Pages/AuditTrail.razor
@@ -0,0 +1,433 @@
+@page "/audittrail"
+@using System.Security.Claims
+@using Humanizer
+@using NodeGuard.Data.Models
+@using NodeGuard.Data.Repositories.Interfaces
+@attribute [Authorize]
+
+Audit Trail
+
Audit Trail
+
+
+
+ Audit logs are stored in the database with a retention period of @Constants.AUDIT_LOG_RETENTION_DAYS days.
+ Logs older than this will be automatically cleaned up.
+
+
+
+@if (!_hasFullAccess)
+{
+
+
+ You are viewing your own audit log entries only. FinanceManager and Superadmin roles have access to all audit logs.
+
+
+}
+
+
+
+
+ Action Type
+
+
+
+
+
+ Event Type
+
+
+
+
+
+ Object Type
+
+
+
+ @if (_hasFullAccess)
+ {
+
+
+ User
+
+
+
+ }
+
+
+ From Date
+
+
+
+
+
+ To Date
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @context.Timestamp.Humanize()
+
+
+
+
+
+
+ @context.ActionType.Humanize()
+
+
+
+
+
+
+ @context.EventType.Humanize()
+
+
+
+
+
+ @if (!string.IsNullOrEmpty(context.Username))
+ {
+
+ @context.Username
+
+ }
+ else
+ {
+ System
+ }
+
+
+
+
+ @if (!string.IsNullOrEmpty(context.IpAddress))
+ {
+ @context.IpAddress
+ }
+ else
+ {
+ N/A
+ }
+
+
+
+
+ @context.ObjectAffected.Humanize()
+
+
+
+
+ @if (!string.IsNullOrEmpty(context.ObjectId))
+ {
+
+ @StringHelper.TruncateTail(context.ObjectId, 15)
+
+ }
+ else
+ {
+ N/A
+ }
+
+
+
+
+ @if (!string.IsNullOrEmpty(context.Details))
+ {
+
+ }
+ else
+ {
+ -
+ }
+
+
+
+
+
+ No audit log entries found.
+
+
+
+
+
+
+
+
+
+
+
+
+ Audit Log Details
+
+
+
+ @if (_selectedAuditLog != null)
+ {
+
+
+
+ Timestamp
+ @_selectedAuditLog.Timestamp.ToString("yyyy-MM-dd HH:mm:ss zzz")
+
+
+ Action
+ @_selectedAuditLog.ActionType.Humanize()
+
+
+ Result
+ @_selectedAuditLog.EventType.Humanize()
+
+
+ User
+ @(_selectedAuditLog.Username ?? "System")
+
+
+ User ID
+ @(_selectedAuditLog.UserId ?? "N/A")
+
+
+ IP Address
+ @(_selectedAuditLog.IpAddress ?? "N/A")
+
+
+ Object Type
+ @_selectedAuditLog.ObjectAffected.Humanize()
+
+
+ Object ID
+ @(_selectedAuditLog.ObjectId ?? "N/A")
+
+
+
+ @if (!string.IsNullOrEmpty(_selectedAuditLog.Details))
+ {
+ Additional Details (JSON)
+ @FormatJson(_selectedAuditLog.Details)
+ }
+ }
+
+
+
+
+
+
+
+@inject IAuditLogRepository AuditLogRepository
+@inject IApplicationUserRepository ApplicationUserRepository
+@inject IToastService ToastService
+
+@code {
+ [CascadingParameter]
+ private ApplicationUser? LoggedUser { get; set; }
+
+ [CascadingParameter]
+ private ClaimsPrincipal? ClaimsPrincipal { get; set; }
+
+ private DataGrid? _auditLogDataGrid;
+ private List _auditLogs = new();
+ private int _totalItems;
+ private bool _hasFullAccess = false;
+ private List _users = new();
+
+ // Filters
+ private AuditActionType? _actionTypeFilter;
+ private AuditEventType? _eventTypeFilter;
+ private AuditObjectType? _objectTypeFilter;
+ private string? _userIdFilter;
+ private DateTime? _fromDate;
+ private DateTime? _toDate;
+
+ // Details modal
+ private Modal? _detailsModal;
+ private AuditLog? _selectedAuditLog;
+
+ protected override async Task OnInitializedAsync()
+ {
+ if (LoggedUser == null) return;
+
+ // Check if user has full access (FinanceManager or Superadmin)
+ _hasFullAccess = ClaimsPrincipal != null &&
+ (ClaimsPrincipal.IsInRole(ApplicationUserRole.FinanceManager.ToString()) ||
+ ClaimsPrincipal.IsInRole(ApplicationUserRole.Superadmin.ToString()));
+
+ // Load users list for filter if user has full access
+ if (_hasFullAccess)
+ {
+ _users = await ApplicationUserRepository.GetAll();
+ }
+ }
+
+ private async Task OnReadData(DataGridReadDataEventArgs e)
+ {
+ if (!e.CancellationToken.IsCancellationRequested)
+ {
+ var fromDateOffset = _fromDate.HasValue ? new DateTimeOffset(_fromDate.Value, TimeSpan.Zero) : (DateTimeOffset?)null;
+ var toDateOffset = _toDate.HasValue ? new DateTimeOffset(_toDate.Value.AddDays(1).AddSeconds(-1), TimeSpan.Zero) : (DateTimeOffset?)null;
+
+ // If user doesn't have full access, filter by their user ID
+ // If user has full access, use the selected user filter (if any)
+ var userIdFilter = _hasFullAccess ? _userIdFilter : LoggedUser?.Id;
+
+ var (logs, totalCount) = await AuditLogRepository.GetPaginatedAsync(
+ e.Page,
+ e.PageSize,
+ _actionTypeFilter,
+ _eventTypeFilter,
+ _objectTypeFilter,
+ userIdFilter,
+ fromDateOffset,
+ toDateOffset);
+
+ _auditLogs = logs;
+ _totalItems = totalCount;
+ }
+ }
+
+ private async Task ApplyFilters()
+ {
+ if (_auditLogDataGrid != null)
+ {
+ await _auditLogDataGrid.Reload();
+ }
+ }
+
+ private async Task ClearFilters()
+ {
+ _actionTypeFilter = null;
+ _eventTypeFilter = null;
+ _objectTypeFilter = null;
+ _userIdFilter = null;
+ _fromDate = null;
+ _toDate = null;
+
+ if (_auditLogDataGrid != null)
+ {
+ await _auditLogDataGrid.Reload();
+ }
+ }
+
+ private async Task ShowDetailsModal(AuditLog auditLog)
+ {
+ _selectedAuditLog = auditLog;
+ if (_detailsModal != null)
+ {
+ await _detailsModal.Show();
+ }
+ }
+
+ private async Task CloseDetailsModal()
+ {
+ _selectedAuditLog = null;
+ if (_detailsModal != null)
+ {
+ await _detailsModal.Hide();
+ }
+ }
+
+ private static Color GetActionBadgeColor(AuditActionType actionType)
+ {
+ return actionType switch
+ {
+ AuditActionType.Create => Color.Success,
+ AuditActionType.Update => Color.Info,
+ AuditActionType.Delete => Color.Danger,
+ AuditActionType.Approve => Color.Success,
+ AuditActionType.Reject => Color.Warning,
+ AuditActionType.Cancel => Color.Secondary,
+ AuditActionType.Login => Color.Primary,
+ AuditActionType.Logout => Color.Secondary,
+ AuditActionType.TwoFactorLogin => Color.Primary,
+ AuditActionType.LoginWithRecoveryCode => Color.Warning,
+ AuditActionType.TwoFactorEnabled => Color.Success,
+ AuditActionType.TwoFactorDisabled => Color.Warning,
+ AuditActionType.TwoFactorReset => Color.Warning,
+ AuditActionType.LockUser => Color.Danger,
+ AuditActionType.UnlockUser => Color.Success,
+ AuditActionType.Transfer => Color.Info,
+ AuditActionType.Close => Color.Warning,
+ AuditActionType.ForceClose => Color.Danger,
+ AuditActionType.Block => Color.Danger,
+ AuditActionType.Unblock => Color.Success,
+ _ => Color.Light
+ };
+ }
+
+ private static Color GetEventBadgeColor(AuditEventType eventType)
+ {
+ return eventType switch
+ {
+ AuditEventType.Success => Color.Success,
+ AuditEventType.Failure => Color.Danger,
+ AuditEventType.Attempt => Color.Warning,
+ _ => Color.Light
+ };
+ }
+
+ private static string FormatJson(string json)
+ {
+ try
+ {
+ var element = System.Text.Json.JsonSerializer.Deserialize(json);
+ return System.Text.Json.JsonSerializer.Serialize(element, new System.Text.Json.JsonSerializerOptions { WriteIndented = true });
+ }
+ catch
+ {
+ return json;
+ }
+ }
+}