Public Access
A host can now carry port forwards (-L, -R, -D), saved inside its encrypted payload as RelayTicketRequest.PortForwardId already assumed, so they sync and merge per forward like tags and pinned paths. The host schema moves to version 8 only for hosts that carry forwards. ISshConnection gains StartForwardAsync over SSH.NET's forwarded ports. Every listener binds loopback and there is no bind-address field: a forward is something any member of a shared keychain can edit. A remote forward's target is resolved here, IPv4 first, rather than by SSH.NET's constructor, which takes the first DNS answer and can send ::1. Forwards start with every terminal opened to the host and stop with its connection; a reconnect starts them again. The session sidebar lists them with state, failure reason and an on/off switch, the status bar counts the listening ones, and the host editor and detail pane gain a PORT FORWARDS section. A forward that cannot start fails alone. The phone does not start forwards yet: it has no surface showing what is listening, and its More screen now says so. No byte counts, since SSH.NET keeps none.
261 lines
16 KiB
XML
261 lines
16 KiB
XML
<UserControl xmlns="https://github.com/avaloniaui"
|
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
xmlns:vm="using:DodoSSH.Client.Shell.ViewModels"
|
|
x:Class="DodoSSH.Client.App.Views.SessionSidebar"
|
|
x:Name="Root"
|
|
x:DataType="vm:MainWindowViewModel">
|
|
|
|
<!--
|
|
── v5b's session sidebar ────────────────────────────────────────────────────────────────────────────────
|
|
300 pixels, Sidebar background, a 1px left border — the design's own right-hand column on both the
|
|
terminal and the SFTP screen. It replaces the pin chip strip that used to sit above the terminal: see
|
|
MainWindowViewModel.ActiveTabPinnedPaths and OpenPinnedPathCommand, both reused here unchanged, and
|
|
MainWindow.axaml for where the old strip's Border used to live.
|
|
|
|
QUICK ACCESS is on both surfaces. SNIPS is the terminal's own — gated on <see cref="ShowsSnips"/>, which
|
|
the terminal usage in MainWindow.axaml sets true and the SFTP usage leaves false, rather than a second
|
|
copy of this file: the two sections share nothing surface-specific except which one is drawn at all.
|
|
|
|
Every row here is a command the shell already exposes for exactly this purpose — see
|
|
MainWindowViewModel.PinFolderFromSidebarCommand, AddSnippetFromSidebarCommand and InsertSnippetCommand —
|
|
so this control carries no logic of its own beyond the list it draws and the click it forwards.
|
|
|
|
── v5c-4: THE SESSION BLOCK AT THE HEAD, AND THE HEADER ROW THAT IS GONE ─────────────────────────────────
|
|
The 60-pixel host header that used to sit above the pane on both surfaces has been retired, and its two
|
|
contents moved up here: the address it printed, and the cross-surface button — "Open SFTP" from a
|
|
terminal, "Open terminal" from SFTP. Which of the two words it is and which command it runs are resolved
|
|
by the shell now rather than handed in from the two usage sites; see
|
|
MainWindowViewModel.SessionCrossSurfaceLabel and OpenOtherSurfaceCommand. The pane keeps that height.
|
|
|
|
The address is the fact the header row existed for, so it moves rather than disappears. It sits where the
|
|
QUICK ACCESS heading used to print the selected tab's short label — that label said less than the address
|
|
does and would be the same word twice beside it.
|
|
|
|
── AND THE COLUMN CLOSES ────────────────────────────────────────────────────────────────────────────────
|
|
300 pixels is a lot of a 1180-pixel window to give a list that is often two rows long, so the column
|
|
folds to a 34-pixel rail carrying the way back. A rail rather than nothing: a panel that vanishes without
|
|
trace is one people report as lost. Both halves live in this control and swap on
|
|
MainWindowViewModel.IsSessionSidebarOpen, so MainWindow.axaml's own "Auto" column takes whichever width
|
|
is showing without knowing anything about the state — and the pane beside it grows into what is freed.
|
|
-->
|
|
|
|
<!--
|
|
A forward's state word: green only while it is listening, which is the one status Live is for, and the
|
|
danger text colour for a forward that could not start. OFF and STARTING stay faint.
|
|
-->
|
|
<UserControl.Styles>
|
|
<Style Selector="TextBlock.fwdstate">
|
|
<Setter Property="Foreground" Value="{StaticResource TextFaint}" />
|
|
</Style>
|
|
<Style Selector="TextBlock.fwdstate.live">
|
|
<Setter Property="Foreground" Value="{StaticResource Live}" />
|
|
</Style>
|
|
<Style Selector="TextBlock.fwdstate.failed">
|
|
<Setter Property="Foreground" Value="{StaticResource DangerText}" />
|
|
</Style>
|
|
</UserControl.Styles>
|
|
|
|
<Panel>
|
|
|
|
<!-- ============ THE RAIL, WHEN THE COLUMN IS CLOSED ============ -->
|
|
<!--
|
|
Painted and bordered like the open column so the closing reads as the same surface narrowing rather
|
|
than as one piece of furniture being swapped for another.
|
|
-->
|
|
<Border Width="34" Background="{StaticResource Sidebar}"
|
|
BorderBrush="{StaticResource Border}" BorderThickness="1,0,0,0"
|
|
IsVisible="{Binding !IsSessionSidebarOpen}">
|
|
<Button Classes="flat sidebargrip" VerticalAlignment="Top" Margin="0,20,0,0"
|
|
Command="{Binding ToggleSessionSidebarCommand}"
|
|
ToolTip.Tip="Show quick access, snips and the way across to the other surface">
|
|
<TextBlock Text="" FontFamily="{StaticResource IconFont}" FontSize="18"
|
|
Foreground="{StaticResource TextFaint}"
|
|
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
|
</Button>
|
|
</Border>
|
|
|
|
<!-- ============ THE COLUMN ============ -->
|
|
<Border Width="300" Background="{StaticResource Sidebar}"
|
|
BorderBrush="{StaticResource Border}" BorderThickness="1,0,0,0"
|
|
IsVisible="{Binding IsSessionSidebarOpen}">
|
|
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
|
<StackPanel Spacing="6" Margin="16,20">
|
|
|
|
<!-- ============ THE SESSION ============ -->
|
|
<!--
|
|
The address, and the button that closes the column. Both on one row, and the address is the
|
|
trimming one: a long account@host:port is exactly the string that would otherwise push the
|
|
close button off the edge of a panel whose whole point is that it can be got rid of.
|
|
|
|
SelectableTextBlock rather than TextBlock, which is what the rest of this application already
|
|
reaches for whenever a string is on screen to be taken somewhere else — a fingerprint, a public
|
|
key, a recovery code. This is the one place the shell prints the endpoint it actually dialled,
|
|
and the endpoint is a thing people paste: into another client, into a ticket, into the ssh
|
|
command they are writing beside this window. Retyping it off the screen is how a digit gets
|
|
dropped from an address that is mostly digits.
|
|
|
|
The trimming and the selection do disagree at the long end, and the ToolTip is why the
|
|
disagreement is tolerable rather than a trap: a drag reaches only the glyphs that were drawn,
|
|
so an address wearing an ellipsis has to be taken with select-all — Ctrl+A, then Ctrl+C — and
|
|
the hover already shows what the full string is, so nobody is copying half of one without
|
|
having been told there is a half.
|
|
-->
|
|
<Grid ColumnDefinitions="*,Auto" Margin="8,0,0,0">
|
|
<SelectableTextBlock Grid.Column="0" FontFamily="{StaticResource MonoFont}" FontWeight="Bold"
|
|
FontSize="12.5" Foreground="{StaticResource AccentText}"
|
|
VerticalAlignment="Center" TextTrimming="CharacterEllipsis"
|
|
Text="{Binding SessionAddress}" ToolTip.Tip="{Binding SessionAddress}" />
|
|
<Button Grid.Column="1" Classes="flat sidebargrip"
|
|
Command="{Binding ToggleSessionSidebarCommand}"
|
|
ToolTip.Tip="Close this column. The terminal takes the width, and the rail it leaves behind brings it back.">
|
|
<TextBlock Text="" FontFamily="{StaticResource IconFont}" FontSize="18"
|
|
Foreground="{StaticResource TextFaint}"
|
|
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
|
</Button>
|
|
</Grid>
|
|
|
|
<!--
|
|
The cross-surface button, stretched across the column rather than sized to its own caption: it
|
|
is the one action in this panel that is not a list row, and a 90-pixel button floating at the
|
|
left of a 300-pixel column would read as unfinished.
|
|
-->
|
|
<Button Classes="headerghost" HorizontalAlignment="Stretch" Margin="0,4,0,10"
|
|
Content="{Binding SessionCrossSurfaceLabel}"
|
|
Command="{Binding OpenOtherSurfaceCommand}" />
|
|
|
|
<!-- ============ QUICK ACCESS ============ -->
|
|
<TextBlock Classes="label" Text="QUICK ACCESS" FontSize="10" Margin="8,0" />
|
|
|
|
<ItemsControl ItemsSource="{Binding ActiveTabPinnedPaths}">
|
|
<ItemsControl.ItemTemplate>
|
|
<DataTemplate x:DataType="x:String">
|
|
<!--
|
|
A single level of $parent[ItemsControl] reaches the shell directly, the same way the old pin
|
|
strip's chips did: this ItemsControl's own DataContext is MainWindowViewModel, so one hop up
|
|
from the path's string DataContext lands on it.
|
|
-->
|
|
<Button Classes="sidebarrow"
|
|
Command="{Binding $parent[ItemsControl].((vm:MainWindowViewModel)DataContext).OpenPinnedPathCommand}"
|
|
CommandParameter="{Binding}"
|
|
ToolTip.Tip="{Binding}">
|
|
<StackPanel Orientation="Horizontal" Spacing="10">
|
|
<TextBlock Text="" FontFamily="{StaticResource IconFont}" FontSize="15"
|
|
Foreground="{StaticResource AccentText}" VerticalAlignment="Center" />
|
|
<TextBlock FontFamily="{StaticResource MonoFont}" FontSize="12.5" Text="{Binding}"
|
|
TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
|
|
</StackPanel>
|
|
</Button>
|
|
</DataTemplate>
|
|
</ItemsControl.ItemTemplate>
|
|
</ItemsControl>
|
|
|
|
<Button Classes="sidebaradd" Command="{Binding PinFolderFromSidebarCommand}"
|
|
ToolTip.Tip="Opens the active tab's host for editing, at QUICK ACCESS.">
|
|
<StackPanel Orientation="Horizontal" Spacing="10">
|
|
<TextBlock Text="" FontFamily="{StaticResource IconFont}" FontSize="14"
|
|
VerticalAlignment="Center" />
|
|
<TextBlock Text="Pin folder" VerticalAlignment="Center" />
|
|
</StackPanel>
|
|
</Button>
|
|
|
|
<!-- ============ FORWARDS (the terminal surface only) ============ -->
|
|
<!--
|
|
The selected tab's forwards: the host's saved ones, as they run over this session. Each row is
|
|
its own switch, and a failed one says why under its notation — a port already taken, most often
|
|
by a second tab to the same host running the same forwards. Nothing here edits a forward; the add
|
|
row goes to the host's editor, where they are saved, as Pin folder does for QUICK ACCESS.
|
|
|
|
No byte counts, which the design draws. SSH.NET keeps no count of what a forward carries, so a
|
|
number here would be invented.
|
|
-->
|
|
<StackPanel Spacing="6" Margin="0,16,0,0" IsVisible="{Binding #Root.ShowsForwards}">
|
|
|
|
<TextBlock Classes="label" Text="FORWARDS" FontSize="10" Margin="8,0" />
|
|
|
|
<ItemsControl ItemsSource="{Binding SelectedTab.Forwards}">
|
|
<ItemsControl.ItemTemplate>
|
|
<DataTemplate x:DataType="vm:PortForwardViewModel">
|
|
<!--
|
|
The reason sits under the row rather than inside it: a sidebar row is a fixed 33 pixels, and
|
|
a refusal is a sentence. Indented to the notation, so it reads as belonging to the row.
|
|
-->
|
|
<StackPanel>
|
|
<Button Classes="sidebarrow" Command="{Binding ToggleCommand}"
|
|
ToolTip.Tip="{Binding Route}">
|
|
<Grid ColumnDefinitions="Auto,*,Auto">
|
|
<TextBlock Grid.Column="0" Text=""
|
|
FontFamily="{StaticResource IconFont}" FontSize="15"
|
|
Foreground="{StaticResource AccentText}" VerticalAlignment="Center"
|
|
Margin="0,0,10,0" />
|
|
<TextBlock Grid.Column="1" FontFamily="{StaticResource MonoFont}" FontSize="12.5"
|
|
Text="{Binding Notation}" TextTrimming="CharacterEllipsis"
|
|
VerticalAlignment="Center" />
|
|
<TextBlock Grid.Column="2" Classes="fwdstate" FontFamily="Montserrat"
|
|
FontWeight="SemiBold" FontSize="9" LetterSpacing="1"
|
|
VerticalAlignment="Center" Margin="8,0,0,0"
|
|
Text="{Binding StateText}"
|
|
Classes.live="{Binding IsListening}" Classes.failed="{Binding IsFailed}" />
|
|
</Grid>
|
|
</Button>
|
|
<TextBlock FontSize="11" Foreground="{StaticResource TextFaint}" TextWrapping="Wrap"
|
|
Margin="37,0,12,4" Text="{Binding Detail}"
|
|
IsVisible="{Binding Detail, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
|
|
</StackPanel>
|
|
</DataTemplate>
|
|
</ItemsControl.ItemTemplate>
|
|
</ItemsControl>
|
|
|
|
<Button Classes="sidebaradd" Command="{Binding AddForwardFromSidebarCommand}"
|
|
ToolTip.Tip="Opens the active tab's host for editing, where its forwards are saved. A new forward starts with the next connection.">
|
|
<StackPanel Orientation="Horizontal" Spacing="10">
|
|
<TextBlock Text="" FontFamily="{StaticResource IconFont}" FontSize="14"
|
|
VerticalAlignment="Center" />
|
|
<TextBlock Text="Add forward" VerticalAlignment="Center" />
|
|
</StackPanel>
|
|
</Button>
|
|
|
|
</StackPanel>
|
|
|
|
<!-- ============ SNIPS (the terminal surface only) ============ -->
|
|
<StackPanel Spacing="6" Margin="0,16,0,0" IsVisible="{Binding #Root.ShowsSnips}">
|
|
|
|
<TextBlock Classes="label" Text="SNIPS" FontSize="10" Margin="8,0" />
|
|
|
|
<ItemsControl ItemsSource="{Binding SnippetsScreen.Visible}">
|
|
<ItemsControl.ItemTemplate>
|
|
<DataTemplate x:DataType="vm:SnippetRowViewModel">
|
|
<Button Classes="sidebarrow"
|
|
Command="{Binding $parent[ItemsControl].((vm:MainWindowViewModel)DataContext).InsertSnippetCommand}"
|
|
CommandParameter="{Binding}"
|
|
ToolTip.Tip="{Binding Snippet.Command}">
|
|
<StackPanel Orientation="Horizontal" Spacing="10">
|
|
<TextBlock Text="{}{ }" FontFamily="{StaticResource MonoFont}" FontWeight="Bold"
|
|
FontSize="11" Foreground="{StaticResource AccentText}"
|
|
VerticalAlignment="Center" />
|
|
<TextBlock FontFamily="{StaticResource MonoFont}" FontSize="12.5" Text="{Binding Label}"
|
|
TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
|
|
</StackPanel>
|
|
</Button>
|
|
</DataTemplate>
|
|
</ItemsControl.ItemTemplate>
|
|
</ItemsControl>
|
|
|
|
<Button Classes="sidebaradd" Command="{Binding AddSnippetFromSidebarCommand}"
|
|
ToolTip.Tip="Opens the snippet editor.">
|
|
<StackPanel Orientation="Horizontal" Spacing="10">
|
|
<TextBlock Text="" FontFamily="{StaticResource IconFont}" FontSize="14"
|
|
VerticalAlignment="Center" />
|
|
<TextBlock Text="Add Snip" VerticalAlignment="Center" />
|
|
</StackPanel>
|
|
</Button>
|
|
|
|
</StackPanel>
|
|
|
|
</StackPanel>
|
|
</ScrollViewer>
|
|
</Border>
|
|
|
|
</Panel>
|
|
|
|
</UserControl>
|