panel-redesign: Server pages and Help on the kit; the help drawer is gone
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>
This commit is contained in:
@@ -109,7 +109,7 @@ func TestRenderSuppliesTheVersion(t *testing.T) {
|
||||
}
|
||||
rec := httptest.NewRecorder()
|
||||
data := map[string]any{"Title": "t", "User": "admin"}
|
||||
engine.Render(rec, http.StatusOK, "backup", data)
|
||||
engine.Render(rec, http.StatusOK, "mail_queue", data)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want 200", rec.Code)
|
||||
@@ -138,7 +138,7 @@ func TestNavMarksActivePage(t *testing.T) {
|
||||
t.Fatalf("New: %v", err)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
err = engine.Page("help").ExecuteTemplate(&buf, "nav", map[string]any{
|
||||
err = engine.Page("mail_queue").ExecuteTemplate(&buf, "nav", map[string]any{
|
||||
"User": "admin",
|
||||
"Active": "mail_queue",
|
||||
"IsGlobal": true,
|
||||
@@ -166,7 +166,7 @@ func TestNavLeadsWithStatusAndPointsDomainsAtItsOwnPath(t *testing.T) {
|
||||
t.Fatalf("New: %v", err)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := engine.Page("help").ExecuteTemplate(&buf, "nav", map[string]any{
|
||||
if err := engine.Page("mail_queue").ExecuteTemplate(&buf, "nav", map[string]any{
|
||||
"User": "admin",
|
||||
"Active": "status",
|
||||
"IsGlobal": true,
|
||||
@@ -195,7 +195,7 @@ func TestNavShowsInboundWhenEnabled(t *testing.T) {
|
||||
}
|
||||
engine.SetInboundEnabled(true)
|
||||
var buf bytes.Buffer
|
||||
if err := engine.Page("help").ExecuteTemplate(&buf, "nav", map[string]any{
|
||||
if err := engine.Page("mail_queue").ExecuteTemplate(&buf, "nav", map[string]any{
|
||||
"User": "admin",
|
||||
"Active": "status",
|
||||
"IsGlobal": true,
|
||||
@@ -226,7 +226,6 @@ func TestOnlyThePagesMadeOfDataDeclareThemselvesWide(t *testing.T) {
|
||||
}
|
||||
wide := map[string]bool{
|
||||
"mail_queue": true,
|
||||
"system_log": true,
|
||||
"inbound": true, "inbound_domain": true,
|
||||
}
|
||||
for name, page := range engine.Pages() {
|
||||
@@ -271,8 +270,6 @@ func TestSettingsPageDocumentsRateLimits(t *testing.T) {
|
||||
|
||||
func TestDrillDownPagesPlaceBackLinkAboveContent(t *testing.T) {
|
||||
drillDown := map[string]bool{
|
||||
"user_form.html": true,
|
||||
"user_delete.html": true,
|
||||
"inbound_domain.html": true,
|
||||
"inbound_delete.html": true,
|
||||
}
|
||||
@@ -419,24 +416,27 @@ func TestMailQueuePageRendersRetryPolicy(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthenticatedLayoutIncludesHelpDrawer(t *testing.T) {
|
||||
// The help drawer is gone from the old layout as well: a topic is a section of
|
||||
// the Help page, linked from the head of the box it explains, and the Help
|
||||
// entry of the navigation leads there.
|
||||
func TestAuthenticatedLayoutHasNoHelpDrawer(t *testing.T) {
|
||||
engine, err := New("test")
|
||||
if err != nil {
|
||||
t.Fatalf("New: %v", err)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := engine.Page("help").ExecuteTemplate(&buf, "layout_legacy.html", map[string]any{
|
||||
"Title": "t", "User": "admin", "Active": "help", "IsGlobal": true,
|
||||
if err := engine.Page("mail_queue").ExecuteTemplate(&buf, "layout_legacy.html", map[string]any{
|
||||
"Title": "t", "User": "admin", "Active": "mail_queue", "IsGlobal": true,
|
||||
}); err != nil {
|
||||
t.Fatalf("execute help layout: %v", err)
|
||||
t.Fatalf("execute legacy layout: %v", err)
|
||||
}
|
||||
out := buf.String()
|
||||
for _, want := range []string{
|
||||
`id="help-off"`, `class="help-drawer"`, `help-pane-status`,
|
||||
`for="help-dns"`, `href="/help"`,
|
||||
} {
|
||||
if !strings.Contains(out, want) {
|
||||
t.Errorf("authenticated layout missing %q", want)
|
||||
if !strings.Contains(out, `href="/help"`) {
|
||||
t.Error("the authenticated layout has no link to the Help page")
|
||||
}
|
||||
for _, gone := range []string{`id="help-off"`, `help-drawer`, `help-pane`, `for="help-`, `help-scrim`} {
|
||||
if strings.Contains(out, gone) {
|
||||
t.Errorf("the authenticated layout still carries the help drawer (%q)", gone)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -479,7 +479,7 @@ func TestNavIncludesHelp(t *testing.T) {
|
||||
t.Fatalf("New: %v", err)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := engine.Page("help").ExecuteTemplate(&buf, "nav", map[string]any{
|
||||
if err := engine.Page("mail_queue").ExecuteTemplate(&buf, "nav", map[string]any{
|
||||
"User": "admin", "Active": "domains", "IsGlobal": true,
|
||||
}); err != nil {
|
||||
t.Fatalf("execute nav: %v", err)
|
||||
@@ -502,7 +502,7 @@ func TestLegacyNavFollowsTheUsersReach(t *testing.T) {
|
||||
data["User"], data["Active"] = "ops", ""
|
||||
data["InboundEnabled"], data["DMARCEnabled"] = true, true
|
||||
var buf bytes.Buffer
|
||||
if err := engine.Page("help").ExecuteTemplate(&buf, "nav", data); err != nil {
|
||||
if err := engine.Page("mail_queue").ExecuteTemplate(&buf, "nav", data); err != nil {
|
||||
t.Fatalf("execute nav: %v", err)
|
||||
}
|
||||
return buf.String()
|
||||
|
||||
Reference in New Issue
Block a user