Files
DodoSSH/src/DodoSSH.Client.App/Views/SessionSidebar.axaml.cs
T
jaap-jan f05deb4fbc
ci / build and test (push) Successful in 3m43s
ci / api image (push) Canceled after 0s
ci / android head (push) Canceled after 4m6s
ci / desktop nightly (push) Canceled after 21s
Add port forwarding: local, remote and dynamic, per terminal session
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.
2026-09-28 20:47:02 +02:00

40 lines
1.5 KiB
C#

using Avalonia;
using Avalonia.Controls;
namespace DodoSSH.Client.App.Views;
/// <summary>
/// The v5b session sidebar: QUICK ACCESS on both surfaces, SNIPS on the terminal's own. See the remark at
/// the top of SessionSidebar.axaml.
/// </summary>
internal sealed partial class SessionSidebar : UserControl
{
/// <summary>Whether the SNIPS section draws below QUICK ACCESS.</summary>
/// <remarks>
/// Set from the usage site rather than inferred from a surface flag on the shell, for the same reason
/// <see cref="SessionTabRow.TabCommand"/> is: which sections a particular instance of this control shows
/// is a fact about where it was placed in <c>MainWindow.axaml</c>, not one this control can read off its
/// own <c>DataContext</c>.
/// </remarks>
internal static readonly StyledProperty<bool> ShowsSnipsProperty =
AvaloniaProperty.Register<SessionSidebar, bool>(nameof(ShowsSnips));
/// <summary>Whether the FORWARDS section draws. The terminal's own, for the reason SNIPS is.</summary>
internal static readonly StyledProperty<bool> ShowsForwardsProperty =
AvaloniaProperty.Register<SessionSidebar, bool>(nameof(ShowsForwards));
public SessionSidebar() => InitializeComponent();
internal bool ShowsSnips
{
get => GetValue(ShowsSnipsProperty);
set => SetValue(ShowsSnipsProperty, value);
}
internal bool ShowsForwards
{
get => GetValue(ShowsForwardsProperty);
set => SetValue(ShowsForwardsProperty, value);
}
}