Merge pull request #193 from lanedirt/80-add-vault-auth-attempt-logging-bugfix

Update admin logs path as /logs folder doesn't work correctly due to …
This commit is contained in:
Leendert de Borst
2024-08-31 15:30:59 +02:00
committed by GitHub
3 changed files with 317 additions and 5 deletions

View File

@@ -48,9 +48,4 @@
<ProjectReference Include="..\Utilities\AliasVault.RazorComponents\AliasVault.RazorComponents.csproj" />
<ProjectReference Include="..\Utilities\Cryptography\Cryptography.csproj" />
</ItemGroup>
<ItemGroup>
<_ContentIncludedByDefault Remove="Main\Components\Refresh\RefreshButton.razor" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,149 @@
@page "/logging/auth"
@using AliasVault.Shared.Models.Enums
@inherits MainBase
<LayoutPageTitle>Auth logs</LayoutPageTitle>
<div class="grid grid-cols-1 px-4 pt-6 xl:grid-cols-3 xl:gap-4 dark:bg-gray-900">
<div class="mb-4 col-span-full xl:mb-2">
<Breadcrumb BreadcrumbItems="BreadcrumbItems" />
<div class="flex items-center justify-between">
<h1 class="text-xl font-semibold text-gray-900 sm:text-2xl dark:text-white">Auth logs</h1>
<RefreshButton OnRefresh="RefreshData" ButtonText="Refresh" />
</div>
<p>This page gives an overview of recent auth attempts.</p>
</div>
</div>
@if (IsLoading)
{
<LoadingIndicator />
}
else
{
<div class="px-4">
<Paginator CurrentPage="CurrentPage" PageSize="PageSize" TotalRecords="TotalRecords" OnPageChanged="HandlePageChanged" />
<div class="mb-4 flex space-x-4">
<div class="flex w-full">
<div class="w-2/3 pr-2">
<input type="text" @bind-value="SearchTerm" @bind-value:event="oninput" id="search" placeholder="Search logs..." class="w-full px-4 py-2 border rounded text-sm text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div class="w-1/3 pl-2">
<select @bind="SelectedEventType" class="w-full px-4 py-2 border rounded text-sm text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500">
<option value="">All event types</option>
@foreach (var eventType in Enum.GetValues<AuthEventType>())
{
<option value="@eventType">@eventType</option>
}
</select>
</div>
</div>
</div>
<table class="w-full text-sm text-left text-gray-500 shadow rounded border">
<thead class="text-xs text-gray-700 uppercase bg-gray-50">
<tr>
<th scope="col" class="px-4 py-3">ID</th>
<th scope="col" class="px-4 py-3">Time</th>
<th scope="col" class="px-4 py-3">Username</th>
<th scope="col" class="px-4 py-3">Event</th>
<th scope="col" class="px-4 py-3">Success</th>
<th scope="col" class="px-4 py-3">IP</th>
</tr>
</thead>
<tbody>
@foreach (var log in LogList)
{
<tr class="bg-white border-b hover:bg-gray-50">
<td class="px-4 py-3 font-medium text-gray-900">@log.Id</td>
<td class="px-4 py-3">@log.Timestamp.ToString("yyyy-MM-dd HH:mm")</td>
<td class="px-4 py-3">@log.Username</td>
<td class="px-4 py-3">@log.EventType</td>
<td class="px-4 py-3"><StatusPill Enabled="log.IsSuccess" TextTrue="Success" TextFalse="Failed" /></td>
<td class="px-4 py-3">@log.IpAddress</td>
</tr>
}
</tbody>
</table>
</div>
}
@code {
private List<AuthLog> LogList { get; set; } = [];
private bool IsLoading { get; set; } = true;
private int CurrentPage { get; set; } = 1;
private int PageSize { get; set; } = 50;
private int TotalRecords { get; set; }
private string _searchTerm = string.Empty;
private string SearchTerm
{
get => _searchTerm;
set
{
if (_searchTerm != value)
{
_searchTerm = value;
_ = RefreshData();
}
}
}
private string _selectedEventType = string.Empty;
private string SelectedEventType
{
get => _selectedEventType;
set
{
if (_selectedEventType != value)
{
_selectedEventType = value;
_ = RefreshData();
}
}
}
/// <inheritdoc />
protected override async Task OnInitializedAsync()
{
await RefreshData();
}
private void HandlePageChanged(int newPage)
{
CurrentPage = newPage;
_ = RefreshData();
}
private async Task RefreshData()
{
IsLoading = true;
StateHasChanged();
var query = DbContext.AuthLogs.AsQueryable();
if (!string.IsNullOrEmpty(SearchTerm))
{
query = query.Where(x => EF.Functions.Like(x.Username.ToLower(), "%" + SearchTerm.ToLower() + "%"));
}
if (!string.IsNullOrEmpty(SelectedEventType))
{
var success = Enum.TryParse<AuthEventType>(SelectedEventType, out var eventType);
if (success)
{
query = query.Where(x => x.EventType == eventType);
}
}
TotalRecords = await query.CountAsync();
LogList = await query
.OrderByDescending(x => x.Timestamp)
.Skip((CurrentPage - 1) * PageSize)
.Take(PageSize)
.ToListAsync();
IsLoading = false;
StateHasChanged();
}
}

View File

@@ -0,0 +1,168 @@
@page "/logging/general"
@inherits MainBase
<LayoutPageTitle>System logs</LayoutPageTitle>
<div class="grid grid-cols-1 px-4 pt-6 xl:grid-cols-3 xl:gap-4 dark:bg-gray-900">
<div class="mb-4 col-span-full xl:mb-2">
<Breadcrumb BreadcrumbItems="BreadcrumbItems" />
<div class="flex items-center justify-between">
<h1 class="text-xl font-semibold text-gray-900 sm:text-2xl dark:text-white">General logs</h1>
<RefreshButton OnRefresh="RefreshData" ButtonText="Refresh" />
</div>
<p>This page gives an overview of recent system logs.</p>
</div>
</div>
@if (IsLoading)
{
<LoadingIndicator />
}
else
{
<div class="px-4">
<Paginator CurrentPage="CurrentPage" PageSize="PageSize" TotalRecords="TotalRecords" OnPageChanged="HandlePageChanged" />
<div class="mb-4 flex space-x-4">
<div class="flex w-full">
<div class="w-2/3 pr-2">
<input type="text" @bind-value="SearchTerm" @bind-value:event="oninput" id="search" placeholder="Search logs..." class="w-full px-4 py-2 border rounded text-sm text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div class="w-1/3 pl-2">
<select @bind="SelectedServiceName" class="w-full px-4 py-2 border rounded text-sm text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500">
<option value="">All Services</option>
@foreach (var service in ServiceNames)
{
<option value="@service">@service</option>
}
</select>
</div>
</div>
</div>
<table class="w-full text-sm text-left text-gray-500 shadow rounded border">
<thead class="text-xs text-gray-700 uppercase bg-gray-50">
<tr>
<th scope="col" class="px-4 py-3">ID</th>
<th scope="col" class="px-4 py-3">Time</th>
<th scope="col" class="px-4 py-3">Application</th>
<th scope="col" class="px-4 py-3">Level</th>
<th scope="col" class="px-4 py-3">Message</th>
</tr>
</thead>
<tbody>
@foreach (var log in LogList)
{
<tr class="bg-white border-b hover:bg-gray-50">
<td class="px-4 py-3 font-medium text-gray-900">@log.Id</td>
<td class="px-4 py-3">@log.TimeStamp.ToString("yyyy-MM-dd HH:mm")</td>
<td class="px-4 py-3">@log.Application</td>
@{
string bgColor = log.Level switch
{
"Information" => "bg-blue-500",
"Error" => "bg-red-500",
"Warning" => "bg-yellow-500",
"Debug" => "bg-green-500",
_ => "bg-gray-500"
};
}
<td class="px-4 py-3">
<span class="px-2 py-1 rounded-full text-white @bgColor">
@log.Level
</span>
</td>
<td class="px-4 py-3" title="@log.Exception">
@if (log.SourceContext.Length > 0)
{
<span>@log.SourceContext: </span>
}
@log.Message
</td>
</tr>
}
</tbody>
</table>
</div>
}
@code {
private List<Log> LogList { get; set; } = [];
private bool IsLoading { get; set; } = true;
private int CurrentPage { get; set; } = 1;
private int PageSize { get; set; } = 50;
private int TotalRecords { get; set; }
private string _searchTerm = string.Empty;
private string SearchTerm
{
get => _searchTerm;
set
{
if (_searchTerm != value)
{
_searchTerm = value;
_ = RefreshData();
}
}
}
private string _selectedServiceName = string.Empty;
private string SelectedServiceName
{
get => _selectedServiceName;
set
{
if (_selectedServiceName != value)
{
_selectedServiceName = value;
_ = RefreshData();
}
}
}
private List<string> ServiceNames { get; set; } = [];
/// <inheritdoc />
protected override async Task OnInitializedAsync()
{
ServiceNames = await DbContext.Logs.Select(l => l.Application).Distinct().ToListAsync();
await RefreshData();
}
private void HandlePageChanged(int newPage)
{
CurrentPage = newPage;
_ = RefreshData();
}
private async Task RefreshData()
{
IsLoading = true;
StateHasChanged();
var query = DbContext.Logs.AsQueryable();
if (!string.IsNullOrEmpty(SearchTerm))
{
query = query.Where(x => EF.Functions.Like(x.Application.ToLower(), "%" + SearchTerm.ToLower() + "%") ||
EF.Functions.Like(x.Message.ToLower(), "%" + SearchTerm.ToLower() + "%") ||
EF.Functions.Like(x.Level.ToLower(), "%" + SearchTerm.ToLower() + "%"));
}
if (!string.IsNullOrEmpty(SelectedServiceName))
{
query = query.Where(x => x.Application == SelectedServiceName);
}
TotalRecords = await query.CountAsync();
LogList = await query
.OrderByDescending(x => x.Id)
.Skip((CurrentPage - 1) * PageSize)
.Take(PageSize)
.ToListAsync();
IsLoading = false;
StateHasChanged();
}
}