maksit-lto-backup/src/MaksIT.LTO.Backup.UI/MainWindow.axaml
2026-09-06 15:33:46 +02:00

676 lines
37 KiB
XML

<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:MaksIT.LTO.Backup.UI.Controls"
x:Class="MaksIT.LTO.Backup.UI.MainWindow"
Title="MaksIT.LTO.Backup"
Icon="/Assets/icon.ico"
Width="1100"
Height="760"
MinWidth="880"
MinHeight="620">
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="160" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="0,0,0,8">
<TextBlock Text="MaksIT.LTO.Backup" FontSize="18" FontWeight="SemiBold" Margin="0,0,12,0" VerticalAlignment="Bottom" />
<TextBlock Text="Operations console" Classes="hint" VerticalAlignment="Bottom" />
</StackPanel>
<Border Grid.Row="1" Classes="statusBar" Margin="0,0,0,8">
<DockPanel LastChildFill="True">
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal" VerticalAlignment="Center">
<CheckBox Content="Live monitor"
VerticalAlignment="Center"
Margin="0,0,10,0"
IsChecked="{Binding LiveMonitorEnabled}" />
<TextBlock Text="Every" VerticalAlignment="Center" Margin="0,0,6,0" />
<ComboBox Width="64"
ItemsSource="{Binding MonitorIntervalOptions}"
SelectedItem="{Binding SelectedMonitorInterval}"
DisplayMemberBinding="{Binding Display}" />
<Button Content="Refresh now" Command="{Binding RefreshMonitorNowCommand}" Width="96" Margin="10,0,0,0" />
</StackPanel>
<TextBlock VerticalAlignment="Center"
TextWrapping="Wrap"
Text="{Binding HeaderStatusText}" />
</DockPanel>
</Border>
<TabControl Grid.Row="2">
<TabItem Header="Monitor">
<Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<controls:GroupBox Header="Tape drive">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="120" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Mode:" Margin="0,0,0,4" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding MonitorDriveModeText}" Margin="0,0,12,4" />
<TextBlock Grid.Row="0" Grid.Column="2" Text="Topology:" Margin="0,0,0,4" />
<TextBlock Grid.Row="0" Grid.Column="3" Text="{Binding MonitorDriveTopologyText}" Margin="0,0,0,4" />
<TextBlock Grid.Row="1" Text="State:" Margin="0,0,0,4" />
<TextBlock Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="3" Text="{Binding MonitorDriveStateText}" FontWeight="SemiBold" Margin="0,0,0,4" />
<TextBlock Grid.Row="2" Text="Path:" Margin="0,0,0,4" />
<TextBlock Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="3" Text="{Binding MonitorDrivePathText}" TextWrapping="Wrap" Margin="0,0,0,4" />
<TextBlock Grid.Row="3" Text="Status code:" />
<TextBlock Grid.Row="3" Grid.Column="1" Text="{Binding MonitorDriveStatusCodeText}" Margin="0,0,12,0" />
<TextBlock Grid.Row="3" Grid.Column="2" Text="Abs. block:" />
<TextBlock Grid.Row="3" Grid.Column="3" Text="{Binding MonitorDriveBlockText}" />
</Grid>
</controls:GroupBox>
<controls:GroupBox Grid.Row="1" Header="Tape library" IsVisible="{Binding IsTapeLibraryUi}">
<StackPanel>
<Grid Margin="0,0,0,6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="120" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Mode:" Margin="0,0,0,4" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding MonitorLibraryModeText}" Margin="0,0,12,4" />
<TextBlock Grid.Row="0" Grid.Column="2" Text="Summary:" Margin="0,0,0,4" />
<TextBlock Grid.Row="0" Grid.Column="3" Text="{Binding MonitorLibrarySummaryText}" FontWeight="SemiBold" Margin="0,0,0,4" />
<TextBlock Grid.Row="1" Text="Path:" Margin="0,0,0,4" />
<TextBlock Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="3" Text="{Binding MonitorLibraryPathText}" TextWrapping="Wrap" Margin="0,0,0,4" />
<TextBlock Grid.Row="2" Text="Drives occupied:" />
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding MonitorLibraryDrivesText}" Margin="0,0,12,0" />
<TextBlock Grid.Row="2" Grid.Column="2" Text="Updated:" />
<TextBlock Grid.Row="2" Grid.Column="3" Text="{Binding MonitorUpdatedText}" />
</Grid>
<TextBlock Classes="warning" TextWrapping="Wrap" Text="{Binding MonitorLibraryErrorText}" />
</StackPanel>
</controls:GroupBox>
<DataGrid Grid.Row="2"
ItemsSource="{Binding MonitorInventory}"
IsVisible="{Binding IsTapeLibraryUi}"
AutoGenerateColumns="False"
IsReadOnly="True">
<DataGrid.Columns>
<DataGridTextColumn Header="Slot" Binding="{Binding Slot}" Width="90" />
<DataGridTextColumn Header="Type" Binding="{Binding ElementType}" Width="120" />
<DataGridTextColumn Header="Occupied" Binding="{Binding Occupied}" Width="90" />
<DataGridTextColumn Header="Barcode" Binding="{Binding Barcode}" Width="160" />
<DataGridTextColumn Header="Cartridge" Binding="{Binding CartridgeId}" Width="*" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</TabItem>
<TabItem Header="Drive" IsVisible="{Binding IsStandaloneDriveUi}">
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<StackPanel Margin="8">
<TextBlock Classes="hint"
Margin="0,0,0,8"
TextWrapping="Wrap"
Text="Standalone drive controls. Load/eject the tape in this drive, then use the Backup tab." />
<controls:GroupBox Header="Drive">
<StackPanel Orientation="Horizontal">
<Button Content="Load tape" Command="{Binding LoadTapeCommand}" Width="110" Margin="0,0,8,0" />
<Button Content="Eject tape" Command="{Binding EjectTapeCommand}" Width="110" Margin="0,0,8,0" />
<Button Content="Get status" Command="{Binding GetStatusCommand}" Width="110" />
</StackPanel>
</controls:GroupBox>
<controls:GroupBox Header="Cartridge memory (MAM)">
<StackPanel>
<TextBlock Classes="hint"
Margin="0,0,0,8"
TextWrapping="Wrap"
Text="Applies to the tape currently in this standalone drive." />
<StackPanel Orientation="Horizontal">
<Button Content="Read attributes" Command="{Binding ReadAttributesCommand}" Width="120" Margin="0,0,12,0" />
<TextBlock Text="Barcode:" VerticalAlignment="Center" Margin="0,0,6,0" />
<TextBox Text="{Binding StandaloneBarcodeText}" Width="200" Margin="0,0,12,0" />
<Button Content="Write barcode" Command="{Binding WriteStandaloneBarcodeCommand}" Width="110" />
</StackPanel>
</StackPanel>
</controls:GroupBox>
</StackPanel>
</ScrollViewer>
</TabItem>
<TabItem Header="Library" IsVisible="{Binding IsTapeLibraryUi}">
<Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="Library operations require Topology = TapeLibrary in Settings."
TextWrapping="Wrap"
Margin="0,0,0,8"
IsVisible="False" />
<controls:GroupBox Grid.Row="1" Header="Loaded cartridge" IsVisible="{Binding IsTapeLibraryUi}">
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,0,0,6">
<TextBlock Text="Active drive:" VerticalAlignment="Center" Margin="0,0,8,0" />
<ComboBox Width="460"
ItemsSource="{Binding OccupiedLibraryDrives}"
SelectedItem="{Binding SelectedOccupiedDrive}"
DisplayMemberBinding="{Binding Summary}" />
</StackPanel>
<TextBlock FontWeight="SemiBold"
Margin="0,0,0,4"
Text="{Binding LibraryLoadedSummaryText}" />
<TextBlock Classes="hint"
TextWrapping="Wrap"
Margin="0,0,0,8"
Text="{Binding LibraryLoadedHintText}" />
<StackPanel Orientation="Horizontal">
<Button Content="Read MAM" Command="{Binding ReadAttributesCommand}" Width="100" Margin="0,0,12,0" />
<TextBlock Text="Barcode:" VerticalAlignment="Center" Margin="0,0,6,0" />
<TextBox Width="200"
Margin="0,0,12,0"
Text="{Binding LibraryBarcodeText}"
ToolTip.Tip="Writes MAM barcode for the cartridge in the selected drive bay" />
<Button Content="Assign barcode" Command="{Binding WriteLibraryBarcodeCommand}" Width="120" />
</StackPanel>
</StackPanel>
</controls:GroupBox>
<controls:GroupBox Grid.Row="2" Header="Load / unload" IsVisible="{Binding IsTapeLibraryUi}">
<StackPanel>
<Button Content="Refresh inventory" Command="{Binding RefreshInventoryCommand}" Width="140" HorizontalAlignment="Left" Margin="0,0,0,8" />
<Grid Margin="0,0,0,8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="140" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Cartridge (source):" VerticalAlignment="Center" Margin="0,0,0,6" />
<ComboBox Grid.Row="0" Grid.Column="1"
Margin="0,0,8,6"
ItemsSource="{Binding LoadSources}"
SelectedItem="{Binding SelectedLoadSource}"
DisplayMemberBinding="{Binding Display}"
ToolTip.Tip="Occupied storage slot or drive bay to take the cartridge from" />
<Button Grid.Row="0" Grid.Column="2"
Content="Load into drive"
Command="{Binding LoadIntoDriveCommand}"
Width="120"
Margin="0,0,0,6"
ToolTip.Tip="Move the selected cartridge into the selected empty drive bay" />
<TextBlock Grid.Row="1" Text="Target drive bay:" VerticalAlignment="Center" Margin="0,0,0,6" />
<ComboBox Grid.Row="1" Grid.Column="1"
Margin="0,0,8,6"
ItemsSource="{Binding LoadDrives}"
SelectedItem="{Binding SelectedLoadDrive}"
DisplayMemberBinding="{Binding Display}"
ToolTip.Tip="Empty drive bay (e.g. 1000, 1001)" />
<TextBlock Grid.Row="2" Text="Unload from drive:" VerticalAlignment="Center" />
<ComboBox Grid.Row="2" Grid.Column="1"
Margin="0,0,8,0"
ItemsSource="{Binding UnloadDrives}"
SelectedItem="{Binding SelectedUnloadDrive}"
DisplayMemberBinding="{Binding Display}"
ToolTip.Tip="Drive bay that currently holds a cartridge" />
<Button Grid.Row="2" Grid.Column="2"
Content="Unload to storage"
Command="{Binding UnloadFromDriveCommand}"
Width="120"
ToolTip.Tip="Move the cartridge from the selected drive into the first empty storage slot" />
</Grid>
<Expander Header="Advanced: move any slot → any slot">
<StackPanel Margin="0,6,0,0">
<StackPanel Orientation="Horizontal" Margin="0,0,0,4">
<TextBlock Text="From:" VerticalAlignment="Center" Margin="0,0,6,0" />
<ComboBox Width="300" Margin="0,0,12,0"
ItemsSource="{Binding SourceSlots}"
SelectedItem="{Binding SelectedSourceSlot}"
DisplayMemberBinding="{Binding Display}" />
<TextBlock Text="To:" VerticalAlignment="Center" Margin="0,0,6,0" />
<ComboBox Width="300" Margin="0,0,12,0"
ItemsSource="{Binding DestinationSlots}"
SelectedItem="{Binding SelectedDestinationSlot}"
DisplayMemberBinding="{Binding Display}" />
<Button Content="Move" Command="{Binding MoveMediumCommand}" Width="80" />
</StackPanel>
<TextBlock Classes="hint"
TextWrapping="Wrap"
Text="Use this for slot-to-slot moves that are not a simple load/unload." />
</StackPanel>
</Expander>
</StackPanel>
</controls:GroupBox>
<TextBlock Grid.Row="3" Text="Inventory" FontWeight="SemiBold" Margin="0,0,0,4" IsVisible="{Binding IsTapeLibraryUi}" />
<DataGrid Grid.Row="4"
ItemsSource="{Binding LibraryInventory}"
IsVisible="{Binding IsTapeLibraryUi}"
AutoGenerateColumns="False"
IsReadOnly="True">
<DataGrid.Columns>
<DataGridTextColumn Header="Slot" Binding="{Binding Slot}" Width="100" />
<DataGridTextColumn Header="Type" Binding="{Binding ElementType}" Width="160" />
<DataGridTextColumn Header="Barcode" Binding="{Binding Barcode}" Width="200" />
<DataGridTextColumn Header="Cartridge" Binding="{Binding CartridgeId}" Width="*" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</TabItem>
<TabItem Header="Backup">
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<StackPanel Margin="8">
<controls:GroupBox Header="Loaded cartridge">
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,0,0,6" IsVisible="{Binding IsSavedTapeLibrary}">
<TextBlock Text="Drive:" VerticalAlignment="Center" Margin="0,0,8,0" Width="64" />
<ComboBox Width="500"
ItemsSource="{Binding OccupiedLibraryDrives}"
SelectedItem="{Binding SelectedOccupiedDrive}"
DisplayMemberBinding="{Binding Summary}" />
</StackPanel>
<TextBlock FontWeight="SemiBold"
Margin="0,0,0,4"
Text="{Binding BackupLoadedSummaryText}" />
<TextBlock Classes="hint"
TextWrapping="Wrap"
Text="{Binding BackupLoadedHintText}" />
</StackPanel>
</controls:GroupBox>
<controls:GroupBox Header="Backup job">
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,0,0,6">
<TextBlock Text="Job:" VerticalAlignment="Center" Margin="0,0,8,0" Width="64" />
<ComboBox Width="400"
ItemsSource="{Binding BackupJobs}"
SelectedItem="{Binding SelectedBackup}"
DisplayMemberBinding="{Binding Name}" />
</StackPanel>
<TextBlock Classes="hint"
TextWrapping="Wrap"
Text="{Binding JobSummaryText}" />
</StackPanel>
</controls:GroupBox>
<controls:GroupBox Header="Operations" IsEnabled="{Binding IsCartridgeLoaded}">
<StackPanel Orientation="Horizontal">
<Button Content="Run backup" Command="{Binding RunBackupCommand}" Width="120" Margin="0,0,8,0" />
<Button Content="Run restore" Command="{Binding RunRestoreCommand}" Width="120" Margin="0,0,8,0" />
<Button Content="Short erase" Command="{Binding EraseCommand}" Width="100" />
</StackPanel>
</controls:GroupBox>
</StackPanel>
</ScrollViewer>
</TabItem>
<TabItem Header="Settings">
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<StackPanel Margin="8">
<TextBlock Classes="hint"
Margin="0,0,0,8"
TextWrapping="Wrap"
Text="Edits apply to configuration.json used by both console and Avalonia UI." />
<controls:GroupBox Header="Device">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Device mode:" VerticalAlignment="Center" Margin="0,0,0,6" />
<ComboBox Grid.Row="0" Grid.Column="1" Margin="0,0,0,6" Width="200" HorizontalAlignment="Left"
ItemsSource="{Binding DeviceModeOptions}"
SelectedItem="{Binding DeviceMode}" />
<TextBlock Grid.Row="1" Text="Topology:" VerticalAlignment="Center" Margin="0,0,0,6" />
<ComboBox Grid.Row="1" Grid.Column="1" Margin="0,0,0,6" Width="200" HorizontalAlignment="Left"
ItemsSource="{Binding TopologyOptions}"
SelectedItem="{Binding SelectedTopology}" />
<TextBlock Grid.Row="2" Text="Tape path:" VerticalAlignment="Center" Margin="0,0,0,6" />
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding TapePathText}" Margin="0,0,0,6" />
<TextBlock Grid.Row="3" Text="Library path:" VerticalAlignment="Center" Margin="0,0,0,6"
IsVisible="{Binding IsTapeLibraryUi}" />
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding LibraryPathText}" Margin="0,0,0,6"
IsVisible="{Binding IsTapeLibraryUi}" />
<TextBlock Grid.Row="4" Text="Write delay (ms):" VerticalAlignment="Center" Margin="0,0,0,6" />
<TextBox Grid.Row="4" Grid.Column="1" Text="{Binding WriteDelayText}" Width="100" HorizontalAlignment="Left" Margin="0,0,0,6" />
<TextBlock Grid.Row="5" Text="Fail-fast checksum:" VerticalAlignment="Center" />
<CheckBox Grid.Row="5" Grid.Column="1" Content="Abort restore on checksum mismatch" VerticalAlignment="Center"
IsChecked="{Binding FailFastChecksum}" />
</Grid>
</controls:GroupBox>
<controls:GroupBox Header="Emulator">
<StackPanel>
<Grid Margin="0,0,0,6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="Data directory:" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Text="{Binding EmulatorDataDirText}" />
</Grid>
<Grid IsVisible="{Binding IsTapeLibraryUi}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Slots:" VerticalAlignment="Center" Margin="0,0,0,6" />
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding EmulatorSlotCountText}" Width="100" HorizontalAlignment="Left" Margin="0,0,0,6" />
<TextBlock Grid.Row="1" Text="Drives:" VerticalAlignment="Center" Margin="0,0,0,6" />
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding EmulatorDriveCountText}" Width="100" HorizontalAlignment="Left" Margin="0,0,0,6" />
<TextBlock Grid.Row="2" Text="I/E ports:" VerticalAlignment="Center" />
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding EmulatorIePortCountText}" Width="100" HorizontalAlignment="Left" />
</Grid>
</StackPanel>
</controls:GroupBox>
<controls:GroupBox Header="Backup jobs">
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,0,0,8">
<TextBlock Text="Job:" VerticalAlignment="Center" Margin="0,0,8,0" Width="64" />
<ComboBox Width="400"
ItemsSource="{Binding BackupJobs}"
SelectedItem="{Binding SelectedSettingsBackup}"
DisplayMemberBinding="{Binding Name}" />
<CheckBox Content="Disabled" Margin="16,0,0,0" VerticalAlignment="Center"
IsChecked="{Binding BackupDisabled}"
ToolTip.Tip="When checked, aggregations skip this job." />
</StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Name:" VerticalAlignment="Center" Margin="0,0,0,6" />
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding BackupNameText}" Margin="0,0,0,6" />
<TextBlock Grid.Row="1" Text="Barcode:" VerticalAlignment="Center" Margin="0,0,0,6" />
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding BackupBarcodeText}" Margin="0,0,0,6"
ToolTip.Tip="Standalone / single-tape MAM label. Library pools use Tape rotation below." />
<TextBlock Grid.Row="2" Text="LTO generation:" VerticalAlignment="Center" Margin="0,0,0,6" />
<ComboBox Grid.Row="2" Grid.Column="1" Width="140" HorizontalAlignment="Left" Margin="0,0,0,6"
ItemsSource="{Binding LtoGenerationOptions}"
SelectedItem="{Binding BackupLtoGen}" />
<TextBlock Grid.Row="3" Text="Write mode:" VerticalAlignment="Center" Margin="0,0,0,6" />
<ComboBox Grid.Row="3" Grid.Column="1" Width="140" HorizontalAlignment="Left" Margin="0,0,0,6"
ItemsSource="{Binding WriteModeOptions}"
SelectedItem="{Binding BackupWriteMode}" />
<TextBlock Grid.Row="4" Text="Source path:" VerticalAlignment="Center" Margin="0,0,0,6" />
<TextBox Grid.Row="4" Grid.Column="1" Text="{Binding BackupSourcePathText}" Margin="0,0,0,6" />
<TextBlock Grid.Row="5" Text="Destination path:" VerticalAlignment="Center" />
<TextBox Grid.Row="5" Grid.Column="1" Text="{Binding BackupDestinationPathText}" />
</Grid>
<controls:GroupBox Header="Tape rotation (library)" Margin="0,12,0,0" IsVisible="{Binding IsTapeLibraryUi}">
<StackPanel>
<TextBlock Classes="hint" TextWrapping="Wrap" Margin="0,0,0,6"
Text="Media pool for this job. Pick barcodes not assigned to other jobs. Never-used tapes first, then oldest LastUsedUtc. Drive claim is automatic." />
<DataGrid Height="120"
AutoGenerateColumns="False"
IsReadOnly="False"
Margin="0,0,0,6"
ItemsSource="{Binding TapeRows}"
SelectedItem="{Binding SelectedTapeRow}">
<DataGrid.Columns>
<DataGridTextColumn Header="Barcode" Binding="{Binding Barcode}" Width="*" />
<DataGridTextColumn Header="Last used (UTC)" Binding="{Binding LastUsedUtcDisplay}" Width="220" IsReadOnly="True" />
</DataGrid.Columns>
</DataGrid>
<StackPanel Orientation="Horizontal">
<ComboBox Width="320"
Margin="0,0,8,0"
PlaceholderText="Available library barcodes"
ItemsSource="{Binding AvailableTapes}"
SelectedItem="{Binding SelectedAvailableTape}"
DisplayMemberBinding="{Binding Display}" />
<Button Content="Refresh list" Command="{Binding RefreshAvailableTapesCommand}" Width="100" Margin="0,0,8,0"
ToolTip.Tip="Reload unassigned barcodes from library inventory" />
<Button Content="Add tape" Command="{Binding AddTapeCommand}" Width="90" Margin="0,0,8,0" />
<Button Content="Remove selected" Command="{Binding RemoveTapeCommand}" Width="120" />
</StackPanel>
</StackPanel>
</controls:GroupBox>
</StackPanel>
</controls:GroupBox>
<controls:GroupBox Header="Aggregations (schedule waves)" IsVisible="{Binding IsTapeLibraryUi}">
<StackPanel>
<TextBlock Classes="hint" TextWrapping="Wrap" Margin="0,0,0,6"
Text="Worker schedules aggregations only. Each wave runs its jobs sequentially with exclusive lease and automatic drive claim/unload." />
<StackPanel Orientation="Horizontal" Margin="0,0,0,8">
<TextBlock Text="Wave:" VerticalAlignment="Center" Margin="0,0,8,0" Width="64" />
<ComboBox Width="280"
ItemsSource="{Binding Aggregations}"
SelectedItem="{Binding SelectedAggregation}"
DisplayMemberBinding="{Binding Name}"
Margin="0,0,8,0" />
<CheckBox Content="Disabled" VerticalAlignment="Center" Margin="0,0,8,0"
IsChecked="{Binding AggregationDisabled}" />
<Button Content="Add wave" Command="{Binding AddAggregationCommand}" Width="90" Margin="0,0,8,0" />
<Button Content="Remove wave" Command="{Binding RemoveAggregationCommand}" Width="100" />
</StackPanel>
<Grid Margin="0,0,0,8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="Name:" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Text="{Binding AggregationNameText}" />
</Grid>
<TextBlock Text="Jobs in this wave (ordered):" FontWeight="SemiBold" Margin="0,0,0,4" />
<ListBox Height="90" Margin="0,0,0,6"
ItemsSource="{Binding AggregationJobNames}"
SelectedItem="{Binding SelectedAggregationJobName}" />
<StackPanel Orientation="Horizontal" Margin="0,0,0,8">
<ComboBox Width="280" Margin="0,0,8,0"
ItemsSource="{Binding BackupJobs}"
SelectedItem="{Binding SelectedAggregationAddJob}"
DisplayMemberBinding="{Binding Name}"
PlaceholderText="Backup job to add" />
<Button Content="Add job" Command="{Binding AggregationAddJobCommand}" Width="90" Margin="0,0,8,0" />
<Button Content="Remove job" Command="{Binding AggregationRemoveJobCommand}" Width="100" Margin="0,0,8,0" />
<Button Content="Move up" Command="{Binding AggregationMoveJobUpCommand}" Width="80" Margin="0,0,8,0" />
<Button Content="Move down" Command="{Binding AggregationMoveJobDownCommand}" Width="90" />
</StackPanel>
<controls:GroupBox Header="Months (empty = all months)">
<ItemsControl ItemsSource="{Binding MonthOptions}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Label}" IsChecked="{Binding IsChecked}" Margin="0,0,8,4" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</controls:GroupBox>
<controls:GroupBox Header="Weekdays (empty = all days)">
<ItemsControl ItemsSource="{Binding WeekdayOptions}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Label}" IsChecked="{Binding IsChecked}" Margin="0,0,8,4" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</controls:GroupBox>
<controls:GroupBox Header="Run Times (UTC)">
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,0,0,6">
<TextBox Width="80" Text="{Binding NewRunTimeText}" Margin="0,0,8,0" />
<Button Content="Add" Command="{Binding AddRunTimeCommand}" Width="60" Margin="0,0,8,0" />
<Button Content="Remove" Command="{Binding RemoveRunTimeCommand}" Width="70" />
</StackPanel>
<ListBox Height="80"
ItemsSource="{Binding RunTimes}"
SelectedItem="{Binding SelectedRunTime}" />
</StackPanel>
</controls:GroupBox>
<controls:GroupBox Header="Minimum Interval">
<StackPanel Orientation="Horizontal">
<TextBox Width="60" Text="{Binding MinIntervalMinutesText}" Margin="0,0,8,0" />
<TextBlock Text="minutes between wave starts" VerticalAlignment="Center" />
</StackPanel>
</controls:GroupBox>
<TextBlock Classes="hint" Margin="0,6,0,0" Text="{Binding LastRunUtcText}" />
</StackPanel>
</controls:GroupBox>
<StackPanel Orientation="Horizontal" Margin="0,4,0,0">
<Button Content="Save configuration" Command="{Binding SaveConfigurationCommand}" Width="140" Margin="0,0,8,0" />
<Button Content="Reload from disk" Command="{Binding ReloadConfigurationCommand}" Width="130" />
</StackPanel>
</StackPanel>
</ScrollViewer>
</TabItem>
<TabItem Header="Service">
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<StackPanel Margin="8">
<TextBlock Classes="hint" TextWrapping="Wrap" Margin="0,0,0,8"
Text="Install, start, and stop the LTO backup Worker (Windows service or systemd). Requires elevation for install/uninstall." />
<controls:GroupBox Header="Worker status">
<StackPanel>
<TextBlock FontWeight="SemiBold" Margin="0,0,0,6" Text="{Binding ServiceStatusText}" />
<TextBlock Classes="hint" TextWrapping="Wrap" Margin="0,0,0,8" Text="{Binding ServiceExePathText}" />
<StackPanel Orientation="Horizontal">
<Button Content="Refresh" Command="{Binding ServiceRefreshCommand}" Width="90" Margin="0,0,8,0" />
<Button Content="Install" Command="{Binding ServiceInstallCommand}" Width="90" Margin="0,0,8,0" />
<Button Content="Uninstall" Command="{Binding ServiceUninstallCommand}" Width="90" Margin="0,0,8,0" />
<Button Content="Start" Command="{Binding ServiceStartCommand}" Width="90" Margin="0,0,8,0" />
<Button Content="Stop" Command="{Binding ServiceStopCommand}" Width="90" />
</StackPanel>
</StackPanel>
</controls:GroupBox>
<controls:GroupBox Header="Service settings">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Service name:" VerticalAlignment="Center" Margin="0,0,0,6" />
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding ServiceNameText}" Margin="0,0,0,6" />
<TextBlock Grid.Row="1" Text="Description:" VerticalAlignment="Center" Margin="0,0,0,6" />
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding ServiceDescriptionText}" Margin="0,0,0,6" />
<TextBlock Grid.Row="2" Text="Executable path:" VerticalAlignment="Center" />
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding ServiceExePathSettingText}"
PlaceholderText="Optional override; empty = resolve next to UI" />
</Grid>
</controls:GroupBox>
</StackPanel>
</ScrollViewer>
</TabItem>
</TabControl>
<controls:GroupBox Grid.Row="3" Header="Log" Margin="0,8,0,0" Padding="6">
<TextBox Name="LogTextBox"
Text="{Binding LogText, Mode=OneWay}"
AcceptsReturn="True"
TextWrapping="Wrap"
IsReadOnly="True"
BorderThickness="0"
Background="Transparent" />
</controls:GroupBox>
</Grid>
</Window>