diff --git a/src/ScriptRunner/ScriptRunner.GUI/ParamsPanelFactory.cs b/src/ScriptRunner/ScriptRunner.GUI/ParamsPanelFactory.cs index d1b3831..138f7f9 100644 --- a/src/ScriptRunner/ScriptRunner.GUI/ParamsPanelFactory.cs +++ b/src/ScriptRunner/ScriptRunner.GUI/ParamsPanelFactory.cs @@ -10,6 +10,7 @@ using Avalonia.Data.Converters; using Avalonia.Media; using ScriptRunner.GUI.ScriptConfigs; +using ScriptRunner.GUI.Settings; using ScriptRunner.GUI.ViewModels; using ScriptRunner.GUI.Views; @@ -17,7 +18,7 @@ namespace ScriptRunner.GUI; public class ParamsPanelFactory { - public ParamsPanel Create(IEnumerable parameters, Dictionary values) + public ParamsPanel Create(ScriptConfig action, Dictionary values) { var paramsPanel = new StackPanel { @@ -25,11 +26,12 @@ public ParamsPanel Create(IEnumerable parameters, Dictionary(); - - foreach (var (param,i) in parameters.Select((x,i)=>(x,i))) + var appSettings = AppSettingsService.Load(); + var secretBindings = appSettings.VaultBindings ?? new List(); + foreach (var (param,i) in action.Params.Select((x,i)=>(x,i))) { values.TryGetValue(param.Name, out var value); - var controlRecord = CreateControlRecord(param, value, i); + var controlRecord = CreateControlRecord(param, value, i, action, secretBindings); controlRecord.Name = param.Name; var actionPanel = new StackPanel { @@ -55,7 +57,8 @@ public ParamsPanel Create(IEnumerable parameters, Dictionary secretBindings) { switch (p.Prompt) { @@ -70,15 +73,40 @@ private static IControlRecord CreateControlRecord(ScriptParam p, string? value, } }; case PromptType.Password: - return new PasswordControl + + var passwordBox = new PasswordBox + { + Password = value, + TabIndex = index, + IsTabStop = true + }; + + if (secretBindings.FirstOrDefault(x => x.ActionName == scriptConfig.Name && x.ParameterName == p.Name) is { } binding) { - Control = new PasswordBox() + var vaultEntries = VaultProvider.ReadFromVault(); + if (vaultEntries.FirstOrDefault(x => x.Name == binding.VaultKey) is { } vaultEntry) { - Password = value, - TabIndex = index, - IsTabStop = true - }, - MaskingRequired = true + passwordBox.VaultKey = vaultEntry.Name; + passwordBox.Password = vaultEntry.Secret; + } + } + + passwordBox.VaultBindingChanged += (sender, args) => + { + if (args.VaultEntryChoice.RememberBinding) + { + AppSettingsService.UpdateVaultBindings(new VaultBinding + { + ActionName = scriptConfig.Name, + ParameterName = p.Name, + VaultKey = args.VaultEntryChoice.SelectedEntry.Name + }); + } + }; + return new PasswordControl + { + Control = passwordBox, + MaskingRequired = true, }; case PromptType.Dropdown: return new DropdownControl diff --git a/src/ScriptRunner/ScriptRunner.GUI/Settings/AppSettingsService.cs b/src/ScriptRunner/ScriptRunner.GUI/Settings/AppSettingsService.cs index adf0f57..f196f1a 100644 --- a/src/ScriptRunner/ScriptRunner.GUI/Settings/AppSettingsService.cs +++ b/src/ScriptRunner/ScriptRunner.GUI/Settings/AppSettingsService.cs @@ -79,6 +79,23 @@ public static void UpdateScriptConfigs(IEnumerable configScri Save(allSettings); } + public static void UpdateVaultBindings(VaultBinding binding) + { + var allSettings = Load(); + allSettings.VaultBindings ??= new List(); + var existingBinding = allSettings.VaultBindings.FirstOrDefault(x => x.ActionName == binding.ActionName && x.ParameterName == x.ParameterName); + if (existingBinding != null) + { + existingBinding.VaultKey = binding.VaultKey; + } + else + { + allSettings.VaultBindings.Add(binding); + } + + Save(allSettings); + } + private static string GetSettingsPath() { return GetSettingsPathFor("settings.json"); diff --git a/src/ScriptRunner/ScriptRunner.GUI/Settings/ScriptRunnerAppSettings.cs b/src/ScriptRunner/ScriptRunner.GUI/Settings/ScriptRunnerAppSettings.cs index 8847016..04b705d 100644 --- a/src/ScriptRunner/ScriptRunner.GUI/Settings/ScriptRunnerAppSettings.cs +++ b/src/ScriptRunner/ScriptRunner.GUI/Settings/ScriptRunnerAppSettings.cs @@ -9,6 +9,7 @@ public class ScriptRunnerAppSettings public LayoutSettings? Layout { get; set; } public Dictionary InstalledActions { get; set; } public List? ConfigScripts { get; set; } + public List VaultBindings { get; set; } } public record ConfigScriptEntry @@ -22,4 +23,11 @@ public record ConfigScriptEntry public class CommandInstallationStatus { public bool IsInstalled { get; set; } +} + +public class VaultBinding +{ + public string ActionName { get; set; } + public string ParameterName { get; set; } + public string VaultKey { get; set; } } \ No newline at end of file diff --git a/src/ScriptRunner/ScriptRunner.GUI/ViewModels/MainWindowViewModel.cs b/src/ScriptRunner/ScriptRunner.GUI/ViewModels/MainWindowViewModel.cs index 88e4357..e289417 100644 --- a/src/ScriptRunner/ScriptRunner.GUI/ViewModels/MainWindowViewModel.cs +++ b/src/ScriptRunner/ScriptRunner.GUI/ViewModels/MainWindowViewModel.cs @@ -236,7 +236,7 @@ private void RenderParameterForm(ScriptConfig action, Dictionary //var actionPanel = new StackPanel(); // Create IPanel with controls for all parameters - var paramsPanel = new ParamsPanelFactory().Create(action.Params, parameterValues); + var paramsPanel = new ParamsPanelFactory().Create(action, parameterValues); // Add panel with param controls to action panel //actionPanel.Children.Add(paramsPanel.Panel); diff --git a/src/ScriptRunner/ScriptRunner.GUI/Views/PasswordBox.axaml.cs b/src/ScriptRunner/ScriptRunner.GUI/Views/PasswordBox.axaml.cs index e65bcea..cfe29e8 100644 --- a/src/ScriptRunner/ScriptRunner.GUI/Views/PasswordBox.axaml.cs +++ b/src/ScriptRunner/ScriptRunner.GUI/Views/PasswordBox.axaml.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Threading.Tasks; using Avalonia; using Avalonia.Controls; @@ -22,24 +23,45 @@ private void InitializeComponent() AvaloniaXamlLoader.Load(this); } + public string VaultKey { get; set; } + private async void PickFromVault(object? sender, RoutedEventArgs e) { var pickerDialog = new VaultPicker(); + if (string.IsNullOrWhiteSpace(VaultKey) == false) + { + pickerDialog.ViewModel.SelectedEntry = pickerDialog.ViewModel.Entries.FirstOrDefault(x => x.Name == VaultKey); + } if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { var sourceWindow = (sender as IControl)?.GetVisualRoot() as Window ?? desktop.MainWindow; - var selectedPassword = await pickerDialog.ShowDialog(sourceWindow); - if (selectedPassword != null) + if (await pickerDialog.ShowDialog(sourceWindow) is { } choice) { + VaultKey = choice.SelectedEntry.Name; + OnVaultBindingChanged(new VaultBindingChangedEventArgs(choice)); Dispatcher.UIThread.Post(() => { - Password = selectedPassword; + Password = choice.SelectedEntry.Secret; }); - } } } + public event EventHandler VaultBindingChanged; + + public class VaultBindingChangedEventArgs : EventArgs + { + public VaultEntryChoice VaultEntryChoice { get; } + + public VaultBindingChangedEventArgs(VaultEntryChoice vaultEntryChoice) + { + VaultEntryChoice = vaultEntryChoice; + } + } + + private void OnVaultBindingChanged(VaultBindingChangedEventArgs e) => VaultBindingChanged?.Invoke(this, e); + + public static readonly DirectProperty PasswordProperty = AvaloniaProperty.RegisterDirect ( name: nameof(Password), diff --git a/src/ScriptRunner/ScriptRunner.GUI/Views/VaultPicker.axaml b/src/ScriptRunner/ScriptRunner.GUI/Views/VaultPicker.axaml index 2e16b1a..7d27bb8 100644 --- a/src/ScriptRunner/ScriptRunner.GUI/Views/VaultPicker.axaml +++ b/src/ScriptRunner/ScriptRunner.GUI/Views/VaultPicker.axaml @@ -2,19 +2,26 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:vm="clr-namespace:ScriptRunner.GUI.Views" mc:Ignorable="d" Width="400" SizeToContent="WidthAndHeight" x:Class="ScriptRunner.GUI.Views.VaultPicker" WindowStartupLocation="CenterOwner" Title="Pick secret"> - - - - - - - - - - - + + + + + + + + + + + + + Remember binding + + + + diff --git a/src/ScriptRunner/ScriptRunner.GUI/Views/VaultPicker.axaml.cs b/src/ScriptRunner/ScriptRunner.GUI/Views/VaultPicker.axaml.cs index 99b0de5..b92320c 100644 --- a/src/ScriptRunner/ScriptRunner.GUI/Views/VaultPicker.axaml.cs +++ b/src/ScriptRunner/ScriptRunner.GUI/Views/VaultPicker.axaml.cs @@ -1,28 +1,63 @@ +using System.Collections.Generic; using Avalonia; using Avalonia.Controls; using Avalonia.Interactivity; -using Avalonia.Markup.Xaml; +using ReactiveUI; using ScriptRunner.GUI.ViewModels; namespace ScriptRunner.GUI.Views { + public class VaultPickerViewModel:ViewModelBase + { + public IReadOnlyList Entries { get; set; } + + public VaultEntry? SelectedEntry + { + get => _selectedEntry; + set => this.RaiseAndSetIfChanged(ref _selectedEntry, value); + } + + private VaultEntry? _selectedEntry; + + + + } + public partial class VaultPicker : Window { public VaultPicker() { - DataContext = VaultProvider.ReadFromVault(); + DataContext = this.ViewModel = new VaultPickerViewModel + { + Entries = VaultProvider.ReadFromVault() + }; InitializeComponent(); #if DEBUG this.AttachDevTools(); #endif } + public VaultPickerViewModel ViewModel { get; set; } private void Accept(object? sender, RoutedEventArgs e) { - Close((SecretsCombo.SelectedItem as VaultEntry)?.Secret); + if(SecretsCombo.SelectedItem is VaultEntry selectedEntry) + { + Close(new VaultEntryChoice() + { + RememberBinding = this.Remember.IsChecked ?? false, + SelectedEntry = selectedEntry + }); + } + } } + + public class VaultEntryChoice + { + public bool RememberBinding { get; set; } + public VaultEntry SelectedEntry { get; set; } + } }