Files
FabledSteward/plugins/traefik/templates/traefik/widget_request_chart.html
T
bvandeusenandClaude Opus 5.5 eaf9f1bcb8
CI / lint (push) Successful in 3s
CI / integration (push) Successful in 53s
CI / unit (push) Successful in 1m22s
CI / publish (push) Successful in 1m26s
fix(traefik): request chart redraws once the dashboard has placed it, and refreshes every 5 min (#5214)
A chart built while its cell was still being laid out could stay blank until
a window resize; it now measures and draws again after layout settles. Each
refresh rebuilds the chart (the flash), and its buckets are minutes wide, so
the widget refreshes every 5 minutes instead of every poll.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 15:24:29 -04:00

101 lines
4.1 KiB
HTML

{# traefik/widget_request_chart.html — requests per second across all routers
and the share answered 5xx, over the last few hours (#5256). The verdict
carries the figures, so the widget still says something if the chart
can't draw; a chart that fails says why in place of the canvas. #}
{% if not labels_json or labels_json == '[]' %}
<div class="w-verdict" style="color:var(--text-muted);">No Traefik requests recorded in the last {{ hours }}h.</div>
{% else %}
<div class="w-verdict {{ 'tone-crit' if peak_err and peak_err >= 1 else ('tone-warn' if peak_err else 'tone-ok') }}">
Now {{ "%.2f"|format(now_req) }} req/s across all routers, peak {{ "%.2f"|format(peak_req) }}.
{%- if peak_err %} 5xx peaked at {{ "%.2f"|format(peak_err) }}% of responses.{% else %} No 5xx.{% endif %}
</div>
<div style="position:relative;height:160px;" id="chart-traefik-req-box-{{ widget_id }}">
<canvas id="chart-traefik-req-{{ widget_id }}" aria-label="Requests per second and 5xx share, last {{ hours }} hours"></canvas>
</div>
<script>
(function() {
var box = document.getElementById("chart-traefik-req-box-{{ widget_id }}");
var ctx = document.getElementById("chart-traefik-req-{{ widget_id }}");
if (!ctx) return;
function fail(why) {
box.style.height = "auto";
box.innerHTML = '<div class="w-note"></div>';
box.firstChild.textContent = "The chart couldn't be drawn: " + why;
}
if (!window.Chart) { fail("the chart library didn't load."); return; }
try {
var existing = Chart.getChart(ctx);
if (existing) existing.destroy();
var times = {{ labels_json | safe }};
var hhmm = function (ms) {
return new Date(ms).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
};
var chart = new Chart(ctx.getContext("2d"), {
type: "line",
data: {
labels: times,
datasets: [
{
label: "Requests/s (all routers)",
data: {{ req_rate_json | safe }},
borderColor: stewardColor("--blue"),
backgroundColor: stewardColor("--blue-dim"),
fill: true, tension: 0.3, pointRadius: 0, borderWidth: 1.5, yAxisID: "y",
},
{
label: "5xx share %",
data: {{ error_rate_json | safe }},
borderColor: stewardColor("--red"),
fill: false, tension: 0.3, pointRadius: 0, borderWidth: 1.5, yAxisID: "y2",
},
]
},
options: {
responsive: true,
maintainAspectRatio: false,
animation: false,
interaction: { mode: "index", intersect: false },
plugins: {
legend: { labels: { boxWidth: 10, font: { size: 10 } } },
tooltip: { callbacks: { title: function (items) { return items.length ? hhmm(times[items[0].dataIndex]) : ""; } } },
},
scales: {
x: {
ticks: {
font: { size: 10 }, maxRotation: 0, autoSkip: true, maxTicksLimit: 6,
callback: function (value) { return hhmm(times[value]); },
},
grid: { display: false },
},
y: {
min: 0,
ticks: { font: { size: 10 } },
title: { display: true, text: "req/s", font: { size: 9 } },
},
y2: {
position: "right",
min: 0,
suggestedMax: 5,
grid: { drawOnChartArea: false },
ticks: { color: stewardColor("--red"), font: { size: 10 } },
title: { display: true, text: "5xx %", color: stewardColor("--red"), font: { size: 9 } },
},
}
}
});
// The chart is built while the dashboard may still be placing this cell,
// and a chart sized then can stay blank until a window resize (#5214).
// Measure and draw again once layout has settled.
setTimeout(function () {
if (!ctx.isConnected) return;
chart.resize();
chart.update("none");
}, 250);
} catch (err) {
fail(err && err.message ? err.message : String(err));
}
})();
</script>
<div class="w-note" style="text-align:right;">last {{ hours }}h, your local time</div>
{% endif %}