feat(tickets): open, comment and resolve tickets from the UI and MCP; known issues on alerts and anomalies (#6351, #6352)
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:
@@ -12,6 +12,7 @@ from datetime import datetime, timedelta
|
||||
|
||||
from sqlalchemy import select
|
||||
|
||||
from steward.mcp.tools.tickets import open_tickets_for
|
||||
from steward.ansible.runner import trigger_run
|
||||
from steward.ansible.sources import BUILTIN_SOURCE_NAME
|
||||
from steward.mcp._context import current_caller, mcp_app
|
||||
@@ -390,7 +391,8 @@ async def get_container(container: str, host: str = "", range: str = "24h", even
|
||||
still resolves by its exact name, or by a part of it that matches one
|
||||
removed container, while Steward retains its logs or events: the reply
|
||||
has `removed: true`, `last_seen_at`, its history and its events, and no
|
||||
live state. A live container has `removed: false`.
|
||||
live state. A live container has `removed: false`. `open_tickets` are the
|
||||
open tickets about it (list_tickets).
|
||||
|
||||
Memory (`mem_used_bytes`, `mem_used_pct`, of its limit or of the host's
|
||||
RAM when it has none) is what its processes hold: anon + shmem.
|
||||
@@ -413,6 +415,8 @@ async def get_container(container: str, host: str = "", range: str = "24h", even
|
||||
mem = await docker_svc.container_points(db, c.host_id, c.name, "mem_used_pct", since)
|
||||
evs = await docker_svc.container_events(db, c.host_id, c.name, events)
|
||||
sick = await docker_svc.unhealthy_since(db, [(c.host_id, c.name)])
|
||||
tickets = await open_tickets_for(
|
||||
db, subject=docker_svc.container_resource(host_names.get(c.host_id, c.host_id), c.name))
|
||||
hour_ago = now - timedelta(hours=1)
|
||||
recent = sum(1 for e in failures if e.at >= hour_ago)
|
||||
try:
|
||||
@@ -433,6 +437,7 @@ async def get_container(container: str, host: str = "", range: str = "24h", even
|
||||
"cpu_pct_history": summarize(cpu),
|
||||
"mem_used_pct_history": summarize(mem),
|
||||
"events": _event_rows(evs, now),
|
||||
"open_tickets": tickets,
|
||||
"next": f'search_container_logs(container="{c.name}", host="{c.host_id}")',
|
||||
}
|
||||
|
||||
|
||||
@@ -66,6 +66,8 @@
|
||||
|
||||
{# ── Its CPU or memory out of its normal for the hour, and past anomalies (#5742) #}
|
||||
{% with back=anomaly_back, base_resource=anomaly_resource %}{% include "_anomaly_card.html" %}{% endwith %}
|
||||
{# ── Its open tickets ──────────────────────────────────────────────────────── #}
|
||||
<div hx-get="/tickets/card?subject={{ anomaly_resource|urlencode }}&back={{ anomaly_back|urlencode }}" hx-trigger="load" hx-swap="outerHTML"></div>
|
||||
|
||||
{# ── What it is set up with, and what it moved since it started ─────────── #}
|
||||
<div class="card">
|
||||
|
||||
Reference in New Issue
Block a user