From 32902d389143abdd60131ccb58d40b75db9affba Mon Sep 17 00:00:00 2001 From: archipelago Date: Mon, 18 May 2026 11:47:12 -0400 Subject: [PATCH] fix(ui): stabilize system status metrics --- CHANGELOG.md | 9 + neode-ui/src/stores/homeStatus.ts | 206 +++++++++++++++ neode-ui/src/views/Home.vue | 63 +++-- neode-ui/src/views/Monitoring.vue | 36 ++- .../src/views/settings/AccountInfoSection.vue | 250 ++++++++++++++++++ neode-ui/src/views/web5/Web5Monitoring.vue | 35 +-- scripts/container-specs.sh | 4 +- scripts/first-boot-containers.sh | 12 +- scripts/lib/common.sh | 8 +- 9 files changed, 554 insertions(+), 69 deletions(-) create mode 100644 neode-ui/src/stores/homeStatus.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index f4c0c9e6..bfe48e08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## v1.7.67-alpha (2026-05-18) + +- Home dashboard status cards now keep the last known good system, VPN, Bitcoin, and FIPS values while route changes or transient RPC failures are in flight, avoiding false "not configured" or "not running" flashes. +- Home, Web5 Monitoring, and the Monitoring page headline cards now share the same live system-stat snapshot for CPU, memory, disk, uptime, and load so the visible numbers agree across the UI. +- Settings What's New is filled through `v1.7.67-alpha`, including the missing historical `v1.7.44-alpha` through `v1.7.66-alpha` entries. +- Bitcoin/Knots/Core shell lifecycle specs now match the Rust app config memory policy: 8 GiB on normal hosts, 4 GiB on low-memory hosts, and pruned Knots uses a larger dbcache on hosts with enough RAM to improve IBD throughput. +- ElectrumX/electrs shell lifecycle specs now match the 4 GiB memory policy used by the Rust app config, reducing drift between first boot, reconcile, and app lifecycle paths. +- Live assessment of `100.70.96.88` identified the current IBD bottlenecks as CPU/thermal/I/O pressure rather than RAM exhaustion, with follow-up work planned for existing-node swap repair, kiosk Chromium CPU reduction, and reconcile failure cleanup. + ## v1.7.66-alpha (2026-05-18) - Nginx Proxy Manager stale-port repair now detects stopped or `Created` Podman records by inspecting `podman ps -a` port metadata, covering records where `podman port nginx-proxy-manager` returns no mapping until start. diff --git a/neode-ui/src/stores/homeStatus.ts b/neode-ui/src/stores/homeStatus.ts new file mode 100644 index 00000000..41ae1948 --- /dev/null +++ b/neode-ui/src/stores/homeStatus.ts @@ -0,0 +1,206 @@ +import { defineStore } from 'pinia' +import { computed, reactive, ref } from 'vue' +import { rpcClient } from '@/api/rpc-client' +import { PackageState, type PackageDataEntry } from '@/types/api' + +type LoadState = 'idle' | 'loading' | 'ready' | 'error' + +interface SystemStatsSnapshot { + cpuPercent: number + memUsed: number + memTotal: number + memPercent: number + diskUsed: number + diskTotal: number + diskPercent: number + uptimeSecs: number + loadAvg1: number + loadAvg5: number + loadAvg15: number + bitcoinSyncPercent: number + bitcoinBlockHeight: number + bitcoinAvailable: boolean | null +} + +const emptyStats = (): SystemStatsSnapshot => ({ + cpuPercent: 0, + memUsed: 0, + memTotal: 0, + memPercent: 0, + diskUsed: 0, + diskTotal: 0, + diskPercent: 0, + uptimeSecs: 0, + loadAvg1: 0, + loadAvg5: 0, + loadAvg15: 0, + bitcoinSyncPercent: 0, + bitcoinBlockHeight: 0, + bitcoinAvailable: null, +}) + +export const useHomeStatusStore = defineStore('homeStatus', () => { + const stats = reactive(emptyStats()) + const systemLoadState = ref('idle') + const bitcoinLoadState = ref('idle') + const vpnLoadState = ref('idle') + const fipsLoadState = ref('idle') + const lastSystemRefreshAt = ref(null) + const lastBitcoinRefreshAt = ref(null) + const lastVpnRefreshAt = ref(null) + const lastFipsRefreshAt = ref(null) + + const vpnStatus = ref<{ + connected: boolean | null + provider: string + }>({ connected: null, provider: '' }) + + const fipsStatus = ref<{ + installed: boolean + service_active: boolean + key_present: boolean + anchor_connected?: boolean + authenticated_peer_count?: number + } | null>(null) + + const systemStatsLoaded = computed(() => systemLoadState.value === 'ready') + const bitcoinKnown = computed(() => stats.bitcoinAvailable !== null) + const vpnKnown = computed(() => vpnStatus.value.connected !== null) + + async function refreshSystemStats() { + systemLoadState.value = systemLoadState.value === 'ready' ? 'ready' : 'loading' + try { + const res = await rpcClient.call<{ + cpu_usage_percent: number + mem_used_bytes: number + mem_total_bytes: number + disk_used_bytes: number + disk_total_bytes: number + uptime_secs: number + load_avg_1?: number + load_avg_5?: number + load_avg_15?: number + }>({ method: 'system.stats' }) + stats.cpuPercent = res.cpu_usage_percent + stats.memUsed = res.mem_used_bytes + stats.memTotal = res.mem_total_bytes + stats.memPercent = res.mem_total_bytes > 0 ? (res.mem_used_bytes / res.mem_total_bytes) * 100 : 0 + stats.diskUsed = res.disk_used_bytes + stats.diskTotal = res.disk_total_bytes + stats.diskPercent = res.disk_total_bytes > 0 ? (res.disk_used_bytes / res.disk_total_bytes) * 100 : 0 + stats.uptimeSecs = res.uptime_secs + stats.loadAvg1 = res.load_avg_1 ?? 0 + stats.loadAvg5 = res.load_avg_5 ?? 0 + stats.loadAvg15 = res.load_avg_15 ?? 0 + systemLoadState.value = 'ready' + lastSystemRefreshAt.value = Date.now() + } catch { + systemLoadState.value = stats.uptimeSecs > 0 ? 'ready' : 'error' + } + } + + async function refreshBitcoin(packages: Record) { + bitcoinLoadState.value = bitcoinLoadState.value === 'ready' ? 'ready' : 'loading' + try { + const btc = await rpcClient.call<{ block_height: number; sync_progress: number }>({ + method: 'bitcoin.getinfo', + timeout: 5000, + }) + stats.bitcoinSyncPercent = (btc.sync_progress ?? 0) * 100 + stats.bitcoinBlockHeight = btc.block_height ?? 0 + stats.bitcoinAvailable = true + bitcoinLoadState.value = 'ready' + lastBitcoinRefreshAt.value = Date.now() + } catch { + const btcPkg = packages['bitcoin-knots'] || packages['bitcoin-core'] || packages.bitcoin + if (btcPkg?.state === PackageState.Running) { + stats.bitcoinAvailable = true + bitcoinLoadState.value = 'ready' + lastBitcoinRefreshAt.value = Date.now() + return + } + + if (btcPkg && (btcPkg.state === PackageState.Stopped || btcPkg.state === PackageState.Exited)) { + stats.bitcoinAvailable = false + bitcoinLoadState.value = 'ready' + lastBitcoinRefreshAt.value = Date.now() + return + } + + // No authoritative package data yet. Keep the previous known value + // rather than flashing "Not running" during route changes/scans. + bitcoinLoadState.value = stats.bitcoinAvailable === null ? 'error' : 'ready' + } + } + + async function refreshVpn(packages: Record) { + vpnLoadState.value = vpnLoadState.value === 'ready' ? 'ready' : 'loading' + try { + const status = await rpcClient.vpnStatus() + vpnStatus.value = { + connected: status.connected, + provider: status.provider ?? status.configured_provider ?? '', + } + vpnLoadState.value = 'ready' + lastVpnRefreshAt.value = Date.now() + } catch { + const tailscale = packages.tailscale + if (tailscale?.state === PackageState.Running) { + vpnStatus.value = { connected: true, provider: 'tailscale' } + vpnLoadState.value = 'ready' + lastVpnRefreshAt.value = Date.now() + return + } + vpnLoadState.value = vpnStatus.value.connected === null ? 'error' : 'ready' + } + } + + async function refreshFips() { + fipsLoadState.value = fipsLoadState.value === 'ready' ? 'ready' : 'loading' + try { + const status = await rpcClient.call<{ + installed: boolean + service_active: boolean + key_present: boolean + anchor_connected?: boolean + authenticated_peer_count?: number + }>({ method: 'fips.status' }) + fipsStatus.value = status + fipsLoadState.value = 'ready' + lastFipsRefreshAt.value = Date.now() + } catch { + fipsLoadState.value = fipsStatus.value ? 'ready' : 'error' + } + } + + async function refresh(packages: Record) { + await Promise.all([ + refreshSystemStats(), + refreshBitcoin(packages), + refreshVpn(packages), + refreshFips(), + ]) + } + + return { + stats, + systemLoadState, + bitcoinLoadState, + vpnLoadState, + fipsLoadState, + systemStatsLoaded, + bitcoinKnown, + vpnKnown, + vpnStatus, + fipsStatus, + lastSystemRefreshAt, + lastBitcoinRefreshAt, + lastVpnRefreshAt, + lastFipsRefreshAt, + refresh, + refreshSystemStats, + refreshBitcoin, + refreshVpn, + refreshFips, + } +}) diff --git a/neode-ui/src/views/Home.vue b/neode-ui/src/views/Home.vue index 92fc3b0c..65804d50 100644 --- a/neode-ui/src/views/Home.vue +++ b/neode-ui/src/views/Home.vue @@ -144,12 +144,12 @@ {{ torConnected ? 'Connected' : 'Offline' }}
-
VPN
- {{ vpnConnected ? 'WireGuard' : 'Not configured' }} +
VPN
+ {{ vpnStatusLabel }}
-
Bitcoin
- {{ bitcoinSyncDisplay }} +
Bitcoin
+ {{ bitcoinSyncDisplay }}
FIPS
@@ -229,7 +229,7 @@