#5741 "Traefik routers are graded against their own normal", milestone 513. - A router's 4xx share and p95 learn their normal (learn_normal in the plugin's setup). Once a router has 5 days of history, a reading over the fixed cutoff but inside its normal is healthy, and the healthy table says it is usual for that router. One out of its normal is a warning that says what is usual, and a metric anomaly. - The 5xx share is a failure: fixed cutoff always, whatever the history or traffic (System 182's ruling). - A 4xx share over fewer requests than Settings -> Thresholds' new "Fewest requests to judge 4xx" (30 per 15 minutes) isn't graded on the page, and isn't judged for anomalies. - Core: learn_normal(gate=(metric, least)) judges a key only while its resource's gate metric averages at least `least`; `least` may be a function, so it follows a setting. learn_normal(since=...) learns only from history after a moment: Traefik latency learns from #5755's per-scrape figures only. - The Traefik page lists the routers' metric anomalies of the last 30 days (_anomaly_card.html). The dashboard widget keeps its why to problems. - Thresholds and docs say when the cutoffs give way to a normal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
56 lines
3.2 KiB
HTML
56 lines
3.2 KiB
HTML
{# plugins/traefik/templates/traefik/widget.html — the dashboard Routers widget
|
|
(#5254): a verdict, routers in trouble with why, then the busiest healthy
|
|
ones, under one header. Only routers Traefik still reports. A certificate
|
|
shows on the router it serves (TLS column, days left); when Traefik's API
|
|
can't say which hosts a router serves, the expiring certificates are listed
|
|
after the routers instead. #}
|
|
{% from "_macros.html" import status_dot %}
|
|
{% if not total %}
|
|
<div class="w-verdict" style="color:var(--text-muted);">No Traefik data yet.</div>
|
|
{% else %}
|
|
{% set crit = problems | selectattr("state", "equalto", "crit") | list %}
|
|
<div class="w-verdict {{ 'tone-crit' if crit else ('tone-warn' if problems or loose_certs else 'tone-ok') }}">
|
|
{%- if problems|length == 1 %}{{ problems[0].name.split('@')[0] }}: {{ problems[0].why }}.
|
|
{%- elif problems %}{{ problems|length }} of {{ total }} routers need a look.
|
|
{%- else %}All {{ total }} router{{ 's' if total != 1 }} healthy{% if loose_certs %}; {{ loose_certs|length }} certificate{{ 's' if loose_certs|length != 1 }} expiring{% endif %}.{% endif -%}
|
|
</div>
|
|
<table class="w-table">
|
|
<colgroup><col><col style="width:4rem"><col class="w-wide-only" style="width:5.5rem"><col style="width:4.2rem">{% if domains_known %}<col style="width:4.5rem">{% endif %}</colgroup>
|
|
<thead><tr><th>Router</th><th>Req/s</th><th class="w-wide-only">Bandwidth</th><th>p95</th>{% if domains_known %}<th>TLS</th>{% endif %}</tr></thead>
|
|
<tbody>
|
|
{% for r in problems + healthy %}
|
|
{% set m = r.latest %}
|
|
<tr>
|
|
<td class="w-name" title="{{ r.name }}">
|
|
{{- status_dot(r.state) -}}
|
|
{{ r.name.split('@')[0] }}
|
|
{%- if r.why and r.state != "ok" %}<span class="w-sub tone-{{ r.state }}">{{ r.why }}</span>{% endif %}
|
|
</td>
|
|
<td>{% if m.request_rate is none %}—{% else %}{{ "%.2f"|format(m.request_rate) }}{% endif %}</td>
|
|
<td class="w-wide-only">{% if m.response_bytes_rate is none %}—{% else %}{{ m.response_bytes_rate|rate }}{% endif %}</td>
|
|
<td style="{{ threshold_style(m.latency_p95_ms, 'latency') if m.latency_p95_ms is not none }}">
|
|
{%- if m.latency_p95_ms is none %}—{% else %}{{ "%.0f"|format(m.latency_p95_ms) }} ms{% endif -%}
|
|
</td>
|
|
{% if domains_known %}
|
|
<td {% if r.cert %}style="{{ threshold_style(r.cert.days, 'tls_days') }}" title="Certificate {{ r.cert.cn }}"{% endif %}>
|
|
{%- if r.cert %}{{ cert_left(r.cert.days) }}{% else %}—{% endif -%}
|
|
</td>
|
|
{% endif %}
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{% if healthy_more %}<div class="w-more">+{{ healthy_more }} more healthy{% if session.user_id %} · <a href="/plugins/traefik/">all routers</a>{% endif %}</div>{% endif %}
|
|
{% if loose_certs %}
|
|
<div class="w-section">Certificates expiring</div>
|
|
{% for c in loose_certs %}
|
|
<div class="w-item">
|
|
{{- status_dot(status_class(c.days, 'tls_days')) -}}
|
|
<span class="w-label" title="{{ c.cn }}">{{ c.cn }}</span>
|
|
<span class="w-meta" style="{{ threshold_style(c.days, 'tls_days') }}">{{ cert_left(c.days) }}</span>
|
|
</div>
|
|
{% endfor %}
|
|
<div class="w-note">Traefik's API didn't say which hosts each router serves, so these can't be shown on their routers.</div>
|
|
{% endif %}
|
|
{% endif %}
|