using System.Collections.ObjectModel; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using OTSSignsOrchestrator.Core.Data; using OTSSignsOrchestrator.Core.Models.DTOs; using OTSSignsOrchestrator.Core.Models.Entities; using OTSSignsOrchestrator.Desktop.Services; namespace OTSSignsOrchestrator.Desktop.ViewModels; /// /// ViewModel for managing SSH host connections. /// Allows adding, editing, testing, and removing remote Docker Swarm hosts. /// public partial class HostsViewModel : ObservableObject { private readonly IServiceProvider _services; [ObservableProperty] private ObservableCollection _hosts = new(); [ObservableProperty] private SshHost? _selectedHost; [ObservableProperty] private bool _isEditing; [ObservableProperty] private string _statusMessage = string.Empty; [ObservableProperty] private bool _isBusy; [ObservableProperty] private ObservableCollection _remoteNodes = new(); [ObservableProperty] private string _nodesStatusMessage = string.Empty; // Edit form fields [ObservableProperty] private string _editLabel = string.Empty; [ObservableProperty] private string _editHost = string.Empty; [ObservableProperty] private int _editPort = 22; [ObservableProperty] private string _editUsername = string.Empty; [ObservableProperty] private string _editPrivateKeyPath = string.Empty; [ObservableProperty] private string _editKeyPassphrase = string.Empty; [ObservableProperty] private string _editPassword = string.Empty; [ObservableProperty] private bool _editUseKeyAuth = true; private Guid? _editingHostId; public HostsViewModel(IServiceProvider services) { _services = services; _ = LoadHostsAsync(); } [RelayCommand] private async Task LoadHostsAsync() { IsBusy = true; try { using var scope = _services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); var hosts = await db.SshHosts.OrderBy(h => h.Label).ToListAsync(); Hosts = new ObservableCollection(hosts); StatusMessage = $"Loaded {hosts.Count} host(s)."; } catch (Exception ex) { StatusMessage = $"Error loading hosts: {ex.Message}"; } finally { IsBusy = false; } } [RelayCommand] private void NewHost() { _editingHostId = null; EditLabel = string.Empty; EditHost = string.Empty; EditPort = 22; EditUsername = string.Empty; EditPrivateKeyPath = string.Empty; EditKeyPassphrase = string.Empty; EditPassword = string.Empty; EditUseKeyAuth = true; IsEditing = true; } [RelayCommand] private void EditSelectedHost() { if (SelectedHost == null) return; _editingHostId = SelectedHost.Id; EditLabel = SelectedHost.Label; EditHost = SelectedHost.Host; EditPort = SelectedHost.Port; EditUsername = SelectedHost.Username; EditPrivateKeyPath = SelectedHost.PrivateKeyPath ?? string.Empty; EditKeyPassphrase = string.Empty; // Don't show existing passphrase EditPassword = string.Empty; // Don't show existing password EditUseKeyAuth = SelectedHost.UseKeyAuth; IsEditing = true; } [RelayCommand] private void CancelEdit() { IsEditing = false; } [RelayCommand] private async Task SaveHostAsync() { if (string.IsNullOrWhiteSpace(EditLabel) || string.IsNullOrWhiteSpace(EditHost) || string.IsNullOrWhiteSpace(EditUsername)) { StatusMessage = "Label, Host, and Username are required."; return; } IsBusy = true; try { using var scope = _services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); SshHost host; if (_editingHostId.HasValue) { host = await db.SshHosts.FindAsync(_editingHostId.Value) ?? throw new KeyNotFoundException("Host not found."); host.Label = EditLabel; host.Host = EditHost; host.Port = EditPort; host.Username = EditUsername; host.PrivateKeyPath = string.IsNullOrWhiteSpace(EditPrivateKeyPath) ? null : EditPrivateKeyPath; host.UseKeyAuth = EditUseKeyAuth; host.UpdatedAt = DateTime.UtcNow; if (!string.IsNullOrEmpty(EditKeyPassphrase)) host.KeyPassphrase = EditKeyPassphrase; if (!string.IsNullOrEmpty(EditPassword)) host.Password = EditPassword; } else { host = new SshHost { Label = EditLabel, Host = EditHost, Port = EditPort, Username = EditUsername, PrivateKeyPath = string.IsNullOrWhiteSpace(EditPrivateKeyPath) ? null : EditPrivateKeyPath, KeyPassphrase = string.IsNullOrEmpty(EditKeyPassphrase) ? null : EditKeyPassphrase, Password = string.IsNullOrEmpty(EditPassword) ? null : EditPassword, UseKeyAuth = EditUseKeyAuth }; db.SshHosts.Add(host); } await db.SaveChangesAsync(); IsEditing = false; StatusMessage = $"Host '{host.Label}' saved."; await LoadHostsAsync(); } catch (Exception ex) { StatusMessage = $"Error saving host: {ex.Message}"; } finally { IsBusy = false; } } [RelayCommand] private async Task DeleteHostAsync() { if (SelectedHost == null) return; IsBusy = true; try { using var scope = _services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); var host = await db.SshHosts.FindAsync(SelectedHost.Id); if (host != null) { db.SshHosts.Remove(host); await db.SaveChangesAsync(); } // Disconnect if connected var ssh = _services.GetRequiredService(); ssh.Disconnect(SelectedHost.Id); StatusMessage = $"Host '{SelectedHost.Label}' deleted."; await LoadHostsAsync(); } catch (Exception ex) { StatusMessage = $"Error deleting host: {ex.Message}"; } finally { IsBusy = false; } } [RelayCommand] private async Task ListNodesAsync() { if (SelectedHost == null) { NodesStatusMessage = "Select a host first."; return; } IsBusy = true; NodesStatusMessage = $"Listing nodes on {SelectedHost.Label}..."; try { var dockerCli = _services.GetRequiredService(); dockerCli.SetHost(SelectedHost); var nodes = await dockerCli.ListNodesAsync(); RemoteNodes = new ObservableCollection(nodes); NodesStatusMessage = $"Found {nodes.Count} node(s) on {SelectedHost.Label}."; } catch (Exception ex) { RemoteNodes.Clear(); NodesStatusMessage = $"Error: {ex.Message}"; } finally { IsBusy = false; } } [RelayCommand] private async Task TestConnectionAsync() { if (SelectedHost == null) return; IsBusy = true; StatusMessage = $"Testing connection to {SelectedHost.Label}..."; try { var ssh = _services.GetRequiredService(); var (success, message) = await ssh.TestConnectionAsync(SelectedHost); // Update DB using var scope = _services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); var host = await db.SshHosts.FindAsync(SelectedHost.Id); if (host != null) { host.LastTestedAt = DateTime.UtcNow; host.LastTestSuccess = success; await db.SaveChangesAsync(); } StatusMessage = success ? $"✓ {SelectedHost.Label}: {message}" : $"✗ {SelectedHost.Label}: {message}"; await LoadHostsAsync(); } catch (Exception ex) { StatusMessage = $"Connection test error: {ex.Message}"; } finally { IsBusy = false; } } }