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>
103 lines
4.2 KiB
HTML
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 %}
|