mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-14 08:10:26 +00:00
Route infrastructure workspace node edits to the update endpoint
The source-manager workspace retired the legacy per-type shells that set the configured-nodes editingNode signal, so every save from the Manage flow fell through to the add endpoint. Editing an existing PVE, PBS or PMG connection then failed with "Authentication credentials required" (or a duplicate-host conflict) and the editor closed anyway, discarding the form. Pass the edited node along with the save call, report save success, and keep the editor open when the save fails. Fixes #1605 Contract-Neutral: #1605 frontend-only fix routing workspace node edits to the update endpoint; no subsystem boundary moved
This commit is contained in:
+7
-2
@@ -52,8 +52,13 @@ export const NodeCredentialSlot: Component<NodeCredentialSlotProps> = (props) =>
|
||||
const [importImpactPreviewError, setImportImpactPreviewError] = createSignal<string | null>(null);
|
||||
|
||||
const handleSave = async (nodeData: Partial<NodeConfig>) => {
|
||||
await props.settings.saveNode(nodeData);
|
||||
props.onSaved();
|
||||
// The configured-nodes state no longer tracks which node the workspace is
|
||||
// editing, so the edit target must travel with the save call; otherwise
|
||||
// edits are misrouted to the add endpoint (#1605).
|
||||
const saved = await props.settings.saveNode(nodeData, props.editingNode ?? null);
|
||||
if (saved) {
|
||||
props.onSaved();
|
||||
}
|
||||
};
|
||||
|
||||
const modalProps: NodeModalProps = {
|
||||
|
||||
+71
-1
@@ -15,7 +15,7 @@ const createSettings = (): InfrastructurePlatformSettingsProps =>
|
||||
savingTemperatureSetting: () => false,
|
||||
handleNodeTemperatureMonitoringChange: vi.fn().mockResolvedValue(undefined),
|
||||
handleTemperatureMonitoringChange: vi.fn().mockResolvedValue(undefined),
|
||||
saveNode: vi.fn().mockResolvedValue(undefined),
|
||||
saveNode: vi.fn().mockResolvedValue(true),
|
||||
}) as unknown as InfrastructurePlatformSettingsProps;
|
||||
|
||||
describe('NodeCredentialSlot', () => {
|
||||
@@ -185,4 +185,74 @@ describe('NodeCredentialSlot', () => {
|
||||
const payload = vi.mocked(settings.saveNode).mock.calls[0][0];
|
||||
expect(payload).not.toHaveProperty('clusterEndpointOverrides');
|
||||
});
|
||||
|
||||
it('passes the edited node to saveNode so edits hit the update path', async () => {
|
||||
const settings = createSettings();
|
||||
const onSaved = vi.fn();
|
||||
const editingNode = {
|
||||
id: 'pve-0',
|
||||
type: 'pve',
|
||||
name: 'homelab',
|
||||
host: 'https://pve1.local:8006',
|
||||
user: '',
|
||||
tokenName: 'root@pam!pulse',
|
||||
hasToken: true,
|
||||
verifySSL: true,
|
||||
status: 'connected',
|
||||
} as unknown as NodeConfigWithStatus;
|
||||
|
||||
render(() => (
|
||||
<NodeCredentialSlot
|
||||
nodeType="pve"
|
||||
settings={settings}
|
||||
editingNode={editingNode}
|
||||
onCancel={vi.fn()}
|
||||
onSaved={onSaved}
|
||||
/>
|
||||
));
|
||||
|
||||
fireEvent.submit(screen.getByRole('button', { name: 'Save changes' }).closest('form')!);
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(settings.saveNode).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
expect(vi.mocked(settings.saveNode).mock.calls[0][1]).toBe(editingNode);
|
||||
await vi.waitFor(() => {
|
||||
expect(onSaved).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps the editor open when the save fails', async () => {
|
||||
const settings = createSettings();
|
||||
vi.mocked(settings.saveNode).mockResolvedValue(false);
|
||||
const onSaved = vi.fn();
|
||||
const editingNode = {
|
||||
id: 'pve-0',
|
||||
type: 'pve',
|
||||
name: 'homelab',
|
||||
host: 'https://pve1.local:8006',
|
||||
user: '',
|
||||
tokenName: 'root@pam!pulse',
|
||||
hasToken: true,
|
||||
verifySSL: true,
|
||||
status: 'connected',
|
||||
} as unknown as NodeConfigWithStatus;
|
||||
|
||||
render(() => (
|
||||
<NodeCredentialSlot
|
||||
nodeType="pve"
|
||||
settings={settings}
|
||||
editingNode={editingNode}
|
||||
onCancel={vi.fn()}
|
||||
onSaved={onSaved}
|
||||
/>
|
||||
));
|
||||
|
||||
fireEvent.submit(screen.getByRole('button', { name: 'Save changes' }).closest('form')!);
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(settings.saveNode).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
expect(onSaved).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -67,7 +67,10 @@ export interface InfrastructurePlatformSettingsProps {
|
||||
savingDockerUpdateActions: Accessor<boolean>;
|
||||
handleDisableDockerUpdateActionsChange: (enabled: boolean) => Promise<void>;
|
||||
handleNodeTemperatureMonitoringChange: (nodeId: string, enabled: boolean | null) => Promise<void>;
|
||||
saveNode: (nodeData: Partial<NodeConfig>) => Promise<void>;
|
||||
saveNode: (
|
||||
nodeData: Partial<NodeConfig>,
|
||||
existingNode?: NodeConfigWithStatus | null,
|
||||
) => Promise<boolean>;
|
||||
showDeleteNodeModal: Accessor<boolean>;
|
||||
cancelDeleteNode: () => void;
|
||||
deleteNode: () => Promise<void>;
|
||||
|
||||
@@ -281,9 +281,12 @@ export const useInfrastructureConfiguredNodesState = ({
|
||||
}
|
||||
};
|
||||
|
||||
const saveNode = async (nodeData: Partial<NodeConfig>) => {
|
||||
const saveNode = async (
|
||||
nodeData: Partial<NodeConfig>,
|
||||
existingNodeOverride?: NodeConfigWithStatus | null,
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
const existingNode = editingNode();
|
||||
const existingNode = existingNodeOverride ?? editingNode();
|
||||
if (existingNode?.id) {
|
||||
await NodesAPI.updateNode(existingNode.id, nodeData as NodeConfig);
|
||||
// clusterEndpointOverrides is a write-only payload field: mirror it
|
||||
@@ -322,8 +325,10 @@ export const useInfrastructureConfiguredNodesState = ({
|
||||
|
||||
setShowNodeModal(false);
|
||||
setEditingNode(null);
|
||||
return true;
|
||||
} catch (error) {
|
||||
notificationStore.error(error instanceof Error ? error.message : 'Operation failed');
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user