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).
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user