Public Access
Ctrl+K reaches every vault the session holds a key for — that is deliberate, and it is what makes the palette worth opening from anywhere. What it did not do was say where a result came from. A team and a person who both call a machine prod-db got two identical rows, and Enter took whichever the ranking happened to put first: the same three characters, two different machines, depending on nothing anybody could see. Nothing new is computed for this. HostRowViewModel.VaultBadge has been filled in since the host list learned to span vaults, and it is already drawn on the hosts board, the keychain, the snippets list and the known-hosts pane. The palette was the one list reaching across every vault that did not print it. ◆ THE VAULT IS THE LAST OF THE FOUR RANKS, AND THAT IS THE POINT. A vault name is the widest reading of the three the palette had: one word matches every host in that vault at once, where a name or an address matches one machine. So it sits behind name-starts-with, name-contains and address — otherwise typing a machine's name would bury it under everybody else's. It matches on HasVaultBadge rather than on VaultName, so the search only ever matches what the row actually shows. A session holding one vault prints no vault on any row, and matching it there would answer "personal" with the entire keychain, ranked behind nothing and explained by nothing on screen. In the row, the right-hand column becomes two lines against the two on the left, in the same order: what this is above, how it is reached below. With one vault the badge is empty, the line collapses, and the kind word stays centred exactly where it was. The name is capped and ellipsised because that column is Auto-sized — a long vault name would otherwise take its width out of the host name beside it. Three tests, one per claim. VaultSharingTests puts platform-gateway in the personal vault and prod-db in "Platform secrets" and types "platform": both come back, the gateway first. ShellFlowTests types the personal vault's own name into a one-vault session and gets nothing. QuickConnectTests is the markup's half — the shared vault's row draws PLATFORM SECRETS, and a one-vault row draws no vault at all — with the shared vault put into the session through the layout suite's own StubTeamServer, as the settings pages' suite does it. 449 App tests and 155 layout tests pass. Both suites were run with -p:NuGetAudit=false: SSH.NET 2025.1.0 has picked up GHSA-q939-rpr3-3284 and NU1903 fails restore repo-wide, which predates this branch and is nothing to do with it. No package or lock file is touched here.
131 lines
7.9 KiB
XML
131 lines
7.9 KiB
XML
<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.QuickConnect"
|
|
x:DataType="vm:MainWindowViewModel">
|
|
|
|
<!--
|
|
Ctrl+K: type a few characters of a host's name and press Enter to connect to it.
|
|
|
|
The design's box promises "search hosts · run command". Only the first half is here, because there is no
|
|
snippet or saved-command item type in the vault for the second half to run — see
|
|
docs/design-import-gaps.md.
|
|
|
|
It connects through the vault's own ConnectCommand rather than opening a session itself, so it inherits
|
|
every refusal that path already makes: a key or credential that has been deleted on another machine, a
|
|
host with no username, a host key that has changed. A second connect path would be a second place for
|
|
those to be forgotten.
|
|
|
|
While this is open the shell collapses the terminal's WebView, and that is not a visual nicety — the
|
|
WebView is a native child window that composites above anything Avalonia draws in the same rectangle, so
|
|
an overlay across the middle of this window would be painted underneath it and take no clicks.
|
|
-->
|
|
|
|
<!--
|
|
The wash is named and takes a press, because clicking away from a palette is how every palette closes.
|
|
The handler answers only for presses whose source is the wash itself, which is what separates "outside"
|
|
from "inside": a press anywhere on the card below reports that control as its source and bubbles through
|
|
here on its way to the window.
|
|
-->
|
|
<!-- 60% of Canvas. Written out because a scrim is a brush with an alpha, and the palette holds no alpha
|
|
variant of a surface — see Palette.axaml, where the only pre-multiplied brushes are the accent washes. -->
|
|
<Border x:Name="Backdrop" Background="#9905050A" PointerPressed="OnBackdropPressed">
|
|
<Border Width="640" VerticalAlignment="Top" Margin="0,160,0,0"
|
|
Background="{StaticResource Chrome}" BorderBrush="{StaticResource BorderMid}"
|
|
BorderThickness="1" CornerRadius="14">
|
|
<StackPanel>
|
|
|
|
<!--
|
|
">_" rather than the bare ">" v4 drew: the mock's own prompt glyph, and the underscore is what
|
|
reads as a cursor waiting for the first keystroke rather than a stray angle bracket. There is
|
|
still no drawn caret in the field beside it, for the reason TextBox.address in this file gives —
|
|
it already has a real one.
|
|
-->
|
|
<Border Padding="16,14" BorderBrush="{StaticResource BorderSubtle}" BorderThickness="0,0,0,1">
|
|
<Grid ColumnDefinitions="Auto,*">
|
|
<TextBlock Grid.Column="0" Classes="mono" Text=">_" FontSize="14" FontWeight="Bold"
|
|
Foreground="{StaticResource Accent}" VerticalAlignment="Center" />
|
|
<TextBox Grid.Column="1" x:Name="Query" Text="{Binding SearchText}"
|
|
PlaceholderText="search hosts" Margin="10,0,0,0"
|
|
FontFamily="{StaticResource MonoFont}" FontSize="14"
|
|
Background="Transparent" BorderThickness="0" />
|
|
</Grid>
|
|
</Border>
|
|
|
|
<!--
|
|
Arrow keys move the selection and Enter takes it; the code-behind owns both, because a ListBox that
|
|
took focus would take the arrow keys away from the box being typed into.
|
|
|
|
A click connects rather than merely selecting, which is what a list of hosts under a search box
|
|
looks like it does — the keyboard route already treats choosing a row and connecting to it as one
|
|
act, and a pointer that only highlighted would leave the palette open over a choice already made.
|
|
The gesture is wired in the code-behind, as the sidebar's double-click is.
|
|
|
|
Classes="tiles" clears the theme's full-bleed hover and selection wash the way the hosts grid's own
|
|
cards do — see the remark on Border.qcrow in App.axaml, which is what paints every state here
|
|
instead. Rows rather than cards, but the same reason applies: a square wash behind a rounded shape
|
|
shows its corners.
|
|
-->
|
|
<ListBox x:Name="Results" Classes="tiles" MaxHeight="320" Focusable="False"
|
|
ItemsSource="{Binding SearchResults}"
|
|
SelectedItem="{Binding SelectedSearchResult}">
|
|
<ListBox.ItemTemplate>
|
|
<DataTemplate x:DataType="vm:HostRowViewModel">
|
|
<Border Classes="qcrow" Height="44" CornerRadius="9" Padding="10,0" Margin="8,2">
|
|
<Grid ColumnDefinitions="Auto,*,Auto">
|
|
<!--
|
|
Green when this host has a terminal open right now, grey otherwise — the same two-state
|
|
dot the hosts grid draws, and the same reason: nothing here pings a machine, so a third
|
|
colour would be a claim this application never checks. See Ellipse.dot in App.axaml.
|
|
-->
|
|
<Ellipse Grid.Column="0" Classes="dot" Classes.live="{Binding IsConnected}"
|
|
Width="8" Height="8" VerticalAlignment="Center" />
|
|
<StackPanel Grid.Column="1" Spacing="1" Margin="10,0,0,0" VerticalAlignment="Center">
|
|
<TextBlock Classes="mono qcrow-text" Text="{Binding Label}" FontSize="13"
|
|
FontWeight="Medium" TextTrimming="CharacterEllipsis" />
|
|
<TextBlock Classes="mono qcrow-subtext" Text="{Binding Address}" FontSize="11.5"
|
|
TextTrimming="CharacterEllipsis" />
|
|
</StackPanel>
|
|
<!--
|
|
Two readings stacked against the two on the left, and in the same order: what this row
|
|
IS above, how it is reached below.
|
|
|
|
◆ THE VAULT, and only when there is more than one to be in — HostRowViewModel.VaultBadge
|
|
is empty otherwise, which collapses the line and leaves the kind word centred as it was.
|
|
The palette searches every readable vault at once, so without it two hosts a team and a
|
|
person both call "prod-db" are two identical rows, and Enter takes whichever the ranking
|
|
happened to put first. Trimmed against a cap because the column is Auto-sized: a long
|
|
vault name would otherwise take its width out of the host name beside it.
|
|
|
|
credential / key / password — never the mock's SSH/SFTP kind column. Every palette
|
|
connect here is SSH, so printing that word would be a constant dressed up as a reading;
|
|
see hosts-v5-design-spec.md's deviations and HostRowViewModel.Authentication.
|
|
-->
|
|
<StackPanel Grid.Column="2" Spacing="1" Margin="10,0,0,0" VerticalAlignment="Center">
|
|
<TextBlock Classes="mono qcrow-subtext" Text="{Binding VaultBadge}" FontSize="10"
|
|
HorizontalAlignment="Right" MaxWidth="150"
|
|
TextTrimming="CharacterEllipsis" IsVisible="{Binding HasVaultBadge}" />
|
|
<TextBlock Classes="mono qcrow-subtext" Text="{Binding Authentication}"
|
|
FontSize="11" HorizontalAlignment="Right" />
|
|
</StackPanel>
|
|
</Grid>
|
|
</Border>
|
|
</DataTemplate>
|
|
</ListBox.ItemTemplate>
|
|
</ListBox>
|
|
|
|
<TextBlock Classes="hint" FontSize="12" Margin="18,16"
|
|
Text="No host matches that."
|
|
IsVisible="{Binding !HasSearchResults}" />
|
|
|
|
<Border Padding="16,10" BorderBrush="{StaticResource BorderSubtle}" BorderThickness="0,1,0,0">
|
|
<TextBlock Classes="mono" FontSize="10" Foreground="{StaticResource TextFaint}"
|
|
Text="↑ ↓ to choose · ENTER or click to connect · ESC to close" />
|
|
</Border>
|
|
|
|
</StackPanel>
|
|
</Border>
|
|
</Border>
|
|
|
|
</UserControl>
|