test / test (push) Canceled after 0s
All 2026-10-15; each written beside the code or text it settles. - A-43: /usr/sbin/postlog is -rwxr-xr-x root:root in the image (postfix 3.7.11-0+deb12u1; dpkg-statoverride lists postdrop and postqueue only): Debian does not install it set-gid, so it is no use to dmarc-ingest (cmd/panel/dmarc_ingest.go). - C-X1: OpenSSL 3.0.22 in the image prints the same public key from `x509 -pubkey` and `pkey -pubout` for RSA-2048, P-256, P-384 and a SEC1 P-256 key - cert-reload's same_pair holds for certbot's ECDSA keys. - C-K5, D-guide:933/:233: on the live server (Docker 29.6.1, userland proxy on 0.0.0.0 and ::, no IPv6 on the network) an outside IPv4 client and the host's own public IPv4 address are logged as themselves; the host's IPv6 address and 127.0.0.1 arrive as 172.18.0.1. Over IPv6 every client is the gateway: one level-1 budget, an application IP list cannot name them, rspamd sees the gateway. Guide paragraph under level 1, notes at the milter and allow-list, comments at the anvil setup and authip.go, and a Security entry in the CHANGELOG (an allow-list naming the gateway would admit every IPv6 client). - D-guide:61: a published port nothing listens on (a throwaway container, -p 20999:25) is refused from outside over IPv4 and from the host's public IPv4, accepted and closed through 127.0.0.1 - Docker's proxy, which IPv6 goes through too (not probed over IPv6 for this). - C-M8: the live installation has received one DMARC report, of one record: nothing to set maxReportRecords by yet - said at the constant. - D-S7, D-arch:516, D-arch:532: a new database filled through the store to 120 days at 1 900 messages a day (228 000 messages, 1.37 million lines, 464 MB) on the test server (2 cores): the longest write is the line prune after a 30-day gap, 1.9 s for 342 000 lines (0.4 s with nothing to delete); the send-log delete 0.6 s; 2 000 lines of a tailer pass 18 ms; VACUUM INTO 1.7 s; a second connection writing every 50 ms waited 1.8 s at most, no SQLITE_BUSY - inside busy_timeout's 5 s. architecture.md's "about a second per million lines" was five times too fast; its "six to ten lines of 200 bytes" is 6.0 lines of 146 bytes on the live data. - D-B16: which favicon is fetched, by the probe server's log: Edge 155 the SVG, the desktop app's embedded Chromium 152 the PNG, with or without sizes="any" - layout.html's "vector wins where supported, raster catches the rest" holds; Firefox and Safari not measured. - D-arch:121: the allow/deny trial again, in rspamd/rspamd:3.11.1-3 with today's multimap.conf: every mix of allow and deny entries matching one message ended reject; a changed map was used about 10 s later. - D-sec:424: rspamd runs as _rspamd, uid 11333; the panel is uid 999. Stand: go vet and go test ./... pass on the server (24 packages); make e2e exit 0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
159 lines
11 KiB
HTML
159 lines
11 KiB
HTML
{{/* The shell of every redesigned page (docs/plans/panel-redesign.md § Three
|
|
layers): a brick navbar with the wordmark and hover menus, the stamp's
|
|
perforated edge, the strip of sibling pages, the page, the footer. It is
|
|
built exactly like the mockups' build.py builds it, and it is the only
|
|
place besides components.html where sp- classes are written. A page never
|
|
writes any of it.
|
|
|
|
layout.html is executed with a Frame (components.go): .Shell is what the
|
|
shell shows, .Page is the page's own data, handed to the page's "content"
|
|
template. Which entries are visible comes from .Shell (Engine.shell derives
|
|
it from the role, the feature flags and the user's reach); the paths are
|
|
written out here, so the menu can be read, and checked against the router
|
|
(guard_routes_test.go), without running anything. */}}
|
|
|
|
{{/* layout_head is the head of both documents; the stylesheets, in this order,
|
|
and the two scripts are all a page ever loads. */}}
|
|
{{define "layout_head"}}<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>{{.Title}} · SelfPost</title>
|
|
{{/* The tab icon is the stamp's small-size variant — the initials rather than
|
|
the two-line wordmark, which stops resolving below 32px. The PNG is declared
|
|
first and the SVG second: a browser takes the last icon whose type it
|
|
understands, so vector wins wherever it is supported and the raster catches
|
|
the rest. HTML links.html#rel-icon leaves "most appropriate" to the
|
|
browser, so measured, 2026-10-15, by which file was requested: Edge 155
|
|
(Chromium) takes the SVG, the Claude desktop app's embedded browser
|
|
(Chromium 152) the PNG, and adding sizes="any" to the SVG changes
|
|
neither. Firefox and Safari not measured (assumptions D-B16). */}}
|
|
<link rel="icon" href="/static/favicon.png" sizes="32x32">
|
|
<link rel="icon" href="/static/favicon.svg" type="image/svg+xml">
|
|
<link rel="stylesheet" href="/static/bulma.min.css">
|
|
<link rel="stylesheet" href="/static/tabler-icons.css">
|
|
<link rel="stylesheet" href="/static/panel.css">
|
|
{{/* Unless told otherwise, htmx injects a stylesheet element of its own into
|
|
the head for the request-indicator classes. The panel uses no
|
|
hx-indicator, and that injected element would be the one thing the
|
|
Content-Security-Policy has to make an exception for, so it
|
|
is switched off here. htmx 2.0.4, src/htmx.js: the ready handler runs
|
|
mergeMetaConfig() before insertIndicatorStyles(), which adds the element
|
|
only while includeIndicatorStyles is not false. Both scripts are defer:
|
|
they run in this order once parsing ends and before DOMContentLoaded
|
|
(HTML Living Standard, parsing.html#the-end). */}}
|
|
<meta name="htmx-config" content='{"includeIndicatorStyles":false}'>
|
|
<script src="/static/htmx.min.js" defer></script>
|
|
<script src="/static/panel.js" defer></script>
|
|
</head>{{end}}
|
|
|
|
{{/* layout.html is the signed-in document. Engine.Render uses
|
|
layout_signed_out instead when there is no user. */}}
|
|
{{define "layout.html"}}<!DOCTYPE html>
|
|
<html lang="en" data-theme="light">
|
|
{{template "layout_head" .Shell}}
|
|
<body>
|
|
{{template "nav" .Shell}}
|
|
{{template "subnav" .Shell}}
|
|
<main>
|
|
<div class="container sp-page">
|
|
{{template "content" .Page}}
|
|
{{/* Appropriate Legal Notices (AGPL-3.0): copyright, how to read the
|
|
licence, where the Corresponding Source is, and that there is no
|
|
warranty. The running version is shown signed in only: it is what a
|
|
backup manifest is checked against on restore, and must not be
|
|
advertised to the internet. */}}
|
|
<footer class="sp-foot">SelfPost {{.Shell.Version}} · {{.Shell.Copyright}} · <a href="/license">License (AGPL-3.0)</a> · <a href="{{.Shell.SourceURL}}">Source</a> · No warranty</footer>
|
|
</div>
|
|
</main>
|
|
</body>
|
|
</html>{{end}}
|
|
|
|
{{/* layout_signed_out is the split brand / form screen of sign-in and setup:
|
|
the stamp on the brick, the page's own form beside it, and the legal
|
|
notice without the version. */}}
|
|
{{define "layout_signed_out"}}<!DOCTYPE html>
|
|
<html lang="en" data-theme="light">
|
|
{{template "layout_head" .Shell}}
|
|
<body>
|
|
<div class="columns is-gapless sp-signin">
|
|
<div class="column is-half sp-signin-brand">
|
|
<div>
|
|
<img src="/static/logo.svg" alt="SelfPost">
|
|
<p>YOUR SMTP. YOUR RULES.</p>
|
|
</div>
|
|
</div>
|
|
<div class="column is-half sp-signin-form">
|
|
{{template "content" .Page}}
|
|
<p class="sp-foot">{{.Shell.Copyright}} · <a href="/license">License (AGPL-3.0)</a> · <a href="{{.Shell.SourceURL}}">Source</a> · No warranty</p>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>{{end}}
|
|
|
|
{{/* nav is the navbar and the perforated edge under it (input: Shell). A group
|
|
with nothing in it for this user is absent; the group of the current page
|
|
carries sp-current and its current entry is-active. */}}
|
|
{{define "nav"}}<nav class="navbar is-primary" aria-label="main navigation">
|
|
<div class="container">
|
|
<div class="navbar-brand"><a class="navbar-item" href="{{.Home}}"><img src="/static/wordmark.svg" alt="SelfPost"></a></div>
|
|
<div class="navbar-menu is-active">
|
|
<div class="navbar-start">
|
|
{{if .ShowOverview}} <a class="navbar-item{{if eq .Section "overview"}} sp-current{{end}}" href="/overview">Overview</a>
|
|
{{end}}{{if .ShowOutbound}} <div class="navbar-item has-dropdown is-hoverable{{if eq .Section "outbound"}} sp-current{{end}}">
|
|
<a class="navbar-link" href="/outbound/domains">Outbound</a>
|
|
<div class="navbar-dropdown">
|
|
<a class="navbar-item{{if and (eq .Section "outbound") (eq .Page "domains")}} is-active{{end}}" href="/outbound/domains"><i class="ti ti-world-upload"></i>Domains</a>
|
|
<a class="navbar-item{{if and (eq .Section "outbound") (eq .Page "log")}} is-active{{end}}" href="/outbound/log"><i class="ti ti-list-details"></i>Log</a>
|
|
{{if .ShowQueue}} <a class="navbar-item{{if and (eq .Section "outbound") (eq .Page "queue")}} is-active{{end}}" href="/outbound/queue"><i class="ti ti-stack-2"></i>Queue</a>
|
|
{{end}}{{if .ShowDMARC}} <a class="navbar-item{{if and (eq .Section "outbound") (eq .Page "dmarc")}} is-active{{end}}" href="/outbound/dmarc"><i class="ti ti-report-analytics"></i>DMARC reports</a>
|
|
{{end}} </div>
|
|
</div>
|
|
{{end}}{{if .ShowInbound}} <div class="navbar-item has-dropdown is-hoverable{{if eq .Section "inbound"}} sp-current{{end}}">
|
|
<a class="navbar-link" href="/inbound/domains">Inbound</a>
|
|
<div class="navbar-dropdown">
|
|
<a class="navbar-item{{if and (eq .Section "inbound") (eq .Page "domains")}} is-active{{end}}" href="/inbound/domains"><i class="ti ti-world-download"></i>Domains</a>
|
|
<a class="navbar-item{{if and (eq .Section "inbound") (eq .Page "log")}} is-active{{end}}" href="/inbound/log"><i class="ti ti-list-details"></i>Log</a>
|
|
{{if .ShowFilterLists}} <a class="navbar-item{{if and (eq .Section "inbound") (eq .Page "filter-lists")}} is-active{{end}}" href="/inbound/filter-lists"><i class="ti ti-filter"></i>Filter lists</a>
|
|
{{end}} </div>
|
|
</div>
|
|
{{end}}{{if .ShowServer}} <div class="navbar-item has-dropdown is-hoverable{{if eq .Section "server"}} sp-current{{end}}">
|
|
<a class="navbar-link" href="/server/health">Server</a>
|
|
<div class="navbar-dropdown">
|
|
<a class="navbar-item{{if and (eq .Section "server") (eq .Page "health")}} is-active{{end}}" href="/server/health"><i class="ti ti-heartbeat"></i>Health</a>
|
|
<a class="navbar-item{{if and (eq .Section "server") (eq .Page "log")}} is-active{{end}}" href="/server/log"><i class="ti ti-file-text"></i>System log</a>
|
|
<a class="navbar-item{{if and (eq .Section "server") (eq .Page "backup")}} is-active{{end}}" href="/server/backup"><i class="ti ti-archive"></i>Backup</a>
|
|
<a class="navbar-item{{if and (eq .Section "server") (eq .Page "users")}} is-active{{end}}" href="/server/users"><i class="ti ti-users"></i>Users</a>
|
|
<a class="navbar-item{{if and (eq .Section "server") (eq .Page "settings")}} is-active{{end}}" href="/server/settings"><i class="ti ti-settings"></i>Settings</a>
|
|
</div>
|
|
</div>
|
|
{{end}} </div>
|
|
<div class="navbar-end">
|
|
<div class="navbar-item has-dropdown is-hoverable{{if eq .Section "user"}} sp-current{{end}}">
|
|
<a class="navbar-link" href="/account"><i class="ti ti-user-circle"></i>{{.User}}</a>
|
|
<div class="navbar-dropdown is-right">
|
|
<a class="navbar-item" href="/account"><i class="ti ti-key"></i>Account</a>
|
|
<a class="navbar-item" href="/help"><i class="ti ti-help"></i>Help</a>
|
|
<hr class="navbar-divider">
|
|
{{/* Signing out is a POST (the handler answers nothing else), so it
|
|
stays a form that works without scripts. The submit button
|
|
carries navbar-item: Bulma resets a button to plain text, so
|
|
it takes the dropdown's entry styling like the links around
|
|
it, and panel.css hands it the hover tint Bulma gives to
|
|
a.navbar-item alone. */}}
|
|
<form method="post" action="/logout"><button type="submit" class="navbar-item"><i class="ti ti-logout"></i>Sign out</button></form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
<div class="sp-perf" aria-hidden="true"></div>{{end}}
|
|
|
|
{{/* subnav is the strip of sibling pages under the edge (input: Shell): the
|
|
pages of the current group, the current one marked. A page outside every
|
|
group (the user menu, the kit page's neighbours) has none. */}}
|
|
{{define "subnav"}}{{if and .ShowOutbound (eq .Section "outbound")}}<div class="sp-subnav"><div class="container"><a href="/outbound/domains"{{if eq .Page "domains"}} aria-current="page"{{end}}><i class="ti ti-world-upload"></i>Domains</a><a href="/outbound/log"{{if eq .Page "log"}} aria-current="page"{{end}}><i class="ti ti-list-details"></i>Log</a>{{if .ShowQueue}}<a href="/outbound/queue"{{if eq .Page "queue"}} aria-current="page"{{end}}><i class="ti ti-stack-2"></i>Queue</a>{{end}}{{if .ShowDMARC}}<a href="/outbound/dmarc"{{if eq .Page "dmarc"}} aria-current="page"{{end}}><i class="ti ti-report-analytics"></i>DMARC reports</a>{{end}}</div></div>
|
|
{{else if and .ShowInbound (eq .Section "inbound")}}<div class="sp-subnav"><div class="container"><a href="/inbound/domains"{{if eq .Page "domains"}} aria-current="page"{{end}}><i class="ti ti-world-download"></i>Domains</a><a href="/inbound/log"{{if eq .Page "log"}} aria-current="page"{{end}}><i class="ti ti-list-details"></i>Log</a>{{if .ShowFilterLists}}<a href="/inbound/filter-lists"{{if eq .Page "filter-lists"}} aria-current="page"{{end}}><i class="ti ti-filter"></i>Filter lists</a>{{end}}</div></div>
|
|
{{else if and .ShowServer (eq .Section "server")}}<div class="sp-subnav"><div class="container"><a href="/server/health"{{if eq .Page "health"}} aria-current="page"{{end}}><i class="ti ti-heartbeat"></i>Health</a><a href="/server/log"{{if eq .Page "log"}} aria-current="page"{{end}}><i class="ti ti-file-text"></i>System log</a><a href="/server/backup"{{if eq .Page "backup"}} aria-current="page"{{end}}><i class="ti ti-archive"></i>Backup</a><a href="/server/users"{{if eq .Page "users"}} aria-current="page"{{end}}><i class="ti ti-users"></i>Users</a><a href="/server/settings"{{if eq .Page "settings"}} aria-current="page"{{end}}><i class="ti ti-settings"></i>Settings</a></div></div>
|
|
{{end}}{{end}}
|