Public Access
Redraw the host keys screen with its pins' own facts beside it
This commit is contained in:
@@ -15,128 +15,210 @@
|
||||
|
||||
The data layer did not move and did not change. Every pin is still a vault item, still end-to-end
|
||||
encrypted, still synced; see KnownHostSecret. What is here is a screen over VaultViewModel.KnownHostPins.
|
||||
|
||||
v5c-3: restyled against KnownHosts.dc.html — still a main-chrome screen (the rail's own Keys item stays
|
||||
the way in), a 40px back arrow to Keychain rather than the old link inside it, a bordered radius-12
|
||||
container in place of the plain list-and-sidebar split, and a Copy fingerprint button beside Withdraw
|
||||
pin. Everything the screen could do before still can: Filter, Selected, ForgetSelectedCommand and the
|
||||
provenance notes below are unchanged, just repainted.
|
||||
-->
|
||||
|
||||
<Grid ColumnDefinitions="*,244">
|
||||
<UserControl.Styles>
|
||||
<!--
|
||||
A row, not a card: Track fill on hover, Track fill plus an accent ring on the selected one, matching
|
||||
the design's own "selected = Track bg + accent ring, hover Track" — the same idiom SnippetsScreen's
|
||||
own ListBox.snipcards uses, at this design's own 8px radius rather than that one's 10.
|
||||
-->
|
||||
<Style Selector="ListBox.pinrows > ListBoxItem /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="CornerRadius" Value="8" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
</Style>
|
||||
<Style Selector="ListBox.pinrows > ListBoxItem:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource Track}" />
|
||||
</Style>
|
||||
<Style Selector="ListBox.pinrows > ListBoxItem:selected /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource Track}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource Accent}" />
|
||||
</Style>
|
||||
<Style Selector="ListBox.pinrows > ListBoxItem:selected:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource Track}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource Accent}" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
|
||||
<Grid Grid.Column="0" RowDefinitions="Auto,Auto,*">
|
||||
<Grid RowDefinitions="Auto,Auto,*" Margin="26">
|
||||
|
||||
<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="HOST KEYS" FontSize="12"
|
||||
FontWeight="SemiBold" LetterSpacing="1" Foreground="{StaticResource Text}"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock Grid.Column="1" Classes="mono" Text="{Binding Summary}" FontSize="10.5"
|
||||
Foreground="{StaticResource TextFaint}" Margin="10,0,0,0"
|
||||
TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
|
||||
|
||||
<!--
|
||||
Matches fingerprints as well as host names, which is the point of it. What somebody does with
|
||||
this screen is check whether a published SHA256:… is the one they approved, and searching only
|
||||
by name would answer a different question.
|
||||
-->
|
||||
<TextBox Grid.Column="2" x:Name="PinFilter" Text="{Binding Filter}" Width="240"
|
||||
PlaceholderText="filter by host or fingerprint" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
<!-- ============ HEADER ============ -->
|
||||
<Grid Grid.Row="0" ColumnDefinitions="Auto,Auto,Auto,*,Auto">
|
||||
<Button Grid.Column="0" Classes="ghost" Width="40" Height="40" Padding="0"
|
||||
Command="{Binding BackCommand}" ToolTip.Tip="Back to Keychain">
|
||||
<TextBlock FontFamily="{StaticResource IconFont}" Text="" FontSize="17"
|
||||
Foreground="{StaticResource TextFaint}" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Button>
|
||||
<TextBlock Grid.Column="1" Text="Host keys" FontSize="33" FontWeight="Bold" LetterSpacing="-0.5"
|
||||
Foreground="{StaticResource Text}" VerticalAlignment="Center" Margin="14,0,0,0" />
|
||||
<Border Grid.Column="2" MinWidth="30" Height="26" CornerRadius="8" Padding="8,0" Margin="12,0,0,0"
|
||||
Background="{StaticResource Chip}" VerticalAlignment="Center" IsVisible="{Binding HasPins}">
|
||||
<TextBlock Classes="mono" Text="{Binding Count}" FontSize="12.5" FontWeight="SemiBold"
|
||||
Foreground="{StaticResource TextDim}" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Border>
|
||||
|
||||
<Grid Grid.Row="1" ColumnDefinitions="2,1.4*,58,104,*,96" Margin="0,6,14,6"
|
||||
IsVisible="{Binding HasVisiblePins}">
|
||||
<TextBlock Grid.Column="1" Classes="label" Text="HOST" FontSize="9.5" LetterSpacing="1"
|
||||
Margin="12,0,8,0" />
|
||||
<TextBlock Grid.Column="2" Classes="label" Text="PORT" FontSize="9.5" LetterSpacing="1" />
|
||||
<TextBlock Grid.Column="3" Classes="label" Text="ALGORITHM" FontSize="9.5" LetterSpacing="1" />
|
||||
<TextBlock Grid.Column="4" Classes="label" Text="FINGERPRINT" FontSize="9.5" LetterSpacing="1" />
|
||||
<TextBlock Grid.Column="5" Classes="label" Text="APPROVED" FontSize="9.5" LetterSpacing="1" />
|
||||
</Grid>
|
||||
|
||||
<ListBox Grid.Row="2" x:Name="PinList" Focusable="True"
|
||||
ItemsSource="{Binding VisiblePins}"
|
||||
SelectedItem="{Binding Selected}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:KnownHostRowViewModel">
|
||||
<Grid ColumnDefinitions="2,1.4*,58,104,*,96" Margin="0,7,14,7">
|
||||
<Border Grid.Column="0" Classes="rowmark" />
|
||||
<TextBlock Grid.Column="1" Classes="mono" Text="{Binding Host}" FontSize="12"
|
||||
FontWeight="Medium" Foreground="{StaticResource Text}" Margin="12,0,8,0"
|
||||
TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
|
||||
<TextBlock Grid.Column="2" Classes="mono" Text="{Binding Port}" FontSize="10.5"
|
||||
Foreground="{StaticResource TextDim}" VerticalAlignment="Center" />
|
||||
<TextBlock Grid.Column="3" Classes="mono" Text="{Binding Algorithm}" FontSize="10"
|
||||
Foreground="{StaticResource TextDim}" Margin="0,0,8,0"
|
||||
TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
|
||||
<!--
|
||||
Never trimmed, and this column is why the table is laid out the way it is. The only thing
|
||||
anybody does with a fingerprint is compare it character by character against one an operator
|
||||
published; an ellipsis in the middle turns that into a glance, which is the habit the whole
|
||||
mechanism exists to replace.
|
||||
-->
|
||||
<TextBlock Grid.Column="4" Classes="mono" Text="{Binding Fingerprint}" FontSize="10.5"
|
||||
Foreground="{StaticResource TextFaint}" Margin="0,0,8,0"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock Grid.Column="5" Classes="mono" Text="{Binding Approved}" FontSize="10"
|
||||
Foreground="{StaticResource TextFaint}" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<TextBlock Grid.Row="2" Classes="hint" Text="{Binding EmptyMessage}" FontSize="12"
|
||||
Margin="24" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
TextAlignment="Center" MaxWidth="340"
|
||||
IsVisible="{Binding !HasVisiblePins}" />
|
||||
|
||||
<!--
|
||||
Matches fingerprints as well as host names, which is the point of it. What somebody does with
|
||||
this screen is check whether a published SHA256:… is the one they approved, and searching only
|
||||
by name would answer a different question.
|
||||
-->
|
||||
<TextBox Grid.Column="4" x:Name="PinFilter" Text="{Binding Filter}" Width="320" Height="40"
|
||||
PlaceholderText="filter by host or fingerprint" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
|
||||
<Border Grid.Column="1" Background="{StaticResource Sidebar}"
|
||||
BorderBrush="{StaticResource Border}" BorderThickness="1,0,0,0">
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="14,16" Spacing="6">
|
||||
<TextBlock Grid.Row="1" Classes="hint" FontSize="12" Margin="0,10,0,0" TextWrapping="Wrap"
|
||||
Text="Every pin is a decision recorded at the moment of connecting. Fingerprints are never trimmed — compare them character by character against what the operator published." />
|
||||
|
||||
<TextBlock Classes="hint" FontSize="12"
|
||||
Text="Choose a pinned key to see it in full, and to withdraw it."
|
||||
IsVisible="{Binding !HasSelection}" />
|
||||
<!-- ============ THE TABLE ============ -->
|
||||
<Border Grid.Row="2" Margin="0,18,0,0" CornerRadius="12" BorderBrush="{StaticResource Border}"
|
||||
BorderThickness="1" ClipToBounds="True">
|
||||
<Grid ColumnDefinitions="*,320">
|
||||
|
||||
<StackPanel Spacing="6" IsVisible="{Binding HasSelection}">
|
||||
<TextBlock Classes="mono" Text="{Binding Selected.Label}" FontSize="13"
|
||||
FontWeight="SemiBold" Foreground="{StaticResource Text}" TextWrapping="Wrap" />
|
||||
<Grid Grid.Column="0" Background="{StaticResource Pane}" RowDefinitions="Auto,*">
|
||||
|
||||
<Border Classes="chip warn" HorizontalAlignment="Left"
|
||||
IsVisible="{Binding !Selected.IsDialledByAHost}">
|
||||
<TextBlock Text="no host uses this" />
|
||||
</Border>
|
||||
<Border Grid.Row="0" Padding="24,14,24,10" BorderBrush="{StaticResource Border}"
|
||||
BorderThickness="0,0,0,1" IsVisible="{Binding HasVisiblePins}">
|
||||
<Grid ColumnDefinitions="2,1.4*,58,104,*,96">
|
||||
<TextBlock Grid.Column="1" Classes="label" Text="HOST" FontSize="9.5" LetterSpacing="1"
|
||||
Margin="12,0,8,0" />
|
||||
<TextBlock Grid.Column="2" Classes="label" Text="PORT" FontSize="9.5" LetterSpacing="1" />
|
||||
<TextBlock Grid.Column="3" Classes="label" Text="ALGORITHM" FontSize="9.5" LetterSpacing="1" />
|
||||
<TextBlock Grid.Column="4" Classes="label" Text="FINGERPRINT" FontSize="9.5" LetterSpacing="1" />
|
||||
<TextBlock Grid.Column="5" Classes="label" Text="APPROVED" FontSize="9.5" LetterSpacing="1" />
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<TextBlock Classes="label" Text="FINGERPRINT" Margin="0,12,0,4" />
|
||||
<Border Background="{StaticResource Raised}" BorderBrush="{StaticResource Border}"
|
||||
BorderThickness="1" CornerRadius="4" Padding="8">
|
||||
<SelectableTextBlock Classes="mono" Text="{Binding Selected.Fingerprint}"
|
||||
FontSize="10.5" Foreground="{StaticResource TextDim}"
|
||||
TextWrapping="Wrap" />
|
||||
</Border>
|
||||
<ListBox Grid.Row="1" x:Name="PinList" Classes="pinrows" Focusable="True" Margin="12,8"
|
||||
ItemsSource="{Binding VisiblePins}" SelectedItem="{Binding Selected}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:KnownHostRowViewModel">
|
||||
<Grid ColumnDefinitions="2,1.4*,58,104,*,96" Margin="12">
|
||||
<Border Grid.Column="0" Classes="rowmark" />
|
||||
<TextBlock Grid.Column="1" Classes="mono" Text="{Binding Host}" FontSize="12.5"
|
||||
FontWeight="Medium" Foreground="{StaticResource Text}" Margin="12,0,8,0"
|
||||
TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
|
||||
<TextBlock Grid.Column="2" Classes="mono" Text="{Binding Port}" FontSize="11"
|
||||
Foreground="{StaticResource TextDim}" VerticalAlignment="Center" />
|
||||
<TextBlock Grid.Column="3" Classes="mono" Text="{Binding Algorithm}" FontSize="10.5"
|
||||
Foreground="{StaticResource TextDim}" Margin="0,0,8,0"
|
||||
TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
|
||||
<!--
|
||||
Never trimmed, and this column is why the table is laid out the way it is. The only thing
|
||||
anybody does with a fingerprint is compare it character by character against one an
|
||||
operator published; an ellipsis in the middle turns that into a glance, which is the habit
|
||||
the whole mechanism exists to replace.
|
||||
-->
|
||||
<TextBlock Grid.Column="4" Classes="mono" Text="{Binding Fingerprint}" FontSize="10.5"
|
||||
Foreground="{StaticResource TextFaint}" Margin="0,0,8,0"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock Grid.Column="5" Classes="mono" Text="{Binding Approved}" FontSize="10.5"
|
||||
Foreground="{StaticResource TextFaint}" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<TextBlock Classes="label" Text="APPROVED" Margin="0,12,0,4" />
|
||||
<TextBlock Classes="mono" Text="{Binding Selected.Approved}" FontSize="11"
|
||||
Foreground="{StaticResource TextDim}" />
|
||||
<!--
|
||||
Said rather than implied. No vault item carries a timestamp, so this date is read back out of
|
||||
the item's own version 7 id — which records when the pin was created and knows nothing about
|
||||
it being re-approved since. Presenting that as "last used" would be inventing a fact.
|
||||
-->
|
||||
<TextBlock Classes="hint" FontSize="11" TextWrapping="Wrap"
|
||||
Text="Taken from the item's identifier, so it is when this key was first approved — not when it was last checked. Nothing here records that." />
|
||||
<TextBlock Grid.Row="1" Classes="hint" Text="{Binding EmptyMessage}" FontSize="12"
|
||||
Margin="24" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
TextAlignment="Center" MaxWidth="340" IsVisible="{Binding !HasVisiblePins}" />
|
||||
|
||||
<TextBlock Classes="hint" FontSize="11" TextWrapping="Wrap" Margin="0,12,0,0"
|
||||
Text="A pin outlives whatever it was approved for: deleting a host leaves it, and so does changing a host's address. That is deliberate — trust is about the endpoint, not the bookmark." />
|
||||
</Grid>
|
||||
|
||||
<Button Classes="danger" Content="FORGET THIS HOST KEY" Margin="0,12,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
Command="{Binding ForgetSelectedCommand}"
|
||||
ToolTip.Tip="Withdraws trust. The next connection to this endpoint asks you to check the fingerprint again, which is the safe direction to be wrong in — and it is the way back from a server that was legitimately rebuilt." />
|
||||
</StackPanel>
|
||||
<!-- ============ DETAIL SIDEBAR ============ -->
|
||||
<Border Grid.Column="1" Background="{StaticResource Sidebar}"
|
||||
BorderBrush="{StaticResource Border}" BorderThickness="1,0,0,0">
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="20,22" Spacing="14">
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
<TextBlock Classes="hint" FontSize="12"
|
||||
Text="Choose a pinned key to see it in full, and to withdraw it."
|
||||
IsVisible="{Binding !HasSelection}" />
|
||||
|
||||
<StackPanel Spacing="14" IsVisible="{Binding HasSelection}">
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="12">
|
||||
<Border Width="36" Height="36" CornerRadius="10" Background="{StaticResource AccentSoft}">
|
||||
<TextBlock FontFamily="{StaticResource IconFont}" Text="" FontSize="17"
|
||||
Foreground="{StaticResource AccentText}" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Border>
|
||||
<StackPanel Spacing="4" VerticalAlignment="Center">
|
||||
<TextBlock Classes="mono" Text="{Binding Selected.Host}" FontSize="13.5" FontWeight="Bold"
|
||||
Foreground="{StaticResource Text}" TextWrapping="Wrap" />
|
||||
<TextBlock Classes="mono" Text="{Binding Selected.Label}" FontSize="10.5"
|
||||
Foreground="{StaticResource TextDim}" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<Border Classes="chip warn" Background="{StaticResource WarnWash}" HorizontalAlignment="Left"
|
||||
IsVisible="{Binding !Selected.IsDialledByAHost}">
|
||||
<TextBlock Text="no host uses this" />
|
||||
</Border>
|
||||
|
||||
<StackPanel Spacing="6">
|
||||
<TextBlock Classes="label" Text="FINGERPRINT" />
|
||||
<Border Background="{StaticResource Pane}" BorderBrush="{StaticResource Border}"
|
||||
BorderThickness="1" CornerRadius="10" Padding="12,10">
|
||||
<SelectableTextBlock Classes="mono" Text="{Binding Selected.Fingerprint}"
|
||||
FontSize="10.5" Foreground="{StaticResource TextDim}"
|
||||
TextWrapping="Wrap" LineHeight="16" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Spacing="6">
|
||||
<TextBlock Classes="label" Text="APPROVED" />
|
||||
<TextBlock Classes="mono" Text="{Binding Selected.Approved}" FontSize="11.5"
|
||||
Foreground="{StaticResource TextDim}" TextWrapping="Wrap" />
|
||||
<!--
|
||||
Said rather than implied. No vault item carries a timestamp, so this date is read back out
|
||||
of the item's own version 7 id — which records when the pin was created and knows nothing
|
||||
about it being re-approved since. Presenting that as "last used" would be inventing a fact.
|
||||
-->
|
||||
<TextBlock Classes="hint" FontSize="10.5" TextWrapping="Wrap"
|
||||
Text="Taken from the item's identifier, so it is when this key was first approved — not when it was last checked. Nothing here records that." />
|
||||
</StackPanel>
|
||||
|
||||
<!--
|
||||
◆ VAULT. Real, not decorative — KnownHostRowViewModel.VaultName comes off the same
|
||||
VaultId/VaultName pair HostRowViewModel carries, so this chip never names a vault the pin
|
||||
is not actually stored in.
|
||||
-->
|
||||
<StackPanel Spacing="6">
|
||||
<TextBlock Classes="label" Text="VAULT" />
|
||||
<Border Classes="chip" HorizontalAlignment="Left">
|
||||
<TextBlock Text="{Binding Selected.VaultName}" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Classes="hint" FontSize="10.5" TextWrapping="Wrap" Margin="0,4,0,0"
|
||||
Text="A pin outlives whatever it was approved for: deleting a host leaves it, and so does changing a host's address. That is deliberate — trust is about the endpoint, not the bookmark." />
|
||||
|
||||
<StackPanel Spacing="8" Margin="0,10,0,0">
|
||||
<Button Classes="ghost" Height="38" HorizontalAlignment="Stretch"
|
||||
Content="COPY FINGERPRINT" Command="{Binding CopyFingerprintCommand}"
|
||||
ToolTip.Tip="Fingerprints are public. Puts it on the clipboard in full." />
|
||||
<Button Classes="danger" Height="38" HorizontalAlignment="Stretch"
|
||||
Content="WITHDRAW PIN" Command="{Binding ForgetSelectedCommand}"
|
||||
ToolTip.Tip="Withdraws trust. The next connection to this endpoint asks you to check the fingerprint again, which is the safe direction to be wrong in — and it is the way back from a server that was legitimately rebuilt." />
|
||||
<TextBlock Classes="hint" FontSize="10.5" TextWrapping="Wrap" TextAlignment="Center"
|
||||
Text="The next connection will ask you to approve this host's key again." />
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
|
||||
Reference in New Issue
Block a user