panel: align page URLs, titles, and headings
test / test (push) Has been cancelled

Settings moves to /settings with a 308 redirect from /account; domains, Status, Users, and user forms get matching browser titles and h1 text; backup page title reflects domain import.

Co-Authored-By: Composer <noreply@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-12 15:24:14 +03:00
parent b87baa6dd6
commit 1d30605533
15 changed files with 41 additions and 17 deletions
+9
View File
@@ -5,6 +5,15 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version
## [Unreleased]
### Changed
- 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
nav labels and `<h1>` text; the backup page title is `SelfPost — backup &
migration` to reflect domain import as well as full backup. Operator guide and
architecture route tables updated.
### Fixed
- image: `mail.log` rotation no longer silently stops when the build context
+2 -1
View File
@@ -170,7 +170,8 @@ unless noted. The table below is a summary — HTMX fragment endpoints
| `/system-log` | `mail.log` tail |
| `/reload` | Reload OpenDKIM + Postfix maps |
| `/backup` | Full backup download (page also hosts the import form) |
| `/account` | Admin username/password |
| `/settings` | Admin username/password and DMARC report address |
| `/users`, `/users/*` | Panel user CRUD (global admin only) |
HTMX polling refreshes monitoring fragments (5 s while the operator is active on
the page, 30 s when the tab is visible but idle, none when hidden — scheduled in
+1 -1
View File
@@ -229,7 +229,7 @@ service healthy and will mail be accepted?"
- **Backup** (`/backup`) — download a full-server backup; the same page hosts
the domain-import form (`POST /domains/import`). See
[Backup, restore, and moving a single domain](#backup-restore-and-moving-a-single-domain).
- **Settings** (`/account`) — change the administrator username and/or password.
- **Settings** (`/settings`) — change the administrator username and/or password.
Application SASL logins are separate and are not changed here.
**Sessions.** A login survives a container restart: sessions live in SQLite, not
+3 -3
View File
@@ -49,7 +49,7 @@ func (h *Handlers) renderAccount(w http.ResponseWriter, r *http.Request, status
}
data := h.pageBase(r)
data["Title"] = "SelfPost — settings"
data["Active"] = "account"
data["Active"] = "settings"
data["FormUsername"] = formUsername
data["FormDMARCEmail"] = formDMARCEmail
data["ShowDMARC"] = showDMARC
@@ -59,7 +59,7 @@ func (h *Handlers) renderAccount(w http.ResponseWriter, r *http.Request, status
data["ReportAuthHub"] = dnscheck.EmailDomain(formDMARCEmail)
data["Error"] = formErr
data["Flash"] = accountFlash(r)
h.view.Render(w, status, "account", data)
h.view.Render(w, status, "settings", data)
}
func accountFlash(r *http.Request) string {
@@ -196,7 +196,7 @@ func (h *Handlers) submitAccount(w http.ResponseWriter, r *http.Request) {
}
logf("panel: user %d account updated (username: %t, password: %t, dmarc email: %t)", user.ID, renaming, repassword, emailChanging)
http.Redirect(w, r, "/account?updated="+updatedFlag(renaming, repassword, emailChanging), http.StatusSeeOther)
http.Redirect(w, r, "/settings?updated="+updatedFlag(renaming, repassword, emailChanging), http.StatusSeeOther)
}
func updatedFlag(renamed, repassword, emailChanged bool) string {
+1 -1
View File
@@ -45,7 +45,7 @@ func (h *Handlers) renderBackupPage(w http.ResponseWriter, r *http.Request, stat
// wrong form.
func (h *Handlers) renderBackupPageWith(w http.ResponseWriter, r *http.Request, status int, importErr, backupErr string) {
data := h.pageBase(r)
data["Title"] = "SelfPost — backup"
data["Title"] = "SelfPost — backup & migration"
data["Active"] = "backup"
data["ImportErr"] = importErr
data["BackupErr"] = backupErr
+1 -1
View File
@@ -41,7 +41,7 @@ func (h *Handlers) renderDashboard(w http.ResponseWriter, r *http.Request, statu
return
}
data := h.pageBase(r)
data["Title"] = "SelfPost"
data["Title"] = "SelfPost — domains"
data["Active"] = "domains"
data["Domains"] = h.domainRows(domains)
data["Error"] = formErr
+5 -1
View File
@@ -121,7 +121,11 @@ func (h *Handlers) renderUserForm(w http.ResponseWriter, r *http.Request, status
return
}
data := h.pageBase(r)
data["Title"] = "SelfPost — user"
if userID != 0 {
data["Title"] = "SelfPost — edit user"
} else {
data["Title"] = "SelfPost — create user"
}
data["Active"] = "users"
data["UserID"] = userID
data["Domains"] = domains
@@ -53,7 +53,7 @@
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 <code>rua=</code> unless a
report address is configured below or in <a href="/account">Settings</a>.
report address is configured below or in <a href="/settings">Settings</a>.
Publish both as TXT records.</p>
<label>SPF — host / name</label>
+1 -1
View File
@@ -81,7 +81,7 @@
{{template "sections" .}}
<div class="session">
<span class="session-user muted">{{template "icon-account"}}User: {{.User}}</span>
{{if eq .Active "account"}}<span aria-current="page">{{template "icon-settings"}}Settings</span>{{else}}<a href="/account">{{template "icon-settings"}}Settings</a>{{end}}
{{if eq .Active "settings"}}<span aria-current="page">{{template "icon-settings"}}Settings</span>{{else}}<a href="/settings">{{template "icon-settings"}}Settings</a>{{end}}
<form class="inline" method="post" action="/logout">
<button type="submit" class="danger">{{template "icon-sign-out"}}Sign out</button>
</form>
@@ -9,7 +9,7 @@
{{if .Error}}<p class="error">{{.Error}}</p>{{end}}
{{if .ShowDMARC}}
<form method="post" action="/account">
<form method="post" action="/settings">
<div class="split">
<div class="card">
<h2>Panel credentials</h2>
@@ -80,7 +80,7 @@ this one stays signed in.</p>
<h2>Panel credentials</h2>
<p class="muted">These are the credentials for this control panel only.
Applications keep their own logins and passwords, which are not affected.</p>
<form method="post" action="/account">
<form method="post" action="/settings">
<label for="username">Username</label>
<input id="username" name="username" autocomplete="username"
autocapitalize="none" spellcheck="false" value="{{.FormUsername}}" required>
+1 -1
View File
@@ -3,7 +3,7 @@
{{define "wide"}}wide{{end}}
{{define "content"}}
<h1>Server status</h1>
<h1>Status</h1>
{{if .Flash}}<div class="flash">{{.Flash}}</div>{{end}}
+1 -1
View File
@@ -1,5 +1,5 @@
{{define "content"}}
<h1>Panel users</h1>
<h1>Users</h1>
{{if .Flash}}<div class="flash">{{.Flash}}</div>{{end}}
+1 -1
View File
@@ -264,7 +264,7 @@ func TestOnlyThePagesMadeOfDataDeclareThemselvesWide(t *testing.T) {
if err != nil {
t.Fatalf("New: %v", err)
}
wide := map[string]bool{"account": 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}
for name, page := range engine.Pages() {
var buf bytes.Buffer
if err := page.ExecuteTemplate(&buf, "wide", nil); err != nil {
+1 -1
View File
@@ -34,7 +34,7 @@ var pageFiles = map[string][]string{
"setup": {"templates/setup.html"},
"login": {"templates/login.html"},
"dashboard": {"templates/dashboard.html"},
"account": {"templates/account.html"},
"settings": {"templates/settings.html"},
"users": {"templates/users.html"},
"user_form": {"templates/user_form.html"},
"backup": {"templates/backup.html", "templates/encrypt_fields.html"},
+11 -1
View File
@@ -151,7 +151,8 @@ func (s *Server) Handler() http.Handler {
authed.HandleFunc("POST /applications/{aid}/delete", h.HandleDeleteApplication)
authed.HandleFunc("POST /reload", h.HandleReload)
authed.HandleFunc("/account", h.HandleAccount)
authed.HandleFunc("/settings", h.HandleAccount)
authed.HandleFunc("/account", redirectSettings)
authed.HandleFunc("GET /users", h.HandleUsers)
authed.HandleFunc("GET /users/new", h.HandleUserNew)
@@ -174,6 +175,15 @@ func (s *Server) Handler() http.Handler {
return s.secure(mux)
}
// redirectSettings sends legacy /account bookmarks to /settings (308 preserves POST).
func redirectSettings(w http.ResponseWriter, r *http.Request) {
target := "/settings"
if r.URL.RawQuery != "" {
target += "?" + r.URL.RawQuery
}
http.Redirect(w, r, target, http.StatusPermanentRedirect)
}
func redirectHome(w http.ResponseWriter, r *http.Request) {
p, ok := auth.PrincipalFromRequest(r)
if ok && !p.IsGlobal() {