Stage 2, step 6. Written by Sonnet, reviewed by Opus.
- Six pages named after their mockups: system-log, backup, users,
user, user-delete, help, with typed page data (view/server.go,
help.go), fixtures and tests. The old templates of the six are gone
(user_form.html, encrypt_fields.html deleted, the others rewritten)
and their names are off legacy_pages.txt, which now holds mail_queue
alone.
- Help is six help_topic blocks (checks, certificate, rdns, dns, apps,
limits); a test renders every fixture page and fails on a Help link
without a topic. The help drawer (help_drawer.html and its includes
in layout_legacy.html) is removed; its rules in legacy.css go with
the next step.
- The user form sets the user's e-mail (plan, Account e-mail): create
and update, validated, and a changed address resyncs DMARC for a
user whose default is the account e-mail - through the helper the
Account profile now shares.
- Handlers pass typed data; routes, POST targets, field names,
validation and authorization are unchanged. A test loads the user
form for four reach combinations and for the only global user, posts
it back untouched and finds the user unchanged.
Fixed on the way
- The user delete confirmation said the domains were not affected. A
domain that follows the user's default report address is left with
none; the page names those domains now.
- A refused backup lost the encryption choice and printed a literal
<code> element in its message.
Differences from the mockups, for the owner:
- Help has no DMARC and no Inbound topic: its outline fixes two boxes
of three topics, although other mockups link to #dmarc and #inbound.
Until the owner decides, Account's Help link goes to #dns and the
inbound MX box has none;
- system log: newest line first, as drawn; "refreshes on its own" (5 s
active, 30 s idle); only status=, warning: and error: lines are
coloured;
- backup: the file field is a plain file input; the encryption box is
ticked, as drawn and as on the domain export, so a plain download
means unticking it first;
- users: an E-mail column; the flash does not name the user;
- user form: no lead; the domain lists are no longer hidden by script
when Global is chosen (the server ignores them for that role).
Evidence
- docs/assets/panel-redesign/evidence/system-log.png, backup.png,
users.png, user.png, user-delete.png, help.png: 1440 px wide,
headless Edge, real templates served locally with their fixtures -
NOT from the container (no Docker on this machine).
- panel/audit.js on the six pages at 1440 and 1280: 720 texts checked,
problems: [].
- The system log's polling swap was not exercised in a browser; it
uses the mechanism of Overview and Health.
- go test -count=1 ./internal/web/... ./cmd/panel
ok internal/web
ok internal/web/auth
ok internal/web/handlers
ok internal/web/validate
ok internal/web/view
ok cmd/panel
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
100 lines
5.5 KiB
Go
100 lines
5.5 KiB
Go
package view
|
|
|
|
// The data of the Help page (templates/help.html, mockup help.html): what each
|
|
// screen checks and what to do when it is not green, as topics that stack inside
|
|
// two boxes. The head of a numbered box on another page links to its topic with
|
|
// the anchor of that topic's ID (Box.Help); nothing is shown in a drawer.
|
|
//
|
|
// The topics are plain data written here once. A topic is only worded for
|
|
// something this build has: Help explains the checks and records the panel
|
|
// shows, not the features of a later release.
|
|
|
|
// Help is the data of the Help page. The Overview box explains the checks of
|
|
// Overview and Health, which only the global role sees, so it is absent for a
|
|
// domain administrator along with its entries in the menu.
|
|
type Help struct {
|
|
Meta
|
|
Head Head
|
|
|
|
Menu SideMenu
|
|
|
|
Overview Box
|
|
OverviewTopics []HelpTopic
|
|
// ShowOverview is whether the Overview box is present.
|
|
ShowOverview bool
|
|
|
|
Outbound Box
|
|
OutboundTopics []HelpTopic
|
|
}
|
|
|
|
// Anchors of the Help page. A link to one of these is a Box.Help of another
|
|
// page; the page must carry exactly the topics its links point at.
|
|
const (
|
|
HelpChecks = "checks"
|
|
HelpCertificate = "certificate"
|
|
HelpRDNS = "rdns"
|
|
HelpDNS = "dns"
|
|
HelpApps = "apps"
|
|
HelpLimits = "limits"
|
|
)
|
|
|
|
// NewHelp builds the page for a viewer. global is the role that sees Overview
|
|
// and Server, and so the topics about their checks.
|
|
func NewHelp(m Meta, global bool) *Help {
|
|
m.Title, m.Section, m.Page = "Help", "user", "help"
|
|
p := &Help{
|
|
Meta: m,
|
|
Head: Head{
|
|
Kicker: "Reference",
|
|
Title: "Help",
|
|
Lead: Plain("What each screen checks and what to do when it is not green. The ? in a box's head opens its topic here."),
|
|
},
|
|
ShowOverview: global,
|
|
Overview: Box{No: "01", Title: "Overview"},
|
|
OverviewTopics: []HelpTopic{
|
|
{ID: HelpChecks, Title: "Health checks", Body: []Text{
|
|
Rich("Six checks, and the worst of them is the postmark. ", TagOf("ok", ""), " needs nothing; ",
|
|
TagOf("warn", ""), " still delivers mail but will stop doing so if ignored; ",
|
|
TagOf("fail", ""), " means mail is being lost or refused now. ", Link("/server/health", "Health"),
|
|
" shows the readings behind them: CPU and memory are the container's own, not the host's spare capacity, and network is a short window, not a daily total."),
|
|
}},
|
|
{ID: HelpCertificate, Title: "TLS certificate", Body: []Text{
|
|
Plain("The certificate Postfix presents on 465 and 587. The reverse proxy or the image mounts it, the panel does not issue it. It warns 14 days before expiry and fails once expired, or when the file is missing or unreadable — clients that verify certificates then refuse to connect."),
|
|
}},
|
|
{ID: HelpRDNS, Title: "Hostname and reverse DNS", Body: []Text{
|
|
Rich("The hostname (", Code("SELFPOST_HOSTNAME"), ") must resolve to this server's address, and that address must resolve back to the hostname. Large receivers reject or spam-folder mail from hosts where the two disagree. The PTR record is set at your hosting provider, not in your DNS zone."),
|
|
}},
|
|
},
|
|
Outbound: Box{No: "02", Title: "Outbound"},
|
|
OutboundTopics: []HelpTopic{
|
|
{ID: HelpDNS, Title: "DNS records", Body: []Text{
|
|
Rich("DKIM proves the message was signed by this relay, SPF lists the addresses allowed to send for the domain, DMARC tells receivers what to do when both fail and where to report. Publish all three exactly as shown; merge SPF into an existing record rather than adding a second one. A domain's status is the worst of its three records, and results are cached for a few minutes, so use Re-check DNS after publishing. The SPF check is shallow: it looks for this server's address in the record itself and does not follow ",
|
|
Code("include:"), " or ", Code("redirect="), ", so a record that relies on them is reported as a warning, not a failure. ",
|
|
Code("p=none"), " does not affect delivery; tighten to ", Code("quarantine"), ", then ", Code("reject"),
|
|
", once the reports look clean. A domain's report address is set in its settings; the default it follows belongs to a user and is set under ",
|
|
Link("/account#dmarc", "Account"), "."),
|
|
}},
|
|
{ID: HelpApps, Title: "Applications", Body: []Text{
|
|
Plain("An application is one SASL login, unique across all domains. It may send as any address of its domain or only as listed addresses, and, with a list of client IPs set, only from those addresses. The password is shown once at creation or regeneration and is not stored in readable form."),
|
|
}},
|
|
{ID: HelpLimits, Title: "Rate limits", Body: []Text{
|
|
Rich("Level 1 is a per-IP ceiling set in ", Code(".env"), ". Level 2 is optional, per domain and per application, entered by hand or computed from the last 30 days of sending, and can never exceed level 1. An application's own limit replaces its domain's."),
|
|
}},
|
|
},
|
|
}
|
|
p.Menu = SideMenu{}
|
|
if global {
|
|
p.Menu.Groups = append(p.Menu.Groups, MenuGroup{Label: "Overview", Items: []MenuItem{
|
|
{Label: "Health checks", Icon: "ti-activity-heartbeat", Href: "#" + HelpChecks},
|
|
{Label: "TLS certificate", Icon: "ti-certificate", Href: "#" + HelpCertificate},
|
|
{Label: "Reverse DNS", Icon: "ti-arrows-exchange", Href: "#" + HelpRDNS},
|
|
}})
|
|
}
|
|
p.Menu.Groups = append(p.Menu.Groups, MenuGroup{Label: "Outbound", Items: []MenuItem{
|
|
{Label: "DNS records", Icon: "ti-world-check", Href: "#" + HelpDNS},
|
|
{Label: "Applications", Icon: "ti-apps", Href: "#" + HelpApps},
|
|
{Label: "Rate limits", Icon: "ti-gauge", Href: "#" + HelpLimits},
|
|
}})
|
|
return p
|
|
}
|