a808964cf1
* Phase 3: app icon (multi-resolution ICO + master PNG) scripts/generate-icons.ps1 renders the icon programmatically with System.Drawing - rounded teal square (#0E7C66) with a stylized white hook glyph - at 16/24/32/48/64/128/256 px and assembles a proper multi-resolution Microsoft ICO. The PNG and ICO outputs land in resources/. The script is the source of truth; re-run after editing the design. GUI csproj uses ApplicationIcon for the EXE icon and embeds the .ico + .png as Resources so MainWindow and AboutDialog can use them via WPF's resource URI scheme. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Phase 5: tray icon with minimize-to-tray and context menu GUI csproj enables UseWindowsForms (NotifyIcon lives in WinForms even in .NET 8). New Services/TrayIcon.cs wraps NotifyIcon with a context menu (Open / Restart service / Exit) and the embedded webhook-server icon. MainWindow creates the TrayIcon, hides itself on minimize and restores on tray double-click. Adds GlobalUsings.cs to alias the WPF defaults for types that exist in both WPF and WinForms (Application, MessageBox, TextBox, Binding, etc.) so existing code keeps compiling without per-file changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Phase 6+7: Inno Setup installer + GitHub Actions release pipeline installer/webhook-server.iss is an Inno Setup 6 script that: - Installs to %ProgramFiles%\WebhookServer - Creates Start Menu folder + GUI shortcut (and optional desktop icon) - Runs install-service.ps1 post-install to register the Windows Service - Runs uninstall-service.ps1 pre-uninstall to remove it - Bundles the webhook-server icon for the installer / uninstaller scripts/build-installer.ps1 is the local build helper: publishes both projects, finds ISCC.exe (PATH or standard install path), compiles the installer with the version pulled from Directory.Build.props, drops the output in dist/. .github/workflows/ci.yml runs build + test on every push/PR to main. .github/workflows/release.yml triggers on v* tags (or manual dispatch), runs tests, installs Inno Setup via choco, builds the installer, and attaches the .exe to a GitHub Release. Pre-1.0 versions are flagged prerelease automatically. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Phase 4: backups + import/export config ConfigStore.SaveAsync now snapshots the previous config to %ProgramData%\WebhookServer\backups\config-<timestamp>.json before overwriting, retaining the last 30. Failures are silent so a backup-write hiccup never blocks an actual save. Three new admin pipe ops: - list-backups: returns newest 50 entries with timestamps and sizes - restore-backup: takes a fileName, refuses path-traversal chars, loads the named backup over the live config (which itself triggers a fresh backup of the current state via the SaveAsync hook) - import-config: replaces the current config with a GUI-supplied ServerConfig, merging encrypted secrets where the GUI didn't supply new plaintext GUI File menu items are wired: - Import config: file picker -> ImportConfigAsync - Export config: SaveFileDialog writes the current config as JSON - Backups: dynamic submenu auto-refreshed when opened, listing backups with timestamp + size; click to confirm-and-restore Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
158 lines
9.0 KiB
XML
158 lines
9.0 KiB
XML
<Window x:Class="WebhookServer.Gui.MainWindow"
|
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
xmlns:vm="clr-namespace:WebhookServer.Gui.ViewModels"
|
|
xmlns:models="clr-namespace:WebhookServer.Core.Models;assembly=WebhookServer.Core"
|
|
mc:Ignorable="d"
|
|
Title="Webhook Server" Height="600" Width="1000"
|
|
Icon="/webhook-server.ico"
|
|
d:DataContext="{d:DesignInstance Type=vm:MainViewModel}">
|
|
<Window.InputBindings>
|
|
<KeyBinding Key="N" Modifiers="Control" Command="{Binding AddEndpointCommand}"/>
|
|
</Window.InputBindings>
|
|
<DockPanel LastChildFill="True">
|
|
<StatusBar DockPanel.Dock="Bottom">
|
|
<StatusBarItem>
|
|
<Ellipse Width="10" Height="10"
|
|
Fill="{Binding IsConnected, Converter={StaticResource ConnFill}}"/>
|
|
</StatusBarItem>
|
|
<StatusBarItem>
|
|
<TextBlock Text="{Binding ConnectionStatus}"/>
|
|
</StatusBarItem>
|
|
</StatusBar>
|
|
|
|
<Menu DockPanel.Dock="Top">
|
|
<MenuItem Header="_File">
|
|
<MenuItem Header="_New endpoint…" Command="{Binding AddEndpointCommand}" InputGestureText="Ctrl+N"/>
|
|
<Separator/>
|
|
<MenuItem Header="_Import config…" Command="{Binding ImportConfigCommand}"/>
|
|
<MenuItem Header="_Export config…" Command="{Binding ExportConfigCommand}"/>
|
|
<MenuItem Header="_Backups"
|
|
ItemsSource="{Binding Backups}"
|
|
SubmenuOpened="OnBackupsSubmenuOpened">
|
|
<MenuItem.ItemContainerStyle>
|
|
<Style TargetType="MenuItem">
|
|
<Setter Property="Header">
|
|
<Setter.Value>
|
|
<MultiBinding StringFormat="{}{0:yyyy-MM-dd HH:mm:ss} ({1:n0} bytes)">
|
|
<Binding Path="SavedAt"/>
|
|
<Binding Path="SizeBytes"/>
|
|
</MultiBinding>
|
|
</Setter.Value>
|
|
</Setter>
|
|
<Setter Property="Command" Value="{Binding DataContext.RestoreBackupCommand, RelativeSource={RelativeSource AncestorType=Window}}"/>
|
|
<Setter Property="CommandParameter" Value="{Binding}"/>
|
|
</Style>
|
|
</MenuItem.ItemContainerStyle>
|
|
</MenuItem>
|
|
<Separator/>
|
|
<MenuItem Header="E_xit" Command="{Binding ExitCommand}"/>
|
|
</MenuItem>
|
|
<MenuItem Header="_Server">
|
|
<MenuItem Header="_Settings…" Command="{Binding EditServerSettingsCommand}"/>
|
|
<Separator/>
|
|
<MenuItem Header="_Restart service" Command="{Binding RestartServiceCommand}"/>
|
|
</MenuItem>
|
|
<MenuItem Header="_Help">
|
|
<MenuItem Header="_About Webhook Server…" Command="{Binding ShowAboutCommand}"/>
|
|
</MenuItem>
|
|
</Menu>
|
|
|
|
<Grid>
|
|
<Grid.RowDefinitions>
|
|
<RowDefinition Height="*"/>
|
|
<RowDefinition Height="5"/>
|
|
<RowDefinition Height="200"/>
|
|
</Grid.RowDefinitions>
|
|
|
|
<DataGrid Grid.Row="0"
|
|
ItemsSource="{Binding Endpoints}"
|
|
SelectedItem="{Binding SelectedEndpoint, Mode=TwoWay}"
|
|
AutoGenerateColumns="False"
|
|
CanUserAddRows="False"
|
|
CanUserDeleteRows="False"
|
|
IsReadOnly="True"
|
|
HeadersVisibility="Column">
|
|
<DataGrid.RowStyle>
|
|
<Style TargetType="DataGridRow">
|
|
<EventSetter Event="MouseDoubleClick" Handler="OnRowDoubleClick"/>
|
|
<Setter Property="ContextMenu">
|
|
<Setter.Value>
|
|
<ContextMenu>
|
|
<MenuItem Header="_Edit…" Command="{Binding DataContext.EditEndpointCommand, RelativeSource={RelativeSource AncestorType=Window}}"/>
|
|
<MenuItem Header="_Copy URL" Command="{Binding DataContext.CopyEndpointUrlCommand, RelativeSource={RelativeSource AncestorType=Window}}"/>
|
|
<Separator/>
|
|
<MenuItem Header="Toggle _enabled"
|
|
Command="{Binding DataContext.ToggleEnabledCommand, RelativeSource={RelativeSource AncestorType=Window}}"
|
|
CommandParameter="{Binding}"/>
|
|
<Separator/>
|
|
<MenuItem Header="_Delete…" Command="{Binding DataContext.DeleteEndpointCommand, RelativeSource={RelativeSource AncestorType=Window}}"/>
|
|
</ContextMenu>
|
|
</Setter.Value>
|
|
</Setter>
|
|
</Style>
|
|
</DataGrid.RowStyle>
|
|
<DataGrid.Columns>
|
|
<DataGridTemplateColumn Header="Enabled" Width="80">
|
|
<DataGridTemplateColumn.CellTemplate>
|
|
<DataTemplate DataType="{x:Type models:EndpointConfig}">
|
|
<CheckBox IsChecked="{Binding Enabled, Mode=OneWay}"
|
|
Command="{Binding DataContext.ToggleEnabledCommand, RelativeSource={RelativeSource AncestorType=Window}}"
|
|
CommandParameter="{Binding}"/>
|
|
</DataTemplate>
|
|
</DataGridTemplateColumn.CellTemplate>
|
|
</DataGridTemplateColumn>
|
|
<DataGridTextColumn Header="Slug" Binding="{Binding Slug}" Width="120"/>
|
|
<DataGridTemplateColumn Header="URL" Width="*">
|
|
<DataGridTemplateColumn.CellTemplate>
|
|
<DataTemplate DataType="{x:Type models:EndpointConfig}">
|
|
<TextBox IsReadOnly="True"
|
|
BorderThickness="0"
|
|
Background="Transparent"
|
|
Padding="0"
|
|
VerticalAlignment="Center">
|
|
<TextBox.Text>
|
|
<MultiBinding Converter="{StaticResource HookUrl}" Mode="OneWay">
|
|
<Binding Path="Slug"/>
|
|
<Binding Path="DataContext.HttpBaseUrl" RelativeSource="{RelativeSource AncestorType=Window}"/>
|
|
</MultiBinding>
|
|
</TextBox.Text>
|
|
</TextBox>
|
|
</DataTemplate>
|
|
</DataGridTemplateColumn.CellTemplate>
|
|
</DataGridTemplateColumn>
|
|
<DataGridTextColumn Header="Auth" Binding="{Binding AuthMode}" Width="80"/>
|
|
<DataGridTextColumn Header="Executor" Binding="{Binding ExecutorType}" Width="140"/>
|
|
<DataGridTextColumn Header="Mode" Binding="{Binding ResponseMode}" Width="80"/>
|
|
<DataGridTextColumn Header="Description" Binding="{Binding Description}" Width="2*"/>
|
|
</DataGrid.Columns>
|
|
</DataGrid>
|
|
|
|
<GridSplitter Grid.Row="1" HorizontalAlignment="Stretch" Background="#DDD"/>
|
|
|
|
<DockPanel Grid.Row="2">
|
|
<Grid DockPanel.Dock="Top">
|
|
<Grid.ColumnDefinitions>
|
|
<ColumnDefinition Width="*"/>
|
|
<ColumnDefinition Width="Auto"/>
|
|
<ColumnDefinition Width="Auto"/>
|
|
</Grid.ColumnDefinitions>
|
|
<TextBlock Text="Recent log entries" FontWeight="Bold" Margin="6,4"/>
|
|
<CheckBox Grid.Column="1" Content="Auto-scroll" IsChecked="{Binding AutoScrollLogs}" VerticalAlignment="Center" Margin="6,2"/>
|
|
<Button Grid.Column="2" Content="Refresh" Command="{Binding RefreshLogTailCommand}" Margin="6,2"/>
|
|
</Grid>
|
|
<TextBox x:Name="LogTailBox"
|
|
Text="{Binding LogTail, Mode=OneWay}"
|
|
IsReadOnly="True"
|
|
FontFamily="Consolas"
|
|
VerticalScrollBarVisibility="Auto"
|
|
HorizontalScrollBarVisibility="Auto"
|
|
TextWrapping="NoWrap"
|
|
TextChanged="OnLogTailChanged"/>
|
|
</DockPanel>
|
|
</Grid>
|
|
</DockPanel>
|
|
</Window>
|