From 04c770c198892618fa69713f47d3b8efbd44eab7 Mon Sep 17 00:00:00 2001 From: SaelixCode Date: Thu, 19 Mar 2026 20:39:57 -0400 Subject: [PATCH] feat(ui): Phase 57 - remote context UX (Option A) + network layer fixes Task 1 - Network layer fixes: - AppStoreView: replace raw fetch() with apiFetch() on /templates and /templates/deploy so x-node-id header is injected for proxy routing - GlobalObservabilityView: replace raw fetch() with apiFetch() on /stacks and /logs/global - HostConsole: append ?nodeId= to WebSocket URL so the upgrade handler correctly routes the PTY session to the active remote node Task 2 - Scoped context two-tier UX (Option A / Portainer-style): - EditorLayout: add a context pill in the top header bar showing the active node name (pulsing blue for remote, green for local) - HostConsole: header now reads "Host Console - [Node Name]" - ResourcesView: title now reads "Resources Hub - [Node Name]" for remote - GlobalObservabilityView: floating node badge in top-left corner for remote - AppStoreView: deploy sheet shows "Deploying to: [Node Name]" badge - SettingsModal: remote nodes only see System Limits + Developer tabs; global-only tabs (Account, Appearance, Notifications, Nodes) are hidden; header subtitle shows remote node name --- CHANGELOG.md | 5 ++ frontend/src/components/AppStoreView.tsx | 15 +++- frontend/src/components/EditorLayout.tsx | 18 +++- .../components/GlobalObservabilityView.tsx | 13 ++- frontend/src/components/HostConsole.tsx | 13 ++- frontend/src/components/ResourcesView.tsx | 5 ++ frontend/src/components/SettingsModal.tsx | 88 ++++++++++++------- 7 files changed, 117 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 655fc69c..ded516d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ 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:** `AppStoreView` and `GlobalObservabilityView` using raw `fetch()` instead of `apiFetch()` — all calls now inject the `x-node-id` header so templates, deploys, stacks, and logs are correctly proxied to the active remote node. +- **Fixed:** `HostConsole` WebSocket URL missing `?nodeId=` query parameter — the upgrade handler now receives the active node ID and routes the PTY session to the correct node. +- **Added:** Two-tier Option A scoped navigation UX — a context pill in the top header bar always shows the active node name (pulsing blue for remote, green for local). +- **Added:** Remote-aware headers in `HostConsole` ("Host Console — [Node Name]"), `ResourcesView` ("Resources Hub — [Node Name]"), `GlobalObservabilityView` (floating node badge), and `AppStoreView` deploy sheet ("Deploying to: [Node Name]"). +- **Added:** `SettingsModal` now scopes its sidebar to the active node type — when a remote node is selected, global-only tabs (Account, Appearance, Notifications, Nodes) are hidden, and the header subtitle shows the remote node name. - **Fixed:** A massive memory leak (browser Out of Memory crash) by throttling historical metrics polling down to 60s and downsampling SQLite metrics payload sizes by 12x. - **Fixed:** A bug where the active node UI dropdown would desync from the actual API requests on initial page load by properly hydrating state from localStorage. - **Fixed:** Remote node proxy forwarding the browser's `sencho_token` cookie to the remote Sencho instance — the remote's `authMiddleware` evaluates `cookieToken || bearerToken` and the cookie (signed with the local JWT secret) was validated before the valid Bearer token, causing 401 on all proxied API calls. Fixed by stripping the `cookie` header in `proxyReq` so only the Bearer token is used for remote authentication. diff --git a/frontend/src/components/AppStoreView.tsx b/frontend/src/components/AppStoreView.tsx index 18065fdd..0fc6e1b9 100644 --- a/frontend/src/components/AppStoreView.tsx +++ b/frontend/src/components/AppStoreView.tsx @@ -8,6 +8,8 @@ import { ScrollArea } from "@/components/ui/scroll-area"; import { Button } from "@/components/ui/button"; import { Search, Rocket, Loader2, Info, ExternalLink, Github, Star } from "lucide-react"; import { toast } from "sonner"; +import { apiFetch } from '@/lib/api'; +import { useNodes } from '@/context/NodeContext'; export interface TemplateEnv { name: string; @@ -36,6 +38,7 @@ interface AppStoreViewProps { } export function AppStoreView({ onDeploySuccess }: AppStoreViewProps) { + const { activeNode } = useNodes(); const [templates, setTemplates] = useState([]); const [searchQuery, setSearchQuery] = useState(''); const [selectedTemplate, setSelectedTemplate] = useState