release: 1.2.3
Close CHANGELOG [Unreleased] as 1.2.3; bump the compose pin and image references. Includes domain detail layout polish and Domains add-row. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+17
-8
@@ -5,17 +5,26 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version
|
|||||||
|
|
||||||
## [Unreleased]
|
## [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
|
### Changed
|
||||||
|
|
||||||
- panel: the domain detail page is wide with paired cards (DKIM ‖ SPF+DMARC;
|
- panel: the domain detail page is wide with paired cards (DKIM+SPF ‖ DMARC;
|
||||||
sending settings ‖ add application; export ‖ danger). DNS status,
|
connection settings ‖ add application; export ‖ danger). DNS status,
|
||||||
Applications and Domain settings are full-width. DNS status is two rows
|
Applications and Domain settings are full-width. DNS status is two rows
|
||||||
(DKIM ‖ SPF, DMARC ‖ report authorization); Domain settings pairs DMARC
|
(DKIM ‖ SPF, DMARC ‖ report authorization) with Host ‖ Type (narrow TXT)
|
||||||
report mode with the domain rate limit; application Edit opens mode and
|
and a Value label when records are present. Domain settings pairs DMARC
|
||||||
rate limit side by side; the custom rua address field is shown only for
|
report mode with the optional level-2 domain rate limit; application Edit
|
||||||
Custom address. The in-nav “On this page” section index is removed (Status
|
opens address mode and an optional level-2 application rate limit side by
|
||||||
already dropped it; the paired domain layout no longer needs it). Section
|
side (with a note that domain level-2 and global level-1 still apply); the
|
||||||
blurbs are shorter.
|
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
|
- panel: page URLs, browser titles, and headings are aligned — **Settings** is
|
||||||
now `/settings` (legacy `/account` redirects with 308); the domains list title
|
now `/settings` (legacy `/account` redirects with 308); the domains list title
|
||||||
is `SelfPost — domains`; Status, Users, and user create/edit titles match their
|
is `SelfPost — domains`; Status, Users, and user create/edit titles match their
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ docker run --rm -d --name selfpost-try \
|
|||||||
-e SELFPOST_HOSTNAME=mail.local.test \
|
-e SELFPOST_HOSTNAME=mail.local.test \
|
||||||
-e PANEL_COOKIE_SECURE=false \
|
-e PANEL_COOKIE_SECURE=false \
|
||||||
-v selfpost-try-data:/data \
|
-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):
|
**Get the setup URL** (pick one):
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
selfpost:
|
selfpost:
|
||||||
image: ghcr.io/mixeme/selfpost:1.2.2
|
image: ghcr.io/mixeme/selfpost:1.2.3
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
SELFPOST_HOSTNAME: "${SELFPOST_HOSTNAME:?set the mail/panel hostname, e.g. mail.example.com}"
|
SELFPOST_HOSTNAME: "${SELFPOST_HOSTNAME:?set the mail/panel hostname, e.g. mail.example.com}"
|
||||||
|
|||||||
+1
-1
@@ -141,7 +141,7 @@ Requires Go 1.26+ and `CGO_ENABLED=0`.
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
make build # bin/panel, bin/selfpost-backup (VERSION=dev by default)
|
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:
|
Or directly:
|
||||||
|
|||||||
+1
-1
@@ -381,7 +381,7 @@ but it can look like an open port in external scans.
|
|||||||
## Fixed image tag
|
## Fixed image tag
|
||||||
|
|
||||||
`deploy/docker-compose.yml` pins an explicit version (`ghcr.io/mixeme/selfpost:X.Y.Z`),
|
`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
|
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:
|
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
|
the panel binary's embedded version and the image tag that produced it are the
|
||||||
|
|||||||
@@ -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
|
beside them would otherwise win the width and leave "Memory" broken across
|
||||||
two lines. */
|
two lines. */
|
||||||
.metric { white-space: nowrap; }
|
.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 { 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
|
/* 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
|
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 > div { min-width: 0; }
|
||||||
.field-pair label { margin-top: 0.45rem; }
|
.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
|
/* 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
|
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
|
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,
|
button.copy:hover, .actions button:hover, .actions > label.toggle:hover,
|
||||||
.actions a.danger:hover, .nav button:hover { background: var(--surface-bg-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 {
|
.actions button.danger, .actions a.danger, .nav button.danger {
|
||||||
color: var(--danger-fg); background: var(--danger-bg); border-color: var(--danger-border);
|
color: var(--danger-fg); background: var(--danger-bg); border-color: var(--danger-border);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,14 +8,14 @@
|
|||||||
<h2>Add a sending domain</h2>
|
<h2>Add a sending domain</h2>
|
||||||
<form method="post" action="/domains">
|
<form method="post" action="/domains">
|
||||||
<label for="name">Domain</label>
|
<label for="name">Domain</label>
|
||||||
|
<div class="input-row">
|
||||||
<input id="name" name="name" type="text" placeholder="example.com"
|
<input id="name" name="name" type="text" placeholder="example.com"
|
||||||
autocomplete="off" autocapitalize="none" spellcheck="false"
|
autocomplete="off" autocapitalize="none" spellcheck="false"
|
||||||
value="{{.FormName}}" autofocus required>
|
value="{{.FormName}}" autofocus required>
|
||||||
{{if .Error}}<p class="error">{{.Error}}</p>{{end}}
|
|
||||||
<button type="submit">Add domain</button>
|
<button type="submit">Add domain</button>
|
||||||
|
</div>
|
||||||
|
{{if .Error}}<p class="error">{{.Error}}</p>{{end}}
|
||||||
</form>
|
</form>
|
||||||
<p class="muted">A DKIM key is generated for the domain; you then publish the
|
|
||||||
shown DNS record. Adding a domain does not create an application.</p>
|
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
|||||||
@@ -37,39 +37,93 @@
|
|||||||
<div class="check-cols">
|
<div class="check-cols">
|
||||||
<div class="check-col">
|
<div class="check-col">
|
||||||
<label>DKIM <span class="st st-{{.DNS.DKIM.Status}}">{{.DNS.DKIM.Status}}</span></label>
|
<label>DKIM <span class="st st-{{.DNS.DKIM.Status}}">{{.DNS.DKIM.Status}}</span></label>
|
||||||
<p class="muted">{{.Record.Name}}</p>
|
<div class="field-pair host-type">
|
||||||
|
<div>
|
||||||
|
<label>Host / name</label>
|
||||||
|
<span class="code">{{.Record.Name}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="field-type">
|
||||||
|
<label>Type</label>
|
||||||
|
<span class="code">TXT</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{if .DNS.DKIM.Records}}
|
||||||
|
<label>Value</label>
|
||||||
|
<span class="code">{{range .DNS.DKIM.Records}}{{.}}
|
||||||
|
{{end}}</span>
|
||||||
|
{{end}}
|
||||||
{{if ne .DNS.DKIM.Status "ok"}}
|
{{if ne .DNS.DKIM.Status "ok"}}
|
||||||
<p class="{{if eq .DNS.DKIM.Status "unknown"}}muted{{else}}error{{end}}">{{.DNS.DKIM.Detail}}</p>
|
<p class="{{if eq .DNS.DKIM.Status "unknown"}}muted{{else}}error{{end}}">{{.DNS.DKIM.Detail}}</p>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if .DNS.DKIM.Records}}<span class="code">{{range .DNS.DKIM.Records}}{{.}}
|
|
||||||
{{end}}</span>{{end}}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="check-col">
|
<div class="check-col">
|
||||||
<label>SPF <span class="st st-{{.DNS.SPF.Status}}">{{.DNS.SPF.Status}}</span></label>
|
<label>SPF <span class="st st-{{.DNS.SPF.Status}}">{{.DNS.SPF.Status}}</span></label>
|
||||||
<p class="muted">{{.Domain.Name}}</p>
|
<div class="field-pair host-type">
|
||||||
|
<div>
|
||||||
|
<label>Host / name</label>
|
||||||
|
<span class="code">{{.Domain.Name}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="field-type">
|
||||||
|
<label>Type</label>
|
||||||
|
<span class="code">TXT</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{if .DNS.SPF.Records}}
|
||||||
|
<label>Value</label>
|
||||||
|
<span class="code">{{range .DNS.SPF.Records}}{{.}}
|
||||||
|
{{end}}</span>
|
||||||
|
{{end}}
|
||||||
{{if ne .DNS.SPF.Status "ok"}}
|
{{if ne .DNS.SPF.Status "ok"}}
|
||||||
<p class="{{if eq .DNS.SPF.Status "unknown"}}muted{{else}}error{{end}}">{{.DNS.SPF.Detail}}</p>
|
<p class="{{if eq .DNS.SPF.Status "unknown"}}muted{{else}}error{{end}}">{{.DNS.SPF.Detail}}</p>
|
||||||
{{end}}
|
{{end}}
|
||||||
<p class="muted">Shallow check: literal address only, no <code>include:</code> /
|
<p class="muted">Shallow check: literal address only, no <code>include:</code> /
|
||||||
<code>redirect=</code>.</p>
|
<code>redirect=</code>.</p>
|
||||||
{{if .DNS.SPF.Records}}<span class="code">{{range .DNS.SPF.Records}}{{.}}
|
|
||||||
{{end}}</span>{{end}}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="check-col">
|
<div class="check-col">
|
||||||
<label>DMARC <span class="st st-{{.DNS.DMARC.Status}}">{{.DNS.DMARC.Status}}</span></label>
|
<label>DMARC <span class="st st-{{.DNS.DMARC.Status}}">{{.DNS.DMARC.Status}}</span></label>
|
||||||
<p class="{{if eq .DNS.DMARC.Status "ok"}}muted{{else}}error{{end}}">{{.DNS.DMARC.Detail}}</p>
|
<div class="field-pair host-type">
|
||||||
{{if .DNS.DMARC.Records}}<span class="code">{{range .DNS.DMARC.Records}}{{.}}
|
<div>
|
||||||
{{end}}</span>{{end}}
|
<label>Host / name</label>
|
||||||
|
<span class="code">{{.DMARCName}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="field-type">
|
||||||
|
<label>Type</label>
|
||||||
|
<span class="code">TXT</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{if .DNS.DMARC.Records}}
|
||||||
|
<label>Value</label>
|
||||||
|
<span class="code">{{range .DNS.DMARC.Records}}{{.}}
|
||||||
|
{{end}}</span>
|
||||||
|
{{end}}
|
||||||
|
{{if ne .DNS.DMARC.Status "ok"}}
|
||||||
|
<p class="{{if eq .DNS.DMARC.Status "unknown"}}muted{{else}}error{{end}}">{{.DNS.DMARC.Detail}}</p>
|
||||||
|
{{else}}
|
||||||
|
<p class="muted">{{.DNS.DMARC.Detail}}</p>
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="check-col">
|
<div class="check-col">
|
||||||
{{if .DNS.DMARCReportAuth.Status}}
|
{{if .DNS.DMARCReportAuth.Status}}
|
||||||
<label>Report authorization <span class="st st-{{.DNS.DMARCReportAuth.Status}}">{{.DNS.DMARCReportAuth.Status}}</span></label>
|
<label>Report authorization <span class="st st-{{.DNS.DMARCReportAuth.Status}}">{{.DNS.DMARCReportAuth.Status}}</span></label>
|
||||||
|
<div class="field-pair host-type">
|
||||||
|
<div>
|
||||||
|
<label>Host / name</label>
|
||||||
|
<span class="code">{{.ReportAuthName}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="field-type">
|
||||||
|
<label>Type</label>
|
||||||
|
<span class="code">TXT</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{if .DNS.DMARCReportAuth.Records}}
|
||||||
|
<label>Value</label>
|
||||||
|
<span class="code">{{range .DNS.DMARCReportAuth.Records}}{{.}}
|
||||||
|
{{end}}</span>
|
||||||
|
{{end}}
|
||||||
<p class="{{if eq .DNS.DMARCReportAuth.Status "ok"}}muted{{else}}error{{end}}">{{.DNS.DMARCReportAuth.Detail}}</p>
|
<p class="{{if eq .DNS.DMARCReportAuth.Status "ok"}}muted{{else}}error{{end}}">{{.DNS.DMARCReportAuth.Detail}}</p>
|
||||||
{{if .DNS.DMARCReportAuth.Records}}<span class="code">{{range .DNS.DMARCReportAuth.Records}}{{.}}
|
|
||||||
{{end}}</span>{{end}}
|
|
||||||
{{else}}
|
{{else}}
|
||||||
<label>Report authorization</label>
|
<label>Report authorization</label>
|
||||||
<p class="muted">Not required (no external <code>rua=</code>).</p>
|
<p class="muted">Not required (no external <code>rua=</code>).</p>
|
||||||
@@ -83,17 +137,23 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="split">
|
<div class="split">
|
||||||
<div class="card" id="dkim">
|
<div class="card" id="dkim-spf">
|
||||||
<h2>DKIM record</h2>
|
<h2>DKIM and SPF records</h2>
|
||||||
|
|
||||||
|
<p class="check-col-title">DKIM</p>
|
||||||
|
<div class="field-pair host-type">
|
||||||
|
<div>
|
||||||
<label>Host / name</label>
|
<label>Host / name</label>
|
||||||
<div class="code-row">
|
<div class="code-row">
|
||||||
<span class="code">{{.Record.Name}}</span>
|
<span class="code">{{.Record.Name}}</span>
|
||||||
<button type="button" class="copy">Copy</button>
|
<button type="button" class="copy">Copy</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field-type">
|
||||||
<label>Type</label>
|
<label>Type</label>
|
||||||
<span class="code">TXT</span>
|
<span class="code">TXT</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<label>Value</label>
|
<label>Value</label>
|
||||||
<div class="code-row">
|
<div class="code-row">
|
||||||
@@ -103,20 +163,21 @@
|
|||||||
|
|
||||||
<p class="muted">Not a secret. Signed with selector
|
<p class="muted">Not a secret. Signed with selector
|
||||||
<strong>{{.Domain.DKIMSelector}}</strong>.</p>
|
<strong>{{.Domain.DKIMSelector}}</strong>.</p>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card" id="spf-dmarc">
|
|
||||||
<h2>SPF and DMARC records</h2>
|
|
||||||
|
|
||||||
<p class="check-col-title">SPF</p>
|
<p class="check-col-title">SPF</p>
|
||||||
|
<div class="field-pair host-type">
|
||||||
|
<div>
|
||||||
<label>Host / name</label>
|
<label>Host / name</label>
|
||||||
<div class="code-row">
|
<div class="code-row">
|
||||||
<span class="code">{{.Domain.Name}}</span>
|
<span class="code">{{.Domain.Name}}</span>
|
||||||
<button type="button" class="copy">Copy</button>
|
<button type="button" class="copy">Copy</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field-type">
|
||||||
<label>Type</label>
|
<label>Type</label>
|
||||||
<span class="code">TXT</span>
|
<span class="code">TXT</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<label>Value</label>
|
<label>Value</label>
|
||||||
<div class="code-row">
|
<div class="code-row">
|
||||||
@@ -126,16 +187,24 @@
|
|||||||
|
|
||||||
<p class="muted">Merge into an existing SPF if the domain already has one —
|
<p class="muted">Merge into an existing SPF if the domain already has one —
|
||||||
do not publish a second record.</p>
|
do not publish a second record.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p class="check-col-title">DMARC</p>
|
<div class="card" id="dmarc">
|
||||||
|
<h2>DMARC record</h2>
|
||||||
|
|
||||||
|
<div class="field-pair host-type">
|
||||||
|
<div>
|
||||||
<label>Host / name</label>
|
<label>Host / name</label>
|
||||||
<div class="code-row">
|
<div class="code-row">
|
||||||
<span class="code">{{.DMARCName}}</span>
|
<span class="code">{{.DMARCName}}</span>
|
||||||
<button type="button" class="copy">Copy</button>
|
<button type="button" class="copy">Copy</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field-type">
|
||||||
<label>Type</label>
|
<label>Type</label>
|
||||||
<span class="code">TXT</span>
|
<span class="code">TXT</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<label>Value{{if eq .DMARCSource "settings"}} <span class="muted">(from Settings)</span>{{else if eq .DMARCSource "custom"}} <span class="muted">(custom)</span>{{else if eq .DMARCSource "none"}} <span class="muted">(no reports)</span>{{end}}</label>
|
<label>Value{{if eq .DMARCSource "settings"}} <span class="muted">(from Settings)</span>{{else if eq .DMARCSource "custom"}} <span class="muted">(custom)</span>{{else if eq .DMARCSource "none"}} <span class="muted">(no reports)</span>{{end}}</label>
|
||||||
<div class="code-row">
|
<div class="code-row">
|
||||||
@@ -151,14 +220,19 @@
|
|||||||
|
|
||||||
{{if .NeedsReportAuth}}
|
{{if .NeedsReportAuth}}
|
||||||
<p class="check-col-title">Report authorization</p>
|
<p class="check-col-title">Report authorization</p>
|
||||||
|
<div class="field-pair host-type">
|
||||||
|
<div>
|
||||||
<label>Host / name</label>
|
<label>Host / name</label>
|
||||||
<div class="code-row">
|
<div class="code-row">
|
||||||
<span class="code">{{.ReportAuthName}}</span>
|
<span class="code">{{.ReportAuthName}}</span>
|
||||||
<button type="button" class="copy">Copy</button>
|
<button type="button" class="copy">Copy</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field-type">
|
||||||
<label>Type</label>
|
<label>Type</label>
|
||||||
<span class="code">TXT</span>
|
<span class="code">TXT</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<label>Value</label>
|
<label>Value</label>
|
||||||
<div class="code-row">
|
<div class="code-row">
|
||||||
@@ -174,8 +248,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="split">
|
<div class="split">
|
||||||
<div class="card" id="settings">
|
<div class="card" id="connection">
|
||||||
<h2>Sending server settings</h2>
|
<h2>Connection settings</h2>
|
||||||
<p class="muted">Same for every domain. Authenticate with an application
|
<p class="muted">Same for every domain. Authenticate with an application
|
||||||
login from below.</p>
|
login from below.</p>
|
||||||
|
|
||||||
@@ -193,8 +267,8 @@
|
|||||||
create or regenerate.</p>
|
create or regenerate.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{/* Create form beside settings, mirroring "Add a sending domain" above the
|
{{/* Create form beside connection settings, mirroring "Add a sending domain"
|
||||||
domains list. */}}
|
above the domains list. */}}
|
||||||
<div class="card" id="add-application">
|
<div class="card" id="add-application">
|
||||||
<h2>Add an application</h2>
|
<h2>Add an application</h2>
|
||||||
<form method="post" action="/domains/{{.Domain.ID}}/applications">
|
<form method="post" action="/domains/{{.Domain.ID}}/applications">
|
||||||
@@ -270,7 +344,10 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="check-col">
|
<div class="check-col">
|
||||||
<p class="check-col-title">Rate limit</p>
|
<p class="check-col-title">Optional level-2 rate limit</p>
|
||||||
|
<p class="muted">Caps this application only. The domain level-2
|
||||||
|
limit (<a href="#domain-settings">Domain settings</a>) and the
|
||||||
|
global level-1 limit still apply.</p>
|
||||||
<form id="rl-{{.ID}}" method="post" action="/applications/{{.ID}}/ratelimit">
|
<form id="rl-{{.ID}}" method="post" action="/applications/{{.ID}}/ratelimit">
|
||||||
<label>Expected client IPs (one per line or comma-separated)</label>
|
<label>Expected client IPs (one per line or comma-separated)</label>
|
||||||
<textarea name="allowed_ips" rows="2" placeholder="203.0.113.10">{{.IPsText}}</textarea>
|
<textarea name="allowed_ips" rows="2" placeholder="203.0.113.10">{{.IPsText}}</textarea>
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ func TestOnlyThePagesMadeOfDataDeclareThemselvesWide(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The domain page pairs cards the same way Status does: three .split rows
|
// 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
|
// 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.
|
// application Edit panel) use .check-cols. Losing a row silently stacks again.
|
||||||
func TestDomainDetailPageHasPairedCards(t *testing.T) {
|
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")
|
t.Error("application Edit mode and Rate limit should be one Edit button")
|
||||||
}
|
}
|
||||||
for _, id := range []string{
|
for _, id := range []string{
|
||||||
`id="dkim"`, `id="dns-status"`, `id="spf-dmarc"`,
|
`id="dkim-spf"`, `id="dns-status"`, `id="dmarc"`,
|
||||||
`id="settings"`, `id="add-application"`, `id="applications"`,
|
`id="connection"`, `id="add-application"`, `id="applications"`,
|
||||||
`id="domain-settings"`, `id="export"`, `id="danger"`,
|
`id="domain-settings"`, `id="export"`, `id="danger"`,
|
||||||
} {
|
} {
|
||||||
if !strings.Contains(src, id) {
|
if !strings.Contains(src, id) {
|
||||||
@@ -241,11 +241,8 @@ func TestDomainDetailPageHasPairedCards(t *testing.T) {
|
|||||||
if strings.Contains(src, `id="rate-limit"`) {
|
if strings.Contains(src, `id="rate-limit"`) {
|
||||||
t.Error("domain rate limit should live inside domain-settings, not its own card")
|
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"`) {
|
if strings.Contains(src, `id="spf-dmarc"`) {
|
||||||
t.Error("standalone SPF card should be merged into spf-dmarc")
|
t.Error("SPF should sit with DKIM, not with DMARC")
|
||||||
}
|
|
||||||
if regexp.MustCompile(`id="dmarc"`).MatchString(src) {
|
|
||||||
t.Error("standalone DMARC card should be merged into spf-dmarc")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user