Files
lmxopcua/src/Client/ZB.MOM.WW.OtOpcUa.Client.UI/Views/MainWindow.axaml
T
Joseph Doherty 1b10194634 fix(client-ui): resolve Low code-review findings (Client.UI-003,004,006,009,010,011)
- Client.UI-003: wire Serilog properly per CLAUDE.md — console sink +
  rolling daily file sink in Program.Main, Log.CloseAndFlush in finally,
  per-VM Log.ForContext<> loggers.
- Client.UI-004: migrate the cert-store folder picker from the obsolete
  OpenFolderDialog to StorageProvider.OpenFolderPickerAsync (with
  TryGetFolderFromPathAsync seed + TryGetLocalPath extraction).
- Client.UI-006: surface formerly silent catch blocks via an observable
  StatusMessage on the Subscriptions / Alarms VMs that bubbles up into
  the shell's status bar; soft fallbacks log at Information level so
  hard failures stay distinguishable.
- Client.UI-009: docs/Client.UI.md now lists Standard Deviation in the
  Aggregate row of the Query Options table.
- Client.UI-010: removed the unused MinDateTimeProperty /
  MaxDateTimeProperty styled properties from DateTimeRangePicker.
- Client.UI-011: updated the cert-store TextBox watermark from the
  legacy AppData/LmxOpcUaClient/pki to the canonical
  AppData/OtOpcUaClient/pki.

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

162 lines
8.8 KiB
XML

<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:ZB.MOM.WW.OtOpcUa.Client.UI.ViewModels"
xmlns:views="using:ZB.MOM.WW.OtOpcUa.Client.UI.Views"
x:Class="ZB.MOM.WW.OtOpcUa.Client.UI.Views.MainWindow"
x:DataType="vm:MainWindowViewModel"
Title="OPC UA Client"
Width="1200"
Height="800">
<DockPanel>
<!-- Top Connection Bar -->
<Border DockPanel.Dock="Top" Background="#F0F0F0" Padding="12">
<StackPanel Spacing="4">
<!-- Always visible: URL + Connect/Disconnect -->
<StackPanel Orientation="Horizontal" Spacing="8">
<StackPanel Spacing="2" VerticalAlignment="Bottom">
<TextBlock Text="Endpoint URL" FontSize="11" Foreground="Gray" />
<TextBox Text="{Binding EndpointUrl}"
Width="400"
Watermark="opc.tcp://host:port" />
</StackPanel>
<Button Content="Connect"
Command="{Binding ConnectCommand}"
VerticalAlignment="Bottom" Padding="16,6" />
<Button Content="Disconnect"
Command="{Binding DisconnectCommand}"
VerticalAlignment="Bottom" Padding="16,6" />
</StackPanel>
<!-- Expandable settings -->
<Expander Header="Connection Settings" Padding="0,4">
<StackPanel Spacing="10" Margin="4,8,4,4">
<!-- Row 1: Authentication -->
<StackPanel Orientation="Horizontal" Spacing="12">
<StackPanel Spacing="2">
<TextBlock Text="Username" FontSize="11" Foreground="Gray" />
<TextBox Text="{Binding Username}"
Width="160"
Watermark="(anonymous)" />
</StackPanel>
<StackPanel Spacing="2">
<TextBlock Text="Password" FontSize="11" Foreground="Gray" />
<TextBox Text="{Binding Password}"
Width="160"
Watermark="(none)"
PasswordChar="*" />
</StackPanel>
<StackPanel Spacing="2">
<TextBlock Text="Security Mode" FontSize="11" Foreground="Gray" />
<ComboBox ItemsSource="{Binding SecurityModes}"
SelectedItem="{Binding SelectedSecurityMode}"
Width="160" />
</StackPanel>
</StackPanel>
<!-- Row 2: Failover + Session -->
<StackPanel Orientation="Horizontal" Spacing="12">
<StackPanel Spacing="2">
<TextBlock Text="Failover URLs (comma-separated)" FontSize="11" Foreground="Gray" />
<TextBox Text="{Binding FailoverUrls}"
Width="400"
Watermark="opc.tcp://backup1:4840, opc.tcp://backup2:4840" />
</StackPanel>
<StackPanel Spacing="2">
<TextBlock Text="Session Timeout (s)" FontSize="11" Foreground="Gray" />
<NumericUpDown Value="{Binding SessionTimeoutSeconds}"
Minimum="1" Maximum="3600"
Width="120"
FormatString="0" />
</StackPanel>
</StackPanel>
<!-- Row 3: Certificates -->
<StackPanel Orientation="Horizontal" Spacing="12">
<StackPanel Spacing="2">
<TextBlock Text="Certificate Store Path" FontSize="11" Foreground="Gray" />
<StackPanel Orientation="Horizontal" Spacing="4">
<TextBox Text="{Binding CertificateStorePath}"
Width="370"
IsReadOnly="True"
Watermark="(default: AppData/OtOpcUaClient/pki)" />
<Button Name="BrowseCertPathButton"
Content="..."
Width="30"
ToolTip.Tip="Browse for folder" />
</StackPanel>
</StackPanel>
<CheckBox IsChecked="{Binding AutoAcceptCertificates}"
Content="Auto-accept untrusted server certificates"
VerticalAlignment="Bottom"
Margin="0,0,0,4" />
</StackPanel>
</StackPanel>
</Expander>
<!-- Redundancy Info -->
<StackPanel Orientation="Horizontal"
Spacing="16"
IsVisible="{Binding RedundancyInfo, Converter={x:Static ObjectConverters.IsNotNull}}"
Margin="0,2,0,0">
<TextBlock Text="{Binding RedundancyInfo.Mode, StringFormat='Redundancy: {0}'}"
FontSize="11" Foreground="Gray" />
<TextBlock Text="{Binding RedundancyInfo.ServiceLevel, StringFormat='Service Level: {0}'}"
FontSize="11" Foreground="Gray" />
<TextBlock Text="{Binding RedundancyInfo.ApplicationUri, StringFormat='URI: {0}'}"
FontSize="11" Foreground="Gray" />
</StackPanel>
</StackPanel>
</Border>
<!-- Bottom Status Bar -->
<Border DockPanel.Dock="Bottom" Background="#F0F0F0" Padding="8,4">
<StackPanel Orientation="Horizontal" Spacing="20">
<TextBlock Text="{Binding StatusMessage}" />
<TextBlock Text="{Binding SessionLabel}" Foreground="Gray" />
<TextBlock Text="{Binding SubscriptionCount, StringFormat='Subscriptions: {0}'}" />
</StackPanel>
</Border>
<!-- Main Content -->
<Grid ColumnDefinitions="300,8,*">
<!-- Left: Browse Tree -->
<Border Grid.Column="0" BorderBrush="#CCCCCC" BorderThickness="0,0,1,0">
<DockPanel>
<TextBlock DockPanel.Dock="Top" Text="Browse Tree" FontWeight="Bold" Margin="8,8,8,4" />
<views:BrowseTreeView DataContext="{Binding BrowseTree}"
Name="BrowseTreePanel">
<views:BrowseTreeView.ContextMenu>
<ContextMenu Name="TreeContextMenu">
<MenuItem Header="Subscribe" Name="SubscribeMenuItem" />
<MenuItem Header="View History" Name="ViewHistoryMenuItem" />
<Separator />
<MenuItem Header="Monitor Alarms" Name="MonitorAlarmsMenuItem" />
</ContextMenu>
</views:BrowseTreeView.ContextMenu>
</views:BrowseTreeView>
</DockPanel>
</Border>
<!-- Splitter -->
<GridSplitter Grid.Column="1" Width="8" Background="Transparent" />
<!-- Right: Tab panels -->
<TabControl Grid.Column="2"
IsEnabled="{Binding IsConnected}"
SelectedIndex="{Binding SelectedTabIndex}">
<TabItem Header="Read/Write">
<views:ReadWriteView DataContext="{Binding ReadWrite}" />
</TabItem>
<TabItem Header="{Binding SubscriptionsTabHeader}">
<views:SubscriptionsView DataContext="{Binding Subscriptions}" />
</TabItem>
<TabItem Header="{Binding AlarmsTabHeader}">
<views:AlarmsView DataContext="{Binding Alarms}" />
</TabItem>
<TabItem Header="History">
<views:HistoryView DataContext="{Binding History}" />
</TabItem>
</TabControl>
</Grid>
</DockPanel>
</Window>