From 9db103cc77a3aa60559f22555a53073841a3e03e Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 6 Jul 2026 17:52:04 +0200 Subject: [PATCH] fix(v5): preserve firewall directions when editing ports --- resources/js/v5/lib/use-canvas-connections.ts | 33 ++++++++++++------- .../V5/V5FrontendSourceContractTest.php | 17 ++++++++++ 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/resources/js/v5/lib/use-canvas-connections.ts b/resources/js/v5/lib/use-canvas-connections.ts index 9b06b6c11..79a14d7fb 100644 --- a/resources/js/v5/lib/use-canvas-connections.ts +++ b/resources/js/v5/lib/use-canvas-connections.ts @@ -28,6 +28,23 @@ export function pruneConnectionPortsByDirection( }; } +function connectionPortsPayload(connection: CanvasConnection): Record { + return Object.fromEntries( + Object.entries(connection.portsByDirection).map(([direction, ports]) => [ + direction, + ports.map((port) => Number(port)).filter((port) => Number.isInteger(port)), + ]), + ); +} + +function preserveActiveDirection(connection: CanvasConnection, activeConnection: CanvasConnection): CanvasConnection { + return { + ...connection, + fromApplicationId: activeConnection.fromApplicationId, + toApplicationId: activeConnection.toApplicationId, + }; +} + function normalizeConnection(connection: V5ResourceConnection): CanvasConnection { return { ...connection, @@ -124,27 +141,20 @@ export function useCanvasConnections(initialConnections: V5ResourceConnection[], const persistConnectionPorts = useCallback( async (updatedConnection: CanvasConnection, previousConnection: CanvasConnection): Promise => { - const portsByDirection = Object.fromEntries( - Object.entries(pruneConnectionPortsByDirection(updatedConnection)).map(([direction, ports]) => [ - direction, - ports.map((port) => Number(port)).filter((port) => Number.isInteger(port)), - ]), - ); - await runOptimisticUpdate({ apply: () => replaceConnection(updatedConnection), rollback: () => replaceConnection(previousConnection), request: async () => { const response = await canvasRequest(`/v5/resource-connections/${updatedConnection.id}`, { method: 'PATCH', - body: { ports_by_direction: portsByDirection }, + body: { ports_by_direction: connectionPortsPayload(updatedConnection) }, }); return parseConnectionResponse(response, 'Could not save allowed ports.'); }, fallbackErrorMessage: 'Could not save allowed ports.', notify, - onSuccess: replaceConnection, + onSuccess: (nextConnection) => replaceConnection(preserveActiveDirection(nextConnection, updatedConnection)), }); }, [notify, replaceConnection], @@ -198,12 +208,11 @@ export function useCanvasConnections(initialConnections: V5ResourceConnection[], ...connection, fromApplicationId, toApplicationId, - portsByDirection: pruneConnectionPortsByDirection(connection, fromApplicationId, toApplicationId), }; - void persistConnectionPorts(updatedConnection, connection); + replaceConnection(updatedConnection); }, - [persistConnectionPorts], + [replaceConnection], ); const setConnectionPortDraft = useCallback((connectionId: string, value: string): void => { diff --git a/tests/Feature/V5/V5FrontendSourceContractTest.php b/tests/Feature/V5/V5FrontendSourceContractTest.php index c2914fbcc..64c224018 100644 --- a/tests/Feature/V5/V5FrontendSourceContractTest.php +++ b/tests/Feature/V5/V5FrontendSourceContractTest.php @@ -930,3 +930,20 @@ it('hardens v5 dashboard fetches and websocket merges against failures', functio expect(strpos($addNginxSource, '!response.ok'))->not->toBeFalse(); }); + +it('preserves all v5 resource connection firewall directions when editing ports', function () { + $connectionsHook = file_get_contents(resource_path('js/v5/lib/use-canvas-connections.ts')); + $updateDirectionSource = substr( + $connectionsHook, + strpos($connectionsHook, 'const updateConnectionDirection'), + strpos($connectionsHook, 'const setConnectionPortDraft') - strpos($connectionsHook, 'const updateConnectionDirection'), + ); + + expect($connectionsHook) + ->toContain('function connectionPortsPayload(connection: CanvasConnection)') + ->toContain('body: { ports_by_direction: connectionPortsPayload(updatedConnection) }') + ->toContain('preserveActiveDirection(nextConnection, updatedConnection)') + ->and($updateDirectionSource) + ->toContain('replaceConnection(updatedConnection)') + ->not->toContain('persistConnectionPorts(updatedConnection, connection)'); +});