diff --git a/frontend/src/pages/TargetDetailPage.jsx b/frontend/src/pages/TargetDetailPage.jsx index a42cd2a..76031cb 100644 --- a/frontend/src/pages/TargetDetailPage.jsx +++ b/frontend/src/pages/TargetDetailPage.jsx @@ -54,6 +54,15 @@ function MetricsTooltip({ active, payload, label }) { ); } +function didMetricSeriesChange(prev = [], next = []) { + if (!Array.isArray(prev) || !Array.isArray(next)) return true; + if (prev.length !== next.length) return true; + if (prev.length === 0 && next.length === 0) return false; + const prevLast = prev[prev.length - 1]; + const nextLast = next[next.length - 1]; + return prevLast?.ts !== nextLast?.ts || Number(prevLast?.value) !== Number(nextLast?.value); +} + async function loadMetric(targetId, metric, range, tokens, refresh) { const { from, to } = toQueryRange(range); return apiFetch( @@ -129,11 +138,18 @@ export function TargetDetailPage() { loadMetric(id, "cache_hit_ratio", "15m", tokens, refreshRef.current), ]); if (!active) return; - setSeries({ connections, xacts, cache }); + const nextSeries = { connections, xacts, cache }; + setSeries((prev) => { + const changed = + didMetricSeriesChange(prev.connections, nextSeries.connections) || + didMetricSeriesChange(prev.xacts, nextSeries.xacts) || + didMetricSeriesChange(prev.cache, nextSeries.cache); + return changed ? nextSeries : prev; + }); } catch { // Keep previous chart values if a live tick fails. } - }, 1000); + }, 3000); return () => { active = false; @@ -429,9 +445,9 @@ export function TargetDetailPage() { } /> - - - + + +