fix: preserve chart history across reconnects, show gaps for missing data
Only wipe real (non-RTT) plugin data on an actual client reboot (boot flag), not on every ordinary OVERDUE/DOWN -> UP recovery. A transient network blip no longer erases CPU/memory/etc. history. Also split the shared time-series chart into separate line/area segments wherever the gap between samples is much larger than the typical spacing, so missing data (host overdue, or history that simply hasn't accumulated across a drop) renders as a visual gap instead of an interpolated line. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DEimzMv4Q5EjFg3hoiZ69T
This commit is contained in:
@@ -847,16 +847,38 @@
|
||||
const yRange = yHigh - yLow || 1;
|
||||
const y = v => PAD.top + cH - ((v - yLow) / yRange) * cH;
|
||||
|
||||
// Build polyline points and filled area path
|
||||
const linePoints = pts.map(p => `${x(p.t).toFixed(1)},${y(p.v).toFixed(1)}`).join(' ');
|
||||
const areaPath = `M${x(pts[0].t).toFixed(1)},${(PAD.top + cH).toFixed(1)} ` +
|
||||
pts.map(p => `L${x(p.t).toFixed(1)},${y(p.v).toFixed(1)}`).join(' ') +
|
||||
` L${x(pts[pts.length-1].t).toFixed(1)},${(PAD.top + cH).toFixed(1)} Z`;
|
||||
|
||||
// Color based on latest value
|
||||
const latest = pts[pts.length - 1].v;
|
||||
const { stroke: strokeColor, fill: fillColor } = opts.colorFor(latest);
|
||||
|
||||
// Split into segments wherever the gap between consecutive samples is
|
||||
// much larger than the typical spacing (e.g. the host was overdue/down
|
||||
// for a while) — draw each segment separately so missing data reads as
|
||||
// a visual gap instead of an interpolated line across dead time.
|
||||
const deltas = [];
|
||||
for (let i = 1; i < pts.length; i++) deltas.push(pts[i].t - pts[i - 1].t);
|
||||
deltas.sort((a, b) => a - b);
|
||||
const medianDelta = deltas[Math.floor(deltas.length / 2)];
|
||||
const gapThreshold = medianDelta * 2.5;
|
||||
|
||||
const segments = [[pts[0]]];
|
||||
for (let i = 1; i < pts.length; i++) {
|
||||
if (pts[i].t - pts[i - 1].t > gapThreshold) segments.push([]);
|
||||
segments[segments.length - 1].push(pts[i]);
|
||||
}
|
||||
|
||||
let linePolylines = '';
|
||||
let areaPaths = '';
|
||||
for (const seg of segments) {
|
||||
if (seg.length < 2) continue;
|
||||
const segPoints = seg.map(p => `${x(p.t).toFixed(1)},${y(p.v).toFixed(1)}`).join(' ');
|
||||
linePolylines += `<polyline points="${segPoints}" fill="none" stroke="${strokeColor}" stroke-width="1.5" stroke-linejoin="round"/>`;
|
||||
const segArea = `M${x(seg[0].t).toFixed(1)},${(PAD.top + cH).toFixed(1)} ` +
|
||||
seg.map(p => `L${x(p.t).toFixed(1)},${y(p.v).toFixed(1)}`).join(' ') +
|
||||
` L${x(seg[seg.length-1].t).toFixed(1)},${(PAD.top + cH).toFixed(1)} Z`;
|
||||
areaPaths += `<path d="${segArea}" fill="${fillColor}" opacity="0.6"/>`;
|
||||
}
|
||||
|
||||
// Compute nice tick step for ~3-5 grid lines
|
||||
const rawStep = yRange / 4;
|
||||
const mag = Math.pow(10, Math.floor(Math.log10(rawStep || 1)));
|
||||
@@ -890,8 +912,8 @@
|
||||
<line x1="${PAD.left}" y1="${PAD.top}" x2="${PAD.left}" y2="${PAD.top + cH}" stroke="#ccc" stroke-width="1"/>
|
||||
<line x1="${PAD.left}" y1="${PAD.top + cH}" x2="${PAD.left + cW}" y2="${PAD.top + cH}" stroke="#ccc" stroke-width="1"/>
|
||||
<g clip-path="url(#${opts.clipId})">
|
||||
<path d="${areaPath}" fill="${fillColor}" opacity="0.6"/>
|
||||
<polyline points="${linePoints}" fill="none" stroke="${strokeColor}" stroke-width="1.5" stroke-linejoin="round"/>
|
||||
${areaPaths}
|
||||
${linePolylines}
|
||||
</g>
|
||||
${xLabels}
|
||||
</svg>`;
|
||||
|
||||
Reference in New Issue
Block a user