diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d1e84c..11804bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,26 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version ## [Unreleased] +## [1.8.0] - 2026-08-18 + +Built-in operator documentation in the panel: a CSS-only help drawer and Help +page so Status checks and domain controls are explained without leaving the +panel. + +### Added + +- **Help** navigation entry and `/help` index page. +- CSS-checkbox help drawer (no extra script): seeded topics for Status (Machine, + TLS, PTR, mail queue, inbound when enabled) and domain cards (DNS, records, + DMARC, connection, applications, settings, export). +- «?» entry points on Status and domain card heads open the matching drawer + topic. + +### Changed + +- [guide.md](docs/guide.md) documents the boundary: in-panel help for card + meaning; the repository guide for installation, env vars, and operations. + ## [1.7.0] - 2026-08-18 Optional DMARC aggregate report ingest: accept `rua=` mail on port 25, parse diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index e5d100a..6c9ecb1 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -22,7 +22,7 @@ services: selfpost: - image: ghcr.io/mixeme/selfpost:1.7.0 + image: ghcr.io/mixeme/selfpost:1.8.0 restart: unless-stopped environment: SELFPOST_HOSTNAME: "${SELFPOST_HOSTNAME:?set the mail/panel hostname, e.g. mail.example.com}" diff --git a/docs/guide.md b/docs/guide.md index d534add..9e0b4ec 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -311,6 +311,18 @@ Schedule `extract-cert.sh` (cron or a timer) alongside Traefik's renewals. After sign-in the panel opens on **Status** — the place to answer "is the service healthy and will mail be accepted?" +### In-panel help + +The panel carries short operator notes so day-to-day work does not require +opening this guide. **Help** in the navigation opens an index; each Status or +domain card's «?» opens the same text in a side drawer (CSS only, no extra +script). That content explains what a check or control *means* — kernel +counters, TLS on port 465, forward-confirmed PTR, DNS publish hints, rate +limits, and similar. It is not a second copy of this document: installation, +reverse-proxy setup, backup/restore, environment variables, and security +assumptions stay here in `docs/guide.md` (linked from the drawer and Help +page). + ### Status `/status` shows supervised processes (Postfix, OpenDKIM, panel), TLS diff --git a/docs/plans/panel-docs.md b/docs/plans/panel-docs.md index dac8014..3250079 100644 --- a/docs/plans/panel-docs.md +++ b/docs/plans/panel-docs.md @@ -1,6 +1,6 @@ # Plan: panel-docs (in-panel operator documentation) -**Status:** candidate +**Status:** agreed **Date:** 2026-08-17 **Version:** `1.x` MINOR; `candidate` until explicitly agreed. @@ -46,9 +46,9 @@ Target version cut: **`1.8.0`** (MINOR). One commit per step; code only after roadmap status is **agreed**. See [development.md](../development.md) § Plan checklists. -- [ ] Help drawer / pages shell (CSS checkbox pattern from mockups) — **Sonnet** -- [ ] Seed Status blurbs (Machine, TLS, PTR, …) — **Sonnet** -- [ ] «?» entry points on domain cards — **Sonnet** -- [ ] [guide.md](../guide.md) boundary: in-panel help vs full guide — **Sonnet** -- [ ] Template tests — **Sonnet** -- [ ] `go vet`, `go test` on touched packages — **Haiku** +- [x] Help drawer / pages shell (CSS checkbox pattern from mockups) — **Sonnet** +- [x] Seed Status blurbs (Machine, TLS, PTR, …) — **Sonnet** +- [x] «?» entry points on domain cards — **Sonnet** +- [x] [guide.md](../guide.md) boundary: in-panel help vs full guide — **Sonnet** +- [x] Template tests — **Sonnet** +- [x] `go vet`, `go test` on touched packages — **Haiku** diff --git a/docs/roadmap.md b/docs/roadmap.md index 6f58a0f..4f85c3f 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -29,7 +29,7 @@ in `git log` and [CHANGELOG.md](../CHANGELOG.md). | ID | Topic | Status | Progress | Plan | |---|---|---|---|---| | contributing | `CONTRIBUTING.md` | candidate | — | — | -| panel-docs | In-panel operator documentation | candidate | 0/6 | [plans/panel-docs.md](plans/panel-docs.md) | +| panel-docs | In-panel operator documentation | agreed | 6/6 | [plans/panel-docs.md](plans/panel-docs.md) | | schema-squash | Squash SQLite migrations into a 2.x baseline | **2.x** | — | — | **Recommended order** (not binding): the next feature is **panel-docs** once diff --git a/internal/web/handlers/handlers_help.go b/internal/web/handlers/handlers_help.go new file mode 100644 index 0000000..d8c8dbf --- /dev/null +++ b/internal/web/handlers/handlers_help.go @@ -0,0 +1,22 @@ +package handlers + +import ( + "net/http" + + "github.com/mixeme/selfpost/internal/web/auth" +) + +func (h *Handlers) HandleHelp(w http.ResponseWriter, r *http.Request) { + p, ok := h.principal(r) + if !ok { + http.Error(w, "internal error", http.StatusInternalServerError) + return + } + data := map[string]any{ + "Title": "SelfPost — help", + "User": auth.CurrentUser(r), + "Active": "help", + "IsGlobal": p.IsGlobal(), + } + h.view.Render(w, http.StatusOK, "help", data) +} diff --git a/internal/web/view/static/panel.css b/internal/web/view/static/panel.css index 65b1747..cfae068 100644 --- a/internal/web/view/static/panel.css +++ b/internal/web/view/static/panel.css @@ -756,3 +756,71 @@ button.copy { flex: none; } .encrypt .encrypt-fields { margin-top: 0; } .encrypt-fields label { margin-top: 0.7rem; } .encrypt-fields .muted { margin: 0.5rem 0 0; font-size: 0.85rem; } + +/* Screen-reader accessible controls that drive UI state (help drawer panes). */ +.ctrl { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); } + +.page-head { + display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; + gap: 0.5rem 1rem; margin-bottom: 1rem; +} +.page-head h1 { margin: 0; } + +.card-head { + display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; + gap: 0.5rem 0.75rem; margin-bottom: 0.7rem; +} +.card-head h2 { margin: 0; } + +.help-link { + flex: none; width: 1.35rem; height: 1.35rem; display: inline-flex; align-items: center; + justify-content: center; border-radius: 50%; border: 1px solid var(--control-border); + color: var(--muted); text-decoration: none; font-family: var(--font-mono); + font-size: 0.75rem; font-weight: 600; background: var(--card-bg); cursor: pointer; margin: 0; +} +.help-link:hover { color: var(--accent-text); border-color: var(--accent-fill); } + +/* Help drawer — CSS checkbox/radio, no script required. */ +.help-drawer { + display: none; position: fixed; top: 0; right: 0; bottom: 0; + width: min(26rem, 100vw); z-index: 30; + background: var(--card-bg); border-left: 1px solid var(--border); + padding: 1.25rem 1.35rem 2rem; overflow-y: auto; +} +html:has(input[name="help"]:checked):not(:has(#help-off:checked)) .help-drawer, +html:has(input[name="help"]:checked):not(:has(#help-off:checked)) .help-scrim { display: block; } +.help-pane { display: none; } +html:has(#help-index:checked) .help-pane-index, +html:has(#help-status:checked) .help-pane-status, +html:has(#help-password:checked) .help-pane-password, +html:has(#help-dns:checked) .help-pane-dns, +html:has(#help-records:checked) .help-pane-records, +html:has(#help-dmarc:checked) .help-pane-dmarc, +html:has(#help-connection:checked) .help-pane-connection, +html:has(#help-apps:checked) .help-pane-apps, +html:has(#help-domain-settings:checked) .help-pane-domain-settings, +html:has(#help-export:checked) .help-pane-export { display: block; } +.help-scrim { + display: none; position: fixed; inset: 0; z-index: 25; + background: rgba(18, 22, 28, 0.28); cursor: pointer; +} +.help-drawer h2 { margin-top: 1.2rem; } +.help-drawer h2:first-of-type, +.help-pane h2 { margin-top: 0; } +.help-drawer .toc { list-style: none; margin: 0.7rem 0 0; padding: 0; } +.help-drawer .toc li { margin: 0.35rem 0 0; } +.help-drawer .toc label { + margin: 0; font-weight: 600; color: var(--accent-text); cursor: pointer; +} +.help-drawer .more { margin-top: 1.2rem; font-size: 0.85rem; } +.help-drawer .more label { + display: inline; margin: 0; font-weight: 600; color: var(--accent-text); cursor: pointer; +} +.help-close { + position: absolute; top: 0.8rem; right: 0.8rem; margin: 0; padding: 0.35rem 0.6rem; + font-size: 0.8rem; font-weight: 600; background: var(--surface-bg); color: var(--fg); + border: 1px solid var(--control-border); border-radius: 5px; cursor: pointer; +} +@media (max-width: 66rem) { + .help-drawer { width: 100vw; } +} diff --git a/internal/web/view/templates/domain_detail.html b/internal/web/view/templates/domain_detail.html index d911bb3..e422802 100644 --- a/internal/web/view/templates/domain_detail.html +++ b/internal/web/view/templates/domain_detail.html @@ -67,7 +67,10 @@ {{if .NewCred}}
Shown once only and not stored. Copy it now — if it is lost, regenerate a new one.
@@ -85,7 +88,10 @@ {{/* Two rows of two checks (.check-cols). */}}Cached a few minutes — use Re-check after publishing.
@@ -141,7 +147,10 @@DKIM
{{template "host_type_copy" .Record.Name}} @@ -159,7 +168,10 @@Same for every domain. Authenticate with an application login from below.
@@ -262,7 +277,10 @@SASL logins for this domain — wildcard (*@domain) or a fixed address list.
@@ -376,7 +394,10 @@Secret file — transfer securely, or encrypt
below as .spde.
{{.ExportErr}}
{{end}} diff --git a/internal/web/view/templates/help.html b/internal/web/view/templates/help.html new file mode 100644 index 0000000..a586de6 --- /dev/null +++ b/internal/web/view/templates/help.html @@ -0,0 +1,62 @@ +{{define "content"}} +Short operator notes inside the panel — not a second copy of +the full guide. The cards keep their readings and controls; this page (and the +drawer opened from each card's «?») holds the explanations that do not belong +on the card itself.
+ +Domain page
+Topics marked as labels open the same text in the help + drawer. For installation, backup, and environment variables see the + operator + guide in the repository.
+CPU and memory are the container's own readings, not the host's spare + capacity. Network is a short window, not a daily total. High CPU with an empty + queue usually means something else on the box — not SelfPost being slow to + send.
+Port 465 presents the certificate the reverse proxy (or the image) + mounted. The panel does not issue certificates. A warn here is expires soon; + an error is missing or unreadable, and clients will refuse submission.
+Forward-confirmed reverse DNS: the A/AAAA for
+ SELFPOST_HOSTNAME must reverse to that same name. PTR is set at
+ the hosting provider, not in this panel.
{{if .}} · Help page{{end}}
{{end}} + +{{/* card_help — «?» on a card head; .ID is the topic slug (dns, records, …). */}} +{{define "card_help"}} + +{{end}} + +{{define "help_drawer"}} + + +{{end}} diff --git a/internal/web/view/templates/layout.html b/internal/web/view/templates/layout.html index 5ce8f93..769c7d1 100644 --- a/internal/web/view/templates/layout.html +++ b/internal/web/view/templates/layout.html @@ -22,6 +22,7 @@ +{{if .User}}{{template "help_inputs" .}}{{end}} {{/* Two columns: the navigation on the left and the page beside it, centred together (see .shell in panel.css). The signed-out pages have no navigation and the shell holds their card alone. */}} @@ -50,6 +51,7 @@