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

@@ -175,7 +175,11 @@
VerticalAlignment="Center" Margin="0,0,8,0" />
<Button Style="{StaticResource SecondaryButton}"
Content="🔑 Activer / changer"
Command="{Binding ActivateLicenseCommand}" />
Command="{Binding ActivateLicenseCommand}"
Margin="0,0,8,0" />
<Button Style="{StaticResource SecondaryButton}"
Content="⚙ Paramètres"
Command="{Binding OpenSettingsCommand}" />
</StackPanel>
</Grid>
</Border>

View File

@@ -0,0 +1,232 @@
<Window x:Class="PSLauncher.App.Views.SettingsDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:PSLauncher.App.ViewModels"
Title="Paramètres"
Width="640" Height="720"
MinWidth="540" MinHeight="500"
WindowStartupLocation="CenterOwner"
Background="{StaticResource Brush.Bg.Window}"
d:DataContext="{d:DesignInstance Type=vm:SettingsViewModel}"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- Header -->
<Border Grid.Row="0" Padding="32,24,32,16">
<TextBlock Text="Paramètres" FontSize="22" FontWeight="SemiBold"
Foreground="{StaticResource Brush.Text.Primary}" />
</Border>
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto" Padding="32,0,32,16">
<StackPanel>
<!-- Serveur -->
<Border Background="{StaticResource Brush.Bg.Card}"
BorderBrush="{StaticResource Brush.Border}" BorderThickness="1"
CornerRadius="6" Padding="20" Margin="0,0,0,16">
<StackPanel>
<TextBlock Text="SERVEUR"
FontSize="11" FontWeight="Bold"
Foreground="{StaticResource Brush.Text.Secondary}"
Margin="0,0,0,8" />
<TextBlock Text="URL de l'API"
Foreground="{StaticResource Brush.Text.Secondary}"
FontSize="12" Margin="0,0,0,4" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0"
Text="{Binding ServerBaseUrl, UpdateSourceTrigger=PropertyChanged}"
Background="#1A1A20"
Foreground="{StaticResource Brush.Text.Primary}"
BorderBrush="{StaticResource Brush.Border}"
BorderThickness="1" Padding="8" />
<Button Grid.Column="1" Margin="8,0,0,0"
Style="{StaticResource SecondaryButton}"
Content="Tester"
Command="{Binding TestConnectionCommand}"
IsEnabled="{Binding IsTestingConnection, Converter={StaticResource InverseBoolToBool}}" />
</Grid>
<TextBlock Text="{Binding ConnectionStatus}"
Foreground="{StaticResource Brush.Text.Secondary}"
FontSize="12" Margin="0,8,0,0" TextWrapping="Wrap" />
</StackPanel>
</Border>
<!-- Installation -->
<Border Background="{StaticResource Brush.Bg.Card}"
BorderBrush="{StaticResource Brush.Border}" BorderThickness="1"
CornerRadius="6" Padding="20" Margin="0,0,0,16">
<StackPanel>
<TextBlock Text="INSTALLATION"
FontSize="11" FontWeight="Bold"
Foreground="{StaticResource Brush.Text.Secondary}"
Margin="0,0,0,8" />
<TextBlock Text="Dossier où sont installées les versions"
Foreground="{StaticResource Brush.Text.Secondary}"
FontSize="12" Margin="0,0,0,4" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0"
Text="{Binding InstallRoot, UpdateSourceTrigger=PropertyChanged}"
Background="#1A1A20"
Foreground="{StaticResource Brush.Text.Primary}"
BorderBrush="{StaticResource Brush.Border}"
BorderThickness="1" Padding="8"
FontFamily="Consolas" />
<Button Grid.Column="1" Margin="8,0,0,0"
Style="{StaticResource SecondaryButton}"
Content="📁 Parcourir"
Command="{Binding BrowseInstallRootCommand}" />
</Grid>
</StackPanel>
</Border>
<!-- License -->
<Border Background="{StaticResource Brush.Bg.Card}"
BorderBrush="{StaticResource Brush.Border}" BorderThickness="1"
CornerRadius="6" Padding="20" Margin="0,0,0,16">
<StackPanel>
<TextBlock Text="LICENSE"
FontSize="11" FontWeight="Bold"
Foreground="{StaticResource Brush.Text.Secondary}"
Margin="0,0,0,8" />
<TextBlock Text="{Binding LicenseInfo}"
Foreground="{StaticResource Brush.Text.Primary}"
FontSize="13" />
<Grid Margin="0,12,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Text="Identifiant machine (anonyme)"
FontSize="12"
Foreground="{StaticResource Brush.Text.Secondary}" />
<TextBlock Text="{Binding MachineId}"
FontFamily="Consolas" FontSize="11"
Foreground="{StaticResource Brush.Text.Primary}"
Margin="0,2,0,0" TextTrimming="CharacterEllipsis" />
</StackPanel>
<Button Grid.Column="1"
Style="{StaticResource SecondaryButton}"
Content="📋 Copier"
Command="{Binding CopyMachineIdCommand}" />
</Grid>
<Button Style="{StaticResource SecondaryButton}"
Content="Désactiver la license sur cette machine"
Command="{Binding DeactivateLicenseCommand}"
Margin="0,12,0,0" HorizontalAlignment="Left" />
</StackPanel>
</Border>
<!-- Cache -->
<Border Background="{StaticResource Brush.Bg.Card}"
BorderBrush="{StaticResource Brush.Border}" BorderThickness="1"
CornerRadius="6" Padding="20" Margin="0,0,0,16">
<StackPanel>
<TextBlock Text="CACHE DE TÉLÉCHARGEMENTS"
FontSize="11" FontWeight="Bold"
Foreground="{StaticResource Brush.Text.Secondary}"
Margin="0,0,0,8" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Text="{Binding CacheDirectory}"
FontFamily="Consolas" FontSize="11"
Foreground="{StaticResource Brush.Text.Primary}"
TextTrimming="CharacterEllipsis" />
<TextBlock FontSize="12"
Foreground="{StaticResource Brush.Text.Secondary}">
<Run Text="Taille actuelle : " />
<Run Text="{Binding CacheSizeDisplay}" FontWeight="SemiBold" />
</TextBlock>
</StackPanel>
<Button Grid.Column="1" Margin="8,0,0,0"
Style="{StaticResource SecondaryButton}"
Content="📁"
Command="{Binding OpenCacheFolderCommand}" />
<Button Grid.Column="2" Margin="8,0,0,0"
Style="{StaticResource SecondaryButton}"
Content="🗑 Vider"
Command="{Binding ClearCacheCommand}" />
</Grid>
</StackPanel>
</Border>
<!-- Logs / About -->
<Border Background="{StaticResource Brush.Bg.Card}"
BorderBrush="{StaticResource Brush.Border}" BorderThickness="1"
CornerRadius="6" Padding="20" Margin="0,0,0,16">
<StackPanel>
<TextBlock Text="LOGS &amp; APPLICATION"
FontSize="11" FontWeight="Bold"
Foreground="{StaticResource Brush.Text.Secondary}"
Margin="0,0,0,8" />
<TextBlock FontSize="13"
Foreground="{StaticResource Brush.Text.Primary}">
<Run Text="Version : " />
<Run Text="{Binding LauncherVersion}" FontWeight="SemiBold" />
</TextBlock>
<Grid Margin="0,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Text="{Binding LogsDirectory}"
FontFamily="Consolas" FontSize="11"
Foreground="{StaticResource Brush.Text.Primary}"
TextTrimming="CharacterEllipsis" />
<TextBlock Text="Logs rotation quotidienne, 10 jours conservés"
FontSize="12"
Foreground="{StaticResource Brush.Text.Secondary}"
Margin="0,2,0,0" />
</StackPanel>
<Button Grid.Column="1" Margin="8,0,0,0"
Style="{StaticResource SecondaryButton}"
Content="📁 Ouvrir"
Command="{Binding OpenLogsFolderCommand}" />
</Grid>
</StackPanel>
</Border>
</StackPanel>
</ScrollViewer>
<!-- Footer -->
<Border Grid.Row="2" Background="{StaticResource Brush.Bg.Footer}"
BorderBrush="{StaticResource Brush.Border}" BorderThickness="0,1,0,0"
Padding="20,12">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button Style="{StaticResource SecondaryButton}"
Content="Annuler"
IsCancel="True"
Margin="0,0,12,0"
Click="OnCancel" />
<Button Style="{StaticResource AccentButton}"
Content="Enregistrer"
Padding="32,8"
IsDefault="True"
Click="OnSave" />
</StackPanel>
</Border>
</Grid>
</Window>

View File

@@ -0,0 +1,28 @@
using System.Windows;
using PSLauncher.App.ViewModels;
namespace PSLauncher.App.Views;
public partial class SettingsDialog : Window
{
private readonly SettingsViewModel _vm;
public SettingsDialog(SettingsViewModel vm)
{
InitializeComponent();
DataContext = _vm = vm;
}
private void OnSave(object sender, RoutedEventArgs e)
{
_vm.SaveCommand.Execute(null);
DialogResult = true;
Close();
}
private void OnCancel(object sender, RoutedEventArgs e)
{
DialogResult = false;
Close();
}
}