mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-03-20 23:52:31 -04:00
Update all admin tables to use new SortableTable component (#220)
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user