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;
}
}