Files
FabledSteward/steward/static/chart-marks.js
T
bvandeusenandClaude Opus 5.5 96f774ce24
CI / lint (push) Successful in 2s
CI / unit (push) Successful in 1m28s
CI / integration (push) Successful in 1m38s
CI / publish (push) Successful in 1m12s
fix(syslog): chart marks give up after 10 s rather than waiting on (#6002)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 06:40:01 -04:00

84 lines
3.2 KiB
JavaScript

// Marks on a time chart: a thin line at each moment something was logged
// (#6002, the syslog errors of a host or a device), so a reading's jump can be
// read against what the device said at the time.
//
// A chart opts in by holding marks in options.plugins.stewardMarks.marks, as
// [{at: epoch ms, level, text}]. A linear x axis takes the time itself; a
// category axis of ISO times takes the nearest label. Hovering near a mark
// shows its text in the chart's title attribute.
(function () {
if (!window.Chart) return;
function xOf(chart, at) {
var x = chart.scales.x;
if (!x) return null;
if (x.type !== 'category') return x.getPixelForValue(at);
var labels = chart.data.labels || [];
if (!labels.length) return null;
var first = Date.parse(labels[0]), last = Date.parse(labels[labels.length - 1]);
if (at < first || at > last) return null;
var best = 0, gap = Infinity;
for (var i = 0; i < labels.length; i++) {
var d = Math.abs(Date.parse(labels[i]) - at);
if (d < gap) { gap = d; best = i; }
}
return x.getPixelForValue(best);
}
Chart.register({
id: 'stewardMarks',
afterDatasetsDraw: function (chart, args, opts) {
var marks = (opts && opts.marks) || [];
if (!marks.length) return;
var area = chart.chartArea, ctx = chart.ctx;
var crit = window.stewardColor ? stewardColor('--red') : 'red';
chart.$stewardMarks = [];
ctx.save();
ctx.lineWidth = 1;
ctx.setLineDash([3, 3]);
ctx.strokeStyle = crit;
ctx.globalAlpha = 0.55;
marks.forEach(function (m) {
var px = xOf(chart, m.at);
if (px == null || px < area.left || px > area.right) return;
ctx.beginPath();
ctx.moveTo(px, area.top);
ctx.lineTo(px, area.bottom);
ctx.stroke();
chart.$stewardMarks.push({ x: px, text: m.text, at: m.at });
});
ctx.restore();
},
afterEvent: function (chart, args) {
var near = (chart.$stewardMarks || []).filter(function (m) {
return Math.abs(m.x - args.event.x) <= 3;
});
chart.canvas.title = near.map(function (m) {
return new Date(m.at).toLocaleString([], { hour12: false }) + ' ' + m.text;
}).join('\n');
},
});
// Fetch marks from `url` and put them on each chart, redrawing it; `note`
// (an element, optional) says in words how many there are.
window.stewardMarkCharts = function (url, charts, note) {
// A deadline, so a slow answer leaves the charts unmarked rather than waiting on.
fetch(url, { credentials: 'same-origin', signal: AbortSignal.timeout(10000) })
.then(function (r) { return r.ok ? r.json() : { marks: [] }; })
.then(function (body) {
var marks = body.marks || [];
charts.forEach(function (ch) {
if (!ch) return;
ch.options.plugins.stewardMarks = { marks: marks };
ch.update('none');
});
if (note) {
note.hidden = !marks.length;
note.textContent = marks.length
? (body.more ? 'The newest ' : '') + marks.length + ' syslog error' + (marks.length === 1 ? '' : 's') +
' in this span marked with dashed red lines; hover one to read it.'
: '';
}
})
.catch(function () {});
};
})();