Public Access
Give the application a settings area built from what really exists
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using DodoSSH.Client.Domain;
|
||||
@@ -54,6 +55,16 @@ internal sealed partial class ImportRowViewModel : ObservableObject
|
||||
|
||||
internal string Address => host.Address;
|
||||
|
||||
/// <summary>What <c>HostName</c> said, on its own — the v5c table's own column, beside <see cref="User"/>
|
||||
/// and <see cref="Port"/> rather than folded into <see cref="Address"/>.</summary>
|
||||
internal string Hostname => host.Hostname;
|
||||
|
||||
/// <summary>What <c>User</c> said, or an em dash where the entry named none.</summary>
|
||||
internal string User => host.Username is { Length: > 0 } user ? user : "—";
|
||||
|
||||
/// <summary>What <c>Port</c> said, defaulting to 22 the same way <see cref="ImportedHost"/> does.</summary>
|
||||
internal string Port => host.Port.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
/// <summary>Whether a host with this address is already in the keychain.</summary>
|
||||
internal bool AlreadyPresent { get; }
|
||||
|
||||
@@ -61,6 +72,23 @@ internal sealed partial class ImportRowViewModel : ObservableObject
|
||||
|
||||
internal bool HasBadge => AlreadyPresent;
|
||||
|
||||
/// <summary>
|
||||
/// What the v5c table's WHAT THIS MEANS chip says, mapped honestly off the two facts this row actually
|
||||
/// carries — nothing this screen cannot back up. Skipped patterns (a wildcard <c>Host</c> block) never
|
||||
/// become a row at all, so there is no third, "skipped" state to draw here; a row's own per-host
|
||||
/// warnings, from <see cref="HasWarnings"/>, are the amber case instead — a flattened <c>ProxyJump</c> or
|
||||
/// a dropped directive is exactly the kind of thing "quieter than the file" that <c>ImportScreen.axaml</c>'s
|
||||
/// own remark says has to be told before it looks like data loss.
|
||||
/// </summary>
|
||||
internal string Meaning => HasWarnings ? Warnings : AlreadyPresent ? "already here" : "new host";
|
||||
|
||||
/// <summary>The warned case wins over "already here" — a warning is the more actionable of the two facts.</summary>
|
||||
internal bool IsMeaningWarned => HasWarnings;
|
||||
|
||||
internal bool IsMeaningExisting => !HasWarnings && AlreadyPresent;
|
||||
|
||||
internal bool IsMeaningNew => !HasWarnings && !AlreadyPresent;
|
||||
|
||||
/// <summary>Whether this row's <c>ssh_config</c> entry named a key at all.</summary>
|
||||
/// <remarks>
|
||||
/// Most do not, and the tick above the list is about the ones that do. Kept as a property rather than
|
||||
@@ -138,7 +166,10 @@ internal sealed partial class ImportRowViewModel : ObservableObject
|
||||
/// <see cref="KeyReport"/>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
internal sealed partial class ImportViewModel(VaultViewModel vault, SshConfigLocator locator) : ObservableObject
|
||||
internal sealed partial class ImportViewModel(
|
||||
VaultViewModel vault,
|
||||
SshConfigLocator locator,
|
||||
Action? onCancel = null) : ObservableObject
|
||||
{
|
||||
internal ObservableCollection<ImportRowViewModel> Rows { get; } = [];
|
||||
|
||||
@@ -202,6 +233,66 @@ internal sealed partial class ImportViewModel(VaultViewModel vault, SshConfigLoc
|
||||
|
||||
internal string ImportLabel => SelectedCount == 1 ? "IMPORT 1 HOST" : $"IMPORT {SelectedCount} HOSTS";
|
||||
|
||||
/// <summary>Whether every row is ticked — what the v5c table's header tick-all box shows.</summary>
|
||||
internal bool AllTicked => Rows.Count > 0 && SelectedCount == Rows.Count;
|
||||
|
||||
/// <summary>
|
||||
/// The v5c header's own mono status line: the file this reads, and whether it has been read yet.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Two real facts and nothing invented — <see cref="ConfigPath"/> and <see cref="HasScanned"/>. The
|
||||
/// fuller narrative belongs to <see cref="Status"/>, which this does not replace: what happened on a scan
|
||||
/// or an import is a sentence, not a fact this header line has room to state honestly in a handful of
|
||||
/// words.
|
||||
/// </remarks>
|
||||
internal string HeaderStatus => HasScanned ? $"{ConfigPath} · scanned" : $"{ConfigPath} · not scanned yet";
|
||||
|
||||
/// <summary>
|
||||
/// The key-material card's own always-visible sentence, ahead of the tick.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The count is hosts naming a key file, not raw <c>IdentityFile</c> lines — a fact <see cref="Rows"/>
|
||||
/// actually carries, where a literal line count would not survive a host that names more than one and is
|
||||
/// only ever bound to the first. The rest of the sentence is <c>ImportViewModel</c>'s own long-standing
|
||||
/// claim, restated in the design's words after checking it against <c>SshConfigLocator</c>: this type is
|
||||
/// the only place in the application that reads a private key out of a directory nobody pointed at file
|
||||
/// by file, and <see cref="ImportAsync"/> is the only place that ever calls
|
||||
/// <see cref="SshConfigLocator.ReadIdentity"/> — never <see cref="ScanAsync"/> — so nothing is read until
|
||||
/// IMPORT is pressed.
|
||||
/// </remarks>
|
||||
internal string KeyMaterialIntro
|
||||
{
|
||||
get
|
||||
{
|
||||
var count = Rows.Count(row => row.HasKeyFile);
|
||||
var directory = Path.GetDirectoryName(ConfigPath) ?? ConfigPath;
|
||||
var noun = count == 1 ? "host names" : "hosts name";
|
||||
|
||||
return $"The scan found {count} {noun} a key file in {directory}. This is the only control in "
|
||||
+ "DodoSSH that opens key material from a directory you did not point at file by file — "
|
||||
+ "nothing is read until Import is pressed.";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The vault every import lands in — see <see cref="VaultViewModel.ImportHostsAsync"/>.</summary>
|
||||
/// <remarks>
|
||||
/// Fixed rather than offered as a picker: the import goes through the same
|
||||
/// <c>session.ActiveVaultId</c> every other bulk write does, and there is no per-import target choice to
|
||||
/// bind — see design-notes/v5c-fidelity-notes.md. Printed as a fact instead of drawn as a dropdown.
|
||||
/// </remarks>
|
||||
internal string VaultName => vault.VaultName;
|
||||
|
||||
/// <summary>The v5c footer's own sentence: how many are ticked, out of how many, and where they land.</summary>
|
||||
internal string SelectionSummary
|
||||
{
|
||||
get
|
||||
{
|
||||
var noun = Rows.Count == 1 ? "entry" : "entries";
|
||||
|
||||
return $"{SelectedCount} of {Rows.Count} {noun} selected · saving to {VaultName}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Reads the file and shows what it found. Writes nothing.</summary>
|
||||
[RelayCommand]
|
||||
private async Task ScanAsync(CancellationToken cancellationToken)
|
||||
@@ -389,6 +480,16 @@ internal sealed partial class ImportViewModel(VaultViewModel vault, SshConfigLoc
|
||||
|
||||
internal void NoteSelectionChanged() => RaiseListState();
|
||||
|
||||
/// <summary>The footer's own Cancel button: back to the Preferences page, nothing stored.</summary>
|
||||
/// <remarks>
|
||||
/// A delegate rather than a reference up to <c>MainWindowViewModel</c>, on the same reasoning
|
||||
/// <c>VaultViewModel</c>'s own <c>copyToClipboard</c> is one: this type has no business knowing settings
|
||||
/// mode exists, and a null delegate — nothing wired, as in a layout test that builds this directly — makes
|
||||
/// the button a no-op rather than a crash.
|
||||
/// </remarks>
|
||||
[RelayCommand]
|
||||
private void Cancel() => onCancel?.Invoke();
|
||||
|
||||
/// <remarks>
|
||||
/// The rows carry the answer as well as the view model, because each one says what it will authenticate
|
||||
/// with and that sentence changes with the tick. Pushed rather than bound per row: a row cannot see a
|
||||
@@ -441,5 +542,11 @@ internal sealed partial class ImportViewModel(VaultViewModel vault, SshConfigLoc
|
||||
OnPropertyChanged(nameof(HasKeyReport));
|
||||
OnPropertyChanged(nameof(SelectedCount));
|
||||
OnPropertyChanged(nameof(ImportLabel));
|
||||
OnPropertyChanged(nameof(AllTicked));
|
||||
OnPropertyChanged(nameof(KeyMaterialIntro));
|
||||
OnPropertyChanged(nameof(SelectionSummary));
|
||||
}
|
||||
|
||||
/// <remarks>The header's own status line is a function of <see cref="HasScanned"/> alone.</remarks>
|
||||
partial void OnHasScannedChanged(bool value) => OnPropertyChanged(nameof(HeaderStatus));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user