Layout update (#98)

This commit is contained in:
Leendert de Borst
2024-07-12 12:45:42 +02:00
parent 4ef0373d31
commit c6ef654c87
15 changed files with 189 additions and 152 deletions

View File

@@ -60,9 +60,6 @@
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="Main\Pages\Sync\StatusMessages\ErrorVaultDecrypt.razor" />
<AdditionalFiles Include="Main\Pages\Sync\StatusMessages\PendingMigrations.razor" />
<AdditionalFiles Include="Main\Pages\Sync\StatusMessages\VaultDecryptionProgress.razor" />
<Content Update="wwwroot\appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -83,16 +80,4 @@
<ProjectReference Include="..\Utilities\CsvImportExport\CsvImportExport.csproj" />
<ProjectReference Include="..\Utilities\FaviconExtractor\FaviconExtractor.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="nginx.conf">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<_ContentIncludedByDefault Remove="Main\Layout\StatusMessages\ErrorVaultDecrypt.razor" />
<_ContentIncludedByDefault Remove="Main\Layout\StatusMessages\PendingMigrations.razor" />
<_ContentIncludedByDefault Remove="Main\Layout\StatusMessages\VaultDecryptionProgress.razor" />
</ItemGroup>
</Project>

View File

@@ -80,7 +80,7 @@
catch
{
// If in release mode show a generic error.
_serverValidationErrors.AddError("An error occurred while processing the login request. Try again (later).");
ServerValidationErrors.AddError("An error occurred while processing the login request. Try again (later).");
}
#endif
finally

View File

@@ -14,7 +14,7 @@
</p>
<FullScreenLoadingIndicator @ref="LoadingIndicator" />
<ServerValidationErrors @ref="_serverValidationErrors" />
<ServerValidationErrors @ref="ServerValidationErrors" />
<EditForm Model="UnlockModel" OnValidSubmit="HandleLogin" class="mt-8 space-y-6">
<DataAnnotationsValidator/>
@@ -38,7 +38,7 @@
private string? Email { get; set; }
private readonly UnlockModel UnlockModel = new();
private FullScreenLoadingIndicator LoadingIndicator = new();
private ServerValidationErrors _serverValidationErrors = new();
private ServerValidationErrors ServerValidationErrors = new();
/// <inheritdoc />
protected override async Task OnInitializedAsync()
@@ -71,14 +71,14 @@
}
LoadingIndicator.Show();
_serverValidationErrors.Clear();
ServerValidationErrors.Clear();
try
{
var errors = await ProcessLoginAsync(Email, UnlockModel.Password);
foreach (var error in errors)
{
_serverValidationErrors.AddError(error);
ServerValidationErrors.AddError(error);
}
StateHasChanged();
}
@@ -86,13 +86,13 @@
catch (Exception ex)
{
// If in debug mode show the actual exception.
_serverValidationErrors.AddError(ex.ToString());
ServerValidationErrors.AddError(ex.ToString());
}
#else
catch
{
// If in release mode show a generic error.
_serverValidationErrors.AddError("An error occurred while processing the login request. Try again (later).");
ServerValidationErrors.AddError("An error occurred while processing the login request. Try again (later).");
}
#endif
finally

View File

@@ -4,67 +4,73 @@
@if (ShowComponent)
{
<div class="flex justify-between">
<h3 class="mb-4 text-xl font-semibold dark:text-white">Email</h3>
<button @onclick="LoadRecentEmailsAsync" type="button" class="text-blue-700 border border-blue-700 hover:bg-blue-700 hover:text-white focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-full text-sm p-2.5 text-center inline-flex items-center dark:border-blue-500 dark:text-blue-500 dark:hover:text-white dark:focus:ring-blue-800 dark:hover:bg-blue-500">
Refresh
</button>
</div>
<div class="p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800">
<div class="flex justify-between">
<h3 class="mb-4 text-xl font-semibold dark:text-white">Email</h3>
<button @onclick="LoadRecentEmailsAsync" type="button" class="text-blue-700 border border-blue-700 hover:bg-blue-700 hover:text-white focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-full text-sm p-2.5 text-center inline-flex items-center dark:border-blue-500 dark:text-blue-500 dark:hover:text-white dark:focus:ring-blue-800 dark:hover:bg-blue-500">
Refresh
</button>
</div>
@if (IsLoading)
{
<LoadingIndicator/>
}
else if (MailboxEmails.Count == 0)
{
<div>No emails found.</div>
}
else
{
<div class="flex flex-col mt-6">
<div class="overflow-x-auto rounded-lg">
<div class="inline-block min-w-full align-middle">
<div class="overflow-hidden shadow sm:rounded-lg">
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-600">
<thead class="bg-gray-50 dark:bg-gray-700">
<tr>
<th scope="col" class="p-4 text-xs font-medium tracking-wider text-left text-gray-500 uppercase dark:text-white">
Subject
</th>
<th scope="col" class="p-4 text-xs font-medium tracking-wider text-left text-gray-500 uppercase dark:text-white">
Date &amp; Time
</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-gray-800">
@foreach (var mail in MailboxEmails)
{
<tr class="hover:bg-gray-50 dark:hover:bg-gray-600">
<td class="p-4 text-sm font-normal text-gray-900 whitespace-nowrap dark:text-white">
<a target="_blank" href="https://spamok.com/@mail.ToLocal/@mail.Id">@(mail.Subject.Substring(0, mail.Subject.Length > 30 ? 30 : mail.Subject.Length))...</a>
</td>
<td class="p-4 text-sm font-normal text-gray-500 whitespace-nowrap dark:text-gray-400">
<a target="_blank" href="https://spamok.com/@mail.ToLocal/@mail.Id">@mail.DateSystem</a>
</td>
@if (IsLoading)
{
<LoadingIndicator/>
}
else if (MailboxEmails.Count == 0)
{
<div>No emails found.</div>
}
else
{
<div class="flex flex-col mt-6">
<div class="overflow-x-auto rounded-lg">
<div class="inline-block min-w-full align-middle">
<div class="overflow-hidden shadow sm:rounded-lg">
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-600">
<thead class="bg-gray-50 dark:bg-gray-700">
<tr>
<th scope="col" class="p-4 text-xs font-medium tracking-wider text-left text-gray-500 uppercase dark:text-white">
Subject
</th>
<th scope="col" class="p-4 text-xs font-medium tracking-wider text-left text-gray-500 uppercase dark:text-white">
Date &amp; Time
</th>
</tr>
}
</tbody>
</table>
</thead>
<tbody class="bg-white dark:bg-gray-800">
@foreach (var mail in MailboxEmails)
{
<tr class="hover:bg-gray-50 dark:hover:bg-gray-600">
<td class="p-4 text-sm font-normal text-gray-900 whitespace-nowrap dark:text-white">
<a target="_blank" href="https://spamok.com/@mail.ToLocal/@mail.Id">@(mail.Subject.Substring(0, mail.Subject.Length > 30 ? 30 : mail.Subject.Length))...</a>
</td>
<td class="p-4 text-sm font-normal text-gray-500 whitespace-nowrap dark:text-gray-400">
<a target="_blank" href="https://spamok.com/@mail.ToLocal/@mail.Id">@mail.DateSystem</a>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
}
}
</div>
}
@code {
/// <summary>
/// The email address to show recent emails for.
/// </summary>
[Parameter]
public string Email { get; set; } = string.Empty;
[Parameter]
public List<MailboxEmailApiModel> MailboxEmails { get; set; } = new List<MailboxEmailApiModel>();
public bool IsLoading { get; set; } = true;
private List<MailboxEmailApiModel> MailboxEmails { get; set; } = new();
private bool IsLoading { get; set; } = true;
private bool ShowComponent { get; set; } = false;
/// <inheritdoc />
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
@@ -76,6 +82,7 @@
}
}
/// <inheritdoc />
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);

View File

@@ -13,9 +13,6 @@
<div class="hidden justify-between items-center w-full lg:flex lg:w-auto lg:order-1">
<ul class="flex flex-col mt-4 space-x-6 text-sm font-medium lg:flex-row xl:space-x-8 lg:mt-0">
<NavLink href="/" class="block rounded text-gray-700 hover:text-primary-700 dark:text-gray-400 dark:hover:text-white" ActiveClass="text-primary-700 dark:text-primary-500" Match="NavLinkMatch.All">
Home
</NavLink>
<NavLink href="/credentials" class="block text-gray-700 hover:text-primary-700 dark:text-gray-400 dark:hover:text-white" ActiveClass="text-primary-700 dark:text-primary-500" Match="NavLinkMatch.All">
Credentials
</NavLink>

View File

@@ -89,10 +89,6 @@ else
<div class="mb-4">
<button type="button" class="px-4 py-2 text-white bg-blue-600 rounded-lg hover:bg-blue-700 focus:ring-4 focus:ring-blue-300 dark:bg-blue-500 dark:hover:bg-blue-600 dark:focus:ring-blue-800" @onclick="GenerateRandomIdentity">Generate Random Identity</button>
<button type="submit" class="px-4 py-2 text-white bg-green-600 rounded-lg hover:bg-green-700 focus:ring-4 focus:ring-green-300 dark:bg-green-500 dark:hover:bg-green-600 dark:focus:ring-green-800">Save Credentials</button>
@if (IsIdentityLoading)
{
<p>Loading...</p>
}
</div>
<div class="grid gap-6">
<div class="col-span-6 sm:col-span-3">
@@ -178,7 +174,6 @@ else
private bool EditMode { get; set; }
private bool Loading { get; set; } = true;
private CredentialEdit Obj { get; set; } = new();
private bool IsIdentityLoading { get; set; }
/// <inheritdoc />
protected override void OnInitialized()
@@ -266,7 +261,7 @@ else
private async Task GenerateRandomIdentity()
{
IsIdentityLoading = true;
GlobalLoadingSpinner.Show();
StateHasChanged();
// Generate a random identity using the IIdentityGenerator implementation.
@@ -292,7 +287,7 @@ else
// Generate password
GenerateRandomPassword();
IsIdentityLoading = false;
GlobalLoadingSpinner.Hide();
StateHasChanged();
}

View File

@@ -0,0 +1,86 @@
@page "/credentials"
@inherits MainBase
@inject CredentialService CredentialService
<LayoutPageTitle>Home</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">Credentials</h1>
<a href="/add-credentials" class="px-4 py-2 text-white bg-primary-600 rounded-lg hover:bg-primary-700 focus:ring-4 focus:ring-primary-300 dark:bg-primary-500 dark:hover:bg-primary-600 dark:focus:ring-primary-800">
+ Add new credentials
</a>
</div>
<p>Find all of your credentials below.</p>
</div>
</div>
@if (IsLoading)
{
<LoadingIndicator />
}
@if (Credentials.Count == 0)
{
<div class="p-4 mx-4 mt-4 bg-white border border-gray-200 rounded-lg shadow-sm dark:border-gray-700 dark:bg-gray-800">
<div class="px-4 py-2 text-gray-400 rounded">
<p class="text-gray-500 dark:text-gray-400">You have no credentials yet. Create your first one now by clicking on the + button in the top right corner.</p>
</div>
</div>
}
else
{
<div class="grid gap-4 px-4 mb-4 md:grid-cols-4 xl:grid-cols-6">
@foreach (var credential in Credentials)
{
<CredentialCard Obj="@credential"/>
}
</div>
}
@code {
private bool IsLoading { get; set; } = true;
private List<CredentialListEntry> Credentials { get; set; } = new();
/// <inheritdoc />
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
// Redirect to /credentials.
NavigationManager.NavigateTo("/credentials");
}
/// <inheritdoc />
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
if (firstRender)
{
await LoadCredentialsAsync();
}
}
private async Task LoadCredentialsAsync()
{
IsLoading = true;
StateHasChanged();
// Load the aliases from the webapi via AliasService.
var credentialListEntries = await CredentialService.GetListAsync();
if (credentialListEntries is null)
{
// Error loading aliases.
GlobalNotificationService.AddErrorMessage("Failed to load credentials.", true);
return;
}
Credentials = credentialListEntries;
IsLoading = false;
StateHasChanged();
}
}

View File

@@ -42,9 +42,7 @@ else
</div>
</div>
</div>
<div class="p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800">
<RecentEmails Email="@Alias.Alias.Email" />
</div>
<RecentEmails Email="@Alias.Alias.Email" />
@if (Alias.Notes != null && Alias.Notes.Length > 0)
{
<div class="p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800">

View File

@@ -1,67 +1,13 @@
@page "/"
@page "/credentials"
@inherits MainBase
@inject CredentialService CredentialService
<LayoutPageTitle>Home</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">Credentials</h1>
<a href="/add-credentials" class="px-4 py-2 text-white bg-primary-600 rounded-lg hover:bg-primary-700 focus:ring-4 focus:ring-primary-300 dark:bg-primary-500 dark:hover:bg-primary-600 dark:focus:ring-primary-800">
+ Add new credentials
</a>
</div>
<p>Find all of your credentials below.</p>
</div>
</div>
@if (IsLoading)
{
<LoadingIndicator />
}
<div class="grid gap-4 px-4 mb-4 md:grid-cols-4 xl:grid-cols-6">
@foreach (var credential in Credentials)
{
<CredentialCard Obj="@credential"/>
}
</div>
@code {
private bool IsLoading { get; set; } = true;
private List<CredentialListEntry> Credentials { get; set; } = new();
/// <inheritdoc />
protected override async Task OnAfterRenderAsync(bool firstRender)
protected override async Task OnInitializedAsync()
{
await base.OnAfterRenderAsync(firstRender);
await base.OnInitializedAsync();
if (firstRender)
{
await LoadCredentialsAsync();
}
}
private async Task LoadCredentialsAsync()
{
IsLoading = true;
StateHasChanged();
// Load the aliases from the webapi via AliasService.
var credentialListEntries = await CredentialService.GetListAsync();
if (credentialListEntries is null)
{
// Error loading aliases.
GlobalNotificationService.AddErrorMessage("Failed to load credentials.", true);
return;
}
Credentials = credentialListEntries;
IsLoading = false;
StateHasChanged();
// Redirect to /credentials.
NavigationManager.NavigateTo("/credentials");
}
}

View File

@@ -7,7 +7,6 @@
using AliasVault.WebApp;
using AliasVault.WebApp.Providers;
using AliasVault.WebApp.Services.Auth;
using Blazored.LocalStorage;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Web;
@@ -17,7 +16,7 @@ var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.Services.AddLogging(logging =>
{
logging.SetMinimumLevel(LogLevel.Information);
logging.SetMinimumLevel(LogLevel.Warning);
logging.AddFilter("Microsoft.AspNetCore.Identity.DataProtectorTokenProvider", LogLevel.Error);
logging.AddFilter("Microsoft.AspNetCore.Identity.UserManager", LogLevel.Error);
});

View File

@@ -631,6 +631,14 @@ video {
grid-column: 1 / -1;
}
.m-6 {
margin: 1.5rem;
}
.m-4 {
margin: 1rem;
}
.mx-3 {
margin-left: 0.75rem;
margin-right: 0.75rem;
@@ -646,6 +654,11 @@ video {
margin-bottom: 1rem;
}
.mx-4 {
margin-left: 1rem;
margin-right: 1rem;
}
.-ml-1 {
margin-left: -0.25rem;
}
@@ -682,6 +695,10 @@ video {
margin-left: 0.75rem;
}
.ml-5 {
margin-left: 1.25rem;
}
.ml-auto {
margin-left: auto;
}
@@ -726,6 +743,14 @@ video {
margin-top: 2rem;
}
.ml-4 {
margin-left: 1rem;
}
.ml-6 {
margin-left: 1.5rem;
}
.block {
display: block;
}
@@ -766,6 +791,10 @@ video {
height: 3rem;
}
.h-32 {
height: 8rem;
}
.h-4 {
height: 1rem;
}

View File

@@ -145,8 +145,7 @@ public class AliasClientDbContext : DbContext
{
var optionsBuilder = new DbContextOptionsBuilder<AliasClientDbContext>();
optionsBuilder.UseSqlite(connection);
optionsBuilder.LogTo(logAction, new[] { DbLoggerCategory.Database.Command.Name }, LogLevel.Information);
optionsBuilder.LogTo(logAction, new[] { DbLoggerCategory.Database.Command.Name }, LogLevel.Warning);
return optionsBuilder.Options;
}

View File

@@ -53,7 +53,7 @@
<Content Include="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Update="appsettings.Development.json" Condition="Exists('appsettings.Development.json')">
<Content Include="appsettings.Development.json" Condition="Exists('appsettings.Development.json')">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>

View File

@@ -241,10 +241,6 @@ public class PlaywrightTest
// If password is not set by test explicitly, generate a random password.
TestUserPassword = TestUserPassword.Length > 0 ? TestUserPassword : Guid.NewGuid().ToString();
// Check that we get redirected to /user/login when accessing the root URL and not authenticated.
await Page.GotoAsync(AppBaseUrl);
await WaitForURLAsync("**/user/login");
// Try to register a new account.
var registerButton = Page.Locator("a[href='/user/register']");
await registerButton.ClickAsync();

View File

@@ -37,7 +37,7 @@ public class DbPersistTest : PlaywrightTest
await RefreshPageAndUnlockVault();
// Wait for the credentials page to load again.
await WaitForURLAsync("**/credentials/**", "View credentials entry");
await WaitForURLAsync("**/credentials");
// Check if the service name is still present in the content.
pageContent = await Page.TextContentAsync("body");