v0.5: settings dialog, persistent Serilog logs, Windows toasts

Settings (⚙ button in top bar)
------------------------------
SettingsDialog opens via OpenSettings command in MainViewModel. Sections:
- Serveur: server URL field + "Tester" button that GETs /api/health
  and reports status inline.
- Installation: installRoot path with Browse button
  (Microsoft.Win32.OpenFolderDialog, .NET 8 native).
- License: shows status / owner / exp / machine ID (read-only,
  copy-to-clipboard button), "Désactiver" wipes the cached license.
- Cache: shows download cache path + current size, opens or empties it.
- Logs & Application: launcher version + logs path with "Open" button.

Apply persists to LocalConfig via IConfigStore. After save, MainViewModel
RebuildList()s so changes to ServerBaseUrl or InstallRoot take effect
without restart.

Persistent Serilog logs
-----------------------
Logs now live in %LocalAppData%/PSLauncher/logs/app-YYYYMMDD.log, rolling
daily, 10 days kept. Output template includes source context and stack
traces. Generic Host wired up via .UseSerilog() so all
ILogger<T>-injected types share the sink. Unhandled AppDomain and
Dispatcher exceptions are routed to Serilog before propagation.

Windows toasts
--------------
IToastService + ToastService backed by Microsoft.Toolkit.Uwp.Notifications
(ToastContentBuilder.Show()). Required bumping the App TFM from
net8.0-windows to net8.0-windows10.0.17763.0 (with explicit
WindowsSdkPackageVersion=10.0.17763.41 to satisfy CommunityToolkit.Mvvm
8.3.2's MVVMTKCFG0003 check). Triggered on:
- successful install completion: "Proserve v{X} est prête à être lancée"
- install/download error: short error excerpt

Misc
----
- InverseBoolConverter for "disable button while busy" patterns.
- Added Markdig.Wpf import to ReleaseNotesViewerDialog (was implicit
  before, now required explicitly).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-01 11:35:22 +02:00
parent 1770def4d0
commit eeff3c007b
11 changed files with 597 additions and 5 deletions

View File

@@ -6,6 +6,8 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.Extensions.Logging;
using PSLauncher.App.Views;
using Microsoft.Extensions.DependencyInjection;
using PSLauncher.App.Services;
using PSLauncher.Core.Configuration;
using PSLauncher.Core.Downloads;
using PSLauncher.Core.Installations;
@@ -28,6 +30,8 @@ public sealed partial class MainViewModel : ObservableObject
private readonly IDownloadManager _downloadManager;
private readonly IZipInstaller _zipInstaller;
private readonly ILicenseService _licenseService;
private readonly IToastService _toastService;
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<MainViewModel> _logger;
private LicenseValidationResponse? _license;
@@ -101,6 +105,8 @@ public sealed partial class MainViewModel : ObservableObject
IDownloadManager downloadManager,
IZipInstaller zipInstaller,
ILicenseService licenseService,
IToastService toastService,
IServiceProvider serviceProvider,
ILogger<MainViewModel> logger)
{
_registry = registry;
@@ -112,6 +118,8 @@ public sealed partial class MainViewModel : ObservableObject
_downloadManager = downloadManager;
_zipInstaller = zipInstaller;
_licenseService = licenseService;
_toastService = toastService;
_serviceProvider = serviceProvider;
_logger = logger;
// Charge la license depuis le cache (pas d'appel réseau au démarrage,
@@ -226,6 +234,20 @@ public sealed partial class MainViewModel : ObservableObject
}
private bool CanCheckUpdates() => !IsBusy;
[RelayCommand]
private void OpenSettings()
{
var vm = _serviceProvider.GetRequiredService<SettingsViewModel>();
var dialog = new Views.SettingsDialog(vm) { Owner = Application.Current.MainWindow };
if (dialog.ShowDialog() == true)
{
// Au retour, l'URL serveur ou l'installRoot ont pu changer → refresh
_license = _licenseService.GetCached();
OnPropertyChanged(nameof(LicenseSummary));
RebuildList();
}
}
[RelayCommand]
private async Task ActivateLicenseAsync()
{
@@ -339,6 +361,9 @@ public sealed partial class MainViewModel : ObservableObject
ProgressDetail = null;
ProgressPercent = 0;
RebuildList();
_toastService.ShowSuccess(
"Installation terminée",
$"Proserve v{row.Version} est prête à être lancée.");
}
catch (OperationCanceledException)
{
@@ -357,6 +382,7 @@ public sealed partial class MainViewModel : ObservableObject
$"Le téléchargement ou l'installation a échoué :\n\n{ex.Message}",
"Erreur", MessageBoxButton.OK, MessageBoxImage.Error);
StatusMessage = $"Erreur : {ex.Message}";
_toastService.ShowError($"Échec v{row.Version}", ex.Message);
}
finally
{