fix: restore fallback machine navigation after deletion

This commit is contained in:
Federico Jaramillo Martinez
2026-06-04 11:12:03 +02:00
parent b3bb7329bf
commit ed647a359a
3 changed files with 56 additions and 7 deletions
+7 -3
View File
@@ -455,9 +455,9 @@ export class PiWebApp extends LitElement {
setNamespacedQueryKey(TERMINAL_ROUTE_NAMESPACE, "terminal", surface.selectedTerminalId, { replace: true });
}
private async selectMachineWithMemory(machine: Machine): Promise<void> {
private async selectMachineWithMemory(machine: Machine, options: { rememberCurrent?: boolean } = {}): Promise<void> {
if (this.state.selectedMachine?.id === machine.id) return;
if (!this.routeRestoreInProgress) this.rememberCurrentMachineNavigation();
if (options.rememberCurrent !== false && !this.routeRestoreInProgress) this.rememberCurrentMachineNavigation();
const seq = ++this.machineNavigationRestoreSeq;
const snapshot = this.machineNavigation.latest(machine.id) ?? emptyMachineNavigationSnapshot(machine.id);
await this.restoreRouteFor(routeFromMachineNavigationSnapshot(snapshot), false, snapshot.surface, snapshot.view);
@@ -1044,7 +1044,11 @@ export class PiWebApp extends LitElement {
private async removeMachine(machine: Machine | undefined = this.state.selectedMachine): Promise<void> {
if (machine === undefined || machine.kind === "local") return;
if (!window.confirm(`Remove ${machine.name}?\n\nThis only removes it from this PI WEB gateway.`)) return;
await this.machines.deleteMachine(machine);
const wasSelected = this.state.selectedMachine?.id === machine.id;
if (wasSelected) this.rememberCurrentMachineNavigation();
const fallback = await this.machines.deleteMachine(machine, { selectFallback: !wasSelected });
if (!this.state.machines.some((candidate) => candidate.id === machine.id)) this.machineNavigation.forget(machine.id);
if (wasSelected && fallback !== undefined) await this.selectMachineWithMemory(fallback, { rememberCurrent: false });
}
private openSelectedMachine(): void {
@@ -72,4 +72,42 @@ describe("MachineController", () => {
expect(state.machineStatuses[remoteMachine.id]).toMatchObject({ machineId: remoteMachine.id, ok: false, status: "offline", error: "Internal Server Error" });
expect(state.error).toContain("Remote is offline");
});
it("returns the fallback machine without selecting it when requested", async () => {
let state: AppState = { ...initialAppState(), machines: [localMachine, remoteMachine], selectedMachine: remoteMachine };
const setState = (patch: Partial<AppState>) => { state = { ...state, ...patch }; };
const updateUrl = vi.fn();
const projects = { loadProjects: vi.fn() };
vi.spyOn(api, "deleteMachine").mockResolvedValue({ deleted: true });
const controller = new MachineController(() => state, setState, updateUrl, projects);
const fallback = await controller.deleteMachine(remoteMachine, { selectFallback: false });
expect(fallback).toEqual(localMachine);
expect(state.machines).toEqual([localMachine]);
expect(state.selectedMachine).toEqual(remoteMachine);
expect(projects.loadProjects).not.toHaveBeenCalled();
expect(updateUrl).not.toHaveBeenCalled();
});
it("selects the fallback machine after deleting the selected machine by default", async () => {
let state: AppState = { ...initialAppState(), machines: [localMachine, remoteMachine], selectedMachine: remoteMachine, selectedProject: { id: "p1", name: "Project", path: "/repo", createdAt: "now" } };
const setState = (patch: Partial<AppState>) => { state = { ...state, ...patch }; };
const updateUrl = vi.fn();
const projects = { loadProjects: vi.fn() };
vi.spyOn(api, "deleteMachine").mockResolvedValue({ deleted: true });
const controller = new MachineController(() => state, setState, updateUrl, projects);
const fallback = await controller.deleteMachine(remoteMachine);
expect(fallback).toEqual(localMachine);
expect(state.selectedMachine).toEqual(localMachine);
expect(state.selectedProject).toBeUndefined();
expect(projects.loadProjects).toHaveBeenCalledOnce();
expect(updateUrl).toHaveBeenCalledOnce();
});
});
@@ -59,20 +59,27 @@ export class MachineController {
}
}
async deleteMachine(machine: Machine | undefined = this.getState().selectedMachine): Promise<void> {
if (machine === undefined) return;
async deleteMachine(machine: Machine | undefined = this.getState().selectedMachine, options: { selectFallback?: boolean } = {}): Promise<Machine | undefined> {
if (machine === undefined) return undefined;
if (machine.kind === "local") {
this.setState({ error: "The local machine cannot be removed." });
return;
return undefined;
}
try {
const wasSelected = this.getState().selectedMachine?.id === machine.id;
await api.deleteMachine(machine.id);
const machines = this.getState().machines.filter((candidate) => candidate.id !== machine.id);
const local = machines.find((candidate) => candidate.id === "local") ?? machines[0];
this.setState({ machines, machineStatuses: omitKey(this.getState().machineStatuses, machine.id) });
if (this.getState().selectedMachine?.id === machine.id && local !== undefined) await this.selectMachine(local);
if (wasSelected && local !== undefined) {
if (options.selectFallback === false) return local;
await this.selectMachine(local);
return local;
}
return undefined;
} catch (error) {
this.setState({ error: String(error) });
return undefined;
}
}