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,229 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using DashCaddy.Desktop.ViewModels;
|
||||
using DashCaddy.Desktop.Models;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DashCaddy.Desktop;
|
||||
|
||||
public sealed partial class MainWindow : Window
|
||||
{
|
||||
public MainViewModel ViewModel => App.MainViewModel;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += MainWindow_Loaded;
|
||||
}
|
||||
|
||||
private async void MainWindow_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await ViewModel.InitializeAsync();
|
||||
}
|
||||
|
||||
// ─── Toolbar Actions ───
|
||||
|
||||
private async void AddService_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var dialog = new AddServiceDialog(ViewModel);
|
||||
dialog.XamlRoot = this.Content.XamlRoot;
|
||||
var result = await dialog.ShowAsync();
|
||||
if (result == ContentDialogResult.Primary)
|
||||
{
|
||||
await ViewModel.RefreshServicesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async void ImportCompose_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var picker = new Windows.Storage.Pickers.FileOpenPicker();
|
||||
picker.FileTypeFilter.Add(".yaml");
|
||||
picker.FileTypeFilter.Add(".yml");
|
||||
picker.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.DocumentsLibrary;
|
||||
|
||||
var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
|
||||
WinRT.Interop.InitializeWithWindow.Initialize(picker, hwnd);
|
||||
|
||||
var file = await picker.PickSingleFileAsync();
|
||||
if (file != null)
|
||||
{
|
||||
ShowLoading("Importing Docker Compose...");
|
||||
try
|
||||
{
|
||||
await ViewModel.ImportComposeAsync(file.Path);
|
||||
await ViewModel.RefreshServicesAsync();
|
||||
}
|
||||
finally
|
||||
{
|
||||
HideLoading();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Templates_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var dialog = new TemplatesDialog(ViewModel);
|
||||
dialog.XamlRoot = this.Content.XamlRoot;
|
||||
_ = dialog.ShowAsync();
|
||||
}
|
||||
|
||||
// ─── Service Actions ───
|
||||
|
||||
private async void ServiceAction_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// Handled by Flyout menu items
|
||||
}
|
||||
|
||||
private async void OpenService_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is MenuFlyoutItem item && item.DataContext is ServiceViewModel service)
|
||||
{
|
||||
await Windows.System.Launcher.LaunchUriAsync(new System.Uri(service.Url));
|
||||
}
|
||||
}
|
||||
|
||||
private async void ViewLogs_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ServiceViewModel service = null;
|
||||
if (sender is MenuFlyoutItem item && item.DataContext is ServiceViewModel svc)
|
||||
{
|
||||
service = svc;
|
||||
}
|
||||
else if (sender is Button)
|
||||
{
|
||||
// "View Logs" bottom button - show all logs
|
||||
var dialog = new LogsDialog(ViewModel);
|
||||
dialog.XamlRoot = this.Content.XamlRoot;
|
||||
await dialog.ShowAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
if (service != null)
|
||||
{
|
||||
var dialog = new ServiceLogsDialog(service, ViewModel.DockerService);
|
||||
dialog.XamlRoot = this.Content.XamlRoot;
|
||||
await dialog.ShowAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async void RestartService_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is MenuFlyoutItem item && item.DataContext is ServiceViewModel service)
|
||||
{
|
||||
ShowLoading($"Restarting {service.Name}...");
|
||||
try
|
||||
{
|
||||
await ViewModel.RestartServiceAsync(service.Id);
|
||||
await ViewModel.RefreshServicesAsync();
|
||||
}
|
||||
finally
|
||||
{
|
||||
HideLoading();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void StopService_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is MenuFlyoutItem item && item.DataContext is ServiceViewModel service)
|
||||
{
|
||||
await ViewModel.StopServiceAsync(service.Id);
|
||||
await ViewModel.RefreshServicesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async void EditService_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is MenuFlyoutItem item && item.DataContext is ServiceViewModel service)
|
||||
{
|
||||
var dialog = new EditServiceDialog(service, ViewModel);
|
||||
dialog.XamlRoot = this.Content.XamlRoot;
|
||||
var result = await dialog.ShowAsync();
|
||||
if (result == ContentDialogResult.Primary)
|
||||
{
|
||||
await ViewModel.RefreshServicesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void RemoveService_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is MenuFlyoutItem item && item.DataContext is ServiceViewModel service)
|
||||
{
|
||||
var dialog = new ContentDialog
|
||||
{
|
||||
Title = "Remove Service",
|
||||
Content = $"Are you sure you want to remove '{service.Name}'? This will stop the container and remove the reverse proxy configuration.",
|
||||
PrimaryButtonText = "Remove",
|
||||
CloseButtonText = "Cancel",
|
||||
DefaultButton = ContentDialogButton.Close,
|
||||
XamlRoot = this.Content.XamlRoot
|
||||
};
|
||||
var result = await dialog.ShowAsync();
|
||||
if (result == ContentDialogResult.Primary)
|
||||
{
|
||||
ShowLoading($"Removing {service.Name}...");
|
||||
try
|
||||
{
|
||||
await ViewModel.RemoveServiceAsync(service.Id);
|
||||
await ViewModel.RefreshServicesAsync();
|
||||
}
|
||||
finally
|
||||
{
|
||||
HideLoading();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void ServiceToggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is ToggleSwitch toggle && toggle.Tag is ServiceViewModel service)
|
||||
{
|
||||
if (toggle.IsOn)
|
||||
{
|
||||
ShowLoading($"Starting {service.Name}...");
|
||||
await ViewModel.StartServiceAsync(service.Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowLoading($"Stopping {service.Name}...");
|
||||
await ViewModel.StopServiceAsync(service.Id);
|
||||
}
|
||||
await ViewModel.RefreshServicesAsync();
|
||||
HideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Bottom Bar ───
|
||||
|
||||
private async void OpenDashboard_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await Windows.System.Launcher.LaunchUriAsync(new System.Uri(ViewModel.DashboardUrl));
|
||||
}
|
||||
|
||||
private async void OpenSettings_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var dialog = new SettingsDialog(ViewModel);
|
||||
dialog.XamlRoot = this.Content.XamlRoot;
|
||||
await dialog.ShowAsync();
|
||||
}
|
||||
|
||||
private async void OpenHelp_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await Windows.System.Launcher.LaunchUriAsync(new System.Uri("https://dashcaddy.net/docs"));
|
||||
}
|
||||
|
||||
// ─── Loading Overlay ───
|
||||
|
||||
private void ShowLoading(string message)
|
||||
{
|
||||
LoadingText.Text = message;
|
||||
LoadingOverlay.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
private void HideLoading()
|
||||
{
|
||||
LoadingOverlay.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user