Files
FabledSteward/plugins/traefik/templates/traefik/widget_request_chart.html
T
bvandeusenandClaude Opus 5.5 0bac1d2068
CI / lint (push) Successful in 3s
CI / integration (push) Successful in 1m27s
CI / unit (push) Failing after 1m38s
CI / publish (push) Skipped
feat(traefik): the page leads with its verdict and the request chart (#5710)
The Traefik page now reads state first: a verdict naming each router that
needs a look, why, and when it started (from its history buckets over the
page's range), then the request chart following the page's time range, then
the problem cards (which say since when too), the proxy's own figures, and
the healthy rest. The verdict is filled out of band by /rows each poll; the
"Needs attention" and "Healthy routers" tiles it replaces are gone.

The request chart takes ?range= from the page; past a day its times carry the
day, and its clock is 24-hour (hourCycle h23).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 10:50:09 -04:00

103 lines
4.2 KiB
HTML

{# traefik/widget_request_chart.html — requests per second across all routers
and the share answered 5xx, over the last few hours (#5256), or over the
Traefik page's time range there (#5710). 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 {{ span }}.</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 {{ span }}"></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 }};
// A 24-hour clock in the reader's zone; past a day, with the day too.
var hhmm = function (ms) {
return new Date(ms).toLocaleString([], { {% if days %}weekday: "short", day: "numeric", {% endif %}hour: "2-digit", minute: "2-digit", hourCycle: "h23" });
};
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 {{ span }}, your local time</div>
{% endif %}