// 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 () {}); }; })();