feat(tickets): open, comment and resolve tickets from the UI and MCP; known issues on alerts and anomalies (#6351, #6352)
CI / lint (push) Successful in 2s
CI / unit (push) Successful in 1m37s
CI / integration (push) Successful in 1m51s
CI / publish (push) Successful in 1m16s

A ticket records a known problem: title, area, hosts, subject, description,
links to alert rules and anomalies, and a log of comments, edits, resolving
and reopening. Opening, resolving (with the resolution as the note) and
reopening go on the timeline; every step is audited, and the audit rows stay
off the timeline.

An alert or anomaly an open ticket covers (by link, by subject, or a
host-wide ticket on the host itself) shows a Known issue badge, and offers
"Open a ticket" prefilled from its rule or anomaly when none does. Hosts,
monitors and containers get a Known issues card.

MCP: list_tickets, get_ticket (viewer); open_ticket, update_ticket,
comment_ticket, resolve_ticket, reopen_ticket (operator). list_alerts and
list_anomalies carry known_issue; get_overview, get_host, get_monitor and
get_container carry open_tickets. Also update_change and delete_change for
changes recorded by hand (author or admin).

Tickets are left out of backups, like the timeline (2026-10-15 ruling).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-24 22:06:43 -04:00
co-authored by Claude Opus 5.5
parent 1ea86c0330
commit 2a561210c3
40 changed files with 1786 additions and 35 deletions
+8 -1
View File
@@ -50,7 +50,8 @@ Held {{ metric_value(a.metric_name, a.peak_level) }}; usual for that hour
{%- endif -%}
{%- endmacro %}
{# One anomaly as a row: badge, resource and metric, when, level, review. #}
{# One anomaly as a row: badge, resource and metric, when, level, review, and
the open tickets that make it a known issue (or a link to open one). #}
{% macro anomaly_row(v, back=none, show_resource=true) -%}
{%- set a = v.anomaly -%}
<div class="anomaly-row" style="padding:0.55rem 0;border-bottom:1px solid var(--border);">
@@ -68,6 +69,12 @@ Held {{ metric_value(a.metric_name, a.peak_level) }}; usual for that hour
{{ anomaly_when(v) }} · {{ anomaly_level(a) }}
</div>
<div style="font-size:0.8rem;color:var(--text-muted);margin-top:0.2rem;">{{ anomaly_review(a, back) }}</div>
{%- if v.tickets %}
<div style="font-size:0.8rem;margin-top:0.2rem;">{{ status_badge("known_issue") }}
{% for t in v.tickets %}<a href="/tickets/{{ t.id }}">#{{ t.id }} {{ t.title }}</a>{{ ", " if not loop.last }}{% endfor %}</div>
{%- elif session.user_role in ('operator', 'admin') and a.review in ('to_review', 'real') %}
<div style="font-size:0.8rem;margin-top:0.2rem;"><a href="/tickets/new?anomaly={{ a.id|urlencode }}{% if back %}&amp;back={{ back|urlencode }}{% endif %}">Open a ticket</a></div>
{%- endif %}
{%- if "syslog" in enabled_plugins and v.last_span %}
{#- The device's own log around its latest occurrence (#6002), read only when opened. #}
<details style="font-size:0.8rem;margin-top:0.3rem;"