#5740 "A metric that departs from its normal is recorded as an anomaly to review, and is findable after it ends", milestone 513. - Every minute each learned key's last 15 minutes is judged against its hour's normal: above the usual hold x1.25 and by the metric's floor opens an occurrence, which ends when the level comes back. - metric_anomalies holds one row per item to review; a repeat while it still waits joins it as an occurrence (metric_anomaly_spans). - An ended row nobody reviews within anomalies.review_days (14, Settings -> Thresholds) lapses; an ongoing one never does. - Learning skips every hour an occurrence covers unless its anomaly was marked normal: no normal is learned without review. - Alerts -> Anomalies lists those to review first, then the reviewed and lapsed under day dividers; marking is operator/admin and audited. - The Overview widget and get_overview warn of open ones and count those to review; the host page shows its own; the weekly report lists the week's and the lapsed; MCP list_anomalies and review_anomaly. - Reviewed and lapsed rows are pruned at data.retention_days, and kept in backups like alert history. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
81 lines
3.3 KiB
HTML
81 lines
3.3 KiB
HTML
{# The weekly report's HTML part: d = reports.build_report_data(), alerts = its
|
||
active alerts with tone and label, period, sent, base = Steward's address or "". #}
|
||
{% extends "_layout.html" %}
|
||
{% from "_parts.html" import badge, button, section, rows, row with context %}
|
||
{% block title %}Weekly report, {{ period }}{% endblock %}
|
||
{% block body %}
|
||
<tr><td style="padding:10px 24px 0 24px;font:600 22px/1.3 {{ c.font }};color:{{ c.text }};">Weekly report</td></tr>
|
||
<tr><td style="padding:2px 24px 0 24px;font:14px/1.5 {{ c.font }};color:{{ c.muted }};">{{ period }}</td></tr>
|
||
|
||
{% if d.hosts_down %}
|
||
<tr><td style="padding:16px 24px 0 24px;">
|
||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0"
|
||
style="border:1px solid {{ c.crit }};border-left:4px solid {{ c.crit }};border-radius:8px;">
|
||
<tr><td style="padding:10px 14px;font:14px/1.5 {{ c.font }};color:{{ c.text }};">
|
||
<strong style="color:{{ c.crit }};">{{ d.hosts_down|length }} {{ "monitor is" if d.hosts_down|length == 1 else "monitors are" }} down now:</strong>
|
||
{{ d.hosts_down|join(", ") }}
|
||
</td></tr></table>
|
||
</td></tr>
|
||
{% endif %}
|
||
|
||
{{ section("Active alerts", base ~ "/alerts/" if base else "") }}
|
||
{% if alerts %}
|
||
{% call rows() %}
|
||
{% for a in alerts %}
|
||
{{ row([badge(a.tone, a.state), a.name ~ " — " ~ a.resource, a.condition]) }}
|
||
{% endfor %}
|
||
{% endcall %}
|
||
{% else %}
|
||
<tr><td style="padding:0 24px;font:14px/1.5 {{ c.font }};color:{{ c.muted }};">None. Nothing is firing.</td></tr>
|
||
{% endif %}
|
||
<tr><td style="padding:8px 24px 0 24px;font:13px/1.5 {{ c.font }};color:{{ c.muted }};">
|
||
This week {{ d.alert_events.fired }} {{ "alert" if d.alert_events.fired == 1 else "alerts" }} fired and {{ d.alert_events.resolved }} resolved.
|
||
</td></tr>
|
||
|
||
{{ section("Uptime over 7 days", base ~ "/monitors/" if base else "") }}
|
||
{% if d.uptime_summary %}
|
||
{% call rows() %}
|
||
{% for m in d.uptime_summary %}
|
||
{{ row([m.name, "—" if m.pct_7d is none else "%.2f%%"|format(m.pct_7d)]) }}
|
||
{% endfor %}
|
||
{% endcall %}
|
||
{% else %}
|
||
<tr><td style="padding:0 24px;font:14px/1.5 {{ c.font }};color:{{ c.muted }};">No monitors are enabled.</td></tr>
|
||
{% endif %}
|
||
|
||
{% if anomalies.seen or anomalies.lapsed %}
|
||
{{ section("Metric anomalies this week", base ~ "/alerts/anomalies" if base else "") }}
|
||
{% call rows() %}
|
||
{% for a in anomalies.seen %}
|
||
{{ row([badge(a.tone, a.review), a.name ~ " — " ~ a.when, a.level]) }}
|
||
{% endfor %}
|
||
{% for a in anomalies.lapsed %}
|
||
{{ row([badge(a.tone, a.review), a.name ~ " — lapsed unreviewed; " ~ a.when, a.level]) }}
|
||
{% endfor %}
|
||
{% endcall %}
|
||
{% if anomalies.to_review %}
|
||
<tr><td style="padding:8px 24px 0 24px;font:13px/1.5 {{ c.font }};color:{{ c.muted }};">
|
||
{{ anomalies.to_review }} {{ "anomaly waits" if anomalies.to_review == 1 else "anomalies wait" }} to be marked normal or real.
|
||
</td></tr>
|
||
{% endif %}
|
||
{% endif %}
|
||
|
||
{% if d.top_routers %}
|
||
{{ section("Busiest Traefik routers, last 24 hours", base ~ "/plugins/traefik/" if base else "") }}
|
||
{% call rows() %}
|
||
{% for r in d.top_routers %}
|
||
{{ row([r.name, "%.2f req/s"|format(r.avg_rate)]) }}
|
||
{% endfor %}
|
||
{% endcall %}
|
||
{% endif %}
|
||
|
||
{% if base %}
|
||
<tr><td style="padding:24px 24px 24px 24px;">{{ button(base ~ "/", "Open Steward") }}</td></tr>
|
||
{% else %}
|
||
<tr><td style="padding:0 0 16px 0;"></td></tr>
|
||
{% endif %}
|
||
{% endblock %}
|
||
{% block footer %}
|
||
Sent {{ sent }}. The weekly report is turned on, and its day and hour set, in Settings › Reports.
|
||
{% endblock %}
|