From 86cb7b73d76dfbd691678ef8583c61450794a8ea Mon Sep 17 00:00:00 2001 From: Arthur Thomas Date: Tue, 6 Aug 2024 18:38:39 -0400 Subject: [PATCH 1/6] chore: code cleanup --- Desktop/Properties/launchSettings.json | 16 +++++------ KeyVaultExplorer/App.axaml.cs | 9 ++----- KeyVaultExplorer/Database/KvExplorerDb.cs | 8 +++--- KeyVaultExplorer/Exceptions/KvExceptions.cs | 1 - KeyVaultExplorer/KeyVaultExplorer.csproj | 2 +- KeyVaultExplorer/Models/AppSettings.cs | 2 +- KeyVaultExplorer/Models/Constants.cs | 4 +-- .../Models/KeyVaultValuesAmalgamation.cs | 2 -- .../Models/SubscriptionDataItems.cs | 1 - KeyVaultExplorer/Services/AuthService.cs | 1 - .../DatabaseEncryptedPasswordManager.cs | 12 ++++----- .../Services/MacOSKeyChainService.cs | 2 +- .../Services/ServiceCollectionExtension.cs | 6 +++-- KeyVaultExplorer/Services/StringExtensions.cs | 6 ++--- KeyVaultExplorer/Services/VaultService.cs | 2 +- .../SecretNameValidationAttribute.cs | 2 -- KeyVaultExplorer/ViewModels/AppViewModel.cs | 11 +------- .../CreateNewSecretVersionViewModel.cs | 27 ++++++------------- .../ViewModels/KeyVaultTreeListViewModel.cs | 6 ++--- KeyVaultExplorer/ViewModels/MainViewModel.cs | 1 - .../ViewModels/NotificationViewModel.cs | 7 ++--- .../ViewModels/PropertiesPageViewModel.cs | 6 +---- .../ViewModels/SettingsPageViewModel.cs | 3 --- .../ViewModels/SubscriptionsPageViewModel.cs | 6 ----- .../ViewModels/VaultPageViewModel.cs | 8 +----- KeyVaultExplorer/ViewModels/ViewModelBase.cs | 1 - .../CustomControls/KeyVaultTreeList.axaml.cs | 7 +---- .../Views/CustomControls/ToolBar.axaml.cs | 8 ------ KeyVaultExplorer/Views/MainView.axaml.cs | 6 ++--- .../Views/Pages/AboutPageWindow.axaml.cs | 1 - .../CreateNewSecretVersion.axaml.cs | 13 --------- .../Views/Pages/SettingsPage.axaml.cs | 4 +-- .../Views/Pages/VaultPage.axaml.cs | 8 +++--- mpdev/msix.json | 2 +- 34 files changed, 54 insertions(+), 147 deletions(-) diff --git a/Desktop/Properties/launchSettings.json b/Desktop/Properties/launchSettings.json index 0c9fab19..1410f8bd 100644 --- a/Desktop/Properties/launchSettings.json +++ b/Desktop/Properties/launchSettings.json @@ -1,11 +1,11 @@ { - "profiles": { - "Desktop": { - "commandName": "Project" - }, - "WSL": { - "commandName": "WSL2", - "distributionName": "" + "profiles": { + "Desktop": { + "commandName": "Project" + }, + "WSL": { + "commandName": "WSL2", + "distributionName": "" + } } - } } \ No newline at end of file diff --git a/KeyVaultExplorer/App.axaml.cs b/KeyVaultExplorer/App.axaml.cs index 0c6a704c..f7908dd5 100644 --- a/KeyVaultExplorer/App.axaml.cs +++ b/KeyVaultExplorer/App.axaml.cs @@ -1,9 +1,7 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Input.Platform; using Avalonia.Markup.Xaml; -using Avalonia.Platform.Storage; using Avalonia.Threading; using Avalonia.VisualTree; using KeyVaultExplorer.Database; @@ -12,9 +10,7 @@ using KeyVaultExplorer.ViewModels; using KeyVaultExplorer.Views; using Microsoft.Extensions.DependencyInjection; -using System.ComponentModel; using System.IO; -using System.Threading.Tasks; namespace KeyVaultExplorer; @@ -25,7 +21,6 @@ public App() DataContext = new AppViewModel(); } - public static void CreateDesktopResources() { Directory.CreateDirectory(Constants.LocalAppDataFolder); @@ -35,8 +30,8 @@ public static void CreateDesktopResources() if (!dbPassExists) DatabaseEncryptedPasswordManager.SetSecret($"keyvaultexplorer_{System.Guid.NewGuid().ToString()[..6]}"); - - Dispatcher.UIThread.Post(async () => { + Dispatcher.UIThread.Post(async () => + { await KvExplorerDb.OpenSqlConnection(); if (!dbExists) diff --git a/KeyVaultExplorer/Database/KvExplorerDb.cs b/KeyVaultExplorer/Database/KvExplorerDb.cs index 22ac428e..dc11dcc8 100644 --- a/KeyVaultExplorer/Database/KvExplorerDb.cs +++ b/KeyVaultExplorer/Database/KvExplorerDb.cs @@ -1,15 +1,13 @@ using KeyVaultExplorer.Models; +using KeyVaultExplorer.Services; using Microsoft.Data.Sqlite; using System; using System.Collections.Generic; +using System.Data.Common; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; -using System.Security.Cryptography; -using Avalonia.Animation; -using System.Data.Common; -using KeyVaultExplorer.Services; namespace KeyVaultExplorer.Database; @@ -24,7 +22,7 @@ public KvExplorerDb() public static async Task OpenSqlConnection() { string DataSource = Path.Combine(Constants.DatabaseFilePath); - var pass = await DatabaseEncryptedPasswordManager.GetSecret(); + var pass = await DatabaseEncryptedPasswordManager.GetSecret(); var connection = new SqliteConnection($"Filename={DataSource}; Password={pass}"); connection.Open(); _connection = connection; diff --git a/KeyVaultExplorer/Exceptions/KvExceptions.cs b/KeyVaultExplorer/Exceptions/KvExceptions.cs index c795a5b1..86ea4185 100644 --- a/KeyVaultExplorer/Exceptions/KvExceptions.cs +++ b/KeyVaultExplorer/Exceptions/KvExceptions.cs @@ -53,7 +53,6 @@ public KeyVaultInsufficientPrivilegesException(string message, Exception inner) } } - public class SubscriptionInsufficientPrivileges : Exception { public SubscriptionInsufficientPrivileges() diff --git a/KeyVaultExplorer/KeyVaultExplorer.csproj b/KeyVaultExplorer/KeyVaultExplorer.csproj index 1b39b50d..4f4bdf4b 100644 --- a/KeyVaultExplorer/KeyVaultExplorer.csproj +++ b/KeyVaultExplorer/KeyVaultExplorer.csproj @@ -60,7 +60,7 @@ - + diff --git a/KeyVaultExplorer/Models/AppSettings.cs b/KeyVaultExplorer/Models/AppSettings.cs index b480cf2b..87345dc3 100644 --- a/KeyVaultExplorer/Models/AppSettings.cs +++ b/KeyVaultExplorer/Models/AppSettings.cs @@ -12,6 +12,6 @@ public class AppSettings [AllowedValues("Inline", "Overlay")] public string SplitViewDisplayMode { get; set; } = "Inline"; - public string PanePlacement { get; set; } = "Left"; + public string PanePlacement { get; set; } = "Left"; } \ No newline at end of file diff --git a/KeyVaultExplorer/Models/Constants.cs b/KeyVaultExplorer/Models/Constants.cs index 133bcae8..7486c5eb 100644 --- a/KeyVaultExplorer/Models/Constants.cs +++ b/KeyVaultExplorer/Models/Constants.cs @@ -1,7 +1,6 @@ using Microsoft.Identity.Client.Extensions.Msal; using System; using System.Collections.Generic; -using System.IO; namespace KeyVaultExplorer.Models; @@ -9,6 +8,7 @@ public static class Constants { // database password file name public const string EncryptedSecretFileName = "keyvaultexplorerforazure_database_password.txt"; + public const string KeychainSecretName = "keyvaultexplorerforazure_database_password"; public const string KeychainServiceName = "keyvaultexplorerforazure"; public const string ProtectedKeyFileName = "keyvaultexplorerforazure_database_key.bin"; @@ -31,7 +31,7 @@ public static class Constants public static readonly string LocalAppDataFolder = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\KeyVaultExplorerForAzure"; - public static readonly string DatabaseFilePath = LocalAppDataFolder+ "\\KeyVaultExplorerForAzure.db"; + public static readonly string DatabaseFilePath = LocalAppDataFolder + "\\KeyVaultExplorerForAzure.db"; public const string KeyChainServiceName = "keyvaultexplorerforazure_msal_service"; public const string KeyChainAccountName = "keyvaultexplorerforazure_msal_account"; diff --git a/KeyVaultExplorer/Models/KeyVaultValuesAmalgamation.cs b/KeyVaultExplorer/Models/KeyVaultValuesAmalgamation.cs index e487d4a5..a0f05f65 100644 --- a/KeyVaultExplorer/Models/KeyVaultValuesAmalgamation.cs +++ b/KeyVaultExplorer/Models/KeyVaultValuesAmalgamation.cs @@ -44,7 +44,6 @@ public class KeyVaultContentsAmalgamation public string TagValuesString => string.Join(", ", Tags?.Values ?? []); - private string? GetRelativeDateString(DateTimeOffset dateTimeOffset, bool isPast = false) { DateTimeOffset now = DateTimeOffset.Now; @@ -79,7 +78,6 @@ public class KeyVaultContentsAmalgamation (_, _) => $"{(isPast ? "" : "in ")}{years} {(years == 1 ? "year" : "years")}{(isPast ? " ago" : "")}" }; } - } public enum KeyVaultItemType diff --git a/KeyVaultExplorer/Models/SubscriptionDataItems.cs b/KeyVaultExplorer/Models/SubscriptionDataItems.cs index cc68bcc0..95d62413 100644 --- a/KeyVaultExplorer/Models/SubscriptionDataItems.cs +++ b/KeyVaultExplorer/Models/SubscriptionDataItems.cs @@ -15,7 +15,6 @@ public partial class SubscriptionDataItem : ObservableObject [ObservableProperty] private bool? isUpdated; - // this sets the default value to make sure we're not tracking initially loaded items, only changed. partial void OnIsPinnedChanging(bool oldValue, bool newValue) { diff --git a/KeyVaultExplorer/Services/AuthService.cs b/KeyVaultExplorer/Services/AuthService.cs index 1803e967..d17b1f8e 100644 --- a/KeyVaultExplorer/Services/AuthService.cs +++ b/KeyVaultExplorer/Services/AuthService.cs @@ -4,7 +4,6 @@ using System; using System.Diagnostics; using System.Linq; -using System.Security.Principal; using System.Threading; using System.Threading.Tasks; diff --git a/KeyVaultExplorer/Services/DatabaseEncryptedPasswordManager.cs b/KeyVaultExplorer/Services/DatabaseEncryptedPasswordManager.cs index 3618f573..63c581f8 100644 --- a/KeyVaultExplorer/Services/DatabaseEncryptedPasswordManager.cs +++ b/KeyVaultExplorer/Services/DatabaseEncryptedPasswordManager.cs @@ -1,13 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using DeviceId; +using KeyVaultExplorer.Models; +using System; +using System.IO; using System.Security.Cryptography; -using System.Security.Principal; using System.Text; using System.Threading.Tasks; -using System.IO; -using DeviceId; -using KeyVaultExplorer.Models; namespace KeyVaultExplorer.Services; @@ -106,6 +103,7 @@ private static byte[] GetMachineEntropy() string deviceId = new DeviceIdBuilder().AddMachineName().AddOsVersion().AddFileToken(Path.Combine(Constants.LocalAppDataFolder, Constants.DeviceFileTokenName)).ToString(); return deviceId.ToByteArray(); } + private static byte[] GetProtectedKey() { if (OperatingSystem.IsWindows()) diff --git a/KeyVaultExplorer/Services/MacOSKeyChainService.cs b/KeyVaultExplorer/Services/MacOSKeyChainService.cs index aa0420b1..dffcff44 100644 --- a/KeyVaultExplorer/Services/MacOSKeyChainService.cs +++ b/KeyVaultExplorer/Services/MacOSKeyChainService.cs @@ -108,11 +108,11 @@ public static string GetPassword(string serviceName, string accountName) } } - public static async Task GetPasswordAsync(string serviceName, string accountName) { return await Task.Run(() => GetPassword(serviceName, accountName)); } + public static async void SetPasswordAsync(string serviceName, string accountName, string password) { await Task.Run(() => SaveToKeychain(serviceName, accountName, password)); diff --git a/KeyVaultExplorer/Services/ServiceCollectionExtension.cs b/KeyVaultExplorer/Services/ServiceCollectionExtension.cs index 42735d66..4a82da53 100644 --- a/KeyVaultExplorer/Services/ServiceCollectionExtension.cs +++ b/KeyVaultExplorer/Services/ServiceCollectionExtension.cs @@ -6,8 +6,10 @@ namespace KeyVaultExplorer.Services; -public static class ServiceCollectionExtensions { - public static void AddCommonServices(this IServiceCollection collection) { +public static class ServiceCollectionExtensions +{ + public static void AddCommonServices(this IServiceCollection collection) + { collection.AddMemoryCache(); collection.AddSingleton(); collection.AddSingleton(); diff --git a/KeyVaultExplorer/Services/StringExtensions.cs b/KeyVaultExplorer/Services/StringExtensions.cs index 9dd590ab..75ca18b9 100644 --- a/KeyVaultExplorer/Services/StringExtensions.cs +++ b/KeyVaultExplorer/Services/StringExtensions.cs @@ -1,5 +1,5 @@ -using System.Runtime.InteropServices; -using System; +using System; +using System.Runtime.InteropServices; namespace KeyVaultExplorer; @@ -8,4 +8,4 @@ public static class StringExtensions public static byte[] ToByteArray(this string s) => s.ToByteSpan().ToArray(); // heap allocation, use only when you cannot operate on spans public static ReadOnlySpan ToByteSpan(this string s) => MemoryMarshal.Cast(s); -} +} \ No newline at end of file diff --git a/KeyVaultExplorer/Services/VaultService.cs b/KeyVaultExplorer/Services/VaultService.cs index 2956d444..ab10be33 100644 --- a/KeyVaultExplorer/Services/VaultService.cs +++ b/KeyVaultExplorer/Services/VaultService.cs @@ -12,7 +12,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; @@ -22,6 +21,7 @@ namespace KeyVaultExplorer.Services; public partial class VaultService { #pragma warning disable IDE0290 // Use primary constructor + public VaultService(AuthService authService, IMemoryCache memoryCache, KvExplorerDb dbContext) #pragma warning restore IDE0290 // Use primary constructor { diff --git a/KeyVaultExplorer/Validations/SecretNameValidationAttribute.cs b/KeyVaultExplorer/Validations/SecretNameValidationAttribute.cs index 512fa9c9..f29f7f61 100644 --- a/KeyVaultExplorer/Validations/SecretNameValidationAttribute.cs +++ b/KeyVaultExplorer/Validations/SecretNameValidationAttribute.cs @@ -20,6 +20,4 @@ public static ValidationResult ValidateName(string value, ValidationContext cont return new ValidationResult("Invalid secret name format."); } - - } \ No newline at end of file diff --git a/KeyVaultExplorer/ViewModels/AppViewModel.cs b/KeyVaultExplorer/ViewModels/AppViewModel.cs index cc77ab57..debcf2ff 100644 --- a/KeyVaultExplorer/ViewModels/AppViewModel.cs +++ b/KeyVaultExplorer/ViewModels/AppViewModel.cs @@ -1,30 +1,23 @@ using Avalonia.Controls; -using Avalonia.Controls.ApplicationLifetimes; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using KeyVaultExplorer.Views; -using KeyVaultExplorer.Services; -using System.Threading.Tasks; -using System.Threading; -using System.Linq; namespace KeyVaultExplorer.ViewModels; public partial class AppViewModel : ViewModelBase { - // private readonly AuthService _authService; [ObservableProperty] private string email; - [ObservableProperty] private bool isAuthenticated = false; + public AppViewModel() { // _authService = Defaults.Locator.GetRequiredService(); - } [RelayCommand] @@ -43,6 +36,4 @@ public void About() var top = Avalonia.Application.Current.GetTopLevel() as MainWindow; aboutWindow.ShowDialog(top); } - - } \ No newline at end of file diff --git a/KeyVaultExplorer/ViewModels/CreateNewSecretVersionViewModel.cs b/KeyVaultExplorer/ViewModels/CreateNewSecretVersionViewModel.cs index cdd1e027..e6bdd7a1 100644 --- a/KeyVaultExplorer/ViewModels/CreateNewSecretVersionViewModel.cs +++ b/KeyVaultExplorer/ViewModels/CreateNewSecretVersionViewModel.cs @@ -1,22 +1,12 @@ -using Avalonia.Controls; -using Avalonia.Controls.ApplicationLifetimes; +using Azure.Security.KeyVault.Secrets; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; -using KeyVaultExplorer.Views; using KeyVaultExplorer.Services; -using System.Threading.Tasks; -using System.Threading; -using System.Linq; -using Azure.Security.KeyVault.Secrets; -using System; -using System.Collections.Generic; -using KeyVaultExplorer.Models; -using System.Collections.ObjectModel; -using Avalonia.Threading; -using System.Diagnostics; -using Azure.ResourceManager.KeyVault; using KeyVaultExplorer.Validations; +using System; using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; namespace KeyVaultExplorer.ViewModels; @@ -120,13 +110,11 @@ private async Task NewVersion() KeyVaultSecretModel = properties; } - partial void OnKeyVaultSecretModelChanged(SecretProperties value) - { - SecretName = value.Name; - } + partial void OnKeyVaultSecretModelChanging(SecretProperties value) { + SecretName = value.Name; HasActivationDateChecked = value.NotBefore.HasValue; HasExpirationDateChecked = value.ExpiresOn.HasValue; ExpiresOnTimespan = value is not null && value.ExpiresOn.HasValue ? value?.ExpiresOn.Value.LocalDateTime.TimeOfDay : null; @@ -140,14 +128,15 @@ partial void OnHasActivationDateCheckedChanged(bool oldValue, bool newValue) KeyVaultSecretModel.NotBefore = null; } } + partial void OnHasExpirationDateCheckedChanged(bool oldValue, bool newValue) { if (newValue is false) { KeyVaultSecretModel.ExpiresOn = null; } - } + //public async Task> GetAvailableSubscriptions() //{ // var subscriptions = new List(); diff --git a/KeyVaultExplorer/ViewModels/KeyVaultTreeListViewModel.cs b/KeyVaultExplorer/ViewModels/KeyVaultTreeListViewModel.cs index 113ea49b..ca31504c 100644 --- a/KeyVaultExplorer/ViewModels/KeyVaultTreeListViewModel.cs +++ b/KeyVaultExplorer/ViewModels/KeyVaultTreeListViewModel.cs @@ -1,6 +1,4 @@ -using Avalonia.Controls; -using Avalonia.Interactivity; -using Avalonia.Threading; +using Avalonia.Threading; using Azure.Core; using Azure.ResourceManager; using Azure.ResourceManager.KeyVault; @@ -298,7 +296,7 @@ await DelaySetIsBusy(async () => // } //} - partial void OnSearchQueryChanged(string value) + partial void OnSearchQueryChanged(string value) { string query = value.Trim(); if (!string.IsNullOrWhiteSpace(query)) diff --git a/KeyVaultExplorer/ViewModels/MainViewModel.cs b/KeyVaultExplorer/ViewModels/MainViewModel.cs index 35b20639..2a0a2952 100644 --- a/KeyVaultExplorer/ViewModels/MainViewModel.cs +++ b/KeyVaultExplorer/ViewModels/MainViewModel.cs @@ -25,7 +25,6 @@ public partial class MainViewModel : ViewModelBase public NavigationFactory NavigationFactory { get; } - partial void OnIsAuthenticatedChanged(bool value) { AuthenticatedUserClaims = _authService.AuthenticatedUserClaims; diff --git a/KeyVaultExplorer/ViewModels/NotificationViewModel.cs b/KeyVaultExplorer/ViewModels/NotificationViewModel.cs index 49683e2b..f7af1b01 100644 --- a/KeyVaultExplorer/ViewModels/NotificationViewModel.cs +++ b/KeyVaultExplorer/ViewModels/NotificationViewModel.cs @@ -1,6 +1,4 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.Notifications; using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Windowing; @@ -27,9 +25,8 @@ public async void ShowPopup(Notification notification) FooterVisibility = TaskDialogFooterVisibility.Always, IsFooterExpanded = false, Buttons = { TaskDialogButton.CloseButton }, - }; - //Avalonia.Application.Current.GetTopLevel() as AppWindow; + //Avalonia.Application.Current.GetTopLevel() as AppWindow; var lifetime = App.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime; td.XamlRoot = lifetime.Windows.Last() as AppWindow; await td.ShowAsync(true); diff --git a/KeyVaultExplorer/ViewModels/PropertiesPageViewModel.cs b/KeyVaultExplorer/ViewModels/PropertiesPageViewModel.cs index dbd45d9b..e2a5e4e9 100644 --- a/KeyVaultExplorer/ViewModels/PropertiesPageViewModel.cs +++ b/KeyVaultExplorer/ViewModels/PropertiesPageViewModel.cs @@ -1,5 +1,4 @@ -using Avalonia.Controls; -using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Data; using Avalonia.Input; using Avalonia.Input.Platform; @@ -219,7 +218,6 @@ private async Task EditVersion() { await viewModel.EditDetailsCommand.ExecuteAsync(null); _notificationViewModel.ShowPopup(new Avalonia.Controls.Notifications.Notification("Success", "The properties have been updated.")); - } catch (KeyVaultInsufficientPrivilegesException ex) { @@ -344,9 +342,7 @@ private async Task NewVersion() dialog.Content = new CreateNewSecretVersion() { DataContext = vm }; var result = await dialog.ShowAsync(true); - } - } catch (KeyVaultItemNotFoundException ex) { diff --git a/KeyVaultExplorer/ViewModels/SettingsPageViewModel.cs b/KeyVaultExplorer/ViewModels/SettingsPageViewModel.cs index a469e7c5..3a71f67a 100644 --- a/KeyVaultExplorer/ViewModels/SettingsPageViewModel.cs +++ b/KeyVaultExplorer/ViewModels/SettingsPageViewModel.cs @@ -8,7 +8,6 @@ using System.Collections.ObjectModel; using System.Diagnostics; using System.IO; -using System.Linq; using System.Reflection; using System.Text.Json; using System.Threading; @@ -148,6 +147,4 @@ private void OpenIssueGithub() { Process.Start(new ProcessStartInfo("https://github.com/cricketthomas/KeyVaultExplorer/issues/new") { UseShellExecute = true, Verb = "open" }); } - - } \ No newline at end of file diff --git a/KeyVaultExplorer/ViewModels/SubscriptionsPageViewModel.cs b/KeyVaultExplorer/ViewModels/SubscriptionsPageViewModel.cs index e438a8b3..c9779f47 100644 --- a/KeyVaultExplorer/ViewModels/SubscriptionsPageViewModel.cs +++ b/KeyVaultExplorer/ViewModels/SubscriptionsPageViewModel.cs @@ -24,7 +24,6 @@ public partial class SubscriptionsPageViewModel : ViewModelBase [ObservableProperty] private ObservableCollection subscriptions; - private readonly KvExplorerDb _dbContext; private readonly IMemoryCache _memoryCache; private readonly VaultService _vaultService; @@ -64,8 +63,6 @@ public async Task GetSubscriptions() IsBusy = false; } - - [RelayCommand] public async Task LoadMore() { @@ -90,9 +87,6 @@ public async Task LoadMore() IsBusy = false; } - - - [RelayCommand] public void SelectAllSubscriptions() { diff --git a/KeyVaultExplorer/ViewModels/VaultPageViewModel.cs b/KeyVaultExplorer/ViewModels/VaultPageViewModel.cs index 7dd4fcd3..73d94ea5 100644 --- a/KeyVaultExplorer/ViewModels/VaultPageViewModel.cs +++ b/KeyVaultExplorer/ViewModels/VaultPageViewModel.cs @@ -1,23 +1,18 @@ using Avalonia.Controls; -using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.Notifications; using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Input.Platform; -using Avalonia.Layout; using Avalonia.Media.Imaging; using Avalonia.Platform; using Azure.Security.KeyVault.Keys; -using Azure.Security.KeyVault.Secrets; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using FluentAvalonia.UI.Windowing; using KeyVaultExplorer.Exceptions; using KeyVaultExplorer.Models; using KeyVaultExplorer.Services; -using Microsoft.VisualBasic; using System; -using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; @@ -187,7 +182,7 @@ public async Task FilterAndLoadVaultValueType(KeyVaultItemType item) { var contents = item == KeyVaultItemType.All ? _vaultContents : _vaultContents.Where(x => item == x.Type); - VaultContents = KeyVaultFilterHelper.FilterByQuery( contents, SearchQuery, item => item.Name, item => item.Tags); + VaultContents = KeyVaultFilterHelper.FilterByQuery(contents, SearchQuery, item => item.Name, item => item.Tags); await DelaySetIsBusy(false); } @@ -379,7 +374,6 @@ partial void OnSearchQueryChanged(string value) list = list.Where(k => k.Type == item); VaultContents = KeyVaultFilterHelper.FilterByQuery(list, value, item => item.Name, item => item.Tags); - } [RelayCommand] diff --git a/KeyVaultExplorer/ViewModels/ViewModelBase.cs b/KeyVaultExplorer/ViewModels/ViewModelBase.cs index 12447288..5bc1a1ca 100644 --- a/KeyVaultExplorer/ViewModels/ViewModelBase.cs +++ b/KeyVaultExplorer/ViewModels/ViewModelBase.cs @@ -4,5 +4,4 @@ namespace KeyVaultExplorer.ViewModels; public class ViewModelBase : ObservableValidator { - } \ No newline at end of file diff --git a/KeyVaultExplorer/Views/CustomControls/KeyVaultTreeList.axaml.cs b/KeyVaultExplorer/Views/CustomControls/KeyVaultTreeList.axaml.cs index 979cd367..7b4b122c 100644 --- a/KeyVaultExplorer/Views/CustomControls/KeyVaultTreeList.axaml.cs +++ b/KeyVaultExplorer/Views/CustomControls/KeyVaultTreeList.axaml.cs @@ -1,5 +1,4 @@ -using Avalonia; -using Avalonia.Controls; +using Avalonia.Controls; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Threading; @@ -87,12 +86,8 @@ private void RefreshKeyVaultList(object sender, RoutedEventArgs e) await (DataContext as KeyVaultTreeListViewModel)!.GetAvailableKeyVaultsCommand.ExecuteAsync(true).ContinueWith((t) => { ((Control)sender)!.RaiseEvent(new RoutedEventArgs(MainView.SignInRoutedEvent)); - }); }, DispatcherPriority.Input); - - - } private void OnDoubleClicked(object sender, TappedEventArgs args) diff --git a/KeyVaultExplorer/Views/CustomControls/ToolBar.axaml.cs b/KeyVaultExplorer/Views/CustomControls/ToolBar.axaml.cs index df5dd9fd..0f7235b9 100644 --- a/KeyVaultExplorer/Views/CustomControls/ToolBar.axaml.cs +++ b/KeyVaultExplorer/Views/CustomControls/ToolBar.axaml.cs @@ -1,15 +1,7 @@ using Avalonia.Controls; -using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Interactivity; -using Avalonia.Threading; -using Azure.Security.KeyVault.Secrets; -using FluentAvalonia.UI.Controls; -using FluentAvalonia.UI.Windowing; -using KeyVaultExplorer.Exceptions; using KeyVaultExplorer.ViewModels; using KeyVaultExplorer.Views.Pages; -using System.Linq; -using System.Threading.Tasks; namespace KeyVaultExplorer.Views.CustomControls; diff --git a/KeyVaultExplorer/Views/MainView.axaml.cs b/KeyVaultExplorer/Views/MainView.axaml.cs index 315855b4..8c52e00f 100644 --- a/KeyVaultExplorer/Views/MainView.axaml.cs +++ b/KeyVaultExplorer/Views/MainView.axaml.cs @@ -9,13 +9,10 @@ using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Media.Animation; using FluentAvalonia.UI.Navigation; -using KeyVaultExplorer.Models; using KeyVaultExplorer.Services; using KeyVaultExplorer.ViewModels; using KeyVaultExplorer.Views.Pages; using System; -using System.Diagnostics; -using System.Linq; #nullable disable @@ -44,7 +41,6 @@ public MainView() mainViewModel = Defaults.Locator.GetRequiredService(); authService = Defaults.Locator.GetRequiredService(); - Dispatcher.UIThread.Post(async () => { await mainViewModel.RefreshTokenAndGetAccountInformation().ContinueWith(async (t) => @@ -61,6 +57,7 @@ await mainViewModel.RefreshTokenAndGetAccountInformation().ContinueWith(async (t } public static MainView Instance { get; private set; } + private FrameNavigationOptions NavOptions => new FrameNavigationOptions { TransitionInfoOverride = new SlideNavigationTransitionInfo(), @@ -212,6 +209,7 @@ private void OnSignOutRoutedEvent(object sender, RoutedEventArgs e) mainViewModel.IsAuthenticated = false; mainViewModel.AuthenticatedUserClaims = null; } + private void TabViewPage_KeyUpFocusSearchBox(object sender, KeyEventArgs e) { if (e.Key == Avalonia.Input.Key.F && (e.KeyModifiers == KeyModifiers.Control || e.Key == Avalonia.Input.Key.LWin || e.Key == Avalonia.Input.Key.RWin)) diff --git a/KeyVaultExplorer/Views/Pages/AboutPageWindow.axaml.cs b/KeyVaultExplorer/Views/Pages/AboutPageWindow.axaml.cs index b5214deb..fadc175e 100644 --- a/KeyVaultExplorer/Views/Pages/AboutPageWindow.axaml.cs +++ b/KeyVaultExplorer/Views/Pages/AboutPageWindow.axaml.cs @@ -1,5 +1,4 @@ using Avalonia.Controls; -using FluentAvalonia.UI.Windowing; namespace KeyVaultExplorer; diff --git a/KeyVaultExplorer/Views/Pages/PropertiesDialogs/CreateNewSecretVersion.axaml.cs b/KeyVaultExplorer/Views/Pages/PropertiesDialogs/CreateNewSecretVersion.axaml.cs index fe365ee0..9a219054 100644 --- a/KeyVaultExplorer/Views/Pages/PropertiesDialogs/CreateNewSecretVersion.axaml.cs +++ b/KeyVaultExplorer/Views/Pages/PropertiesDialogs/CreateNewSecretVersion.axaml.cs @@ -1,16 +1,5 @@ -using Avalonia; using Avalonia.Controls; -using Avalonia.Input; -using Avalonia.Interactivity; -using Avalonia.Threading; -using Azure.Security.KeyVault.Secrets; -using CommunityToolkit.Mvvm.Input; -using FluentAvalonia.UI.Controls; -using KeyVaultExplorer.Exceptions; -using KeyVaultExplorer.Models; using KeyVaultExplorer.ViewModels; -using System; -using System.Threading.Tasks; namespace KeyVaultExplorer; @@ -23,8 +12,6 @@ public CreateNewSecretVersion() DataContext = vm; } - - //private void Subscription_SelectionChanged(object? sender, Avalonia.Controls.SelectionChangedEventArgs e) //{ // var item = (sender as AutoCompleteBox)!.SelectedItem as SubscriptionDataItem; diff --git a/KeyVaultExplorer/Views/Pages/SettingsPage.axaml.cs b/KeyVaultExplorer/Views/Pages/SettingsPage.axaml.cs index d60c74c6..5b2894cb 100644 --- a/KeyVaultExplorer/Views/Pages/SettingsPage.axaml.cs +++ b/KeyVaultExplorer/Views/Pages/SettingsPage.axaml.cs @@ -4,7 +4,6 @@ using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Navigation; using KeyVaultExplorer.ViewModels; -using KeyVaultExplorer.Views.CustomControls; namespace KeyVaultExplorer.Views.Pages; @@ -43,11 +42,10 @@ private void OnNavigatedTo(object sender, NavigationEventArgs e) if (e.NavigationMode != NavigationMode.Back && IsInitialLoad) { IsInitialLoad = false; - Dispatcher.UIThread.InvokeAsync(async() => + Dispatcher.UIThread.InvokeAsync(async () => { await (DataContext as SettingsPageViewModel)!.SignInOrRefreshTokenCommand.ExecuteAsync(null); //((Control)sender)!.RaiseEvent(new RoutedEventArgs(MainView.SignInRoutedEvent)); - }, DispatcherPriority.Background); } } diff --git a/KeyVaultExplorer/Views/Pages/VaultPage.axaml.cs b/KeyVaultExplorer/Views/Pages/VaultPage.axaml.cs index 5c35f865..b73b531d 100644 --- a/KeyVaultExplorer/Views/Pages/VaultPage.axaml.cs +++ b/KeyVaultExplorer/Views/Pages/VaultPage.axaml.cs @@ -13,7 +13,6 @@ using KeyVaultExplorer.Models; using KeyVaultExplorer.ViewModels; using System; -using System.Diagnostics; using System.Linq; using System.Threading.Tasks; @@ -170,7 +169,7 @@ private void CreateSecret_Clicked(object? sender, Avalonia.Interactivity.RoutedE private async Task CreateNewSecret() { var lifetime = App.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime; - + var vm = new CreateNewSecretVersionViewModel() { KeyVaultSecretModel = new SecretProperties("new_secret") { Enabled = true }, @@ -191,7 +190,7 @@ private async Task CreateNewSecret() FallbackValue = false, Source = vm, }); - + var dialog = new TaskDialog() { Title = "Create New Secret", @@ -219,8 +218,7 @@ private async Task CreateNewSecret() } }; - ; + ; var result = await dialog.ShowAsync(); } - } \ No newline at end of file diff --git a/mpdev/msix.json b/mpdev/msix.json index acae2694..791794d0 100644 --- a/mpdev/msix.json +++ b/mpdev/msix.json @@ -6,7 +6,7 @@ "outputDirectory": "Key Vault Explorer\\output", "packageName": "Key Vault Explorer", "publisher": "Arthur Thomas IV", - "version": "1.0.263.0", + "version": "1.0.293.0", "platform": "x86", "installDir": "%ProgramFiles(x86)%\\Key Vault Explorer", "icon": "AppIcon.ico", From eb54610bcc61c187cda23d0489010571e3b57c2d Mon Sep 17 00:00:00 2001 From: Arthur Thomas Date: Wed, 7 Aug 2024 07:14:21 -0400 Subject: [PATCH 2/6] fix issue where vault may not be sorting after refresh --- KeyVaultExplorer/ViewModels/VaultPageViewModel.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/KeyVaultExplorer/ViewModels/VaultPageViewModel.cs b/KeyVaultExplorer/ViewModels/VaultPageViewModel.cs index 73d94ea5..73601af8 100644 --- a/KeyVaultExplorer/ViewModels/VaultPageViewModel.cs +++ b/KeyVaultExplorer/ViewModels/VaultPageViewModel.cs @@ -368,12 +368,7 @@ partial void OnSearchQueryChanged(string value) return; } - var list = _vaultContents; - - if (item != KeyVaultItemType.All) - list = list.Where(k => k.Type == item); - - VaultContents = KeyVaultFilterHelper.FilterByQuery(list, value, item => item.Name, item => item.Tags); + VaultContents = KeyVaultFilterHelper.FilterByQuery(item != KeyVaultItemType.All ? _vaultContents.Where(k => k.Type == item) : _vaultContents, value ?? SearchQuery, item => item.Name, item => item.Tags); } [RelayCommand] From 1e7a02b4e25e8d85eede61641d6694706cf3f136 Mon Sep 17 00:00:00 2001 From: Arthur Thomas Date: Wed, 7 Aug 2024 21:22:07 -0400 Subject: [PATCH 3/6] chore: performance improvements offloading to background thread --- .../ViewModels/KeyVaultTreeListViewModel.cs | 9 +++++---- .../ViewModels/PropertiesPageViewModel.cs | 7 +++++-- .../ViewModels/TabViewPageViewModel.cs | 16 ++++++++++----- .../ViewModels/VaultPageViewModel.cs | 20 ++++++++++++++----- .../Views/Pages/TabViewPage.axaml | 2 +- 5 files changed, 37 insertions(+), 17 deletions(-) diff --git a/KeyVaultExplorer/ViewModels/KeyVaultTreeListViewModel.cs b/KeyVaultExplorer/ViewModels/KeyVaultTreeListViewModel.cs index ca31504c..c4fa20df 100644 --- a/KeyVaultExplorer/ViewModels/KeyVaultTreeListViewModel.cs +++ b/KeyVaultExplorer/ViewModels/KeyVaultTreeListViewModel.cs @@ -77,7 +77,7 @@ public async Task GetAvailableKeyVaults(bool isRefresh = false) TreeViewList.Clear(); } - await Dispatcher.UIThread.InvokeAsync(async () => + await Task.Run(async () => { await DelaySetIsBusy(async () => { @@ -130,8 +130,9 @@ await DelaySetIsBusy(async () => _notificationViewModel.ShowPopup(new Avalonia.Controls.Notifications.Notification { Message = ex.Message, Title = "Error" }); } }); - }, DispatcherPriority.Background); - _treeViewList = TreeViewList; + }); + + Dispatcher.UIThread.Post(() => _treeViewList = TreeViewList , DispatcherPriority.Background); } // this will set isBusy to true if the fetching takes longer than 1500 ms. @@ -296,7 +297,7 @@ await DelaySetIsBusy(async () => // } //} - partial void OnSearchQueryChanged(string value) + partial void OnSearchQueryChanged(string value) { string query = value.Trim(); if (!string.IsNullOrWhiteSpace(query)) diff --git a/KeyVaultExplorer/ViewModels/PropertiesPageViewModel.cs b/KeyVaultExplorer/ViewModels/PropertiesPageViewModel.cs index e2a5e4e9..1c72ab7c 100644 --- a/KeyVaultExplorer/ViewModels/PropertiesPageViewModel.cs +++ b/KeyVaultExplorer/ViewModels/PropertiesPageViewModel.cs @@ -388,9 +388,12 @@ private async Task ShouldShowValue(bool val) { if (IsSecret && val && IsEnabled) { - await Dispatcher.UIThread.InvokeAsync(async () => + var s = await Task.Run(async () => + { + return await _vaultService.GetSecret(kvUri: OpenedItem.SecretProperties.VaultUri, secretName: OpenedItem.SecretProperties.Name).ConfigureAwait(false); + }); + await Dispatcher.UIThread.InvokeAsync(() => { - var s = await _vaultService.GetSecret(kvUri: OpenedItem.SecretProperties.VaultUri, secretName: OpenedItem.SecretProperties.Name).ConfigureAwait(false); SecretPlainText = s.Value; }); } diff --git a/KeyVaultExplorer/ViewModels/TabViewPageViewModel.cs b/KeyVaultExplorer/ViewModels/TabViewPageViewModel.cs index 2b7d1d1d..5b6a3f6c 100644 --- a/KeyVaultExplorer/ViewModels/TabViewPageViewModel.cs +++ b/KeyVaultExplorer/ViewModels/TabViewPageViewModel.cs @@ -6,6 +6,7 @@ using FluentAvalonia.UI.Controls; using KeyVaultExplorer.Views.Pages; using System.Collections.ObjectModel; +using System.Threading.Tasks; namespace KeyVaultExplorer.ViewModels; @@ -17,14 +18,19 @@ public TabViewPageViewModel() { Documents = new ObservableCollection(); #if DEBUG - Documents.Add(AddDocument(1)); + Documents.Add(AddDocument(1)); #endif - Dispatcher.UIThread.Post(async () => + _settingsPageViewModel = Defaults.Locator.GetRequiredService(); + + var settings = Task.Run(async () => + { + return await _settingsPageViewModel.GetAppSettings(); + }); + + Dispatcher.UIThread.InvokeAsync(async () => { - _settingsPageViewModel = Defaults.Locator.GetRequiredService(); - var settings = await _settingsPageViewModel.GetAppSettings(); - SplitViewDisplayMode = settings.SplitViewDisplayMode == "Inline" ? SplitViewDisplayMode.Inline : SplitViewDisplayMode.Overlay; + SplitViewDisplayMode = (await settings).SplitViewDisplayMode == "Inline" ? SplitViewDisplayMode.Inline : SplitViewDisplayMode.Overlay; }); } diff --git a/KeyVaultExplorer/ViewModels/VaultPageViewModel.cs b/KeyVaultExplorer/ViewModels/VaultPageViewModel.cs index 73601af8..038625eb 100644 --- a/KeyVaultExplorer/ViewModels/VaultPageViewModel.cs +++ b/KeyVaultExplorer/ViewModels/VaultPageViewModel.cs @@ -1,4 +1,5 @@ -using Avalonia.Controls; +using Avalonia; +using Avalonia.Controls; using Avalonia.Controls.Notifications; using Avalonia.Controls.Primitives; using Avalonia.Input; @@ -6,6 +7,7 @@ using Avalonia.Media.Imaging; using Avalonia.Platform; using Azure.Security.KeyVault.Keys; +using Azure.Security.KeyVault.Secrets; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using FluentAvalonia.UI.Windowing; @@ -41,8 +43,6 @@ public partial class VaultPageViewModel : ViewModelBase [ObservableProperty] private string authorizationMessage; - private Bitmap BitmapImage; - [ObservableProperty] private bool hasAuthorizationError = false; @@ -64,6 +64,8 @@ public partial class VaultPageViewModel : ViewModelBase [ObservableProperty] private Uri vaultUri; + private readonly Lazy BitmapImage; + public VaultPageViewModel() { _vaultService = Defaults.Locator.GetRequiredService(); @@ -72,7 +74,7 @@ public VaultPageViewModel() _notificationViewModel = Defaults.Locator.GetRequiredService(); _clipboardService = Defaults.Locator.GetRequiredService(); vaultContents = []; - BitmapImage = new Bitmap(AssetLoader.Open(new Uri("avares://KeyVaultExplorer/Assets/AppIcon.ico"))).CreateScaledBitmap(new Avalonia.PixelSize(24, 24), BitmapInterpolationMode.HighQuality); + BitmapImage = new Lazy(() => LoadImage("avares://KeyVaultExplorer/Assets/AppIcon.ico")); #if DEBUG for (int i = 0; i < 5; i++) @@ -113,6 +115,14 @@ public VaultPageViewModel() #endif } + public Bitmap LazyLoadedImage => BitmapImage.Value.CreateScaledBitmap(new Avalonia.PixelSize(24, 24), BitmapInterpolationMode.HighQuality); + + private static Bitmap LoadImage(string uri) + { + var asset = AssetLoader.Open(new Uri(uri)); + return new Bitmap(asset); + } + public Dictionary LoadedItemTypes { get; set; } = new() { }; private IEnumerable _vaultContents { get; set; } = []; @@ -430,7 +440,7 @@ private void ShowProperties(KeyVaultContentsAmalgamation model) var taskDialog = new AppWindow { Title = $"{model.Type} {model.Name} Properties", - Icon = BitmapImage, + Icon = LazyLoadedImage, SizeToContent = SizeToContent.Manual, WindowStartupLocation = WindowStartupLocation.CenterOwner, ShowAsDialog = false, diff --git a/KeyVaultExplorer/Views/Pages/TabViewPage.axaml b/KeyVaultExplorer/Views/Pages/TabViewPage.axaml index d4b70325..8427f2a2 100644 --- a/KeyVaultExplorer/Views/Pages/TabViewPage.axaml +++ b/KeyVaultExplorer/Views/Pages/TabViewPage.axaml @@ -34,7 +34,7 @@ VerticalAlignment="Stretch" DisplayMode="{Binding SplitViewDisplayMode}" IsPaneOpen="True" - OpenPaneLength="280" + OpenPaneLength="330" PaneBackground="{x:Null}"> Date: Thu, 8 Aug 2024 22:49:20 -0400 Subject: [PATCH 4/6] fix: bitmaps and add lazy loading fix: tree search fix: errors when logging in or out of accounts with saved preferences change window size to be larger --- KeyVaultExplorer/Database/KvExplorerDb.cs | 42 ++-- KeyVaultExplorer/Models/KeyVaultModel.cs | 1 + KeyVaultExplorer/Services/AuthService.cs | 5 + KeyVaultExplorer/Services/VaultService.cs | 9 +- KeyVaultExplorer/ViewModels/.editorconfig | 232 ++++++++++++++++++ KeyVaultExplorer/ViewModels/FilterService.cs | 49 ++++ .../ViewModels/KeyVaultTreeListViewModel.cs | 58 ++--- .../ViewModels/SubscriptionsPageViewModel.cs | 6 +- .../CustomControls/KeyVaultTreeList.axaml | 8 +- KeyVaultExplorer/Views/MainPage.axaml | 4 +- KeyVaultExplorer/Views/MainWindow.axaml | 8 +- .../Views/Pages/TabViewPage.axaml | 4 +- KeyVaultExplorer/Views/Pages/VaultPage.axaml | 2 +- .../Views/Pages/VaultPage.axaml.cs | 8 + 14 files changed, 359 insertions(+), 77 deletions(-) create mode 100644 KeyVaultExplorer/ViewModels/.editorconfig create mode 100644 KeyVaultExplorer/ViewModels/FilterService.cs diff --git a/KeyVaultExplorer/Database/KvExplorerDb.cs b/KeyVaultExplorer/Database/KvExplorerDb.cs index dc11dcc8..da8dc115 100644 --- a/KeyVaultExplorer/Database/KvExplorerDb.cs +++ b/KeyVaultExplorer/Database/KvExplorerDb.cs @@ -76,36 +76,18 @@ CREATE INDEX IF NOT EXISTS IX_QuickAccess_KeyVaultId ON QuickAccess ( await createTableCommand.ExecuteNonQueryAsync(); } - public List GetQuickAccessItems() + public async IAsyncEnumerable GetQuickAccessItemsAsyncEnumerable(string tenantId = null) { var command = _connection.CreateCommand(); - command.CommandText = "SELECT Id, Name, VaultUri, KeyVaultId, SubscriptionDisplayName, SubscriptionId, TenantId, Location FROM QuickAccess;"; + var query = new StringBuilder("SELECT Id, Name, VaultUri, KeyVaultId, SubscriptionDisplayName, SubscriptionId, TenantId, Location FROM QuickAccess"); - var reader = command.ExecuteReader(); - - var items = new List(); - while (reader.Read()) + if (!string.IsNullOrWhiteSpace(tenantId)) { - var item = new QuickAccess - { - Id = reader.GetInt32(0), - Name = reader.GetString(1), - VaultUri = reader.GetString(2), - KeyVaultId = reader.GetString(3), - SubscriptionDisplayName = reader.IsDBNull(4) ? null : reader.GetString(4), - SubscriptionId = reader.IsDBNull(5) ? null : reader.GetString(5), - TenantId = reader.GetString(6), - Location = reader.GetString(7), - }; - items.Add(item); + query.Append($" WHERE TenantId = '{tenantId}'"); } - return items; - } + query.Append(";"); + command.CommandText = query.ToString(); - public async IAsyncEnumerable GetQuickAccessItemsAsyncEnumerable() - { - var command = _connection.CreateCommand(); - command.CommandText = "SELECT Id, Name, VaultUri, KeyVaultId, SubscriptionDisplayName, SubscriptionId, TenantId, Location FROM QuickAccess;"; var reader = await command.ExecuteReaderAsync(); @@ -191,11 +173,17 @@ public async Task GetToggleSettings() return settings; } - public async Task> GetStoredSubscriptions() + public async Task> GetStoredSubscriptions(string tenantId = null) { var command = _connection.CreateCommand(); - command.CommandText = "SELECT DisplayName, SubscriptionId, TenantId FROM Subscriptions;"; - + var query = new StringBuilder("SELECT DisplayName, SubscriptionId, TenantId FROM Subscriptions"); + + if (!string.IsNullOrWhiteSpace(tenantId)) + { + query.Append($" WHERE TenantId = '{tenantId.ToUpperInvariant()}'"); + } + query.Append(";"); + command.CommandText = query.ToString(); var reader = command.ExecuteReader(); var subscriptions = new List(); diff --git a/KeyVaultExplorer/Models/KeyVaultModel.cs b/KeyVaultExplorer/Models/KeyVaultModel.cs index 8fd6547a..64165c45 100644 --- a/KeyVaultExplorer/Models/KeyVaultModel.cs +++ b/KeyVaultExplorer/Models/KeyVaultModel.cs @@ -32,5 +32,6 @@ public partial class KvResourceGroupModel : ObservableObject public ObservableCollection KeyVaultResources { get; set; } = []; public string ResourceGroupDisplayName { get; set; } = null!; + public ResourceGroupResource ResourceGroupResource { get; set; } = null!; } \ No newline at end of file diff --git a/KeyVaultExplorer/Services/AuthService.cs b/KeyVaultExplorer/Services/AuthService.cs index d17b1f8e..300f1fb2 100644 --- a/KeyVaultExplorer/Services/AuthService.cs +++ b/KeyVaultExplorer/Services/AuthService.cs @@ -21,6 +21,9 @@ public class AuthService public string TenantName { get; private set; } + public string TenantId { get; private set; } + + public IAccount Account { get; private set; } public AuthService() @@ -63,6 +66,7 @@ public async Task LoginAsync(CancellationToken cancellatio IsAuthenticated = true; TenantName = authenticationResult.Account.Username.Split("@").TakeLast(1).Single(); + TenantId = authenticationResult.TenantId; AuthenticatedUserClaims = new AuthenticatedUserClaims() { Username = authenticationResult.Account.Username, @@ -100,6 +104,7 @@ public async Task RefreshTokenAsync(CancellationToken canc authenticationResult = await authenticationClient.AcquireTokenSilent(Constants.Scopes, accounts.FirstOrDefault()).WithForceRefresh(true).ExecuteAsync(); IsAuthenticated = true; TenantName = Account.Username.Split("@").TakeLast(1).Single(); + TenantId = authenticationResult.TenantId; AuthenticatedUserClaims = new AuthenticatedUserClaims() { Username = authenticationResult.Account.Username, diff --git a/KeyVaultExplorer/Services/VaultService.cs b/KeyVaultExplorer/Services/VaultService.cs index ab10be33..687feba5 100644 --- a/KeyVaultExplorer/Services/VaultService.cs +++ b/KeyVaultExplorer/Services/VaultService.cs @@ -156,14 +156,15 @@ public async IAsyncEnumerable GetKeyVaultResourceBySubscrip var placeholder = new KeyVaultResourcePlaceholder(); var rgPlaceholder = new KvResourceGroupModel() //needed to show chevron { - KeyVaultResources = [placeholder] + KeyVaultResources = [placeholder], + //ResourceGroupDisplayName = string.Empty }; - var subscriptions = await _memoryCache.GetOrCreateAsync("subscriptions", async (f) => + var subscriptions = await _memoryCache.GetOrCreateAsync($"subscriptions_{_authService.TenantId}", async (f) => { - f.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(2); + f.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(1); - var savedSubscriptions = await _dbContext.GetStoredSubscriptions(); + var savedSubscriptions = await _dbContext.GetStoredSubscriptions(_authService.TenantId ?? null); List subscriptionCollection = []; foreach (var sub in savedSubscriptions) { diff --git a/KeyVaultExplorer/ViewModels/.editorconfig b/KeyVaultExplorer/ViewModels/.editorconfig new file mode 100644 index 00000000..875356ee --- /dev/null +++ b/KeyVaultExplorer/ViewModels/.editorconfig @@ -0,0 +1,232 @@ +# Remove the line below if you want to inherit .editorconfig settings from higher directories +root = true + +# C# files +[*.cs] + +#### Core EditorConfig Options #### + +# Indentation and spacing +indent_size = 4 +indent_style = space +tab_width = 4 + +# New line preferences +end_of_line = crlf +insert_final_newline = false + +#### .NET Coding Conventions #### + +# Organize usings +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = false +file_header_template = unset + +# this. and Me. preferences +dotnet_style_qualification_for_event = false +dotnet_style_qualification_for_field = false +dotnet_style_qualification_for_method = false +dotnet_style_qualification_for_property = false + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true +dotnet_style_predefined_type_for_member_access = true + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_operators = never_if_unnecessary +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members + +# Expression-level preferences +dotnet_style_coalesce_expression = true +dotnet_style_collection_initializer = true +dotnet_style_explicit_tuple_names = true +dotnet_style_namespace_match_folder = true +dotnet_style_null_propagation = true +dotnet_style_object_initializer = true +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true +dotnet_style_prefer_collection_expression = when_types_loosely_match +dotnet_style_prefer_compound_assignment = true +dotnet_style_prefer_conditional_expression_over_assignment = true +dotnet_style_prefer_conditional_expression_over_return = true +dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed +dotnet_style_prefer_inferred_anonymous_type_member_names = true +dotnet_style_prefer_inferred_tuple_names = true +dotnet_style_prefer_is_null_check_over_reference_equality_method = true +dotnet_style_prefer_simplified_boolean_expressions = true +dotnet_style_prefer_simplified_interpolation = true + +# Field preferences +dotnet_style_readonly_field = true + +# Parameter preferences +dotnet_code_quality_unused_parameters = all + +# Suppression preferences +dotnet_remove_unnecessary_suppression_exclusions = none + +# New line preferences +dotnet_style_allow_multiple_blank_lines_experimental = true +dotnet_style_allow_statement_immediately_after_block_experimental = true + +#### C# Coding Conventions #### + +# var preferences +csharp_style_var_elsewhere = false +csharp_style_var_for_built_in_types = false +csharp_style_var_when_type_is_apparent = false + +# Expression-bodied members +csharp_style_expression_bodied_accessors = true +csharp_style_expression_bodied_constructors = false +csharp_style_expression_bodied_indexers = true +csharp_style_expression_bodied_lambdas = true +csharp_style_expression_bodied_local_functions = false +csharp_style_expression_bodied_methods = false +csharp_style_expression_bodied_operators = false +csharp_style_expression_bodied_properties = true + +# Pattern matching preferences +csharp_style_pattern_matching_over_as_with_null_check = true +csharp_style_pattern_matching_over_is_with_cast_check = true +csharp_style_prefer_extended_property_pattern = true +csharp_style_prefer_not_pattern = true +csharp_style_prefer_pattern_matching = true +csharp_style_prefer_switch_expression = true + +# Null-checking preferences +csharp_style_conditional_delegate_call = true + +# Modifier preferences +csharp_prefer_static_anonymous_function = true +csharp_prefer_static_local_function = true +csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async +csharp_style_prefer_readonly_struct = true +csharp_style_prefer_readonly_struct_member = true + +# Code-block preferences +csharp_prefer_braces = true +csharp_prefer_simple_using_statement = true +csharp_style_namespace_declarations = block_scoped +csharp_style_prefer_method_group_conversion = true +csharp_style_prefer_primary_constructors = true +csharp_style_prefer_top_level_statements = true + +# Expression-level preferences +csharp_prefer_simple_default_expression = true +csharp_style_deconstructed_variable_declaration = true +csharp_style_implicit_object_creation_when_type_is_apparent = true +csharp_style_inlined_variable_declaration = true +csharp_style_prefer_index_operator = true +csharp_style_prefer_local_over_anonymous_function = true +csharp_style_prefer_null_check_over_type_check = true +csharp_style_prefer_range_operator = true +csharp_style_prefer_tuple_swap = true +csharp_style_prefer_utf8_string_literals = true +csharp_style_throw_expression = true +csharp_style_unused_value_assignment_preference = discard_variable +csharp_style_unused_value_expression_statement_preference = discard_variable + +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace + +# New line preferences +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true +csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true +csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true +csharp_style_allow_embedded_statements_on_same_line_experimental = true + +#### C# Formatting Rules #### + +# New line preferences +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = all +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Wrapping preferences +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case diff --git a/KeyVaultExplorer/ViewModels/FilterService.cs b/KeyVaultExplorer/ViewModels/FilterService.cs new file mode 100644 index 00000000..fdc1f289 --- /dev/null +++ b/KeyVaultExplorer/ViewModels/FilterService.cs @@ -0,0 +1,49 @@ +using KeyVaultExplorer.Models; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +namespace KeyVaultExplorer.ViewModels; + +public static class FilterService +{ + public static IList Filter(IList allSubscriptions, string query) + { + if (string.IsNullOrWhiteSpace(query)) + { + return allSubscriptions; + } + + var filteredSubscriptions = new List(); + + foreach (var subscription in allSubscriptions) + { + var filteredResourceGroups = subscription.ResourceGroups + .Where(resourceGroup => + resourceGroup.ResourceGroupDisplayName != null + && resourceGroup.ResourceGroupDisplayName.Contains(query, StringComparison.OrdinalIgnoreCase) + || resourceGroup.KeyVaultResources.Any(keyVault => + keyVault.HasData + && keyVault.Data.Name.Contains(query, StringComparison.OrdinalIgnoreCase) + ) + ).ToList(); + + if (filteredResourceGroups.Count != 0) + { + var filteredSubscription = new KvSubscriptionModel + { + Subscription = subscription.Subscription, + SubscriptionDisplayName = subscription.SubscriptionDisplayName, + SubscriptionId = subscription.SubscriptionId, + IsExpanded = true, // Expand to show child nodes + ResourceGroups = new ObservableCollection(filteredResourceGroups) + }; + + filteredSubscriptions.Add(filteredSubscription); + } + } + + return filteredSubscriptions; + } +} \ No newline at end of file diff --git a/KeyVaultExplorer/ViewModels/KeyVaultTreeListViewModel.cs b/KeyVaultExplorer/ViewModels/KeyVaultTreeListViewModel.cs index c4fa20df..c1d168c7 100644 --- a/KeyVaultExplorer/ViewModels/KeyVaultTreeListViewModel.cs +++ b/KeyVaultExplorer/ViewModels/KeyVaultTreeListViewModel.cs @@ -8,6 +8,8 @@ using KeyVaultExplorer.Models; using KeyVaultExplorer.Services; using System; +using System.Buffers; +using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; @@ -22,7 +24,7 @@ namespace KeyVaultExplorer.ViewModels; public partial class KeyVaultTreeListViewModel : ViewModelBase { - public IEnumerable _treeViewList; + public ObservableCollection _treeViewList = []; [ObservableProperty] private bool isBusy = false; @@ -34,7 +36,7 @@ public partial class KeyVaultTreeListViewModel : ViewModelBase private KeyVaultResource selectedTreeItem; [ObservableProperty] - private ObservableCollection treeViewList; + private ObservableCollection treeViewList = []; private readonly AuthService _authService; private readonly KvExplorerDb _dbContext; @@ -50,7 +52,6 @@ public KeyVaultTreeListViewModel() _notificationViewModel = Defaults.Locator.GetRequiredService(); // PropertyChanged += OnMyViewModelPropertyChanged; - TreeViewList = []; //foreach (var item in TreeViewList) //{ // item.PropertyChanged += KvSubscriptionModel_PropertyChanged; @@ -74,7 +75,7 @@ public async Task GetAvailableKeyVaults(bool isRefresh = false) { if (isRefresh) { - TreeViewList.Clear(); + _treeViewList.Clear(); } await Task.Run(async () => @@ -89,7 +90,7 @@ await DelaySetIsBusy(async () => { item.PropertyChanged += KvSubscriptionModel_PropertyChanged; item.HasSubNodeDataBeenFetched = false; - TreeViewList.Add(item); + _treeViewList.Add(item); } //pinned items, insert the item so it appears instantly, then replace it once it finishes process items from KV @@ -98,12 +99,12 @@ await DelaySetIsBusy(async () => SubscriptionDisplayName = "Quick Access", SubscriptionId = "", IsExpanded = true, - ResourceGroups = [new KvResourceGroupModel { }], + ResourceGroups = [new KvResourceGroupModel { ResourceGroupDisplayName = string.Empty }], }; - TreeViewList.Insert(0, quickAccess); + _treeViewList.Insert(0, quickAccess); - var savedItems = _dbContext.GetQuickAccessItemsAsyncEnumerable(); + var savedItems = _dbContext.GetQuickAccessItemsAsyncEnumerable(_authService.TenantId ?? null); var token = new CustomTokenCredential(await _authService.GetAzureArmTokenSilent()); var armClient = new ArmClient(token); await foreach (var item in savedItems) @@ -117,9 +118,9 @@ await DelaySetIsBusy(async () => quickAccess.ResourceGroups[0].ResourceGroupDisplayName = "Pinned"; quickAccess.ResourceGroups[0].IsExpanded = true; - TreeViewList[0] = quickAccess; + _treeViewList[0] = quickAccess; - foreach (var sub in TreeViewList) + foreach (var sub in _treeViewList) { sub.ResourceGroups.CollectionChanged += TreeViewSubNode_CollectionChanged; } @@ -127,12 +128,18 @@ await DelaySetIsBusy(async () => catch (Exception ex) { Debug.Write(ex); - _notificationViewModel.ShowPopup(new Avalonia.Controls.Notifications.Notification { Message = ex.Message, Title = "Error" }); + Dispatcher.UIThread.Post(() => _notificationViewModel.ShowPopup(new Avalonia.Controls.Notifications.Notification { Message = ex.Message, Title = "Error" }), DispatcherPriority.Background); } }); }); - Dispatcher.UIThread.Post(() => _treeViewList = TreeViewList , DispatcherPriority.Background); + var searched = await Task.Run(() => + { + return new ObservableCollection(FilterService.Filter(_treeViewList, SearchQuery)); + }); + Dispatcher.UIThread.Post(() => { + TreeViewList = searched; + }, DispatcherPriority.Background); } // this will set isBusy to true if the fetching takes longer than 1500 ms. @@ -297,29 +304,17 @@ await DelaySetIsBusy(async () => // } //} - partial void OnSearchQueryChanged(string value) + partial void OnSearchQueryChanged(string value) { string query = value.Trim(); - if (!string.IsNullOrWhiteSpace(query)) - { - TreeViewList = new ObservableCollection(_treeViewList); - } + var searched = Task.Run(() => + { + return new ObservableCollection(FilterService.Filter(_treeViewList, query)); ; + }); - // var searchValues = SearchValues.Create(query.AsSpan()); - // var listSearched = _treeViewList.Where(v => - // v.SubscriptionDisplayName.AsSpan().ContainsAny(searchValues) || - // //v.KeyVaultResources.Any(x => x.HasData && x.Data.Name.Contains(query)) - // v.KeyVaultResources.Any(x => x.HasData && x.Data.Name.AsSpan().ContainsAny(searchValues)) - //); - - var listSearched = _treeViewList.Where(v => - v.SubscriptionDisplayName.Contains(query, StringComparison.OrdinalIgnoreCase) - || v.ResourceGroups.Any(r => r.ResourceGroupDisplayName is not null && r.ResourceGroupDisplayName.Contains(query, StringComparison.OrdinalIgnoreCase) - || r.KeyVaultResources.Any(kr => kr.HasData && kr.Data.Name.Contains(query, StringComparison.OrdinalIgnoreCase))) - ); - TreeViewList = new ObservableCollection(listSearched); + var res = searched.GetAwaiter().GetResult(); + Dispatcher.UIThread.InvokeAsync(() => TreeViewList = res, DispatcherPriority.Background); } - [RelayCommand] private void OpenInAzure(KeyVaultResource model) { @@ -363,4 +358,5 @@ private void TreeViewSubNode_CollectionChanged(object sender, NotifyCollectionCh // } //} } + } \ No newline at end of file diff --git a/KeyVaultExplorer/ViewModels/SubscriptionsPageViewModel.cs b/KeyVaultExplorer/ViewModels/SubscriptionsPageViewModel.cs index c9779f47..83cb7670 100644 --- a/KeyVaultExplorer/ViewModels/SubscriptionsPageViewModel.cs +++ b/KeyVaultExplorer/ViewModels/SubscriptionsPageViewModel.cs @@ -27,6 +27,7 @@ public partial class SubscriptionsPageViewModel : ViewModelBase private readonly KvExplorerDb _dbContext; private readonly IMemoryCache _memoryCache; private readonly VaultService _vaultService; + private readonly AuthService _authService; private NotificationViewModel _notificationViewModel; public SubscriptionsPageViewModel() @@ -34,6 +35,7 @@ public SubscriptionsPageViewModel() _vaultService = Defaults.Locator.GetRequiredService(); _dbContext = Defaults.Locator.GetRequiredService(); _memoryCache = Defaults.Locator.GetRequiredService(); + _authService = Defaults.Locator.GetRequiredService(); _notificationViewModel = Defaults.Locator.GetRequiredService(); Subscriptions = []; } @@ -43,7 +45,7 @@ public async Task GetSubscriptions() { int count = 0; - var savedSubscriptions = (await _dbContext.GetStoredSubscriptions()).ToDictionary(s => s.SubscriptionId); + var savedSubscriptions = (await _dbContext.GetStoredSubscriptions(_authService.TenantId ?? null)).ToDictionary(s => s.SubscriptionId); await foreach (var item in _vaultService.GetAllSubscriptions()) { @@ -67,7 +69,7 @@ public async Task GetSubscriptions() public async Task LoadMore() { int count = 0; - var savedSubscriptions = (await _dbContext.GetStoredSubscriptions()).ToDictionary(s => s.SubscriptionId); + var savedSubscriptions = (await _dbContext.GetStoredSubscriptions(_authService.TenantId ?? null)).ToDictionary(s => s.SubscriptionId); await foreach (var item in _vaultService.GetAllSubscriptions(continuationToken: ContinuationToken)) { diff --git a/KeyVaultExplorer/Views/CustomControls/KeyVaultTreeList.axaml b/KeyVaultExplorer/Views/CustomControls/KeyVaultTreeList.axaml index f3c48827..1083a095 100644 --- a/KeyVaultExplorer/Views/CustomControls/KeyVaultTreeList.axaml +++ b/KeyVaultExplorer/Views/CustomControls/KeyVaultTreeList.axaml @@ -100,7 +100,7 @@ @@ -116,7 +116,7 @@ @@ -213,7 +213,7 @@ - @@ -274,7 +274,7 @@ diff --git a/KeyVaultExplorer/Views/Pages/VaultPage.axaml b/KeyVaultExplorer/Views/Pages/VaultPage.axaml index c06adc7e..c3cb4eb1 100644 --- a/KeyVaultExplorer/Views/Pages/VaultPage.axaml +++ b/KeyVaultExplorer/Views/Pages/VaultPage.axaml @@ -150,7 +150,7 @@ Width="16" Height="16" VerticalAlignment="Bottom" - RenderOptions.BitmapInterpolationMode="None" + RenderOptions.BitmapInterpolationMode="HighQuality" UriSource="avares://KeyVaultExplorer/Assets/Add.png" /> diff --git a/KeyVaultExplorer/Views/Pages/VaultPage.axaml.cs b/KeyVaultExplorer/Views/Pages/VaultPage.axaml.cs index b73b531d..e5f3396f 100644 --- a/KeyVaultExplorer/Views/Pages/VaultPage.axaml.cs +++ b/KeyVaultExplorer/Views/Pages/VaultPage.axaml.cs @@ -13,6 +13,7 @@ using KeyVaultExplorer.Models; using KeyVaultExplorer.ViewModels; using System; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; @@ -90,6 +91,13 @@ private void OnDataGridRowContextRequested(object sender, ContextRequestedEventA private void OnDoubleTapped(object sender, TappedEventArgs e) { + // hack to prevent double click on column header from opening the properties flyout + var control = (e.Source as Control); + if (control.Name is not null && control.Name.EndsWith("PART_ColumnHeaderRoot")) + { + e.Handled = true; + return; + } var dg = (DataGrid)sender; var model = dg.SelectedItem as KeyVaultContentsAmalgamation; (DataContext as VaultPageViewModel).ShowPropertiesCommand.Execute(model); From 1d1555be3206275f7171d0e7c32b52b1a3f0d57b Mon Sep 17 00:00:00 2001 From: Arthur Thomas Date: Thu, 8 Aug 2024 23:46:52 -0400 Subject: [PATCH 5/6] add ablity to refresh subscriptions fix issue with subscriptions not saving --- KeyVaultExplorer/Database/KvExplorerDb.cs | 6 +++--- .../ViewModels/SubscriptionsPageViewModel.cs | 12 +++++++++--- .../Views/Pages/SubscriptionsPage.axaml | 19 +++++++++++++++++-- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/KeyVaultExplorer/Database/KvExplorerDb.cs b/KeyVaultExplorer/Database/KvExplorerDb.cs index da8dc115..c25d9cde 100644 --- a/KeyVaultExplorer/Database/KvExplorerDb.cs +++ b/KeyVaultExplorer/Database/KvExplorerDb.cs @@ -45,8 +45,8 @@ public static async void InitializeDatabase() BEGIN TRANSACTION; -- Table: Subscriptions CREATE TABLE IF NOT EXISTS Subscriptions ( - DisplayName TEXT NOT NULL CONSTRAINT UQ_DisplayName UNIQUE ON CONFLICT IGNORE, - SubscriptionId TEXT (200) PRIMARY KEY UNIQUE ON CONFLICT IGNORE, + DisplayName TEXT NOT NULL, + SubscriptionId TEXT (200) PRIMARY KEY UNIQUE ON CONFLICT IGNORE, TenantId TEXT (200) ); CREATE UNIQUE INDEX IF NOT EXISTS IX_Subscriptions_DisplayName_SubscriptionsId ON Subscriptions ( @@ -200,7 +200,7 @@ public async Task> GetStoredSubscriptions(string tenantId = return subscriptions; } - public async Task InsertSubscriptions(IEnumerable subscriptions) + public async Task InsertSubscriptions(IList subscriptions) { foreach (var subscription in subscriptions) { diff --git a/KeyVaultExplorer/ViewModels/SubscriptionsPageViewModel.cs b/KeyVaultExplorer/ViewModels/SubscriptionsPageViewModel.cs index 83cb7670..7b4beab9 100644 --- a/KeyVaultExplorer/ViewModels/SubscriptionsPageViewModel.cs +++ b/KeyVaultExplorer/ViewModels/SubscriptionsPageViewModel.cs @@ -43,7 +43,9 @@ public SubscriptionsPageViewModel() [RelayCommand] public async Task GetSubscriptions() { + int count = 0; + Subscriptions.Clear(); var savedSubscriptions = (await _dbContext.GetStoredSubscriptions(_authService.TenantId ?? null)).ToDictionary(s => s.SubscriptionId); @@ -108,7 +110,7 @@ public void ClearSelectedSubscriptions() [RelayCommand] public async Task SaveSelectedSubscriptions() { - var updatedItems = Subscriptions.Where(i => i.IsUpdated is not null); + var updatedItems = Subscriptions.Where(i => i.IsUpdated is not null || i.IsUpdated == true); var added = updatedItems.Where(i => i.IsPinned).Select(s => new Subscriptions { @@ -119,9 +121,13 @@ public async Task SaveSelectedSubscriptions() var removed = updatedItems.Where(i => !i.IsPinned).Select(s => s.Data.SubscriptionId); - await _dbContext.InsertSubscriptions(added); + await _dbContext.InsertSubscriptions(added.ToList()); await _dbContext.RemoveSubscriptionsBySubscriptionIDs(removed); - _memoryCache.Remove("subscriptions"); + _memoryCache.Remove($"subscriptions_{_authService.TenantId}"); _notificationViewModel.AddMessage(new Avalonia.Controls.Notifications.Notification("Saved", "Your changes have been saved.", Avalonia.Controls.Notifications.NotificationType.Information)); + + foreach (var item in Subscriptions) + item.IsUpdated = false; + } } \ No newline at end of file diff --git a/KeyVaultExplorer/Views/Pages/SubscriptionsPage.axaml b/KeyVaultExplorer/Views/Pages/SubscriptionsPage.axaml index 396db0bb..6ef8b911 100644 --- a/KeyVaultExplorer/Views/Pages/SubscriptionsPage.axaml +++ b/KeyVaultExplorer/Views/Pages/SubscriptionsPage.axaml @@ -67,6 +67,21 @@ +