feat(configmanager): integrate SecureStore for credential management
Add SecureStore integration to ConfigManager for secure handling of connection strings and sensitive configuration values. Includes store/secret management UI, encrypted .store file support, and comprehensive test coverage.
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:JdeScoping.ConfigManager.ViewModels.Dialogs"
|
||||
x:Class="JdeScoping.ConfigManager.Views.Dialogs.NewStoreDialog"
|
||||
x:DataType="vm:NewStoreDialogViewModel"
|
||||
Title="Create New Secure Store"
|
||||
Width="550" Height="480"
|
||||
MinWidth="450" MinHeight="400"
|
||||
Background="#151920"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
CanResize="False"
|
||||
ShowInTaskbar="False">
|
||||
|
||||
<DockPanel>
|
||||
<!-- Header -->
|
||||
<Border DockPanel.Dock="Top" Background="#1C2128" Padding="24,16"
|
||||
BorderBrush="#2D3540" BorderThickness="0,0,0,1">
|
||||
<TextBlock Text="Create New Secure Store"
|
||||
Foreground="#E6EDF5" FontSize="18" FontWeight="SemiBold"/>
|
||||
</Border>
|
||||
|
||||
<!-- Footer -->
|
||||
<Border DockPanel.Dock="Bottom" Background="#1C2128" Padding="24,16"
|
||||
BorderBrush="#2D3540" BorderThickness="0,1,0,0">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Spacing="8">
|
||||
<Button Content="Cancel" Click="CancelButton_Click"
|
||||
Background="Transparent" BorderBrush="#3D4550"
|
||||
Foreground="#9BA8B8" Padding="16,8" MinWidth="80"/>
|
||||
<Button Content="Create" Click="CreateButton_Click"
|
||||
IsEnabled="{Binding IsValid}"
|
||||
Background="#5C9AFF" Foreground="#0D0F12"
|
||||
Padding="16,8" FontWeight="Medium" MinWidth="80"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Content -->
|
||||
<ScrollViewer Background="#151920" Padding="24,16">
|
||||
<StackPanel Spacing="16">
|
||||
<!-- Store Path -->
|
||||
<Border Background="#1C2128" BorderBrush="#2D3540" BorderThickness="1"
|
||||
CornerRadius="4" Padding="16">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="Store Location" Foreground="#E6EDF5"
|
||||
FontWeight="SemiBold" FontSize="14"/>
|
||||
<Grid ColumnDefinitions="*,Auto" Margin="0,4,0,0">
|
||||
<TextBox Grid.Column="0"
|
||||
Text="{Binding StorePath, Mode=TwoWay}"
|
||||
Watermark="Path to new secure store file..."
|
||||
Background="#0D0F12" Foreground="#E6EDF5"
|
||||
BorderBrush="#3D4550" Padding="8"/>
|
||||
<Button Grid.Column="1" Content="Browse..."
|
||||
Command="{Binding BrowseStorePathCommand}"
|
||||
Background="#2D3540" Foreground="#E6EDF5"
|
||||
Margin="8,0,0,0" Padding="12,8"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Encryption Method -->
|
||||
<Border Background="#1C2128" BorderBrush="#2D3540" BorderThickness="1"
|
||||
CornerRadius="4" Padding="16">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="Encryption Method" Foreground="#E6EDF5"
|
||||
FontWeight="SemiBold" FontSize="14"/>
|
||||
<RadioButton Content="Use Key File (recommended for production)"
|
||||
IsChecked="{Binding UseKeyFile, Mode=TwoWay}"
|
||||
Foreground="#9BA8B8" Margin="0,4"/>
|
||||
<RadioButton Content="Use Password"
|
||||
IsChecked="{Binding UsePassword, Mode=TwoWay}"
|
||||
Foreground="#9BA8B8" Margin="0,4"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Key File Settings -->
|
||||
<Border Background="#1C2128" BorderBrush="#2D3540" BorderThickness="1"
|
||||
CornerRadius="4" Padding="16"
|
||||
IsVisible="{Binding UseKeyFile}">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="Key File" Foreground="#E6EDF5"
|
||||
FontWeight="SemiBold" FontSize="14"/>
|
||||
<Grid ColumnDefinitions="*,Auto,Auto" Margin="0,4,0,0">
|
||||
<TextBox Grid.Column="0"
|
||||
Text="{Binding KeyFilePath, Mode=TwoWay}"
|
||||
Watermark="Path to key file..."
|
||||
Background="#0D0F12" Foreground="#E6EDF5"
|
||||
BorderBrush="#3D4550" Padding="8"/>
|
||||
<Button Grid.Column="1" Content="Browse..."
|
||||
Command="{Binding BrowseKeyFilePathCommand}"
|
||||
Background="#2D3540" Foreground="#E6EDF5"
|
||||
Margin="8,0,0,0" Padding="12,8"/>
|
||||
<Button Grid.Column="2" Content="Generate"
|
||||
Command="{Binding GenerateKeyFileCommand}"
|
||||
Background="#3D8C40" Foreground="#E6EDF5"
|
||||
Margin="8,0,0,0" Padding="12,8"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Password Settings -->
|
||||
<Border Background="#1C2128" BorderBrush="#2D3540" BorderThickness="1"
|
||||
CornerRadius="4" Padding="16"
|
||||
IsVisible="{Binding UsePassword}">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="Password" Foreground="#E6EDF5"
|
||||
FontWeight="SemiBold" FontSize="14"/>
|
||||
<Grid ColumnDefinitions="120,*" RowDefinitions="Auto,Auto" Margin="0,4,0,0">
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="Password:"
|
||||
Foreground="#9BA8B8" VerticalAlignment="Center" Margin="0,8"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="1"
|
||||
PasswordChar="*"
|
||||
Text="{Binding Password, Mode=TwoWay}"
|
||||
Background="#0D0F12" Foreground="#E6EDF5"
|
||||
BorderBrush="#3D4550" Padding="8" Margin="0,4"/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Text="Confirm:"
|
||||
Foreground="#9BA8B8" VerticalAlignment="Center" Margin="0,8"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="1"
|
||||
PasswordChar="*"
|
||||
Text="{Binding ConfirmPassword, Mode=TwoWay}"
|
||||
Background="#0D0F12" Foreground="#E6EDF5"
|
||||
BorderBrush="#3D4550" Padding="8" Margin="0,4"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Validation Error -->
|
||||
<TextBlock Text="{Binding ValidationError}"
|
||||
Foreground="#FF6B6B" FontSize="12"
|
||||
IsVisible="{Binding ValidationError, Converter={StaticResource StringToBool}}"
|
||||
Margin="0,4,0,0"/>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</DockPanel>
|
||||
</Window>
|
||||
@@ -0,0 +1,129 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Platform.Storage;
|
||||
using JdeScoping.ConfigManager.Constants;
|
||||
using JdeScoping.ConfigManager.Services.SecureStore;
|
||||
using JdeScoping.ConfigManager.ViewModels.Dialogs;
|
||||
using MsBox.Avalonia;
|
||||
using MsBox.Avalonia.Enums;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Views.Dialogs;
|
||||
|
||||
/// <summary>
|
||||
/// Dialog for creating a new secure store.
|
||||
/// </summary>
|
||||
public partial class NewStoreDialog : Window
|
||||
{
|
||||
private readonly ISecureStoreManager _secureStoreManager;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the view model for this dialog.
|
||||
/// </summary>
|
||||
public NewStoreDialogViewModel ViewModel => (NewStoreDialogViewModel)DataContext!;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NewStoreDialog"/> class.
|
||||
/// </summary>
|
||||
public NewStoreDialog() : this(new SecureStoreManager())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NewStoreDialog"/> class with the specified secure store manager.
|
||||
/// </summary>
|
||||
/// <param name="secureStoreManager">The secure store manager for key generation.</param>
|
||||
public NewStoreDialog(ISecureStoreManager secureStoreManager)
|
||||
{
|
||||
_secureStoreManager = secureStoreManager ?? throw new ArgumentNullException(nameof(secureStoreManager));
|
||||
InitializeComponent();
|
||||
DataContext = new NewStoreDialogViewModel();
|
||||
Loaded += NewStoreDialog_Loaded;
|
||||
}
|
||||
|
||||
private void NewStoreDialog_Loaded(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel.OnShowSaveFileDialog += ShowSaveFileDialogAsync;
|
||||
ViewModel.OnGenerateKeyFile += GenerateKeyFileAsync;
|
||||
}
|
||||
|
||||
private async Task<string?> ShowSaveFileDialogAsync(string title, string fileTypeName, string pattern, string defaultExtension)
|
||||
{
|
||||
var file = await StorageProvider.SaveFilePickerAsync(new FilePickerSaveOptions
|
||||
{
|
||||
Title = title,
|
||||
DefaultExtension = defaultExtension,
|
||||
FileTypeChoices = new[]
|
||||
{
|
||||
new FilePickerFileType(fileTypeName) { Patterns = new[] { pattern } },
|
||||
new FilePickerFileType(SecureStoreFileExtensions.AllFilesTypeName) { Patterns = new[] { SecureStoreFileExtensions.AllFilesPattern } }
|
||||
}
|
||||
});
|
||||
|
||||
return file?.Path.LocalPath;
|
||||
}
|
||||
|
||||
private async Task<string?> GenerateKeyFileAsync(string title, string fileTypeName, string pattern, string defaultExtension)
|
||||
{
|
||||
var file = await StorageProvider.SaveFilePickerAsync(new FilePickerSaveOptions
|
||||
{
|
||||
Title = title,
|
||||
DefaultExtension = defaultExtension,
|
||||
FileTypeChoices = new[]
|
||||
{
|
||||
new FilePickerFileType(fileTypeName) { Patterns = new[] { pattern } },
|
||||
new FilePickerFileType(SecureStoreFileExtensions.AllFilesTypeName) { Patterns = new[] { SecureStoreFileExtensions.AllFilesPattern } }
|
||||
}
|
||||
});
|
||||
|
||||
if (file == null)
|
||||
return null;
|
||||
|
||||
var path = file.Path.LocalPath;
|
||||
|
||||
try
|
||||
{
|
||||
// Generate a new key file using the local SecureStoreManager
|
||||
_secureStoreManager.GenerateKeyFile(path);
|
||||
|
||||
var box = MessageBoxManager.GetMessageBoxStandard(
|
||||
SecureStoreStrings.KeyGeneratedTitle,
|
||||
string.Format(SecureStoreStrings.KeyFileGeneratedFormat, path),
|
||||
ButtonEnum.Ok,
|
||||
MsBox.Avalonia.Enums.Icon.Info);
|
||||
await box.ShowWindowDialogAsync(this);
|
||||
|
||||
return path;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var box = MessageBoxManager.GetMessageBoxStandard(
|
||||
SecureStoreStrings.ErrorTitle,
|
||||
string.Format(SecureStoreStrings.FailedToGenerateKeyFormat, ex.Message),
|
||||
ButtonEnum.Ok,
|
||||
MsBox.Avalonia.Enums.Icon.Error);
|
||||
await box.ShowWindowDialogAsync(this);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async void CreateButton_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!ViewModel.IsValid)
|
||||
{
|
||||
var box = MessageBoxManager.GetMessageBoxStandard(
|
||||
SecureStoreStrings.ValidationErrorTitle,
|
||||
ViewModel.ValidationError ?? SecureStoreStrings.DefaultValidationError,
|
||||
ButtonEnum.Ok,
|
||||
MsBox.Avalonia.Enums.Icon.Warning);
|
||||
await box.ShowWindowDialogAsync(this);
|
||||
return;
|
||||
}
|
||||
|
||||
Close(true);
|
||||
}
|
||||
|
||||
private void CancelButton_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
Close(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:JdeScoping.ConfigManager.ViewModels.Dialogs"
|
||||
x:Class="JdeScoping.ConfigManager.Views.Dialogs.SecretEditDialog"
|
||||
x:DataType="vm:SecretEditDialogViewModel"
|
||||
Title="{Binding DialogTitle}"
|
||||
Width="500" Height="320"
|
||||
MinWidth="400" MinHeight="280"
|
||||
Background="#151920"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
CanResize="False"
|
||||
ShowInTaskbar="False">
|
||||
|
||||
<DockPanel>
|
||||
<!-- Header -->
|
||||
<Border DockPanel.Dock="Top" Background="#1C2128" Padding="24,16"
|
||||
BorderBrush="#2D3540" BorderThickness="0,0,0,1">
|
||||
<TextBlock Text="{Binding DialogTitle}"
|
||||
Foreground="#E6EDF5" FontSize="18" FontWeight="SemiBold"/>
|
||||
</Border>
|
||||
|
||||
<!-- Footer -->
|
||||
<Border DockPanel.Dock="Bottom" Background="#1C2128" Padding="24,16"
|
||||
BorderBrush="#2D3540" BorderThickness="0,1,0,0">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Spacing="8">
|
||||
<Button Content="Cancel" Click="CancelButton_Click"
|
||||
Background="Transparent" BorderBrush="#3D4550"
|
||||
Foreground="#9BA8B8" Padding="16,8" MinWidth="80"/>
|
||||
<Button Content="Save" Click="SaveButton_Click"
|
||||
IsEnabled="{Binding IsValid}"
|
||||
Background="#5C9AFF" Foreground="#0D0F12"
|
||||
Padding="16,8" FontWeight="Medium" MinWidth="80"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Content -->
|
||||
<Grid Background="#151920" Margin="24,16" RowDefinitions="Auto,*,Auto">
|
||||
<!-- Key -->
|
||||
<Border Grid.Row="0" Background="#1C2128" BorderBrush="#2D3540" BorderThickness="1"
|
||||
CornerRadius="4" Padding="16" Margin="0,0,0,16">
|
||||
<Grid ColumnDefinitions="80,*">
|
||||
<TextBlock Text="Key:" Foreground="#9BA8B8"
|
||||
VerticalAlignment="Center" Margin="0,0,16,0"/>
|
||||
<TextBox Grid.Column="1"
|
||||
Text="{Binding Key, Mode=TwoWay}"
|
||||
IsEnabled="{Binding IsKeyEditable}"
|
||||
Watermark="Enter secret key..."
|
||||
Background="#0D0F12" Foreground="#E6EDF5"
|
||||
BorderBrush="#3D4550" Padding="8"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Value -->
|
||||
<Border Grid.Row="1" Background="#1C2128" BorderBrush="#2D3540" BorderThickness="1"
|
||||
CornerRadius="4" Padding="16">
|
||||
<Grid ColumnDefinitions="80,*">
|
||||
<TextBlock Text="Value:" Foreground="#9BA8B8"
|
||||
VerticalAlignment="Top" Margin="0,8,16,0"/>
|
||||
<TextBox Grid.Column="1"
|
||||
Text="{Binding Value, Mode=TwoWay}"
|
||||
TextWrapping="Wrap"
|
||||
AcceptsReturn="True"
|
||||
Watermark="Enter secret value..."
|
||||
Background="#0D0F12" Foreground="#E6EDF5"
|
||||
BorderBrush="#3D4550" Padding="8"
|
||||
MinHeight="80"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Validation Error -->
|
||||
<TextBlock Grid.Row="2"
|
||||
Text="{Binding ValidationError}"
|
||||
Foreground="#FF6B6B" FontSize="12"
|
||||
IsVisible="{Binding ValidationError, Converter={StaticResource StringToBool}}"
|
||||
Margin="0,8,0,0"/>
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
</Window>
|
||||
@@ -0,0 +1,60 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using JdeScoping.ConfigManager.Constants;
|
||||
using JdeScoping.ConfigManager.ViewModels.Dialogs;
|
||||
using MsBox.Avalonia;
|
||||
using MsBox.Avalonia.Enums;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Views.Dialogs;
|
||||
|
||||
/// <summary>
|
||||
/// Dialog for adding or editing a secret in the secure store.
|
||||
/// </summary>
|
||||
public partial class SecretEditDialog : Window
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the view model for this dialog.
|
||||
/// </summary>
|
||||
public SecretEditDialogViewModel ViewModel => (SecretEditDialogViewModel)DataContext!;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SecretEditDialog"/> class for creating a new secret.
|
||||
/// </summary>
|
||||
public SecretEditDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = new SecretEditDialogViewModel();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SecretEditDialog"/> class for editing an existing secret.
|
||||
/// </summary>
|
||||
/// <param name="key">The secret key.</param>
|
||||
/// <param name="value">The secret value.</param>
|
||||
public SecretEditDialog(string key, string value)
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = new SecretEditDialogViewModel(key, value);
|
||||
}
|
||||
|
||||
private async void SaveButton_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!ViewModel.IsValid)
|
||||
{
|
||||
var box = MessageBoxManager.GetMessageBoxStandard(
|
||||
SecureStoreStrings.ValidationErrorTitle,
|
||||
ViewModel.ValidationError ?? SecureStoreStrings.DefaultValidationError,
|
||||
ButtonEnum.Ok,
|
||||
MsBox.Avalonia.Enums.Icon.Warning);
|
||||
await box.ShowWindowDialogAsync(this);
|
||||
return;
|
||||
}
|
||||
|
||||
Close(true);
|
||||
}
|
||||
|
||||
private void CancelButton_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
Close(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:JdeScoping.ConfigManager.ViewModels.Dialogs"
|
||||
x:Class="JdeScoping.ConfigManager.Views.Dialogs.UnlockStoreDialog"
|
||||
x:DataType="vm:UnlockStoreDialogViewModel"
|
||||
Title="Unlock Secure Store"
|
||||
Width="500" Height="400"
|
||||
MinWidth="400" MinHeight="350"
|
||||
Background="#151920"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
CanResize="False"
|
||||
ShowInTaskbar="False">
|
||||
|
||||
<DockPanel>
|
||||
<!-- Header -->
|
||||
<Border DockPanel.Dock="Top" Background="#1C2128" Padding="24,16"
|
||||
BorderBrush="#2D3540" BorderThickness="0,0,0,1">
|
||||
<TextBlock Text="Unlock Secure Store"
|
||||
Foreground="#E6EDF5" FontSize="18" FontWeight="SemiBold"/>
|
||||
</Border>
|
||||
|
||||
<!-- Footer -->
|
||||
<Border DockPanel.Dock="Bottom" Background="#1C2128" Padding="24,16"
|
||||
BorderBrush="#2D3540" BorderThickness="0,1,0,0">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Spacing="8">
|
||||
<Button Content="Cancel" Click="CancelButton_Click"
|
||||
Background="Transparent" BorderBrush="#3D4550"
|
||||
Foreground="#9BA8B8" Padding="16,8" MinWidth="80"/>
|
||||
<Button Content="Unlock" Click="UnlockButton_Click"
|
||||
IsEnabled="{Binding IsValid}"
|
||||
Background="#5C9AFF" Foreground="#0D0F12"
|
||||
Padding="16,8" FontWeight="Medium" MinWidth="80"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Content -->
|
||||
<ScrollViewer Background="#151920" Padding="24,16">
|
||||
<StackPanel Spacing="16">
|
||||
<!-- Store Path (Read-Only) -->
|
||||
<Border Background="#1C2128" BorderBrush="#2D3540" BorderThickness="1"
|
||||
CornerRadius="4" Padding="16">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="Store File" Foreground="#E6EDF5"
|
||||
FontWeight="SemiBold" FontSize="14"/>
|
||||
<TextBox Text="{Binding StorePath}"
|
||||
IsReadOnly="True"
|
||||
Background="#0D0F12" Foreground="#9BA8B8"
|
||||
BorderBrush="#3D4550" Padding="8" Margin="0,4,0,0"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Decryption Method -->
|
||||
<Border Background="#1C2128" BorderBrush="#2D3540" BorderThickness="1"
|
||||
CornerRadius="4" Padding="16">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="Decryption Method" Foreground="#E6EDF5"
|
||||
FontWeight="SemiBold" FontSize="14"/>
|
||||
<RadioButton Content="Use Key File"
|
||||
IsChecked="{Binding UseKeyFile, Mode=TwoWay}"
|
||||
Foreground="#9BA8B8" Margin="0,4"/>
|
||||
<RadioButton Content="Use Password"
|
||||
IsChecked="{Binding UsePassword, Mode=TwoWay}"
|
||||
Foreground="#9BA8B8" Margin="0,4"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Key File Settings -->
|
||||
<Border Background="#1C2128" BorderBrush="#2D3540" BorderThickness="1"
|
||||
CornerRadius="4" Padding="16"
|
||||
IsVisible="{Binding UseKeyFile}">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="Key File" Foreground="#E6EDF5"
|
||||
FontWeight="SemiBold" FontSize="14"/>
|
||||
<Grid ColumnDefinitions="*,Auto" Margin="0,4,0,0">
|
||||
<TextBox Grid.Column="0"
|
||||
Text="{Binding KeyFilePath, Mode=TwoWay}"
|
||||
Watermark="Path to key file..."
|
||||
Background="#0D0F12" Foreground="#E6EDF5"
|
||||
BorderBrush="#3D4550" Padding="8"/>
|
||||
<Button Grid.Column="1" Content="Browse..."
|
||||
Command="{Binding BrowseKeyFilePathCommand}"
|
||||
Background="#2D3540" Foreground="#E6EDF5"
|
||||
Margin="8,0,0,0" Padding="12,8"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Password Settings -->
|
||||
<Border Background="#1C2128" BorderBrush="#2D3540" BorderThickness="1"
|
||||
CornerRadius="4" Padding="16"
|
||||
IsVisible="{Binding UsePassword}">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="Password" Foreground="#E6EDF5"
|
||||
FontWeight="SemiBold" FontSize="14"/>
|
||||
<Grid ColumnDefinitions="100,*" Margin="0,4,0,0">
|
||||
<TextBlock Text="Password:" Foreground="#9BA8B8"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBox Grid.Column="1"
|
||||
PasswordChar="*"
|
||||
Text="{Binding Password, Mode=TwoWay}"
|
||||
Background="#0D0F12" Foreground="#E6EDF5"
|
||||
BorderBrush="#3D4550" Padding="8" Margin="0,4"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Validation Error -->
|
||||
<TextBlock Text="{Binding ValidationError}"
|
||||
Foreground="#FF6B6B" FontSize="12"
|
||||
IsVisible="{Binding ValidationError, Converter={StaticResource StringToBool}}"
|
||||
Margin="0,4,0,0"/>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</DockPanel>
|
||||
</Window>
|
||||
@@ -0,0 +1,80 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Platform.Storage;
|
||||
using JdeScoping.ConfigManager.Constants;
|
||||
using JdeScoping.ConfigManager.ViewModels.Dialogs;
|
||||
using MsBox.Avalonia;
|
||||
using MsBox.Avalonia.Enums;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Views.Dialogs;
|
||||
|
||||
/// <summary>
|
||||
/// Dialog for unlocking an existing secure store.
|
||||
/// </summary>
|
||||
public partial class UnlockStoreDialog : Window
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the view model for this dialog.
|
||||
/// </summary>
|
||||
public UnlockStoreDialogViewModel ViewModel => (UnlockStoreDialogViewModel)DataContext!;
|
||||
|
||||
/// <summary>
|
||||
/// Design-time constructor. Required for XAML previewer.
|
||||
/// </summary>
|
||||
public UnlockStoreDialog() : this(string.Empty)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UnlockStoreDialog"/> class.
|
||||
/// </summary>
|
||||
/// <param name="storePath">The path to the store file to unlock.</param>
|
||||
public UnlockStoreDialog(string storePath)
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = new UnlockStoreDialogViewModel(storePath);
|
||||
Loaded += UnlockStoreDialog_Loaded;
|
||||
}
|
||||
|
||||
private void UnlockStoreDialog_Loaded(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel.OnShowOpenFileDialog += ShowOpenFileDialogAsync;
|
||||
}
|
||||
|
||||
private async Task<string?> ShowOpenFileDialogAsync(string title, string fileTypeName, string pattern)
|
||||
{
|
||||
var files = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
||||
{
|
||||
Title = title,
|
||||
AllowMultiple = false,
|
||||
FileTypeFilter = new[]
|
||||
{
|
||||
new FilePickerFileType(fileTypeName) { Patterns = new[] { pattern } },
|
||||
new FilePickerFileType(SecureStoreFileExtensions.AllFilesTypeName) { Patterns = new[] { SecureStoreFileExtensions.AllFilesPattern } }
|
||||
}
|
||||
});
|
||||
|
||||
return files.Count > 0 ? files[0].Path.LocalPath : null;
|
||||
}
|
||||
|
||||
private async void UnlockButton_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!ViewModel.IsValid)
|
||||
{
|
||||
var box = MessageBoxManager.GetMessageBoxStandard(
|
||||
SecureStoreStrings.ValidationErrorTitle,
|
||||
ViewModel.ValidationError ?? SecureStoreStrings.DefaultValidationError,
|
||||
ButtonEnum.Ok,
|
||||
MsBox.Avalonia.Enums.Icon.Warning);
|
||||
await box.ShowWindowDialogAsync(this);
|
||||
return;
|
||||
}
|
||||
|
||||
Close(true);
|
||||
}
|
||||
|
||||
private void CancelButton_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
Close(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:JdeScoping.ConfigManager.ViewModels.Forms"
|
||||
x:Class="JdeScoping.ConfigManager.Views.Forms.SecretFormView"
|
||||
x:DataType="vm:SecretFormViewModel">
|
||||
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Spacing="24" MaxWidth="600">
|
||||
<!-- Header -->
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Spacing="12">
|
||||
<PathIcon Data="M12 1L3 5V11C3 16.55 6.84 21.74 12 23C17.16 21.74 21 16.55 21 11V5L12 1M12 7C13.4 7 14.8 8.1 14.8 9.5V11C15.4 11 16 11.6 16 12.3V15.8C16 16.4 15.4 17 14.7 17H9.2C8.6 17 8 16.4 8 15.7V12.2C8 11.6 8.6 11 9.2 11V9.5C9.2 8.1 10.6 7 12 7M12 8.2C11.2 8.2 10.5 8.7 10.5 9.5V11H13.5V9.5C13.5 8.7 12.8 8.2 12 8.2Z"
|
||||
Width="24" Height="24" Foreground="#3B82F6"/>
|
||||
<TextBlock Text="Secret Settings"
|
||||
Foreground="#E6EDF5" FontSize="18" FontWeight="SemiBold"
|
||||
VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
<Border Height="1" Background="#2D3540" Margin="0,12,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Secret Details Card -->
|
||||
<Border Background="#0D0F12" BorderBrush="#2D3540" BorderThickness="1"
|
||||
CornerRadius="6" Padding="16">
|
||||
<StackPanel Spacing="16">
|
||||
<TextBlock Text="Secret Details" Foreground="#E6EDF5"
|
||||
FontWeight="SemiBold" FontSize="14"/>
|
||||
|
||||
<!-- Key (Read-only) -->
|
||||
<StackPanel Spacing="4">
|
||||
<TextBlock Text="Key"
|
||||
Foreground="#9BA8B8" FontSize="12" FontWeight="Medium"/>
|
||||
<TextBox Text="{Binding Key}"
|
||||
Background="#1A1F26" Foreground="#9BA8B8"
|
||||
BorderBrush="#2D3540" Height="36"
|
||||
FontFamily="JetBrains Mono"
|
||||
IsReadOnly="True"/>
|
||||
<TextBlock Text="The unique identifier for this secret (cannot be changed)"
|
||||
Foreground="#5C6A7A" FontSize="11"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Value with Show/Hide -->
|
||||
<StackPanel Spacing="4">
|
||||
<TextBlock Text="Value"
|
||||
Foreground="#9BA8B8" FontSize="12" FontWeight="Medium"/>
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<!-- Masked value display when hidden -->
|
||||
<TextBox Grid.Column="0"
|
||||
Text="{Binding Value}"
|
||||
Background="#232A35" Foreground="#E6EDF5"
|
||||
BorderBrush="#3D4550" Height="36"
|
||||
FontFamily="JetBrains Mono"
|
||||
PasswordChar="*"
|
||||
RevealPassword="{Binding IsValueVisible}"
|
||||
Watermark="Enter secret value..."/>
|
||||
<Button Grid.Column="1"
|
||||
Command="{Binding ToggleVisibilityCommand}"
|
||||
Background="#3D4550" Foreground="#E6EDF5"
|
||||
Padding="12,8" Margin="8,0,0,0"
|
||||
CornerRadius="4" Height="36"
|
||||
VerticalAlignment="Top">
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<!-- Eye icon for Show -->
|
||||
<PathIcon Data="M12 9C10.34 9 9 10.34 9 12C9 13.66 10.34 15 12 15C13.66 15 15 13.66 15 12C15 10.34 13.66 9 12 9M12 17C9.24 17 7 14.76 7 12C7 9.24 9.24 7 12 7C14.76 7 17 9.24 17 12C17 14.76 14.76 17 12 17M12 4.5C7 4.5 2.73 7.61 1 12C2.73 16.39 7 19.5 12 19.5C17 19.5 21.27 16.39 23 12C21.27 7.61 17 4.5 12 4.5Z"
|
||||
Width="14" Height="14" Foreground="#E6EDF5"
|
||||
IsVisible="{Binding !IsValueVisible}"/>
|
||||
<!-- Eye-off icon for Hide -->
|
||||
<PathIcon Data="M11.83 9L15 12.16V12C15 10.34 13.66 9 12 9H11.83M7.53 9.8L9.08 11.35C9.03 11.56 9 11.77 9 12C9 13.66 10.34 15 12 15C12.22 15 12.44 14.97 12.65 14.92L14.2 16.47C13.53 16.8 12.79 17 12 17C9.24 17 7 14.76 7 12C7 11.21 7.2 10.47 7.53 9.8M2 4.27L4.28 6.55L4.73 7C3.08 8.3 1.78 10 1 12C2.73 16.39 7 19.5 12 19.5C13.55 19.5 15.03 19.2 16.38 18.66L16.81 19.08L19.73 22L21 20.73L3.27 3M12 7C14.76 7 17 9.24 17 12C17 12.64 16.87 13.26 16.64 13.82L19.57 16.75C21.07 15.5 22.27 13.86 23 12C21.27 7.61 17 4.5 12 4.5C10.6 4.5 9.26 4.75 8 5.2L10.17 7.35C10.74 7.13 11.35 7 12 7Z"
|
||||
Width="14" Height="14" Foreground="#E6EDF5"
|
||||
IsVisible="{Binding IsValueVisible}"/>
|
||||
<TextBlock Text="{Binding VisibilityButtonText}" FontSize="12"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
<TextBlock Text="The secret value (encrypted at rest)"
|
||||
Foreground="#5C6A7A" FontSize="11"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Actions Card -->
|
||||
<Border Background="#0D0F12" BorderBrush="#2D3540" BorderThickness="1"
|
||||
CornerRadius="6" Padding="16">
|
||||
<StackPanel Spacing="16">
|
||||
<TextBlock Text="Actions" Foreground="#E6EDF5"
|
||||
FontWeight="SemiBold" FontSize="14"/>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="12">
|
||||
<!-- Copy to Clipboard Button -->
|
||||
<Button Command="{Binding CopyToClipboardCommand}"
|
||||
Background="#3B82F6" Foreground="#FFFFFF"
|
||||
Padding="16,10" CornerRadius="6">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<PathIcon Data="M19 21H8V7H19M19 5H8C6.9 5 6 5.9 6 7V21C6 22.1 6.9 23 8 23H19C20.1 23 21 22.1 21 21V7C21 5.9 20.1 5 19 5M16 1H4C2.9 1 2 1.9 2 3V17H4V3H16V1Z"
|
||||
Width="16" Height="16" Foreground="#FFFFFF"/>
|
||||
<TextBlock Text="Copy to Clipboard"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<!-- Delete Button -->
|
||||
<Button Command="{Binding DeleteCommand}"
|
||||
Background="#DC2626" Foreground="#FFFFFF"
|
||||
Padding="16,10" CornerRadius="6">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<PathIcon Data="M19 4H15.5L14.5 3H9.5L8.5 4H5V6H19M6 19C6 20.1 6.9 21 8 21H16C17.1 21 18 20.1 18 19V7H6V19Z"
|
||||
Width="16" Height="16" Foreground="#FFFFFF"/>
|
||||
<TextBlock Text="Delete"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="Warning: Deleting a secret cannot be undone if the store is saved."
|
||||
Foreground="#FF6B6B" FontSize="11" TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,11 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Views.Forms;
|
||||
|
||||
public partial class SecretFormView : UserControl
|
||||
{
|
||||
public SecretFormView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:JdeScoping.ConfigManager.ViewModels.Forms"
|
||||
x:Class="JdeScoping.ConfigManager.Views.Forms.SecureStoreLockedFormView"
|
||||
x:DataType="vm:SecureStoreLockedFormViewModel">
|
||||
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Spacing="24" MaxWidth="600">
|
||||
<!-- Header with Lock Icon -->
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Spacing="12">
|
||||
<PathIcon Data="M12 17C10.89 17 10 16.1 10 15C10 13.89 10.89 13 12 13C13.11 13 14 13.89 14 15C14 16.1 13.11 17 12 17M18 8C19.1 8 20 8.9 20 10V20C20 21.1 19.1 22 18 22H6C4.9 22 4 21.1 4 20V10C4 8.9 4.9 8 6 8H7V6C7 3.24 9.24 1 12 1C14.76 1 17 3.24 17 6V8H18M12 3C10.34 3 9 4.34 9 6V8H15V6C15 4.34 13.66 3 12 3Z"
|
||||
Width="24" Height="24" Foreground="#FFB84D"/>
|
||||
<TextBlock Text="{Binding StoreName}"
|
||||
Foreground="#E6EDF5" FontSize="18" FontWeight="SemiBold"
|
||||
VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
<Border Height="1" Background="#2D3540" Margin="0,12,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Lock Status Card -->
|
||||
<Border Background="#0D0F12" BorderBrush="#2D3540" BorderThickness="1"
|
||||
CornerRadius="6" Padding="16">
|
||||
<StackPanel Spacing="16">
|
||||
<!-- Status Message -->
|
||||
<StackPanel Orientation="Horizontal" Spacing="12" HorizontalAlignment="Center">
|
||||
<PathIcon Data="M12 17C10.89 17 10 16.1 10 15C10 13.89 10.89 13 12 13C13.11 13 14 13.89 14 15C14 16.1 13.11 17 12 17M18 8C19.1 8 20 8.9 20 10V20C20 21.1 19.1 22 18 22H6C4.9 22 4 21.1 4 20V10C4 8.9 4.9 8 6 8H7V6C7 3.24 9.24 1 12 1C14.76 1 17 3.24 17 6V8H18M12 3C10.34 3 9 4.34 9 6V8H15V6C15 4.34 13.66 3 12 3Z"
|
||||
Width="48" Height="48" Foreground="#5C6A7A"/>
|
||||
</StackPanel>
|
||||
<TextBlock Text="This store is locked"
|
||||
Foreground="#9BA8B8" FontSize="14" FontWeight="Medium"
|
||||
HorizontalAlignment="Center"/>
|
||||
<TextBlock Text="Enter your password to unlock and view secrets"
|
||||
Foreground="#5C6A7A" FontSize="12"
|
||||
HorizontalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Store Information Card -->
|
||||
<Border Background="#0D0F12" BorderBrush="#2D3540" BorderThickness="1"
|
||||
CornerRadius="6" Padding="16">
|
||||
<StackPanel Spacing="16">
|
||||
<TextBlock Text="Store Information" Foreground="#E6EDF5"
|
||||
FontWeight="SemiBold" FontSize="14"/>
|
||||
|
||||
<!-- Store Path -->
|
||||
<StackPanel Spacing="4">
|
||||
<TextBlock Text="Store Path"
|
||||
Foreground="#9BA8B8" FontSize="12" FontWeight="Medium"/>
|
||||
<TextBox Text="{Binding StorePath}"
|
||||
Background="#232A35" Foreground="#E6EDF5"
|
||||
BorderBrush="#3D4550" Height="36"
|
||||
FontFamily="JetBrains Mono" FontSize="11"
|
||||
IsReadOnly="True"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Last Modified -->
|
||||
<StackPanel Spacing="4" IsVisible="{Binding LastModified, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||
<TextBlock Text="Last Modified"
|
||||
Foreground="#9BA8B8" FontSize="12" FontWeight="Medium"/>
|
||||
<TextBlock Text="{Binding LastModified, StringFormat='{}{0:yyyy-MM-dd HH:mm:ss}'}"
|
||||
Foreground="#E6EDF5" FontSize="13"
|
||||
FontFamily="JetBrains Mono"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Unlock Button -->
|
||||
<Button Command="{Binding UnlockCommand}"
|
||||
HorizontalAlignment="Center"
|
||||
Background="#3B82F6" Foreground="#FFFFFF"
|
||||
Padding="24,12" CornerRadius="6"
|
||||
FontWeight="SemiBold">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<PathIcon Data="M18 8C19.1 8 20 8.9 20 10V20C20 21.1 19.1 22 18 22H6C4.9 22 4 21.1 4 20V10C4 8.9 4.9 8 6 8H17V6C17 3.24 14.76 1 12 1C10.07 1 8.39 2.11 7.53 3.71L6.14 2.43C7.34 0.88 9.54 0 12 0C15.87 0 19 3.13 19 7V8H18M12 17C13.11 17 14 16.1 14 15C14 13.89 13.11 13 12 13C10.89 13 10 13.89 10 15C10 16.1 10.89 17 12 17Z"
|
||||
Width="16" Height="16" Foreground="#FFFFFF"/>
|
||||
<TextBlock Text="Unlock Store..."/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,11 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Views.Forms;
|
||||
|
||||
public partial class SecureStoreLockedFormView : UserControl
|
||||
{
|
||||
public SecureStoreLockedFormView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:JdeScoping.ConfigManager.ViewModels.Forms"
|
||||
x:Class="JdeScoping.ConfigManager.Views.Forms.SecureStoreUnlockedFormView"
|
||||
x:DataType="vm:SecureStoreUnlockedFormViewModel">
|
||||
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Spacing="24" MaxWidth="600">
|
||||
<!-- Header with Unlock Icon -->
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Spacing="12">
|
||||
<PathIcon Data="M18 8C19.1 8 20 8.9 20 10V20C20 21.1 19.1 22 18 22H6C4.9 22 4 21.1 4 20V10C4 8.9 4.9 8 6 8H17V6C17 3.24 14.76 1 12 1C10.07 1 8.39 2.11 7.53 3.71L5.71 2.39C6.83 0.92 9.15 0 12 0C15.87 0 19 3.13 19 7V8H18M12 17C13.11 17 14 16.1 14 15C14 13.89 13.11 13 12 13C10.89 13 10 13.89 10 15C10 16.1 10.89 17 12 17Z"
|
||||
Width="24" Height="24" Foreground="#22C55E"/>
|
||||
<TextBlock Text="{Binding StoreName}"
|
||||
Foreground="#E6EDF5" FontSize="18" FontWeight="SemiBold"
|
||||
VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
<Border Height="1" Background="#2D3540" Margin="0,12,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Store Status Card -->
|
||||
<Border Background="#0D0F12" BorderBrush="#2D3540" BorderThickness="1"
|
||||
CornerRadius="6" Padding="16">
|
||||
<StackPanel Spacing="16">
|
||||
<TextBlock Text="Store Status" Foreground="#E6EDF5"
|
||||
FontWeight="SemiBold" FontSize="14"/>
|
||||
|
||||
<!-- Store Path -->
|
||||
<StackPanel Spacing="4">
|
||||
<TextBlock Text="Store Path"
|
||||
Foreground="#9BA8B8" FontSize="12" FontWeight="Medium"/>
|
||||
<TextBox Text="{Binding StorePath}"
|
||||
Background="#232A35" Foreground="#E6EDF5"
|
||||
BorderBrush="#3D4550" Height="36"
|
||||
FontFamily="JetBrains Mono" FontSize="11"
|
||||
IsReadOnly="True"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Secret Count -->
|
||||
<StackPanel Spacing="4">
|
||||
<TextBlock Text="Secrets"
|
||||
Foreground="#9BA8B8" FontSize="12" FontWeight="Medium"/>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock Text="{Binding SecretCount}"
|
||||
Foreground="#E6EDF5" FontSize="24" FontWeight="Bold"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBlock Text="secret(s) stored"
|
||||
Foreground="#5C6A7A" FontSize="13"
|
||||
VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Unsaved Changes Status -->
|
||||
<Border Background="#151920" BorderBrush="#2D3540" BorderThickness="1"
|
||||
CornerRadius="4" Padding="12">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<!-- Status indicator when no unsaved changes -->
|
||||
<PathIcon Data="M12 2C6.48 2 2 6.48 2 12C2 17.52 6.48 22 12 22C17.52 22 22 17.52 22 12C22 6.48 17.52 2 12 2M10 17L5 12L6.41 10.59L10 14.17L17.59 6.58L19 8L10 17Z"
|
||||
Width="20" Height="20" Foreground="#22C55E"
|
||||
IsVisible="{Binding !HasUnsavedChanges}"/>
|
||||
<TextBlock Text="All changes saved"
|
||||
Foreground="#22C55E" FontSize="13" FontWeight="Medium"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding !HasUnsavedChanges}"/>
|
||||
|
||||
<!-- Status indicator when there are unsaved changes -->
|
||||
<PathIcon Data="M12 2C6.48 2 2 6.48 2 12C2 17.52 6.48 22 12 22C17.52 22 22 17.52 22 12C22 6.48 17.52 2 12 2M13 17H11V15H13V17M13 13H11V7H13V13Z"
|
||||
Width="20" Height="20" Foreground="#FFB84D"
|
||||
IsVisible="{Binding HasUnsavedChanges}"/>
|
||||
<TextBlock Text="Unsaved changes"
|
||||
Foreground="#FFB84D" FontSize="13" FontWeight="Medium"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding HasUnsavedChanges}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Actions Card -->
|
||||
<Border Background="#0D0F12" BorderBrush="#2D3540" BorderThickness="1"
|
||||
CornerRadius="6" Padding="16">
|
||||
<StackPanel Spacing="16">
|
||||
<TextBlock Text="Actions" Foreground="#E6EDF5"
|
||||
FontWeight="SemiBold" FontSize="14"/>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="12">
|
||||
<!-- Lock Store Button -->
|
||||
<Button Command="{Binding LockCommand}"
|
||||
Background="#3D4550" Foreground="#E6EDF5"
|
||||
Padding="16,10" CornerRadius="6">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<PathIcon Data="M12 17C10.89 17 10 16.1 10 15C10 13.89 10.89 13 12 13C13.11 13 14 13.89 14 15C14 16.1 13.11 17 12 17M18 8C19.1 8 20 8.9 20 10V20C20 21.1 19.1 22 18 22H6C4.9 22 4 21.1 4 20V10C4 8.9 4.9 8 6 8H7V6C7 3.24 9.24 1 12 1C14.76 1 17 3.24 17 6V8H18M12 3C10.34 3 9 4.34 9 6V8H15V6C15 4.34 13.66 3 12 3Z"
|
||||
Width="16" Height="16" Foreground="#E6EDF5"/>
|
||||
<TextBlock Text="Lock Store"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<!-- Add Secret Button -->
|
||||
<Button Command="{Binding AddSecretCommand}"
|
||||
Background="#22C55E" Foreground="#FFFFFF"
|
||||
Padding="16,10" CornerRadius="6">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<PathIcon Data="M19 13H13V19H11V13H5V11H11V5H13V11H19V13Z"
|
||||
Width="16" Height="16" Foreground="#FFFFFF"/>
|
||||
<TextBlock Text="Add Secret"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<!-- Save Button -->
|
||||
<Button Command="{Binding SaveCommand}"
|
||||
Background="#3B82F6" Foreground="#FFFFFF"
|
||||
Padding="16,10" CornerRadius="6"
|
||||
IsEnabled="{Binding HasUnsavedChanges}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<PathIcon Data="M15 9H5V5H15M12 19C10.34 19 9 17.66 9 16C9 14.34 10.34 13 12 13C13.66 13 15 14.34 15 16C15 17.66 13.66 19 12 19M17 3H5C3.89 3 3 3.9 3 5V19C3 20.1 3.89 21 5 21H19C20.1 21 21 20.1 21 19V7L17 3Z"
|
||||
Width="16" Height="16" Foreground="#FFFFFF"/>
|
||||
<TextBlock Text="Save"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Views.Forms;
|
||||
|
||||
public partial class SecureStoreUnlockedFormView : UserControl
|
||||
{
|
||||
public SecureStoreUnlockedFormView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,15 @@
|
||||
<DataTemplate DataType="{x:Type forms:PipelineFormViewModel}">
|
||||
<views:PipelineFormView/>
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="{x:Type forms:SecureStoreLockedFormViewModel}">
|
||||
<views:SecureStoreLockedFormView/>
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="{x:Type forms:SecureStoreUnlockedFormViewModel}">
|
||||
<views:SecureStoreUnlockedFormView/>
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="{x:Type forms:SecretFormViewModel}">
|
||||
<views:SecretFormView/>
|
||||
</DataTemplate>
|
||||
</Window.DataTemplates>
|
||||
|
||||
<DockPanel>
|
||||
@@ -57,6 +66,35 @@
|
||||
<Separator/>
|
||||
<MenuItem Header="View _Backups..."/>
|
||||
</MenuItem>
|
||||
<MenuItem Header="_Secure Stores">
|
||||
<MenuItem Header="_New Store..." Command="{Binding NewStoreCommand}" InputGesture="Ctrl+Shift+N">
|
||||
<MenuItem.Icon>
|
||||
<TextBlock Text="+" FontSize="14" FontWeight="Bold"/>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="_Add Existing Store..." Command="{Binding AddExistingStoreCommand}" InputGesture="Ctrl+Shift+O">
|
||||
<MenuItem.Icon>
|
||||
<TextBlock Text="..." FontSize="14"/>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator/>
|
||||
<MenuItem Header="_Save Store" Command="{Binding SaveStoreCommand}" InputGesture="Ctrl+Shift+S">
|
||||
<MenuItem.Icon>
|
||||
<TextBlock Text="S" FontSize="12" FontWeight="Bold"/>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="_Lock All Stores" Command="{Binding LockAllStoresCommand}">
|
||||
<MenuItem.Icon>
|
||||
<TextBlock Text="L" FontSize="12" FontWeight="Bold"/>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator/>
|
||||
<MenuItem Header="_Generate Key File..." Command="{Binding GenerateKeyFileCommand}">
|
||||
<MenuItem.Icon>
|
||||
<TextBlock Text="K" FontSize="12" FontWeight="Bold"/>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
</MenuItem>
|
||||
<MenuItem Header="_Help">
|
||||
<MenuItem Header="_About ConfigManager"/>
|
||||
</MenuItem>
|
||||
@@ -74,6 +112,9 @@
|
||||
<Border Width="1" Height="20" Background="#2D3540" Margin="4,0"/>
|
||||
<Button Content="Test" Command="{Binding TestConnectionCommand}" Classes="toolbar"/>
|
||||
<Button Content="Validate" Command="{Binding ValidateCommand}" Classes="toolbar"/>
|
||||
<Border Width="1" Height="20" Background="#2D3540" Margin="4,0"/>
|
||||
<Button Content="Unlock" Command="{Binding UnlockStoreCommand}" ToolTip.Tip="Unlock/Lock Store" Classes="toolbar"/>
|
||||
<Button Content="+ Secret" Command="{Binding AddSecretCommand}" ToolTip.Tip="Add Secret" Classes="toolbar"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
@@ -123,6 +164,17 @@
|
||||
SelectedItem="{Binding SelectedNode}"
|
||||
Background="Transparent"
|
||||
Margin="8">
|
||||
<TreeView.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="Unlock Store..." Command="{Binding UnlockStoreCommand}"/>
|
||||
<MenuItem Header="Lock Store" Command="{Binding LockStoreCommand}"/>
|
||||
<Separator/>
|
||||
<MenuItem Header="Add Secret..." Command="{Binding AddSecretCommand}"/>
|
||||
<MenuItem Header="Delete Secret" Command="{Binding DeleteSecretCommand}"/>
|
||||
<Separator/>
|
||||
<MenuItem Header="Save Store" Command="{Binding SaveStoreCommand}"/>
|
||||
</ContextMenu>
|
||||
</TreeView.ContextMenu>
|
||||
<TreeView.ItemTemplate>
|
||||
<TreeDataTemplate ItemsSource="{Binding Children}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
|
||||
Reference in New Issue
Block a user