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

@@ -1,9 +1,11 @@
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Windows;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using PSLauncher.App.Services;
using PSLauncher.App.ViewModels;
using PSLauncher.App.Views;
using PSLauncher.Core.Configuration;
@@ -15,6 +17,7 @@ using PSLauncher.Core.Manifests;
using PSLauncher.Core.Process;
using PSLauncher.Core.Updates;
using PSLauncher.Models;
using Serilog;
namespace PSLauncher.App;
@@ -22,18 +25,46 @@ public partial class App : Application
{
private IHost? _host;
public static string LogsDirectory { get; private set; } = string.Empty;
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
// Logs avant tout — pour ne rien perdre des erreurs au boot
LogsDirectory = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"PSLauncher", "logs");
Directory.CreateDirectory(LogsDirectory);
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.Enrich.FromLogContext()
.WriteTo.Debug()
.WriteTo.File(
path: Path.Combine(LogsDirectory, "app-.log"),
rollingInterval: RollingInterval.Day,
retainedFileCountLimit: 10,
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] {SourceContext} {Message:lj}{NewLine}{Exception}")
.CreateLogger();
Log.Information("PSLauncher starting (logs in {Path})", LogsDirectory);
AppDomain.CurrentDomain.UnhandledException += (_, args) =>
Log.Fatal((Exception)args.ExceptionObject, "Unhandled exception (AppDomain)");
DispatcherUnhandledException += (_, args) =>
{
Log.Error(args.Exception, "Unhandled UI exception");
args.Handled = false;
};
_host = Host.CreateDefaultBuilder()
.ConfigureLogging(b => { b.ClearProviders(); b.AddDebug(); })
.UseSerilog()
.ConfigureServices((_, services) =>
{
services.AddSingleton<IConfigStore, ConfigStore>();
services.AddSingleton<LocalConfig>(sp => sp.GetRequiredService<IConfigStore>().Load());
// HttpClient partagé (timeout long pour les gros téléchargements)
services.AddSingleton(sp =>
{
var http = new HttpClient(new SocketsHttpHandler
@@ -44,7 +75,7 @@ public partial class App : Application
{
Timeout = Timeout.InfiniteTimeSpan
};
http.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("PSLauncher", "0.2"));
http.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("PSLauncher", "0.5"));
return http;
});
@@ -75,6 +106,9 @@ public partial class App : Application
sp.GetRequiredService<LocalConfig>(),
sp.GetRequiredService<ILogger<LicenseService>>()));
services.AddSingleton<IToastService, ToastService>();
services.AddTransient<SettingsViewModel>();
services.AddSingleton<MainViewModel>();
services.AddSingleton<MainWindow>();
})
@@ -88,6 +122,8 @@ public partial class App : Application
protected override void OnExit(ExitEventArgs e)
{
Log.Information("PSLauncher shutting down");
Log.CloseAndFlush();
_host?.Dispose();
base.OnExit(e);
}