mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-30 12:09:15 +00:00
Merge pull request #34 from AnsoCode/fix/node-switch-dashboard-refresh
fix: dashboard cards and stacks list do not update on remote node switch
This commit is contained in:
@@ -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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [Unreleased]
|
## [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:** 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:** 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.
|
- **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.
|
||||||
|
|||||||
@@ -123,12 +123,17 @@ export default function EditorLayout() {
|
|||||||
if (!background) setIsLoading(true);
|
if (!background) setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
const res = await apiFetch('/stacks');
|
const res = await apiFetch('/stacks');
|
||||||
const stacks = await res.json();
|
if (!res.ok) {
|
||||||
setFiles(Array.isArray(stacks) ? stacks : []);
|
setFiles([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = await res.json();
|
||||||
|
const fileList: string[] = Array.isArray(data) ? data : [];
|
||||||
|
setFiles(fileList);
|
||||||
|
|
||||||
// Fetch status for each stack
|
// Fetch status for each stack
|
||||||
const statuses: StackStatus = {};
|
const statuses: StackStatus = {};
|
||||||
for (const file of stacks) {
|
for (const file of fileList) {
|
||||||
try {
|
try {
|
||||||
const containersRes = await apiFetch(`/stacks/${file}/containers`);
|
const containersRes = await apiFetch(`/stacks/${file}/containers`);
|
||||||
const containers = await containersRes.json();
|
const containers = await containersRes.json();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useEffect, useMemo } from 'react';
|
import { useState, useEffect, useMemo } from 'react';
|
||||||
|
import { useNodes } from '@/context/NodeContext';
|
||||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from './ui/card';
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from './ui/card';
|
||||||
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts';
|
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts';
|
||||||
import { ChartContainer, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart';
|
import { ChartContainer, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart';
|
||||||
@@ -53,6 +54,7 @@ const formatBytes = (bytes: number) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function HomeDashboard() {
|
export default function HomeDashboard() {
|
||||||
|
const { activeNode } = useNodes();
|
||||||
const [dockerRunInput, setDockerRunInput] = useState('');
|
const [dockerRunInput, setDockerRunInput] = useState('');
|
||||||
const [isConverting, setIsConverting] = useState(false);
|
const [isConverting, setIsConverting] = useState(false);
|
||||||
const [convertedYaml, setConvertedYaml] = useState('');
|
const [convertedYaml, setConvertedYaml] = useState('');
|
||||||
@@ -62,11 +64,13 @@ export default function HomeDashboard() {
|
|||||||
const [systemStats, setSystemStats] = useState<SystemStats | null>(null);
|
const [systemStats, setSystemStats] = useState<SystemStats | null>(null);
|
||||||
const [metrics, setMetrics] = useState<any[]>([]);
|
const [metrics, setMetrics] = useState<any[]>([]);
|
||||||
|
|
||||||
// Fetch stats from backend
|
// Fetch container stats - re-runs when active node changes so stale data is cleared immediately
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
setStats({ active: 0, exited: 0, total: 0, inactive: 0 });
|
||||||
const fetchStats = async () => {
|
const fetchStats = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await apiFetch('/stats');
|
const res = await apiFetch('/stats');
|
||||||
|
if (!res.ok) return;
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
setStats(data);
|
setStats(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -76,10 +80,12 @@ export default function HomeDashboard() {
|
|||||||
fetchStats();
|
fetchStats();
|
||||||
const interval = setInterval(fetchStats, 5000);
|
const interval = setInterval(fetchStats, 5000);
|
||||||
return () => clearInterval(interval);
|
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(() => {
|
useEffect(() => {
|
||||||
|
setSystemStats(null);
|
||||||
|
setMetrics([]);
|
||||||
const fetchSystemStats = async () => {
|
const fetchSystemStats = async () => {
|
||||||
try {
|
try {
|
||||||
const [sysRes, metricsRes] = await Promise.all([
|
const [sysRes, metricsRes] = await Promise.all([
|
||||||
@@ -95,7 +101,7 @@ export default function HomeDashboard() {
|
|||||||
fetchSystemStats();
|
fetchSystemStats();
|
||||||
const interval = setInterval(fetchSystemStats, 5000);
|
const interval = setInterval(fetchSystemStats, 5000);
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, []);
|
}, [activeNode?.id]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
const chartData = useMemo(() => {
|
const chartData = useMemo(() => {
|
||||||
const buckets: Record<string, { time: string; timestamp: number; cpu: number; ram: number }> = {};
|
const buckets: Record<string, { time: string; timestamp: number; cpu: number; ram: number }> = {};
|
||||||
|
|||||||
Reference in New Issue
Block a user