From 43cd64e5b47b26b36559182b2280d4b026a57cd7 Mon Sep 17 00:00:00 2001 From: Syngnat Date: Mon, 27 Jul 2026 21:01:51 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(redis):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E7=9B=91=E6=8E=A7=E5=9B=BE=E8=A1=A8=E6=8F=90=E7=A4=BA=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E9=94=99=E4=BD=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 使用唯一时间戳作为监控图表横轴数据键 - 统一格式化刻度与提示时间并覆盖跨分钟回归 Fixes #689 --- .../components/RedisMonitor.polling.test.tsx | 34 +++++++++ frontend/src/components/RedisMonitor.tsx | 70 ++++++++++++++++--- 2 files changed, 96 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/RedisMonitor.polling.test.tsx b/frontend/src/components/RedisMonitor.polling.test.tsx index 4f45e138..089491da 100644 --- a/frontend/src/components/RedisMonitor.polling.test.tsx +++ b/frontend/src/components/RedisMonitor.polling.test.tsx @@ -183,6 +183,40 @@ describe('RedisMonitor polling', () => { expect(redisApi.RedisGetServerInfo).toHaveBeenCalledTimes(2); }); + it('uses unique chart x values when samples share the same second across minutes', async () => { + vi.setSystemTime(new Date('2026-07-27T10:00:22')); + + await act(async () => { + renderer = create(renderMonitor(true)); + }); + await act(async () => { + renderer!.root.findAllByType('button')[0].props.onClick(); + }); + + vi.setSystemTime(new Date('2026-07-27T10:01:22')); + await act(async () => { + renderer!.root.findAllByType('button')[1].props.onClick(); + }); + + const charts = renderer!.root.findAll(node => Array.isArray(node.props.data)); + expect(charts).toHaveLength(4); + + const points = charts[0].props.data as Array<{ timestamp?: number }>; + expect(points).toHaveLength(2); + expect(new Set(points.map(point => point.timestamp))).toHaveLength(2); + + const xAxes = renderer!.root.findAll(node => node.props.dataKey === 'timestamp'); + expect(xAxes).toHaveLength(4); + expect(xAxes.every(node => node.props.type === 'number')).toBe(true); + + const labels = points.map(point => xAxes[0].props.tickFormatter(point.timestamp)); + expect(labels[0]).not.toBe(labels[1]); + + const tooltips = renderer!.root.findAll(node => typeof node.props.labelFormatter === 'function'); + expect(tooltips).toHaveLength(4); + expect(tooltips[0].props.labelFormatter(points[1].timestamp)).toBe(labels[1]); + }); + it('discards a pending manual refresh after deactivation and lets a new generation run', async () => { const staleRefresh = createDeferred>(); redisApi.RedisGetServerInfo diff --git a/frontend/src/components/RedisMonitor.tsx b/frontend/src/components/RedisMonitor.tsx index 059c3bf5..272a30e1 100644 --- a/frontend/src/components/RedisMonitor.tsx +++ b/frontend/src/components/RedisMonitor.tsx @@ -27,7 +27,7 @@ interface RedisMonitorProps { // Data point for charts interface MetricPoint { - time: string; + timestamp: number; qps: number; memory: number; // in MB memory_rss: number; // in MB @@ -40,6 +40,17 @@ interface MetricPoint { const MAX_HISTORY_POINTS = 60; // Keep up to 60 data points const POLL_INTERVAL_MS = 2000; +const formatMetricTimestamp = (timestamp: unknown) => { + const value = Number(timestamp); + if (!Number.isFinite(value)) return ''; + + return new Date(value).toLocaleTimeString([], { + hour12: false, + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + }); +}; const RedisMonitor: React.FC = ({ connectionId, redisDB, isActive = true }) => { const connections = useStore(state => state.connections); @@ -94,8 +105,7 @@ const RedisMonitor: React.FC = ({ connectionId, redisDB, isAc const infoMap = res.data as Record; setCurrentInfo(infoMap); - const now = new Date(); - const timeStr = now.toLocaleTimeString([], { hour12: false, second: '2-digit' }); + const timestamp = Date.now(); // Parse values const qps = parseInt(infoMap['instantaneous_ops_per_sec'] || '0', 10); @@ -118,7 +128,7 @@ const RedisMonitor: React.FC = ({ connectionId, redisDB, isAc }); const point: MetricPoint = { - time: timeStr, + timestamp, qps, memory: parseFloat((memBytes / 1024 / 1024).toFixed(2)), memory_rss: parseFloat((memRssBytes / 1024 / 1024).toFixed(2)), @@ -334,11 +344,22 @@ const RedisMonitor: React.FC = ({ connectionId, redisDB, isAc - + @@ -356,12 +377,23 @@ const RedisMonitor: React.FC = ({ connectionId, redisDB, isAc - + [`${value} MB`]} + labelFormatter={formatMetricTimestamp} /> @@ -383,12 +415,23 @@ const RedisMonitor: React.FC = ({ connectionId, redisDB, isAc - + [`${value} s`]} + labelFormatter={formatMetricTimestamp} /> @@ -408,12 +451,23 @@ const RedisMonitor: React.FC = ({ connectionId, redisDB, isAc - +