Files

277 lines
8.8 KiB
C#
Raw Permalink Normal View History

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;
/// <summary>
/// ViewModel for managing SSH host connections.
/// Allows adding, editing, testing, and removing remote Docker Swarm hosts.
/// </summary>
public partial class HostsViewModel : ObservableObject
{
private readonly IServiceProvider _services;
[ObservableProperty] private ObservableCollection<SshHost> _hosts = new();
[ObservableProperty] private SshHost? _selectedHost;
[ObservableProperty] private bool _isEditing;
[ObservableProperty] private string _statusMessage = string.Empty;
[ObservableProperty] private bool _isBusy;
[ObservableProperty] private ObservableCollection<NodeInfo> _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<XiboContext>();
var hosts = await db.SshHosts.OrderBy(h => h.Label).ToListAsync();
Hosts = new ObservableCollection<SshHost>(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<XiboContext>();
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<XiboContext>();
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<SshConnectionService>();
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<SshDockerCliService>();
dockerCli.SetHost(SelectedHost);
var nodes = await dockerCli.ListNodesAsync();
RemoteNodes = new ObservableCollection<NodeInfo>(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<SshConnectionService>();
var (success, message) = await ssh.TestConnectionAsync(SelectedHost);
// Update DB
using var scope = _services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<XiboContext>();
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;
}
}
}