fix(ui): ssh settings tab survives an offline worker without wiping workspaces
This commit is contained in:
@@ -13,6 +13,9 @@ public sealed partial class SshSettingsTabViewModel : ViewModelBase
|
|||||||
private readonly IWorkerClient _worker;
|
private readonly IWorkerClient _worker;
|
||||||
private readonly Func<string> _readSshConfig;
|
private readonly Func<string> _readSshConfig;
|
||||||
|
|
||||||
|
/// <summary>True once the stored workspaces were read; gates <see cref="SaveAsync"/>.</summary>
|
||||||
|
private bool _loaded;
|
||||||
|
|
||||||
/// <summary>Configured workspaces, edited in place.</summary>
|
/// <summary>Configured workspaces, edited in place.</summary>
|
||||||
public ObservableCollection<SshWorkspaceRowViewModel> Workspaces { get; } = new();
|
public ObservableCollection<SshWorkspaceRowViewModel> Workspaces { get; } = new();
|
||||||
|
|
||||||
@@ -35,8 +38,13 @@ public sealed partial class SshSettingsTabViewModel : ViewModelBase
|
|||||||
catch (Exception) { /* no ssh config = no suggestions, hosts can still be typed */ }
|
catch (Exception) { /* no ssh config = no suggestions, hosts can still be typed */ }
|
||||||
|
|
||||||
Workspaces.Clear();
|
Workspaces.Clear();
|
||||||
foreach (var w in await _worker.GetSshWorkspacesAsync())
|
_loaded = false;
|
||||||
|
List<SshWorkspace> stored;
|
||||||
|
try { stored = await _worker.GetSshWorkspacesAsync(); }
|
||||||
|
catch (Exception) { return; } // worker offline: the modal already reports it, and Save must not wipe what it couldn't read
|
||||||
|
foreach (var w in stored)
|
||||||
Workspaces.Add(new SshWorkspaceRowViewModel(w.Id, Remove) { Name = w.Name, Host = w.Host, RemotePath = w.RemotePath });
|
Workspaces.Add(new SshWorkspaceRowViewModel(w.Id, Remove) { Name = w.Name, Host = w.Host, RemotePath = w.RemotePath });
|
||||||
|
_loaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
@@ -53,8 +61,10 @@ public sealed partial class SshSettingsTabViewModel : ViewModelBase
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Persists all rows via the worker.</summary>
|
/// <summary>Persists all rows via the worker; a no-op when the load failed.</summary>
|
||||||
public Task SaveAsync() => _worker.SetSshWorkspacesAsync(Workspaces.Select(r => r.ToModel()).ToList());
|
public Task SaveAsync() => _loaded
|
||||||
|
? _worker.SetSshWorkspacesAsync(Workspaces.Select(r => r.ToModel()).ToList())
|
||||||
|
: Task.CompletedTask;
|
||||||
|
|
||||||
/// <summary>Concrete <c>Host</c> aliases from ssh config text; wildcard patterns and <c>Match</c> blocks are skipped.</summary>
|
/// <summary>Concrete <c>Host</c> aliases from ssh config text; wildcard patterns and <c>Match</c> blocks are skipped.</summary>
|
||||||
public static List<string> ParseHosts(string configText)
|
public static List<string> ParseHosts(string configText)
|
||||||
|
|||||||
@@ -74,4 +74,23 @@ public sealed class SshSettingsTabViewModelTests
|
|||||||
await vm.LoadAsync();
|
await vm.LoadAsync();
|
||||||
Assert.Empty(vm.Hosts);
|
Assert.Empty(vm.Hosts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class OfflineWorker : StubWorkerClient
|
||||||
|
{
|
||||||
|
public bool SetCalled;
|
||||||
|
public override Task<List<SshWorkspace>> GetSshWorkspacesAsync() => throw new InvalidOperationException("offline");
|
||||||
|
public override Task SetSshWorkspacesAsync(List<SshWorkspace> workspaces) { SetCalled = true; return Task.CompletedTask; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task WorkerOffline_LoadDoesNotThrow_AndSaveDoesNotWipe()
|
||||||
|
{
|
||||||
|
var worker = new OfflineWorker();
|
||||||
|
var vm = new SshSettingsTabViewModel(worker, () => "");
|
||||||
|
|
||||||
|
await vm.LoadAsync();
|
||||||
|
await vm.SaveAsync();
|
||||||
|
|
||||||
|
Assert.False(worker.SetCalled);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user