Merge remote-tracking branch 'origin/main' into develop

This commit is contained in:
SaelixCode
2026-03-20 08:45:42 -04:00
3 changed files with 31 additions and 1 deletions
+8 -1
View File
@@ -25,4 +25,11 @@ Thumbs.db
#PRD
Product Requirements Document
plans/.claude/
# Claude Code
.claude/
CLAUDE.md
plans/
# Playwright MCP
.playwright-mcp/
+2
View File
@@ -5,6 +5,8 @@ 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:** 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:** Container stats (CPU/RAM/NET), bash exec terminal, and "Open App" button all broken for remote nodes — stats and exec WebSockets connected to the bare root (`ws://host`) with no `?nodeId=` query param, so the upgrade handler couldn't detect the remote node and skipped the WS proxy. Moved all generic WebSockets to `/ws?nodeId=` path; upgrade handler now correctly proxies them to the remote Sencho instance.
- **Fixed:** Bash exec and stats WebSockets not reaching the backend in `npm run dev` — Vite proxy config now includes `ws: true` on `/api` and a new `/ws` proxy entry so all WebSocket upgrades are forwarded to `localhost:3000`.
- **Fixed:** Backend WS message handler crashing when a proxied WebSocket arrives from a gateway and the forwarded `nodeId` doesn't exist in the remote instance's DB — now falls back to the default local node instead of throwing.
+21
View File
@@ -356,6 +356,18 @@ const remoteNodeProxy = createProxyMiddleware<Request, Response>({
if (node?.api_token) {
proxyReq.setHeader('Authorization', `Bearer ${node.api_token}`);
}
// Strip the ?nodeId= query param so the remote's nodeContextMiddleware
// doesn't reject the request with 404 ("Node X not found") — the remote
// has no record of the gateway's node IDs and should treat the request
// as local. This affects endpoints like EventSource /api/containers/:id/logs
// that pass nodeId as a query param rather than the x-node-id header.
if (proxyReq.path.includes('nodeId=')) {
const [pathname, qs] = proxyReq.path.split('?');
const params = new URLSearchParams(qs || '');
params.delete('nodeId');
const newQs = params.toString();
proxyReq.path = pathname + (newQs ? `?${newQs}` : '');
}
},
error: (err, _req, proxyRes) => {
console.error('[Proxy] Remote node error:', (err as Error).message);
@@ -440,6 +452,15 @@ server.on('upgrade', async (req, socket, head) => {
const wsTarget = node.api_url.replace(/\/$/, '').replace(/^https?/, (m) => m === 'https' ? 'wss' : 'ws');
req.headers['authorization'] = `Bearer ${node.api_token}`;
delete req.headers['x-node-id'];
// Strip the browser's session cookie — it is signed by this instance's JWT secret and
// would fail verification on the remote. Auth is handled exclusively via the Bearer token.
delete req.headers['cookie'];
// Strip nodeId from the forwarded URL so the remote treats the request as a local one.
// The remote has no record of the gateway's nodeId, so leaving it would cause unnecessary
// fallback logic. Removing it lets the remote default cleanly to its own local node.
const fwdUrl = new URL(req.url!, `http://${req.headers.host || 'localhost'}`);
fwdUrl.searchParams.delete('nodeId');
req.url = fwdUrl.pathname + (fwdUrl.searchParams.toString() ? `?${fwdUrl.searchParams.toString()}` : '');
wsProxyServer.ws(req, socket, head, { target: wsTarget });
return;
}