diff --git a/AGENTS.md b/AGENTS.md index a3c4269..42cbcb9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,9 +1,9 @@ # PROJECT KNOWLEDGE BASE -**Generated:** Fri Mar 20 2026 -**Commit:** c137bdc +**Generated:** Sat Mar 21 2026 +**Commit:** ac35068 **Branch:** main -**Tag:** 2.1.9-beta9 +**Tag:** 2.1.9-beta10 ## OVERVIEW FLVX (formerly Flux Panel) is a traffic forwarding management system built on a forked GOST v3 stack. It ships as a Go-based admin API (SQLite/PostgreSQL) + Vite/React UI + Go forwarding agent, with optional mobile WebView wrappers. diff --git a/vite-frontend/src/pages/node.tsx b/vite-frontend/src/pages/node.tsx index 683d755..ac186d6 100644 --- a/vite-frontend/src/pages/node.tsx +++ b/vite-frontend/src/pages/node.tsx @@ -609,6 +609,8 @@ export default function NodePage() { if (!metric || typeof metric !== "object") return node; + const incomingUptime = metric.uptime ?? 0; + return { ...node, connectionStatus: "online", @@ -620,7 +622,7 @@ export default function NodePage() { downloadTraffic: metric.netInBytes ?? metric.bytes_received ?? 0, uploadSpeed: metric.netOutSpeed ?? metric.net_out_speed ?? 0, downloadSpeed: metric.netInSpeed ?? metric.net_in_speed ?? 0, - uptime: metric.uptime ?? 0, + uptime: incomingUptime || node.systemInfo?.uptime || 0, diskUsage: metric.diskUsage ?? metric.disk_usage, load1: metric.load1, load5: metric.load5, diff --git a/vite-frontend/src/pages/node/monitor-view.tsx b/vite-frontend/src/pages/node/monitor-view.tsx index bdab2c2..3bd128f 100644 --- a/vite-frontend/src/pages/node/monitor-view.tsx +++ b/vite-frontend/src/pages/node/monitor-view.tsx @@ -386,27 +386,31 @@ export function MonitorView({ nodeMap, viewMode = "grid" }: MonitorViewProps) { const metric = raw as Record; const receivedAt = Date.now(); - const normalized: RealtimeNodeMetric = { - receivedAt, - cpuUsage: Number(metric.cpuUsage ?? metric.cpu_usage ?? 0), - memoryUsage: Number(metric.memoryUsage ?? metric.memory_usage ?? 0), - diskUsage: Number(metric.diskUsage ?? metric.disk_usage ?? 0), - netInBytes: Number(metric.netInBytes ?? metric.bytes_received ?? 0), - netOutBytes: Number(metric.netOutBytes ?? metric.bytes_transmitted ?? 0), - netInSpeed: Number(metric.netInSpeed ?? metric.net_in_speed ?? 0), - netOutSpeed: Number(metric.netOutSpeed ?? metric.net_out_speed ?? 0), - load1: Number(metric.load1 ?? 0), - load5: Number(metric.load5 ?? 0), - load15: Number(metric.load15 ?? 0), - tcpConns: Number(metric.tcpConns ?? metric.tcp_conns ?? 0), - udpConns: Number(metric.udpConns ?? metric.udp_conns ?? 0), - uptime: Number(metric.uptime ?? 0), - }; + const incomingUptime = Number(metric.uptime ?? 0); - setRealtimeNodeMetrics((prev) => ({ - ...prev, - [nodeId]: normalized, - })); + setRealtimeNodeMetrics((prev) => { + const normalized: RealtimeNodeMetric = { + receivedAt, + cpuUsage: Number(metric.cpuUsage ?? metric.cpu_usage ?? 0), + memoryUsage: Number(metric.memoryUsage ?? metric.memory_usage ?? 0), + diskUsage: Number(metric.diskUsage ?? metric.disk_usage ?? 0), + netInBytes: Number(metric.netInBytes ?? metric.bytes_received ?? 0), + netOutBytes: Number(metric.netOutBytes ?? metric.bytes_transmitted ?? 0), + netInSpeed: Number(metric.netInSpeed ?? metric.net_in_speed ?? 0), + netOutSpeed: Number(metric.netOutSpeed ?? metric.net_out_speed ?? 0), + load1: Number(metric.load1 ?? 0), + load5: Number(metric.load5 ?? 0), + load15: Number(metric.load15 ?? 0), + tcpConns: Number(metric.tcpConns ?? metric.tcp_conns ?? 0), + udpConns: Number(metric.udpConns ?? metric.udp_conns ?? 0), + uptime: incomingUptime || prev[nodeId]?.uptime || 0, + }; + + return { + ...prev, + [nodeId]: normalized, + }; + }); setRealtimeNodeStatus((prev) => ({ ...prev, [nodeId]: "online", diff --git a/vite-frontend/src/pages/node/system-info.ts b/vite-frontend/src/pages/node/system-info.ts index d67844c..35c66ee 100644 --- a/vite-frontend/src/pages/node/system-info.ts +++ b/vite-frontend/src/pages/node/system-info.ts @@ -48,13 +48,49 @@ const parseRawSystemInfo = (messageData: unknown): RawSystemInfo | null => { return null; }; +/** + * Heuristic to verify the parsed object actually contains system-info fields. + * Prevents non-metric messages (command responses, etc.) from being mistakenly + * treated as system info, which would reset all values to 0 and cause UI flicker. + */ +const SYSTEM_INFO_KEYS = [ + "uptime", + "cpu_usage", + "memory_usage", + "disk_usage", + "bytes_received", + "bytes_transmitted", + "net_in_speed", + "net_out_speed", + "tcp_conns", + "udp_conns", + "load1", + "load5", + "load15", +] as const; + +const looksLikeSystemInfo = (raw: RawSystemInfo): boolean => { + let matched = 0; + + for (const key of SYSTEM_INFO_KEYS) { + if (key in raw && raw[key] !== undefined) { + matched++; + if (matched >= 3) { + return true; + } + } + } + + return false; +}; + export const buildNodeSystemInfo = ( messageData: unknown, previous: NodeSystemInfo | null | undefined, ): NodeSystemInfo | null => { const raw = parseRawSystemInfo(messageData); - if (!raw) { + if (!raw || !looksLikeSystemInfo(raw)) { return null; }