feat(configmanager): add MainWindowViewModel with service wiring
Complete MainWindowViewModel implementation with: - Constructor injection for IConfigFileService, IValidationService, IBackupService, IAutoDiscoveryService, and ILogger - Properties: ConfigFolderPath, HasUnsavedChanges, ValidationStatus, ValidationStatusColor, SelectedNode, SelectedFormViewModel, TreeNodes - Commands: OpenFolderCommand, SaveCommand, ExitCommand, UndoCommand, RedoCommand, ValidateCommand, TestConnectionCommand - Methods: InitializeAsync, OpenFolderAsync, LoadConfigAsync, BuildTreeNodes, OnSelectedNodeChanged, SaveAsync, Validate, TestConnectionAsync Update App.axaml.cs to register all services in DI container: - IFileSystem -> FileSystem (singleton) - IAutoDiscoveryService -> AutoDiscoveryService (singleton) - IBackupService -> BackupService (singleton) - IDiffService -> DiffService (singleton) - IValidationService -> ValidationService (singleton) - IConfigFileService -> ConfigFileService (scoped) - MainWindowViewModel (transient) Wire MainWindow.DataContext to resolved MainWindowViewModel.
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using JdeScoping.ConfigManager.Services;
|
||||
using JdeScoping.ConfigManager.ViewModels;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
@@ -8,13 +10,22 @@ namespace JdeScoping.ConfigManager;
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the dependency injection service provider for the application.
|
||||
/// </summary>
|
||||
public static IServiceProvider Services { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the Avalonia XAML loader.
|
||||
/// </summary>
|
||||
public override void Initialize()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the framework initialization is complete; configures services and sets the main window.
|
||||
/// </summary>
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
@@ -23,7 +34,10 @@ public partial class App : Application
|
||||
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
desktop.MainWindow = new Views.MainWindow();
|
||||
desktop.MainWindow = new Views.MainWindow
|
||||
{
|
||||
DataContext = Services.GetRequiredService<MainWindowViewModel>()
|
||||
};
|
||||
}
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
@@ -31,8 +45,22 @@ public partial class App : Application
|
||||
|
||||
private void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
// Logging
|
||||
services.AddLogging(builder => builder
|
||||
.AddConsole()
|
||||
.SetMinimumLevel(LogLevel.Debug));
|
||||
|
||||
// Services - File system abstraction
|
||||
services.AddSingleton<IFileSystem, FileSystem>();
|
||||
|
||||
// Services - Configuration management
|
||||
services.AddSingleton<IAutoDiscoveryService, AutoDiscoveryService>();
|
||||
services.AddSingleton<IBackupService, BackupService>();
|
||||
services.AddSingleton<IDiffService, DiffService>();
|
||||
services.AddSingleton<IValidationService, ValidationService>();
|
||||
services.AddScoped<IConfigFileService, ConfigFileService>();
|
||||
|
||||
// ViewModels
|
||||
services.AddTransient<MainWindowViewModel>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Windows.Input;
|
||||
using Avalonia.Media;
|
||||
using JdeScoping.ConfigManager.Models;
|
||||
using JdeScoping.ConfigManager.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace JdeScoping.ConfigManager.ViewModels;
|
||||
|
||||
/// <summary>
|
||||
/// Main window view model.
|
||||
/// This is a stub implementation for Task 11 - full implementation in Task 13.
|
||||
/// </summary>
|
||||
public class MainWindowViewModel : ViewModelBase
|
||||
{
|
||||
private readonly IConfigFileService _configFileService;
|
||||
private readonly IValidationService _validationService;
|
||||
private readonly IBackupService _backupService;
|
||||
private readonly IAutoDiscoveryService _autoDiscoveryService;
|
||||
private readonly ILogger<MainWindowViewModel>? _logger;
|
||||
|
||||
private string _configFolderPath = "No folder selected";
|
||||
private bool _hasUnsavedChanges;
|
||||
private string _validationStatus = "Valid";
|
||||
@@ -17,6 +25,9 @@ public class MainWindowViewModel : ViewModelBase
|
||||
private TreeNodeViewModel? _selectedNode;
|
||||
private object? _selectedFormViewModel;
|
||||
|
||||
private ConfigModel? _appSettings;
|
||||
private PipelinesConfigModel? _pipelines;
|
||||
|
||||
public string ConfigFolderPath
|
||||
{
|
||||
get => _configFolderPath;
|
||||
@@ -44,7 +55,11 @@ public class MainWindowViewModel : ViewModelBase
|
||||
public TreeNodeViewModel? SelectedNode
|
||||
{
|
||||
get => _selectedNode;
|
||||
set => SetProperty(ref _selectedNode, value);
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _selectedNode, value))
|
||||
OnSelectedNodeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public object? SelectedFormViewModel
|
||||
@@ -63,15 +78,219 @@ public class MainWindowViewModel : ViewModelBase
|
||||
public ICommand ValidateCommand { get; }
|
||||
public ICommand TestConnectionCommand { get; }
|
||||
|
||||
public MainWindowViewModel()
|
||||
public MainWindowViewModel(
|
||||
IConfigFileService configFileService,
|
||||
IValidationService validationService,
|
||||
IBackupService backupService,
|
||||
IAutoDiscoveryService autoDiscoveryService,
|
||||
ILogger<MainWindowViewModel>? logger)
|
||||
{
|
||||
// Stub commands - full implementation in Task 13
|
||||
OpenFolderCommand = new RelayCommand(() => { });
|
||||
SaveCommand = new RelayCommand(() => { });
|
||||
ExitCommand = new RelayCommand(() => { });
|
||||
UndoCommand = new RelayCommand(() => { });
|
||||
RedoCommand = new RelayCommand(() => { });
|
||||
ValidateCommand = new RelayCommand(() => { });
|
||||
TestConnectionCommand = new RelayCommand(() => { });
|
||||
_configFileService = configFileService;
|
||||
_validationService = validationService;
|
||||
_backupService = backupService;
|
||||
_autoDiscoveryService = autoDiscoveryService;
|
||||
_logger = logger;
|
||||
|
||||
OpenFolderCommand = new AsyncRelayCommand(OpenFolderAsync);
|
||||
SaveCommand = new AsyncRelayCommand(SaveAsync, () => HasUnsavedChanges);
|
||||
ExitCommand = new RelayCommand(() => Environment.Exit(0));
|
||||
UndoCommand = new RelayCommand(() => { }, () => false); // TODO: Implement undo/redo
|
||||
RedoCommand = new RelayCommand(() => { }, () => false); // TODO: Implement undo/redo
|
||||
ValidateCommand = new RelayCommand(Validate);
|
||||
TestConnectionCommand = new AsyncRelayCommand(TestConnectionAsync);
|
||||
|
||||
_ = InitializeAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Design-time constructor for XAML previewer.
|
||||
/// </summary>
|
||||
public MainWindowViewModel() : this(
|
||||
new ConfigFileService(new FileSystem()),
|
||||
new ValidationService(),
|
||||
new BackupService(new FileSystem()),
|
||||
new AutoDiscoveryService(new FileSystem()),
|
||||
null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the view model by auto-discovering and loading configuration.
|
||||
/// </summary>
|
||||
private async Task InitializeAsync()
|
||||
{
|
||||
var folder = await _autoDiscoveryService.FindConfigFolderAsync();
|
||||
if (folder != null)
|
||||
{
|
||||
await LoadConfigAsync(folder);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a folder picker dialog to select a configuration folder.
|
||||
/// </summary>
|
||||
private async Task OpenFolderAsync()
|
||||
{
|
||||
// TODO: Show folder picker dialog
|
||||
_logger?.LogInformation("Open folder requested");
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads configuration files from the specified folder.
|
||||
/// </summary>
|
||||
/// <param name="folderPath">Path to the configuration folder.</param>
|
||||
private async Task LoadConfigAsync(string folderPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
ConfigFolderPath = folderPath;
|
||||
|
||||
var appSettingsPath = Path.Combine(folderPath, "appsettings.json");
|
||||
var pipelinesPath = Path.Combine(folderPath, "Pipelines", "pipelines.json");
|
||||
|
||||
_appSettings = await _configFileService.LoadAppSettingsAsync(appSettingsPath);
|
||||
|
||||
if (File.Exists(pipelinesPath))
|
||||
{
|
||||
_pipelines = await _configFileService.LoadPipelinesAsync(pipelinesPath);
|
||||
}
|
||||
|
||||
BuildTreeNodes();
|
||||
Validate();
|
||||
|
||||
_logger?.LogInformation("Loaded configuration from {Path}", folderPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogError(ex, "Failed to load configuration from {Path}", folderPath);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the tree nodes representing the configuration structure.
|
||||
/// </summary>
|
||||
private void BuildTreeNodes()
|
||||
{
|
||||
TreeNodes.Clear();
|
||||
|
||||
// Settings folder
|
||||
var settingsFolder = new TreeNodeViewModel("Settings", "gear", TreeNodeType.Folder) { IsExpanded = true };
|
||||
settingsFolder.Children.Add(new TreeNodeViewModel("DataSync", "sync", TreeNodeType.SettingsSection) { SectionKey = "DataSync" });
|
||||
settingsFolder.Children.Add(new TreeNodeViewModel("DataAccess", "database", TreeNodeType.SettingsSection) { SectionKey = "DataAccess" });
|
||||
settingsFolder.Children.Add(new TreeNodeViewModel("Auth", "lock", TreeNodeType.SettingsSection) { SectionKey = "Auth" });
|
||||
settingsFolder.Children.Add(new TreeNodeViewModel("Ldap", "users", TreeNodeType.SettingsSection) { SectionKey = "Ldap" });
|
||||
settingsFolder.Children.Add(new TreeNodeViewModel("Search", "search", TreeNodeType.SettingsSection) { SectionKey = "Search" });
|
||||
settingsFolder.Children.Add(new TreeNodeViewModel("ExcelExport", "file-spreadsheet", TreeNodeType.SettingsSection) { SectionKey = "ExcelExport" });
|
||||
TreeNodes.Add(settingsFolder);
|
||||
|
||||
// Pipelines folder
|
||||
var pipelinesFolder = new TreeNodeViewModel("Pipelines", "workflow", TreeNodeType.Folder) { IsExpanded = true };
|
||||
if (_pipelines != null)
|
||||
{
|
||||
foreach (var (name, _) in _pipelines.Pipelines)
|
||||
{
|
||||
pipelinesFolder.Children.Add(new TreeNodeViewModel(name, "zap", TreeNodeType.Pipeline) { SectionKey = name });
|
||||
}
|
||||
}
|
||||
TreeNodes.Add(pipelinesFolder);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the selected tree node changes; updates the form view model.
|
||||
/// </summary>
|
||||
private void OnSelectedNodeChanged()
|
||||
{
|
||||
// TODO: Load appropriate form ViewModel based on selected node
|
||||
SelectedFormViewModel = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves all configuration changes to disk.
|
||||
/// </summary>
|
||||
private async Task SaveAsync()
|
||||
{
|
||||
if (_appSettings == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
var appSettingsPath = Path.Combine(ConfigFolderPath, "appsettings.json");
|
||||
|
||||
// Create backup before saving
|
||||
if (File.Exists(appSettingsPath))
|
||||
{
|
||||
await _backupService.CreateBackupAsync(appSettingsPath);
|
||||
}
|
||||
|
||||
// Save appsettings
|
||||
await _configFileService.SaveAppSettingsAsync(appSettingsPath, _appSettings);
|
||||
|
||||
// Save pipelines if loaded
|
||||
if (_pipelines != null)
|
||||
{
|
||||
var pipelinesPath = Path.Combine(ConfigFolderPath, "Pipelines", "pipelines.json");
|
||||
if (File.Exists(pipelinesPath))
|
||||
{
|
||||
await _backupService.CreateBackupAsync(pipelinesPath);
|
||||
}
|
||||
await _configFileService.SavePipelinesAsync(pipelinesPath, _pipelines);
|
||||
}
|
||||
|
||||
HasUnsavedChanges = false;
|
||||
_logger?.LogInformation("Configuration saved");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogError(ex, "Failed to save configuration");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the current configuration and updates the status bar.
|
||||
/// </summary>
|
||||
private void Validate()
|
||||
{
|
||||
var errors = 0;
|
||||
var warnings = 0;
|
||||
|
||||
if (_appSettings != null)
|
||||
{
|
||||
var result = _validationService.ValidateAppSettings(_appSettings);
|
||||
errors += result.Errors.Count;
|
||||
warnings += result.Warnings.Count;
|
||||
}
|
||||
|
||||
if (_pipelines != null)
|
||||
{
|
||||
var result = _validationService.ValidatePipelines(_pipelines);
|
||||
errors += result.Errors.Count;
|
||||
warnings += result.Warnings.Count;
|
||||
}
|
||||
|
||||
if (errors > 0)
|
||||
{
|
||||
ValidationStatus = $"Errors: {errors}, Warnings: {warnings}";
|
||||
ValidationStatusColor = new SolidColorBrush(Color.Parse("#FF6B6B"));
|
||||
}
|
||||
else if (warnings > 0)
|
||||
{
|
||||
ValidationStatus = $"Warnings: {warnings}";
|
||||
ValidationStatusColor = new SolidColorBrush(Color.Parse("#FFB84D"));
|
||||
}
|
||||
else
|
||||
{
|
||||
ValidationStatus = "Valid";
|
||||
ValidationStatusColor = new SolidColorBrush(Color.Parse("#3DD68C"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests database connections defined in the configuration.
|
||||
/// </summary>
|
||||
private async Task TestConnectionAsync()
|
||||
{
|
||||
// TODO: Implement connection testing
|
||||
_logger?.LogInformation("Test connection requested");
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user