panel: match monitoring URLs to their nav labels
/sendlog -> /deliveries, /queue -> /mail-queue, /logtail -> /system-log, along with the HTMX polling fragments under each. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,13 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
|
||||
- panel: the three monitoring pages now live at URLs that match their nav
|
||||
labels — Deliveries at `/deliveries` (was `/sendlog`), Mail queue at
|
||||
`/mail-queue` (was `/queue`), System log at `/system-log` (was `/logtail`).
|
||||
Bookmarks to the old paths stop working.
|
||||
|
||||
## [0.2.0] - 2026-08-03
|
||||
|
||||
- panel: every authenticated page now ends with the running version
|
||||
|
||||
@@ -77,8 +77,8 @@
|
||||
|
||||
1. **Нет раздела «эксплуатация»** — прямое требование ТЗ 11 п. 7
|
||||
([specification.md:427](specification.md)). В README нет ни слова про
|
||||
`/status` (проверки PTR/hostname), `/sendlog` (журнал отправки, фильтры),
|
||||
`/queue` (очередь Postfix), `/logtail` (хвост `mail.log`), `/reload`,
|
||||
`/status` (проверки PTR/hostname), `/deliveries` (журнал отправки, фильтры),
|
||||
`/mail-queue` (очередь Postfix), `/system-log` (хвост `mail.log`), `/reload`,
|
||||
`/account`, `/backup` — при том что всё это реализовано
|
||||
([internal/web/web.go:152](../internal/web/web.go:152)–188). Не описана и
|
||||
процедура апгрейда (бамп тега → `docker compose up -d`), хотя раздел
|
||||
|
||||
@@ -15,7 +15,8 @@ const usernameKey ctxKey = 0
|
||||
//
|
||||
// It also extends the sliding session (plan B.1) on activity, defined as
|
||||
// everything except a GET request carrying HX-Request: the four monitoring
|
||||
// fragments (/status/fragment, /queue/body, /logtail/body, /sendlog/rows)
|
||||
// fragments (/status/fragment, /mail-queue/body, /system-log/body,
|
||||
// /deliveries/rows)
|
||||
// poll every 5s regardless of whether anyone is looking at the tab, so
|
||||
// counting those as activity would make "N days idle" mean "N days since a
|
||||
// browser tab was last open" instead.
|
||||
|
||||
@@ -38,9 +38,9 @@
|
||||
<div class="links">
|
||||
{{if eq .Active "status"}}<span aria-current="page">Status</span>{{else}}<a href="/status">Status</a>{{end}}
|
||||
{{if eq .Active "domains"}}<span aria-current="page">Domains</span>{{else}}<a href="/domains">Domains</a>{{end}}
|
||||
{{if eq .Active "sendlog"}}<span aria-current="page">Deliveries</span>{{else}}<a href="/sendlog">Deliveries</a>{{end}}
|
||||
{{if eq .Active "queue"}}<span aria-current="page">Mail queue</span>{{else}}<a href="/queue">Mail queue</a>{{end}}
|
||||
{{if eq .Active "logtail"}}<span aria-current="page">System log</span>{{else}}<a href="/logtail">System log</a>{{end}}
|
||||
{{if eq .Active "sendlog"}}<span aria-current="page">Deliveries</span>{{else}}<a href="/deliveries">Deliveries</a>{{end}}
|
||||
{{if eq .Active "queue"}}<span aria-current="page">Mail queue</span>{{else}}<a href="/mail-queue">Mail queue</a>{{end}}
|
||||
{{if eq .Active "logtail"}}<span aria-current="page">System log</span>{{else}}<a href="/system-log">System log</a>{{end}}
|
||||
{{if eq .Active "backup"}}<span aria-current="page">Backup</span>{{else}}<a href="/backup">Backup</a>{{end}}
|
||||
</div>
|
||||
<div class="session">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{{define "logtail_body"}}
|
||||
<div id="logtail-body" hx-get="/logtail/body" hx-trigger="every 5s" hx-swap="outerHTML">
|
||||
<div id="logtail-body" hx-get="/system-log/body" hx-trigger="every 5s" hx-swap="outerHTML">
|
||||
{{if .Error}}<p class="error">{{.Error}}</p>{{end}}
|
||||
<span class="code">{{if .Lines}}{{range .Lines}}{{.}}
|
||||
{{end}}{{else}}No log lines yet.{{end}}</span>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{{define "queue_body"}}
|
||||
<div id="queue-body" hx-get="/queue/body" hx-trigger="every 5s" hx-swap="outerHTML">
|
||||
<div id="queue-body" hx-get="/mail-queue/body" hx-trigger="every 5s" hx-swap="outerHTML">
|
||||
{{if .Error}}<p class="error">{{.Error}}</p>{{end}}
|
||||
<span class="code">{{if .Output}}{{.Output}}{{else}}Queue is empty.{{end}}</span>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
<div class="card">
|
||||
<h2>Filter</h2>
|
||||
<form method="get" action="/sendlog">
|
||||
<form method="get" action="/deliveries">
|
||||
<label for="domain">Domain</label>
|
||||
<select id="domain" name="domain">
|
||||
<option value="">All domains</option>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{{define "sendlog_rows"}}
|
||||
<div id="sendlog-rows" hx-get="/sendlog/rows?domain={{.FilterDomain}}&app={{.FilterApp}}&p={{.Page}}"
|
||||
<div id="sendlog-rows" hx-get="/deliveries/rows?domain={{.FilterDomain}}&app={{.FilterApp}}&p={{.Page}}"
|
||||
hx-trigger="every 5s" hx-swap="outerHTML">
|
||||
{{if .Rows}}
|
||||
<table>
|
||||
@@ -22,8 +22,8 @@
|
||||
</table>
|
||||
<p class="muted">
|
||||
Page {{.Page}}{{if .LastPage}} of {{.LastPage}}{{end}}
|
||||
{{if .HasPrev}} · <a href="/sendlog?domain={{.FilterDomain}}&app={{.FilterApp}}&p={{.PrevPage}}">← Newer</a>{{end}}
|
||||
{{if .HasNext}} · <a href="/sendlog?domain={{.FilterDomain}}&app={{.FilterApp}}&p={{.NextPage}}">Older →</a>{{end}}
|
||||
{{if .HasPrev}} · <a href="/deliveries?domain={{.FilterDomain}}&app={{.FilterApp}}&p={{.PrevPage}}">← Newer</a>{{end}}
|
||||
{{if .HasNext}} · <a href="/deliveries?domain={{.FilterDomain}}&app={{.FilterApp}}&p={{.NextPage}}">Older →</a>{{end}}
|
||||
</p>
|
||||
{{else}}
|
||||
<p class="muted">No messages logged yet.</p>
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<p class="error">{{.QueueError}}</p>
|
||||
{{else}}
|
||||
<p>{{if .QueueSummary}}{{.QueueSummary}}{{else}}Mail queue is empty.{{end}}</p>
|
||||
<a class="btn" href="/queue">Full queue</a>
|
||||
<a class="btn" href="/mail-queue">Full queue</a>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -108,10 +108,10 @@ func TestNavMarksActivePage(t *testing.T) {
|
||||
if !strings.Contains(out, `<span aria-current="page">Mail queue</span>`) {
|
||||
t.Errorf("active page is not marked:\n%s", out)
|
||||
}
|
||||
if strings.Contains(out, `href="/queue"`) {
|
||||
if strings.Contains(out, `href="/mail-queue"`) {
|
||||
t.Errorf("active page still links to itself:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, `href="/sendlog"`) {
|
||||
if !strings.Contains(out, `href="/deliveries"`) {
|
||||
t.Errorf("inactive pages are not linked:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
+6
-6
@@ -180,12 +180,12 @@ func (s *Server) Handler() http.Handler {
|
||||
|
||||
// Monitoring screens (spec 7.2.11-13): each page and its HTMX polling
|
||||
// fragment (spec 7.1 — the /rows and /body endpoints return HTML, not JSON).
|
||||
authed.HandleFunc("GET /sendlog", s.handleSendLog)
|
||||
authed.HandleFunc("GET /sendlog/rows", s.handleSendLogRows)
|
||||
authed.HandleFunc("GET /queue", s.handleQueue)
|
||||
authed.HandleFunc("GET /queue/body", s.handleQueueBody)
|
||||
authed.HandleFunc("GET /logtail", s.handleLogTail)
|
||||
authed.HandleFunc("GET /logtail/body", s.handleLogTailBody)
|
||||
authed.HandleFunc("GET /deliveries", s.handleSendLog)
|
||||
authed.HandleFunc("GET /deliveries/rows", s.handleSendLogRows)
|
||||
authed.HandleFunc("GET /mail-queue", s.handleQueue)
|
||||
authed.HandleFunc("GET /mail-queue/body", s.handleQueueBody)
|
||||
authed.HandleFunc("GET /system-log", s.handleLogTail)
|
||||
authed.HandleFunc("GET /system-log/body", s.handleLogTailBody)
|
||||
|
||||
mux.Handle("/", s.requireAuth(authed))
|
||||
|
||||
|
||||
@@ -187,10 +187,10 @@ func (c *panelClient) setRateLimit(path, allowedIP string, maxMessages, windowSe
|
||||
return nil
|
||||
}
|
||||
|
||||
// sendLogRows returns the raw /sendlog/rows HTML fragment, filtered to one
|
||||
// sendLogRows returns the raw /deliveries/rows HTML fragment, filtered to one
|
||||
// domain, for polling a row's status without parsing full HTML into structs.
|
||||
func (c *panelClient) sendLogRows(domain string) (string, error) {
|
||||
_, body, err := c.get("/sendlog/rows?domain=" + url.QueryEscape(domain))
|
||||
_, body, err := c.get("/deliveries/rows?domain=" + url.QueryEscape(domain))
|
||||
return body, err
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user