diff --git a/docs/core/alerting.md b/docs/core/alerting.md index 99ce1d3..032e4d2 100644 --- a/docs/core/alerting.md +++ b/docs/core/alerting.md @@ -81,7 +81,8 @@ whole host", then its mounts, CPU cores, disks and so on, under a heading for each kind. A part that reports nothing a rule can watch, such as an interface's traffic, isn't listed. The **Metric** list holds only what the picked resource reports. Under it, a preview shows the metric's current reading and its last -24 hours (lowest to highest, with an hourly sparkline), so the threshold can be +24 hours (lowest to highest, with an hourly sparkline; it loads just after the +page, since a day of samples can take a while to read), so the threshold can be set against real numbers. ### Rules that cover hosts @@ -176,7 +177,7 @@ bursts: each alert posts on its own, at once. ### Send test -**Send test** on a rule's page (operator role), or `send_test_alert` over MCP, +**Send test** at the top of a rule's page (operator role), or `send_test_alert` over MCP, sends a `TEST` alert for that rule now, by email and webhook, the same way a real alert would go. It uses the rule's name and condition and the latest reading of what the rule watches. A rule that covers hosts uses its first diff --git a/steward/alerts/routes.py b/steward/alerts/routes.py index 39786b7..734ad14 100644 --- a/steward/alerts/routes.py +++ b/steward/alerts/routes.py @@ -281,9 +281,11 @@ async def rule_target_api(): @alerts_bp.get("/api/metric_preview") @require_role(UserRole.viewer) async def metric_preview_api(): - """What the picked metric reads now and over the last day (#5475).""" + """What the picked metric reads now and over the last day (#5475). Its own + request, after the page: a cold day of one series can take many seconds + to read (#5485), and the page doesn't wait for it.""" async with current_app.db_sessionmaker() as db: - ctx = await _target_from_args(db) + ctx = await _target_from_args(db, with_preview=True) return await render_template("alerts/_metric_preview.html", **ctx) @@ -300,28 +302,30 @@ async def cover_groups_api(): async def _target(db, source: str, *, host: str = "", resource: str = "", metric: str = "", - covers: str = "resource", keep: bool = False) -> dict: - """The rule form's target pickers and the chosen metric's preview (#5475); - `keep` on a rule's own page, so its resource and metric stay on offer.""" + covers: str = "resource", keep: bool = False, with_preview: bool = False) -> dict: + """The rule form's target pickers (#5475), from the latest-value table; + `keep` on a rule's own page, so its resource and metric stay on offer. + The chosen metric's preview only `with_preview`: it reads a day of raw + samples, so pages and picker swaps leave it to its own request (#5485).""" from steward.core.status import sparkline_svg target = await alert_svc.rule_target(db, source, host=host, resource=resource, metric=metric, keep=keep) preview = None - if target["resource"] and target["metric"]: + if with_preview and target["resource"] and target["metric"]: preview = await alert_svc.metric_preview(db, source, target["resource"], target["metric"]) if preview: # From zero to the series' own peak (UI & Design ruling 8), so a # disk at 61-62% reads flat rather than as a cliff. preview["spark"] = sparkline_svg(preview["series"], width=120, height=20, stroke="currentColor", floor=1.0) - return {**target, "preview": preview, "current_covers": covers} + return {**target, "preview": preview, "preview_read": with_preview, "current_covers": covers} -async def _target_from_args(db) -> dict: +async def _target_from_args(db, *, with_preview: bool = False) -> dict: a = request.args return await _target(db, a.get("source_module", ""), host=a.get("pick_host", ""), resource=a.get("resource_name", ""), metric=a.get("metric_name", ""), - covers=a.get("covers", "resource")) + covers=a.get("covers", "resource"), with_preview=with_preview) async def _cover_choices(db, source: str, rule: AlertRule | None = None) -> dict: diff --git a/steward/templates/alerts/_metric_preview.html b/steward/templates/alerts/_metric_preview.html index 59455dc..7be3c01 100644 --- a/steward/templates/alerts/_metric_preview.html +++ b/steward/templates/alerts/_metric_preview.html @@ -13,6 +13,8 @@ {% endif %} +{% elif metric and resource and not preview_read %} +
Reading the last day…
{% elif metric and resource %}No reading of this yet from {{ resource }}.
{% endif %} diff --git a/steward/templates/alerts/_rule_target.html b/steward/templates/alerts/_rule_target.html index 354778e..33b7202 100644 --- a/steward/templates/alerts/_rule_target.html +++ b/steward/templates/alerts/_rule_target.html @@ -3,7 +3,8 @@ so on), so a host's parts never crowd the list of hosts. Any other source: one Resource picker. Then the metrics that resource reports, and a preview of the chosen one. Picking a host or part swaps this block alone - (/alerts/api/rule_target); picking a metric swaps the preview. #} + (/alerts/api/rule_target); picking a metric swaps the preview, which + is read in its own request after the block is drawn (#5485). #} {% set one = current_covers|default("resource") == "resource" %}{{ metric_about(metric) }}
{% else %}{% endif %} -@@ -17,6 +21,7 @@ {%- if fired %}Last fired {{ fired|ago }}{% else %}Never fired{% endif -%} {%- if history_count is defined and history_count is not none %} · {{ history_count }} change{{ "s" if history_count != 1 }} of state on record{% endif -%}
+ {% if rule.covers != "resource" %}Covers {% if rule.covers == "group" %}every host in {{ rule.cover_group }}{% else %}every host{% endif %}: @@ -139,15 +144,8 @@ {%- else %}It notifies nobody yet: no email server with recipients and no webhook is set {%- if session.user_role == 'admin' %} (Settings → Notifications){% endif %}.{% endif -%}
- {% if rule %} -Sends a TEST alert for this rule as saved, now, to everywhere above. - Nothing fires, and the rule's history doesn't change.
- -To check that it arrives, use + Send test at the top of this page.
{% endif %}