Restore installer GUI launch (via shellexec) + checkpoint descriptions
Two follow-ups to the previous Config Checkpoints commit:
1. Bring back the post-install "Launch Webhook Server" checkbox in the
installer. The previous attempt failed because Inno Setup's
postinstall flag launches via CreateProcess after Setup exits,
bypassing the GUI's requireAdministrator manifest. Adding the
shellexec flag switches to ShellExecute, which DOES honor the
manifest and triggers a clean UAC prompt - so the post-install
GUI launch works as expected.
2. Each checkpoint now carries a description, stored in a sidecar
.meta.json file next to the snapshot. Defaults:
- Auto-on-save: "Before save"
- Midnight scheduler: "Nightly auto-checkpoint"
- Manual: opens a small dialog so the user can type a meaningful
description (defaults to "Manual checkpoint" if blank)
The dialog and pruning both clean up sidecars alongside snapshots.
The Config Checkpoints grid grows a Description column between
When and Size.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -101,6 +101,6 @@ public sealed class AdminPipeClient
|
||||
public Task<AdminResponse> ImportConfigAsync(ServerConfig config, CancellationToken ct = default) =>
|
||||
InvokeAsync(AdminOps.ImportConfig, config, ct);
|
||||
|
||||
public Task<BackupEntry?> CreateCheckpointAsync(CancellationToken ct = default) =>
|
||||
InvokeAsync<BackupEntry>(AdminOps.CreateCheckpoint, null, ct);
|
||||
public Task<BackupEntry?> CreateCheckpointAsync(string? description, CancellationToken ct = default) =>
|
||||
InvokeAsync<BackupEntry>(AdminOps.CreateCheckpoint, new CreateCheckpointArgs { Description = description }, ct);
|
||||
}
|
||||
|
||||
@@ -46,9 +46,20 @@ public sealed partial class ConfigCheckpointsViewModel : ObservableObject
|
||||
[RelayCommand]
|
||||
private async Task TakeCheckpointAsync()
|
||||
{
|
||||
// Prompt for an optional description on the UI thread.
|
||||
string? description = null;
|
||||
var prompted = Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
var dlg = new Views.TakeCheckpointDialog { Owner = Application.Current.MainWindow };
|
||||
if (dlg.ShowDialog() != true) return false;
|
||||
description = string.IsNullOrWhiteSpace(dlg.Description) ? null : dlg.Description;
|
||||
return true;
|
||||
});
|
||||
if (!prompted) return;
|
||||
|
||||
try
|
||||
{
|
||||
var entry = await _client.CreateCheckpointAsync().ConfigureAwait(false);
|
||||
var entry = await _client.CreateCheckpointAsync(description).ConfigureAwait(false);
|
||||
await RefreshAsync().ConfigureAwait(false);
|
||||
if (entry is not null)
|
||||
{
|
||||
|
||||
@@ -39,11 +39,13 @@
|
||||
HeadersVisibility="Column"
|
||||
GridLinesVisibility="Horizontal">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="When (local)" Width="200"
|
||||
<DataGridTextColumn Header="When (local)" Width="170"
|
||||
Binding="{Binding SavedAt, StringFormat='{}{0:yyyy-MM-dd HH:mm:ss}', ConverterCulture=en-US}"/>
|
||||
<DataGridTextColumn Header="Description" Width="*"
|
||||
Binding="{Binding Description}"/>
|
||||
<DataGridTextColumn Header="Size" Width="100"
|
||||
Binding="{Binding SizeBytes, StringFormat='{}{0:n0} bytes'}"/>
|
||||
<DataGridTextColumn Header="File name" Width="*"
|
||||
<DataGridTextColumn Header="File name" Width="200"
|
||||
Binding="{Binding FileName}" FontFamily="Consolas"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<Window x:Class="WebhookServer.Gui.Views.TakeCheckpointDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="Take checkpoint"
|
||||
Height="180" Width="440"
|
||||
ResizeMode="NoResize"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Icon="/webhook-server.ico"
|
||||
ShowInTaskbar="False">
|
||||
<Grid Margin="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" TextWrapping="Wrap"
|
||||
Text="Description for this checkpoint (optional):"/>
|
||||
<TextBox x:Name="DescriptionBox" Grid.Row="1" Margin="0,8,0,0" MaxLength="120">
|
||||
<TextBox.InputBindings>
|
||||
<KeyBinding Key="Enter" Command="{Binding OkCommand, ElementName=Self, FallbackValue={x:Null}}"/>
|
||||
</TextBox.InputBindings>
|
||||
</TextBox>
|
||||
<TextBlock Grid.Row="2" Foreground="Gray" FontStyle="Italic" FontSize="11" Margin="0,4,0,0"
|
||||
Text="Examples: 'Before adding new endpoint', 'Pre-AD-policy-change'. Leave blank to use 'Manual checkpoint'."/>
|
||||
|
||||
<StackPanel Grid.Row="4" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,12,0,0">
|
||||
<Button Content="OK" Width="80" IsDefault="True" Click="OnOk" Margin="0,0,8,0"/>
|
||||
<Button Content="Cancel" Width="80" IsCancel="True" Click="OnCancel"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -0,0 +1,27 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace WebhookServer.Gui.Views;
|
||||
|
||||
public partial class TakeCheckpointDialog : Window
|
||||
{
|
||||
public string Description { get; private set; } = "";
|
||||
|
||||
public TakeCheckpointDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += (_, _) => DescriptionBox.Focus();
|
||||
}
|
||||
|
||||
private void OnOk(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Description = DescriptionBox.Text?.Trim() ?? "";
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void OnCancel(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = false;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user