diff --git a/.gitignore b/.gitignore index 0debb2da..06361713 100644 --- a/.gitignore +++ b/.gitignore @@ -457,3 +457,4 @@ $RECYCLE.BIN/ /KeyVaultExplorer.Desktop/mac /mpdev .DS_Store +/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/msbuild.ps1 diff --git a/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/App.xaml.cs b/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/App.xaml.cs index ac5d615f..290c20af 100644 --- a/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/App.xaml.cs +++ b/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/App.xaml.cs @@ -70,7 +70,7 @@ protected override async void OnLaunched(LaunchActivatedEventArgs args) LogLevel.Error) // Default filters for core Uno Platform namespaces - .CoreLogLevel(LogLevel.Warning); + .CoreLogLevel(LogLevel.Critical); #if DEBUG //Uno Platform namespace filter groups diff --git a/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/AzureKeyVaultStudio.csproj b/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/AzureKeyVaultStudio.csproj index 7d24fc1d..dc796633 100644 --- a/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/AzureKeyVaultStudio.csproj +++ b/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/AzureKeyVaultStudio.csproj @@ -12,7 +12,7 @@ AzureKeyVaultStudio io.github.cricketthomas.AzureKeyVaultExplorer - 2.0.5.0 + 2.1.0.0 2 cricketthomas @@ -77,6 +77,7 @@ + @@ -114,11 +115,4 @@ - - - - Always - - - diff --git a/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/Models/KeyVaultValuesAmalgamation.cs b/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/Models/KeyVaultValuesAmalgamation.cs index 83d03f2b..a7101af0 100644 --- a/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/Models/KeyVaultValuesAmalgamation.cs +++ b/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/Models/KeyVaultValuesAmalgamation.cs @@ -1,3 +1,4 @@ +using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using Azure.Security.KeyVault.Certificates; using Azure.Security.KeyVault.Keys; @@ -33,6 +34,7 @@ public sealed class KeyVaultItemProperties public string[] TagValues => Tags is not null ? [.. Tags.Values] : []; public string[] TagKeys => Tags is not null ? [.. Tags.Keys] : []; public string TagValuesString => string.Join(", ", Tags?.Values ?? []); + public ObservableCollection EditableTags { get; set; } = new ObservableCollection(); public DateTimeOffset? LastModifiedDate => UpdatedOn.HasValue ? UpdatedOn.Value.ToLocalTime() : CreatedOn?.ToLocalTime(); public string? WhenLastModified => LastModifiedDate.HasValue ? FormatRelativeDate(LastModifiedDate.Value, true) : null; @@ -108,15 +110,7 @@ public SecretProperties ToSecretProperties() properties.Enabled = Enabled; properties.NotBefore = NotBefore; properties.ExpiresOn = ExpiresOn; - - if (Tags != null && Tags.Count > 0) - { - foreach (var tag in Tags) - { - properties.Tags[tag.Key] = tag.Value; - } - } - + ApplyEditableTags(properties.Tags); return properties; } @@ -126,15 +120,7 @@ public KeyProperties ToKeyProperties() properties.Enabled = Enabled; properties.NotBefore = NotBefore; properties.ExpiresOn = ExpiresOn; - - if (Tags != null && Tags.Count > 0) - { - foreach (var tag in Tags) - { - properties.Tags[tag.Key] = tag.Value; - } - } - + ApplyEditableTags(properties.Tags); return properties; } @@ -142,14 +128,7 @@ public CertificateProperties ToCertificateProperties() { var properties = new CertificateProperties(Id); properties.Enabled = Enabled; - if (Tags != null && Tags.Count > 0) - { - foreach (var tag in Tags) - { - properties.Tags[tag.Key] = tag.Value; - } - } - + ApplyEditableTags(properties.Tags); return properties; } @@ -188,7 +167,8 @@ private static KeyVaultItemProperties Create( RecoveryLevel = recoveryLevel, Managed = managed, Tags = tags is null ? new Dictionary() : new Dictionary(tags), - Type = type + Type = type, + EditableTags = tags is null ? []: new ObservableCollection(tags.Select(t => new TagItem { Key = t.Key, Value = t.Value })) }; } @@ -225,6 +205,32 @@ private static KeyVaultItemProperties Create( ( < 366, _) => $"{(isPast ? string.Empty : "in ")}{months} {(months == 1 ? "month" : "months")}{(isPast ? " ago" : string.Empty)}", (_, _) => $"{(isPast ? string.Empty : "in ")}{years} {(years == 1 ? "year" : "years")}{(isPast ? " ago" : string.Empty)}" }; + + + + } + + public void ApplyEditableTags(IDictionary targetTags) + { + targetTags.Clear(); + + if (EditableTags is null || EditableTags.Count == 0) + return; + + var seenKeys = new HashSet(StringComparer.OrdinalIgnoreCase); + + foreach (var tag in EditableTags) + { + var key = tag.Key?.Trim(); + + if (string.IsNullOrWhiteSpace(key)) + continue; + + if (!seenKeys.Add(key)) + throw new InvalidOperationException("Duplicate tag keys are not allowed."); + + targetTags[key] = tag.Value; + } } } @@ -235,3 +241,12 @@ public enum KeyVaultItemType Key = 2, All = 3 } + +public partial class TagItem : ObservableObject +{ + [ObservableProperty] + public partial string Key { get; set; } = string.Empty; + + [ObservableProperty] + public partial string Value { get; set; } = string.Empty; +} diff --git a/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/Package.appxmanifest b/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/Package.appxmanifest index acc46fd0..118d56bd 100644 --- a/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/Package.appxmanifest +++ b/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/Package.appxmanifest @@ -6,7 +6,7 @@ xmlns:uap18="http://schemas.microsoft.com/appx/manifest/uap/windows10/18" IgnorableNamespaces="uap rescap uap18"> - + Key Vault Explorer Arthur Thomas IV diff --git a/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/Presentation/MainViewModel.cs b/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/Presentation/MainViewModel.cs index a1320d85..cddfb79b 100644 --- a/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/Presentation/MainViewModel.cs +++ b/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/Presentation/MainViewModel.cs @@ -7,18 +7,16 @@ namespace AzureKeyVaultStudio.Presentation; public partial class MainViewModel : ObservableObject { + private const double PaneMaxWidth = 800; + private const double PaneMinWidth = 120; private readonly IAuthenticationService _authentication; + private readonly AuthService _authService; + private readonly IDispatcher _dispatcher; + private readonly KeyVaultTreeViewModel _keyVaultTreeViewModel; private readonly ILocalSettingsService _localSettings; private readonly INavigator _navigator; - private readonly IDispatcher _dispatcher; - private readonly VaultService _vaultService; - private readonly AuthService _authService; private readonly IServiceProvider _serviceProvider; - - private readonly KeyVaultTreeViewModel _keyVaultTreeViewModel; - public KeyVaultTreeViewModel KeyVaultTreeViewModel => _keyVaultTreeViewModel; - - + private readonly VaultService _vaultService; public MainViewModel( IDispatcher dispatcher, IStringLocalizer localizer, @@ -37,7 +35,7 @@ public MainViewModel( _dispatcher = dispatcher; _vaultService = vaultService; _serviceProvider = serviceProvider; - keyVaultTreeViewModel.SetDispatcher(dispatcher); // HACK + keyVaultTreeViewModel.SetDispatcher(dispatcher); // HACK _keyVaultTreeViewModel = keyVaultTreeViewModel; _authService = authService; @@ -56,30 +54,33 @@ public MainViewModel( }); } - public string? Title { get; } - + public string FontIconType => PanePlacement == SplitViewPanePlacement.Left ? "\uE8E4" : "\uE8E2"; public ICommand GoToSecond { get; } - - public ICommand Logout { get; } - - //private async Task GoToSecondView() - //{ - // await _navigator.NavigateViewModelAsync(this, data: new Entity(Name!)); - //} - - public async Task DoLogout(CancellationToken token) + public double InvertedSplitViewWidth { - await _authentication.LogoutAsync(token); - } + get => PaneMaxWidth - (SplitViewWidth - PaneMinWidth); + set + { + var clamped = Math.Clamp(value, PaneMinWidth, PaneMaxWidth); + var newActual = PaneMaxWidth - (clamped - PaneMinWidth); - [ObservableProperty] - public partial SplitViewDisplayMode SplitViewDisplay { get; set; } = SplitViewDisplayMode.Inline; + if (SplitViewWidth != newActual) + { + SplitViewWidth = newActual; + } + } + } + public bool IsPaneLeft => PanePlacement == SplitViewPanePlacement.Left; [ObservableProperty] public partial bool IsPaneOpen { get; set; } = true; + public KeyVaultTreeViewModel KeyVaultTreeViewModel => _keyVaultTreeViewModel; + public ICommand Logout { get; } [ObservableProperty] - public partial double SplitViewWidth { get; set; } = 220; + [NotifyPropertyChangedFor(nameof(IsPaneLeft))] + [NotifyPropertyChangedFor(nameof(FontIconType))] + public partial SplitViewPanePlacement PanePlacement { get; set; } = SplitViewPanePlacement.Right; [ObservableProperty] public partial string SearchQuery { get; set; } = string.Empty; @@ -88,43 +89,24 @@ public async Task DoLogout(CancellationToken token) public partial object? SelectedKeyVaultItem { get; set; } [ObservableProperty] - [NotifyPropertyChangedFor(nameof(IsPaneLeft))] - [NotifyPropertyChangedFor(nameof(FontIconType))] - public partial SplitViewPanePlacement PanePlacement { get; set; } = SplitViewPanePlacement.Right; - - public bool IsPaneLeft => PanePlacement == SplitViewPanePlacement.Left; - - public string FontIconType => PanePlacement == SplitViewPanePlacement.Left ? "\uE8E4" : "\uE8E2"; - //public FontIcon FontIconType => PanePlacement == SplitViewPanePlacement.Left ? new FontIcon() { Glyph = "" } : new FontIcon() { Glyph = "" }; - - private void LoadSplitViewSettings() - { - SplitViewDisplay = GetSettingsValue(nameof(SplitViewDisplay), SplitViewDisplayMode.Inline); - SplitViewWidth = _localSettings.GetValue(nameof(SplitViewWidth), 300d); - IsPaneOpen = _localSettings.GetValue(nameof(IsPaneOpen), true); - PanePlacement = GetSettingsValue(nameof(PanePlacement), SplitViewPanePlacement.Right); - } - + public partial SplitViewDisplayMode SplitViewDisplay { get; set; } = SplitViewDisplayMode.Inline; + [ObservableProperty] + public partial double SplitViewWidth { get; set; } = 220; + public string? Title { get; } [RelayCommand] - public void ToggleSplitViewDisplay() + public void ClosePane() { - SplitViewDisplay = SplitViewDisplay == SplitViewDisplayMode.Overlay ? SplitViewDisplayMode.Inline : SplitViewDisplayMode.Overlay; + IsPaneOpen = false; + WeakReferenceMessenger.Default.Send(new PaneStateChangedMessage(false)); } - [RelayCommand] - public void TogglePaneLocation() + public async Task DoLogout(CancellationToken token) { - PanePlacement = PanePlacement == SplitViewPanePlacement.Right ? SplitViewPanePlacement.Left : SplitViewPanePlacement.Right; + await _authentication.LogoutAsync(token); } - - - private void Save(string key, T value) - { - _localSettings.SetValue(key, value); - } [RelayCommand] public void SaveAllSettings() { @@ -134,35 +116,33 @@ public void SaveAllSettings() Save(nameof(IsPaneOpen), IsPaneOpen); } - [RelayCommand] - public void ClosePane() + public void TogglePaneLocation() { - IsPaneOpen = false; - WeakReferenceMessenger.Default.Send(new PaneStateChangedMessage(false)); + PanePlacement = PanePlacement == SplitViewPanePlacement.Right ? SplitViewPanePlacement.Left : SplitViewPanePlacement.Right; } - private const double PaneMinWidth = 100; - private const double PaneMaxWidth = 1000; - - public double InvertedSplitViewWidth + [RelayCommand] + public void ToggleSplitViewDisplay() { - get => PaneMaxWidth - (SplitViewWidth - PaneMinWidth); - set - { - var clamped = Math.Clamp(value, PaneMinWidth, PaneMaxWidth); - var newActual = PaneMaxWidth - (clamped - PaneMinWidth); - - if (SplitViewWidth != newActual) - { - SplitViewWidth = newActual; - OnPropertyChanged(nameof(SplitViewWidth)); - } - } + SplitViewDisplay = SplitViewDisplay == SplitViewDisplayMode.Overlay ? SplitViewDisplayMode.Inline : SplitViewDisplayMode.Overlay; } + private T GetSettingsValue(string key, T defaultValue) where T : struct, Enum { var stringValue = _localSettings.GetValue(key, defaultValue.ToString()); return Enum.TryParse(stringValue, out var result) ? result : defaultValue; } + + private void LoadSplitViewSettings() + { + SplitViewDisplay = GetSettingsValue(nameof(SplitViewDisplay), SplitViewDisplayMode.Inline); + SplitViewWidth = _localSettings.GetValue(nameof(SplitViewWidth), 300d); + IsPaneOpen = _localSettings.GetValue(nameof(IsPaneOpen), true); + PanePlacement = GetSettingsValue(nameof(PanePlacement), SplitViewPanePlacement.Right); + } + private void Save(string key, T value) + { + _localSettings.SetValue(key, value); + } } diff --git a/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/Presentation/VaultTabContentPage.xaml b/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/Presentation/VaultTabContentPage.xaml index 079d2444..1c6adab7 100644 --- a/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/Presentation/VaultTabContentPage.xaml +++ b/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/Presentation/VaultTabContentPage.xaml @@ -278,12 +278,11 @@ Height="20" MaxWidth="80" Padding="5,2" - Background="{ThemeResource SystemAccentColor}" + Background="{ThemeResource AccentAcrylicBackgroundFillColorDefaultBrush}" CornerRadius="2"> diff --git a/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/Strings/en/Resources.resw b/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/Strings/en/Resources.resw index 5f31bba6..d8af5ac2 100644 --- a/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/Strings/en/Resources.resw +++ b/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/Strings/en/Resources.resw @@ -576,6 +576,9 @@ Tags + + Tags + Updated @@ -669,4 +672,13 @@ Documentation: Tenant setup guide - \ No newline at end of file + + Remove Tag + + + Add Tag + + + Close + + diff --git a/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/Strings/es/Resources.resw b/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/Strings/es/Resources.resw index ec874485..fae95085 100644 --- a/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/Strings/es/Resources.resw +++ b/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/Strings/es/Resources.resw @@ -576,6 +576,9 @@ Etiquetas + + Etiquetas + Actualizado @@ -669,4 +672,13 @@ Documentación: Guía de configuración del tenant + + Eliminar etiqueta + + + Agregar etiqueta + + + Cerrar + \ No newline at end of file diff --git a/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/Strings/fr/Resources.resw b/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/Strings/fr/Resources.resw index 1102d977..188f09a2 100644 --- a/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/Strings/fr/Resources.resw +++ b/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/Strings/fr/Resources.resw @@ -576,6 +576,9 @@ Étiquettes + + Étiquettes + Mis à jour @@ -669,4 +672,13 @@ Documentation : Guide de configuration du tenant + + Supprimer l’étiquette + + + Ajouter étiquette + + + Fermer + \ No newline at end of file diff --git a/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/Strings/pt-BR/Resources.resw b/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/Strings/pt-BR/Resources.resw index 3370399d..1a7c1467 100644 --- a/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/Strings/pt-BR/Resources.resw +++ b/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/Strings/pt-BR/Resources.resw @@ -576,6 +576,9 @@ Tags + + Tags + Atualizado @@ -669,4 +672,13 @@ Documentação: Guia de configuração do tenant + + Remover tag + + + Adicionar tag + + + Fechar + \ No newline at end of file diff --git a/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/UserControls/ItemDetails.xaml b/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/UserControls/ItemDetails.xaml index ae0700be..822160ea 100644 --- a/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/UserControls/ItemDetails.xaml +++ b/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/UserControls/ItemDetails.xaml @@ -21,7 +21,6 @@ mc:Ignorable="d skia"> - @@ -39,12 +38,6 @@ - - + Icon="OpenFile"> + + + + + Icon="Refresh"> + + + + - - - - - - - - + + + + + + + - - + + diff --git a/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/UserControls/NewItem.xaml b/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/UserControls/NewItem.xaml index 6c17cdee..e716efe8 100644 --- a/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/UserControls/NewItem.xaml +++ b/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/UserControls/NewItem.xaml @@ -5,7 +5,6 @@ xmlns:controls="using:CommunityToolkit.WinUI.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="using:AzureKeyVaultStudio.UserControls" - xmlns:localx="AzureKeyVaultStudio.UserControls.NewItem" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:skia="http://uno.ui/skia" d:DesignHeight="300" @@ -121,7 +120,6 @@ - @@ -130,7 +128,8 @@ @@ -144,6 +143,9 @@ + + + diff --git a/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/UserControls/NewItem.xaml.cs b/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/UserControls/NewItem.xaml.cs index de9caae3..3d99af2a 100644 --- a/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/UserControls/NewItem.xaml.cs +++ b/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/UserControls/NewItem.xaml.cs @@ -31,7 +31,7 @@ private void NewItem_Loaded(object sender, RoutedEventArgs e) { WeakReferenceMessenger.Default.Register(this, ViewModel.MessengerToken, async (r, m) => { - ShowDialogMessage(_localizer["NewItemErrorTitle"], m.Data, _localizer["NewItemDismissButtonText"]); + ShowDialogMessage(_localizer["NewItemErrorTitle"], m.Data, _localizer["NewItemDismissButtonText"], true); }); WeakReferenceMessenger.Default.Register(this, ViewModel.MessengerToken, async (r, m) => { @@ -41,7 +41,7 @@ private void NewItem_Loaded(object sender, RoutedEventArgs e) }); } - private void ShowDialogMessage(string title, string message, string dismissButtonText) + private void ShowDialogMessage(string title, string message, string dismissMessageButtonText, bool isError = false) { _ = DispatcherQueue.TryEnqueue(async () => { @@ -62,11 +62,23 @@ private void ShowDialogMessage(string title, string message, string dismissButto HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled, MaxHeight = 300 }, - CloseButtonText = "OK", + XamlRoot = this.XamlRoot, RequestedTheme = this.ActualTheme, }; - contentDialog.CloseButtonClick += ContentDialog_CloseButtonClick; + + + // allow users to dismiss only if its an error alert, they can still close the window via parent. + // otherwise, show just the close button since users are done with the window and we dont support creating multiple secrets aat a time, yet + if (isError) + { + contentDialog.PrimaryButtonText = dismissMessageButtonText ?? "OK"; + } + else + { + contentDialog.CloseButtonText = _localizer?["CloseButtonText"] ?? "Close"; + contentDialog.CloseButtonClick += ContentDialog_CloseButtonClick; + } await contentDialog.ShowAsync(); }); } diff --git a/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/UserControls/NewVersionDialog.xaml b/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/UserControls/NewVersionDialog.xaml index aae6c81e..693fbff1 100644 --- a/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/UserControls/NewVersionDialog.xaml +++ b/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/UserControls/NewVersionDialog.xaml @@ -8,7 +8,7 @@ RequestedTheme="Default" Style="{ThemeResource DefaultContentDialogStyle}"> @@ -103,13 +103,16 @@ Text="Secret Value" /> + + diff --git a/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/UserControls/TagsEditor.xaml b/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/UserControls/TagsEditor.xaml new file mode 100644 index 00000000..1ff8e9e2 --- /dev/null +++ b/src/uno/AzureKeyVaultStudio/AzureKeyVaultStudio/UserControls/TagsEditor.xaml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + +