Update all admin tables to use new SortableTable component (#220)

This commit is contained in:
Leendert de Borst
2024-10-07 20:03:23 +02:00
parent bbb168d764
commit fa664ea918
2 changed files with 189 additions and 80 deletions

View File

@@ -1,4 +1,5 @@
@page "/emails"
@using AliasVault.RazorComponents.Tables
@inherits MainBase
<LayoutPageTitle>Emails</LayoutPageTitle>
@@ -6,7 +7,7 @@
<PageHeader
BreadcrumbItems="@BreadcrumbItems"
Title="Emails"
Description="This page gives an overview of recently received mails by this AliasVault server.">
Description="This page gives an overview of recently received mails by this AliasVault server. Note that all email fields except 'To' are encrypted with the public key of the user and cannot be decrypted by the server.">
<CustomActions>
<RefreshButton OnClick="RefreshData" ButtonText="Refresh" />
</CustomActions>
@@ -21,59 +22,66 @@ else
<div class="overflow-x-auto px-4">
<Paginator CurrentPage="CurrentPage" PageSize="PageSize" TotalRecords="TotalRecords" OnPageChanged="HandlePageChanged" />
<table class="w-full text-sm text-left text-gray-500 shadow rounded border mt-8">
<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">From</th>
<th scope="col" class="px-4 py-3">To</th>
<th scope="col" class="px-4 py-3">Subject</th>
<th scope="col" class="px-4 py-3">Preview</th>
<th scope="col" class="px-4 py-3">Attachments</th>
<SortableTable TItem="Log" Columns="@_tableColumns" SortColumn="@SortColumn" SortDirection="@SortDirection" OnSortChanged="HandleSortChanged">
@foreach (var email in EmailList)
{
<tr class="bg-white border-b hover:bg-gray-50">
<td class="px-4 py-3 font-medium text-gray-900">
@email.Id
</td>
<td class="px-4 py-3">
@email.DateSystem.ToString("yyyy-MM-dd HH:mm")
</td>
<td class="px-4 py-3">
@(email.FromLocal.Length > 15 ? email.FromLocal.Substring(0, 15) : email.FromLocal)@@@(email.FromDomain.Length > 15 ? email.FromDomain.Substring(0, 15) : email.FromDomain)
</td>
<td class="px-4 py-3">
@email.ToLocal@@@email.ToDomain
</td>
<td class="px-4 py-3">
@(email.Subject.Length > 30 ? email.Subject.Substring(0, 30) : email.Subject)
</td>
<td class="px-4 py-3">
<span class="line-clamp-1">
@(email.MessagePreview?.Length > 30 ? email.MessagePreview.Substring(0, 30) : email.MessagePreview)
</span>
</td>
<td class="px-4 py-3">
@email.Attachments.Count
</td>
</tr>
</thead>
<tbody>
@foreach (var email in EmailList)
{
<tr class="bg-white border-b hover:bg-gray-50">
<td class="px-4 py-3 font-medium text-gray-900">
@email.Id
</td>
<td class="px-4 py-3">
@email.DateSystem.ToString("yyyy-MM-dd HH:mm")
</td>
<td class="px-4 py-3">
@(email.FromLocal.Length > 15 ? email.FromLocal.Substring(0, 15) : email.FromLocal)@@@(email.FromDomain.Length > 15 ? email.FromDomain.Substring(0, 15) : email.FromDomain)
</td>
<td class="px-4 py-3">
@email.ToLocal@@@email.ToDomain
</td>
<td class="px-4 py-3">
@(email.Subject.Length > 30 ? email.Subject.Substring(0, 30) : email.Subject)
</td>
<td class="px-4 py-3">
<span class="line-clamp-1">
@(email.MessagePreview?.Length > 30 ? email.MessagePreview.Substring(0, 30) : email.MessagePreview)
</span>
</td>
<td class="px-4 py-3">
@email.Attachments.Count
</td>
</tr>
}
</tbody>
</table>
}
</SortableTable>
</div>
}
@code {
private readonly List<TableColumn> _tableColumns = [
new TableColumn { Title = "ID", PropertyName = "Id" },
new TableColumn { Title = "Time", PropertyName = "DateSystem" },
new TableColumn { Title = "From", PropertyName = "From" },
new TableColumn { Title = "To", PropertyName = "To" },
new TableColumn { Title = "Subject", PropertyName = "Subject" },
new TableColumn { Title = "Preview", PropertyName = "MessagePreview" },
new TableColumn { Title = "Attachments", PropertyName = "Attachments" },
];
private List<Email> EmailList { 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 SortColumn { get; set; } = "Id";
private SortDirection SortDirection { get; set; } = SortDirection.Descending;
private async Task HandleSortChanged((string column, SortDirection direction) sort)
{
SortColumn = sort.column;
SortDirection = sort.direction;
await RefreshData();
}
/// <inheritdoc />
protected override async Task OnAfterRenderAsync(bool firstRender)
{
@@ -94,9 +102,53 @@ else
IsLoading = true;
StateHasChanged();
TotalRecords = await DbContext.Emails.CountAsync();
EmailList = await DbContext.Emails
.OrderByDescending(x => x.DateSystem)
IQueryable<Email> query = DbContext.Emails;
// Apply sort
switch (SortColumn)
{
case "Id":
query = SortDirection == SortDirection.Ascending
? query.OrderBy(x => x.Id)
: query.OrderByDescending(x => x.Id);
break;
case "DateSystem":
query = SortDirection == SortDirection.Ascending
? query.OrderBy(x => x.DateSystem)
: query.OrderByDescending(x => x.DateSystem);
break;
case "From":
query = SortDirection == SortDirection.Ascending
? query.OrderBy(x => x.FromLocal + "@" + x.FromDomain)
: query.OrderByDescending(x => x.FromLocal + "@" + x.FromDomain);
break;
case "To":
query = SortDirection == SortDirection.Ascending
? query.OrderBy(x => x.ToLocal + "@" + x.ToDomain)
: query.OrderByDescending(x => x.ToLocal + "@" + x.ToDomain);
break;
case "Subject":
query = SortDirection == SortDirection.Ascending
? query.OrderBy(x => x.Subject)
: query.OrderByDescending(x => x.Subject);
break;
case "MessagePreview":
query = SortDirection == SortDirection.Ascending
? query.OrderBy(x => x.MessagePreview)
: query.OrderByDescending(x => x.MessagePreview);
break;
case "Attachments":
query = SortDirection == SortDirection.Ascending
? query.OrderBy(x => x.Attachments.Count)
: query.OrderByDescending(x => x.Attachments.Count);
break;
default:
query = query.OrderByDescending(x => x.DateSystem);
break;
}
TotalRecords = await query.CountAsync();
EmailList = await query
.Skip((CurrentPage - 1) * PageSize)
.Take(PageSize)
.ToListAsync();

View File

@@ -1,4 +1,5 @@
@page "/users"
@using AliasVault.RazorComponents.Tables
@inherits MainBase
<LayoutPageTitle>Users</LayoutPageTitle>
@@ -24,43 +25,40 @@ else
<div class="mb-4">
<input type="text" @bind-value="SearchTerm" @bind-value:event="oninput" id="search" placeholder="Search users..." 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>
<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">Registered</th>
<th scope="col" class="px-4 py-3">Username</th>
<th scope="col" class="px-4 py-3"># Vaults</th>
<th scope="col" class="px-4 py-3"># Email claims</th>
<th scope="col" class="px-4 py-3">Storage</th>
<th scope="col" class="px-4 py-3">2FA</th>
<th scope="col" class="px-4 py-3">Last vault update</th>
<th scope="col" class="px-4 py-3">Actions</th>
</tr>
</thead>
<tbody id="logTableBody">
@foreach (var user in UserList)
{
<tr class="bg-white border-b hover:bg-gray-50">
<td class="px-4 py-3 font-medium text-gray-900">@user.Id</td>
<td class="px-4 py-3">@user.CreatedAt.ToString("yyyy-MM-dd HH:mm")</td>
<td class="px-4 py-3">@user.UserName</td>
<td class="px-4 py-3">@user.VaultCount</td>
<td class="px-4 py-3">@user.EmailClaimCount</td>
<td class="px-4 py-3">@Math.Round((double)user.VaultStorageInKb / 1024, 1) MB</td>
<td class="px-4 py-3"><StatusPill Enabled="user.TwoFactorEnabled" /></td>
<td class="px-4 py-3">@user.LastVaultUpdate.ToString("yyyy-MM-dd HH:mm")</td>
<td class="px-4 py-3">
<LinkButton Color="primary" Href="@($"users/{user.Id}")" Text="View" />
</td>
</tr>
}
</tbody>
</table>
<SortableTable TItem="Log" Columns="@_tableColumns" SortColumn="@SortColumn" SortDirection="@SortDirection" OnSortChanged="HandleSortChanged">
@foreach (var user in UserList)
{
<tr class="bg-white border-b hover:bg-gray-50">
<td class="px-4 py-3 font-medium text-gray-900">@user.Id</td>
<td class="px-4 py-3">@user.CreatedAt.ToString("yyyy-MM-dd HH:mm")</td>
<td class="px-4 py-3">@user.UserName</td>
<td class="px-4 py-3">@user.VaultCount</td>
<td class="px-4 py-3">@user.EmailClaimCount</td>
<td class="px-4 py-3">@Math.Round((double)user.VaultStorageInKb / 1024, 1) MB</td>
<td class="px-4 py-3"><StatusPill Enabled="user.TwoFactorEnabled" /></td>
<td class="px-4 py-3">@user.LastVaultUpdate.ToString("yyyy-MM-dd HH:mm")</td>
<td class="px-4 py-3">
<LinkButton Color="primary" Href="@($"users/{user.Id}")" Text="View" />
</td>
</tr>
}
</SortableTable>
</div>
}
@code {
private readonly List<TableColumn> _tableColumns = [
new TableColumn { Title = "ID", PropertyName = "Id" },
new TableColumn { Title = "Registered", PropertyName = "CreatedAt" },
new TableColumn { Title = "Username", PropertyName = "UserName" },
new TableColumn { Title = "# Vaults", PropertyName = "VaultCount" },
new TableColumn { Title = "# Email claims", PropertyName = "EmailClaimCount" },
new TableColumn { Title = "Storage", PropertyName = "VaultStorageInKb" },
new TableColumn { Title = "2FA", PropertyName = "TwoFactorEnabled" },
new TableColumn { Title = "LastVaultUpdate", PropertyName = "LastVaultUpdate" },
];
private List<UserViewModel> UserList { get; set; } = [];
private bool IsLoading { get; set; } = true;
private int CurrentPage { get; set; } = 1;
@@ -81,6 +79,16 @@ else
}
}
private string SortColumn { get; set; } = "CreatedAt";
private SortDirection SortDirection { get; set; } = SortDirection.Descending;
private async Task HandleSortChanged((string column, SortDirection direction) sort)
{
SortColumn = sort.column;
SortDirection = sort.direction;
await RefreshData();
}
/// <inheritdoc />
protected override async Task OnAfterRenderAsync(bool firstRender)
{
@@ -108,10 +116,59 @@ else
query = query.Where(x => EF.Functions.Like(x.UserName!.ToLower(), "%" + SearchTerm.ToLower() + "%"));
}
// Apply sort.
switch (SortColumn)
{
case "Id":
query = SortDirection == SortDirection.Ascending
? query.OrderBy(x => x.Id)
: query.OrderByDescending(x => x.Id);
break;
case "CreatedAt":
query = SortDirection == SortDirection.Ascending
? query.OrderBy(x => x.CreatedAt)
: query.OrderByDescending(x => x.CreatedAt);
break;
case "UserName":
query = SortDirection == SortDirection.Ascending
? query.OrderBy(x => x.UserName)
: query.OrderByDescending(x => x.UserName);
break;
case "VaultCount":
query = SortDirection == SortDirection.Ascending
? query.OrderBy(x => x.Vaults.Count())
: query.OrderByDescending(x => x.Vaults.Count());
break;
case "EmailClaimCount":
query = SortDirection == SortDirection.Ascending
? query.OrderBy(x => x.EmailClaims.Count())
: query.OrderByDescending(x => x.EmailClaims.Count());
break;
case "VaultStorageInKb":
query = SortDirection == SortDirection.Ascending
? query.OrderBy(x => x.Vaults.Sum(v => v.FileSize))
: query.OrderByDescending(x => x.Vaults.Sum(v => v.FileSize));
break;
case "TwoFactorEnabled":
query = SortDirection == SortDirection.Ascending
? query.OrderBy(x => x.TwoFactorEnabled)
: query.OrderByDescending(x => x.TwoFactorEnabled);
break;
case "LastVaultUpdate":
query = SortDirection == SortDirection.Ascending
? query.OrderBy(x => x.Vaults.Max(v => v.CreatedAt))
: query.OrderByDescending(x => x.Vaults.Max(v => v.CreatedAt));
break;
default:
query = SortDirection == SortDirection.Ascending
? query.OrderBy(x => x.Id)
: query.OrderByDescending(x => x.Id);
break;
}
TotalRecords = await query.CountAsync();
var users = await query
.OrderBy(x => x.CreatedAt)
.Skip((CurrentPage - 1) * PageSize)
.Take(PageSize)
.Select(u => new