Public Access
The terminal surface with no session used to be an empty frame, with the tab row's "+" the only thing on it that did anything. It now holds a prompt, in the order ssh asks: "connect to:" takes a saved host's name, matched exactly, with the hosts that match so far listed under it for Tab or a click, or user@host[:port] for a machine that is not saved. A password line follows only when one is needed, worded as ssh words it. Escape steps back a line; recent connections are listed below. It is drawn by Avalonia in the WebView's rectangle, which the occlusion rule allows because the two are exclusive: the prompt shows only while there are no tabs, the WebView only for a tab with a session. Saved hosts connect through a new VaultViewModel.ConnectToHostAsync, which the drawer's own connect now goes through too, so every refusal is still made once. Each attempt now carries whether it may act on the drawer's remember-password tick, and the prompt's may not: otherwise a tick left on in a closed drawer would save a password typed at a prompt that never offered to keep anything. Covered by nine ShellFlowTests cases and four ConnectPromptTests; manual check 7.10b.
114 lines
6.4 KiB
XML
114 lines
6.4 KiB
XML
<UserControl xmlns="https://github.com/avaloniaui"
|
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
xmlns:vm="using:DodoSSH.Client.Shell.ViewModels"
|
|
xmlns:views="using:DodoSSH.Client.App.Views"
|
|
x:Class="DodoSSH.Client.App.Views.ConnectPrompt"
|
|
x:DataType="vm:MainWindowViewModel"
|
|
Background="{StaticResource Pane}">
|
|
|
|
<!--
|
|
What the terminal's rectangle holds when Terminal is pressed with nothing open: a prompt, because asking
|
|
is what a terminal with nothing in it does. It used to be an empty frame, with the tab row's "+" the only
|
|
thing on it that did anything.
|
|
|
|
Two lines, in the order ssh itself asks. The first takes a saved host's name — matched exactly, with the
|
|
names that match so far printed under it for Tab or a click to take — or user@host[:port] for a machine
|
|
that is not saved. The second appears only when a password is needed, worded as ssh words it, and is
|
|
never offered for a host whose keychain binding supplies its own credential.
|
|
|
|
Drawn by Avalonia in the WebView's own rectangle, which the occlusion rule allows only because the two
|
|
are exclusive: this shows while there are no tabs, and the WebView only for a selected tab with a
|
|
session. See MainWindowViewModel.IsConnectPromptShowing.
|
|
|
|
Everything it does goes through the vault's own connect paths — ConnectToHostAsync for a saved host,
|
|
ConnectToEndpointAsync for an address — so every refusal those make is made here too, and nothing typed
|
|
at this prompt is written to the keychain.
|
|
-->
|
|
<ScrollViewer HorizontalScrollBarVisibility="Disabled">
|
|
<StackPanel Margin="18,16" Spacing="3" TextElement.FontFamily="{StaticResource MonoFont}">
|
|
|
|
<TextBlock Classes="mono" FontSize="13" Foreground="{StaticResource TextFaint}" TextWrapping="Wrap"
|
|
Text="No session open. Connect to a saved host by name, or to any machine as user@host[:port]." />
|
|
|
|
<!-- ============ what to connect to ============ -->
|
|
<Grid ColumnDefinitions="Auto,*" Margin="0,14,0,0">
|
|
<TextBlock Grid.Column="0" Classes="mono" FontSize="13" FontWeight="Bold"
|
|
Foreground="{StaticResource AccentText}" Text="connect to:" VerticalAlignment="Center" />
|
|
<TextBox Grid.Column="1" x:Name="Target" Classes="prompt" Margin="8,0,0,0"
|
|
Text="{Binding PromptText}" IsReadOnly="{Binding IsPromptAskingForPassword}" />
|
|
</Grid>
|
|
|
|
<!--
|
|
The saved hosts that match so far, best first, the first one Tab's. A wrapped row rather than a list,
|
|
because this is a shell's completion line and not a picker: a name and an address, nothing to scroll.
|
|
-->
|
|
<ItemsControl ItemsSource="{Binding PromptMatches}" Margin="0,2,0,0"
|
|
IsVisible="{Binding ShowsPromptMatches}">
|
|
<ItemsControl.ItemsPanel>
|
|
<ItemsPanelTemplate>
|
|
<WrapPanel />
|
|
</ItemsPanelTemplate>
|
|
</ItemsControl.ItemsPanel>
|
|
<ItemsControl.ItemTemplate>
|
|
<DataTemplate x:DataType="vm:HostRowViewModel">
|
|
<Button Classes="flat" Focusable="False" Padding="6,2" Margin="0,0,6,0" CornerRadius="5"
|
|
Cursor="Hand" ToolTip.Tip="{Binding Address}"
|
|
Command="{Binding $parent[views:ConnectPrompt].((vm:MainWindowViewModel)DataContext).ChoosePromptMatchCommand}"
|
|
CommandParameter="{Binding}">
|
|
<TextBlock Classes="mono" FontSize="12" Foreground="{StaticResource TextDim}"
|
|
Text="{Binding Label}" />
|
|
</Button>
|
|
</DataTemplate>
|
|
</ItemsControl.ItemTemplate>
|
|
</ItemsControl>
|
|
|
|
<!-- ============ the password, when one is needed ============ -->
|
|
<Grid x:Name="PasswordLine" ColumnDefinitions="Auto,*" IsVisible="{Binding IsPromptAskingForPassword}">
|
|
<TextBlock Grid.Column="0" Classes="mono" FontSize="13" Foreground="{StaticResource Text}"
|
|
Text="{Binding PromptPasswordLabel}" VerticalAlignment="Center" />
|
|
<TextBox Grid.Column="1" x:Name="Password" Classes="prompt" Margin="8,0,0,0" PasswordChar="•"
|
|
Text="{Binding PromptPassword}" />
|
|
</Grid>
|
|
|
|
<TextBlock Classes="mono" FontSize="12.5" TextWrapping="Wrap" Margin="0,4,0,0"
|
|
Foreground="{StaticResource WarnText}"
|
|
Text="{Binding PromptRefusal}" IsVisible="{Binding HasPromptRefusal}" />
|
|
|
|
<TextBlock Classes="mono" FontSize="10.5" Margin="0,10,0,0" Foreground="{StaticResource TextGhost}"
|
|
Text="ENTER to connect · TAB to complete · ESC to go back · CTRL+K to search hosts" />
|
|
|
|
<!--
|
|
============ where you have been ============
|
|
|
|
The machines most recently connected to, newest first — the vault's own connection log, deduplicated
|
|
by address, so it survives a restart and arrives on a new machine with the keychain. A click puts the
|
|
row on the prompt line and carries on from there exactly as if it had been typed: a saved host
|
|
connects, or asks for its password; an address asks for its password.
|
|
-->
|
|
<StackPanel Margin="0,22,0,0" Spacing="1" IsVisible="{Binding HasRecentConnections}">
|
|
<TextBlock Classes="mono" FontSize="11" Foreground="{StaticResource TextFaint}" Text="recent"
|
|
Margin="0,0,0,4" />
|
|
<ItemsControl ItemsSource="{Binding RecentConnections}">
|
|
<ItemsControl.ItemTemplate>
|
|
<DataTemplate x:DataType="vm:ConnectionLogRowViewModel">
|
|
<Button Classes="flat" Focusable="False" Padding="6,3" CornerRadius="5" Cursor="Hand"
|
|
HorizontalAlignment="Left"
|
|
Command="{Binding $parent[views:ConnectPrompt].((vm:MainWindowViewModel)DataContext).ChoosePromptRecentCommand}"
|
|
CommandParameter="{Binding}">
|
|
<StackPanel Orientation="Horizontal" Spacing="14">
|
|
<TextBlock Classes="mono" FontSize="12.5" Foreground="{StaticResource TextDim}"
|
|
Text="{Binding Address}" />
|
|
<TextBlock Classes="mono" FontSize="12" Foreground="{StaticResource TextFaint}"
|
|
Text="{Binding HostLabel}" />
|
|
</StackPanel>
|
|
</Button>
|
|
</DataTemplate>
|
|
</ItemsControl.ItemTemplate>
|
|
</ItemsControl>
|
|
</StackPanel>
|
|
|
|
</StackPanel>
|
|
</ScrollViewer>
|
|
|
|
</UserControl>
|