0b7c20a1fa
* GUI: hide-to-tray on X button; tray persists until explicit Exit The minimize-to-tray behavior already worked, but clicking the X button killed the GUI process and took the tray with it. That made "tray when the GUI window is closed" a UX dead end - the only way to get the tray was to leave the window minimized. Now: - X button / Alt+F4 -> hide window, tray stays alive - Tray double-click -> reopens window - File -> Exit (or tray's Exit menu) -> truly quits the process Wired by adding a RealExitRequested event on MainViewModel that the window subscribes to (so File -> Exit sets the ExitForReal flag before calling Shutdown), and a parallel onExit callback on TrayIcon for the tray menu's Exit item. The Closing handler checks ExitForReal: if false (X / Alt+F4) it cancels the close and hides; if true, it disposes the tray and lets the close proceed. Auto-start at login is still TBD - if you want the tray to be there without manually launching the GUI after a reboot, that's a separate Task Scheduler entry. Skipping for now. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Add File -> Minimize to tray toggle (default on) Adds a checkable MenuItem so the user can opt out of the hide-to-tray behavior. Persisted per-user to %APPDATA%\WebhookServer\gui.json so the choice survives restarts. When ticked (default): X / Alt+F4 / minimize hide to tray, GUI process keeps running, tray icon persists. When unticked: X actually closes the app, minimize is a regular Windows minimize. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Fix endpoint-row context menu: bindings via PlacementTarget.Tag The ContextMenu lived in its own popup visual tree, so the menu items' RelativeSource={RelativeSource AncestorType=Window} couldn't find the Window and the bindings silently failed - none of Edit / Copy URL / Toggle / Delete actually fired their commands. Standard WPF workaround: park MainViewModel on each DataGridRow's Tag (still in the Window's visual tree, so the row Setter binding resolves) and reach it from the menu items via PlacementTarget.Tag. The toggle command parameter likewise comes from PlacementTarget.DataContext (the EndpointConfig the row represents). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * v0.1.3 --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
157 lines
9.3 KiB
XML
157 lines
9.3 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="Config _Checkpoints…" Command="{Binding ShowConfigCheckpointsCommand}"/>
|
|
<Separator/>
|
|
<MenuItem Header="_Minimize to tray"
|
|
IsCheckable="True"
|
|
IsChecked="{Binding MinimizeToTrayEnabled, Mode=TwoWay}"
|
|
ToolTip="When ticked, closing or minimizing the window hides it to the tray and keeps the GUI process alive. Untick to make the X button quit the app."/>
|
|
<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="_Documentation…" Command="{Binding OpenDocumentationCommand}"/>
|
|
<Separator/>
|
|
<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"/>
|
|
<!-- The ContextMenu lives in its own visual tree (a popup), so
|
|
AncestorType=Window doesn't resolve from inside menu items.
|
|
Stash MainViewModel on the row's Tag here (still in the
|
|
Window's tree), then reach it from the menu via
|
|
PlacementTarget.Tag. -->
|
|
<Setter Property="Tag" Value="{Binding DataContext, RelativeSource={RelativeSource AncestorType=Window}}"/>
|
|
<Setter Property="ContextMenu">
|
|
<Setter.Value>
|
|
<ContextMenu>
|
|
<MenuItem Header="_Edit…"
|
|
Command="{Binding PlacementTarget.Tag.EditEndpointCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
|
|
<MenuItem Header="_Copy URL"
|
|
Command="{Binding PlacementTarget.Tag.CopyEndpointUrlCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
|
|
<Separator/>
|
|
<MenuItem Header="Toggle _enabled"
|
|
Command="{Binding PlacementTarget.Tag.ToggleEnabledCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
|
|
CommandParameter="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
|
|
<Separator/>
|
|
<MenuItem Header="_Delete…"
|
|
Command="{Binding PlacementTarget.Tag.DeleteEndpointCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
|
|
</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>
|