Files
dashcaddy/desktop/ViewModels/MainViewModel.cs
Hermes 86e4c9fc81 wip: Windows desktop app scaffold (WinUI 3/.NET 8) + NSIS installer + docs
Owner decision pending (STATE.md DC-100 tick): adopt/ship/park.
Preserved from fragile git stash to named branch 2026-08-23.
NOTE: requires a Windows build machine (WinUI XAML compiler + MSIX do not
cross-build on Linux) — see WINDOWS_APP_BUILD.md in this tree.
Secret-scanned clean 2026-08-23 (no keys/tokens/PEM in tree).
2026-08-22 23:50:34 -07:00

166 lines
5.6 KiB
C#

using CommunityToolkit.Mvvm.ComponentModel;
using DashCaddy.Desktop.Models;
using DashCaddy.Desktop.Services;
using System.Collections.ObjectModel;
using System.Collections.Generic;
namespace DashCaddy.Desktop.ViewModels;
public partial class MainViewModel : ObservableObject
{
private readonly DockerService _dockerService;
private readonly ApiClient _apiClient;
private readonly CaddyConfigGenerator _caddyGenerator;
private readonly DnsClient _dnsClient;
[ObservableProperty]
private ObservableCollection<ServiceViewModel> _services = new();
[ObservableProperty]
private int _servicesHealthy;
[ObservableProperty]
private int _servicesTotal;
[ObservableProperty]
private bool _dnsHealthy = true;
[ObservableProperty]
private bool _certsHealthy = true;
[ObservableProperty]
private int _certsDaysRemaining = 45;
// For dialogs
public ObservableCollection<ServiceTemplate> Templates { get; } = new(TemplateRegistry.GetAll());
public List<VariableViewModel> TemplateVariables { get; set; } = new();
public Dictionary<string, string> TemplateVariablesDict { get; set; } = new();
public ServiceTemplate SelectedTemplate { get; set; }
public string SelectedComposeFile { get; set; }
public object CustomService { get; set; }
public string ServicesSummary => $"{ServicesHealthy}/{ServicesTotal} running";
public string CertsSummary => $"{CertsDaysRemaining} days";
public string DashboardUrl => "https://status.local";
public MainViewModel(DockerService dockerService, ApiClient apiClient, CaddyConfigGenerator caddyGenerator, DnsClient dnsClient)
{
_dockerService = dockerService;
_apiClient = apiClient;
_caddyGenerator = caddyGenerator;
_dnsClient = dnsClient;
}
public async Task InitializeAsync()
{
await RefreshServicesAsync();
await CheckHealthAsync();
}
public async Task RefreshServicesAsync()
{
try
{
var services = await _apiClient.GetServicesAsync();
Services.Clear();
foreach (var svc in services)
{
Services.Add(new ServiceViewModel
{
Id = svc.Id,
Name = svc.Name,
Type = svc.Type,
Url = svc.Url,
Health = Enum.TryParse<ServiceHealth>(svc.Health, true, out var h) ? h : ServiceHealth.Unknown,
IsRunning = svc.State == "running",
Port = svc.Port,
Host = svc.Host
});
}
ServicesHealthy = Services.Count(s => s.Health == ServiceHealth.Healthy);
ServicesTotal = Services.Count;
}
catch
{
await RefreshFromDockerAsync();
}
}
private async Task RefreshFromDockerAsync()
{
var containers = await _dockerService.GetContainersAsync("dashcaddy");
Services.Clear();
foreach (var c in containers)
{
Services.Add(new ServiceViewModel
{
Id = c.ID[..12],
Name = c.Names.FirstOrDefault()?.TrimStart('/') ?? "unknown",
Type = "docker",
Url = $"http://localhost:{c.Ports.FirstOrDefault()?.PublicPort ?? 0}",
Health = c.State == "running" ? ServiceHealth.Healthy : ServiceHealth.Unhealthy,
IsRunning = c.State == "running"
});
}
ServicesHealthy = Services.Count(s => s.Health == ServiceHealth.Healthy);
ServicesTotal = Services.Count;
}
public async Task CheckHealthAsync()
{
try
{
var health = await _apiClient.GetHealthAsync();
DnsHealthy = health.Dns == "healthy";
CertsHealthy = health.Certs == "healthy";
CertsDaysRemaining = health.CertsDaysRemaining;
}
catch
{
DnsHealthy = false;
CertsHealthy = false;
}
}
public async Task StartServiceAsync(string id) => await _dockerService.StartContainerAsync(id);
public async Task StopServiceAsync(string id) => await _dockerService.StopContainerAsync(id);
public async Task RestartServiceAsync(string id) => await _dockerService.RestartContainerAsync(id);
public async Task RemoveServiceAsync(string id)
{
await _dockerService.RemoveContainerAsync(id);
await _apiClient.RemoveServiceAsync(id);
_caddyGenerator.RegenerateConfig(Services.Select(s => s.ToServiceModel()).ToList());
await _caddyGenerator.ReloadCaddyAsync();
}
public async Task ImportComposeAsync(string filePath)
{
var json = await File.ReadAllTextAsync(filePath);
var services = ComposeParser.ParseJson(json);
foreach (var svc in services)
{
await _apiClient.CreateServiceAsync(svc);
await _dockerService.CreateContainerAsync(svc);
}
_caddyGenerator.RegenerateConfig(Services.Select(s => s.ToServiceModel()).ToList());
await _caddyGenerator.ReloadCaddyAsync();
}
public async Task<ServiceModel> CreateServiceFromTemplateAsync()
{
if (SelectedTemplate == null) return null;
var service = SelectedTemplate.Instantiate(TemplateVariablesDict);
await _apiClient.CreateServiceAsync(service);
await _dockerService.CreateContainerAsync(service);
_caddyGenerator.RegenerateConfig(Services.Select(s => s.ToServiceModel()).ToList());
await _caddyGenerator.ReloadCaddyAsync();
return service;
}
}