Public Access
Desktop: show the right vault's people, leave settings on lock, say when an import fell short, and stop host names being cut to four letters
- Choosing a vault while the screen was busy went through a gate that did nothing, so the members
panel kept showing the previous vault's people under the new name. The read now starts regardless;
the selection generation discards answers for an earlier choice.
- Locking or signing out from Settings left the window in settings mode under the lock card, with no
title bar to drag, minimise or close, and unlock returned to Settings. Leaving the unlocked state
leaves Settings and closes the members panel; Escape closes that panel before leaving Settings.
- Both importers reported success when the keychain was busy and skipped the batch, or failed partway.
ImportOutcome now carries refused hosts and why a batch stopped, and the screens say so.
- Host cards: the sync badge took 70 of 214 pixels from the name ("bastion" drew as "bast…", two k8s
workers both as "k8s…"); it now sits on the address line. Tag chips wrap and trim inside the card.
Keychain names trim inside their column instead of printing over TYPE, and the Security page's
caption wraps inside its card.
This commit is contained in:
1 parent
0e8fe4fc80
commit
a4b81da597
11 files changed
+314
-68
No files matched your search
@@ -8838,53 +8838,94 @@ internal sealed partial class VaultViewModel(
|
||||
ArgumentNullException.ThrowIfNull(requests);
|
||||
|
||||
var target = vaultId ?? session.ActiveVaultId;
|
||||
var hosts = 0;
|
||||
var keys = 0;
|
||||
var refused = 0;
|
||||
var tally = new ImportTally();
|
||||
var ran = false;
|
||||
string? failure = null;
|
||||
|
||||
await RunAsync(
|
||||
requests.Count == 1 ? "Importing 1 host…" : $"Importing {requests.Count} hosts…",
|
||||
async () =>
|
||||
{
|
||||
// Keyed on the path rather than on the material, and filled as the batch runs rather than up
|
||||
// front: the second host naming a file has to land on the id the first one produced, and
|
||||
// nothing is reloaded until the batch finishes.
|
||||
var bound = new Dictionary<string, Guid>(StringComparer.OrdinalIgnoreCase);
|
||||
ran = true;
|
||||
|
||||
foreach (var request in requests)
|
||||
try
|
||||
{
|
||||
if (!request.Host.TryValidate(out _))
|
||||
{
|
||||
refused++;
|
||||
continue;
|
||||
}
|
||||
|
||||
var (keyId, created) = await BindImportedKeyAsync(
|
||||
request, bound, target, cancellationToken)
|
||||
.ConfigureAwait(true);
|
||||
|
||||
if (created)
|
||||
{
|
||||
keys++;
|
||||
}
|
||||
|
||||
var host = keyId is { } id ? request.Host with { SshKeyId = id } : request.Host;
|
||||
|
||||
await session.Hosts
|
||||
.CreateAsync(target, host, cancellationToken)
|
||||
.ConfigureAwait(true);
|
||||
|
||||
hosts++;
|
||||
await ImportEachAsync(requests, target, tally, cancellationToken).ConfigureAwait(true);
|
||||
}
|
||||
catch (Exception exception) when (exception is not OutOfMemoryException
|
||||
and not OperationCanceledException)
|
||||
{
|
||||
// Reported, and the hosts written before it are still reloaded below: they are in the
|
||||
// keychain whether or not the rest followed, and the screen has to say both.
|
||||
failure = exception.Message;
|
||||
}
|
||||
|
||||
await ReloadAsync(cancellationToken).ConfigureAwait(true);
|
||||
|
||||
Status = DescribeImport(hosts, keys, refused);
|
||||
Status = DescribeImport(tally.Hosts, tally.Keys, tally.Refused)
|
||||
+ (failure is null ? string.Empty : $" The import stopped early: {failure}");
|
||||
}).ConfigureAwait(true);
|
||||
|
||||
if (!ran)
|
||||
{
|
||||
// RunAsync returns at once while anything else is running — a sync pass, a save — and the
|
||||
// screen asking would otherwise have said it imported nothing as though that were the result.
|
||||
failure = "the keychain was busy with something else. Nothing was imported; try again.";
|
||||
}
|
||||
|
||||
await AutoSyncAsync(cancellationToken).ConfigureAwait(true);
|
||||
|
||||
return new ImportOutcome(hosts, keys);
|
||||
return new ImportOutcome(tally.Hosts, tally.Keys, tally.Refused, failure);
|
||||
}
|
||||
|
||||
/// <summary>Writes each host of an import, and any key it brought, counting as it goes.</summary>
|
||||
/// <remarks>
|
||||
/// Counted into <paramref name="tally"/> rather than returned, so that when a write fails partway the
|
||||
/// caller still knows how many landed before it — which is what it has to tell the person.
|
||||
/// </remarks>
|
||||
private async Task ImportEachAsync(
|
||||
IReadOnlyList<ImportedHostRequest> batch,
|
||||
Guid into,
|
||||
ImportTally tally,
|
||||
CancellationToken token)
|
||||
{
|
||||
// Keyed on the path rather than on the material, and filled as the batch runs rather than up
|
||||
// front: the second host naming a file has to land on the id the first one produced, and nothing
|
||||
// is reloaded until the batch finishes.
|
||||
var bound = new Dictionary<string, Guid>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var request in batch)
|
||||
{
|
||||
if (!request.Host.TryValidate(out _))
|
||||
{
|
||||
tally.Refused++;
|
||||
continue;
|
||||
}
|
||||
|
||||
var (keyId, created) = await BindImportedKeyAsync(request, bound, into, token)
|
||||
.ConfigureAwait(true);
|
||||
|
||||
if (created)
|
||||
{
|
||||
tally.Keys++;
|
||||
}
|
||||
|
||||
var host = keyId is { } id ? request.Host with { SshKeyId = id } : request.Host;
|
||||
|
||||
await session.Hosts.CreateAsync(into, host, token).ConfigureAwait(true);
|
||||
|
||||
tally.Hosts++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>What one import has written so far.</summary>
|
||||
private sealed class ImportTally
|
||||
{
|
||||
internal int Hosts { get; set; }
|
||||
|
||||
internal int Keys { get; set; }
|
||||
|
||||
internal int Refused { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>Finds or stores the key an imported host should be bound to.</summary>
|
||||
|
||||
Reference in new issue
Block a user