panel-redesign: links into Domain settings carry no anchor
Follows the design commit before it: the domain page's menu entries
Report address / Rate limit / Export, the DMARC record's note and the
delete page's "Export the domain" lead to /outbound/domains/{id}/settings
as it opens, not scrolled to a box.
go test -count=1 ./internal/web/...: ok
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -105,7 +105,7 @@ func TestNewDomainHasNoReports(t *testing.T) {
|
|||||||
|
|
||||||
// The domain the stand started with has none either.
|
// The domain the stand started with has none either.
|
||||||
has(t, "domain page", pageOf(t, s.h.HandleDomainDetail, "/outbound/domains/1"),
|
has(t, "domain page", pageOf(t, s.h.HandleDomainDetail, "/outbound/domains/1"),
|
||||||
`Report address: <a href="/outbound/domains/1/settings#reports">no reports</a>`)
|
`Report address: <a href="/outbound/domains/1/settings">no reports</a>`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Each of the three choices is saved, comes back selected, and is what the
|
// Each of the three choices is saved, comes back selected, and is what the
|
||||||
@@ -130,7 +130,7 @@ func TestDomainReportAddressThreeChoices(t *testing.T) {
|
|||||||
has(t, "settings after hosted", settings(), `<option value="hosted" selected>`)
|
has(t, "settings after hosted", settings(), `<option value="hosted" selected>`)
|
||||||
lacks(t, "settings after hosted", settings(), `value="`+hostedForExample+`"`) // the typed field stays empty
|
lacks(t, "settings after hosted", settings(), `value="`+hostedForExample+`"`) // the typed field stays empty
|
||||||
has(t, "domain page after hosted", pageOf(t, s.h.HandleDomainDetail, "/outbound/domains/1"),
|
has(t, "domain page after hosted", pageOf(t, s.h.HandleDomainDetail, "/outbound/domains/1"),
|
||||||
`Report address: <a href="/outbound/domains/1/settings#reports">`+hostedEscaped+`</a>`,
|
`Report address: <a href="/outbound/domains/1/settings">`+hostedEscaped+`</a>`,
|
||||||
`rua=mailto:`+hostedEscaped)
|
`rua=mailto:`+hostedEscaped)
|
||||||
// The ingest allow-list was rebuilt for the new address.
|
// The ingest allow-list was rebuilt for the new address.
|
||||||
if last := maps.got[len(maps.got)-1]; !contains(last.Recipients, hostedForExample) {
|
if last := maps.got[len(maps.got)-1]; !contains(last.Recipients, hostedForExample) {
|
||||||
@@ -145,7 +145,7 @@ func TestDomainReportAddressThreeChoices(t *testing.T) {
|
|||||||
}
|
}
|
||||||
has(t, "settings after custom", settings(), `<option value="custom" selected>`, `value="reports@hub.example"`)
|
has(t, "settings after custom", settings(), `<option value="custom" selected>`, `value="reports@hub.example"`)
|
||||||
has(t, "domain page after custom", pageOf(t, s.h.HandleDomainDetail, "/outbound/domains/1"),
|
has(t, "domain page after custom", pageOf(t, s.h.HandleDomainDetail, "/outbound/domains/1"),
|
||||||
`Report address: <a href="/outbound/domains/1/settings#reports">reports@hub.example</a>`, `rua=mailto:reports@hub.example`,
|
`Report address: <a href="/outbound/domains/1/settings">reports@hub.example</a>`, `rua=mailto:reports@hub.example`,
|
||||||
`<h3>Report authorization</h3>`) // the address is on another domain than the senders
|
`<h3>Report authorization</h3>`) // the address is on another domain than the senders
|
||||||
if last := maps.got[len(maps.got)-1]; contains(last.Recipients, hostedForExample) || contains(last.Recipients, "reports@hub.example") {
|
if last := maps.got[len(maps.got)-1]; contains(last.Recipients, hostedForExample) || contains(last.Recipients, "reports@hub.example") {
|
||||||
t.Errorf("the allow-list after choosing another host: %v", last.Recipients)
|
t.Errorf("the allow-list after choosing another host: %v", last.Recipients)
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ func TestDomainPageShowsRecordsApplicationsAndConnection(t *testing.T) {
|
|||||||
`<td class="sp-mono sp-nowrap"><strong>prod-server</strong></td>`, `alerts@<wbr>example.org`, `200 / h`, `2 IPs`,
|
`<td class="sp-mono sp-nowrap"><strong>prod-server</strong></td>`, `alerts@<wbr>example.org`, `200 / h`, `2 IPs`,
|
||||||
`href="/outbound/domains/1/applications/`+idStr(a.ID)+`">Edit</a>`,
|
`href="/outbound/domains/1/applications/`+idStr(a.ID)+`">Edit</a>`,
|
||||||
`mail.example.org`, `STARTTLS (submission)`, `action="/outbound/domains/1/dns-recheck"`,
|
`mail.example.org`, `STARTTLS (submission)`, `action="/outbound/domains/1/dns-recheck"`,
|
||||||
`href="/outbound/log?domain=example.org"`, `href="/outbound/domains/1/settings#reports"`, `href="/outbound/domains/1/delete"`)
|
`href="/outbound/log?domain=example.org"`, `href="/outbound/domains/1/settings"`, `href="/outbound/domains/1/delete"`)
|
||||||
|
|
||||||
// The flash the redirects carry.
|
// The flash the redirects carry.
|
||||||
rec := send(s.h.HandleDomainDetail, &globalPrincipal, "GET", "/outbound/domains/1?appsaved=1", s.paths, nil)
|
rec := send(s.h.HandleDomainDetail, &globalPrincipal, "GET", "/outbound/domains/1?appsaved=1", s.paths, nil)
|
||||||
@@ -339,7 +339,7 @@ func TestDeleteConfirmNamesTheApplicationsThatGoWithTheDomain(t *testing.T) {
|
|||||||
t.Fatalf("delete confirmation = %d:\n%s", rec.Code, body)
|
t.Fatalf("delete confirmation = %d:\n%s", rec.Code, body)
|
||||||
}
|
}
|
||||||
has(t, "delete", body, `<h1 class="title is-3">Delete example.org</h1>`, `permanently delete its DKIM signing key;`,
|
has(t, "delete", body, `<h1 class="title is-3">Delete example.org</h1>`, `permanently delete its DKIM signing key;`,
|
||||||
`<form method="post" action="/outbound/domains/1/delete">`, `Delete example.org</button>`, `href="/outbound/domains/1/settings#export"`)
|
`<form method="post" action="/outbound/domains/1/delete">`, `Delete example.org</button>`, `href="/outbound/domains/1/settings"`)
|
||||||
lacks(t, "delete", body, `credentials`)
|
lacks(t, "delete", body, `credentials`)
|
||||||
|
|
||||||
for _, login := range []string{"prod-server", "alerts"} {
|
for _, login := range []string{"prod-server", "alerts"} {
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ type DMARCRecordInput struct {
|
|||||||
// delivery, so the standing note says how to tighten it.
|
// delivery, so the standing note says how to tighten it.
|
||||||
func DMARCRecord(in DMARCRecordInput) Record {
|
func DMARCRecord(in DMARCRecordInput) Record {
|
||||||
r := Record{Name: "DMARC", Host: in.Host, Type: "TXT", Value: in.Value,
|
r := Record{Name: "DMARC", Host: in.Host, Type: "TXT", Value: in.Value,
|
||||||
Note: Rich("Report address: ", Link(in.SettingsHref+"#reports", in.Source))}
|
Note: Rich("Report address: ", Link(in.SettingsHref, in.Source))}
|
||||||
reports := Rich("reports")
|
reports := Rich("reports")
|
||||||
if in.ReportsHref != "" {
|
if in.ReportsHref != "" {
|
||||||
reports = Rich(Link(in.ReportsHref, "reports"))
|
reports = Rich(Link(in.ReportsHref, "reports"))
|
||||||
@@ -497,9 +497,9 @@ func (p *OutDomain) Menu() SideMenu {
|
|||||||
}
|
}
|
||||||
settings := DomainHref(p.id) + "/settings"
|
settings := DomainHref(p.id) + "/settings"
|
||||||
rare := MenuGroup{Label: "Rarely changed", Items: []MenuItem{
|
rare := MenuGroup{Label: "Rarely changed", Items: []MenuItem{
|
||||||
{Label: "Report address", Icon: "ti-mail-forward", Href: settings + "#reports"},
|
{Label: "Report address", Icon: "ti-mail-forward", Href: settings},
|
||||||
{Label: "Rate limit", Icon: "ti-gauge", Href: settings + "#limit", Tag: p.limitTag},
|
{Label: "Rate limit", Icon: "ti-gauge", Href: settings, Tag: p.limitTag},
|
||||||
{Label: "Export", Icon: "ti-file-export", Href: settings + "#export"},
|
{Label: "Export", Icon: "ti-file-export", Href: settings},
|
||||||
}}
|
}}
|
||||||
if p.canDelete {
|
if p.canDelete {
|
||||||
rare.Items = append(rare.Items, MenuItem{Label: "Delete domain", Icon: "ti-trash", Href: DomainHref(p.id) + "/delete", Danger: true})
|
rare.Items = append(rare.Items, MenuItem{Label: "Delete domain", Icon: "ti-trash", Href: DomainHref(p.id) + "/delete", Danger: true})
|
||||||
@@ -748,7 +748,7 @@ func NewOutDomainDelete(m Meta, id int64, name string, logins []string) *OutDoma
|
|||||||
Confirm: "Delete " + name,
|
Confirm: "Delete " + name,
|
||||||
KeepHref: href,
|
KeepHref: href,
|
||||||
Moving: Box{Icon: "ti-file-export", Title: "Moving it instead?"},
|
Moving: Box{Icon: "ti-file-export", Title: "Moving it instead?"},
|
||||||
MovingText: Rich(Link(href+"/settings#export", "Export the domain"),
|
MovingText: Rich(Link(href+"/settings", "Export the domain"),
|
||||||
" first. The file carries the key and the application passwords, so the new instance needs no DNS change."),
|
" first. The file carries the key and the application passwords, so the new instance needs no DNS change."),
|
||||||
}
|
}
|
||||||
if n := appsPhrase(len(logins)); n != "" {
|
if n := appsPhrase(len(logins)); n != "" {
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ func TestOutDomainRecordsSayWhatDNSHolds(t *testing.T) {
|
|||||||
`sp2026._domainkey.example.org`, `<textarea class="textarea" rows="3" readonly>`,
|
`sp2026._domainkey.example.org`, `<textarea class="textarea" rows="3" readonly>`,
|
||||||
`<h3>SPF</h3>`, `<span class="tag is-danger is-light">mismatch</span>`, `v=spf1 ip4:203.0.113.25 ~all`,
|
`<h3>SPF</h3>`, `<span class="tag is-danger is-light">mismatch</span>`, `v=spf1 ip4:203.0.113.25 ~all`,
|
||||||
`In DNS now`, `v=spf1 include:_spf.google.com ~all`, `Shallow check: <code>include:</code> is not followed.`,
|
`In DNS now`, `v=spf1 include:_spf.google.com ~all`, `Shallow check: <code>include:</code> is not followed.`,
|
||||||
`<h3>DMARC</h3>`, `Report address: <a href="/outbound/domains/1/settings#reports">example.org@dmarc.mail.example.org</a>`,
|
`<h3>DMARC</h3>`, `Report address: <a href="/outbound/domains/1/settings">example.org@dmarc.mail.example.org</a>`,
|
||||||
`<code>p=none</code> does not affect delivery.`, `<a href="/outbound/dmarc/domains/1">reports</a>`,
|
`<code>p=none</code> does not affect delivery.`, `<a href="/outbound/dmarc/domains/1">reports</a>`,
|
||||||
)
|
)
|
||||||
if got := strings.Count(out, `In DNS now`); got != 1 {
|
if got := strings.Count(out, `In DNS now`); got != 1 {
|
||||||
@@ -180,7 +180,7 @@ func TestOutDomainMenuFollowsTheViewer(t *testing.T) {
|
|||||||
out := renderSignedIn(t, "out-domain", outDomainFixture())
|
out := renderSignedIn(t, "out-domain", outDomainFixture())
|
||||||
pageHas(t, "Domain", out,
|
pageHas(t, "Domain", out,
|
||||||
`href="#dns"`, `href="#apps"`, `href="#connection"`, `href="/outbound/log?domain=example.org"`, `href="/outbound/dmarc/domains/1"`,
|
`href="#dns"`, `href="#apps"`, `href="#connection"`, `href="/outbound/log?domain=example.org"`, `href="/outbound/dmarc/domains/1"`,
|
||||||
`href="/outbound/domains/1/settings#reports"`, `href="/outbound/domains/1/settings#limit"`, `href="/outbound/domains/1/settings#export"`,
|
`href="/outbound/domains/1/settings"`, `href="/outbound/domains/1/settings"`, `href="/outbound/domains/1/settings"`,
|
||||||
`href="/outbound/domains/1/delete"`, `id="dns"`, `id="apps"`, `id="connection"`,
|
`href="/outbound/domains/1/delete"`, `id="dns"`, `id="apps"`, `id="connection"`,
|
||||||
`class="sp-help" href="/help#dns"`, `class="sp-help" href="/help#apps"`,
|
`class="sp-help" href="/help#dns"`, `class="sp-help" href="/help#apps"`,
|
||||||
)
|
)
|
||||||
@@ -437,7 +437,7 @@ func TestOutDomainDeleteNamesWhatGoesWithIt(t *testing.T) {
|
|||||||
`reload OpenDKIM so the domain is no longer signed.`, `Inbound domains are not touched.`,
|
`reload OpenDKIM so the domain is no longer signed.`, `Inbound domains are not touched.`,
|
||||||
`<form method="post" action="/outbound/domains/1/delete">`, `class="button is-danger"`, `Delete example.org</button>`,
|
`<form method="post" action="/outbound/domains/1/delete">`, `class="button is-danger"`, `Delete example.org</button>`,
|
||||||
`href="/outbound/domains/1">Keep it</a>`,
|
`href="/outbound/domains/1">Keep it</a>`,
|
||||||
`<h2>Moving it instead?</h2>`, `<a href="/outbound/domains/1/settings#export">Export the domain</a>`,
|
`<h2>Moving it instead?</h2>`, `<a href="/outbound/domains/1/settings">Export the domain</a>`,
|
||||||
)
|
)
|
||||||
|
|
||||||
// No applications: nothing to say about them.
|
// No applications: nothing to say about them.
|
||||||
|
|||||||
Reference in New Issue
Block a user