feat(app): add host, docker, and health check view models

This commit is contained in:
dualfroz committed 2026-08-26 16:30:39 +02:00
1 parent 2648a9a0a6
commit 419316efef
5 files changed
+229

No files matched your search

@@ -0,0 +1,30 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Vantage.Core.Models;
namespace Vantage.App.ViewModels;
public partial class DockerContainerViewModel : ViewModelBase
{
[ObservableProperty] private DockerContainer container;
public DockerContainerViewModel(DockerContainer container)
{
this.container = container;
}
public string Id => Container.Id;
public string Name => Container.Name;
public string Image => Container.Image;
public string StatusText => Container.StatusText;
public bool IsRunning => Container.IsRunning;
public ContainerState State => Container.State;
partial void OnContainerChanged(DockerContainer value)
{
OnPropertyChanged(nameof(Name));
OnPropertyChanged(nameof(Image));
OnPropertyChanged(nameof(StatusText));
OnPropertyChanged(nameof(IsRunning));
OnPropertyChanged(nameof(State));
}
}
@@ -0,0 +1,87 @@
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Vantage.Core.Docker;
using Vantage.Core.Models;
namespace Vantage.App.ViewModels;
public partial class DockerViewModel : ViewModelBase
{
private readonly IDockerProvider dockerProvider;
[ObservableProperty] private bool isBusy;
[ObservableProperty] private string? lastError;
[ObservableProperty] private DockerContainerViewModel? selectedContainer;
public DockerViewModel(IDockerProvider dockerProvider)
{
this.dockerProvider = dockerProvider;
}
public ObservableCollection<DockerContainerViewModel> Containers { get; } = new();
public HostConfig? Host { get; private set; }
public void SetHost(HostConfig? host)
{
Host = host;
Containers.Clear();
}
[RelayCommand]
public async Task RefreshAsync(CancellationToken cancellationToken)
{
if (Host is null)
return;
IsBusy = true;
LastError = null;
try
{
var containers = await dockerProvider.ListContainersAsync(Host, cancellationToken).ConfigureAwait(false);
Containers.Clear();
foreach (var container in containers)
Containers.Add(new DockerContainerViewModel(container));
}
catch (Exception ex)
{
LastError = ex.Message;
}
finally
{
IsBusy = false;
}
}
[RelayCommand]
private Task StartAsync(DockerContainerViewModel? container) => ExecuteAsync(DockerAction.Start, container);
[RelayCommand]
private Task StopAsync(DockerContainerViewModel? container) => ExecuteAsync(DockerAction.Stop, container);
[RelayCommand]
private Task RestartAsync(DockerContainerViewModel? container) => ExecuteAsync(DockerAction.Restart, container);
private async Task ExecuteAsync(DockerAction action, DockerContainerViewModel? container)
{
if (Host is null || container is null)
return;
IsBusy = true;
LastError = null;
try
{
await dockerProvider.ExecuteAsync(Host, action, container.Id, CancellationToken.None).ConfigureAwait(false);
await RefreshAsync(CancellationToken.None).ConfigureAwait(false);
}
catch (Exception ex)
{
LastError = ex.Message;
}
finally
{
IsBusy = false;
}
}
}
@@ -0,0 +1,41 @@
using System.Globalization;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Vantage.Core.Health;
using Vantage.Core.Models;
namespace Vantage.App.ViewModels;
public partial class HealthCheckViewModel : ViewModelBase
{
private readonly IHealthChecker checker;
[ObservableProperty] private HealthStatus status = HealthStatus.Unknown;
[ObservableProperty] private string statusText = "Not checked";
[ObservableProperty] private string latencyText = "-";
[ObservableProperty] private string lastCheckedText = "never";
public HealthCheckViewModel(HealthCheckConfig config, IHealthChecker checker)
{
Config = config;
this.checker = checker;
}
public HealthCheckConfig Config { get; }
public string Name => string.IsNullOrWhiteSpace(Config.Name) ? Config.Target : Config.Name;
public string Target => Config.Target;
public string KindText => Config.Kind.ToString().ToUpperInvariant();
[RelayCommand]
public async Task RunAsync(CancellationToken cancellationToken)
{
var checkResult = await checker.CheckAsync(Config, cancellationToken).ConfigureAwait(false);
Status = checkResult.Status;
StatusText = checkResult.Status + (string.IsNullOrEmpty(checkResult.Message) ? "" : $" - {checkResult.Message}");
LatencyText = checkResult.LatencyMs is { } ms
? ms.ToString("0", CultureInfo.InvariantCulture) + " ms"
: "-";
LastCheckedText = checkResult.CheckedAt.ToLocalTime().ToString("HH:mm:ss", CultureInfo.InvariantCulture);
}
}
@@ -0,0 +1,64 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Vantage.Core.Metrics;
using Vantage.Core.Models;
namespace Vantage.App.ViewModels;
public partial class HostViewModel : ViewModelBase
{
private readonly MetricsHistory cpuHistory;
private readonly MetricsHistory memoryHistory;
[ObservableProperty] private double cpuUsagePercent;
[ObservableProperty] private double memoryUsedPercent;
[ObservableProperty] private double diskUsedPercent;
[ObservableProperty] private string memoryText = "-";
[ObservableProperty] private string diskText = "-";
[ObservableProperty] private string loadText = "-";
[ObservableProperty] private string uptimeText = "-";
[ObservableProperty] private string statusText = "Not polled yet";
[ObservableProperty] private bool isOnline;
[ObservableProperty] private string? lastError;
public HostViewModel(HostConfig config, int historyLength)
{
Config = config;
cpuHistory = new MetricsHistory(historyLength);
memoryHistory = new MetricsHistory(historyLength);
}
public HostConfig Config { get; }
public string Name => string.IsNullOrWhiteSpace(Config.Name) ? Config.Address : Config.Name;
public string Endpoint => $"{Config.Username}@{Config.Address}:{Config.Port}";
public IReadOnlyList<double> CpuPoints => cpuHistory.Snapshot();
public IReadOnlyList<double> MemoryPoints => memoryHistory.Snapshot();
public void ApplyMetrics(HostMetrics metrics)
{
CpuUsagePercent = metrics.CpuUsagePercent;
MemoryUsedPercent = metrics.MemoryUsedPercent;
DiskUsedPercent = metrics.DiskUsedPercent;
MemoryText = ValueFormatting.BytesPair(metrics.MemoryUsedBytes, metrics.MemoryTotalBytes);
DiskText = ValueFormatting.BytesPair(metrics.DiskUsedBytes, metrics.DiskTotalBytes);
LoadText = $"{metrics.Load1:0.00} {metrics.Load5:0.00} {metrics.Load15:0.00}";
UptimeText = ValueFormatting.Uptime(metrics.Uptime);
cpuHistory.Add(metrics.CpuUsagePercent);
memoryHistory.Add(metrics.MemoryUsedPercent);
OnPropertyChanged(nameof(CpuPoints));
OnPropertyChanged(nameof(MemoryPoints));
IsOnline = true;
LastError = null;
StatusText = "Online";
}
public void ApplyError(string message)
{
IsOnline = false;
LastError = message;
StatusText = "Unreachable";
}
}
@@ -0,0 +1,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace Vantage.App.ViewModels;
public abstract class ViewModelBase : ObservableObject
{
}