diff --git a/NEW/src/Utils/JdeScoping.ConfigManager/App.axaml.cs b/NEW/src/Utils/JdeScoping.ConfigManager/App.axaml.cs index 153b6f1..6ea16f7 100644 --- a/NEW/src/Utils/JdeScoping.ConfigManager/App.axaml.cs +++ b/NEW/src/Utils/JdeScoping.ConfigManager/App.axaml.cs @@ -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 { + /// + /// Gets the dependency injection service provider for the application. + /// public static IServiceProvider Services { get; private set; } = null!; + /// + /// Initializes the Avalonia XAML loader. + /// public override void Initialize() { AvaloniaXamlLoader.Load(this); } + /// + /// Called when the framework initialization is complete; configures services and sets the main window. + /// 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() + }; } 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(); + + // Services - Configuration management + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddScoped(); + + // ViewModels + services.AddTransient(); } } diff --git a/NEW/src/Utils/JdeScoping.ConfigManager/ViewModels/MainWindowViewModel.cs b/NEW/src/Utils/JdeScoping.ConfigManager/ViewModels/MainWindowViewModel.cs index b28fc71..8365dba 100644 --- a/NEW/src/Utils/JdeScoping.ConfigManager/ViewModels/MainWindowViewModel.cs +++ b/NEW/src/Utils/JdeScoping.ConfigManager/ViewModels/MainWindowViewModel.cs @@ -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; /// /// Main window view model. -/// This is a stub implementation for Task 11 - full implementation in Task 13. /// public class MainWindowViewModel : ViewModelBase { + private readonly IConfigFileService _configFileService; + private readonly IValidationService _validationService; + private readonly IBackupService _backupService; + private readonly IAutoDiscoveryService _autoDiscoveryService; + private readonly ILogger? _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? 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(); + } + + /// + /// Design-time constructor for XAML previewer. + /// + public MainWindowViewModel() : this( + new ConfigFileService(new FileSystem()), + new ValidationService(), + new BackupService(new FileSystem()), + new AutoDiscoveryService(new FileSystem()), + null) + { + } + + /// + /// Initializes the view model by auto-discovering and loading configuration. + /// + private async Task InitializeAsync() + { + var folder = await _autoDiscoveryService.FindConfigFolderAsync(); + if (folder != null) + { + await LoadConfigAsync(folder); + } + } + + /// + /// Opens a folder picker dialog to select a configuration folder. + /// + private async Task OpenFolderAsync() + { + // TODO: Show folder picker dialog + _logger?.LogInformation("Open folder requested"); + await Task.CompletedTask; + } + + /// + /// Loads configuration files from the specified folder. + /// + /// Path to the configuration folder. + 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); + } + } + + /// + /// Builds the tree nodes representing the configuration structure. + /// + 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); + } + + /// + /// Called when the selected tree node changes; updates the form view model. + /// + private void OnSelectedNodeChanged() + { + // TODO: Load appropriate form ViewModel based on selected node + SelectedFormViewModel = null; + } + + /// + /// Saves all configuration changes to disk. + /// + 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"); + } + } + + /// + /// Validates the current configuration and updates the status bar. + /// + 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")); + } + } + + /// + /// Tests database connections defined in the configuration. + /// + private async Task TestConnectionAsync() + { + // TODO: Implement connection testing + _logger?.LogInformation("Test connection requested"); + await Task.CompletedTask; } }