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>
79 lines
3.6 KiB
HTML
79 lines
3.6 KiB
HTML
{# tickets/ticket.html — one ticket: what is wrong and where, what it links to,
|
|
its log (opened, comments, edits, resolved, reopened), then for operators a
|
|
comment box and Resolve, or Reopen once resolved. #}
|
|
{% extends "base.html" %}
|
|
{% from "_macros.html" import crumbs, page_header, status_badge %}
|
|
{% block title %}#{{ t.id }} {{ t.title }} — Tickets — Steward{% endblock %}
|
|
{% block breadcrumb %}{{ crumbs([("Tickets", "/tickets/"), ("#" ~ t.id, "")]) }}{% endblock %}
|
|
{% block content %}
|
|
{% set can_edit = session.user_role in ('operator', 'admin') %}
|
|
{% call page_header("#" ~ t.id ~ " " ~ t.title) %}
|
|
{% if can_edit %}<a class="btn btn-ghost btn-sm" href="/tickets/{{ t.id }}/edit">Edit</a>{% endif %}
|
|
{% endcall %}
|
|
|
|
<p class="page-verdict">
|
|
{{ status_badge(t.status) }}
|
|
<span class="chip">{{ area_label }}</span>
|
|
{%- for h in t.hosts %}{% if loop.first %} · {% endif %}<a href="/timeline/?host={{ h|urlencode }}">{{ h }}</a>{{ ", " if not loop.last }}{% endfor %}
|
|
{%- if t.subject %} · {{ t.subject }}{% endif %}
|
|
· opened by {{ t.opened_by }}, {{ t.opened_at|when }}
|
|
{%- if t.status == 'resolved' %} · resolved by {{ t.resolved_by }}, {{ t.resolved_at|when }}{% endif %}
|
|
</p>
|
|
|
|
{% if error %}<div class="alert alert-error" role="alert">{{ error }}</div>{% endif %}
|
|
|
|
{% if t.status == 'resolved' %}
|
|
<section class="card" aria-labelledby="tk-resolution">
|
|
<h2 id="tk-resolution" class="section-title">Resolution</h2>
|
|
<p style="white-space:pre-line;margin:0;">{{ t.resolution }}</p>
|
|
</section>
|
|
{% endif %}
|
|
|
|
<section class="card" aria-labelledby="tk-about">
|
|
<h2 id="tk-about" class="section-title">What is wrong</h2>
|
|
{% if t.body %}<p style="white-space:pre-line;margin:0;">{{ t.body }}</p>
|
|
{% else %}<p class="tone-muted" style="margin:0;">No description.</p>{% endif %}
|
|
{% if links %}
|
|
<h3 class="section-title" style="margin-top:1rem;">Linked</h3>
|
|
<ul class="check-list">
|
|
{% for l in links %}<li>{% if l.url %}<a href="{{ l.url }}">{{ l.label }}</a>{% else %}<span class="tone-dim">{{ l.label }}</span>{% endif %}</li>{% endfor %}
|
|
</ul>
|
|
{% endif %}
|
|
</section>
|
|
|
|
<section class="card" aria-labelledby="tk-log">
|
|
<h2 id="tk-log" class="section-title">Log ({{ tz_label() }})</h2>
|
|
{% set said = {"opened": "opened it", "comment": "commented", "edited": "edited", "resolved": "resolved it", "reopened": "reopened it"} %}
|
|
<table class="table">
|
|
<tbody>
|
|
{% for n in log %}
|
|
<tr>
|
|
<td class="nowrap" style="width:1%;">{{ n.at|when }}</td>
|
|
<td>
|
|
<strong>{{ n.actor }}</strong> {{ said.get(n.kind, n.kind) }}{% if n.kind == "edited" and n.body %}: {{ n.body }}{% endif %}
|
|
{%- if n.body and n.kind != "edited" %}<div style="white-space:pre-line;">{{ n.body }}</div>{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
|
|
{% if can_edit %}
|
|
<form method="post" action="/tickets/{{ t.id }}/comment" style="margin-top:1rem;">{{ csrf_field() }}
|
|
<div class="field">
|
|
<label class="field-label" for="tk-text">{{ "Comment, or what resolved it" if t.status == 'open' else "Comment, or why it is back" }}</label>
|
|
<textarea class="input" id="tk-text" name="text" rows="3" maxlength="8000">{{ text or "" }}</textarea>
|
|
</div>
|
|
<div class="form-actions">
|
|
<button type="submit" class="btn btn-ghost">Comment</button>
|
|
{% if t.status == 'open' %}
|
|
<button type="submit" class="btn" formaction="/tickets/{{ t.id }}/resolve">Resolve</button>
|
|
{% else %}
|
|
<button type="submit" class="btn" formaction="/tickets/{{ t.id }}/reopen">Reopen</button>
|
|
{% endif %}
|
|
</div>
|
|
</form>
|
|
{% endif %}
|
|
</section>
|
|
{% endblock %}
|