Archived
fix: restore fallback machine navigation after deletion
This commit is contained in:
@@ -455,9 +455,9 @@ export class PiWebApp extends LitElement {
|
|||||||
setNamespacedQueryKey(TERMINAL_ROUTE_NAMESPACE, "terminal", surface.selectedTerminalId, { replace: true });
|
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.state.selectedMachine?.id === machine.id) return;
|
||||||
if (!this.routeRestoreInProgress) this.rememberCurrentMachineNavigation();
|
if (options.rememberCurrent !== false && !this.routeRestoreInProgress) this.rememberCurrentMachineNavigation();
|
||||||
const seq = ++this.machineNavigationRestoreSeq;
|
const seq = ++this.machineNavigationRestoreSeq;
|
||||||
const snapshot = this.machineNavigation.latest(machine.id) ?? emptyMachineNavigationSnapshot(machine.id);
|
const snapshot = this.machineNavigation.latest(machine.id) ?? emptyMachineNavigationSnapshot(machine.id);
|
||||||
await this.restoreRouteFor(routeFromMachineNavigationSnapshot(snapshot), false, snapshot.surface, snapshot.view);
|
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> {
|
private async removeMachine(machine: Machine | undefined = this.state.selectedMachine): Promise<void> {
|
||||||
if (machine === undefined || machine.kind === "local") return;
|
if (machine === undefined || machine.kind === "local") return;
|
||||||
if (!window.confirm(`Remove ${machine.name}?\n\nThis only removes it from this PI WEB gateway.`)) 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 {
|
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.machineStatuses[remoteMachine.id]).toMatchObject({ machineId: remoteMachine.id, ok: false, status: "offline", error: "Internal Server Error" });
|
||||||
expect(state.error).toContain("Remote is offline");
|
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> {
|
async deleteMachine(machine: Machine | undefined = this.getState().selectedMachine, options: { selectFallback?: boolean } = {}): Promise<Machine | undefined> {
|
||||||
if (machine === undefined) return;
|
if (machine === undefined) return undefined;
|
||||||
if (machine.kind === "local") {
|
if (machine.kind === "local") {
|
||||||
this.setState({ error: "The local machine cannot be removed." });
|
this.setState({ error: "The local machine cannot be removed." });
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
const wasSelected = this.getState().selectedMachine?.id === machine.id;
|
||||||
await api.deleteMachine(machine.id);
|
await api.deleteMachine(machine.id);
|
||||||
const machines = this.getState().machines.filter((candidate) => candidate.id !== machine.id);
|
const machines = this.getState().machines.filter((candidate) => candidate.id !== machine.id);
|
||||||
const local = machines.find((candidate) => candidate.id === "local") ?? machines[0];
|
const local = machines.find((candidate) => candidate.id === "local") ?? machines[0];
|
||||||
this.setState({ machines, machineStatuses: omitKey(this.getState().machineStatuses, machine.id) });
|
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) {
|
} catch (error) {
|
||||||
this.setState({ error: String(error) });
|
this.setState({ error: String(error) });
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user