diff --git a/CHANGELOG.md b/CHANGELOG.md index eed4f5f4..76212264 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:** Remote node system stats, container stats, logs, and exec returning errors — `remoteNodeProxy` middleware was already positioned before API route definitions, but `/api/system/stats` contained a dead remote branch that called `NodeRegistry.getDocker()` for remote nodes, which throws since remote nodes have no direct Docker socket access. Removed the broken branch; remote requests are correctly intercepted by the proxy middleware before reaching any route handler. - **Added:** Background image update checker — `ImageUpdateService` polls OCI-compliant registries (Docker Hub, GHCR, LSCR, etc.) every 6 hours using manifest digest comparison against local `RepoDigests`. Results cached in a new `stack_update_status` SQLite table. A pulsing blue dot badge appears in the stack list sidebar for stacks with available updates. Manual refresh available via `POST /api/image-updates/refresh` (rate-limited to once per 10 minutes). - **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. diff --git a/backend/src/index.ts b/backend/src/index.ts index 26a88aa2..082e560a 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1139,35 +1139,11 @@ app.get('/api/logs/global/stream', async (req: Request, res: Response) => { // Get host system stats app.get('/api/system/stats', async (req: Request, res: Response) => { try { - const nodeId = req.nodeId ?? NodeRegistry.getInstance().getDefaultNodeId(); - const node = NodeRegistry.getInstance().getNode(nodeId); - const rxSec = Math.max(0, globalDockerNetwork.rxSec); const txSec = Math.max(0, globalDockerNetwork.txSec); - if (node && node.type === 'remote') { - // Remote node: use Docker daemon info for CPU/RAM - disk is not available via Docker API - const docker = NodeRegistry.getInstance().getDocker(nodeId); - const info = await docker.info(); - - res.json({ - cpu: { - usage: '0', - cores: info.NCPU ?? 0, - }, - memory: { - total: info.MemTotal ?? 0, - used: 0, - free: info.MemTotal ?? 0, - usagePercent: '0', - }, - disk: null, - network: { rxBytes: 0, txBytes: 0, rxSec, txSec }, - }); - return; - } - - // Local node: use systeminformation for accurate host metrics + // Remote node requests are intercepted and proxied by remoteNodeProxy before reaching here. + // This handler only runs for local nodes. const [currentLoad, mem, fsSize] = await Promise.all([ si.currentLoad(), si.mem(),