Files
selfpost/internal/web/view/templates/delivery.html
T
mix 60e08e802e release: 1.2.0: Panel 1.2.0-1.2.5: layout, RBAC, tests, GUI and CI fixes
[1.2.0]
The second MINOR after 1.0.0: domain administrators with per-domain scope, a panel visual refresh on the SelfPost palette, and refinements to navigation and the send log. Upgrading runs one SQLite migration (`0005_panel_users`); the single administrator becomes a global user.
Added:
  - panel: **domain-admin role** — global administrators manage panel users and
Fixed:
  - Docker build: `LICENSE` is no longer excluded by `.dockerignore`, so the
  - panel: on signed-in pages with a narrow card (**Settings**, the user form)
Changed:
  - panel: navigation session icons are distinct — **Settings** uses a gear,
  - panel: `/` redirects domain administrators to `/domains`; global users still
  - panel: **Sign out** in the navigation column uses the same type size and
  - panel: **visual style** brought in line with the SelfPost mark — brick accent
  - panel: the send log's **status is a badge**, in the same ok/warn/error/unknown
  … (1 more in CHANGELOG)
[1.2.1]
Panel refinements after 1.2.0: navigation icons, status-badge centreing, drill-down back-link placement, and safeguards for the sole global administrator. Upgrading is a tag bump; no migration.
Fixed:
  - panel: status badge text sat low in the box (and below the heading or label
  - panel: the **Users** navigation icon was two full silhouettes with staggered
  - panel: the **Settings** navigation icon was a sun-with-rays (circle plus
  - panel: the user create/edit form placed «Back to users» at the bottom of the
Changed:
  - panel: the user edit form disables role change and delete for the only global
  - panel: the user create/edit form hides **Assigned domains** when the role is
  - panel: **Settings** shows panel credentials and DMARC aggregate reports side
[1.2.2]
Status page layout after 1.2.1: paired cards in a wide column, denser machine and check copy, and a user-form checkbox fix. Upgrading is a tag bump; no migration.
Changed:
  - panel: **Status** is wide again so paired cards fill the column. Layout:
Fixed:
  - panel: cards inside `.split` used `margin: 0 auto`, which in a CSS grid
  - panel: on the user create/edit form, **Assigned domains** checkboxes stacked
[1.2.3]
Domain detail layout and panel polish after 1.2.2. Upgrading is a tag bump; no migration.
Changed:
  - panel: the domain detail page is wide with paired cards (DKIM+SPF ‖ DMARC;
  - panel: Domains list — **Add domain** sits beside the domain field; the
  - panel: page URLs, browser titles, and headings are aligned — **Settings** is
Fixed:
  - image: `mail.log` rotation no longer silently stops when the build context
[1.2.4]
Level-2 rate-limit semantics inverted after 1.2.3, plus a small DNS field height fix. Upgrading is a tag bump; no migration.
Changed:
  - rate limiting (level 2): domain ceilings apply to every client IP (no IP
Fixed:
  - panel: on the domain DNS status grid, the Type (TXT) field height matches
[1.2.5]
Rate-limit form polish after 1.2.4. Upgrading is a tag bump; no migration.
Changed:
  - panel: rate-limit forms refined — level-1 backstop as muted copy on the

Squashed CHANGELOG sections: [1.2.0], [1.2.1], [1.2.2], [1.2.3], [1.2.4], [1.2.5].

Full detail: CHANGELOG.md (1.2.0, 1.2.1, 1.2.2, 1.2.3, 1.2.4, 1.2.5).
2026-09-22 15:24:52 +03:00

125 lines
5.3 KiB
HTML

{{/* One message's page. It takes the whole column rather than the reading
measure (see the "wide" block in layout.html): the two cards below stand
side by side, and under them are raw mail.log lines, which are long by
nature and were the reason the send log opted out too. */}}
{{define "wide"}}wide{{end}}
{{define "content"}}
{{/* The subject is the page's name — it is what the message was about, and
what an operator arriving from the log is holding in their head. The route
under it is the other half of the identity: who it was from and who it was
for, with the outcome badged on the end so the answer is on the first line
of the page rather than somewhere in the cards below. */}}
<h1 class="subject">{{if .Row.Subject}}{{.Row.Subject}}{{else}}(no subject){{end}}</h1>
<p class="route">
<span class="addr">{{.Row.From}}</span>
<span class="arrow" aria-hidden="true">&rarr;</span>
<span class="addr">{{if .Row.To}}{{.Row.To}}{{else}}—{{end}}</span>
<span class="st st-{{.Level}}">{{.Row.Status}}</span>
</p>
{{template "back_link" (back .BackURL "Back to deliveries")}}
{{/* The two columns: what was recorded on the left, in what order it happened
on the right. They are a pair — the facts are only worth reading against
the history and the history only means anything for a known message — so
they are read together rather than one after the other (.split in
panel.css lays them down into one column when there is no room). */}}
<div class="split">
<div class="card" id="message">
<h2>Message</h2>
<p class="muted">What the journal recorded as Postfix accepted this
message. The delivery log lists only what identifies a row at a glance;
everything the table has no column for is here.</p>
<div class="facts">
<div class="fact">
<span class="fact-label">Domain</span>
<span class="fact-value">{{if .Row.Domain}}{{.Row.Domain}}{{else}}—{{end}}</span>
</div>
<div class="fact">
<span class="fact-label">Application</span>
<span class="fact-value">{{if .Row.AppLogin}}{{.Row.AppLogin}}{{else}}—{{end}}</span>
</div>
<div class="fact">
<span class="fact-label">Accepted</span>
<span class="fact-value">{{.Row.CreatedAt.Format "2006-01-02 15:04:05"}} UTC</span>
</div>
<div class="fact">
<span class="fact-label">Status reported</span>
<span class="fact-value">{{.Row.UpdatedAt.Format "2006-01-02 15:04:05"}} UTC</span>
</div>
{{/* The queue id is monospace because it is an identifier to compare
character by character against a log line, not a word to read. */}}
<div class="fact">
<span class="fact-label">Queue id</span>
<span class="fact-value mono">{{if .Row.QueueID}}{{.Row.QueueID}}{{else}}—{{end}}</span>
</div>
<div class="fact">
<span class="fact-label">Journal id</span>
<span class="fact-value mono">{{.Row.ID}}</span>
</div>
</div>
{{if not .Row.QueueID}}
<p class="muted">A message with no queue id was refused before Postfix
queued it — under a level-2 rate limit — so it has no delivery attempt to
show.</p>
{{end}}
</div>
<div class="card" id="history">
<h2>History</h2>
<p class="muted">A row is written when the message is accepted and updated
once when Postfix reports the attempt for this recipient, so these are the
two moments the journal knows about — not a trace of the SMTP conversation,
which is in the delivery log below.</p>
<ol class="timeline">
{{range .Events}}
<li class="event lvl-{{.Level}}{{if .At.IsZero}} pending{{end}}">
<p class="event-time">{{if .At.IsZero}}not yet{{else}}{{.At.Format "2006-01-02 15:04:05"}} UTC{{end}}</p>
<p class="event-title"><span class="st st-{{.Level}}">{{.Status}}</span> {{.Title}}</p>
<p class="event-detail muted">{{.Detail}}</p>
</li>
{{end}}
</ol>
</div>
</div>
{{/* Under both, at the column's full width: what Postfix itself wrote. The
queue id used to be printed on this page as something to go and search the
system log for by hand — this is that search, already done. */}}
<div class="card" id="delivery-log">
<h2>Delivery log</h2>
<p class="muted">The lines Postfix wrote about this message in
<code>mail.log</code>, oldest first — the connection to the receiving server,
its reply, and the status that reply was filed as. Only lines carrying this
message's queue id are shown; the whole log is on the
<a href="/system-log">System log</a> page.</p>
{{if .LogRows}}
{{/* Two columns, the way the send log itself is a table: the times down one
edge are what the message's pace is read off — the seconds between the
connection and the reply — and lining them up is what makes that
readable. A line whose head was not a timestamp keeps its whole text in
the second column and leaves the first empty. */}}
<table class="log">
<thead>
<tr><th>Time</th><th>Message</th></tr>
</thead>
<tbody>
{{range .LogRows}}
<tr>
<td class="time muted">{{if .Time}}{{.Time}}{{else}}—{{end}}</td>
<td class="log-text">{{.Text}}</td>
</tr>
{{end}}
</tbody>
</table>
{{else}}
<p class="muted">{{.LogNote}}</p>
{{end}}
</div>
{{end}}