From 3b2634f9dd0bc2a8386028605aa935b62f6e1dd9 Mon Sep 17 00:00:00 2001 From: SaelixCode Date: Fri, 20 Mar 2026 09:57:01 -0400 Subject: [PATCH] fix(dashboard): surface server error messages in create-stack flow - Read JSON error body from non-ok responses before throwing, so status-specific messages (409 already exists, 400 invalid name) reach the user instead of generic hardcoded strings - Apply defensive toast pattern: error?.message || error?.error || fallback --- CHANGELOG.md | 1 + frontend/src/components/HomeDashboard.tsx | 14 ++++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a217ee4a..eeea30f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +- **Fixed:** `HomeDashboard` create-stack error handling — HTTP error responses were thrown as hardcoded strings, discarding the server's actual error message (e.g. "Stack already exists", "Invalid stack name"). Now reads the JSON error body before throwing, and uses the defensive `error?.message || error?.error || fallback` toast pattern. - **Fixed:** LogViewer (container SSE log stream) returning 404 on remote nodes — the `?nodeId=` query param was forwarded by `remoteNodeProxy` to the remote server, where `nodeContextMiddleware` rejected it with `Node X not found` because the gateway's node IDs don't exist on the remote instance. Fixed by stripping `nodeId` from `proxyReq.path` in `onProxyReq`, mirroring the existing `x-node-id` header removal. - **Fixed:** Terminal logs and container stats WebSockets failing with "HTTP Authentication failed" on remote nodes — the WebSocket upgrade proxy was forwarding the browser's `cookie` header to the remote Sencho instance. The remote's `authMiddleware` picks `cookieToken` before `bearerToken`, and the cookie (signed with the local JWT secret) fails verification on the remote, returning 401. Fixed by deleting the `cookie` header before `wsProxyServer.ws()`, mirroring the `proxyReq.removeHeader('cookie')` already present in the HTTP proxy. Also strips the gateway's `nodeId` query param from the forwarded URL so the remote defaults cleanly to its own local node. - **Fixed:** `streamStats` Docker stats stream leaking after WebSocket client disconnect — the Docker daemon stream was never destroyed when the WS closed, causing orphaned streams to poll the daemon indefinitely. Fixed by adding a `ws.on('close')` handler that calls `stats.destroy()`. Also guards all `ws.send()` calls with a `readyState === OPEN` check to prevent silent errors on a closed socket. diff --git a/frontend/src/components/HomeDashboard.tsx b/frontend/src/components/HomeDashboard.tsx index 355eae46..a71b45f6 100644 --- a/frontend/src/components/HomeDashboard.tsx +++ b/frontend/src/components/HomeDashboard.tsx @@ -174,23 +174,29 @@ export default function HomeDashboard() { method: 'POST', body: JSON.stringify({ stackName }), }); - if (!createResponse.ok) throw new Error('Failed to create stack'); + if (!createResponse.ok) { + const err = await createResponse.json().catch(() => ({})); + throw new Error(err.error || 'Failed to create stack'); + } // Save the converted YAML content const saveResponse = await apiFetch(`/stacks/${stackName}`, { method: 'PUT', body: JSON.stringify({ content: convertedYaml }), }); - if (!saveResponse.ok) throw new Error('Failed to save stack content'); + if (!saveResponse.ok) { + const err = await saveResponse.json().catch(() => ({})); + throw new Error(err.error || 'Failed to save stack content'); + } setCreateDialogOpen(false); setNewStackName(''); setConvertedYaml(''); setDockerRunInput(''); window.location.reload(); // Refresh to show new stack - } catch (error) { + } catch (error: any) { console.error('Failed to create stack:', error); - toast.error('Failed to create stack'); + toast.error(error?.message || error?.error || 'Failed to create stack'); } };