Merge branch 'main' into the Android head

Main grew the screens the host-management plan called for — hosts, pins, snippets, logs,
import, teams — plus the ObjectStore and Import projects behind two of them, and moved
WindowsDeviceKeyStore into the desktop head's Platform folder.

Five of those view models landed in a directory this branch had already moved, so they
join the rest in DodoSSH.Client.Shell: git spotted the rename and put them there, and the
namespaces followed. Shell picks up ObjectStore and Import as a result, which the Android
head then gets transitively and will use neither of at first — scoped storage means there
is no ~/.ssh/config to import, and file transfer is out of its first scope.

Desktop suites green at 155 and 64.
This commit is contained in:
jaap-jan committed 2026-07-31 21:03:22 +02:00
commit 2caedd93ff
199 files changed
+31294 -775

No files matched your search

@@ -0,0 +1,119 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:DodoSSH.Client.Shell.ViewModels"
x:Class="DodoSSH.Client.App.Views.ImportScreen"
x:DataType="vm:ImportViewModel">
<!--
Importing ~/.ssh/config.
A preview and then a button, rather than one action, and that is the whole design. This reads a file
the application did not write, out of the user's home directory, and a real ssh_config often holds
forty entries for machines that stopped existing years ago. So scanning writes nothing and the list
says what each entry means; importing is a separate press on a set somebody has looked at.
Reachable from the preferences screen and not from the nav rail. It is a task rather than a
destination — done once, or once a year — and a seventh rail entry would cost every screen a slot for
something almost nobody is looking at.
-->
<Grid RowDefinitions="Auto,Auto,Auto,*,Auto">
<Border Grid.Row="0" Padding="14,0" Height="44"
BorderBrush="{StaticResource Border}" BorderThickness="0,0,0,1">
<Grid ColumnDefinitions="Auto,*,Auto" VerticalAlignment="Center">
<TextBlock Grid.Column="0" Classes="mono" Text="IMPORT SSH CONFIG" FontSize="11"
FontWeight="SemiBold" LetterSpacing="1" Foreground="{StaticResource Text}"
VerticalAlignment="Center" />
<TextBlock Grid.Column="1" Classes="mono" Text="{Binding ConfigPath}" FontSize="9.5"
Foreground="{StaticResource TextFaint}" Margin="10,0" VerticalAlignment="Center"
TextTrimming="CharacterEllipsis" />
<Button Grid.Column="2" Classes="ghost" Content="SCAN" Command="{Binding ScanCommand}"
IsEnabled="{Binding !IsBusy}"
ToolTip.Tip="Reads the file and shows what it found. Nothing is stored." />
</Grid>
</Border>
<TextBlock Grid.Row="1" Classes="hint" Text="{Binding Status}" FontSize="11" Margin="14,12,14,0"
TextWrapping="Wrap" />
<!--
What could not be honoured, above the list rather than beside it. Every one of these is a way the
import is quieter than the file — an ignored Match block, a dropped ProxyCommand — and a person
comparing the two needs to be told before they conclude the parser lost something.
-->
<Border Grid.Row="2" Margin="14,12,14,0" Padding="10,8" CornerRadius="4"
Background="{StaticResource WarnWash}" BorderBrush="{StaticResource WarnSoft}"
BorderThickness="1" IsVisible="{Binding HasWarnings}">
<ItemsControl ItemsSource="{Binding Warnings}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="x:String">
<TextBlock Text="{Binding}" Foreground="{StaticResource WarnText}" FontSize="10"
TextWrapping="Wrap" Margin="0,2" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
<Grid Grid.Row="3" RowDefinitions="Auto,*" Margin="0,12,0,0" IsVisible="{Binding HasRows}">
<Grid Grid.Row="0" ColumnDefinitions="34,1.1*,1.4*,1.6*,96" Margin="14,0,14,6">
<TextBlock Grid.Column="1" Classes="label" Text="NAME" FontSize="8.5" LetterSpacing="1" />
<TextBlock Grid.Column="2" Classes="label" Text="ADDRESS" FontSize="8.5" LetterSpacing="1" />
<TextBlock Grid.Column="3" Classes="label" Text="AUTHENTICATION" FontSize="8.5" LetterSpacing="1" />
<TextBlock Grid.Column="4" Classes="label" Text="STATE" FontSize="8.5" LetterSpacing="1" />
</Grid>
<ScrollViewer Grid.Row="1">
<ItemsControl ItemsSource="{Binding Rows}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:ImportRowViewModel">
<StackPanel Margin="14,0">
<Grid ColumnDefinitions="34,1.1*,1.4*,1.6*,96" Margin="0,7">
<CheckBox Grid.Column="0" IsChecked="{Binding IsSelected}" VerticalAlignment="Center" />
<TextBlock Grid.Column="1" Classes="mono" Text="{Binding Alias}" FontSize="11"
FontWeight="Medium" Foreground="{StaticResource Text}" Margin="0,0,8,0"
TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
<TextBlock Grid.Column="2" Classes="mono" Text="{Binding Address}" FontSize="9.5"
Foreground="{StaticResource TextDim}" Margin="0,0,8,0"
TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
<TextBlock Grid.Column="3" Classes="mono" Text="{Binding Authentication}" FontSize="9.5"
Foreground="{StaticResource TextFaint}" Margin="0,0,8,0"
TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
<Border Grid.Column="4" Classes="chip" HorizontalAlignment="Left"
VerticalAlignment="Center" IsVisible="{Binding HasBadge}">
<TextBlock Text="{Binding Badge}" FontSize="8.5" />
</Border>
</Grid>
<TextBlock Classes="hint" Text="{Binding Warnings}" FontSize="9.5" Margin="34,0,0,8"
TextWrapping="Wrap" Foreground="{StaticResource WarnText}"
IsVisible="{Binding HasWarnings}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
<Border Grid.Row="4" Padding="14,10" Background="{StaticResource Panel}"
BorderBrush="{StaticResource Border}" BorderThickness="0,1,0,0"
IsVisible="{Binding HasRows}">
<StackPanel Spacing="8">
<!--
Said before the button, not after. A key path is recorded and the key itself is not read: that is
the difference between a bookmark that connects and one that asks for a password, and somebody
who is not told will conclude the import was broken.
-->
<TextBlock Classes="hint" FontSize="10" TextWrapping="Wrap"
Text="Key files are not read. Where ssh_config names an IdentityFile the path is recorded as a note, and the host asks for a password until you bind it to a key in your keychain. Nothing here reaches into ~/.ssh for private key material." />
<StackPanel Orientation="Horizontal" Spacing="8">
<Button Classes="accent" Content="{Binding ImportLabel}" Command="{Binding ImportCommand}"
IsEnabled="{Binding !IsBusy}" />
<Button Classes="ghost" Content="TICK ALL / NONE" Command="{Binding ToggleAllCommand}" />
</StackPanel>
</StackPanel>
</Border>
</Grid>
</UserControl>