From ee9311ad030fa813a0433b2fbdb0810f8829584f Mon Sep 17 00:00:00 2001 From: SaelixCode Date: Thu, 19 Mar 2026 16:43:07 -0400 Subject: [PATCH] fix: dashboard cards and stacks list do not update on remote node switch - HomeDashboard: both polling useEffects now depend on activeNode?.id so intervals restart and stale local-node data is cleared immediately on node switch; added res.ok guard before calling res.json() to prevent error objects being written into stats/systemStats state - EditorLayout: refreshStacks now guards res.ok before parsing the JSON body and iterates a typed fileList instead of the raw response value, preventing SyntaxError/TypeError when proxy returns a non-JSON response for an unreachable remote node --- CHANGELOG.md | 2 ++ frontend/src/components/EditorLayout.tsx | 11 ++++++++--- frontend/src/components/HomeDashboard.tsx | 14 ++++++++++---- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a3e8e9b..d728b7d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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:** Dashboard cards (Active Containers, Host CPU, Host RAM, Docker Network) showing stale local-node data after switching to a remote node — `HomeDashboard` polling effects now depend on `activeNode?.id` and clear state immediately on node change. +- **Fixed:** `refreshStacks` crashing with `SyntaxError` or `TypeError` when the remote proxy returns a non-JSON response (e.g., connection refused to unreachable remote node) — now checks `res.ok` before calling `res.json()` and iterates a typed `fileList` instead of the raw parsed value. - **Fixed:** Restored Local/Remote type selector and fixed state resets in the Add Node modal — form now resets to defaults every time the dialog opens, and the title reflects the chosen type dynamically. - **Fixed:** Remote node connection details failing to display Containers, Images, and CPU metrics — `testRemoteConnection` now fires parallel requests to `/api/stats`, `/api/system/stats`, and `/api/system/images` after auth succeeds, mapping real values into the info panel. - **Fixed:** Suppressed `[DEP0060] DeprecationWarning: util._extend` from `http-proxy@1.18.1` — override is applied to `process.emitWarning` before the proxy instances are created, cleanly intercepting the warning at its call site without suppressing other warnings. diff --git a/frontend/src/components/EditorLayout.tsx b/frontend/src/components/EditorLayout.tsx index ded5a025..85743313 100644 --- a/frontend/src/components/EditorLayout.tsx +++ b/frontend/src/components/EditorLayout.tsx @@ -123,12 +123,17 @@ export default function EditorLayout() { if (!background) setIsLoading(true); try { const res = await apiFetch('/stacks'); - const stacks = await res.json(); - setFiles(Array.isArray(stacks) ? stacks : []); + if (!res.ok) { + setFiles([]); + return; + } + const data = await res.json(); + const fileList: string[] = Array.isArray(data) ? data : []; + setFiles(fileList); // Fetch status for each stack const statuses: StackStatus = {}; - for (const file of stacks) { + for (const file of fileList) { try { const containersRes = await apiFetch(`/stacks/${file}/containers`); const containers = await containersRes.json(); diff --git a/frontend/src/components/HomeDashboard.tsx b/frontend/src/components/HomeDashboard.tsx index 2b22a2d8..915f2ba7 100644 --- a/frontend/src/components/HomeDashboard.tsx +++ b/frontend/src/components/HomeDashboard.tsx @@ -1,4 +1,5 @@ import { useState, useEffect, useMemo } from 'react'; +import { useNodes } from '@/context/NodeContext'; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from './ui/card'; import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts'; import { ChartContainer, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart'; @@ -53,6 +54,7 @@ const formatBytes = (bytes: number) => { }; export default function HomeDashboard() { + const { activeNode } = useNodes(); const [dockerRunInput, setDockerRunInput] = useState(''); const [isConverting, setIsConverting] = useState(false); const [convertedYaml, setConvertedYaml] = useState(''); @@ -62,11 +64,13 @@ export default function HomeDashboard() { const [systemStats, setSystemStats] = useState(null); const [metrics, setMetrics] = useState([]); - // Fetch stats from backend + // Fetch container stats - re-runs when active node changes so stale data is cleared immediately useEffect(() => { + setStats({ active: 0, exited: 0, total: 0, inactive: 0 }); const fetchStats = async () => { try { const res = await apiFetch('/stats'); + if (!res.ok) return; const data = await res.json(); setStats(data); } catch (error) { @@ -76,10 +80,12 @@ export default function HomeDashboard() { fetchStats(); const interval = setInterval(fetchStats, 5000); return () => clearInterval(interval); - }, []); + }, [activeNode?.id]); // eslint-disable-line react-hooks/exhaustive-deps - // Fetch system stats from backend + // Fetch system stats and historical metrics - re-runs when active node changes useEffect(() => { + setSystemStats(null); + setMetrics([]); const fetchSystemStats = async () => { try { const [sysRes, metricsRes] = await Promise.all([ @@ -95,7 +101,7 @@ export default function HomeDashboard() { fetchSystemStats(); const interval = setInterval(fetchSystemStats, 5000); return () => clearInterval(interval); - }, []); + }, [activeNode?.id]); // eslint-disable-line react-hooks/exhaustive-deps const chartData = useMemo(() => { const buckets: Record = {};