From 1d30605533c5ea6ec3ea5df7e89036b3151cd15d Mon Sep 17 00:00:00 2001 From: Mikhail Yenuchenko Date: Wed, 12 Aug 2026 15:24:14 +0300 Subject: [PATCH] panel: align page URLs, titles, and headings 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 Co-authored-by: Cursor --- CHANGELOG.md | 9 +++++++++ docs/architecture.md | 3 ++- docs/guide.md | 2 +- internal/web/handlers/handlers_account.go | 6 +++--- internal/web/handlers/handlers_backup.go | 2 +- internal/web/handlers/handlers_domains.go | 2 +- internal/web/handlers/handlers_users.go | 6 +++++- internal/web/view/templates/domain_detail.html | 2 +- internal/web/view/templates/layout.html | 2 +- .../view/templates/{account.html => settings.html} | 4 ++-- internal/web/view/templates/status.html | 2 +- internal/web/view/templates/users.html | 2 +- internal/web/view/templates_test.go | 2 +- internal/web/view/view.go | 2 +- internal/web/web.go | 12 +++++++++++- 15 files changed, 41 insertions(+), 17 deletions(-) rename internal/web/view/templates/{account.html => settings.html} (98%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c3de1e..735aad0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 `

` 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 diff --git a/docs/architecture.md b/docs/architecture.md index aba6fe2..7d3b663 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -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 diff --git a/docs/guide.md b/docs/guide.md index 0b76805..17b0fe4 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -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 diff --git a/internal/web/handlers/handlers_account.go b/internal/web/handlers/handlers_account.go index 5661e5a..c359327 100644 --- a/internal/web/handlers/handlers_account.go +++ b/internal/web/handlers/handlers_account.go @@ -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 { diff --git a/internal/web/handlers/handlers_backup.go b/internal/web/handlers/handlers_backup.go index 5d14e8e..b07b83e 100644 --- a/internal/web/handlers/handlers_backup.go +++ b/internal/web/handlers/handlers_backup.go @@ -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 diff --git a/internal/web/handlers/handlers_domains.go b/internal/web/handlers/handlers_domains.go index b68bfaf..d090be4 100644 --- a/internal/web/handlers/handlers_domains.go +++ b/internal/web/handlers/handlers_domains.go @@ -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 diff --git a/internal/web/handlers/handlers_users.go b/internal/web/handlers/handlers_users.go index 6edb923..ce80617 100644 --- a/internal/web/handlers/handlers_users.go +++ b/internal/web/handlers/handlers_users.go @@ -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 diff --git a/internal/web/view/templates/domain_detail.html b/internal/web/view/templates/domain_detail.html index c2ea521..0f894be 100644 --- a/internal/web/view/templates/domain_detail.html +++ b/internal/web/view/templates/domain_detail.html @@ -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 rua= unless a - report address is configured below or in Settings. + report address is configured below or in Settings. Publish both as TXT records.

diff --git a/internal/web/view/templates/layout.html b/internal/web/view/templates/layout.html index 1f44f9a..002d38c 100644 --- a/internal/web/view/templates/layout.html +++ b/internal/web/view/templates/layout.html @@ -81,7 +81,7 @@ {{template "sections" .}}
{{template "icon-account"}}User: {{.User}} - {{if eq .Active "account"}}{{template "icon-settings"}}Settings{{else}}{{template "icon-settings"}}Settings{{end}} + {{if eq .Active "settings"}}{{template "icon-settings"}}Settings{{else}}{{template "icon-settings"}}Settings{{end}}
diff --git a/internal/web/view/templates/account.html b/internal/web/view/templates/settings.html similarity index 98% rename from internal/web/view/templates/account.html rename to internal/web/view/templates/settings.html index f0471d1..9153c33 100644 --- a/internal/web/view/templates/account.html +++ b/internal/web/view/templates/settings.html @@ -9,7 +9,7 @@ {{if .Error}}

{{.Error}}

{{end}} {{if .ShowDMARC}} -
+

Panel credentials

@@ -80,7 +80,7 @@ this one stays signed in.

Panel credentials

These are the credentials for this control panel only. Applications keep their own logins and passwords, which are not affected.

- + diff --git a/internal/web/view/templates/status.html b/internal/web/view/templates/status.html index bf6353d..109b14f 100644 --- a/internal/web/view/templates/status.html +++ b/internal/web/view/templates/status.html @@ -3,7 +3,7 @@ {{define "wide"}}wide{{end}} {{define "content"}} -

Server status

+

Status

{{if .Flash}}
{{.Flash}}
{{end}} diff --git a/internal/web/view/templates/users.html b/internal/web/view/templates/users.html index 62e82ed..5e932b5 100644 --- a/internal/web/view/templates/users.html +++ b/internal/web/view/templates/users.html @@ -1,5 +1,5 @@ {{define "content"}} -

Panel users

+

Users

{{if .Flash}}
{{.Flash}}
{{end}} diff --git a/internal/web/view/templates_test.go b/internal/web/view/templates_test.go index 642d125..4006885 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{"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 { diff --git a/internal/web/view/view.go b/internal/web/view/view.go index cec4935..6cbd788 100644 --- a/internal/web/view/view.go +++ b/internal/web/view/view.go @@ -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"}, diff --git a/internal/web/web.go b/internal/web/web.go index ba7d59b..f9504fa 100644 --- a/internal/web/web.go +++ b/internal/web/web.go @@ -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() {