diff --git a/CHANGELOG.md b/CHANGELOG.md index 55c80b6..28ed089 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,17 +5,26 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version ## [Unreleased] +## [1.2.3] - 2026-08-12 + +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; - sending settings ‖ add application; export ‖ danger). DNS status, +- panel: the domain detail page is wide with paired cards (DKIM+SPF ‖ DMARC; + connection settings ‖ add application; export ‖ danger). DNS status, Applications and Domain settings are full-width. DNS status is two rows - (DKIM ‖ SPF, DMARC ‖ report authorization); Domain settings pairs DMARC - report mode with the domain rate limit; application Edit opens mode and - rate limit side by side; the custom rua address field is shown only for - Custom address. The in-nav “On this page” section index is removed (Status - already dropped it; the paired domain layout no longer needs it). Section - blurbs are shorter. + (DKIM ‖ SPF, DMARC ‖ report authorization) with Host ‖ Type (narrow TXT) + and a Value label when records are present. Domain settings pairs DMARC + report mode with the optional level-2 domain rate limit; application Edit + opens address mode and an optional level-2 application rate limit side by + side (with a note that domain level-2 and global level-1 still apply); the + custom `rua=` address field is shown only for Custom address. The in-nav + “On this page” section index is removed. Shorter blurbs; *Sending server + settings* renamed **Connection settings**. +- panel: Domains list — **Add domain** sits beside the domain field; the + lead blurb under the form is dropped. - 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/README.md b/README.md index 72b4066..a32780f 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ docker run --rm -d --name selfpost-try \ -e SELFPOST_HOSTNAME=mail.local.test \ -e PANEL_COOKIE_SECURE=false \ -v selfpost-try-data:/data \ - ghcr.io/mixeme/selfpost:1.2.2 + ghcr.io/mixeme/selfpost:1.2.3 ``` **Get the setup URL** (pick one): diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index 2826e96..7502ff5 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -22,7 +22,7 @@ services: selfpost: - image: ghcr.io/mixeme/selfpost:1.2.2 + image: ghcr.io/mixeme/selfpost:1.2.3 restart: unless-stopped environment: SELFPOST_HOSTNAME: "${SELFPOST_HOSTNAME:?set the mail/panel hostname, e.g. mail.example.com}" diff --git a/docs/development.md b/docs/development.md index 93f36b8..40bc595 100644 --- a/docs/development.md +++ b/docs/development.md @@ -141,7 +141,7 @@ Requires Go 1.26+ and `CGO_ENABLED=0`. ```sh make build # bin/panel, bin/selfpost-backup (VERSION=dev by default) -make build VERSION=1.2.2 +make build VERSION=1.2.3 ``` Or directly: diff --git a/docs/guide.md b/docs/guide.md index 17b0fe4..41a5320 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -381,7 +381,7 @@ but it can look like an open port in external scans. ## Fixed image tag `deploy/docker-compose.yml` pins an explicit version (`ghcr.io/mixeme/selfpost:X.Y.Z`), -deliberately never `:latest`. The current pin is `1.2.2`. Intermediate +deliberately never `:latest`. The current pin is `1.2.3`. Intermediate CHANGELOG sections (`0.2.0`…`0.6.0`) record development cuts from before that image was published. Pinning matters because of the backup version check above: the panel binary's embedded version and the image tag that produced it are the diff --git a/internal/web/view/static/panel.css b/internal/web/view/static/panel.css index 94f8e04..8eab861 100644 --- a/internal/web/view/static/panel.css +++ b/internal/web/view/static/panel.css @@ -508,8 +508,20 @@ meter { width: 5rem; height: 0.7rem; vertical-align: middle; margin-right: 0.4re beside them would otherwise win the width and leave "Memory" broken across two lines. */ .metric { white-space: nowrap; } +/* Copy sits at the top of the value — right for a long DKIM key. The row's + .code uses the compact button's vertical padding so a one-line value is the + same height as Copy and does not leave the button hanging under an empty + band of padding. */ .code-row { display: flex; align-items: flex-start; gap: 0.5rem; } -.code-row .code { flex: 1; min-width: 0; } +.code-row .code { + flex: 1; min-width: 0; margin-top: 0; + padding-top: 0.45rem; padding-bottom: 0.45rem; +} +/* A short field with its submit on the same row (Add domain). The button + keeps the card's usual filled look; its top margin is for stacked forms. */ +.input-row { display: flex; align-items: stretch; gap: 0.5rem; } +.input-row input { flex: 1; min-width: 0; } +.input-row button { margin-top: 0; flex: none; white-space: nowrap; } /* Two cards abreast, on the one page that has a pair of them worth reading together (a delivery's facts beside its history). auto-fit with a minimum @@ -557,6 +569,15 @@ meter { width: 5rem; height: 0.7rem; vertical-align: middle; margin-right: 0.4re } .field-pair > div { min-width: 0; } .field-pair label { margin-top: 0.45rem; } +/* Host / name beside a DNS Type that is always TXT — size the type column to + that token rather than giving it half the row. */ +.field-pair.host-type { + grid-template-columns: minmax(0, 1fr) auto; +} +.field-type { width: max-content; } +.field-type .code { + width: fit-content; min-width: 2.75rem; text-align: center; box-sizing: border-box; +} /* The subject heads a delivery's page, and it is the one heading in the panel whose text we do not control: it may be a hundred characters with nothing to break on. It wraps to as many lines as it needs (this is the page's name, not @@ -646,7 +667,7 @@ button.copy, .actions button, .actions > label.toggle, .actions a.danger, .nav b } button.copy:hover, .actions button:hover, .actions > label.toggle:hover, .actions a.danger:hover, .nav button:hover { background: var(--surface-bg-hover); } -button.copy { flex: none; margin-top: 0.3rem; } +button.copy { flex: none; } .actions button.danger, .actions a.danger, .nav button.danger { color: var(--danger-fg); background: var(--danger-bg); border-color: var(--danger-border); } diff --git a/internal/web/view/templates/dashboard.html b/internal/web/view/templates/dashboard.html index 510d95a..8e1f84f 100644 --- a/internal/web/view/templates/dashboard.html +++ b/internal/web/view/templates/dashboard.html @@ -8,14 +8,14 @@

Add a sending domain

- +
+ + +
{{if .Error}}

{{.Error}}

{{end}} -
-

A DKIM key is generated for the domain; you then publish the - shown DNS record. Adding a domain does not create an application.

{{end}} diff --git a/internal/web/view/templates/domain_detail.html b/internal/web/view/templates/domain_detail.html index 2322977..c44e519 100644 --- a/internal/web/view/templates/domain_detail.html +++ b/internal/web/view/templates/domain_detail.html @@ -37,39 +37,93 @@
-

{{.Record.Name}}

+
+
+ + {{.Record.Name}} +
+
+ + TXT +
+
+ {{if .DNS.DKIM.Records}} + + {{range .DNS.DKIM.Records}}{{.}} +{{end}} + {{end}} {{if ne .DNS.DKIM.Status "ok"}}

{{.DNS.DKIM.Detail}}

{{end}} - {{if .DNS.DKIM.Records}}{{range .DNS.DKIM.Records}}{{.}} -{{end}}{{end}}
-

{{.Domain.Name}}

+
+
+ + {{.Domain.Name}} +
+
+ + TXT +
+
+ {{if .DNS.SPF.Records}} + + {{range .DNS.SPF.Records}}{{.}} +{{end}} + {{end}} {{if ne .DNS.SPF.Status "ok"}}

{{.DNS.SPF.Detail}}

{{end}}

Shallow check: literal address only, no include: / redirect=.

- {{if .DNS.SPF.Records}}{{range .DNS.SPF.Records}}{{.}} -{{end}}{{end}}
-

{{.DNS.DMARC.Detail}}

- {{if .DNS.DMARC.Records}}{{range .DNS.DMARC.Records}}{{.}} -{{end}}{{end}} +
+
+ + {{.DMARCName}} +
+
+ + TXT +
+
+ {{if .DNS.DMARC.Records}} + + {{range .DNS.DMARC.Records}}{{.}} +{{end}} + {{end}} + {{if ne .DNS.DMARC.Status "ok"}} +

{{.DNS.DMARC.Detail}}

+ {{else}} +

{{.DNS.DMARC.Detail}}

+ {{end}}
{{if .DNS.DMARCReportAuth.Status}} +
+
+ + {{.ReportAuthName}} +
+
+ + TXT +
+
+ {{if .DNS.DMARCReportAuth.Records}} + + {{range .DNS.DMARCReportAuth.Records}}{{.}} +{{end}} + {{end}}

{{.DNS.DMARCReportAuth.Detail}}

- {{if .DNS.DMARCReportAuth.Records}}{{range .DNS.DMARCReportAuth.Records}}{{.}} -{{end}}{{end}} {{else}}

Not required (no external rua=).

@@ -83,18 +137,24 @@
-
-

DKIM record

+
+

DKIM and SPF records

- -
- {{.Record.Name}} - +

DKIM

+
+
+ +
+ {{.Record.Name}} + +
+
+
+ + TXT +
- - TXT -
{{.Record.Value}} @@ -103,21 +163,22 @@

Not a secret. Signed with selector {{.Domain.DKIMSelector}}.

-
- -
-

SPF and DMARC records

SPF

- -
- {{.Domain.Name}} - +
+
+ +
+ {{.Domain.Name}} + +
+
+
+ + TXT +
- - TXT -
{{.SPFExample}} @@ -126,17 +187,25 @@

Merge into an existing SPF if the domain already has one — do not publish a second record.

+
-

DMARC

- -
- {{.DMARCName}} - +
+

DMARC record

+ +
+
+ +
+ {{.DMARCName}} + +
+
+
+ + TXT +
- - TXT -
{{.DMARCExample}} @@ -151,15 +220,20 @@ {{if .NeedsReportAuth}}

Report authorization

- -
- {{.ReportAuthName}} - +
+
+ +
+ {{.ReportAuthName}} + +
+
+
+ + TXT +
- - TXT -
{{.ReportAuthValue}} @@ -174,8 +248,8 @@
-
-

Sending server settings

+
+

Connection settings

Same for every domain. Authenticate with an application login from below.

@@ -193,8 +267,8 @@ create or regenerate.

- {{/* Create form beside settings, mirroring "Add a sending domain" above the - domains list. */}} + {{/* Create form beside connection settings, mirroring "Add a sending domain" + above the domains list. */}}

Add an application

@@ -270,7 +344,10 @@
-

Rate limit

+

Optional level-2 rate limit

+

Caps this application only. The domain level-2 + limit (Domain settings) and the + global level-1 limit still apply.

diff --git a/internal/web/view/templates_test.go b/internal/web/view/templates_test.go index 2e3ddd4..1aaf43e 100644 --- a/internal/web/view/templates_test.go +++ b/internal/web/view/templates_test.go @@ -207,7 +207,7 @@ func TestOnlyThePagesMadeOfDataDeclareThemselvesWide(t *testing.T) { } // The domain page pairs cards the same way Status does: three .split rows -// (DKIM|SPF+DMARC, settings|add-app, export|danger). DNS status, Applications +// (DKIM+SPF|DMARC, connection|add-app, export|danger). DNS status, Applications // and Domain settings are full-width; DNS status and Domain settings (and the // application Edit panel) use .check-cols. Losing a row silently stacks again. func TestDomainDetailPageHasPairedCards(t *testing.T) { @@ -230,8 +230,8 @@ func TestDomainDetailPageHasPairedCards(t *testing.T) { t.Error("application Edit mode and Rate limit should be one Edit button") } for _, id := range []string{ - `id="dkim"`, `id="dns-status"`, `id="spf-dmarc"`, - `id="settings"`, `id="add-application"`, `id="applications"`, + `id="dkim-spf"`, `id="dns-status"`, `id="dmarc"`, + `id="connection"`, `id="add-application"`, `id="applications"`, `id="domain-settings"`, `id="export"`, `id="danger"`, } { if !strings.Contains(src, id) { @@ -241,11 +241,8 @@ func TestDomainDetailPageHasPairedCards(t *testing.T) { if strings.Contains(src, `id="rate-limit"`) { t.Error("domain rate limit should live inside domain-settings, not its own card") } - if strings.Contains(src, `id="spf"`) && !strings.Contains(src, `id="spf-dmarc"`) { - t.Error("standalone SPF card should be merged into spf-dmarc") - } - if regexp.MustCompile(`id="dmarc"`).MatchString(src) { - t.Error("standalone DMARC card should be merged into spf-dmarc") + if strings.Contains(src, `id="spf-dmarc"`) { + t.Error("SPF should sit with DKIM, not with DMARC") } }