panel: show the running version in the layout footer
Nothing in the UI said which build was running, though it is the value a backup manifest is compared against on restore and the first thing worth knowing when the panel misbehaves — it was only in the startup log line and `panel -version`. Add it as a small footer in the shared layout, supplied from render() alongside .Active so no handler has to pass it, and gated on .User: the login and setup pages face the internet and should not advertise a version. Tests cover both the footer and render() supplying the key, since neither is visible from any single handler. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,11 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- panel: every authenticated page now ends with the running version
|
||||
(`SelfPost 1.1.0`) in a small footer. It is the value a backup manifest is
|
||||
checked against on restore, and the first thing to establish when the panel
|
||||
behaves unexpectedly. The login and setup pages deliberately do not show it.
|
||||
|
||||
- panel: the domain page now shows the **SPF and DMARC records it expects**,
|
||||
with host, value and a Copy button, next to the DKIM record it already
|
||||
showed — previously it only said "also configure SPF and DMARC (see the
|
||||
|
||||
@@ -63,6 +63,9 @@ td.actions { text-align: right; }
|
||||
@media (prefers-color-scheme: dark) { .code { background: #14171a !important; border-color: #2b3138 !important; } }
|
||||
h2 { font-size: 1.05rem; margin: 0 0 0.4rem; }
|
||||
.back { display: inline-block; margin-bottom: 1rem; }
|
||||
/* Build version, closing every authenticated page. Quiet on purpose: it is
|
||||
reference material, not something to read on the way past. */
|
||||
.version { margin-top: 1.6rem; text-align: right; font-size: 0.8rem; color: #6b7280; }
|
||||
select, textarea {
|
||||
width: 100%; padding: 0.55rem 0.7rem; font-size: 1rem;
|
||||
border: 1px solid #cfd4da; border-radius: 6px; background: #fff; color: inherit;
|
||||
|
||||
@@ -79,10 +79,13 @@ func (s *Server) render(w http.ResponseWriter, status int, page string, data any
|
||||
// The layout's navigation compares .Active against each item, so the key
|
||||
// must exist on every authenticated page. Defaulting it here keeps a page
|
||||
// that forgets it from failing to render — it simply highlights nothing.
|
||||
// .Version, shown in the layout's footer, is supplied the same way: it is
|
||||
// the same value on every page, so no handler should have to pass it.
|
||||
if m, ok := data.(map[string]any); ok {
|
||||
if _, has := m["Active"]; !has {
|
||||
m["Active"] = ""
|
||||
}
|
||||
m["Version"] = s.cfg.Version
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := tmpl.ExecuteTemplate(&buf, "layout.html", data); err != nil {
|
||||
|
||||
@@ -19,6 +19,11 @@
|
||||
<main>
|
||||
{{if .User}}{{template "nav" .}}{{end}}
|
||||
{{template "content" .}}
|
||||
{{/* The running version, on every authenticated page: it is what a backup
|
||||
manifest is checked against on restore and the first thing to establish
|
||||
when something behaves unexpectedly. Only for signed-in administrators —
|
||||
the login and setup pages must not advertise it to the internet. */}}
|
||||
{{if .User}}<footer class="version">SelfPost {{.Version}}</footer>{{end}}
|
||||
</main>
|
||||
</body>
|
||||
</html>{{end}}
|
||||
|
||||
@@ -3,6 +3,8 @@ package web
|
||||
import (
|
||||
"bytes"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
@@ -27,6 +29,68 @@ func TestEveryPageResolvesNav(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// The version comes from render(), not from each handler's data map, so the
|
||||
// footer is only correct as long as every page composes with the layout and
|
||||
// render keeps supplying the key. Both are asserted here rather than trusted.
|
||||
func TestLayoutShowsTheVersionOnlyWhenSignedIn(t *testing.T) {
|
||||
tmpl, err := loadTemplates()
|
||||
if err != nil {
|
||||
t.Fatalf("loadTemplates: %v", err)
|
||||
}
|
||||
rendered := 0
|
||||
for name := range tmpl.pages {
|
||||
var buf bytes.Buffer
|
||||
err := tmpl.pages[name].ExecuteTemplate(&buf, "layout.html", map[string]any{
|
||||
"Title": "t", "User": "admin", "Active": "", "Version": "9.9.9-test",
|
||||
})
|
||||
if err != nil {
|
||||
// Pages whose content block needs more data than this cannot be
|
||||
// rendered here; the footer is in the shared layout, so one page
|
||||
// that does render proves it for all of them.
|
||||
continue
|
||||
}
|
||||
rendered++
|
||||
if !strings.Contains(buf.String(), "SelfPost 9.9.9-test") {
|
||||
t.Errorf("page %q does not show the version in the layout footer", name)
|
||||
}
|
||||
}
|
||||
if rendered == 0 {
|
||||
t.Fatal("no page rendered, so the footer was never actually checked")
|
||||
}
|
||||
|
||||
// Signed out (login, setup) the version must not be advertised.
|
||||
var buf bytes.Buffer
|
||||
if err := tmpl.pages["login"].ExecuteTemplate(&buf, "layout.html", map[string]any{
|
||||
"Title": "t", "Active": "", "Version": "9.9.9-test",
|
||||
}); err != nil {
|
||||
t.Fatalf("execute login: %v", err)
|
||||
}
|
||||
if strings.Contains(buf.String(), "9.9.9-test") {
|
||||
t.Errorf("the login page shows the version to unauthenticated visitors:\n%s", buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderSuppliesTheVersion(t *testing.T) {
|
||||
tmpl, err := loadTemplates()
|
||||
if err != nil {
|
||||
t.Fatalf("loadTemplates: %v", err)
|
||||
}
|
||||
s := &Server{tmpl: tmpl, cfg: Config{Version: "9.9.9-test"}}
|
||||
rec := httptest.NewRecorder()
|
||||
data := map[string]any{"Title": "t", "User": "admin"}
|
||||
s.render(rec, http.StatusOK, "backup", data)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want 200", rec.Code)
|
||||
}
|
||||
if got := data["Version"]; got != "9.9.9-test" {
|
||||
t.Errorf("render did not supply Version (got %v)", got)
|
||||
}
|
||||
if !strings.Contains(rec.Body.String(), "SelfPost 9.9.9-test") {
|
||||
t.Errorf("rendered page does not show the version:\n%s", rec.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNavMarksActivePage(t *testing.T) {
|
||||
tmpl, err := loadTemplates()
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user