From 4c5e312f119ab45ab7c73397b9b898b070d8e4d4 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Mon, 7 Oct 2024 13:11:11 +0200 Subject: [PATCH 01/11] Update welcome.razor margins (#220) --- src/AliasVault.Client/Main/Pages/Welcome.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AliasVault.Client/Main/Pages/Welcome.razor b/src/AliasVault.Client/Main/Pages/Welcome.razor index edd2000ea..327f58ae6 100644 --- a/src/AliasVault.Client/Main/Pages/Welcome.razor +++ b/src/AliasVault.Client/Main/Pages/Welcome.razor @@ -1,7 +1,7 @@ @page "/welcome" @inherits MainBase -
+

Welcome to AliasVault

It looks like you are new here. The instructions on this page will help to get you started.

From 35f35b8bbee50e7dbf238f06cdfa9ee041da6bbb Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Mon, 7 Oct 2024 14:34:53 +0200 Subject: [PATCH 02/11] Refactor page headers in client to shared component (#220) --- .../Main/Components/Layout/Breadcrumb.razor | 52 ------------- .../Main/Models/BreadcrumbItem.cs | 24 ------ .../Main/Pages/Account/ManageLayout.razor | 2 +- src/AliasVault.Admin/Main/Pages/Emails.razor | 2 +- src/AliasVault.Admin/Main/Pages/Home.razor | 2 +- .../Main/Pages/Logging/Auth.razor | 2 +- .../Main/Pages/Logging/General.razor | 2 +- .../Main/Pages/Users/Delete.razor | 2 +- .../Main/Pages/Users/Users.razor | 2 +- .../Main/Pages/Users/View.razor | 4 +- src/AliasVault.Admin/Main/_Imports.razor | 1 + .../Main/Pages/Credentials/AddEdit.razor | 42 +++------- .../Main/Pages/Credentials/Delete.razor | 18 ++--- .../Main/Pages/Credentials/Home.razor | 18 ++--- .../Main/Pages/Credentials/View.razor | 36 ++++----- .../Main/Pages/Emails/Home.razor | 18 ++--- .../Main/Pages/Settings/General.razor | 12 ++- .../Settings/Security/ChangePassword.razor | 2 +- .../Pages/Settings/Security/Disable2Fa.razor | 2 +- .../Pages/Settings/Security/Enable2Fa.razor | 2 +- .../Pages/Settings/Security/Security.razor | 18 ++--- .../Main/Pages/Settings/Vault.razor | 14 ++-- .../Main/Pages/Test/Test1.razor | 14 ++-- .../Main/Pages/Test/Test2.razor | 14 ++-- src/AliasVault.Client/_Imports.razor | 1 + src/AliasVault.Client/tailwind.config.js | 1 + .../wwwroot/css/tailwind.css | 78 +++---------------- .../Buttons/Button.razor | 14 +--- .../Buttons/ButtonStyles.cs | 38 +++++++++ .../Buttons/CancelButton.razor | 26 +++++++ .../Buttons/ConfirmButton.razor | 26 +++++++ .../Buttons/LinkButton.razor | 46 +++++++++++ .../Headings}/Breadcrumb.razor | 5 +- .../Headings/H1.razor | 9 +++ .../Headings/PageHeader.razor | 43 ++++++++++ .../Models/BreadcrumbItem.cs | 0 36 files changed, 297 insertions(+), 295 deletions(-) delete mode 100644 src/AliasVault.Admin/Main/Components/Layout/Breadcrumb.razor delete mode 100644 src/AliasVault.Admin/Main/Models/BreadcrumbItem.cs create mode 100644 src/Shared/AliasVault.RazorComponents/Buttons/ButtonStyles.cs create mode 100644 src/Shared/AliasVault.RazorComponents/Buttons/CancelButton.razor create mode 100644 src/Shared/AliasVault.RazorComponents/Buttons/ConfirmButton.razor create mode 100644 src/Shared/AliasVault.RazorComponents/Buttons/LinkButton.razor rename src/{AliasVault.Client/Main/Components/Layout => Shared/AliasVault.RazorComponents/Headings}/Breadcrumb.razor (96%) create mode 100644 src/Shared/AliasVault.RazorComponents/Headings/H1.razor create mode 100644 src/Shared/AliasVault.RazorComponents/Headings/PageHeader.razor rename src/{AliasVault.Client/Main => Shared/AliasVault.RazorComponents}/Models/BreadcrumbItem.cs (100%) diff --git a/src/AliasVault.Admin/Main/Components/Layout/Breadcrumb.razor b/src/AliasVault.Admin/Main/Components/Layout/Breadcrumb.razor deleted file mode 100644 index 8221c3020..000000000 --- a/src/AliasVault.Admin/Main/Components/Layout/Breadcrumb.razor +++ /dev/null @@ -1,52 +0,0 @@ -@inherits ComponentBase - - - -@code { - /// - /// Gets or sets the list of breadcrumb items. - /// - [Parameter] - public List BreadcrumbItems { get; set; } = new(); - - /// - protected override void OnInitialized() - { - base.OnInitialized(); - // Remove first item if it is the home page - if (BreadcrumbItems.Any() && BreadcrumbItems[0].DisplayName == "Home") - { - BreadcrumbItems.RemoveAt(0); - } - } -} diff --git a/src/AliasVault.Admin/Main/Models/BreadcrumbItem.cs b/src/AliasVault.Admin/Main/Models/BreadcrumbItem.cs deleted file mode 100644 index 2e18b9a56..000000000 --- a/src/AliasVault.Admin/Main/Models/BreadcrumbItem.cs +++ /dev/null @@ -1,24 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) lanedirt. All rights reserved. -// Licensed under the MIT license. See LICENSE.md file in the project root for full license information. -// -//----------------------------------------------------------------------- - -namespace AliasVault.Admin.Main.Models; - -/// -/// Breadcrumb item model. -/// -public class BreadcrumbItem -{ - /// - /// Gets or sets the display name. - /// - public string? DisplayName { get; set; } - - /// - /// Gets or sets the URL. - /// - public string? Url { get; set; } -} diff --git a/src/AliasVault.Admin/Main/Pages/Account/ManageLayout.razor b/src/AliasVault.Admin/Main/Pages/Account/ManageLayout.razor index f4e2ae308..24c8e77ef 100644 --- a/src/AliasVault.Admin/Main/Pages/Account/ManageLayout.razor +++ b/src/AliasVault.Admin/Main/Pages/Account/ManageLayout.razor @@ -5,7 +5,7 @@
-

Manage account

+

Manage account

Manage your profile here.

diff --git a/src/AliasVault.Admin/Main/Pages/Emails.razor b/src/AliasVault.Admin/Main/Pages/Emails.razor index be6c33298..0745cbcc2 100644 --- a/src/AliasVault.Admin/Main/Pages/Emails.razor +++ b/src/AliasVault.Admin/Main/Pages/Emails.razor @@ -7,7 +7,7 @@
-

Emails

+

Emails

This page gives an overview of recently received mails by this AliasVault server.

diff --git a/src/AliasVault.Admin/Main/Pages/Home.razor b/src/AliasVault.Admin/Main/Pages/Home.razor index 60deb63cb..99c7b2c2d 100644 --- a/src/AliasVault.Admin/Main/Pages/Home.razor +++ b/src/AliasVault.Admin/Main/Pages/Home.razor @@ -7,7 +7,7 @@
-

AliasVault Admin

+

AliasVault Admin

Welcome to the AliasVault admin portal.

diff --git a/src/AliasVault.Admin/Main/Pages/Logging/Auth.razor b/src/AliasVault.Admin/Main/Pages/Logging/Auth.razor index b44a8d349..5a52b2e7e 100644 --- a/src/AliasVault.Admin/Main/Pages/Logging/Auth.razor +++ b/src/AliasVault.Admin/Main/Pages/Logging/Auth.razor @@ -8,7 +8,7 @@
-

Auth logs

+

Auth logs

diff --git a/src/AliasVault.Admin/Main/Pages/Logging/General.razor b/src/AliasVault.Admin/Main/Pages/Logging/General.razor index 0c131e3f2..3ce7a5294 100644 --- a/src/AliasVault.Admin/Main/Pages/Logging/General.razor +++ b/src/AliasVault.Admin/Main/Pages/Logging/General.razor @@ -7,7 +7,7 @@
-

General logs

+

General logs

diff --git a/src/AliasVault.Admin/Main/Pages/Users/Delete.razor b/src/AliasVault.Admin/Main/Pages/Users/Delete.razor index 639964ef2..9912b5c1a 100644 --- a/src/AliasVault.Admin/Main/Pages/Users/Delete.razor +++ b/src/AliasVault.Admin/Main/Pages/Users/Delete.razor @@ -7,7 +7,7 @@
-

Delete user

+

Delete user

You can delete the user below.

diff --git a/src/AliasVault.Admin/Main/Pages/Users/Users.razor b/src/AliasVault.Admin/Main/Pages/Users/Users.razor index c4a134d93..f1d3fff3a 100644 --- a/src/AliasVault.Admin/Main/Pages/Users/Users.razor +++ b/src/AliasVault.Admin/Main/Pages/Users/Users.razor @@ -7,7 +7,7 @@
-

Users

+

Users

This page gives an overview of all registered users and the associated vaults.

diff --git a/src/AliasVault.Admin/Main/Pages/Users/View.razor b/src/AliasVault.Admin/Main/Pages/Users/View.razor index 9890718bd..79be5899f 100644 --- a/src/AliasVault.Admin/Main/Pages/Users/View.razor +++ b/src/AliasVault.Admin/Main/Pages/Users/View.razor @@ -14,7 +14,7 @@ else
- +
diff --git a/src/AliasVault.Admin/Main/_Imports.razor b/src/AliasVault.Admin/Main/_Imports.razor index 50d9a4ba8..4633343e6 100644 --- a/src/AliasVault.Admin/Main/_Imports.razor +++ b/src/AliasVault.Admin/Main/_Imports.razor @@ -20,6 +20,7 @@ @using AliasVault.RazorComponents @using AliasVault.RazorComponents.Alerts @using AliasVault.RazorComponents.Buttons +@using AliasVault.RazorComponents.Headings @using AliasVault.Admin.Main.Models @using AliasVault.Admin.Main.Pages @using AliasVault.Admin.Services diff --git a/src/AliasVault.Client/Main/Pages/Credentials/AddEdit.razor b/src/AliasVault.Client/Main/Pages/Credentials/AddEdit.razor index 262b1adc9..923819be7 100644 --- a/src/AliasVault.Client/Main/Pages/Credentials/AddEdit.razor +++ b/src/AliasVault.Client/Main/Pages/Credentials/AddEdit.razor @@ -11,39 +11,15 @@ @inject IJSRuntime JSRuntime @implements IAsyncDisposable -@if (EditMode) -{ - Edit credentials -} -else { - Add credentials -} - -
-
- -
- @if (EditMode) - { -

Edit credentials

- } - else { -

Add credentials

- } -
- - -
-
- @if (EditMode) - { -

Edit the existing credentials entry below.

- } - else { -

Create a new credentials entry below.

- } -
-
+ + + Save Credentials + Cancel + + @if (Loading) { diff --git a/src/AliasVault.Client/Main/Pages/Credentials/Delete.razor b/src/AliasVault.Client/Main/Pages/Credentials/Delete.razor index 6864ce3cf..3c737e5a5 100644 --- a/src/AliasVault.Client/Main/Pages/Credentials/Delete.razor +++ b/src/AliasVault.Client/Main/Pages/Credentials/Delete.razor @@ -4,15 +4,11 @@ Delete credentials entry -
-
- -
-

Delete credentials

-
-

You can delete a credentials entry below.

-
-
+ + @if (IsLoading) { @@ -55,8 +51,8 @@ else protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); - BreadcrumbItems.Add(new BreadcrumbItem { Url = "credentials/" + Id, DisplayName = "View Credentials Entry" }); - BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = "Delete credentials" }); + BreadcrumbItems.Add(new BreadcrumbItem { Url = "credentials/" + Id, DisplayName = "View credentials entry" }); + BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = "Delete credential" }); } /// diff --git a/src/AliasVault.Client/Main/Pages/Credentials/Home.razor b/src/AliasVault.Client/Main/Pages/Credentials/Home.razor index 757a28f78..2023e6336 100644 --- a/src/AliasVault.Client/Main/Pages/Credentials/Home.razor +++ b/src/AliasVault.Client/Main/Pages/Credentials/Home.razor @@ -4,16 +4,14 @@ Home -
-
- -
-

Credentials

- -
-

Find all of your credentials below.

-
-
+ + + + + @if (IsLoading) { diff --git a/src/AliasVault.Client/Main/Pages/Credentials/View.razor b/src/AliasVault.Client/Main/Pages/Credentials/View.razor index 61712752c..f2aa12008 100644 --- a/src/AliasVault.Client/Main/Pages/Credentials/View.razor +++ b/src/AliasVault.Client/Main/Pages/Credentials/View.razor @@ -10,24 +10,24 @@ } else { + + + + + + +
- - -
@@ -48,7 +48,7 @@ else } - @if (Alias.Attachments != null && Alias.Attachments.Count > 0) + @if (Alias.Attachments.Count > 0) { } diff --git a/src/AliasVault.Client/Main/Pages/Emails/Home.razor b/src/AliasVault.Client/Main/Pages/Emails/Home.razor index 453d6c789..6d8b7bdfa 100644 --- a/src/AliasVault.Client/Main/Pages/Emails/Home.razor +++ b/src/AliasVault.Client/Main/Pages/Emails/Home.razor @@ -17,16 +17,14 @@ } -
-
- -
-

Emails

- -
-

Below you can find all recent emails sent to one of the email addresses used in your credentials.

-
-
+ + + + + @if (IsLoading) { diff --git a/src/AliasVault.Client/Main/Pages/Settings/General.razor b/src/AliasVault.Client/Main/Pages/Settings/General.razor index c359c0279..e651985f7 100644 --- a/src/AliasVault.Client/Main/Pages/Settings/General.razor +++ b/src/AliasVault.Client/Main/Pages/Settings/General.razor @@ -3,13 +3,11 @@ General settings -
-
- -

General settings

-

Configure general AliasVault settings.

-
-
+ +

Email Settings

diff --git a/src/AliasVault.Client/Main/Pages/Settings/Security/ChangePassword.razor b/src/AliasVault.Client/Main/Pages/Settings/Security/ChangePassword.razor index 21f5adc71..52b8b9e65 100644 --- a/src/AliasVault.Client/Main/Pages/Settings/Security/ChangePassword.razor +++ b/src/AliasVault.Client/Main/Pages/Settings/Security/ChangePassword.razor @@ -11,7 +11,7 @@
-

Change password

+

Change password

Changing your master password also changes the vault encryption keys. It is advised to periodically change your master password to keep your vaults secure.

diff --git a/src/AliasVault.Client/Main/Pages/Settings/Security/Disable2Fa.razor b/src/AliasVault.Client/Main/Pages/Settings/Security/Disable2Fa.razor index d8ed9bb3a..a65862c40 100644 --- a/src/AliasVault.Client/Main/Pages/Settings/Security/Disable2Fa.razor +++ b/src/AliasVault.Client/Main/Pages/Settings/Security/Disable2Fa.razor @@ -13,7 +13,7 @@ else
-

Disable two-factor authentication

+

Disable two-factor authentication

Disabling two-factor authentication means you will be able to login with only your password.

diff --git a/src/AliasVault.Client/Main/Pages/Settings/Security/Enable2Fa.razor b/src/AliasVault.Client/Main/Pages/Settings/Security/Enable2Fa.razor index 5f3b4a35c..2c360051f 100644 --- a/src/AliasVault.Client/Main/Pages/Settings/Security/Enable2Fa.razor +++ b/src/AliasVault.Client/Main/Pages/Settings/Security/Enable2Fa.razor @@ -8,7 +8,7 @@
-

Enable two-factor authentication

+

Enable two-factor authentication

Enable two-factor authentication to increase the security of your vaults.

diff --git a/src/AliasVault.Client/Main/Pages/Settings/Security/Security.razor b/src/AliasVault.Client/Main/Pages/Settings/Security/Security.razor index 2c1a72e3e..3ac386699 100644 --- a/src/AliasVault.Client/Main/Pages/Settings/Security/Security.razor +++ b/src/AliasVault.Client/Main/Pages/Settings/Security/Security.razor @@ -4,16 +4,14 @@ Security settings -
-
- -
-

Security settings

- -
-

Configure security settings.

-
-
+ + + + + diff --git a/src/AliasVault.Client/Main/Pages/Settings/Vault.razor b/src/AliasVault.Client/Main/Pages/Settings/Vault.razor index 550a687eb..d4580262c 100644 --- a/src/AliasVault.Client/Main/Pages/Settings/Vault.razor +++ b/src/AliasVault.Client/Main/Pages/Settings/Vault.razor @@ -5,15 +5,11 @@ Vault settings -
-
- -
-

Vault settings

-
-

On this page you can configure your vault settings.

-
-
+ +

Export vault

diff --git a/src/AliasVault.Client/Main/Pages/Test/Test1.razor b/src/AliasVault.Client/Main/Pages/Test/Test1.razor index cd4408a0e..e82467dd0 100644 --- a/src/AliasVault.Client/Main/Pages/Test/Test1.razor +++ b/src/AliasVault.Client/Main/Pages/Test/Test1.razor @@ -4,15 +4,11 @@ Test webapi call 1 -
-
- -
-

Test webapi call 1

-
-

Test webapi call 1.

-
-
+ + @if (IsLoading) { diff --git a/src/AliasVault.Client/Main/Pages/Test/Test2.razor b/src/AliasVault.Client/Main/Pages/Test/Test2.razor index d6dfac6ce..a633ec842 100644 --- a/src/AliasVault.Client/Main/Pages/Test/Test2.razor +++ b/src/AliasVault.Client/Main/Pages/Test/Test2.razor @@ -4,15 +4,11 @@ Test webapi call 2 -
-
- -
-

Test webapi call 2

-
-

Test webapi call 2.

-
-
+ + @if (IsLoading) { diff --git a/src/AliasVault.Client/_Imports.razor b/src/AliasVault.Client/_Imports.razor index 6793fed06..9006a1adb 100644 --- a/src/AliasVault.Client/_Imports.razor +++ b/src/AliasVault.Client/_Imports.razor @@ -27,6 +27,7 @@ @using AliasVault.RazorComponents @using AliasVault.RazorComponents.Alerts @using AliasVault.RazorComponents.Buttons +@using AliasVault.RazorComponents.Headings @using Microsoft.AspNetCore.Components.Authorization @using Microsoft.AspNetCore.Components.WebAssembly.Authentication @using Blazored.LocalStorage diff --git a/src/AliasVault.Client/tailwind.config.js b/src/AliasVault.Client/tailwind.config.js index d02ab3413..4b67e5747 100644 --- a/src/AliasVault.Client/tailwind.config.js +++ b/src/AliasVault.Client/tailwind.config.js @@ -3,6 +3,7 @@ module.exports = { content: [ './**/*.html', './**/*.razor', + '../Shared/AliasVault.RazorComponents/**/*.cs', '../Shared/AliasVault.RazorComponents/**/*.razor', ], safelist: [ diff --git a/src/AliasVault.Client/wwwroot/css/tailwind.css b/src/AliasVault.Client/wwwroot/css/tailwind.css index b88d6885c..dc72fc79c 100644 --- a/src/AliasVault.Client/wwwroot/css/tailwind.css +++ b/src/AliasVault.Client/wwwroot/css/tailwind.css @@ -554,40 +554,6 @@ video { --tw-contain-style: ; } -.container { - width: 100%; -} - -@media (min-width: 640px) { - .container { - max-width: 640px; - } -} - -@media (min-width: 768px) { - .container { - max-width: 768px; - } -} - -@media (min-width: 1024px) { - .container { - max-width: 1024px; - } -} - -@media (min-width: 1280px) { - .container { - max-width: 1280px; - } -} - -@media (min-width: 1536px) { - .container { - max-width: 1536px; - } -} - .sr-only { position: absolute; width: 1px; @@ -600,6 +566,10 @@ video { border-width: 0; } +.visible { + visibility: visible; +} + .static { position: static; } @@ -1072,6 +1042,12 @@ video { margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); } +.space-x-3 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.75rem * var(--tw-space-x-reverse)); + margin-left: calc(0.75rem * calc(1 - var(--tw-space-x-reverse))); +} + .space-x-4 > :not([hidden]) ~ :not([hidden]) { --tw-space-x-reverse: 0; margin-right: calc(1rem * var(--tw-space-x-reverse)); @@ -1517,10 +1493,6 @@ video { padding-right: 0.75rem; } -.pt-10 { - padding-top: 2.5rem; -} - .pt-16 { padding-top: 4rem; } @@ -1913,11 +1885,6 @@ video { background-color: rgb(220 38 38 / var(--tw-bg-opacity)); } -.hover\:bg-red-700:hover { - --tw-bg-opacity: 1; - background-color: rgb(185 28 28 / var(--tw-bg-opacity)); -} - .hover\:bg-red-800:hover { --tw-bg-opacity: 1; background-color: rgb(153 27 27 / var(--tw-bg-opacity)); @@ -2119,11 +2086,6 @@ video { background-color: rgb(17 24 39 / var(--tw-bg-opacity)); } -.dark\:bg-green-500:is(.dark *) { - --tw-bg-opacity: 1; - background-color: rgb(34 197 94 / var(--tw-bg-opacity)); -} - .dark\:bg-green-600:is(.dark *) { --tw-bg-opacity: 1; background-color: rgb(22 163 74 / var(--tw-bg-opacity)); @@ -2139,11 +2101,6 @@ video { background-color: rgb(214 131 56 / var(--tw-bg-opacity)); } -.dark\:bg-red-500:is(.dark *) { - --tw-bg-opacity: 1; - background-color: rgb(239 68 68 / var(--tw-bg-opacity)); -} - .dark\:bg-red-600:is(.dark *) { --tw-bg-opacity: 1; background-color: rgb(220 38 38 / var(--tw-bg-opacity)); @@ -2267,11 +2224,6 @@ video { background-color: rgb(55 65 81 / var(--tw-bg-opacity)); } -.dark\:hover\:bg-green-600:hover:is(.dark *) { - --tw-bg-opacity: 1; - background-color: rgb(22 163 74 / var(--tw-bg-opacity)); -} - .dark\:hover\:bg-green-700:hover:is(.dark *) { --tw-bg-opacity: 1; background-color: rgb(21 128 61 / var(--tw-bg-opacity)); @@ -2287,11 +2239,6 @@ video { background-color: rgb(184 112 47 / var(--tw-bg-opacity)); } -.dark\:hover\:bg-red-600:hover:is(.dark *) { - --tw-bg-opacity: 1; - background-color: rgb(220 38 38 / var(--tw-bg-opacity)); -} - .dark\:hover\:bg-red-700:hover:is(.dark *) { --tw-bg-opacity: 1; background-color: rgb(185 28 28 / var(--tw-bg-opacity)); @@ -2569,11 +2516,6 @@ video { .lg\:gap-4 { gap: 1rem; } - - .lg\:px-0 { - padding-left: 0px; - padding-right: 0px; - } } @media (min-width: 1280px) { diff --git a/src/Shared/AliasVault.RazorComponents/Buttons/Button.razor b/src/Shared/AliasVault.RazorComponents/Buttons/Button.razor index a89ebbdbc..c473cca9e 100644 --- a/src/Shared/AliasVault.RazorComponents/Buttons/Button.razor +++ b/src/Shared/AliasVault.RazorComponents/Buttons/Button.razor @@ -52,17 +52,9 @@ /// A string containing the CSS classes for the button. private string GetButtonClasses() { - var baseClasses = "flex center items-center px-3 py-2 text-sm font-medium text-white rounded-lg focus:outline-none focus:ring-4"; - var colorClasses = Color switch - { - "primary" => "bg-primary-700 hover:bg-primary-800 focus:ring-primary-300 dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800", - "danger" => "bg-red-700 hover:bg-red-800 focus:ring-red-300 dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-800", - "success" => "bg-green-700 hover:bg-green-800 focus:ring-green-300 dark:bg-green-600 dark:hover:bg-green-700 dark:focus:ring-green-800", - "secondary" => "bg-secondary-700 hover:bg-secondary-800 focus:ring-secondary-300 dark:bg-secondary-600 dark:hover:bg-secondary-700 dark:focus:ring-secondary-800", - _ => "bg-gray-700 hover:bg-gray-800 focus:ring-gray-300 dark:bg-gray-600 dark:hover:bg-gray-700 dark:focus:ring-gray-800" - }; - var disabledClasses = IsDisabled ? "bg-gray-400 cursor-not-allowed" : ""; + var colorClasses = ButtonStyles.GetColorClasses(Color); + var disabledClasses = IsDisabled ? ButtonStyles.DisabledClasses : ""; - return $"{baseClasses} {colorClasses} {disabledClasses} {AdditionalClasses}".Trim(); + return $"{ButtonStyles.BaseClasses} {colorClasses} {disabledClasses} {AdditionalClasses}".Trim(); } } diff --git a/src/Shared/AliasVault.RazorComponents/Buttons/ButtonStyles.cs b/src/Shared/AliasVault.RazorComponents/Buttons/ButtonStyles.cs new file mode 100644 index 000000000..4c38344dc --- /dev/null +++ b/src/Shared/AliasVault.RazorComponents/Buttons/ButtonStyles.cs @@ -0,0 +1,38 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) lanedirt. All rights reserved. +// Licensed under the MIT license. See LICENSE.md file in the project root for full license information. +// +//----------------------------------------------------------------------- + +namespace AliasVault.RazorComponents.Buttons; + +/// +/// A static class that provides CSS classes for buttons. +/// +public static class ButtonStyles +{ + /// + /// Gets the base CSS classes for buttons. + /// + public static string BaseClasses => "flex center items-center px-3 py-2 text-sm font-medium text-white rounded-lg focus:outline-none focus:ring-4"; + + /// + /// Gets the CSS classes for a disabled button. + /// + public static string DisabledClasses => "bg-gray-400 cursor-not-allowed"; + + /// + /// Gets the color-specific CSS classes for a button based on the provided color. + /// + /// The color name for the button (e.g., "primary", "danger", "success", "secondary"). + /// A string containing the appropriate CSS classes for the specified color. + public static string GetColorClasses(string color) => color switch + { + "primary" => "bg-primary-700 hover:bg-primary-800 focus:ring-primary-300 dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800", + "danger" => "bg-red-700 hover:bg-red-800 focus:ring-red-300 dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-800", + "success" => "bg-green-700 hover:bg-green-800 focus:ring-green-300 dark:bg-green-600 dark:hover:bg-green-700 dark:focus:ring-green-800", + "secondary" => "bg-secondary-700 hover:bg-secondary-800 focus:ring-secondary-300 dark:bg-secondary-600 dark:hover:bg-secondary-700 dark:focus:ring-secondary-800", + _ => "bg-gray-700 hover:bg-gray-800 focus:ring-gray-300 dark:bg-gray-600 dark:hover:bg-gray-700 dark:focus:ring-gray-800", + }; +} diff --git a/src/Shared/AliasVault.RazorComponents/Buttons/CancelButton.razor b/src/Shared/AliasVault.RazorComponents/Buttons/CancelButton.razor new file mode 100644 index 000000000..5e222bdfe --- /dev/null +++ b/src/Shared/AliasVault.RazorComponents/Buttons/CancelButton.razor @@ -0,0 +1,26 @@ + + +@code { + /// + /// The event to call in the parent when the cancel button is clicked. + /// + [Parameter] + public EventCallback OnClick { get; set; } + + /// + /// The content to be displayed inside the button. + /// + [Parameter] + public RenderFragment? ChildContent { get; set; } + + /// + /// Handles the button click event. + /// + private async Task HandleClick() + { + await OnClick.InvokeAsync(); + } +} diff --git a/src/Shared/AliasVault.RazorComponents/Buttons/ConfirmButton.razor b/src/Shared/AliasVault.RazorComponents/Buttons/ConfirmButton.razor new file mode 100644 index 000000000..273d11c06 --- /dev/null +++ b/src/Shared/AliasVault.RazorComponents/Buttons/ConfirmButton.razor @@ -0,0 +1,26 @@ + + +@code { + /// + /// The event to call in the parent when the confirm button is clicked. + /// + [Parameter] + public EventCallback OnClick { get; set; } + + /// + /// The content to be displayed inside the button. + /// + [Parameter] + public RenderFragment? ChildContent { get; set; } + + /// + /// Handles the button click event. + /// + private async Task HandleClick() + { + await OnClick.InvokeAsync(); + } +} diff --git a/src/Shared/AliasVault.RazorComponents/Buttons/LinkButton.razor b/src/Shared/AliasVault.RazorComponents/Buttons/LinkButton.razor new file mode 100644 index 000000000..f4601c237 --- /dev/null +++ b/src/Shared/AliasVault.RazorComponents/Buttons/LinkButton.razor @@ -0,0 +1,46 @@ + + @Text + + +@code { + /// + /// Gets or sets the URL that the hyperlink points to. + /// + [Parameter] + public string Href { get; set; } = string.Empty; + + /// + /// Gets or sets the main text of the button. + /// + [Parameter] + public string Text { get; set; } = string.Empty; + + /// + /// Gets or sets the additional text that appears on larger screens. + /// + [Parameter] + public string AdditionalText { get; set; } = string.Empty; + + /// + /// Gets or sets the color theme of the button. + /// + [Parameter] + public string Color { get; set; } = "primary"; + + /// + /// Gets or sets additional CSS classes to apply to the button. + /// + [Parameter] + public string AdditionalClasses { get; set; } = string.Empty; + + /// + /// Gets the CSS classes for the link button based on the color and additional classes. + /// + /// A string containing the CSS classes for the link button. + private string GetButtonClasses() + { + var colorClasses = ButtonStyles.GetColorClasses(Color); + + return $"{ButtonStyles.BaseClasses} {colorClasses} {AdditionalClasses}".Trim(); + } +} diff --git a/src/AliasVault.Client/Main/Components/Layout/Breadcrumb.razor b/src/Shared/AliasVault.RazorComponents/Headings/Breadcrumb.razor similarity index 96% rename from src/AliasVault.Client/Main/Components/Layout/Breadcrumb.razor rename to src/Shared/AliasVault.RazorComponents/Headings/Breadcrumb.razor index e40d4516d..3ec229606 100644 --- a/src/AliasVault.Client/Main/Components/Layout/Breadcrumb.razor +++ b/src/Shared/AliasVault.RazorComponents/Headings/Breadcrumb.razor @@ -1,6 +1,7 @@ -@inherits ComponentBase +@using AliasVault.Client.Main.Models +@inherits ComponentBase -
diff --git a/src/AliasVault.Admin/Main/Pages/Account/Manage/ResetAuthenticator.razor b/src/AliasVault.Admin/Main/Pages/Account/Manage/ResetAuthenticator.razor index 1056f3d6a..cab5eb930 100644 --- a/src/AliasVault.Admin/Main/Pages/Account/Manage/ResetAuthenticator.razor +++ b/src/AliasVault.Admin/Main/Pages/Account/Manage/ResetAuthenticator.razor @@ -23,7 +23,7 @@
- + Reset authenticator key
diff --git a/src/AliasVault.Admin/Main/Pages/Account/Manage/TwoFactorAuthentication.razor b/src/AliasVault.Admin/Main/Pages/Account/Manage/TwoFactorAuthentication.razor index b10fc8b1b..1bff518c1 100644 --- a/src/AliasVault.Admin/Main/Pages/Account/Manage/TwoFactorAuthentication.razor +++ b/src/AliasVault.Admin/Main/Pages/Account/Manage/TwoFactorAuthentication.razor @@ -46,18 +46,12 @@
diff --git a/src/AliasVault.Admin/Main/Pages/Users/Delete.razor b/src/AliasVault.Admin/Main/Pages/Users/Delete.razor index db16729ed..fdeb53398 100644 --- a/src/AliasVault.Admin/Main/Pages/Users/Delete.razor +++ b/src/AliasVault.Admin/Main/Pages/Users/Delete.razor @@ -26,13 +26,10 @@ else
@Obj?.UserName
- - - +
+ + +
} diff --git a/src/AliasVault.Admin/Main/Pages/Users/Users.razor b/src/AliasVault.Admin/Main/Pages/Users/Users.razor index fe3759015..1063b4957 100644 --- a/src/AliasVault.Admin/Main/Pages/Users/Users.razor +++ b/src/AliasVault.Admin/Main/Pages/Users/Users.razor @@ -51,7 +51,7 @@ else @user.LastVaultUpdate.ToString("yyyy-MM-dd HH:mm") - View + } diff --git a/src/AliasVault.Admin/Main/Pages/Users/View.razor b/src/AliasVault.Admin/Main/Pages/Users/View.razor index b78c9fdbb..f29b80d41 100644 --- a/src/AliasVault.Admin/Main/Pages/Users/View.razor +++ b/src/AliasVault.Admin/Main/Pages/Users/View.razor @@ -36,20 +36,14 @@ else Authenticator key(s) active: @TwoFactorKeysCount @if (User.TwoFactorEnabled) { - + } else { if (TwoFactorKeysCount > 0) { - - + + } }
@@ -64,54 +58,54 @@ else - - - - - - - - - - - - + + + + + + + + + + + + - @{ - Vault? previousEntry = null; - } - @foreach (var entry in VaultList.OrderBy(e => e.UpdatedAt)) - { - - - - - - - - - - - - + @{ + Vault? previousEntry = null; + } + @foreach (var entry in VaultList.OrderBy(e => e.UpdatedAt)) + { + + + + + + + + + + + + - previousEntry = entry; - } + previousEntry = entry; + }
IDCreatedUpdatedFilesizeDB versionRevisionCredentialsEmail ClaimsStatusActions
IDCreatedUpdatedFilesizeDB versionRevisionCredentialsEmail ClaimsStatusActions
@entry.Id@entry.CreatedAt.ToString("yyyy-MM-dd HH:mm")@entry.UpdatedAt.ToString("yyyy-MM-dd HH:mm")@Math.Round((double)entry.FileSize / 1024, 1) MB@entry.Version@entry.RevisionNumber@entry.CredentialsCount@entry.EmailClaimsCount - @if (entry == LatestVault) - { - Current - } - @if (previousEntry != null && HasPasswordChanged(entry, previousEntry)) - { - Password Changed - } - - @if (entry != LatestVault) - { - - } -
@entry.Id@entry.CreatedAt.ToString("yyyy-MM-dd HH:mm")@entry.UpdatedAt.ToString("yyyy-MM-dd HH:mm")@Math.Round((double)entry.FileSize / 1024, 1) MB@entry.Version@entry.RevisionNumber@entry.CredentialsCount@entry.EmailClaimsCount + @if (entry == LatestVault) + { + Current + } + @if (previousEntry != null && HasPasswordChanged(entry, previousEntry)) + { + Password Changed + } + + @if (entry != LatestVault) + { + + } +
@@ -144,7 +138,7 @@ else @entry.CreatedAt.ToString("yyyy-MM-dd HH:mm") @entry.ExpireDate.ToString("yyyy-MM-dd HH:mm") - + } diff --git a/src/AliasVault.Admin/tailwind.config.js b/src/AliasVault.Admin/tailwind.config.js index d02ab3413..0f275c0da 100644 --- a/src/AliasVault.Admin/tailwind.config.js +++ b/src/AliasVault.Admin/tailwind.config.js @@ -4,6 +4,7 @@ module.exports = { './**/*.html', './**/*.razor', '../Shared/AliasVault.RazorComponents/**/*.razor', + '../Shared/AliasVault.RazorComponents/**/*.cs', ], safelist: [ 'w-64', diff --git a/src/AliasVault.Admin/wwwroot/css/tailwind.css b/src/AliasVault.Admin/wwwroot/css/tailwind.css index bd1bfc572..cc872a34e 100644 --- a/src/AliasVault.Admin/wwwroot/css/tailwind.css +++ b/src/AliasVault.Admin/wwwroot/css/tailwind.css @@ -600,6 +600,10 @@ video { border-width: 0; } +.visible { + visibility: visible; +} + .invisible { visibility: hidden; } @@ -715,10 +719,6 @@ video { margin-right: 1rem; } -.ms-1 { - margin-inline-start: 0.25rem; -} - .mt-0 { margin-top: 0px; } @@ -948,10 +948,6 @@ video { align-items: flex-start; } -.items-end { - align-items: flex-end; -} - .items-center { align-items: center; } @@ -1026,6 +1022,12 @@ video { margin-bottom: calc(1.5rem * var(--tw-space-y-reverse)); } +.space-y-3 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.75rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.75rem * var(--tw-space-y-reverse)); +} + .divide-y > :not([hidden]) ~ :not([hidden]) { --tw-divide-y-reverse: 0; border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse))); @@ -1177,6 +1179,11 @@ video { background-color: rgb(209 213 219 / var(--tw-bg-opacity)); } +.bg-gray-400 { + --tw-bg-opacity: 1; + background-color: rgb(156 163 175 / var(--tw-bg-opacity)); +} + .bg-gray-50 { --tw-bg-opacity: 1; background-color: rgb(249 250 251 / var(--tw-bg-opacity)); @@ -1192,6 +1199,11 @@ video { background-color: rgb(75 85 99 / var(--tw-bg-opacity)); } +.bg-gray-700 { + --tw-bg-opacity: 1; + background-color: rgb(55 65 81 / var(--tw-bg-opacity)); +} + .bg-gray-800 { --tw-bg-opacity: 1; background-color: rgb(31 41 55 / var(--tw-bg-opacity)); @@ -1222,6 +1234,11 @@ video { background-color: rgb(22 163 74 / var(--tw-bg-opacity)); } +.bg-green-700 { + --tw-bg-opacity: 1; + background-color: rgb(21 128 61 / var(--tw-bg-opacity)); +} + .bg-primary-100 { --tw-bg-opacity: 1; background-color: rgb(253 222 133 / var(--tw-bg-opacity)); @@ -1686,6 +1703,16 @@ video { background-color: rgb(249 250 251 / var(--tw-bg-opacity)); } +.hover\:bg-gray-800:hover { + --tw-bg-opacity: 1; + background-color: rgb(31 41 55 / var(--tw-bg-opacity)); +} + +.hover\:bg-green-800:hover { + --tw-bg-opacity: 1; + background-color: rgb(22 101 52 / var(--tw-bg-opacity)); +} + .hover\:bg-primary-200:hover { --tw-bg-opacity: 1; background-color: rgb(251 203 116 / var(--tw-bg-opacity)); @@ -1757,11 +1784,6 @@ video { box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); } -.focus\:ring-blue-300:focus { - --tw-ring-opacity: 1; - --tw-ring-color: rgb(147 197 253 / var(--tw-ring-opacity)); -} - .focus\:ring-blue-500:focus { --tw-ring-opacity: 1; --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity)); @@ -1777,6 +1799,11 @@ video { --tw-ring-color: rgb(209 213 219 / var(--tw-ring-opacity)); } +.focus\:ring-green-300:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(134 239 172 / var(--tw-ring-opacity)); +} + .focus\:ring-primary-300:focus { --tw-ring-opacity: 1; --tw-ring-color: rgb(248 185 99 / var(--tw-ring-opacity)); @@ -1851,6 +1878,11 @@ video { background-color: rgb(17 24 39 / var(--tw-bg-opacity)); } +.dark\:bg-green-600:is(.dark *) { + --tw-bg-opacity: 1; + background-color: rgb(22 163 74 / var(--tw-bg-opacity)); +} + .dark\:bg-primary-500:is(.dark *) { --tw-bg-opacity: 1; background-color: rgb(244 149 65 / var(--tw-bg-opacity)); @@ -1994,6 +2026,11 @@ video { background-color: rgb(55 65 81 / var(--tw-bg-opacity)); } +.dark\:hover\:bg-green-700:hover:is(.dark *) { + --tw-bg-opacity: 1; + background-color: rgb(21 128 61 / var(--tw-bg-opacity)); +} + .dark\:hover\:bg-primary-600:hover:is(.dark *) { --tw-bg-opacity: 1; background-color: rgb(214 131 56 / var(--tw-bg-opacity)); @@ -2044,6 +2081,16 @@ video { --tw-ring-color: rgb(55 65 81 / var(--tw-ring-opacity)); } +.dark\:focus\:ring-gray-800:focus:is(.dark *) { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(31 41 55 / var(--tw-ring-opacity)); +} + +.dark\:focus\:ring-green-800:focus:is(.dark *) { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(22 101 52 / var(--tw-ring-opacity)); +} + .dark\:focus\:ring-primary-500:focus:is(.dark *) { --tw-ring-opacity: 1; --tw-ring-color: rgb(244 149 65 / var(--tw-ring-opacity)); @@ -2064,11 +2111,6 @@ video { --tw-ring-color: rgb(153 27 27 / var(--tw-ring-opacity)); } -.dark\:focus\:ring-red-900:focus:is(.dark *) { - --tw-ring-opacity: 1; - --tw-ring-color: rgb(127 29 29 / var(--tw-ring-opacity)); -} - @media (min-width: 640px) { .sm\:flex { display: flex; diff --git a/src/AliasVault.Client/wwwroot/css/tailwind.css b/src/AliasVault.Client/wwwroot/css/tailwind.css index dc72fc79c..79e323daf 100644 --- a/src/AliasVault.Client/wwwroot/css/tailwind.css +++ b/src/AliasVault.Client/wwwroot/css/tailwind.css @@ -687,10 +687,6 @@ video { margin-bottom: 1rem; } -.mb-5 { - margin-bottom: 1.25rem; -} - .mb-6 { margin-bottom: 1.5rem; } diff --git a/src/Shared/AliasVault.RazorComponents/Buttons/Button.razor b/src/Shared/AliasVault.RazorComponents/Buttons/Button.razor index c473cca9e..d49a123f4 100644 --- a/src/Shared/AliasVault.RazorComponents/Buttons/Button.razor +++ b/src/Shared/AliasVault.RazorComponents/Buttons/Button.razor @@ -1,4 +1,5 @@ - + +@code { + /// + /// The content to be displayed inside the button. + /// + [Parameter] + public RenderFragment? ChildContent { get; set; } +} From afc605afd0bf3eed2124e67bbc18fe3b275c2786 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Mon, 7 Oct 2024 16:11:42 +0200 Subject: [PATCH 06/11] Update LinkButton (#220) --- src/AliasVault.Admin/wwwroot/css/tailwind.css | 4 ++++ .../AliasVault.RazorComponents/Buttons/LinkButton.razor | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/AliasVault.Admin/wwwroot/css/tailwind.css b/src/AliasVault.Admin/wwwroot/css/tailwind.css index cc872a34e..0f356110e 100644 --- a/src/AliasVault.Admin/wwwroot/css/tailwind.css +++ b/src/AliasVault.Admin/wwwroot/css/tailwind.css @@ -747,6 +747,10 @@ video { margin-top: 2rem; } +.ms-1 { + margin-inline-start: 0.25rem; +} + .line-clamp-1 { overflow: hidden; display: -webkit-box; diff --git a/src/Shared/AliasVault.RazorComponents/Buttons/LinkButton.razor b/src/Shared/AliasVault.RazorComponents/Buttons/LinkButton.razor index 7adb812f8..268603211 100644 --- a/src/Shared/AliasVault.RazorComponents/Buttons/LinkButton.razor +++ b/src/Shared/AliasVault.RazorComponents/Buttons/LinkButton.razor @@ -1,5 +1,5 @@ - @Text @if (AdditionalText.Length > 0) { } + @Text @if (AdditionalText.Length > 0) { } @code { From d5ba3a63e44e5845c518203d2530648bf5b46449 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Mon, 7 Oct 2024 16:29:59 +0200 Subject: [PATCH 07/11] Refactor misc buttons in client app to shared button components (#220) --- .../Main/Pages/Credentials/AddEdit.razor | 2 +- .../Components/ActiveSessionsSection.razor | 3 +-- .../Main/Pages/Settings/Vault.razor | 20 ++++++++----------- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/AliasVault.Client/Main/Pages/Credentials/AddEdit.razor b/src/AliasVault.Client/Main/Pages/Credentials/AddEdit.razor index 923819be7..ba6b7f4c9 100644 --- a/src/AliasVault.Client/Main/Pages/Credentials/AddEdit.razor +++ b/src/AliasVault.Client/Main/Pages/Credentials/AddEdit.razor @@ -72,7 +72,7 @@ else

Login credentials

- +
diff --git a/src/AliasVault.Client/Main/Pages/Settings/Security/Components/ActiveSessionsSection.razor b/src/AliasVault.Client/Main/Pages/Settings/Security/Components/ActiveSessionsSection.razor index 0de9e5ded..8aa81b54f 100644 --- a/src/AliasVault.Client/Main/Pages/Settings/Security/Components/ActiveSessionsSection.razor +++ b/src/AliasVault.Client/Main/Pages/Settings/Security/Components/ActiveSessionsSection.razor @@ -36,8 +36,7 @@ @session.CreatedAt.ToLocalTime().ToString("g") @session.ExpireDate.ToLocalTime().ToString("g") - + } diff --git a/src/AliasVault.Client/Main/Pages/Settings/Vault.razor b/src/AliasVault.Client/Main/Pages/Settings/Vault.razor index d4580262c..1e7c671d0 100644 --- a/src/AliasVault.Client/Main/Pages/Settings/Vault.razor +++ b/src/AliasVault.Client/Main/Pages/Settings/Vault.razor @@ -15,14 +15,10 @@

Export vault

- +
- +
@@ -66,13 +62,13 @@ else if (!string.IsNullOrEmpty(ImportSuccessMessage)) { try { - // Decode the base64 string to a byte array + // Decode the base64 string to a byte array. byte[] fileBytes = Convert.FromBase64String(await DbService.ExportSqliteToBase64Async()); - // Create a memory stream from the byte array + // Create a memory stream from the byte array. using (MemoryStream memoryStream = new MemoryStream(fileBytes)) { - // Invoke JavaScript to initiate the download + // Invoke JavaScript to initiate the download. await JsInteropService.DownloadFileFromStream("aliasvault-client.sqlite", memoryStream.ToArray()); } } @@ -90,10 +86,10 @@ else if (!string.IsNullOrEmpty(ImportSuccessMessage)) var csvBytes = CsvImportExport.CredentialCsvService.ExportCredentialsToCsv(credentials); - // Create a memory stream from the byte array + // Create a memory stream from the byte array. using (MemoryStream memoryStream = new MemoryStream(csvBytes)) { - // Invoke JavaScript to initiate the download + // Invoke JavaScript to initiate the download. await JsInteropService.DownloadFileFromStream("aliasvault-client.csv", memoryStream.ToArray()); } } @@ -123,7 +119,7 @@ else if (!string.IsNullOrEmpty(ImportSuccessMessage)) var fileContent = System.Text.Encoding.UTF8.GetString(buffer); var importedCredentials = CsvImportExport.CredentialCsvService.ImportCredentialsFromCsv(fileContent); - // Loop through the imported credentials and actually add them to the database + // Loop through the imported credentials and actually add them to the database. foreach (var importedCredential in importedCredentials) { await CredentialService.InsertEntryAsync(importedCredential, false); From bbb168d764a3f09e0b2c6c6296ae14bb32b1a558 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Mon, 7 Oct 2024 17:47:07 +0200 Subject: [PATCH 08/11] Refactor table to common SortableTable component (#220) --- .../Main/Pages/Logging/Auth.razor | 71 +- .../Main/Pages/Logging/General.razor | 126 ++- src/AliasVault.Admin/wwwroot/css/tailwind.css | 12 + .../wwwroot/css/tailwind.css | 4 + .../AliasServerDb/AliasServerDbContext.cs | 21 +- src/Databases/AliasServerDb/Log.cs | 2 +- ...53012_DatetimeOffsetToDateTime.Designer.cs | 831 ++++++++++++++++++ ...20241007153012_DatetimeOffsetToDateTime.cs | 22 + .../AliasServerDbContextModelSnapshot.cs | 2 +- .../AliasVault.RazorComponents.csproj | 2 +- .../Tables/SortDirection.cs | 24 + .../Tables/SortableTable.razor | 87 ++ .../Tables/TableColumn.cs | 29 + .../AliasVault.RazorComponents/_Imports.razor | 1 + 14 files changed, 1176 insertions(+), 58 deletions(-) create mode 100644 src/Databases/AliasServerDb/Migrations/20241007153012_DatetimeOffsetToDateTime.Designer.cs create mode 100644 src/Databases/AliasServerDb/Migrations/20241007153012_DatetimeOffsetToDateTime.cs create mode 100644 src/Shared/AliasVault.RazorComponents/Tables/SortDirection.cs create mode 100644 src/Shared/AliasVault.RazorComponents/Tables/SortableTable.razor create mode 100644 src/Shared/AliasVault.RazorComponents/Tables/TableColumn.cs diff --git a/src/AliasVault.Admin/Main/Pages/Logging/Auth.razor b/src/AliasVault.Admin/Main/Pages/Logging/Auth.razor index 5981d1a29..ced490889 100644 --- a/src/AliasVault.Admin/Main/Pages/Logging/Auth.razor +++ b/src/AliasVault.Admin/Main/Pages/Logging/Auth.razor @@ -1,4 +1,5 @@ @page "/logging/auth" +@using AliasVault.RazorComponents.Tables @using AliasVault.Shared.Models.Enums @inherits MainBase @@ -39,6 +40,21 @@ else
+ + + @foreach (var log in LogList) + { + + @log.Id + @log.Timestamp.ToString("yyyy-MM-dd HH:mm") + @log.Username + @log.EventType + + @log.IpAddress + + } + + @@ -68,6 +84,15 @@ else } @code { + private readonly List _tableColumns = [ + new TableColumn { Title = "ID", PropertyName = "Id" }, + new TableColumn { Title = "Time", PropertyName = "Timestamp" }, + new TableColumn { Title = "Username", PropertyName = "Username" }, + new TableColumn { Title = "Event", PropertyName = "EventType" }, + new TableColumn { Title = "Success", PropertyName = "IsSuccess" }, + new TableColumn { Title = "IP", PropertyName = "IpAddress" }, + ]; + private List LogList { get; set; } = []; private bool IsLoading { get; set; } = true; private int CurrentPage { get; set; } = 1; @@ -102,6 +127,16 @@ else } } + 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(); + } + /// protected override async Task OnAfterRenderAsync(bool firstRender) { @@ -138,9 +173,43 @@ else } } + // Apply sort. + switch (SortColumn) + { + case "Timestamp": + query = SortDirection == SortDirection.Ascending + ? query.OrderBy(x => x.Timestamp) + : query.OrderByDescending(x => x.Timestamp); + break; + case "Username": + query = SortDirection == SortDirection.Ascending + ? query.OrderBy(x => x.Username) + : query.OrderByDescending(x => x.Username); + break; + case "EventType": + query = SortDirection == SortDirection.Ascending + ? query.OrderBy(x => x.EventType) + : query.OrderByDescending(x => x.EventType); + break; + case "IsSuccess": + query = SortDirection == SortDirection.Ascending + ? query.OrderBy(x => x.IsSuccess) + : query.OrderByDescending(x => x.IsSuccess); + break; + case "IpAddress": + query = SortDirection == SortDirection.Ascending + ? query.OrderBy(x => x.IpAddress) + : query.OrderByDescending(x => x.IpAddress); + break; + default: + query = SortDirection == SortDirection.Ascending + ? query.OrderBy(x => x.Id) + : query.OrderByDescending(x => x.Id); + break; + } + TotalRecords = await query.CountAsync(); LogList = await query - .OrderByDescending(x => x.Timestamp) .Skip((CurrentPage - 1) * PageSize) .Take(PageSize) .ToListAsync(); diff --git a/src/AliasVault.Admin/Main/Pages/Logging/General.razor b/src/AliasVault.Admin/Main/Pages/Logging/General.razor index 151729827..c5d7389aa 100644 --- a/src/AliasVault.Admin/Main/Pages/Logging/General.razor +++ b/src/AliasVault.Admin/Main/Pages/Logging/General.razor @@ -1,4 +1,5 @@ @page "/logging/general" +@using AliasVault.RazorComponents.Tables @inherits MainBase System logs @@ -32,60 +33,57 @@ else @foreach (var service in ServiceNames) { - + } -
- - - - - - - - - - - @foreach (var log in LogList) - { - - - - - @{ - string bgColor = log.Level switch - { - "Information" => "bg-blue-500", - "Error" => "bg-red-500", - "Warning" => "bg-yellow-500", - "Debug" => "bg-green-500", - _ => "bg-gray-500" - }; + + @foreach (var log in LogList) + { + + + + + @{ + string bgColor = log.Level switch + { + "Information" => "bg-blue-500", + "Error" => "bg-red-500", + "Warning" => "bg-yellow-500", + "Debug" => "bg-green-500", + _ => "bg-gray-500" + }; + } + + - - - } - -
IDTimeApplicationLevelMessage
@log.Id@log.TimeStamp.ToString("yyyy-MM-dd HH:mm")@log.Application
@log.Id@log.TimeStamp.ToString("yyyy-MM-dd HH:mm")@log.Application + + @log.Level + + + @if (log.SourceContext.Length > 0) + { + @log.SourceContext: } - - - @log.Level - - - @if (log.SourceContext.Length > 0) - { - @log.SourceContext: - } - @log.Message -
+ @log.Message + + + } +
} @code { + private readonly List _tableColumns = [ + new TableColumn { Title = "ID", PropertyName = "Id" }, + new TableColumn { Title = "Time", PropertyName = "Timestamp" }, + new TableColumn { Title = "Application", PropertyName = "Application" }, + new TableColumn { Title = "Level", PropertyName = "Level" }, + new TableColumn { Title = "Message", PropertyName = "Message" }, + ]; + private List LogList { get; set; } = []; private bool IsLoading { get; set; } = true; private int CurrentPage { get; set; } = 1; @@ -122,6 +120,16 @@ else private List ServiceNames { 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(); + } + /// protected override async Task OnAfterRenderAsync(bool firstRender) { @@ -132,7 +140,6 @@ else } } - private void HandlePageChanged(int newPage) { CurrentPage = newPage; @@ -158,9 +165,38 @@ else query = query.Where(x => x.Application == SelectedServiceName); } + // Apply sort. + switch (SortColumn) + { + case "Application": + query = SortDirection == SortDirection.Ascending + ? query.OrderBy(x => x.Application) + : query.OrderByDescending(x => x.Application); + break; + case "Message": + query = SortDirection == SortDirection.Ascending + ? query.OrderBy(x => x.Message) + : query.OrderByDescending(x => x.Message); + break; + case "Level": + query = SortDirection == SortDirection.Ascending + ? query.OrderBy(x => x.Level) + : query.OrderByDescending(x => x.Level); + break; + case "Timestamp": + query = SortDirection == SortDirection.Ascending + ? query.OrderBy(x => x.TimeStamp) + : query.OrderByDescending(x => x.TimeStamp); + break; + default: + query = SortDirection == SortDirection.Ascending + ? query.OrderBy(x => x.Id) + : query.OrderByDescending(x => x.Id); + break; + } + TotalRecords = await query.CountAsync(); LogList = await query - .OrderByDescending(x => x.Id) .Skip((CurrentPage - 1) * PageSize) .Take(PageSize) .ToListAsync(); diff --git a/src/AliasVault.Admin/wwwroot/css/tailwind.css b/src/AliasVault.Admin/wwwroot/css/tailwind.css index 0f356110e..ea97227db 100644 --- a/src/AliasVault.Admin/wwwroot/css/tailwind.css +++ b/src/AliasVault.Admin/wwwroot/css/tailwind.css @@ -751,6 +751,10 @@ video { margin-inline-start: 0.25rem; } +.ml-1 { + margin-left: 0.25rem; +} + .line-clamp-1 { overflow: hidden; display: -webkit-box; @@ -822,6 +826,10 @@ video { height: 100%; } +.h-3 { + height: 0.75rem; +} + .w-1\/2 { width: 50%; } @@ -874,6 +882,10 @@ video { width: 100%; } +.w-3 { + width: 0.75rem; +} + .max-w-2xl { max-width: 42rem; } diff --git a/src/AliasVault.Client/wwwroot/css/tailwind.css b/src/AliasVault.Client/wwwroot/css/tailwind.css index 79e323daf..4b0fb961d 100644 --- a/src/AliasVault.Client/wwwroot/css/tailwind.css +++ b/src/AliasVault.Client/wwwroot/css/tailwind.css @@ -763,6 +763,10 @@ video { margin-top: 2rem; } +.ml-1 { + margin-left: 0.25rem; +} + .line-clamp-2 { overflow: hidden; display: -webkit-box; diff --git a/src/Databases/AliasServerDb/AliasServerDbContext.cs b/src/Databases/AliasServerDb/AliasServerDbContext.cs index 31e71426b..9309f30ab 100644 --- a/src/Databases/AliasServerDb/AliasServerDbContext.cs +++ b/src/Databases/AliasServerDb/AliasServerDbContext.cs @@ -133,17 +133,20 @@ public class AliasServerDbContext : WorkerStatusDbContext, IDataProtectionKeyCon protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); - foreach (var entity in modelBuilder.Model.GetEntityTypes()) - { - foreach (var property in entity.GetProperties()) - { - // NOTE: This is a workaround for SQLite. Add conditional check if SQLite is used. - // NOTE: SQL server doesn't need this override. - // SQLite does not support varchar(max) so we use TEXT. - if (property.ClrType == typeof(string) && property.GetMaxLength() == null) + // NOTE: This is a workaround for SQLite. Add conditional check if SQLite is used. + // NOTE: SQL server doesn't need this override. + if (Database.IsSqlite()) + { + foreach (var entity in modelBuilder.Model.GetEntityTypes()) + { + foreach (var property in entity.GetProperties()) { - property.SetColumnType("TEXT"); + // SQLite does not support varchar(max) so we use TEXT. + if (property.ClrType == typeof(string) && property.GetMaxLength() == null) + { + property.SetColumnType("TEXT"); + } } } } diff --git a/src/Databases/AliasServerDb/Log.cs b/src/Databases/AliasServerDb/Log.cs index 6e0370660..90a22146b 100644 --- a/src/Databases/AliasServerDb/Log.cs +++ b/src/Databases/AliasServerDb/Log.cs @@ -54,7 +54,7 @@ public class Log /// /// Gets or sets the timestamp of the log entry. /// - public DateTimeOffset TimeStamp { get; set; } + public DateTime TimeStamp { get; set; } /// /// Gets or sets the exception associated with the log entry. diff --git a/src/Databases/AliasServerDb/Migrations/20241007153012_DatetimeOffsetToDateTime.Designer.cs b/src/Databases/AliasServerDb/Migrations/20241007153012_DatetimeOffsetToDateTime.Designer.cs new file mode 100644 index 000000000..2467b8299 --- /dev/null +++ b/src/Databases/AliasServerDb/Migrations/20241007153012_DatetimeOffsetToDateTime.Designer.cs @@ -0,0 +1,831 @@ +// +using System; +using AliasServerDb; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace AliasServerDb.Migrations +{ + [DbContext(typeof(AliasServerDbContext))] + [Migration("20241007153012_DatetimeOffsetToDateTime")] + partial class DatetimeOffsetToDateTime + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("Proxies:ChangeTracking", false) + .HasAnnotation("Proxies:CheckEquality", false) + .HasAnnotation("Proxies:LazyLoading", true); + + modelBuilder.Entity("AliasServerDb.AdminRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("AdminRoles"); + }); + + modelBuilder.Entity("AliasServerDb.AdminUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LastPasswordChanged") + .HasColumnType("TEXT"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("AdminUsers"); + }); + + modelBuilder.Entity("AliasServerDb.AliasVaultRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("AliasVaultRoles"); + }); + + modelBuilder.Entity("AliasServerDb.AliasVaultUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasColumnType("TEXT"); + + b.Property("PasswordChangedAt") + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UserName") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("AliasVaultUsers"); + }); + + modelBuilder.Entity("AliasServerDb.AliasVaultUserRefreshToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeviceIdentifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("ExpireDate") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("IpAddress") + .HasMaxLength(45) + .HasColumnType("TEXT"); + + b.Property("PreviousTokenValue") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AliasVaultUserRefreshTokens"); + }); + + modelBuilder.Entity("AliasServerDb.AuthLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AdditionalInfo") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("Browser") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Country") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("DeviceType") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("EventType") + .HasColumnType("nvarchar(50)"); + + b.Property("FailureReason") + .HasColumnType("INTEGER"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("IsSuccess") + .HasColumnType("INTEGER"); + + b.Property("IsSuspiciousActivity") + .HasColumnType("INTEGER"); + + b.Property("OperatingSystem") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("RequestPath") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Timestamp") + .HasColumnType("TEXT"); + + b.Property("UserAgent") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex(new[] { "EventType" }, "IX_EventType"); + + b.HasIndex(new[] { "IpAddress" }, "IX_IpAddress"); + + b.HasIndex(new[] { "Timestamp" }, "IX_Timestamp"); + + b.HasIndex(new[] { "Username", "IsSuccess", "Timestamp" }, "IX_Username_IsSuccess_Timestamp") + .IsDescending(false, false, true); + + b.HasIndex(new[] { "Username", "Timestamp" }, "IX_Username_Timestamp") + .IsDescending(false, true); + + b.ToTable("AuthLogs"); + }); + + modelBuilder.Entity("AliasServerDb.Email", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("DateSystem") + .HasColumnType("TEXT"); + + b.Property("EncryptedSymmetricKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("From") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FromDomain") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FromLocal") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("MessageHtml") + .HasColumnType("TEXT"); + + b.Property("MessagePlain") + .HasColumnType("TEXT"); + + b.Property("MessagePreview") + .HasColumnType("TEXT"); + + b.Property("MessageSource") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PushNotificationSent") + .HasColumnType("INTEGER"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("To") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ToDomain") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ToLocal") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserEncryptionKeyId") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("Visible") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("DateSystem"); + + b.HasIndex("PushNotificationSent"); + + b.HasIndex("ToLocal"); + + b.HasIndex("UserEncryptionKeyId"); + + b.HasIndex("Visible"); + + b.ToTable("Emails"); + }); + + modelBuilder.Entity("AliasServerDb.EmailAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Bytes") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("EmailId") + .HasColumnType("INTEGER"); + + b.Property("Filename") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Filesize") + .HasColumnType("INTEGER"); + + b.Property("MimeType") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EmailId"); + + b.ToTable("EmailAttachments"); + }); + + modelBuilder.Entity("AliasServerDb.Log", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Application") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Exception") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Level") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("LogEvent") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("LogEvent"); + + b.Property("Message") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("MessageTemplate") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Properties") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SourceContext") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TimeStamp") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Application"); + + b.HasIndex("TimeStamp"); + + b.ToTable("Logs", (string)null); + }); + + modelBuilder.Entity("AliasServerDb.UserEmailClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Address") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("AddressDomain") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("AddressLocal") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Address") + .IsUnique(); + + b.HasIndex("UserId"); + + b.ToTable("UserEmailClaims"); + }); + + modelBuilder.Entity("AliasServerDb.UserEncryptionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("IsPrimary") + .HasColumnType("INTEGER"); + + b.Property("PublicKey") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserEncryptionKeys"); + }); + + modelBuilder.Entity("AliasServerDb.Vault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CredentialsCount") + .HasColumnType("INTEGER"); + + b.Property("EmailClaimsCount") + .HasColumnType("INTEGER"); + + b.Property("EncryptionSettings") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("EncryptionType") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FileSize") + .HasColumnType("INTEGER"); + + b.Property("RevisionNumber") + .HasColumnType("INTEGER"); + + b.Property("Salt") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("VaultBlob") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Verifier") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Vaults"); + }); + + modelBuilder.Entity("AliasVault.WorkerStatus.Database.WorkerServiceStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CurrentStatus") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("DesiredStatus") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Heartbeat") + .HasColumnType("TEXT"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar"); + + b.HasKey("Id"); + + b.ToTable("WorkerServiceStatuses"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FriendlyName") + .HasColumnType("TEXT"); + + b.Property("Xml") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("DataProtectionKeys"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("RoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("UserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.ToTable("UserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.ToTable("UserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("UserTokens", (string)null); + }); + + modelBuilder.Entity("AliasServerDb.AliasVaultUserRefreshToken", b => + { + b.HasOne("AliasServerDb.AliasVaultUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("AliasServerDb.Email", b => + { + b.HasOne("AliasServerDb.UserEncryptionKey", "EncryptionKey") + .WithMany("Emails") + .HasForeignKey("UserEncryptionKeyId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("EncryptionKey"); + }); + + modelBuilder.Entity("AliasServerDb.EmailAttachment", b => + { + b.HasOne("AliasServerDb.Email", "Email") + .WithMany("Attachments") + .HasForeignKey("EmailId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Email"); + }); + + modelBuilder.Entity("AliasServerDb.UserEmailClaim", b => + { + b.HasOne("AliasServerDb.AliasVaultUser", "User") + .WithMany("EmailClaims") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("AliasServerDb.UserEncryptionKey", b => + { + b.HasOne("AliasServerDb.AliasVaultUser", "User") + .WithMany("EncryptionKeys") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("AliasServerDb.Vault", b => + { + b.HasOne("AliasServerDb.AliasVaultUser", "User") + .WithMany("Vaults") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("AliasServerDb.AliasVaultUser", b => + { + b.Navigation("EmailClaims"); + + b.Navigation("EncryptionKeys"); + + b.Navigation("Vaults"); + }); + + modelBuilder.Entity("AliasServerDb.Email", b => + { + b.Navigation("Attachments"); + }); + + modelBuilder.Entity("AliasServerDb.UserEncryptionKey", b => + { + b.Navigation("Emails"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Databases/AliasServerDb/Migrations/20241007153012_DatetimeOffsetToDateTime.cs b/src/Databases/AliasServerDb/Migrations/20241007153012_DatetimeOffsetToDateTime.cs new file mode 100644 index 000000000..483a37051 --- /dev/null +++ b/src/Databases/AliasServerDb/Migrations/20241007153012_DatetimeOffsetToDateTime.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace AliasServerDb.Migrations +{ + /// + public partial class DatetimeOffsetToDateTime : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/src/Databases/AliasServerDb/Migrations/AliasServerDbContextModelSnapshot.cs b/src/Databases/AliasServerDb/Migrations/AliasServerDbContextModelSnapshot.cs index 1a09bd173..a31749918 100644 --- a/src/Databases/AliasServerDb/Migrations/AliasServerDbContextModelSnapshot.cs +++ b/src/Databases/AliasServerDb/Migrations/AliasServerDbContextModelSnapshot.cs @@ -452,7 +452,7 @@ namespace AliasServerDb.Migrations .IsRequired() .HasColumnType("TEXT"); - b.Property("TimeStamp") + b.Property("TimeStamp") .HasColumnType("TEXT"); b.HasKey("Id"); diff --git a/src/Shared/AliasVault.RazorComponents/AliasVault.RazorComponents.csproj b/src/Shared/AliasVault.RazorComponents/AliasVault.RazorComponents.csproj index ae089f7b6..0d2a96e6b 100644 --- a/src/Shared/AliasVault.RazorComponents/AliasVault.RazorComponents.csproj +++ b/src/Shared/AliasVault.RazorComponents/AliasVault.RazorComponents.csproj @@ -18,7 +18,7 @@ - + diff --git a/src/Shared/AliasVault.RazorComponents/Tables/SortDirection.cs b/src/Shared/AliasVault.RazorComponents/Tables/SortDirection.cs new file mode 100644 index 000000000..7b258556e --- /dev/null +++ b/src/Shared/AliasVault.RazorComponents/Tables/SortDirection.cs @@ -0,0 +1,24 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) lanedirt. All rights reserved. +// Licensed under the MIT license. See LICENSE.md file in the project root for full license information. +// +//----------------------------------------------------------------------- + +namespace AliasVault.RazorComponents.Tables; + +/// +/// An enum that represents the direction of a sort. +/// +public enum SortDirection +{ + /// + /// The sort is ascending. + /// + Ascending, + + /// + /// The sort is descending. + /// + Descending, +} diff --git a/src/Shared/AliasVault.RazorComponents/Tables/SortableTable.razor b/src/Shared/AliasVault.RazorComponents/Tables/SortableTable.razor new file mode 100644 index 000000000..542f6503a --- /dev/null +++ b/src/Shared/AliasVault.RazorComponents/Tables/SortableTable.razor @@ -0,0 +1,87 @@ +@typeparam TItem + + + + + @foreach (var column in Columns) + { + + } + + + + @ChildContent + +
+ @if (column.Sortable) + { + + } + else + { + @column.Title + } +
+ +@code { + /// + /// Gets or sets the columns to display in the table. + /// + [Parameter] public List Columns { get; set; } = new(); + + /// + /// Gets or sets the child content of the table. + /// + [Parameter] public RenderFragment ChildContent { get; set; } = null!; + + /// + /// Gets or sets the column to sort by. + /// + [Parameter] public string SortColumn { get; set; } = string.Empty; + + /// + /// Gets or sets the direction to sort by. + /// + [Parameter] public SortDirection SortDirection { get; set; } + + /// + /// Gets or sets the event to invoke when the sort changes. + /// + [Parameter] public EventCallback<(string, SortDirection)> OnSortChanged { get; set; } + + private async Task OnSort(string columnName) + { + if (SortColumn == columnName) + { + SortDirection = SortDirection == SortDirection.Ascending + ? SortDirection.Descending + : SortDirection.Ascending; + } + else + { + SortColumn = columnName; + SortDirection = SortDirection.Ascending; + } + + await OnSortChanged.InvokeAsync((SortColumn, SortDirection)); + } +} diff --git a/src/Shared/AliasVault.RazorComponents/Tables/TableColumn.cs b/src/Shared/AliasVault.RazorComponents/Tables/TableColumn.cs new file mode 100644 index 000000000..0ba5a6793 --- /dev/null +++ b/src/Shared/AliasVault.RazorComponents/Tables/TableColumn.cs @@ -0,0 +1,29 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) lanedirt. All rights reserved. +// Licensed under the MIT license. See LICENSE.md file in the project root for full license information. +// +//----------------------------------------------------------------------- + +namespace AliasVault.RazorComponents.Tables; + +/// +/// A class that represents a column in a table. +/// +public class TableColumn +{ + /// + /// Gets or sets the title of the column. + /// + public required string Title { get; set; } + + /// + /// Gets or sets the name of the property to bind to. + /// + public required string PropertyName { get; set; } + + /// + /// Gets or sets a value indicating whether the column is sortable. + /// + public bool Sortable { get; set; } = true; +} diff --git a/src/Shared/AliasVault.RazorComponents/_Imports.razor b/src/Shared/AliasVault.RazorComponents/_Imports.razor index 1b7413bbb..d6862780f 100644 --- a/src/Shared/AliasVault.RazorComponents/_Imports.razor +++ b/src/Shared/AliasVault.RazorComponents/_Imports.razor @@ -1,2 +1,3 @@ @using AliasVault.RazorComponents.Models +@using AliasVault.RazorComponents.Tables @using Microsoft.AspNetCore.Components.Web From fa664ea9181e8560fdb89f3509ea3a93a604bb84 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Mon, 7 Oct 2024 20:03:23 +0200 Subject: [PATCH 09/11] Update all admin tables to use new SortableTable component (#220) --- src/AliasVault.Admin/Main/Pages/Emails.razor | 144 ++++++++++++------ .../Main/Pages/Users/Users.razor | 125 ++++++++++----- 2 files changed, 189 insertions(+), 80 deletions(-) diff --git a/src/AliasVault.Admin/Main/Pages/Emails.razor b/src/AliasVault.Admin/Main/Pages/Emails.razor index 21d33a74e..0cd61ee51 100644 --- a/src/AliasVault.Admin/Main/Pages/Emails.razor +++ b/src/AliasVault.Admin/Main/Pages/Emails.razor @@ -1,4 +1,5 @@ @page "/emails" +@using AliasVault.RazorComponents.Tables @inherits MainBase Emails @@ -6,7 +7,7 @@ + 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."> @@ -21,59 +22,66 @@ else
- - - - - - - - - - + + @foreach (var email in EmailList) + { + + + + + + + + - - - @foreach (var email in EmailList) - { - - - - - - - - - - } - -
IDTimeFromToSubjectPreviewAttachments
+ @email.Id + + @email.DateSystem.ToString("yyyy-MM-dd HH:mm") + + @(email.FromLocal.Length > 15 ? email.FromLocal.Substring(0, 15) : email.FromLocal)@@@(email.FromDomain.Length > 15 ? email.FromDomain.Substring(0, 15) : email.FromDomain) + + @email.ToLocal@@@email.ToDomain + + @(email.Subject.Length > 30 ? email.Subject.Substring(0, 30) : email.Subject) + + + @(email.MessagePreview?.Length > 30 ? email.MessagePreview.Substring(0, 30) : email.MessagePreview) + + + @email.Attachments.Count +
- @email.Id - - @email.DateSystem.ToString("yyyy-MM-dd HH:mm") - - @(email.FromLocal.Length > 15 ? email.FromLocal.Substring(0, 15) : email.FromLocal)@@@(email.FromDomain.Length > 15 ? email.FromDomain.Substring(0, 15) : email.FromDomain) - - @email.ToLocal@@@email.ToDomain - - @(email.Subject.Length > 30 ? email.Subject.Substring(0, 30) : email.Subject) - - - @(email.MessagePreview?.Length > 30 ? email.MessagePreview.Substring(0, 30) : email.MessagePreview) - - - @email.Attachments.Count -
+ } +
} @code { + private readonly List _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 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(); + } + /// 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 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(); diff --git a/src/AliasVault.Admin/Main/Pages/Users/Users.razor b/src/AliasVault.Admin/Main/Pages/Users/Users.razor index 1063b4957..6041ef9a1 100644 --- a/src/AliasVault.Admin/Main/Pages/Users/Users.razor +++ b/src/AliasVault.Admin/Main/Pages/Users/Users.razor @@ -1,4 +1,5 @@ @page "/users" +@using AliasVault.RazorComponents.Tables @inherits MainBase Users @@ -24,43 +25,40 @@ else
- - - - - - - - - - - - - - - - @foreach (var user in UserList) - { - - - - - - - - - - - - } - -
IDRegisteredUsername# Vaults# Email claimsStorage2FALast vault updateActions
@user.Id@user.CreatedAt.ToString("yyyy-MM-dd HH:mm")@user.UserName@user.VaultCount@user.EmailClaimCount@Math.Round((double)user.VaultStorageInKb / 1024, 1) MB@user.LastVaultUpdate.ToString("yyyy-MM-dd HH:mm") - -
+ + + @foreach (var user in UserList) + { + + @user.Id + @user.CreatedAt.ToString("yyyy-MM-dd HH:mm") + @user.UserName + @user.VaultCount + @user.EmailClaimCount + @Math.Round((double)user.VaultStorageInKb / 1024, 1) MB + + @user.LastVaultUpdate.ToString("yyyy-MM-dd HH:mm") + + + + + } +
} @code { + private readonly List _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 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(); + } + /// 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 From 14845e77e04075bff3704483bbb3bbfb1fd6601a Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Mon, 7 Oct 2024 20:29:29 +0200 Subject: [PATCH 10/11] Update tests (#220) --- .../Main/Components/Widgets/CreateNewIdentityWidget.razor | 4 ++-- src/AliasVault.Client/Main/Layout/Footer.razor | 2 +- .../Main/Pages/Credentials/AddEdit.razor | 2 +- src/AliasVault.Client/Main/Pages/Emails/Home.razor | 2 +- src/AliasVault.Client/Main/Pages/Welcome.razor | 4 ++-- .../Tests/Client/Shard1/CredentialTests.cs | 4 ++-- .../Tests/Client/Shard1/JwtTokenTests.cs | 4 ++-- .../Tests/Client/Shard5/UnlockTests.cs | 8 ++++---- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/AliasVault.Client/Main/Components/Widgets/CreateNewIdentityWidget.razor b/src/AliasVault.Client/Main/Components/Widgets/CreateNewIdentityWidget.razor index 83268dda4..0eaa59288 100644 --- a/src/AliasVault.Client/Main/Components/Widgets/CreateNewIdentityWidget.razor +++ b/src/AliasVault.Client/Main/Components/Widgets/CreateNewIdentityWidget.razor @@ -5,7 +5,7 @@ @implements IAsyncDisposable @if (IsPopupVisible) @@ -13,7 +13,7 @@
-

Create New Identity

+

Create New Alias

diff --git a/src/AliasVault.Client/Main/Layout/Footer.razor b/src/AliasVault.Client/Main/Layout/Footer.razor index 2afbbeb5d..9dd9de3d1 100644 --- a/src/AliasVault.Client/Main/Layout/Footer.razor +++ b/src/AliasVault.Client/Main/Layout/Footer.razor @@ -23,7 +23,7 @@ @code { private static readonly string[] Quotes = [ - "Tip: Use the g+c (go create) keyboard shortcut to quickly create a new identity.", + "Tip: Use the g+c (go create) keyboard shortcut to quickly create a new alias.", "Tip: Use the g+f (go find) keyboard shortcut to focus the search field.", "Tip: Use the g+h (go home) keyboard shortcut to go to the homepage.", "Tip: Use the g+l (go lock) keyboard shortcut to lock the vault.", diff --git a/src/AliasVault.Client/Main/Pages/Credentials/AddEdit.razor b/src/AliasVault.Client/Main/Pages/Credentials/AddEdit.razor index ba6b7f4c9..efcb2592c 100644 --- a/src/AliasVault.Client/Main/Pages/Credentials/AddEdit.razor +++ b/src/AliasVault.Client/Main/Pages/Credentials/AddEdit.razor @@ -228,7 +228,7 @@ else if (!EditMode) { - // When creating a new identity: start with focus on the service name input. + // When creating a new alias: start with focus on the service name input. await JsInteropService.FocusElementById("service-name"); } } diff --git a/src/AliasVault.Client/Main/Pages/Emails/Home.razor b/src/AliasVault.Client/Main/Pages/Emails/Home.razor index 6d8b7bdfa..84819127f 100644 --- a/src/AliasVault.Client/Main/Pages/Emails/Home.razor +++ b/src/AliasVault.Client/Main/Pages/Emails/Home.razor @@ -34,7 +34,7 @@ else if (NoEmailClaims) {
-

You are not using any private email addresses (yet). Create a new identity and use a private email address supported by AliasVault. All emails received by these private email addresses will show up here.

+

You are not using any private email addresses (yet). Create a new alias and use a private email address supported by AliasVault. All emails received by these private email addresses will show up here.

} diff --git a/src/AliasVault.Client/Main/Pages/Welcome.razor b/src/AliasVault.Client/Main/Pages/Welcome.razor index 327f58ae6..05eee94e8 100644 --- a/src/AliasVault.Client/Main/Pages/Welcome.razor +++ b/src/AliasVault.Client/Main/Pages/Welcome.razor @@ -11,7 +11,7 @@

How do I use AliasVault?

-

Create a random identity with an associated email address. To get started, simply click the "+ New Identity" button in the top right corner.

+

Create a random identity with an associated email address. To get started, simply click the "+ New Alias" button in the top right corner.

What is the purpose of AliasVault?

@@ -28,7 +28,7 @@

Get Started Now

- Go ahead and create a new login by clicking "+ New Identity" in the top right. Or explore these options: + Go ahead and create a new login by clicking "+ New Alias" in the top right. Or explore these options:

diff --git a/src/Tests/AliasVault.E2ETests/Tests/Client/Shard1/CredentialTests.cs b/src/Tests/AliasVault.E2ETests/Tests/Client/Shard1/CredentialTests.cs index 0c16a2122..dd5ea367a 100644 --- a/src/Tests/AliasVault.E2ETests/Tests/Client/Shard1/CredentialTests.cs +++ b/src/Tests/AliasVault.E2ETests/Tests/Client/Shard1/CredentialTests.cs @@ -100,7 +100,7 @@ public class CredentialTests : ClientPlaywrightTest Assert.That(pageContent, Does.Contain(serviceNameBefore), "Created credential service name does not appear on login page."); // Click the edit button. - var editButton = Page.Locator("text=Edit credentials entry").First; + var editButton = Page.Locator("text=Edit").First; await editButton.ClickAsync(); await WaitForUrlAsync("edit", "Save Credentials"); @@ -113,7 +113,7 @@ public class CredentialTests : ClientPlaywrightTest var submitButton = Page.Locator("text=Save Credentials").First; await submitButton.ClickAsync(); - await WaitForUrlAsync("credentials/**", "Delete credentials entry"); + await WaitForUrlAsync("credentials/**", "Delete"); pageContent = await Page.TextContentAsync("body"); Assert.That(pageContent, Does.Contain("Credentials updated"), "Credential update confirmation message not shown."); diff --git a/src/Tests/AliasVault.E2ETests/Tests/Client/Shard1/JwtTokenTests.cs b/src/Tests/AliasVault.E2ETests/Tests/Client/Shard1/JwtTokenTests.cs index 381205530..48db7736f 100644 --- a/src/Tests/AliasVault.E2ETests/Tests/Client/Shard1/JwtTokenTests.cs +++ b/src/Tests/AliasVault.E2ETests/Tests/Client/Shard1/JwtTokenTests.cs @@ -36,7 +36,7 @@ public class JwtTokenTests : ClientPlaywrightTest await WaitForUrlAsync("test/2", "Test 2 OK"); var pageContent = await Page.TextContentAsync("body"); - Assert.That(pageContent, Does.Contain("Test webapi call 2."), "No page content after refreshing access token."); + Assert.That(pageContent, Does.Contain("Test webapi call 2"), "No page content after refreshing access token."); } /// @@ -74,7 +74,7 @@ public class JwtTokenTests : ClientPlaywrightTest await WaitForUrlAsync(startUrl, "Test 1 OK"); var pageContent = await Page.TextContentAsync("body"); - Assert.That(pageContent, Does.Contain("Test webapi call 1."), "No index content after unlocking database with a expired JWT token."); + Assert.That(pageContent, Does.Contain("Test webapi call 1"), "No index content after unlocking database with a expired JWT token."); } /// diff --git a/src/Tests/AliasVault.E2ETests/Tests/Client/Shard5/UnlockTests.cs b/src/Tests/AliasVault.E2ETests/Tests/Client/Shard5/UnlockTests.cs index e8c300368..dedad1ec7 100644 --- a/src/Tests/AliasVault.E2ETests/Tests/Client/Shard5/UnlockTests.cs +++ b/src/Tests/AliasVault.E2ETests/Tests/Client/Shard5/UnlockTests.cs @@ -30,10 +30,10 @@ public class UnlockTests : ClientPlaywrightTest await RefreshPageAndUnlockVault(); // Check if we get redirected back to the page we were trying to access. - await WaitForUrlAsync(startUrl, "Test webapi call 1."); + await WaitForUrlAsync(startUrl, "Test webapi call 1"); var pageContent = await Page.TextContentAsync("body"); - Assert.That(pageContent, Does.Contain("Test webapi call 1."), "No index content after unlocking database."); + Assert.That(pageContent, Does.Contain("Test 1 OK."), "No index content after unlocking database."); } /// @@ -69,9 +69,9 @@ public class UnlockTests : ClientPlaywrightTest await submitButton.ClickAsync(); // Check if we get redirected back to the page we were trying to access. - await WaitForUrlAsync(startUrl, "Test webapi call 1."); + await WaitForUrlAsync(startUrl, "Test webapi call 1"); var pageContent = await Page.TextContentAsync("body"); - Assert.That(pageContent, Does.Contain("Test webapi call 1."), "No index content after unlocking database."); + Assert.That(pageContent, Does.Contain("Test 1 OK."), "No index content after unlocking database."); } } From 62d2249f406d0ddc925016667ffd898596ee7f4d Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Mon, 7 Oct 2024 20:50:20 +0200 Subject: [PATCH 11/11] Refactor (#220) --- src/AliasVault.Admin/Main/Pages/Emails.razor | 2 +- .../Main/Pages/Logging/Auth.razor | 28 +++-- .../Main/Pages/Logging/General.razor | 2 +- .../Main/Pages/Users/Users.razor | 111 ++++++++++-------- ...20241007153012_DatetimeOffsetToDateTime.cs | 3 +- .../Tables/SortableTable.razor | 4 +- .../Common/ClientPlaywrightTest.cs | 4 +- 7 files changed, 87 insertions(+), 67 deletions(-) diff --git a/src/AliasVault.Admin/Main/Pages/Emails.razor b/src/AliasVault.Admin/Main/Pages/Emails.razor index 0cd61ee51..49a64cde7 100644 --- a/src/AliasVault.Admin/Main/Pages/Emails.razor +++ b/src/AliasVault.Admin/Main/Pages/Emails.razor @@ -22,7 +22,7 @@ else
- + @foreach (var email in EmailList) { diff --git a/src/AliasVault.Admin/Main/Pages/Logging/Auth.razor b/src/AliasVault.Admin/Main/Pages/Logging/Auth.razor index ced490889..7c76d4081 100644 --- a/src/AliasVault.Admin/Main/Pages/Logging/Auth.razor +++ b/src/AliasVault.Admin/Main/Pages/Logging/Auth.razor @@ -41,7 +41,7 @@ else
- + @foreach (var log in LogList) { @@ -173,6 +173,23 @@ else } } + query = ApplySort(query); + + TotalRecords = await query.CountAsync(); + LogList = await query + .Skip((CurrentPage - 1) * PageSize) + .Take(PageSize) + .ToListAsync(); + + IsLoading = false; + StateHasChanged(); + } + + /// + /// Apply sort to the query. + /// + private IQueryable ApplySort(IQueryable query) + { // Apply sort. switch (SortColumn) { @@ -208,14 +225,7 @@ else break; } - TotalRecords = await query.CountAsync(); - LogList = await query - .Skip((CurrentPage - 1) * PageSize) - .Take(PageSize) - .ToListAsync(); - - IsLoading = false; - StateHasChanged(); + return query; } private async Task DeleteLogsWithConfirmation() diff --git a/src/AliasVault.Admin/Main/Pages/Logging/General.razor b/src/AliasVault.Admin/Main/Pages/Logging/General.razor index c5d7389aa..02ab7eb6e 100644 --- a/src/AliasVault.Admin/Main/Pages/Logging/General.razor +++ b/src/AliasVault.Admin/Main/Pages/Logging/General.razor @@ -40,7 +40,7 @@ else
- + @foreach (var log in LogList) { diff --git a/src/AliasVault.Admin/Main/Pages/Users/Users.razor b/src/AliasVault.Admin/Main/Pages/Users/Users.razor index 6041ef9a1..603985ac3 100644 --- a/src/AliasVault.Admin/Main/Pages/Users/Users.razor +++ b/src/AliasVault.Admin/Main/Pages/Users/Users.razor @@ -26,7 +26,7 @@ else
- + @foreach (var user in UserList) { @@ -117,57 +117,9 @@ else } // 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; - } + query = ApplySort(query); TotalRecords = await query.CountAsync(); - var users = await query .Skip((CurrentPage - 1) * PageSize) .Take(PageSize) @@ -204,4 +156,63 @@ else IsLoading = false; StateHasChanged(); } + + /// + /// Apply sort to the query. + /// + private IQueryable ApplySort(IQueryable query) + { + // 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; + } + + return query; + } + } diff --git a/src/Databases/AliasServerDb/Migrations/20241007153012_DatetimeOffsetToDateTime.cs b/src/Databases/AliasServerDb/Migrations/20241007153012_DatetimeOffsetToDateTime.cs index 483a37051..c835ed734 100644 --- a/src/Databases/AliasServerDb/Migrations/20241007153012_DatetimeOffsetToDateTime.cs +++ b/src/Databases/AliasServerDb/Migrations/20241007153012_DatetimeOffsetToDateTime.cs @@ -1,4 +1,5 @@ -using Microsoft.EntityFrameworkCore.Migrations; +// +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/src/Shared/AliasVault.RazorComponents/Tables/SortableTable.razor b/src/Shared/AliasVault.RazorComponents/Tables/SortableTable.razor index 542f6503a..d46a49dd7 100644 --- a/src/Shared/AliasVault.RazorComponents/Tables/SortableTable.razor +++ b/src/Shared/AliasVault.RazorComponents/Tables/SortableTable.razor @@ -1,6 +1,4 @@ -@typeparam TItem - - +
@foreach (var column in Columns) diff --git a/src/Tests/AliasVault.E2ETests/Common/ClientPlaywrightTest.cs b/src/Tests/AliasVault.E2ETests/Common/ClientPlaywrightTest.cs index f00219079..986394ab8 100644 --- a/src/Tests/AliasVault.E2ETests/Common/ClientPlaywrightTest.cs +++ b/src/Tests/AliasVault.E2ETests/Common/ClientPlaywrightTest.cs @@ -253,8 +253,8 @@ public class ClientPlaywrightTest : PlaywrightTest await Page.ClickAsync("text=" + credentialName); // Wait for the credential details page to load. - await WaitForUrlAsync("credentials/**", "Delete credentials entry"); - await Page.ClickAsync("text=Delete credentials entry"); + await WaitForUrlAsync("credentials/**", "Delete"); + await Page.ClickAsync("text=Delete"); // Wait for the delete credential page to load. await WaitForUrlAsync("credentials/**/delete", "You can delete a credentials entry below");