diff --git a/CHANGELOG.md b/CHANGELOG.md index 735aad0..609b348 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version ### Changed +- panel: the domain detail page is wide with paired cards (DKIM ‖ DNS status; + SPF ‖ DMARC; sending settings ‖ add application; rate limit ‖ export). + Applications and Danger stay full-width; SPF and DMARC are separate cards; + section blurbs are shorter. - panel: page URLs, browser titles, and headings are aligned — **Settings** is now `/settings` (legacy `/account` redirects with 308); the domains list title is `SelfPost — domains`; Status, Users, and user create/edit titles match their diff --git a/internal/web/view/templates/domain_detail.html b/internal/web/view/templates/domain_detail.html index 0f894be..bd20a05 100644 --- a/internal/web/view/templates/domain_detail.html +++ b/internal/web/view/templates/domain_detail.html @@ -1,3 +1,7 @@ +{{/* Wide so the .split pairs fill the column rather than the 48rem reading + measure (same pattern as Status). */}} +{{define "wide"}}wide{{end}} + {{define "content"}}
This password is shown once only and is not - stored. Copy it now — if it is lost, regenerate a new one.
+Shown once only and not stored. Copy it now + — if it is lost, regenerate a new one.
Publish this TXT record in the DNS for {{.Domain.Name}}. - It is not a secret and can be viewed at any time.
+Publish this TXT for {{.Domain.Name}}. + It is not a secret.
- -Mail is signed with selector {{.Domain.DKIMSelector}}.
-These two are not generated the way the DKIM record above is —
- they are policy, and the domain may already publish an SPF record for other
- senders. SelfPost is a send-only relay — most operators have no inbox on the
- sending domain, so the suggested DMARC record omits rua= unless a
- report address is configured below or in Settings.
- Publish both as TXT records.
A domain may have only one SPF record. If it already has one,
- do not add a second — add this server's mechanism to the existing record
- instead, before its all term.
The report address is on this sending domain. SelfPost does - not receive inbound mail — use a mailbox elsewhere or wait for in-panel report - reception in a future release.
- {{end}} - - {{if .NeedsReportAuth}} - -p=none changes nothing about delivery. Omit
- rua= on a send-only domain, or point it at a mailbox that
- receives inbound mail. Tighten to p=quarantine and then
- p=reject once aggregate reports look clean.
What DNS publishes for {{.Domain.Name}} right - now, checked against the key this server signs with. Results are cached for a - few minutes — after publishing a record, use Re-check.
- - -{{.DNS.DKIM.Detail}}
- {{if .DNS.DKIM.Records}}{{range .DNS.DKIM.Records}}{{.}} -{{end}}{{end}} - - -{{.DNS.SPF.Detail}}
- {{if .DNS.SPF.Records}}{{range .DNS.SPF.Records}}{{.}} -{{end}}{{end}} -The SPF check is deliberately shallow: it looks for a
- mechanism that literally covers this server's address and does not follow
- include: or redirect=, so a record that authorises
- the server through an include is reported as “cannot tell”, not as a failure.
{{.DNS.DMARC.Detail}}
- {{if .DNS.DMARC.Records}}{{range .DNS.DMARC.Records}}{{.}} -{{end}}{{end}} - - {{if .DNS.DMARCReportAuth.Status}} - -{{.DNS.DMARCReportAuth.Detail}}
- {{if .DNS.DMARCReportAuth.Records}}{{range .DNS.DMARCReportAuth.Records}}{{.}} -{{end}}{{end}} - {{end}} - - -Point the mail client or script at these settings and - authenticate with an application login and password from the - Applications section below. They are the same for every - domain on this server.
- - -Authentication is required on every port. The username is the - application's login (see the table below) and the password is the one shown - once when that application was created or its password regenerated — if it was - lost, generate a new one.
-Suggested TXT. If the domain already has SPF, merge this + server's mechanism into it — do not add a second record.
+ + +Policy TXT. SelfPost is send-only — omit rua=
+ or point it at a mailbox elsewhere (Settings default
+ or custom below).
The report address is on this sending domain. SelfPost does + not receive inbound mail — use a mailbox elsewhere or wait for in-panel report + reception in a future release.
+ {{end}} + + {{if .NeedsReportAuth}} + +p=none does not affect delivery. Tighten to
+ p=quarantine then p=reject once reports look clean.
Same for every domain. Authenticate with an application + login from below.
+ + +Auth required on every port. The password is shown once at + create or regenerate.
+{{.Error}}
{{end}} + +Password shown once. Login unique across domains; letters, + digits, '.', '-' and '_'.
+Each application is a SASL login/password an app or script - uses to send mail as this domain. A login may send from any address of the - domain (wildcard) or only from a fixed list of addresses.
+SASL logins for this domain — wildcard (*@domain) or a fixed + address list.
{{if .Apps}}Optional level-2 limit (spec 7.4): cap how many messages this - domain may send from its expected client IP(s) within a time window, summed - across all its applications. It counts messages — one message to many - recipients counts once. Leave the IP list empty to disable it and rely only on - the global level-1 limit. Applications that send from changing IPs should be - left unbound here.
+Optional level-2 cap across this domain's applications from + the listed client IPs. Empty IP list = inactive (level-1 only).
-Status: - {{if .DomainHasRL}}active{{else}}inactive (level-1 only){{end}}.
+Status: + {{if .DomainHasRL}}active{{else}}inactive (level-1 only){{end}}.
-Download this domain to move it to another SelfPost instance: - its DKIM key, selector and every application with its working password. On - import the DNS record stays the same, so no DNS change is needed.
-The export file is a secret — it contains the
- private DKIM key and application passwords. Transfer it securely and delete it
- after the import, or encrypt it below and move a .spde file
- (SelfPost domain export) instead; the import form asks for the password.
{{.ExportErr}}
{{end}} -DKIM key, selector and application passwords for another + SelfPost instance. DNS stays the same on import.
+Secret file — transfer securely, or encrypt
+ below as .spde.
{{.ExportErr}}
{{end}} +Deleting this domain also deletes its DKIM key and every - application bound to it.
+Deletes the DKIM key and every application on this domain.
Delete domainOn this page
{{if .NewCred}}New application password{{end}} DKIM DNS record - SPF and DMARC records DNS status + SPF record + DMARC record Sending server settings Add an application Applications diff --git a/internal/web/view/templates_test.go b/internal/web/view/templates_test.go index 4006885..51d69a4 100644 --- a/internal/web/view/templates_test.go +++ b/internal/web/view/templates_test.go @@ -264,7 +264,7 @@ func TestOnlyThePagesMadeOfDataDeclareThemselvesWide(t *testing.T) { if err != nil { t.Fatalf("New: %v", err) } - wide := map[string]bool{"settings": true, "deliveries": true, "delivery": true, "mail_queue": true, "status": true, "system_log": true} + wide := map[string]bool{"settings": true, "deliveries": true, "delivery": true, "mail_queue": true, "status": true, "system_log": true, "domain_detail": true} for name, page := range engine.Pages() { var buf bytes.Buffer if err := page.ExecuteTemplate(&buf, "wide", nil); err != nil { @@ -280,6 +280,32 @@ func TestOnlyThePagesMadeOfDataDeclareThemselvesWide(t *testing.T) { } } +// The domain page pairs cards the same way Status does: four .split rows +// (DKIM|DNS, SPF|DMARC, settings|add-app, rate-limit|export) with Applications +// and Danger full-width. Losing a row silently stacks the page again. +func TestDomainDetailPageHasPairedCards(t *testing.T) { + body, err := fs.ReadFile(assetsFS, "templates/domain_detail.html") + if err != nil { + t.Fatalf("read domain_detail: %v", err) + } + src := string(body) + if got := strings.Count(src, `class="split"`); got != 4 { + t.Errorf("domain detail has %d .split rows, want 4", got) + } + for _, id := range []string{ + `id="dkim"`, `id="dns-status"`, `id="spf"`, `id="dmarc"`, + `id="settings"`, `id="add-application"`, `id="applications"`, + `id="rate-limit"`, `id="export"`, `id="danger"`, + } { + if !strings.Contains(src, id) { + t.Errorf("domain detail is missing %s", id) + } + } + if strings.Contains(src, `id="spf-dmarc"`) { + t.Error("domain detail still has the combined spf-dmarc card; SPF and DMARC are separate") + } +} + // Drill-down pages carry an up-link directly under the heading and above the // cards. A link at the bottom of a form is easy to miss and drifts from the // rest of the panel, so the shared back_link template is mandatory on those