From 4a586cf11761b59a6f794473ce3e51e54696ce40 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Fri, 28 Jun 2024 10:05:50 +0200 Subject: [PATCH 1/3] Add DbStatus indicator to UI (#55) --- .../AliasVault.WebApp.csproj | 3 + src/AliasVault.WebApp/Auth/Pages/Unlock.razor | 4 +- .../Loading/SmallLoadingIndicator.razor | 7 + .../Layout/ClientDbStatusIndicator.razor | 57 ++++++++ .../Layout/ClientDbStatusIndicator.razor.css | 83 ++++++++++++ src/AliasVault.WebApp/Layout/TopMenu.razor | 4 +- src/AliasVault.WebApp/Pages/Base/PageBase.cs | 5 +- src/AliasVault.WebApp/Pages/Dbtest.razor | 40 +++--- src/AliasVault.WebApp/Program.cs | 3 +- .../DbService.cs} | 88 ++++++++----- .../Services/Database/DbServiceState.cs | 122 ++++++++++++++++++ src/AliasVault.WebApp/_Imports.razor | 1 + .../AliasVault.E2ETests/Tests/UnlockTests.cs | 2 - 13 files changed, 358 insertions(+), 61 deletions(-) create mode 100644 src/AliasVault.WebApp/Components/Loading/SmallLoadingIndicator.razor create mode 100644 src/AliasVault.WebApp/Layout/ClientDbStatusIndicator.razor create mode 100644 src/AliasVault.WebApp/Layout/ClientDbStatusIndicator.razor.css rename src/AliasVault.WebApp/Services/{AliasClientDbService.cs => Database/DbService.cs} (84%) create mode 100644 src/AliasVault.WebApp/Services/Database/DbServiceState.cs diff --git a/src/AliasVault.WebApp/AliasVault.WebApp.csproj b/src/AliasVault.WebApp/AliasVault.WebApp.csproj index 4411ae927..a9fd2d83f 100644 --- a/src/AliasVault.WebApp/AliasVault.WebApp.csproj +++ b/src/AliasVault.WebApp/AliasVault.WebApp.csproj @@ -60,6 +60,9 @@ + + true + PreserveNewest diff --git a/src/AliasVault.WebApp/Auth/Pages/Unlock.razor b/src/AliasVault.WebApp/Auth/Pages/Unlock.razor index 41f26bda9..84eb269fb 100644 --- a/src/AliasVault.WebApp/Auth/Pages/Unlock.razor +++ b/src/AliasVault.WebApp/Auth/Pages/Unlock.razor @@ -5,7 +5,7 @@ @inject NavigationManager NavigationManager @inject AuthService AuthService @inject GlobalNotificationService GlobalNotificationService -@inject AliasClientDbService AliasClientDbService +@inject DbService DbService @inject ILocalStorageService LocalStorage @using System.Text.Json @using AliasVault.Shared.Models @@ -145,7 +145,7 @@ AuthService.StoreEncryptionKey(passwordHash); // Try to retrieve vault again from remote. - await AliasClientDbService.GetDbContextAsync(); + await DbService.GetDbContextAsync(); // Redirect to home page. await AuthStateProvider.GetAuthenticationStateAsync(); diff --git a/src/AliasVault.WebApp/Components/Loading/SmallLoadingIndicator.razor b/src/AliasVault.WebApp/Components/Loading/SmallLoadingIndicator.razor new file mode 100644 index 000000000..c6e69416e --- /dev/null +++ b/src/AliasVault.WebApp/Components/Loading/SmallLoadingIndicator.razor @@ -0,0 +1,7 @@ +
+ + Loading... +
diff --git a/src/AliasVault.WebApp/Layout/ClientDbStatusIndicator.razor b/src/AliasVault.WebApp/Layout/ClientDbStatusIndicator.razor new file mode 100644 index 000000000..d4eb3ca6e --- /dev/null +++ b/src/AliasVault.WebApp/Layout/ClientDbStatusIndicator.razor @@ -0,0 +1,57 @@ +@using AliasVault.WebApp.Services.Database +@implements IDisposable +@inject DbService DbService + +@if (Loading) +{ +
+ +

@Message

+
+} + + + +@code { + private bool Loading { get; set; } = false; + private string Message { get; set; } = ""; + private bool _isSaving => DbService.GetState().CurrentState.Status == DbServiceState.DatabaseStatus.Saving; + + protected override void OnInitialized() + { + DbService.GetState().StateChanged += OnDatabaseStateChanged; + } + + private async void OnDatabaseStateChanged(object? sender, DbServiceState.DatabaseState newState) + { + await InvokeAsync(StateHasChanged); + if (newState.Status == DbServiceState.DatabaseStatus.Saving) + { + // Show loading indicator for at least 0.5 seconds even if the save operation is faster. + Message = "Saving..."; + await ShowLoadingIndicatorAsync(); + } + else if (newState.Status == DbServiceState.DatabaseStatus.Loading) + { + Message = "Loading..."; + await ShowLoadingIndicatorAsync(); + } + } + + private async Task ShowLoadingIndicatorAsync() + { + Loading = true; + StateHasChanged(); + await Task.Delay(500); + Loading = false; + StateHasChanged(); + } + + public void Dispose() + { + DbService.GetState().StateChanged -= OnDatabaseStateChanged; + } +} diff --git a/src/AliasVault.WebApp/Layout/ClientDbStatusIndicator.razor.css b/src/AliasVault.WebApp/Layout/ClientDbStatusIndicator.razor.css new file mode 100644 index 000000000..881d128a5 --- /dev/null +++ b/src/AliasVault.WebApp/Layout/ClientDbStatusIndicator.razor.css @@ -0,0 +1,83 @@ +.navbar-toggler { + background-color: rgba(255, 255, 255, 0.1); +} + +.top-row { + height: 3.5rem; + background-color: rgba(0,0,0,0.4); +} + +.navbar-brand { + font-size: 1.1rem; +} + +.bi { + display: inline-block; + position: relative; + width: 1.25rem; + height: 1.25rem; + margin-right: 0.75rem; + top: -1px; + background-size: cover; +} + +.bi-house-door-fill-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E"); +} + +.bi-plus-square-fill-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E"); +} + +.bi-list-nested-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E"); +} + +.nav-item { + font-size: 0.9rem; + padding-bottom: 0.5rem; +} + + .nav-item:first-of-type { + padding-top: 1rem; + } + + .nav-item:last-of-type { + padding-bottom: 1rem; + } + + .nav-item ::deep a { + color: #d7d7d7; + border-radius: 4px; + height: 3rem; + display: flex; + align-items: center; + line-height: 3rem; + } + +.nav-item ::deep a.active { + background-color: rgba(255,255,255,0.37); + color: white; +} + +.nav-item ::deep a:hover { + background-color: rgba(255,255,255,0.1); + color: white; +} + +@media (min-width: 641px) { + .navbar-toggler { + display: none; + } + + .collapse { + /* Never collapse the sidebar for wide screens */ + display: block; + } + + .nav-scrollable { + /* Allow sidebar to scroll for tall menus */ + height: calc(100vh - 3.5rem); + overflow-y: auto; + } +} diff --git a/src/AliasVault.WebApp/Layout/TopMenu.razor b/src/AliasVault.WebApp/Layout/TopMenu.razor index b459eeaf8..fa39d6468 100644 --- a/src/AliasVault.WebApp/Layout/TopMenu.razor +++ b/src/AliasVault.WebApp/Layout/TopMenu.razor @@ -24,7 +24,9 @@ -
+
+ + diff --git a/src/AliasVault.WebApp/Pages/Base/PageBase.cs b/src/AliasVault.WebApp/Pages/Base/PageBase.cs index 293dcc2ab..90862f72a 100644 --- a/src/AliasVault.WebApp/Pages/Base/PageBase.cs +++ b/src/AliasVault.WebApp/Pages/Base/PageBase.cs @@ -10,6 +10,7 @@ namespace AliasVault.WebApp.Pages.Base; using AliasVault.WebApp.Auth.Services; using AliasVault.WebApp.Components.Models; using AliasVault.WebApp.Services; +using AliasVault.WebApp.Services.Database; using Blazored.LocalStorage; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Authorization; @@ -49,10 +50,10 @@ public class PageBase : OwningComponentBase public IJSRuntime Js { get; set; } = null!; /// - /// Gets or sets the AliasClientDbService. + /// Gets or sets the DbService. /// [Inject] - public AliasClientDbService AliasClientDbService { get; set; } = null!; + public DbService DbService { get; set; } = null!; /// /// Gets or sets the AuthService. diff --git a/src/AliasVault.WebApp/Pages/Dbtest.razor b/src/AliasVault.WebApp/Pages/Dbtest.razor index 0ea294b66..ffdec555d 100644 --- a/src/AliasVault.WebApp/Pages/Dbtest.razor +++ b/src/AliasVault.WebApp/Pages/Dbtest.razor @@ -22,14 +22,12 @@ } - - -
@foreach (var password in Passwords) {
+

@password.CreatedAt

@password.Value

@@ -40,6 +38,15 @@ @code { private bool IsLoading { get; set; } = true; private List Passwords { get; set; } = new(); + private AliasClientDbContext dbContext = new(); + + private async Task RemovePassword(Password password) + { + var db = await DbService.GetDbContextAsync(); + db.Passwords.Remove(password); + await DbService.SaveDatabaseAsync(); + await RefreshAliasFromDbAsync(); + } /// protected override async Task OnAfterRenderAsync(bool firstRender) @@ -48,22 +55,20 @@ if (firstRender) { - await LoadAliasFromDbAsync(); + await RefreshAliasFromDbAsync(); } } - private async Task LoadAliasFromDbAsync() + private async Task RefreshAliasFromDbAsync() { IsLoading = true; StateHasChanged(); - var db = await AliasClientDbService.GetDbContextAsync(); + var db = await DbService.GetDbContextAsync(); // Load all passwords from the database var passwords = await db.Passwords.ToListAsync(); - // Show them on page - Passwords = passwords; IsLoading = false; StateHasChanged(); @@ -71,24 +76,13 @@ private async Task AddRecord() { - var db = await AliasClientDbService.GetDbContextAsync(); + var db = await DbService.GetDbContextAsync(); // Insert row in database db.Passwords.Add(new Password() { Id = Guid.NewGuid(), Value = "Test factory insert SQLite", CreatedAt = DateTime.Now }); - // Save changes - await db.SaveChangesAsync(); - - await LoadAliasFromDbAsync(); - } - - private async Task ExportDbToString() - { - await AliasClientDbService.SaveDatabaseAsync(); - } - - private async Task SaveDb() - { - await (await AliasClientDbService.GetDbContextAsync()).SaveChangesAsync(); + // Save changes and upload db to remote. + await DbService.SaveDatabaseAsync(); + await RefreshAliasFromDbAsync(); } } diff --git a/src/AliasVault.WebApp/Program.cs b/src/AliasVault.WebApp/Program.cs index bf9d73270..05d87887d 100644 --- a/src/AliasVault.WebApp/Program.cs +++ b/src/AliasVault.WebApp/Program.cs @@ -9,6 +9,7 @@ using AliasVault.WebApp; using AliasVault.WebApp.Auth.Providers; using AliasVault.WebApp.Auth.Services; using AliasVault.WebApp.Services; +using AliasVault.WebApp.Services.Database; using Blazored.LocalStorage; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.Web; @@ -36,7 +37,7 @@ builder.Services.AddTransient(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddSingleton(); diff --git a/src/AliasVault.WebApp/Services/AliasClientDbService.cs b/src/AliasVault.WebApp/Services/Database/DbService.cs similarity index 84% rename from src/AliasVault.WebApp/Services/AliasClientDbService.cs rename to src/AliasVault.WebApp/Services/Database/DbService.cs index 8c8dd0f54..f12ffb0eb 100644 --- a/src/AliasVault.WebApp/Services/AliasClientDbService.cs +++ b/src/AliasVault.WebApp/Services/Database/DbService.cs @@ -1,18 +1,17 @@ //----------------------------------------------------------------------- -// +// // 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.WebApp.Services; +namespace AliasVault.WebApp.Services.Database; using System.Data; using System.Net.Http.Json; using AliasClientDb; using AliasVault.Shared.Models.WebApi; using AliasVault.WebApp.Auth.Services; -using Microsoft.AspNetCore.Components; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.JSInterop; @@ -22,32 +21,45 @@ using Microsoft.JSInterop; /// with a AliasClientDb database instance that is only persisted in memory due to the encryption requirements of the /// database itself. The database should not be persisted to disk when in un-encrypted form. ///
-public class AliasClientDbService +public class DbService { private readonly AuthService _authService; private readonly IJSRuntime _jsRuntime; private readonly HttpClient _httpClient; + private readonly DbServiceState _state = new(); private AliasClientDbContext? _dbContext; private Task _initializationTask; private bool _isSuccessfullyInitialized; private int _retryCount; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// AuthService. /// IJSRuntime. /// HttpClient. - public AliasClientDbService(AuthService authService, IJSRuntime jsRuntime, HttpClient httpClient) + public DbService(AuthService authService, IJSRuntime jsRuntime, HttpClient httpClient) { _authService = authService; _jsRuntime = jsRuntime; _httpClient = httpClient; + // Set the initial state of the database service. + _state.UpdateState(DbServiceState.DatabaseStatus.Idle); + // Initialize the database asynchronously _initializationTask = InitializeDatabaseAsync(); } + /// + /// Gets database service state object which can be subscribed to. + /// + /// DbServiceState instance. + public DbServiceState GetState() + { + return _state; + } + /// /// Ensures that the service initialization is complete before proceeding. /// @@ -91,46 +103,58 @@ public class AliasClientDbService { await EnsureInitializedAsync(); - using var memoryStream = new MemoryStream(); - using var connection = new SqliteConnection(_dbContext!.Database.GetDbConnection().ConnectionString); - await connection.OpenAsync(); - using var command = connection.CreateCommand(); - command.CommandText = "VACUUM main INTO @fileName"; + // Set the initial state of the database service. + _state.UpdateState(DbServiceState.DatabaseStatus.Saving); - var tempFileName = Path.GetRandomFileName(); - command.Parameters.Add(new SqliteParameter("@fileName", tempFileName)); - await command.ExecuteNonQueryAsync(); + // Save the actual dbContext. + await _dbContext!.SaveChangesAsync(); - var bytes = await File.ReadAllBytesAsync(tempFileName); + string base64String = await ExportSqliteToBase64Async(); - string base64String = Convert.ToBase64String(bytes); - File.Delete(tempFileName); - - // Encrypt base64 string. - // Encrypt using IJSInterop - Console.WriteLine("Encrypted using key: " + _authService.GetEncryptionKeyAsBase64Async()); + // Encrypt base64 string using IJSInterop. string encryptedBase64String = await _jsRuntime.InvokeAsync("cryptoInterop.encrypt", base64String, _authService.GetEncryptionKeyAsBase64Async()); - // Decrypt it again - string decryptedBase64String = await _jsRuntime.InvokeAsync("cryptoInterop.decrypt", encryptedBase64String, _authService.GetEncryptionKeyAsBase64Async()); - - // Print original, encrypted and decrypted sting to console - Console.WriteLine("Original: " + base64String); - Console.WriteLine("Encrypted: " + encryptedBase64String); - Console.WriteLine("Decrypted: " + decryptedBase64String); - // Save to webapi. var success = await SaveToServerAsync(encryptedBase64String); if (success) { Console.WriteLine("Database succesfully saved to server."); + _state.UpdateState(DbServiceState.DatabaseStatus.Idle); } else { Console.WriteLine("Failed to save database to server."); + _state.UpdateState(DbServiceState.DatabaseStatus.Error); } } + /// + /// Export the in-memory SQLite database to a base64 string. + /// + /// Base64 encoded string that represents SQLite database. + public async Task ExportSqliteToBase64Async() + { + var tempFileName = Path.GetRandomFileName(); + + // Export SQLite memory database to a temp file. + using var memoryStream = new MemoryStream(); + using var connection = new SqliteConnection(_dbContext!.Database.GetDbConnection().ConnectionString); + await connection.OpenAsync(); + using var command = connection.CreateCommand(); + command.CommandText = "VACUUM main INTO @fileName"; + command.Parameters.Add(new SqliteParameter("@fileName", tempFileName)); + await command.ExecuteNonQueryAsync(); + + // Get bytes. + var bytes = await File.ReadAllBytesAsync(tempFileName); + string base64String = Convert.ToBase64String(bytes); + + // Delete temp file. + File.Delete(tempFileName); + + return base64String; + } + private static async Task ImportDbContextFromBase64Async(AliasClientDbContext dbContext, string base64String) { var bytes = Convert.FromBase64String(base64String); @@ -202,6 +226,8 @@ public class AliasClientDbService return; } + _state.UpdateState(DbServiceState.DatabaseStatus.Loading); + // Create a new in-memory database. string connectionString = "Data Source=AliasClientDb.sqlite"; using var connection = new SqliteConnection(connectionString); @@ -221,10 +247,12 @@ public class AliasClientDbService if (loaded) { _isSuccessfullyInitialized = true; + _state.UpdateState(DbServiceState.DatabaseStatus.Idle); Console.WriteLine("Database succesfully loaded from server."); } else { + _state.UpdateState(DbServiceState.DatabaseStatus.Error); Console.WriteLine("Failed to load database from server."); } } @@ -284,7 +312,7 @@ public class AliasClientDbService try { - await _httpClient.PostAsJsonAsync("api/v1/Vault", vaultObject); + await _httpClient.PostAsJsonAsync("api/v1/Vault", vaultObject); return true; } catch diff --git a/src/AliasVault.WebApp/Services/Database/DbServiceState.cs b/src/AliasVault.WebApp/Services/Database/DbServiceState.cs new file mode 100644 index 000000000..8f35343ba --- /dev/null +++ b/src/AliasVault.WebApp/Services/Database/DbServiceState.cs @@ -0,0 +1,122 @@ +//----------------------------------------------------------------------- +// +// 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.WebApp.Services.Database; + +/// +/// Class to manage the state of the AliasClientDbService that others can subscribe to events for. +/// +public class DbServiceState +{ + private DatabaseState _currentState = new(); + + /// + /// Subscribe to this event to get notified when the state of the database changes. + /// + public event EventHandler StateChanged = (sender, e) => { }; + + /// + /// Database status enum. + /// + public enum DatabaseStatus + { + /// + /// No database operation is in progress. + /// + Idle, + + /// + /// Database is loading from server. + /// + Loading, + + /// + /// Database is saving to server. + /// + Saving, + + /// + /// An error occurred during a database operation. + /// + Error, + } + + /// + /// Gets the current state of the database. + /// + public DatabaseState CurrentState + { + get => _currentState; + private set + { + if (_currentState != value) + { + _currentState = value; + OnStateChanged(_currentState); + } + } + } + + /// + /// Update the state of the database. + /// + /// New status. + public void UpdateState(DatabaseStatus status) + { + CurrentState = new DatabaseState + { + Status = status, + Message = string.Empty, + LastUpdated = DateTime.Now, + }; + } + + /// + /// Update the state of the database with an additional message. + /// + /// New status. + /// Status message. + public void UpdateState(DatabaseStatus status, string message) + { + CurrentState = new DatabaseState + { + Status = status, + Message = message, + LastUpdated = DateTime.Now, + }; + } + + /// + /// OnStateChanged event handler. + /// + /// The new state. + protected virtual void OnStateChanged(DatabaseState newState) + { + StateChanged?.Invoke(this, newState); + } + + /// + /// Database state class. + /// + public class DatabaseState + { + /// + /// Gets or sets the current status of the database. + /// + public DatabaseStatus Status { get; set; } = DatabaseStatus.Idle; + + /// + /// Gets or sets the message associated with the current status. + /// + public string Message { get; set; } = string.Empty; + + /// + /// Gets or sets the last time the state was updated. + /// + public DateTime LastUpdated { get; set; } = DateTime.Now; + } +} diff --git a/src/AliasVault.WebApp/_Imports.razor b/src/AliasVault.WebApp/_Imports.razor index 3b0f46fa9..bae80ee01 100644 --- a/src/AliasVault.WebApp/_Imports.razor +++ b/src/AliasVault.WebApp/_Imports.razor @@ -17,6 +17,7 @@ @using AliasVault.WebApp.Components.Loading @using AliasVault.WebApp.Pages.Base @using AliasVault.WebApp.Services +@using AliasVault.WebApp.Services.Database @using Microsoft.AspNetCore.Components.Authorization @using Microsoft.AspNetCore.Components.WebAssembly.Authentication @using Blazored.LocalStorage diff --git a/src/Tests/AliasVault.E2ETests/Tests/UnlockTests.cs b/src/Tests/AliasVault.E2ETests/Tests/UnlockTests.cs index 356608a14..f5cb928ee 100644 --- a/src/Tests/AliasVault.E2ETests/Tests/UnlockTests.cs +++ b/src/Tests/AliasVault.E2ETests/Tests/UnlockTests.cs @@ -14,8 +14,6 @@ namespace AliasVault.E2ETests.Tests; [TestFixture] public class UnlockTests : PlaywrightTest { - private static readonly Random Random = new(); - /// /// Test that the unlock page is displayed after hard refresh which should /// clear the encryption key from memory. From f29606ea94b149b593cbb98132159e5e0546a13d Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Fri, 28 Jun 2024 10:47:38 +0200 Subject: [PATCH 2/3] Update UI style (#55) --- .../AliasVault.WebApp.csproj | 2 +- .../Loading/SmallLoadingIndicator.razor | 19 +++- ...ndicator.razor => DbStatusIndicator.razor} | 19 ++-- ....razor.css => DbStatusIndicator.razor.css} | 0 src/AliasVault.WebApp/Layout/NavMenu.razor | 39 -------- .../Layout/NavMenu.razor.css | 83 ----------------- src/AliasVault.WebApp/Layout/TopMenu.razor | 40 +++++---- .../wwwroot/css/tailwind.css | 89 ++++++------------- 8 files changed, 78 insertions(+), 213 deletions(-) rename src/AliasVault.WebApp/Layout/{ClientDbStatusIndicator.razor => DbStatusIndicator.razor} (77%) rename src/AliasVault.WebApp/Layout/{ClientDbStatusIndicator.razor.css => DbStatusIndicator.razor.css} (100%) delete mode 100644 src/AliasVault.WebApp/Layout/NavMenu.razor delete mode 100644 src/AliasVault.WebApp/Layout/NavMenu.razor.css diff --git a/src/AliasVault.WebApp/AliasVault.WebApp.csproj b/src/AliasVault.WebApp/AliasVault.WebApp.csproj index a9fd2d83f..6aa922302 100644 --- a/src/AliasVault.WebApp/AliasVault.WebApp.csproj +++ b/src/AliasVault.WebApp/AliasVault.WebApp.csproj @@ -60,7 +60,7 @@ - + true diff --git a/src/AliasVault.WebApp/Components/Loading/SmallLoadingIndicator.razor b/src/AliasVault.WebApp/Components/Loading/SmallLoadingIndicator.razor index c6e69416e..4547c0479 100644 --- a/src/AliasVault.WebApp/Components/Loading/SmallLoadingIndicator.razor +++ b/src/AliasVault.WebApp/Components/Loading/SmallLoadingIndicator.razor @@ -1,7 +1,22 @@ -
-
+ Loading...
+ +@code { + /// + /// Optional title of the loading indicator. + /// + [Parameter] + public string Title { get; set; } = string.Empty; + + /// + /// Set spinning to false to stop the animation. + /// + [Parameter] + public bool Spinning { get; set; } = true; + +} diff --git a/src/AliasVault.WebApp/Layout/ClientDbStatusIndicator.razor b/src/AliasVault.WebApp/Layout/DbStatusIndicator.razor similarity index 77% rename from src/AliasVault.WebApp/Layout/ClientDbStatusIndicator.razor rename to src/AliasVault.WebApp/Layout/DbStatusIndicator.razor index d4eb3ca6e..f9939f72b 100644 --- a/src/AliasVault.WebApp/Layout/ClientDbStatusIndicator.razor +++ b/src/AliasVault.WebApp/Layout/DbStatusIndicator.razor @@ -1,14 +1,16 @@ -@using AliasVault.WebApp.Services.Database -@implements IDisposable +@implements IDisposable @inject DbService DbService @if (Loading) {
- -

@Message

+
} +else +{ + +}