Public Access
Restyle the keychain, the snips and the logs to their v5b shapes
This commit is contained in:
@@ -26,17 +26,117 @@
|
||||
when somebody is in a team — but it is still not a selector, because every table on this screen already
|
||||
spans every keychain this session holds a key for and each row names its own. What it carries instead is
|
||||
the one keychain question with an answer: where a new item is filed.
|
||||
|
||||
── v5b — Keychain.dc.html ──────────────────────────────────────────────────────────────────────────────
|
||||
A fidelity pass over the shape above, not a new one. What moved:
|
||||
|
||||
◆ THE HEADER. "Keychain" 33 bold, a count chip, and on the right a "Host keys" ghost button — the
|
||||
design's own doorway to the pins screen, wired to the shell's existing ShowScreenCommand via
|
||||
$parent[Window] since this screen's own DataContext is the vault rather than the shell — and one
|
||||
"+ New key" accent button rather than the five GENERATE / + SSH KEY / + PASSWORD / + TAG / + BUCKET
|
||||
buttons the toolbar used to spread across the table's own header. ◆ DECIDED DEVIATION: the design draws
|
||||
one button because its mock has one "add" concept; this application has five, and folding five capabilities
|
||||
into one visible button without losing any of them means the button opens a menu naming all five, in the
|
||||
same order the old toolbar had them, rather than guessing which one the design's single button "really"
|
||||
meant. GENERATE keeps its own tooltip; the strip's own essay comment about why it lost the word KEY is now
|
||||
moot — the width pressure that produced it left with the buttons.
|
||||
|
||||
◆ THE RAIL. 200px, Sidebar-bg, KEYCHAIN tracked label, category rows restyled to Button.cat's own idiom
|
||||
(unchanged binding, new width). Below the divider: SCOPES stays — this session's current vault, named,
|
||||
since that fact is real and the design's mock is a single-vault sample with nothing to show it — and then
|
||||
NEW ITEMS FILE TO, the design's own wording for the picker this screen already had as "NEW ITEMS GO TO".
|
||||
Adopted rather than kept: nothing in this file's own essay comments ever defended "GO TO" over "FILE TO",
|
||||
and the comment above the picker already says "where a new item is filed" — the design's word was this
|
||||
screen's own vocabulary already.
|
||||
|
||||
◆ THE TABLE. A 44px sub-toolbar (the section summary, left; a 240px filter box, right — this table had no
|
||||
filter box before, and the design's has one) and tracked column headers. The design's own five are
|
||||
NAME/TYPE/VAULT/USED BY/MODIFIED; MODIFIED is dropped — VaultItem carries no timestamp of any kind (id,
|
||||
secret, version, three sync flags — see docs/design-import-gaps.md) — and USED BY is real for a key or a
|
||||
credential (which hosts authenticate with it, the same fact VaultViewModel.HostsBoundTo already computes
|
||||
for the deletion warning) and empty for a tag (its own host count already covers the same ground) or a
|
||||
bucket (nothing in this codebase resolves a host's authentication to an object store). Rows: a type glyph,
|
||||
the mono name, the type word, and the vault chip — VaultItemRowViewModel.VaultBadge, empty except where
|
||||
more than one vault is in play, the same convention every other list in this application follows.
|
||||
|
||||
◆ THE DETAIL PANE, 300px. PUBLIC KEY draws when the selected key has one stored. FINGERPRINT is not
|
||||
drawn at all — this codebase has never computed one; see docs/design-import-gaps.md's own recorded gap,
|
||||
"no algorithm field, no fingerprint, and computing either means parsing armour the type stores verbatim."
|
||||
USED BY draws real host rows — the same HostsBoundTo scan, with each host's own two-state dot — only for
|
||||
a key or a credential, and only while at least one host actually authenticates with the selected item; the
|
||||
"in use · N hosts" chip beside the vault chip is gated the same way. The action buttons keep their
|
||||
existing commands and their existing honesty: COPY PUBLIC KEY only for a key, MOVE only where
|
||||
CanMoveSelectedItem says there is somewhere to move to, DELETE always. "Choose something on the left…"
|
||||
stays as the empty state, restyled.
|
||||
-->
|
||||
|
||||
<Grid ColumnDefinitions="176,*,244">
|
||||
<Grid RowDefinitions="Auto,*" Margin="26">
|
||||
|
||||
<!-- Categories and scopes -->
|
||||
<!-- ============ THE HEADER ============ -->
|
||||
<Grid Grid.Row="0" Margin="0,0,0,20" ColumnDefinitions="Auto,Auto,*,Auto,Auto">
|
||||
|
||||
<TextBlock Grid.Column="0" Text="Keychain" FontSize="33" FontWeight="Bold" LetterSpacing="-0.5"
|
||||
Foreground="{StaticResource Text}" VerticalAlignment="Center" />
|
||||
|
||||
<Border Grid.Column="1" Margin="12,0,0,0" MinWidth="34" Height="30" CornerRadius="9" Padding="8,0"
|
||||
Background="{StaticResource Chip}" VerticalAlignment="Center">
|
||||
<TextBlock Classes="mono" Text="{Binding TotalItemCount}" FontSize="12.5"
|
||||
Foreground="{StaticResource TextDim}" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Border>
|
||||
|
||||
<!--
|
||||
◆ "Host keys", to the pins screen. $parent[Window] is what reaches the shell from here: this
|
||||
control's own DataContext is the vault, not MainWindowViewModel, and Window is the nearest ancestor
|
||||
whose DataContext is the shell — see MainWindow.axaml, which sets this screen's DataContext to
|
||||
{Binding Vault} rather than putting IsKeychainScreen and the vault on one element.
|
||||
-->
|
||||
<Button Grid.Column="3" Classes="ghost" Height="40" Margin="0,0,10,0"
|
||||
Command="{Binding $parent[Window].((vm:MainWindowViewModel)DataContext).ShowScreenCommand}"
|
||||
CommandParameter="{x:Static vm:ShellScreen.KnownHosts}"
|
||||
ToolTip.Tip="Host keys you have approved, and how to withdraw one">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock FontFamily="{StaticResource IconFont}" FontSize="15" Text=""
|
||||
Foreground="{StaticResource TextFaint}" />
|
||||
<TextBlock Text="Host keys" FontSize="13.5" FontWeight="SemiBold"
|
||||
Foreground="{StaticResource TextDim}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<!--
|
||||
◆ "+ New key": one button standing in for the five this screen can still do. See the file-level
|
||||
remark above for why a menu rather than a guess at which one the design meant.
|
||||
-->
|
||||
<Button Grid.Column="4" Classes="accent" Height="40" FontSize="13.5" Content="+ New key">
|
||||
<Button.Flyout>
|
||||
<MenuFlyout Placement="BottomEdgeAlignedRight">
|
||||
<MenuItem Header="Generate a key…" Command="{Binding NewGeneratedKeyCommand}"
|
||||
ToolTip.Tip="Makes a new key pair here, so the private half never becomes a file on this disk." />
|
||||
<MenuItem Header="Add SSH key…" Command="{Binding NewKeyCommand}"
|
||||
ToolTip.Tip="Pastes in a key you already have." />
|
||||
<MenuItem Header="Add password…" Command="{Binding NewCredentialCommand}" />
|
||||
<MenuItem Header="Add tag…" Command="{Binding NewTagCommand}"
|
||||
ToolTip.Tip="A name to put on hosts. Usually made from a host's editor instead; this is for setting a scheme up before there is anything to put it on." />
|
||||
<MenuItem Header="Add bucket…" Command="{Binding NewObjectStoreCommand}"
|
||||
ToolTip.Tip="An S3-compatible bucket, to browse beside a host on the Files screen." />
|
||||
</MenuFlyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
|
||||
</Grid>
|
||||
|
||||
<!-- ============ THE BORDERED BODY ============ -->
|
||||
<Border Grid.Row="1" BorderBrush="{StaticResource Border}" BorderThickness="1" CornerRadius="12"
|
||||
ClipToBounds="True">
|
||||
<Grid ColumnDefinitions="200,*,300">
|
||||
|
||||
<!-- ============ Categories and scopes ============ -->
|
||||
<Border Grid.Column="0" Background="{StaticResource Sidebar}"
|
||||
BorderBrush="{StaticResource Border}" BorderThickness="0,0,1,0">
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="0,12">
|
||||
<StackPanel Margin="10,16">
|
||||
|
||||
<TextBlock Classes="label" Text="KEYCHAIN" Margin="14,0,14,8" />
|
||||
<TextBlock Classes="label" Text="KEYCHAIN" Margin="12,0,12,10" FontSize="10" />
|
||||
|
||||
<Button Classes="flat cat" Command="{Binding ShowSectionCommand}"
|
||||
CommandParameter="{x:Static vm:VaultSection.All}"
|
||||
@@ -44,7 +144,7 @@
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<Border Grid.Column="0" Classes="rowmark catmark" />
|
||||
<TextBlock Grid.Column="1" Text="ALL" Margin="12,0,0,0" />
|
||||
<TextBlock Grid.Column="2" Text="{Binding TotalItemCount}"
|
||||
<TextBlock Grid.Column="2" Classes="mono" Text="{Binding TotalItemCount}"
|
||||
Foreground="{StaticResource TextFaint}" />
|
||||
</Grid>
|
||||
</Button>
|
||||
@@ -55,7 +155,7 @@
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<Border Grid.Column="0" Classes="rowmark catmark" />
|
||||
<TextBlock Grid.Column="1" Text="SSH KEYS" Margin="12,0,0,0" />
|
||||
<TextBlock Grid.Column="2" Text="{Binding Keys.Count}"
|
||||
<TextBlock Grid.Column="2" Classes="mono" Text="{Binding Keys.Count}"
|
||||
Foreground="{StaticResource TextFaint}" />
|
||||
</Grid>
|
||||
</Button>
|
||||
@@ -66,21 +166,21 @@
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<Border Grid.Column="0" Classes="rowmark catmark" />
|
||||
<TextBlock Grid.Column="1" Text="PASSWORDS" Margin="12,0,0,0" />
|
||||
<TextBlock Grid.Column="2" Text="{Binding Credentials.Count}"
|
||||
<TextBlock Grid.Column="2" Classes="mono" Text="{Binding Credentials.Count}"
|
||||
Foreground="{StaticResource TextFaint}" />
|
||||
</Grid>
|
||||
</Button>
|
||||
|
||||
<!--
|
||||
Buckets. A category here rather than a screen of its own, unlike the approved host keys: a bucket
|
||||
is something somebody creates, edits and keeps a secret for, which is what the other two
|
||||
categories are. A pin is a decision recorded at connect time and is not.
|
||||
Buckets. A category here rather than a screen of its own, unlike the approved host keys: a
|
||||
bucket is something somebody creates, edits and keeps a secret for, which is what the other
|
||||
two categories are. A pin is a decision recorded at connect time and is not.
|
||||
-->
|
||||
<!--
|
||||
Tags. The odd category: it is the only one holding nothing secret — a tag is a name. It is here
|
||||
because the reason a tag is an item at all is that renaming it should be one write instead of
|
||||
twenty, and a rename needs somewhere to happen; so does deleting, or the host editor's picker
|
||||
fills with names nobody uses and never empties.
|
||||
Tags. The odd category: it is the only one holding nothing secret — a tag is a name. It is
|
||||
here because the reason a tag is an item at all is that renaming it should be one write
|
||||
instead of twenty, and a rename needs somewhere to happen; so does deleting, or the host
|
||||
editor's picker fills with names nobody uses and never empties.
|
||||
-->
|
||||
<Button Classes="flat cat" Command="{Binding ShowSectionCommand}"
|
||||
CommandParameter="{x:Static vm:VaultSection.Tags}"
|
||||
@@ -88,7 +188,7 @@
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<Border Grid.Column="0" Classes="rowmark catmark" />
|
||||
<TextBlock Grid.Column="1" Text="TAGS" Margin="12,0,0,0" />
|
||||
<TextBlock Grid.Column="2" Text="{Binding Tags.Count}"
|
||||
<TextBlock Grid.Column="2" Classes="mono" Text="{Binding Tags.Count}"
|
||||
Foreground="{StaticResource TextFaint}" />
|
||||
</Grid>
|
||||
</Button>
|
||||
@@ -99,33 +199,35 @@
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<Border Grid.Column="0" Classes="rowmark catmark" />
|
||||
<TextBlock Grid.Column="1" Text="BUCKETS" Margin="12,0,0,0" />
|
||||
<TextBlock Grid.Column="2" Text="{Binding ObjectStores.Count}"
|
||||
<TextBlock Grid.Column="2" Classes="mono" Text="{Binding ObjectStores.Count}"
|
||||
Foreground="{StaticResource TextFaint}" />
|
||||
</Grid>
|
||||
</Button>
|
||||
|
||||
<Border Height="1" Background="{StaticResource BorderSubtle}" Margin="14,10" />
|
||||
<Border Height="1" Background="{StaticResource BorderSubtle}" Margin="12,12" />
|
||||
|
||||
<TextBlock Classes="label" Text="SCOPES" Margin="14,0,14,8" />
|
||||
<TextBlock Classes="label" Text="SCOPES" Margin="12,0,12,8" FontSize="10" />
|
||||
|
||||
<!--
|
||||
Still not a selector. Every list on this screen now spans every vault this session holds a key
|
||||
for, and each row names its own vault — so there is nothing to switch to. What the picker below
|
||||
chooses is where a *new* item is filed, which is a different question and the only one that has
|
||||
an answer worth asking for.
|
||||
Still not a selector. Every list on this screen now spans every vault this session holds a
|
||||
key for, and each row names its own vault — so there is nothing to switch to. What the
|
||||
picker below chooses is where a *new* item is filed, which is a different question and the
|
||||
only one that has an answer worth asking for.
|
||||
-->
|
||||
<StackPanel Orientation="Horizontal" Margin="14,2" Spacing="7">
|
||||
<StackPanel Orientation="Horizontal" Margin="12,2" Spacing="7">
|
||||
<Ellipse Width="6" Height="6" Fill="{StaticResource Accent}" VerticalAlignment="Center" />
|
||||
<TextBlock Classes="mono" Text="{Binding HostsHeading}" FontSize="11"
|
||||
Foreground="{StaticResource Text}" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
|
||||
<!--
|
||||
Hidden at one vault, which is where most people stay. A control offering a single option is a
|
||||
question with no answer.
|
||||
Hidden at one vault, which is where most people stay. A control offering a single option is
|
||||
a question with no answer. "NEW ITEMS FILE TO" — the design's own wording, adopted: this
|
||||
screen's own comments already call the act "filing" (see above), so the design's label was
|
||||
this application's own vocabulary already.
|
||||
-->
|
||||
<StackPanel Margin="14,10,14,0" Spacing="4" IsVisible="{Binding HasVaultChoice}">
|
||||
<TextBlock Classes="label" Text="NEW ITEMS GO TO" />
|
||||
<StackPanel Margin="12,14,12,0" Spacing="4" IsVisible="{Binding HasVaultChoice}">
|
||||
<TextBlock Classes="label" Text="NEW ITEMS FILE TO" FontSize="10" />
|
||||
<ComboBox ItemsSource="{Binding TargetVaults}"
|
||||
SelectedItem="{Binding SelectedTargetVault}"
|
||||
HorizontalAlignment="Stretch">
|
||||
@@ -140,11 +242,11 @@
|
||||
</StackPanel>
|
||||
|
||||
<!--
|
||||
Items that would not decrypt. Shown here rather than only in the status line because this is the
|
||||
screen the number is about, and because a non-zero count after a rekey is the signal that new
|
||||
grants are needed rather than a transient.
|
||||
Items that would not decrypt. Shown here rather than only in the status line because this is
|
||||
the screen the number is about, and because a non-zero count after a rekey is the signal that
|
||||
new grants are needed rather than a transient.
|
||||
-->
|
||||
<Border Classes="chip warn" Margin="14,12,14,0" HorizontalAlignment="Left"
|
||||
<Border Classes="chip warn" Margin="12,14,12,0" HorizontalAlignment="Left"
|
||||
IsVisible="{Binding HasUnreadableItems}">
|
||||
<TextBlock Text="{Binding UnreadableSummary}" />
|
||||
</Border>
|
||||
@@ -153,99 +255,67 @@
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
|
||||
<!-- The table -->
|
||||
<Grid Grid.Column="1" RowDefinitions="Auto,Auto,*">
|
||||
<!-- ============ The table ============ -->
|
||||
<Grid Grid.Column="1" RowDefinitions="Auto,Auto,*" Background="{StaticResource Pane}">
|
||||
|
||||
<Border Grid.Row="0" Padding="14,0" Height="44"
|
||||
<Border Grid.Row="0" Padding="16,0" Height="44"
|
||||
BorderBrush="{StaticResource Border}" BorderThickness="0,0,0,1">
|
||||
<!--
|
||||
◆ THE SUMMARY IS THE COLUMN THAT GIVES WAY, and that is what stops this header overflowing again.
|
||||
|
||||
It used to be Auto,Auto,*,Auto — a fixed title, a fixed summary, slack, then the buttons — so the
|
||||
slack column was the only thing absorbing a change of width, and the strip fell off the right edge
|
||||
the moment the five buttons wanted more than it had. That is not hypothetical: it is why GENERATE
|
||||
lost the word KEY (see below), and it happened again the moment the type scale went up a point.
|
||||
Buying pixels by shortening a caption fixes one instance of a shape that keeps producing them.
|
||||
|
||||
So the summary sits in the star column and trims, and the buttons are Auto and always get their
|
||||
full width. That is the rule worth encoding rather than the pixels: a trimmed summary is a fact
|
||||
you can read by widening the window, and a clipped button is a dead end. The titlebar's search box
|
||||
is arranged this way for the same reason.
|
||||
-->
|
||||
<Grid ColumnDefinitions="Auto,*,Auto" VerticalAlignment="Center">
|
||||
<TextBlock Grid.Column="0" Classes="mono" Text="{Binding SectionTitle}" FontSize="12"
|
||||
FontWeight="SemiBold" LetterSpacing="1" Foreground="{StaticResource Text}"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock Grid.Column="1" Classes="mono" Text="{Binding SectionSummary}" FontSize="10.5"
|
||||
Foreground="{StaticResource TextFaint}" Margin="10,0,10,0" VerticalAlignment="Center"
|
||||
<Grid ColumnDefinitions="*,Auto" VerticalAlignment="Center">
|
||||
<TextBlock Grid.Column="0" Classes="mono" Text="{Binding SectionSummary}" FontSize="10.5"
|
||||
Foreground="{StaticResource TextFaint}" Margin="0,0,10,0" VerticalAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="6">
|
||||
<!--
|
||||
Always offered. Every category left on this screen is one things can be added to — the one
|
||||
that was not, HOST KEYS, is now its own screen, and a pin still cannot be typed in there
|
||||
either. See KnownHostsScreen.
|
||||
|
||||
GENERATE carries no KEY, which it lost when a fifth button arrived and the strip could still
|
||||
overflow. The arrangement above is what keeps it inside now; the short caption stays because
|
||||
its tooltip carries what the word did and the two key buttons are adjacent, so which one
|
||||
generates is not in doubt.
|
||||
-->
|
||||
<Button Classes="ghost" Content="GENERATE" Command="{Binding NewGeneratedKeyCommand}"
|
||||
ToolTip.Tip="Makes a new key pair here, so the private half never becomes a file on this disk." />
|
||||
<Button Classes="ghost" Content="+ SSH KEY" Command="{Binding NewKeyCommand}"
|
||||
ToolTip.Tip="Pastes in a key you already have." />
|
||||
<Button Classes="ghost" Content="+ PASSWORD" Command="{Binding NewCredentialCommand}" />
|
||||
<Button Classes="ghost" Content="+ TAG" Command="{Binding NewTagCommand}"
|
||||
ToolTip.Tip="A name to put on hosts. Usually made from a host's editor instead; this is for setting a scheme up before there is anything to put it on." />
|
||||
<Button Classes="accent" Content="+ BUCKET" Command="{Binding NewObjectStoreCommand}"
|
||||
ToolTip.Tip="An S3-compatible bucket, to browse beside a host on the Files screen." />
|
||||
</StackPanel>
|
||||
<TextBox Grid.Column="1" x:Name="ItemFilterBox" Text="{Binding ItemFilter}" Width="240"
|
||||
Height="30" CornerRadius="9" PlaceholderText="filter items"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!--
|
||||
The design's columns are NAME / TYPE / FINGERPRINT / SCOPE / ACCESS / LAST. Three of those six have
|
||||
nothing behind them: there is one scope, no roles, and no item carries a last-used or modified time —
|
||||
VaultItem is (id, secret, version, three sync flags) and nothing else. What replaces them is the one
|
||||
thing this build does know and the design had no column for: whether a change is still sitting in
|
||||
this machine's outbox.
|
||||
The design's columns are NAME / TYPE / VAULT / USED BY / MODIFIED. MODIFIED is not here — no
|
||||
item this application stores carries a timestamp; see the file-level remark above and
|
||||
docs/design-import-gaps.md.
|
||||
-->
|
||||
<Grid Grid.Row="1" ColumnDefinitions="2,1.3*,74,*,88" Margin="0,6,14,6"
|
||||
<Grid Grid.Row="1" ColumnDefinitions="2,2.2*,1*,1*,1.4*" Margin="0,10,16,6"
|
||||
IsVisible="{Binding HasVaultItems}">
|
||||
<TextBlock Grid.Column="1" Classes="label" Text="NAME" FontSize="9.5" LetterSpacing="1"
|
||||
Margin="12,0,8,0" />
|
||||
<TextBlock Grid.Column="2" Classes="label" Text="TYPE" FontSize="9.5" LetterSpacing="1" />
|
||||
<TextBlock Grid.Column="3" Classes="label" Text="DETAIL" FontSize="9.5" LetterSpacing="1" />
|
||||
<TextBlock Grid.Column="4" Classes="label" Text="STATE" FontSize="9.5" LetterSpacing="1" />
|
||||
<TextBlock Grid.Column="3" Classes="label" Text="VAULT" FontSize="9.5" LetterSpacing="1" />
|
||||
<TextBlock Grid.Column="4" Classes="label" Text="USED BY" FontSize="9.5" LetterSpacing="1" />
|
||||
</Grid>
|
||||
|
||||
<ListBox Grid.Row="2" x:Name="ItemList" Focusable="True"
|
||||
<ListBox Grid.Row="2" x:Name="ItemList" Classes="filerows" Focusable="True"
|
||||
ItemsSource="{Binding VaultItems}"
|
||||
SelectedItem="{Binding SelectedVaultItem}">
|
||||
SelectedItem="{Binding SelectedVaultItem}"
|
||||
Margin="8,0,8,10">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:VaultItemRowViewModel">
|
||||
<Grid ColumnDefinitions="2,1.3*,74,*,88" Margin="0,7,14,7">
|
||||
<Border Grid.Column="0" Classes="rowmark" />
|
||||
<TextBlock Grid.Column="1" Classes="mono" Text="{Binding Name}" FontSize="12"
|
||||
FontWeight="Medium" Foreground="{StaticResource Text}" Margin="12,0,8,0"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
<TextBlock Grid.Column="2" Classes="mono" Text="{Binding Type}" FontSize="10"
|
||||
Foreground="{StaticResource TextDim}" VerticalAlignment="Center" />
|
||||
<TextBlock Grid.Column="3" Classes="mono" Text="{Binding Detail}" FontSize="10.5"
|
||||
Foreground="{StaticResource TextFaint}" Margin="0,0,8,0"
|
||||
TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
|
||||
<Border Grid.Column="4" Classes="chip warn" HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center" IsVisible="{Binding HasBadge}">
|
||||
<TextBlock Text="{Binding Badge}" FontSize="9.5" />
|
||||
<Grid ColumnDefinitions="2,2.2*,1*,1*,1.4*" Height="40" Margin="6,0,10,0">
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="10" Margin="10,0,8,0"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock FontFamily="{StaticResource IconFont}" FontSize="15"
|
||||
Text="{Binding IconGlyph}" Foreground="{StaticResource AccentText}"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock Classes="mono" Text="{Binding Name}" FontSize="12.5" FontWeight="Medium"
|
||||
Foreground="{StaticResource Text}" TextTrimming="CharacterEllipsis" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="2" Classes="mono" Text="{Binding Type}" FontSize="10.5"
|
||||
Foreground="{StaticResource TextGhost}" VerticalAlignment="Center" />
|
||||
<Border Grid.Column="3" Classes="chip" HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center" IsVisible="{Binding HasVaultBadge}">
|
||||
<TextBlock Text="{Binding VaultBadge}" FontSize="10" />
|
||||
</Border>
|
||||
<TextBlock Grid.Column="4" Classes="mono" Text="{Binding UsedBySummary}" FontSize="10.5"
|
||||
Foreground="{StaticResource TextFaint}" VerticalAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis" IsVisible="{Binding HasUsedBySummary}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<!--
|
||||
The empty state says which category is empty and what to do about it, rather than showing an empty
|
||||
grid that reads as a list still loading.
|
||||
The empty state says which category is empty and what to do about it, rather than showing an
|
||||
empty grid that reads as a list still loading.
|
||||
-->
|
||||
<TextBlock Grid.Row="2" Classes="hint" Text="{Binding EmptySectionMessage}" FontSize="12"
|
||||
Margin="24" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
@@ -254,54 +324,107 @@
|
||||
|
||||
</Grid>
|
||||
|
||||
<!-- The detail pane, and the editors -->
|
||||
<!-- ============ The detail pane, and the editors ============ -->
|
||||
<Border Grid.Column="2" Background="{StaticResource Sidebar}"
|
||||
BorderBrush="{StaticResource Border}" BorderThickness="1,0,0,0">
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="14,16">
|
||||
<StackPanel Margin="18,20" Spacing="14">
|
||||
|
||||
<!-- Nothing selected. -->
|
||||
<TextBlock Classes="hint" FontSize="12"
|
||||
Text="Choose something on the left to see what is known about it."
|
||||
IsVisible="{Binding !HasSelectedVaultItem}" />
|
||||
|
||||
<StackPanel Spacing="6" IsVisible="{Binding HasSelectedVaultItem}">
|
||||
<TextBlock Classes="mono" Text="{Binding SelectedVaultItem.Name}" FontSize="13"
|
||||
FontWeight="SemiBold" Foreground="{StaticResource Text}" TextWrapping="Wrap" />
|
||||
<StackPanel Spacing="14" IsVisible="{Binding HasSelectedVaultItem}">
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="12">
|
||||
<Border Width="36" Height="36" CornerRadius="10" Background="{StaticResource AccentWash}"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock FontFamily="{StaticResource IconFont}" FontSize="17"
|
||||
Text="{Binding SelectedVaultItem.IconGlyph}"
|
||||
Foreground="{StaticResource AccentText}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</Border>
|
||||
<StackPanel Spacing="4" VerticalAlignment="Center">
|
||||
<TextBlock Classes="mono" Text="{Binding SelectedVaultItem.Name}" FontSize="13.5"
|
||||
FontWeight="Bold" Foreground="{StaticResource Text}" TextWrapping="Wrap" />
|
||||
<TextBlock Classes="mono" Text="{Binding SelectedVaultItem.Detail}" FontSize="10.5"
|
||||
Foreground="{StaticResource TextGhost}" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<Border Classes="chip">
|
||||
<TextBlock Text="{Binding SelectedVaultItem.Type}" />
|
||||
</Border>
|
||||
<Border Classes="chip accent">
|
||||
<TextBlock Text="{Binding HostsHeading}" />
|
||||
<!--
|
||||
"in use · N hosts" — real only for a key or a credential with at least one host actually
|
||||
bound to it, off the same VaultViewModel.HostsBoundTo scan the USED BY list below reads.
|
||||
-->
|
||||
<Border CornerRadius="5" Background="{StaticResource LiveWash}" Padding="9,0" Height="21"
|
||||
VerticalAlignment="Center" IsVisible="{Binding HasSelectedItemInUseSummary}">
|
||||
<TextBlock Text="{Binding SelectedItemInUseSummary}" FontSize="11" FontWeight="Medium"
|
||||
Foreground="{StaticResource Live}" VerticalAlignment="Center" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Classes="label" Text="{Binding SelectedDetailHeading}" Margin="0,12,0,4" />
|
||||
<Border Background="{StaticResource Raised}" BorderBrush="{StaticResource Border}"
|
||||
BorderThickness="1" CornerRadius="4" Padding="8">
|
||||
<SelectableTextBlock Classes="mono" Text="{Binding SelectedVaultItem.Detail}"
|
||||
FontSize="10.5" Foreground="{StaticResource TextDim}"
|
||||
TextWrapping="Wrap" />
|
||||
<!--
|
||||
PUBLIC KEY, when the selected key has one stored — never the private half. FINGERPRINT is
|
||||
not drawn: this codebase has never computed one, and computing it here would mean parsing
|
||||
armour the type stores verbatim; see docs/design-import-gaps.md's own recorded gap.
|
||||
-->
|
||||
<StackPanel Spacing="6" IsVisible="{Binding SelectedItemIsKey}">
|
||||
<TextBlock Classes="label" Text="PUBLIC KEY" FontSize="10" />
|
||||
<Border CornerRadius="10" Background="{StaticResource Pane}"
|
||||
BorderBrush="{StaticResource BorderMid}" BorderThickness="1" Padding="12,10"
|
||||
IsVisible="{Binding SelectedKey.Key.PublicKey, Converter={x:Static StringConverters.IsNotNullOrEmpty}, FallbackValue=False}">
|
||||
<SelectableTextBlock Classes="mono" Text="{Binding SelectedKey.Key.PublicKey}"
|
||||
FontSize="10.5" LineHeight="17"
|
||||
Foreground="{StaticResource TextGhost}" TextWrapping="Wrap" />
|
||||
</Border>
|
||||
<TextBlock Classes="hint" FontSize="10.5"
|
||||
Text="No public half is stored for this key."
|
||||
IsVisible="{Binding !SelectedKey.Key.PublicKey, FallbackValue=False}" />
|
||||
</StackPanel>
|
||||
|
||||
<!--
|
||||
What the design puts here — who added it, when, who it is shared with, and a TEST CONNECT
|
||||
button — has nothing behind it. Items carry no author, no timestamps and no sharing, and
|
||||
nothing can exercise a credential without a host to exercise it against. Rather than five
|
||||
empty rows, this says what is missing in one line.
|
||||
USED BY: real host rows, off the same scan the "in use" chip above reads, with each host's
|
||||
own two-state dot — never a third colour, since nothing here pings anything.
|
||||
-->
|
||||
<TextBlock Classes="hint" FontSize="10.5" Margin="0,12,0,0"
|
||||
Text="Keychain items record no author, no timestamps and no sharing yet, so there is nothing more to show here." />
|
||||
<StackPanel Spacing="6" IsVisible="{Binding HasSelectedItemUsedByHosts}">
|
||||
<TextBlock Classes="label" Text="USED BY" FontSize="10" />
|
||||
<StackPanel Spacing="2">
|
||||
<ItemsControl ItemsSource="{Binding SelectedItemUsedByHosts}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:UsedByHostRowViewModel">
|
||||
<StackPanel Orientation="Horizontal" Spacing="9" Height="28">
|
||||
<Ellipse Classes="dot" Classes.live="{Binding IsConnected}"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock Classes="mono" Text="{Binding Label}" FontSize="12"
|
||||
Foreground="{StaticResource TextFaint}" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="6" Margin="0,14,0,0"
|
||||
IsVisible="{Binding ShowsItemActions}">
|
||||
<!--
|
||||
What the design puts here beyond what is above — who added it, when, and a TEST CONNECT
|
||||
button — has nothing behind it: items carry no author or timestamp, and nothing can
|
||||
exercise a credential without a host to exercise it against.
|
||||
-->
|
||||
<TextBlock Classes="hint" FontSize="10.5"
|
||||
Text="Keychain items record no author or timestamp yet, so there is nothing more to show here." />
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="6" IsVisible="{Binding ShowsItemActions}">
|
||||
<Button Classes="ghost" Content="EDIT" Command="{Binding EditSelectedItemCommand}" />
|
||||
<!--
|
||||
Only where there is somewhere to move to, unlike EDIT beside it, which is the same rule the
|
||||
host's MOVE follows on the phone: a button that answers with "this is the only vault you can
|
||||
write to" is a button that should not have been drawn. Keys and passwords only — a tag and a
|
||||
bucket are read from the active vault alone, so "another vault" is not a question they have.
|
||||
Only where there is somewhere to move to, unlike EDIT beside it, which is the same rule
|
||||
the host's MOVE follows on the phone: a button that answers with "this is the only vault
|
||||
you can write to" is a button that should not have been drawn. Keys and passwords only —
|
||||
a tag and a bucket are read from the active vault alone, so "another vault" is not a
|
||||
question they have.
|
||||
-->
|
||||
<Button Classes="ghost" Content="MOVE" Command="{Binding MoveSelectedItemCommand}"
|
||||
IsVisible="{Binding CanMoveSelectedItem}"
|
||||
@@ -310,17 +433,23 @@
|
||||
</StackPanel>
|
||||
|
||||
<!--
|
||||
◆ MOVING THE ITEM TO ANOTHER VAULT, in the place those buttons were. The host's panel, over
|
||||
here — see HostDrawer.axaml — and what it is for is the thing a shared vault could not do until
|
||||
now: a key typed into a personal vault before the team existed was stuck there, and the only
|
||||
way across was to paste the private half into a second item and delete the first.
|
||||
|
||||
The two sentences under the picker are the whole of the decision. The first says what a move
|
||||
is; the second says what points at this key, because everything that does is re-aimed at it in
|
||||
its new vault and somebody moving a key twenty machines use should see the twenty first.
|
||||
The public half only, and there is no button for the other one. Installing a key means
|
||||
pasting this line into a host's authorized_keys; a private key on the clipboard is a
|
||||
private key in every application on the machine.
|
||||
-->
|
||||
<StackPanel Spacing="8" Margin="0,14,0,0" IsVisible="{Binding IsMovingItem}">
|
||||
<TextBlock Classes="label" Text="MOVE TO VAULT" />
|
||||
<Button Classes="ghost" Content="Copy public key" HorizontalAlignment="Left"
|
||||
IsVisible="{Binding SelectedItemIsKey}"
|
||||
Command="{Binding CopyPublicKeyCommand}"
|
||||
ToolTip.Tip="Copies the authorized_keys line for this key, which is what a host needs to let it in." />
|
||||
|
||||
<!--
|
||||
◆ MOVING THE ITEM TO ANOTHER VAULT, in the place those buttons were. See HostDrawer.axaml —
|
||||
the thing a shared vault could not do until now: a key typed into a personal vault before
|
||||
the team existed was stuck there, and the only way across was to paste the private half
|
||||
into a second item and delete the first.
|
||||
-->
|
||||
<StackPanel Spacing="8" IsVisible="{Binding IsMovingItem}">
|
||||
<TextBlock Classes="label" Text="MOVE TO VAULT" FontSize="10" />
|
||||
<ComboBox HorizontalAlignment="Stretch" ItemsSource="{Binding MoveItemVaultChoices}"
|
||||
SelectedItem="{Binding SelectedMoveItemVault}">
|
||||
<ComboBox.ItemTemplate>
|
||||
@@ -342,24 +471,11 @@
|
||||
</StackPanel>
|
||||
|
||||
<!--
|
||||
The public half only, and there is no button for the other one. Installing a key means pasting
|
||||
this line into a host's authorized_keys; a private key on the clipboard is a private key in
|
||||
every application on the machine.
|
||||
-->
|
||||
<Button Classes="ghost" Content="COPY PUBLIC KEY" Margin="0,6,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
IsVisible="{Binding SelectedItemIsKey}"
|
||||
Command="{Binding CopyPublicKeyCommand}"
|
||||
ToolTip.Tip="Copies the authorized_keys line for this key, which is what a host needs to let it in." />
|
||||
|
||||
<!--
|
||||
The question DELETE asks, in the place those two buttons were. Here rather than over the
|
||||
screen, because this pane is where the item being deleted is described: the name, the kind and
|
||||
what is stored are all still on screen above it, which is most of what somebody checks before
|
||||
answering. See ConfirmDeleteCard.
|
||||
The question DELETE asks, in the place the buttons above were. Here rather than over the
|
||||
screen, because this pane is where the item being deleted is described.
|
||||
-->
|
||||
<Border Background="{StaticResource DangerWash}" BorderBrush="{StaticResource DangerSoft}"
|
||||
BorderThickness="1" CornerRadius="4" Padding="10" Margin="0,14,0,0"
|
||||
BorderThickness="1" CornerRadius="10" Padding="12"
|
||||
IsVisible="{Binding IsConfirmingDeletion}">
|
||||
<views:ConfirmDeleteCard />
|
||||
</Border>
|
||||
@@ -367,19 +483,13 @@
|
||||
</StackPanel>
|
||||
|
||||
<!--
|
||||
Making a key, as opposed to pasting one in. A step of its own and a short one: an algorithm, a
|
||||
comment, and a button. What it produces lands in the editor below, unsaved — so there is still
|
||||
exactly one thing on this screen that writes a key, and it is still SAVE.
|
||||
Making a key, as opposed to pasting one in. A step of its own and a short one: an algorithm,
|
||||
a comment, and a button.
|
||||
-->
|
||||
<StackPanel Spacing="6" IsVisible="{Binding IsGeneratingKey}">
|
||||
<TextBlock Classes="label" Text="NEW SSH KEY" Margin="0,0,0,4" />
|
||||
<TextBlock Classes="label" Text="NEW SSH KEY" FontSize="10" />
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<!--
|
||||
Buttons and a command rather than a selector bound to the algorithm, which is the same
|
||||
choice the category rail makes and for the same reason: a selector moves its own highlight
|
||||
before anything can refuse, so it can end up showing a choice nobody made.
|
||||
-->
|
||||
<Button Classes="flat choice" Content="ED25519"
|
||||
Classes.active="{Binding GeneratesEd25519}"
|
||||
Command="{Binding ChooseKeyAlgorithmCommand}"
|
||||
@@ -396,11 +506,6 @@
|
||||
<TextBlock Classes="hint" FontSize="10.5" TextWrapping="Wrap"
|
||||
Text="This is what the key is called here and what is written into it, so the line on a host says where it came from." />
|
||||
|
||||
<!--
|
||||
Said plainly rather than left to be discovered. Writing an encrypted openssh-key-v1 file needs
|
||||
bcrypt_pbkdf, which .NET has no primitive for — and the defence it buys is one this product
|
||||
already makes: a passphrase protects a key file on a disk, and this key is never on one.
|
||||
-->
|
||||
<TextBlock Classes="hint" FontSize="10.5" TextWrapping="Wrap" Margin="0,4,0,0"
|
||||
Text="The key file itself has no passphrase. Your keychain passphrase is what protects it, and it never reaches the server in a form it can read." />
|
||||
|
||||
@@ -413,12 +518,12 @@
|
||||
|
||||
<!-- The key editor. -->
|
||||
<StackPanel Spacing="6" IsVisible="{Binding IsEditingKey}">
|
||||
<TextBlock Classes="label" Text="SSH KEY" Margin="0,0,0,4" />
|
||||
<TextBlock Classes="label" Text="SSH KEY" FontSize="10" />
|
||||
<TextBox Text="{Binding KeyEditorLabel}" PlaceholderText="name" />
|
||||
<!--
|
||||
Not a password box. The armour has to be visible to be pasted and checked — a masked
|
||||
multi-line box makes "did the whole key arrive?" unanswerable — and the mistake this actually
|
||||
prevents is pasting the .pub file, which SshKeySecret.TryValidate rejects by name.
|
||||
multi-line box makes "did the whole key arrive?" unanswerable — and the mistake this
|
||||
actually prevents is pasting the .pub file, which SshKeySecret.TryValidate rejects by name.
|
||||
-->
|
||||
<TextBox Text="{Binding KeyEditorPrivateKey}"
|
||||
PlaceholderText="-----BEGIN OPENSSH PRIVATE KEY-----"
|
||||
@@ -439,19 +544,19 @@
|
||||
|
||||
<!-- The password editor. -->
|
||||
<StackPanel Spacing="6" IsVisible="{Binding IsEditingCredential}">
|
||||
<TextBlock Classes="label" Text="PASSWORD" Margin="0,0,0,4" />
|
||||
<TextBlock Classes="label" Text="PASSWORD" FontSize="10" />
|
||||
<TextBox Text="{Binding CredentialEditorLabel}" PlaceholderText="name" />
|
||||
<!--
|
||||
Optional, and the reason a credential is worth being its own item rather than two more fields on
|
||||
a host: one account on twenty machines is described once and rotated once. Left blank, each host
|
||||
supplies its own username and only the password is shared.
|
||||
Optional, and the reason a credential is worth being its own item rather than two more
|
||||
fields on a host: one account on twenty machines is described once and rotated once. Left
|
||||
blank, each host supplies its own username and only the password is shared.
|
||||
-->
|
||||
<TextBox Text="{Binding CredentialEditorUsername}"
|
||||
PlaceholderText="username (blank: use each host's own)" />
|
||||
<!--
|
||||
Masked, unlike the private key box, and the difference is not inconsistency. A key's armour has
|
||||
to be visible to be checked for truncation after a paste; a password is short, usually typed,
|
||||
and shoulder-surfing is the likelier problem.
|
||||
Masked, unlike the private key box, and the difference is not inconsistency. A key's armour
|
||||
has to be visible to be checked for truncation after a paste; a password is short, usually
|
||||
typed, and shoulder-surfing is the likelier problem.
|
||||
-->
|
||||
<TextBox Text="{Binding CredentialEditorPassword}" PlaceholderText="password" PasswordChar="•" />
|
||||
<TextBox Text="{Binding CredentialEditorNotes}" PlaceholderText="notes" AcceptsReturn="True"
|
||||
@@ -465,12 +570,12 @@
|
||||
</StackPanel>
|
||||
|
||||
<!--
|
||||
The tag editor, and the whole of it is one box. What it does not have is the point: renaming a
|
||||
tag touches no host, because every host wearing it names its id. That is the entire reason a tag
|
||||
is an item rather than a string repeated inside twenty payloads.
|
||||
The tag editor, and the whole of it is one box. What it does not have is the point: renaming
|
||||
a tag touches no host, because every host wearing it names its id. That is the entire reason
|
||||
a tag is an item rather than a string repeated inside twenty payloads.
|
||||
-->
|
||||
<StackPanel Spacing="6" IsVisible="{Binding IsEditingTag}">
|
||||
<TextBlock Classes="label" Text="TAG" Margin="0,0,0,4" />
|
||||
<TextBlock Classes="label" Text="TAG" FontSize="10" />
|
||||
<TextBox Text="{Binding TagEditorLabel}" PlaceholderText="name">
|
||||
<TextBox.KeyBindings>
|
||||
<KeyBinding Gesture="Enter" Command="{Binding SaveTagCommand}" />
|
||||
@@ -486,20 +591,21 @@
|
||||
|
||||
<!-- The bucket editor. -->
|
||||
<StackPanel Spacing="6" IsVisible="{Binding IsEditingObjectStore}">
|
||||
<TextBlock Classes="label" Text="BUCKET" Margin="0,0,0,4" />
|
||||
<TextBlock Classes="label" Text="BUCKET" FontSize="10" />
|
||||
<TextBox Text="{Binding BucketEditorLabel}" PlaceholderText="name" />
|
||||
<TextBox Text="{Binding BucketEditorBucket}" PlaceholderText="bucket" />
|
||||
<TextBox Text="{Binding BucketEditorAccessKeyId}" PlaceholderText="access key id" />
|
||||
<!--
|
||||
Masked, like a password and for the same reason: a secret access key is one. The access key id
|
||||
beside it is an identifier and is shown, which is also why the two are separate boxes.
|
||||
Masked, like a password and for the same reason: a secret access key is one. The access
|
||||
key id beside it is an identifier and is shown, which is also why the two are separate
|
||||
boxes.
|
||||
-->
|
||||
<TextBox Text="{Binding BucketEditorSecretAccessKey}" PlaceholderText="secret access key"
|
||||
PasswordChar="•" />
|
||||
<TextBox Text="{Binding BucketEditorRegion}" PlaceholderText="region (e.g. eu-west-1)" />
|
||||
<!--
|
||||
Blank means Amazon, and then the region resolves the host. Anything else is a full URL, which
|
||||
is what makes this work against a self-hosted service.
|
||||
Blank means Amazon, and then the region resolves the host. Anything else is a full URL,
|
||||
which is what makes this work against a self-hosted service.
|
||||
-->
|
||||
<TextBox Text="{Binding BucketEditorEndpoint}"
|
||||
PlaceholderText="endpoint (blank: Amazon S3)" />
|
||||
@@ -507,7 +613,8 @@
|
||||
Content="Address the bucket as a path" />
|
||||
<!--
|
||||
Said where the decision is made. Getting this wrong produces a DNS failure whose message
|
||||
mentions neither buckets nor this setting, which is the worst kind of thing to leave to a guess.
|
||||
mentions neither buckets nor this setting, which is the worst kind of thing to leave to a
|
||||
guess.
|
||||
-->
|
||||
<TextBlock Classes="hint" FontSize="10.5"
|
||||
Text="Off for Amazon S3. On for most self-hosted services — MinIO and Ceph have no wildcard DNS, so the bucket cannot be a subdomain." />
|
||||
@@ -526,5 +633,8 @@
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
|
||||
@@ -18,39 +18,114 @@
|
||||
The connections list shows anything still open at the top, marked "still open" rather than with a dash. A
|
||||
dash would read as a missing recording, and the two are opposite facts — an entry is written once, when a
|
||||
connection closes, so a live session is deliberately not in the vault yet.
|
||||
|
||||
── v5b — Logs.dc.html ──────────────────────────────────────────────────────────────────────────────────
|
||||
A fidelity pass, not a new shape. What moved:
|
||||
|
||||
◆ THE HEADER. "Logs" 33 bold plus the two-segment CONNECTIONS/KEYCHAIN control, restyled onto
|
||||
Border.navtrack/Button.navseg — the same h31 track the rail's own SSH/SFTP/S3 switcher already uses,
|
||||
reused rather than redrawn, since it is the identical shape at the identical size. Two segments and not
|
||||
three: LOGS is the screen's own title, not a tab, so the design's own two — CONNECTIONS and KEYCHAIN —
|
||||
are the whole of what this screen switches between, exactly matching LogSection's own two values. No
|
||||
deviation to record here. On the right: a mono status sentence — LogsViewModel.HeaderStatusLine, which
|
||||
shows a refresh error when there is one and otherwise the fact this screen's own header comment already
|
||||
states about whichever log is showing — and REFRESH, unchanged.
|
||||
|
||||
◆ THE BODY. A bordered radius-12 container on Pane bg wraps each table now, in place of the plain
|
||||
background either one drew before. Tracked column headers are unchanged — HOST/ADDRESS/LASTED/KIND/
|
||||
STARTED/FROM for connections, ITEM/TYPE/WHAT/FIELDS/WHEN for keychain activity, both already matching the
|
||||
design exactly. Rows keep their real two-state host dot (green only for a session genuinely open right
|
||||
now, per HasVisible on ConnectionLogRowViewModel.IsLive — never a third colour) and their existing
|
||||
"failed"/"host key refused" amber badges. WHAT, on the activity table, is now a small coloured chip rather
|
||||
than plain text — green for created, purple for a plain change, red for deleted — reading the same
|
||||
ActivityOperation this screen already resolved to a word; a chip rather than a fabricated new fact.
|
||||
|
||||
◆ THE FOOTER. Logs.dc.html's own lock-glyph sentence — "Both logs are ordinary synced keychain items —
|
||||
end-to-end encrypted. The server learns only that rows exist and when they were written." — verified
|
||||
against this file's own header remark above and ADR 0001 before shipping it: both are true, so the
|
||||
sentence is drawn as literal text rather than reworded.
|
||||
-->
|
||||
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<UserControl.Styles>
|
||||
<Style Selector="Border.logtable">
|
||||
<Setter Property="BorderBrush" Value="{StaticResource Border}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="CornerRadius" Value="12" />
|
||||
<Setter Property="Background" Value="{StaticResource Pane}" />
|
||||
<Setter Property="ClipToBounds" Value="True" />
|
||||
</Style>
|
||||
<Style Selector="ListBox.logrows > ListBoxItem /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="CornerRadius" Value="8" />
|
||||
</Style>
|
||||
<Style Selector="ListBox.logrows > ListBoxItem:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource Track}" />
|
||||
</Style>
|
||||
<!--
|
||||
WHAT, on the activity table: created/changed/deleted, coloured the way the item badges are. "changed"
|
||||
is the base rule's own colour — it is the default ActivityLogRowViewModel.Operation falls through to —
|
||||
and .created/.deleted are declared after it so they win; Avalonia has no specificity and settles two
|
||||
matching rules by declaration order, the same trap this application's other screens record.
|
||||
-->
|
||||
<Style Selector="Border.opchip">
|
||||
<Setter Property="Background" Value="{StaticResource AccentWash}" />
|
||||
</Style>
|
||||
<Style Selector="Border.opchip > TextBlock">
|
||||
<Setter Property="Foreground" Value="{StaticResource AccentText}" />
|
||||
</Style>
|
||||
<Style Selector="Border.opchip.created">
|
||||
<Setter Property="Background" Value="{StaticResource LiveWash}" />
|
||||
</Style>
|
||||
<Style Selector="Border.opchip.created > TextBlock">
|
||||
<Setter Property="Foreground" Value="{StaticResource Live}" />
|
||||
</Style>
|
||||
<Style Selector="Border.opchip.deleted">
|
||||
<Setter Property="Background" Value="{StaticResource DangerWash}" />
|
||||
</Style>
|
||||
<Style Selector="Border.opchip.deleted > TextBlock">
|
||||
<Setter Property="Foreground" Value="{StaticResource Danger}" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
|
||||
<Border Grid.Row="0" Padding="14,0" Height="44"
|
||||
BorderBrush="{StaticResource Border}" BorderThickness="0,0,0,1">
|
||||
<Grid ColumnDefinitions="Auto,Auto,Auto,*,Auto" VerticalAlignment="Center">
|
||||
<TextBlock Grid.Column="0" Classes="mono" Text="LOGS" FontSize="12" FontWeight="SemiBold"
|
||||
LetterSpacing="1" Foreground="{StaticResource Text}" VerticalAlignment="Center"
|
||||
Margin="0,0,14,0" />
|
||||
<Grid RowDefinitions="Auto,*" Margin="26">
|
||||
|
||||
<Button Grid.Column="1" Classes="flat cat" Content="CONNECTIONS"
|
||||
Classes.active="{Binding ShowsConnections}"
|
||||
Command="{Binding ShowSectionCommand}"
|
||||
<!-- ============ THE HEADER ============ -->
|
||||
<Grid Grid.Row="0" Margin="0,0,0,20" ColumnDefinitions="Auto,Auto,*,Auto,Auto">
|
||||
|
||||
<TextBlock Grid.Column="0" Text="Logs" FontSize="33" FontWeight="Bold" LetterSpacing="-0.5"
|
||||
Foreground="{StaticResource Text}" VerticalAlignment="Center" />
|
||||
|
||||
<Border Grid.Column="1" Classes="navtrack" Margin="14,0,0,0" Width="200" VerticalAlignment="Center">
|
||||
<Grid ColumnDefinitions="*,*">
|
||||
<Button Grid.Column="0" Classes="navseg" Classes.active="{Binding ShowsConnections}"
|
||||
Content="CONNECTIONS" Command="{Binding ShowSectionCommand}"
|
||||
CommandParameter="{x:Static vm:LogSection.Connections}" />
|
||||
<Button Grid.Column="2" Classes="flat cat" Content="KEYCHAIN"
|
||||
Classes.active="{Binding ShowsActivity}"
|
||||
Command="{Binding ShowSectionCommand}"
|
||||
<Button Grid.Column="1" Classes="navseg" Classes.active="{Binding ShowsActivity}"
|
||||
Content="KEYCHAIN" Command="{Binding ShowSectionCommand}"
|
||||
CommandParameter="{x:Static vm:LogSection.Activity}" />
|
||||
|
||||
<TextBlock Grid.Column="3" Classes="mono" Text="{Binding Status}" FontSize="10.5"
|
||||
Foreground="{StaticResource TextFaint}" Margin="14,0,0,0"
|
||||
TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
|
||||
|
||||
<Button Grid.Column="4" Classes="ghost" Content="REFRESH" Command="{Binding RefreshCommand}"
|
||||
IsEnabled="{Binding !IsBusy}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- ============ Connections ============ -->
|
||||
<Grid Grid.Row="1" RowDefinitions="Auto,*" IsVisible="{Binding ShowsConnections}">
|
||||
<TextBlock Grid.Column="3" Classes="mono" Text="{Binding HeaderStatusLine}" FontSize="10.5"
|
||||
Foreground="{StaticResource TextFaint}" Margin="0,0,14,0"
|
||||
TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
|
||||
|
||||
<Grid Grid.Row="0" ColumnDefinitions="1.2*,1.6*,88,72,90,*" Margin="14,6,14,6"
|
||||
<Button Grid.Column="4" Classes="ghost" Height="40"
|
||||
Command="{Binding RefreshCommand}" IsEnabled="{Binding !IsBusy}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock FontFamily="{StaticResource IconFont}" FontSize="15" Text=""
|
||||
Foreground="{StaticResource TextFaint}" />
|
||||
<TextBlock Text="Refresh" FontSize="13.5" FontWeight="SemiBold"
|
||||
Foreground="{StaticResource TextDim}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
</Grid>
|
||||
|
||||
<!-- ============ Connections ============ -->
|
||||
<Border Grid.Row="1" Classes="logtable" IsVisible="{Binding ShowsConnections}">
|
||||
<Grid RowDefinitions="Auto,*,Auto">
|
||||
|
||||
<Grid Grid.Row="0" ColumnDefinitions="1.2*,1.6*,88,72,90,*" Margin="20,14,20,10"
|
||||
IsVisible="{Binding HasConnections}">
|
||||
<TextBlock Grid.Column="0" Classes="label" Text="HOST" FontSize="9.5" LetterSpacing="1" />
|
||||
<TextBlock Grid.Column="1" Classes="label" Text="ADDRESS" FontSize="9.5" LetterSpacing="1" />
|
||||
@@ -60,37 +135,35 @@
|
||||
<TextBlock Grid.Column="5" Classes="label" Text="FROM" FontSize="9.5" LetterSpacing="1" />
|
||||
</Grid>
|
||||
|
||||
<ListBox Grid.Row="1" x:Name="ConnectionList" Focusable="True"
|
||||
ItemsSource="{Binding Connections}">
|
||||
<ListBox Grid.Row="1" x:Name="ConnectionList" Classes="logrows" Focusable="True"
|
||||
ItemsSource="{Binding Connections}" Margin="12,0,12,10">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:ConnectionLogRowViewModel">
|
||||
<Grid ColumnDefinitions="1.2*,1.6*,88,72,90,*" Margin="0,6,14,6">
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="6" Margin="14,0,8,0">
|
||||
<Grid ColumnDefinitions="1.2*,1.6*,88,72,90,*" Height="40" Margin="8,0,10,0">
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="9" Margin="12,0,8,0"
|
||||
VerticalAlignment="Center">
|
||||
<Ellipse Classes="dot" Classes.live="{Binding IsLive}" VerticalAlignment="Center" />
|
||||
<TextBlock Classes="mono" Text="{Binding HostLabel}" FontSize="12" FontWeight="Medium"
|
||||
Foreground="{StaticResource Text}" TextTrimming="CharacterEllipsis"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock Classes="mono" Text="{Binding HostLabel}" FontSize="12.5" FontWeight="Medium"
|
||||
Foreground="{StaticResource Text}" TextTrimming="CharacterEllipsis" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="1" Classes="mono" Text="{Binding Address}" FontSize="10.5"
|
||||
Foreground="{StaticResource TextDim}" Margin="0,0,8,0"
|
||||
<TextBlock Grid.Column="1" Classes="mono" Text="{Binding Address}" FontSize="11"
|
||||
Foreground="{StaticResource TextGhost}" Margin="0,0,8,0"
|
||||
TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="6" VerticalAlignment="Center">
|
||||
<TextBlock Classes="mono" Text="{Binding Duration}" FontSize="10.5"
|
||||
Foreground="{StaticResource TextDim}" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="3" Classes="mono" Text="{Binding Kind}" FontSize="10"
|
||||
<TextBlock Grid.Column="2" Classes="mono" Text="{Binding Duration}" FontSize="11"
|
||||
Foreground="{StaticResource TextGhost}" VerticalAlignment="Center" />
|
||||
<TextBlock Grid.Column="3" Classes="mono" Text="{Binding Kind}" FontSize="10.5"
|
||||
Foreground="{StaticResource TextFaint}" VerticalAlignment="Center" />
|
||||
<TextBlock Grid.Column="4" Classes="mono" Text="{Binding Started}" FontSize="10"
|
||||
<TextBlock Grid.Column="4" Classes="mono" Text="{Binding Started}" FontSize="10.5"
|
||||
Foreground="{StaticResource TextFaint}" VerticalAlignment="Center" />
|
||||
<StackPanel Grid.Column="5" Orientation="Horizontal" Spacing="6" VerticalAlignment="Center">
|
||||
<TextBlock Classes="mono" Text="{Binding DeviceName}" FontSize="10"
|
||||
<StackPanel Grid.Column="5" Orientation="Horizontal" Spacing="8" VerticalAlignment="Center">
|
||||
<TextBlock Classes="mono" Text="{Binding DeviceName}" FontSize="10.5"
|
||||
Foreground="{StaticResource TextFaint}"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
<!--
|
||||
Only when there is something to say. A connection that opened and closed says nothing
|
||||
here; one that was refused says so, and that is the row worth finding in a long list.
|
||||
-->
|
||||
<Border Classes="chip warn" Padding="4,0" IsVisible="{Binding HasOutcome}">
|
||||
<Border Classes="chip warn" Padding="7,0" Height="18" IsVisible="{Binding HasOutcome}">
|
||||
<TextBlock Text="{Binding Outcome}" FontSize="9.5" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
@@ -103,12 +176,27 @@
|
||||
Margin="24" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
TextAlignment="Center" MaxWidth="420"
|
||||
IsVisible="{Binding !HasConnections}" />
|
||||
|
||||
<!-- ◆ THE FOOTER. Verified against this file's own header remark and ADR 0001; see the file-level note. -->
|
||||
<Border Grid.Row="2" BorderBrush="{StaticResource Border}" BorderThickness="0,1,0,0"
|
||||
Padding="20,12">
|
||||
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||
<TextBlock FontFamily="{StaticResource IconFont}" FontSize="13" Text=""
|
||||
Foreground="{StaticResource TextFaint}" />
|
||||
<TextBlock Classes="hint" FontSize="11" TextWrapping="Wrap"
|
||||
Foreground="{StaticResource TextFaint}"
|
||||
Text="Both logs are ordinary synced keychain items — end-to-end encrypted. The server learns only that rows exist and when they were written." />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- ============ Keychain changes ============ -->
|
||||
<Grid Grid.Row="1" RowDefinitions="Auto,*" IsVisible="{Binding ShowsActivity}">
|
||||
<Border Grid.Row="1" Classes="logtable" IsVisible="{Binding ShowsActivity}">
|
||||
<Grid RowDefinitions="Auto,*,Auto">
|
||||
|
||||
<Grid Grid.Row="0" ColumnDefinitions="1.2*,90,96,*,90" Margin="14,6,14,6"
|
||||
<Grid Grid.Row="0" ColumnDefinitions="1.4*,0.8*,0.8*,1.6*,0.8*" Margin="20,14,20,10"
|
||||
IsVisible="{Binding HasActivity}">
|
||||
<TextBlock Grid.Column="0" Classes="label" Text="ITEM" FontSize="9.5" LetterSpacing="1" />
|
||||
<TextBlock Grid.Column="1" Classes="label" Text="TYPE" FontSize="9.5" LetterSpacing="1" />
|
||||
@@ -117,26 +205,26 @@
|
||||
<TextBlock Grid.Column="4" Classes="label" Text="WHEN" FontSize="9.5" LetterSpacing="1" />
|
||||
</Grid>
|
||||
|
||||
<ListBox Grid.Row="1" x:Name="ActivityList" Focusable="True" ItemsSource="{Binding Activity}">
|
||||
<ListBox Grid.Row="1" x:Name="ActivityList" Classes="logrows" Focusable="True"
|
||||
ItemsSource="{Binding Activity}" Margin="12,0,12,10">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:ActivityLogRowViewModel">
|
||||
<Grid ColumnDefinitions="1.2*,90,96,*,90" Margin="14,6,14,6">
|
||||
<TextBlock Grid.Column="0" Classes="mono" Text="{Binding ItemLabel}" FontSize="12"
|
||||
FontWeight="Medium" Foreground="{StaticResource Text}" Margin="0,0,8,0"
|
||||
<Grid ColumnDefinitions="1.4*,0.8*,0.8*,1.6*,0.8*" Height="40" Margin="8,0,10,0">
|
||||
<TextBlock Grid.Column="0" Classes="mono" Text="{Binding ItemLabel}" FontSize="12.5"
|
||||
FontWeight="Medium" Foreground="{StaticResource Text}" Margin="4,0,8,0"
|
||||
TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
|
||||
<TextBlock Grid.Column="1" Classes="mono" Text="{Binding ItemKind}" FontSize="10"
|
||||
Foreground="{StaticResource TextFaint}" VerticalAlignment="Center" />
|
||||
<TextBlock Grid.Column="2" Classes="mono" Text="{Binding Operation}" FontSize="10.5"
|
||||
Foreground="{StaticResource TextDim}" VerticalAlignment="Center" />
|
||||
<!--
|
||||
The names of the fields that changed, and never what they changed to. A log that recorded
|
||||
an old password would be a plaintext credential store with a vault drawn around it.
|
||||
-->
|
||||
<TextBlock Grid.Column="1" Classes="mono" Text="{Binding ItemKind}" FontSize="10.5"
|
||||
Foreground="{StaticResource TextGhost}" VerticalAlignment="Center" />
|
||||
<Border Grid.Column="2" Classes="opchip" Classes.created="{Binding IsCreated}"
|
||||
Classes.deleted="{Binding IsDeleted}" Height="19" CornerRadius="5" Padding="8,0"
|
||||
HorizontalAlignment="Left" VerticalAlignment="Center">
|
||||
<TextBlock Text="{Binding Operation}" FontSize="10" FontWeight="SemiBold" />
|
||||
</Border>
|
||||
<TextBlock Grid.Column="3" Classes="mono" Text="{Binding ChangedFields}" FontSize="10.5"
|
||||
Foreground="{StaticResource TextFaint}" Margin="0,0,8,0"
|
||||
TextTrimming="CharacterEllipsis" VerticalAlignment="Center"
|
||||
IsVisible="{Binding HasChangedFields}" />
|
||||
<TextBlock Grid.Column="4" Classes="mono" Text="{Binding At}" FontSize="10"
|
||||
<TextBlock Grid.Column="4" Classes="mono" Text="{Binding At}" FontSize="10.5"
|
||||
Foreground="{StaticResource TextFaint}" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
@@ -147,7 +235,20 @@
|
||||
Margin="24" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
TextAlignment="Center" MaxWidth="420"
|
||||
IsVisible="{Binding !HasActivity}" />
|
||||
|
||||
<Border Grid.Row="2" BorderBrush="{StaticResource Border}" BorderThickness="0,1,0,0"
|
||||
Padding="20,12">
|
||||
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||
<TextBlock FontFamily="{StaticResource IconFont}" FontSize="13" Text=""
|
||||
Foreground="{StaticResource TextFaint}" />
|
||||
<TextBlock Classes="hint" FontSize="11" TextWrapping="Wrap"
|
||||
Foreground="{StaticResource TextFaint}"
|
||||
Text="Both logs are ordinary synced keychain items — end-to-end encrypted. The server learns only that rows exist and when they were written." />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
|
||||
|
||||
@@ -10,56 +10,134 @@
|
||||
The list and the writing belong to the vault, as every other item kind's do; this screen is the filter,
|
||||
the editor and the insert over the top. See SnippetsViewModel.
|
||||
|
||||
The two buttons at the bottom right are the whole safety design, and their wording is load-bearing.
|
||||
A terminal is one input stream with no notion of being at a prompt — the remote may be inside vi, or at
|
||||
a sudo password prompt with echo off — so this application cannot say "run this command", only "type
|
||||
this into whatever is there". RUN appears solely for a snippet whose own flag says it runs, which makes
|
||||
that a decision taken once while writing it rather than a button beside every one of them.
|
||||
The two buttons at the bottom right of the detail pane are the whole safety design, and their wording is
|
||||
load-bearing. A terminal is one input stream with no notion of being at a prompt — the remote may be
|
||||
inside vi, or at a sudo password prompt with echo off — so this application cannot say "run this
|
||||
command", only "type this into whatever is there". RUN appears solely for a snippet whose own flag says
|
||||
it runs, which makes that a decision taken once while writing it rather than a button beside every one
|
||||
of them.
|
||||
|
||||
── v5b — Snippets.dc.html ──────────────────────────────────────────────────────────────────────────────
|
||||
A fidelity pass, not a new shape. What moved:
|
||||
|
||||
◆ THE SCREEN'S OWN TITLE. The design renames this screen "Snips" — adopted here on screen, matching the
|
||||
nav rail, which already says Snips (see NavRail.axaml's own remark on the mock's wording for this
|
||||
destination).
|
||||
|
||||
◆ THE HEADER. "Snips" 33 bold, a count chip, the existing filter box restyled to 280px, and a
|
||||
"+ New snip" accent button in place of the old ghost "+ NEW SNIPPET" — same NewCommand.
|
||||
|
||||
◆ THE LIST. Card rows rather than a flat list — radius 10, Track fill and an accent ring on the selected
|
||||
one, Track on hover — with a { } glyph, the mono name, the "runs immediately" chip (real: it is
|
||||
RunsOnInsert, already tracked per snippet), the vault chip, and the mono command preview beneath. No
|
||||
modified date: SnippetSecret carries none, and neither does any other item kind — VaultItem is (id,
|
||||
secret, version, three sync flags) and nothing else; see docs/design-import-gaps.md. The bottom action
|
||||
row keeps EDIT / MOVE TO VAULT… / DELETE, restyled and DELETE now danger-coloured to match.
|
||||
|
||||
◆ THE DETAIL/EDITOR SIDEBAR, widened to 340px per the design. Two modes exist as they always have — the
|
||||
editor while IsEditing, the selected snippet's own facts and its insert controls otherwise — because the
|
||||
two are genuinely different forms doing different things, not a cosmetic split; merging them would let
|
||||
somebody fixing a typo re-file a snippet by leaving a vault picker where they found it. VAULT draws a
|
||||
lock glyph beside the vault name, per the design. "Runs on insert"/"runs immediately" keeps this screen's
|
||||
own longer caption rather than the design's shorter one — it states the operational consequence
|
||||
("typed at the prompt and waits for you") where the design's states only the rationale for having the
|
||||
setting once; both are true, the existing one is more actionable. The insert button keeps its own dynamic
|
||||
label — "TYPE INTO {tab}" or "NO TERMINAL OPEN" — over the design's static "Type into terminal", because
|
||||
naming the destination tab is a fact the design's caption does not carry and this screen already had.
|
||||
Its own caption similarly stays: it names vi and a sudo prompt with echo off as the two ways "at a
|
||||
prompt" can be wrong, which is more than the design's own sentence says.
|
||||
|
||||
◆ THE DELETE CONFIRMATION. Snippets.dc.html's own modal — vault-wide reach, a tombstone, no undo — is
|
||||
exactly what VaultViewModel.HowFarADeletionGoes already says for every other kind of item, so
|
||||
SnippetsViewModel.RequestDelete/ConfirmDelete/CancelDelete say it in the same words. Additive next to the
|
||||
existing uncounted DeleteCommand rather than a change to it — that command is what the phone's own DELETE
|
||||
row still calls, with no confirmation card on that screen to answer one; see SnippetsViewModel's own
|
||||
remarks on both commands.
|
||||
-->
|
||||
|
||||
<Grid ColumnDefinitions="*,300">
|
||||
|
||||
<Grid Grid.Column="0" RowDefinitions="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="SNIPPETS" FontSize="12"
|
||||
FontWeight="SemiBold" LetterSpacing="1" Foreground="{StaticResource Text}"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock Grid.Column="1" Classes="mono" Text="{Binding Status}" FontSize="10.5"
|
||||
Foreground="{StaticResource TextFaint}" Margin="10,0,0,0"
|
||||
TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
|
||||
|
||||
<UserControl.Styles>
|
||||
<!--
|
||||
The command is searched as well as the name: half of what anybody remembers about a saved
|
||||
command is a word that was inside it.
|
||||
A card, not a full-bleed row: 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" — distinct from
|
||||
ListBox.filerows, which draws no ring, because a snippet card is chosen for one editor at a time rather
|
||||
than opened directly.
|
||||
-->
|
||||
<TextBox Grid.Column="2" x:Name="SnippetFilter" Text="{Binding Filter}" Width="240"
|
||||
PlaceholderText="filter by name or command" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
<Style Selector="ListBox.snipcards > ListBoxItem /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="CornerRadius" Value="10" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
</Style>
|
||||
<Style Selector="ListBox.snipcards > ListBoxItem:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource Track}" />
|
||||
</Style>
|
||||
<Style Selector="ListBox.snipcards > ListBoxItem:selected /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource Track}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource Accent}" />
|
||||
</Style>
|
||||
<Style Selector="ListBox.snipcards > ListBoxItem:selected:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource Track}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource Accent}" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
|
||||
<Grid RowDefinitions="Auto,*" Margin="26">
|
||||
|
||||
<!-- ============ THE HEADER ============ -->
|
||||
<Grid Grid.Row="0" Margin="0,0,0,20" ColumnDefinitions="Auto,Auto,*,Auto,Auto">
|
||||
|
||||
<TextBlock Grid.Column="0" Text="Snips" FontSize="33" FontWeight="Bold" LetterSpacing="-0.5"
|
||||
Foreground="{StaticResource Text}" VerticalAlignment="Center" />
|
||||
|
||||
<Border Grid.Column="1" Margin="12,0,0,0" MinWidth="34" Height="30" CornerRadius="9" Padding="8,0"
|
||||
Background="{StaticResource Chip}" VerticalAlignment="Center">
|
||||
<TextBlock Classes="mono" Text="{Binding Visible.Count}" FontSize="12.5"
|
||||
Foreground="{StaticResource TextDim}" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Border>
|
||||
|
||||
<ListBox Grid.Row="1" x:Name="SnippetList" Focusable="True"
|
||||
<!--
|
||||
The command is searched as well as the name: half of what anybody remembers about a saved command is
|
||||
a word that was inside it.
|
||||
-->
|
||||
<TextBox Grid.Column="3" x:Name="SnippetFilter" Text="{Binding Filter}" Width="280" Height="40"
|
||||
CornerRadius="10" Margin="0,0,10,0"
|
||||
PlaceholderText="filter by name or command" VerticalAlignment="Center" />
|
||||
|
||||
<Button Grid.Column="4" Classes="accent" Height="40" FontSize="13.5" Content="+ New snip"
|
||||
Command="{Binding NewCommand}" />
|
||||
|
||||
</Grid>
|
||||
|
||||
<!-- ============ THE BORDERED BODY ============ -->
|
||||
<Border Grid.Row="1" BorderBrush="{StaticResource Border}" BorderThickness="1" CornerRadius="12"
|
||||
ClipToBounds="True">
|
||||
<Grid ColumnDefinitions="*,340">
|
||||
|
||||
<Grid Grid.Column="0" RowDefinitions="*,Auto" Background="{StaticResource Pane}">
|
||||
|
||||
<ListBox Grid.Row="0" x:Name="SnippetList" Classes="snipcards" Focusable="True"
|
||||
ItemsSource="{Binding Visible}"
|
||||
SelectedItem="{Binding Selected}">
|
||||
SelectedItem="{Binding Selected}"
|
||||
Margin="10,10,10,4">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:SnippetRowViewModel">
|
||||
<Grid ColumnDefinitions="2,*" Margin="0,7,14,7">
|
||||
<Border Grid.Column="0" Classes="rowmark" />
|
||||
<StackPanel Grid.Column="1" Margin="12,0,0,0" Spacing="2">
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<TextBlock Classes="mono" Text="{Binding Label}" FontSize="12" FontWeight="Medium"
|
||||
Foreground="{StaticResource Text}" TextTrimming="CharacterEllipsis" />
|
||||
<StackPanel Margin="6,7" Spacing="6">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock Classes="mono" Text="{}{ }" FontSize="11" FontWeight="Bold"
|
||||
Foreground="{StaticResource AccentText}" VerticalAlignment="Center" />
|
||||
<TextBlock Classes="mono" Text="{Binding Label}" FontSize="13" FontWeight="Medium"
|
||||
Foreground="{StaticResource Text}" VerticalAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
<!--
|
||||
The flag, where the decision is made. A snippet that presses Enter for you is not the
|
||||
same kind of thing as one that does not, and the list is where somebody chooses between
|
||||
them.
|
||||
-->
|
||||
<Border Classes="chip warn" Padding="4,0" IsVisible="{Binding RunsOnInsert}">
|
||||
<Border Classes="chip warn" Padding="7,0" Height="18" VerticalAlignment="Center"
|
||||
IsVisible="{Binding RunsOnInsert}">
|
||||
<TextBlock Text="runs immediately" FontSize="9.5" />
|
||||
</Border>
|
||||
<Border Classes="chip warn" Padding="4,0"
|
||||
<Border Classes="chip warn" Padding="7,0" Height="18" VerticalAlignment="Center"
|
||||
IsVisible="{Binding Badge, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
|
||||
<TextBlock Text="{Binding Badge}" FontSize="9.5" />
|
||||
</Border>
|
||||
@@ -68,7 +146,8 @@
|
||||
snippet is a command the rest of a team can read and insert; the row is where somebody
|
||||
decides whether the thing they are about to edit is theirs alone.
|
||||
-->
|
||||
<Border Classes="chip" Padding="4,0" IsVisible="{Binding HasVaultBadge}">
|
||||
<Border Classes="chip" Padding="7,0" Height="18" VerticalAlignment="Center"
|
||||
IsVisible="{Binding HasVaultBadge}">
|
||||
<TextBlock Text="{Binding VaultBadge}" FontSize="9.5" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
@@ -76,37 +155,35 @@
|
||||
Newlines shown as ⏎ rather than dropped. A three-line snippet flattened into one run of
|
||||
text reads as a single command, which is the thing being decided about on this row.
|
||||
-->
|
||||
<TextBlock Classes="mono" Text="{Binding Preview}" FontSize="10.5"
|
||||
<TextBlock Classes="mono" Text="{Binding Preview}" FontSize="11.5"
|
||||
Foreground="{StaticResource TextFaint}" TextTrimming="CharacterEllipsis" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<TextBlock Grid.Row="1" Classes="hint" Text="{Binding EmptyMessage}" FontSize="12"
|
||||
<TextBlock Grid.Row="0" Classes="hint" Text="{Binding EmptyMessage}" FontSize="12"
|
||||
Margin="24" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
TextAlignment="Center" MaxWidth="360"
|
||||
IsVisible="{Binding !HasVisible}" />
|
||||
|
||||
<Border Grid.Row="2" Padding="14,8" BorderBrush="{StaticResource BorderSubtle}"
|
||||
<Border Grid.Row="1" Padding="16,10" BorderBrush="{StaticResource BorderSubtle}"
|
||||
BorderThickness="0,1,0,0">
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<Button Classes="ghost" Content="+ NEW SNIPPET" Command="{Binding NewCommand}" />
|
||||
<Button Classes="ghost" Content="EDIT" Command="{Binding EditCommand}"
|
||||
IsEnabled="{Binding HasSelection}" />
|
||||
<Button Classes="ghost" Content="DELETE" Command="{Binding DeleteCommand}"
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<Button Classes="ghost" Content="Edit" Command="{Binding EditCommand}"
|
||||
IsEnabled="{Binding HasSelection}" />
|
||||
<!--
|
||||
Sharing a snippet, which is what moving one into a team's vault is. Beside EDIT rather than
|
||||
inside it: the two vaults are encrypted under different keys, so this is a re-seal into one and
|
||||
a tombstone in the other — nothing a SAVE could do — and a picker inside the form would let
|
||||
somebody correcting a typo hand a command to a team by leaving it where they found it. The
|
||||
picker itself opens beside the snippet, in the pane on the right.
|
||||
inside it: the two vaults are encrypted under different keys, so this is a re-seal into one
|
||||
and a tombstone in the other — nothing a SAVE could do — and a picker inside the form would
|
||||
let somebody correcting a typo hand a command to a team by leaving it where they found it.
|
||||
The picker itself opens beside the snippet, in the pane on the right.
|
||||
-->
|
||||
<Button Classes="ghost" Content="MOVE TO VAULT…" Command="{Binding MoveCommand}"
|
||||
<Button Classes="ghost" Content="Move to vault…" Command="{Binding MoveCommand}"
|
||||
IsEnabled="{Binding CanMove}"
|
||||
ToolTip.Tip="Re-encrypts this snippet with another vault's key. Everybody who holds that key can then read and insert it." />
|
||||
<Button Classes="danger" Content="Delete" Command="{Binding RequestDeleteCommand}"
|
||||
IsEnabled="{Binding HasSelection}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
@@ -115,20 +192,22 @@
|
||||
<Border Grid.Column="1" Background="{StaticResource Sidebar}"
|
||||
BorderBrush="{StaticResource Border}" BorderThickness="1,0,0,0">
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="14,16" Spacing="8">
|
||||
<StackPanel Margin="18,20" Spacing="10">
|
||||
|
||||
<!-- ============ The editor ============ -->
|
||||
<StackPanel Spacing="6" IsVisible="{Binding IsEditing}">
|
||||
<TextBox Text="{Binding EditorLabel}" PlaceholderText="name" />
|
||||
<StackPanel Spacing="8" IsVisible="{Binding IsEditing}">
|
||||
<TextBlock Classes="label" Text="NAME" FontSize="10" />
|
||||
<TextBox Text="{Binding EditorLabel}" PlaceholderText="name" FontFamily="{StaticResource MonoFont}" />
|
||||
|
||||
<!--
|
||||
Which vault a *new* snippet is filed into, asked on the form it is being typed into rather
|
||||
than through a standing preference elsewhere. Not drawn for an existing snippet — its vault is
|
||||
not a field of this form, and changing it is MOVE — and not drawn at all where there is only
|
||||
one vault to choose between, because a control offering one option is a question nobody asked.
|
||||
than through a standing preference elsewhere. Not drawn for an existing snippet — its vault
|
||||
is not a field of this form, and changing it is MOVE — and not drawn at all where there is
|
||||
only one vault to choose between, because a control offering one option is a question
|
||||
nobody asked.
|
||||
-->
|
||||
<StackPanel Spacing="4" IsVisible="{Binding ShowsEditorVaultChoice}">
|
||||
<TextBlock Classes="label" Text="VAULT" />
|
||||
<TextBlock Classes="label" Text="VAULT" FontSize="10" />
|
||||
<ComboBox HorizontalAlignment="Stretch" ItemsSource="{Binding EditorVaultChoices}"
|
||||
SelectedItem="{Binding EditorSelectedVault}">
|
||||
<ComboBox.ItemTemplate>
|
||||
@@ -141,94 +220,104 @@
|
||||
Text="A shared vault means everybody holding its key can read this command and insert it into their own terminals." />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Classes="label" Text="COMMAND" FontSize="10" />
|
||||
<!--
|
||||
Stored exactly as typed — no trimming, no newline normalisation. A here-document's terminator
|
||||
has to arrive on a line of its own, and tidying the trailing newline off it leaves the shell
|
||||
waiting for one that never comes.
|
||||
Stored exactly as typed — no trimming, no newline normalisation. A here-document's
|
||||
terminator has to arrive on a line of its own, and tidying the trailing newline off it
|
||||
leaves the shell waiting for one that never comes.
|
||||
-->
|
||||
<TextBox Text="{Binding EditorCommand}" PlaceholderText="the command" AcceptsReturn="True"
|
||||
Height="140" TextWrapping="NoWrap" FontFamily="{StaticResource MonoFont}"
|
||||
FontSize="12" />
|
||||
<TextBox Text="{Binding EditorNotes}" PlaceholderText="notes" AcceptsReturn="True"
|
||||
Height="48" TextWrapping="Wrap" />
|
||||
<CheckBox IsChecked="{Binding EditorRunsOnInsert}"
|
||||
Content="Press Enter after inserting this" />
|
||||
<TextBlock Classes="hint" FontSize="11" TextWrapping="Wrap"
|
||||
|
||||
<Grid ColumnDefinitions="*,Auto" Margin="0,4,0,0">
|
||||
<StackPanel Grid.Column="0" Spacing="3">
|
||||
<TextBlock Text="Runs on insert" FontSize="12.5" FontWeight="SemiBold"
|
||||
Foreground="{StaticResource TextDim}" />
|
||||
<TextBlock Classes="hint" FontSize="10.5" TextWrapping="Wrap"
|
||||
Text="Off means the command is typed at the prompt and waits for you. That single Enter is the only thing standing between a saved command and a running one, so leave it off unless you meant it." />
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<Button Classes="accent" Content="SAVE" Command="{Binding SaveCommand}" />
|
||||
<Button Classes="ghost" Content="CANCEL" Command="{Binding CancelCommand}" />
|
||||
</StackPanel>
|
||||
<CheckBox Grid.Column="1" IsChecked="{Binding EditorRunsOnInsert}"
|
||||
VerticalAlignment="Top" Margin="10,0,0,0" />
|
||||
</Grid>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="6" Margin="0,6,0,0">
|
||||
<Button Classes="accent" Content="Save" Command="{Binding SaveCommand}" />
|
||||
<Button Classes="ghost" Content="Cancel" Command="{Binding CancelCommand}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- ============ The selected snippet ============ -->
|
||||
<StackPanel Spacing="6" IsVisible="{Binding !IsEditing}">
|
||||
<StackPanel Spacing="10" IsVisible="{Binding !IsEditing}">
|
||||
|
||||
<TextBlock Classes="hint" FontSize="12"
|
||||
Text="Choose a snippet to see it in full and put it into a terminal."
|
||||
IsVisible="{Binding !HasSelection}" />
|
||||
|
||||
<StackPanel Spacing="6" IsVisible="{Binding HasSelection}">
|
||||
<TextBlock Classes="mono" Text="{Binding Selected.Label}" FontSize="13"
|
||||
FontWeight="SemiBold" Foreground="{StaticResource Text}" TextWrapping="Wrap" />
|
||||
<StackPanel Spacing="10" IsVisible="{Binding HasSelection}">
|
||||
<TextBlock Classes="mono" Text="{Binding Selected.Label}" FontSize="15"
|
||||
FontWeight="Bold" Foreground="{StaticResource Text}" TextWrapping="Wrap" />
|
||||
|
||||
<!--
|
||||
Named here as well as on the row, because this pane is where somebody decides to put a
|
||||
command into a production terminal, and who else holds a key to it is part of that decision.
|
||||
The row's badge is off the screen by the time this is being read.
|
||||
-->
|
||||
<Border Classes="chip" Padding="4,0" HorizontalAlignment="Left"
|
||||
IsVisible="{Binding HasSelectionVaultBadge}">
|
||||
<TextBlock Text="{Binding SelectionVaultBadge}" FontSize="9.5" />
|
||||
</Border>
|
||||
|
||||
<TextBlock Classes="label" Text="COMMAND" Margin="0,10,0,4" />
|
||||
<Border Background="{StaticResource Raised}" BorderBrush="{StaticResource Border}"
|
||||
BorderThickness="1" CornerRadius="4" Padding="8">
|
||||
<TextBlock Classes="label" Text="COMMAND" FontSize="10" />
|
||||
<Border CornerRadius="10" Background="{StaticResource Pane}"
|
||||
BorderBrush="{StaticResource BorderMid}" BorderThickness="1" Padding="12">
|
||||
<SelectableTextBlock Classes="mono" Text="{Binding Selected.Snippet.Command}"
|
||||
FontSize="10.5" Foreground="{StaticResource TextDim}"
|
||||
TextWrapping="Wrap" />
|
||||
FontSize="11.5" LineHeight="19"
|
||||
Foreground="{StaticResource TextFaint}" TextWrapping="Wrap" />
|
||||
</Border>
|
||||
|
||||
<TextBlock Classes="mono" Text="{Binding Selected.Snippet.Notes}" FontSize="11"
|
||||
Foreground="{StaticResource TextFaint}" TextWrapping="Wrap" Margin="0,6,0,0"
|
||||
Foreground="{StaticResource TextFaint}" TextWrapping="Wrap"
|
||||
IsVisible="{Binding Selected.Snippet.Notes, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
|
||||
|
||||
<!--
|
||||
The button names the tab it will type into. This screen is not the terminal — the strip
|
||||
above it is — so "INSERT" alone would leave somebody working out which of six open tabs is
|
||||
about to receive a command, at the moment that is worst to be wrong about.
|
||||
VAULT, with the design's own lock glyph. Named here as well as on the row, because this
|
||||
pane is where somebody decides to put a command into a production terminal, and who else
|
||||
holds a key to it is part of that decision.
|
||||
-->
|
||||
<StackPanel Spacing="4" IsVisible="{Binding HasSelectionVaultBadge}">
|
||||
<TextBlock Classes="label" Text="VAULT" FontSize="10" />
|
||||
<Border Height="38" CornerRadius="10" Background="{StaticResource Pane}"
|
||||
BorderBrush="{StaticResource BorderMid}" BorderThickness="1" Padding="12,0">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Grid.Column="0" Classes="mono" Text="{Binding SelectionVaultBadge}"
|
||||
FontSize="11.5" Foreground="{StaticResource TextDim}"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock Grid.Column="1" FontFamily="{StaticResource IconFont}" Text=""
|
||||
FontSize="14" Foreground="{StaticResource TextFaint}"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<!--
|
||||
The button names the tab it will type into, over the design's static "Type into
|
||||
terminal" — see the file-level remark above for why the dynamic label is the truer one.
|
||||
-->
|
||||
<StackPanel Spacing="6" IsVisible="{Binding ShowsSelectionActions}">
|
||||
<Button Classes="accent" Content="{Binding InsertLabel}" Margin="0,14,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
<Button Classes="accent" Content="{Binding InsertLabel}" HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Center"
|
||||
Command="{Binding InsertCommand}" IsEnabled="{Binding CanInsert}"
|
||||
ToolTip.Tip="Types the command at the prompt and stops. Nothing runs until you press Enter there." />
|
||||
|
||||
<Button Classes="danger" Content="{Binding RunLabel}" HorizontalAlignment="Left"
|
||||
<Button Classes="danger" Content="{Binding RunLabel}" HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Center"
|
||||
Command="{Binding RunCommand}"
|
||||
IsVisible="{Binding SelectionRuns}" IsEnabled="{Binding CanInsert}"
|
||||
ToolTip.Tip="Types the command and presses Enter. Offered because this snippet is marked as one that runs." />
|
||||
|
||||
<TextBlock Classes="hint" FontSize="11" TextWrapping="Wrap" Margin="0,10,0,0"
|
||||
<TextBlock Classes="hint" FontSize="10.5" TextWrapping="Wrap" TextAlignment="Center"
|
||||
Text="Whatever is in the terminal receives this. Nothing here can tell whether that is a shell prompt, an editor, or a password prompt with the echo off — so check the tab before you insert." />
|
||||
</StackPanel>
|
||||
|
||||
<!--
|
||||
◆ SHARING THE SNIPPET, which is what moving it into a team's vault amounts to. A picker and
|
||||
two buttons, not a question with a yes: what is being asked is which vault, and a move is
|
||||
undone by moving it back rather than by being careful — so this is not drawn in the danger
|
||||
colours the deletion question uses.
|
||||
|
||||
It takes the insert controls' place while it is up, for the reason the host pane hides
|
||||
CONNECT: the button that opened this is still on screen otherwise, offering to open it again.
|
||||
|
||||
The sentence is the part worth keeping. Unlike a host, a snippet crosses whole — it has no
|
||||
group and no tags to leave behind — so what there is to say is who can read it afterwards,
|
||||
and for a command that may carry a hostname or a path that is the whole of the decision.
|
||||
◆ SHARING THE SNIPPET, which is what moving it into a team's vault amounts to. A picker
|
||||
and two buttons, not a question with a yes.
|
||||
-->
|
||||
<StackPanel Spacing="8" Margin="0,14,0,0" IsVisible="{Binding IsMoving}">
|
||||
<TextBlock Classes="label" Text="MOVE TO VAULT" />
|
||||
<StackPanel Spacing="8" IsVisible="{Binding IsMoving}">
|
||||
<TextBlock Classes="label" Text="MOVE TO VAULT" FontSize="10" />
|
||||
<ComboBox HorizontalAlignment="Stretch" ItemsSource="{Binding MoveVaultChoices}"
|
||||
SelectedItem="{Binding SelectedMoveVault}">
|
||||
<ComboBox.ItemTemplate>
|
||||
@@ -240,10 +329,30 @@
|
||||
<TextBlock Classes="hint" FontSize="10.5" TextWrapping="Wrap"
|
||||
Text="The snippet is re-encrypted with the other vault's key, so everybody who holds that key can read this command and insert it — and nobody else can. Nothing else about it changes." />
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<Button Classes="accent" Content="MOVE" Command="{Binding ConfirmMoveCommand}" />
|
||||
<Button Classes="ghost" Content="CANCEL" Command="{Binding CancelMoveCommand}" />
|
||||
<Button Classes="accent" Content="Move" Command="{Binding ConfirmMoveCommand}" />
|
||||
<Button Classes="ghost" Content="Cancel" Command="{Binding CancelMoveCommand}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!--
|
||||
◆ THE DELETE CONFIRMATION. See RequestDelete's own remarks for why this is additive
|
||||
beside DeleteCommand rather than a change to it.
|
||||
-->
|
||||
<Border Background="{StaticResource DangerWash}" BorderBrush="{StaticResource DangerSoft}"
|
||||
BorderThickness="1" CornerRadius="10" Padding="12"
|
||||
IsVisible="{Binding IsConfirmingDelete}">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Classes="heading" FontSize="14" TextWrapping="Wrap"
|
||||
Text="{Binding DeleteQuestion}" />
|
||||
<TextBlock Foreground="{StaticResource WarnText}" FontSize="12" TextWrapping="Wrap"
|
||||
Text="{Binding DeleteConsequence}" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<Button Classes="danger" Content="Delete snippet"
|
||||
Command="{Binding ConfirmDeleteCommand}" />
|
||||
<Button Classes="ghost" Content="Cancel" Command="{Binding CancelDeleteCommand}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
@@ -252,5 +361,8 @@
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
|
||||
Reference in New Issue
Block a user