#5509 "Delivery you can see: each alert's email state in history, and outgoing email on Settings -> System" (milestone 501). - Alerts -> History: an alert's email reads from the outbox (#5506): "gathering" with when it sends, "sending", "retrying" with the attempt, next try and the server's last answer, "sent", or "failed" naming each reader it didn't reach and why (outbox.delivery / email_states). Events from before the outbox keep what they recorded. The Notified column shows once an alert has a queued email. - Settings -> System gets "Outgoing email": every email of the last 7 days not yet sent, failures first, then the longest retrying, with recipient, kind, state and last error, and a count of what was sent. Failures and anything still retrying after 5 minutes lead the page's verdict (outbox.outgoing). - get_alert_history (MCP) adds `email`: the same delivery state. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
130 lines
6.2 KiB
HTML
130 lines
6.2 KiB
HTML
{# settings/system.html — Steward's own health (#5467): a verdict, then each of
|
||
its process metrics now and over the last day, from the self-monitor tick.
|
||
Each is a metric an alert rule can watch under "Steward itself". #}
|
||
{% extends "base.html" %}
|
||
{% from "_macros.html" import crumbs %}
|
||
{% from "settings/_tabs.html" import settings_tabs %}
|
||
{% block title %}Settings — System — Steward{% endblock %}
|
||
{% block section_tabs %}{{ settings_tabs("system") }}{% endblock %}
|
||
{% block breadcrumb %}{{ crumbs([("Settings", "/settings/"), ("System", "")]) }}{% endblock %}
|
||
{% block content %}
|
||
<p class="page-verdict">
|
||
{%- if problems %}{% set said = problems|join("; ") %}<span class="tone-warn">{{ said[:1]|upper }}{{ said[1:] }}.</span>
|
||
{%- elif not seen %}<span class="tone-dim">No readings yet: the first arrive within a minute of Steward starting.</span>
|
||
{%- else %}<span class="tone-ok">Steward is running normally.</span>{% endif -%}
|
||
</p>
|
||
|
||
<h2 class="section-title" style="margin:1.5rem 0 1rem;">Steward itself</h2>
|
||
<div class="card-flush">
|
||
<table class="table">
|
||
<thead><tr><th>Measure</th><th>Now</th><th>Last 24 h</th></tr></thead>
|
||
<tbody>
|
||
{% for r in rows %}
|
||
<tr>
|
||
<td class="cell-name"><span title="{{ metric_label(r.metric) }}">{{ metric_label(r.metric) }}</span>
|
||
<div class="cell-sub" title="{{ metric_about(r.metric) }}">{{ metric_about(r.metric) }}</div></td>
|
||
<td class="nowrap">{{ metric_value(r.metric, r.now) }}
|
||
{%- if r.at %}<div class="cell-sub">{{ r.at|ago }}</div>{% endif %}</td>
|
||
<td class="nowrap">
|
||
{%- if r.low is not none %}{{ metric_value(r.metric, r.low) }} – {{ metric_value(r.metric, r.high) }}
|
||
<span class="metric-preview-spark" aria-hidden="true">{{ r.spark|safe }}</span>
|
||
{%- else %}<span class="tone-dim">—</span>{% endif -%}
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<p class="field-hint" style="margin-top:0.75rem;">
|
||
Recorded each minute (the cleanup each hour). Any of these can be watched by an
|
||
<a href="/alerts/rules/new">alert rule</a>: pick the source "Steward itself".</p>
|
||
|
||
{# Outgoing email (#5509): every email waits in the outbox until the mail
|
||
server takes it. What hasn't gone yet, failures first, then the longest
|
||
retrying; sent mail is only counted. #}
|
||
<h2 class="section-title" style="margin:2rem 0 0.5rem;">Outgoing email</h2>
|
||
<p style="margin:0 0 1rem;">
|
||
{%- if not email.rows %}Nothing waiting to send. {% endif -%}
|
||
{{ "{:,}".format(email.sent) }} sent in the last 7 days.</p>
|
||
{% if email.rows %}
|
||
<div class="card-flush">
|
||
<table class="table">
|
||
<thead><tr><th>To</th><th>What</th><th>State</th><th>Queued</th></tr></thead>
|
||
<tbody>
|
||
{% for r in email.rows %}
|
||
<tr>
|
||
<td class="cell-name"><span title="{{ r.recipient }}">{{ r.recipient }}</span></td>
|
||
<td class="nowrap">{{ kind_labels.get(r.kind, r.kind) }}</td>
|
||
<td>
|
||
{%- if r.state == "failed" %}<span class="badge badge-red">failed</span>
|
||
{%- elif r.state == "pending" and r.attempts %}<span class="badge badge-yellow">retrying</span>
|
||
<span class="tone-dim">attempt {{ r.attempts }}{% if r.next_attempt_at %}, next {{ r.next_attempt_at|when }}{% endif %}</span>
|
||
{%- elif r.state == "held" %}<span class="badge badge-yellow">gathering</span>
|
||
<span class="tone-dim">sends {{ r.send_after|when }}</span>
|
||
{%- else %}<span class="badge badge-yellow">sending</span>{% endif %}
|
||
{%- if r.last_error %}<div class="cell-note" style="max-width:32rem;overflow-wrap:anywhere;">{{ r.last_error }}</div>{% endif -%}
|
||
</td>
|
||
<td class="nowrap">{{ r.created_at|ago }}</td>
|
||
</tr>
|
||
{% endfor %}
|
||
{% if email.more %}
|
||
<tr><td colspan="4" class="tone-dim">+{{ email.more }} more not yet sent</td></tr>
|
||
{% endif %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{% endif %}
|
||
<p class="field-hint" style="margin-top:0.75rem;">
|
||
An email the server refuses is failed at once; one it can't take yet is tried
|
||
again for up to an hour. The mail server is set under
|
||
<a href="/settings/notifications/">Notifications</a>.</p>
|
||
|
||
{# Storage (#5469): Postgres's own figures for each table, then what the
|
||
hourly cleanup last removed. Read from the catalog, never by counting. #}
|
||
<h2 class="section-title" style="margin:2rem 0 0.5rem;">Storage</h2>
|
||
<p class="field-hint" style="margin:0 0 1rem;">The database takes {{ storage.total|bytes }} on disk.</p>
|
||
<div class="card-flush">
|
||
<table class="table">
|
||
<thead><tr><th>Table</th><th class="num">On disk</th><th class="num">Rows (estimate)</th></tr></thead>
|
||
<tbody>
|
||
{% for t in storage.tables %}
|
||
<tr>
|
||
<td class="cell-name"><span title="{{ t.name }}">{{ t.name }}</span></td>
|
||
<td class="num nowrap">{{ t.bytes|bytes }}</td>
|
||
<td class="num nowrap">{% if t.rows is not none %}{{ "{:,}".format(t.rows) }}{% else %}—{% endif %}</td>
|
||
</tr>
|
||
{% endfor %}
|
||
{% if storage.rest.count %}
|
||
<tr><td class="tone-dim">+{{ storage.rest.count }} smaller tables</td>
|
||
<td class="num nowrap tone-dim">{{ storage.rest.bytes|bytes }}</td><td></td></tr>
|
||
{% endif %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<h2 class="section-title" style="margin:2rem 0 0.5rem;">Last cleanup</h2>
|
||
{% if not cleanup %}
|
||
<p class="tone-dim">None has finished since this version started: the cleanup runs each hour.</p>
|
||
{% else %}
|
||
<p style="margin:0 0 1rem;">Ran {{ cleanup.at|ago }} and took {{ cleanup.secs|duration }}.
|
||
{%- if not cleanup.removed %} Nothing was past its window ({{ cleanup.passes }} passes).{% endif %}</p>
|
||
{% if cleanup.removed %}
|
||
<div class="card-flush">
|
||
<table class="table">
|
||
<thead><tr><th>What went</th><th class="num">Rows</th></tr></thead>
|
||
<tbody>
|
||
{% for what, n in cleanup.removed %}
|
||
<tr><td class="cell-name"><span title="{{ what }}">{{ what }}</span></td>
|
||
<td class="num nowrap">{{ "{:,}".format(n) }}</td></tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{% endif %}
|
||
{% endif %}
|
||
<p class="field-hint" style="margin-top:0.75rem;">
|
||
How long each kind of data is kept is set under
|
||
<a href="/settings/general/">General</a> (data and audit log) and
|
||
<a href="/settings/thresholds/">Thresholds & retention</a> (metrics, Docker and logs).</p>
|
||
{% endblock %}
|