Files
webhook-server/src/WebhookServer.Gui/MainWindow.xaml
T
justin 87bcb6807f GUI UX, secret visibility, browser-friendly hooks, deploy script
GUI:
- URL column in endpoint grid + Copy URL toolbar button so the full
  http://host:port/hook/<slug> is one click away
- Double-click a row to open the edit dialog
- Bearer/HMAC sections in the editor hide when the auth mode doesn't
  use them, and reappear with previously-entered values when switched
  back
- Log panel auto-scroll checkbox (default on) plus 3s polling so log
  entries stream in without manual refresh
- Secret fields are now plain text with a Copy button. Anyone who can
  open the admin-pipe-ACL'd GUI is already SYSTEM-equivalent on the
  host, so masking the value just made recovery harder. PFX password
  in Server Settings gets the same treatment.

Service:
- Admin pipe ops log info-level lines on every mutation
  (create/update/delete/enable/disable/update-config/bind-https) so
  GUI activity is visible in the Serilog file
- /hook/{slug} accepts GET as well as POST so a browser smoke-test
  works without curl
- /favicon.ico returns 204 so browser hits don't pollute logs with 404s
- AdminPipeServer no longer strips plaintext secrets when sending
  config to the GUI; the pipe ACL already restricts to SYSTEM/Admins

Scripts:
- New deploy.ps1: stops + republishes + copies binaries to
  C:\Program Files\WebhookServer + (re)installs the Windows Service
- install-service.ps1 now uses sc.exe argv splatting consistently for
  both create and config paths

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 08:47:11 -04:00

119 lines
6.5 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"
d:DataContext="{d:DesignInstance Type=vm:MainViewModel}">
<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>
<ToolBar DockPanel.Dock="Top">
<Button Content="Refresh" Command="{Binding RefreshCommand}"/>
<Separator/>
<Button Content="Add" Command="{Binding AddEndpointCommand}"/>
<Button Content="Edit" Command="{Binding EditEndpointCommand}"
IsEnabled="{Binding SelectedEndpoint, Converter={StaticResource NotNull}}"/>
<Button Content="Delete" Command="{Binding DeleteEndpointCommand}"
IsEnabled="{Binding SelectedEndpoint, Converter={StaticResource NotNull}}"/>
<Separator/>
<Button Content="Copy URL" Command="{Binding CopyEndpointUrlCommand}"
IsEnabled="{Binding SelectedEndpoint, Converter={StaticResource NotNull}}"
ToolTip="Copy the full webhook URL for the selected endpoint to the clipboard"/>
<Separator/>
<Button Content="Server Settings…" Command="{Binding EditServerSettingsCommand}"/>
</ToolBar>
<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"/>
</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>