feat(alerts): one rule can cover every host, or every host in a group (#5464)
#5464 "Group rules: one rule covers every host, or every host in a group", step 2 of milestone 499 "Alerts you can trust". Each rule named one host, so the live instance carried six rules for three hosts, and a new host had no agent-offline or disk alert until someone copied them. - alert_rules.covers / cover_group / parent_id (migration 0041). A rule on host_agent may cover "all_hosts" or a "group" (an inventory group: the Hosts ruling, #5294). A covering rule has no state; Steward keeps one ordinary child rule per covered host, named "<rule>: <host>", so each host's alert keeps its own state, history and acknowledgement. - alerts.service.reconcile_children: a covered host that has reported in the last 7 days gets a child; a host no longer covered loses its child; children take the parent's condition, checks, notify setting, action and on/off. Run on save and each minute (alert_reconcile). - A child is changed through its parent: edit, toggle and delete of a child are refused (409 / PartOfCoveringRule); its edit page opens the parent. - Rule form: a Covers choice for host sources, with the group picker; fixed once the rule exists. Rules list: a covering rule reads "every host in swarm · 3 hosts" with its hosts' rules nested under it, each opening its history. The history picker and the rule count leave covering rules out. - MCP: create_alert_rule takes covers/group; update_alert_rule takes group and passes changes down; list_alert_rules shows covers, hosts and part_of. - docs/core/alerting.md: "Rules that cover hosts". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,13 @@
|
||||
{%- 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 -%}
|
||||
</p>
|
||||
{% if rule.covers != "resource" %}
|
||||
<p class="field-hint" style="margin-top:-0.5rem;">
|
||||
Covers {% if rule.covers == "group" %}every host in <strong>{{ rule.cover_group }}</strong>{% else %}every host{% endif %}:
|
||||
{% if covered %}{{ covered|length }} now ({{ covered|join(", ") }}), each with its own alert and history.
|
||||
{%- else %}no host yet; one is covered within a minute of reporting.{% endif %}
|
||||
Changes here apply to all of them.</p>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{{ page_header("New alert rule") }}
|
||||
{% endif %}
|
||||
@@ -212,6 +219,16 @@
|
||||
document.addEventListener("input", function (e) {
|
||||
if (e.target.name === "threshold" && !e.target.form.querySelector("[data-threshold-echo]").hidden) showDuration(e.target.form);
|
||||
});
|
||||
// Covering hosts swaps the resource picker for the group picker (#5464).
|
||||
document.addEventListener("change", function (e) {
|
||||
if (!e.target.hasAttribute("data-covers")) return;
|
||||
var form = e.target.form, v = e.target.value;
|
||||
var res = form.querySelector("[data-cover-resource]"), grp = form.querySelector("[data-cover-group]");
|
||||
res.hidden = v !== "resource";
|
||||
res.querySelectorAll("select").forEach(function (s) { s.disabled = v !== "resource" || s.dataset.none === "1"; });
|
||||
grp.hidden = v !== "group";
|
||||
grp.querySelector("select").disabled = v !== "group";
|
||||
});
|
||||
// The playbook's fields show only while "run a playbook" is ticked.
|
||||
document.addEventListener("change", function (e) {
|
||||
if (!e.target.hasAttribute("data-ansible-toggle")) return;
|
||||
|
||||
Reference in New Issue
Block a user