142c1defbb
The page column was the 48rem reading measure, which the send log, the mail queue and the system log widened to 64rem for their tables. The navigation and the page are centred as a pair, so that difference did not only change the page's width: it moved the navigation column and the left edge of every card on the way between two pages. The column is now 64rem throughout and the measure lives inside it — a page's heading, cards, back link and version footer are held to 48rem and centred in the column, and the pages made of data opt out and take the column whole. Which pages those are is declared by the page (a "wide" block in its template, the same mechanism as the section index) rather than derived from the navigation entry: .Active cannot tell the send log from a single delivery's page, which is prose and now keeps the measure instead of inheriting the log's width. Two rules follow from centring the children rather than the cards. The heading takes its margins as longhands, since the shorthand re-zeroed the auto side margins and pinned it a measure's width left of the card under it; and the back link is a block, since auto margins centre a block and do nothing for an inline-block. The scrollbar's width is reserved on every page as well. Without it a short page and a long one are laid out in viewports differing by that width, which moved the same things again, in the same direction, for a second reason. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
117 lines
9.2 KiB
HTML
117 lines
9.2 KiB
HTML
{{define "layout.html"}}<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>{{.Title}}</title>
|
|
{{/* The tab icon is the stamp's small-size variant — the initials rather than
|
|
the two-line wordmark, which stops resolving below 32px (the thresholds are
|
|
recorded in docs/assets/selfpost-proof.html). The PNG is declared first and
|
|
the SVG second: a browser takes the last icon whose type it understands, so
|
|
vector wins wherever it is supported and the raster catches the rest. */}}
|
|
<link rel="icon" href="/static/favicon.png" sizes="32x32">
|
|
<link rel="icon" href="/static/favicon.svg" type="image/svg+xml">
|
|
<link rel="stylesheet" href="/static/panel.css">
|
|
{{/* Unless told otherwise, htmx injects a stylesheet element of its own into
|
|
the head for the request-indicator classes. The panel uses no
|
|
hx-indicator, and that injected element would be the one thing the
|
|
Content-Security-Policy has to make an exception for, so it
|
|
is switched off here. */}}
|
|
<meta name="htmx-config" content='{"includeIndicatorStyles":false}'>
|
|
<script src="/static/htmx.min.js" defer></script>
|
|
<script src="/static/panel.js" defer></script>
|
|
</head>
|
|
<body>
|
|
{{/* 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. */}}
|
|
<div class="shell">
|
|
{{if .User}}{{template "nav" .}}{{end}}
|
|
{{/* The page name travels onto <main> as a class so the stylesheet can size a
|
|
page to what it holds — the signed-out pages are narrower than the rest
|
|
(see main.page-* in panel.css). It is .Active, already carried for the
|
|
navigation, rather than a second field every handler would have to
|
|
remember to set. The "wide" block beside it is how a page made of data
|
|
asks for the whole column instead of the reading measure; it is the page's
|
|
own declaration rather than a name in the stylesheet, since .Active cannot
|
|
tell the send log from a single delivery's page. */}}
|
|
<main class="page-{{.Active}} {{template "wide" .}}">
|
|
{{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>
|
|
</div>
|
|
</body>
|
|
</html>{{end}}
|
|
|
|
{{/* nav is the panel's navigation bar. It is rendered here, from the layout, so
|
|
every authenticated page has it without the page's own template having to
|
|
remember to include it; .Active names the current page so it is highlighted
|
|
instead of linking to itself. Unauthenticated pages (login, setup) carry no
|
|
.User and get no nav. */}}
|
|
{{define "nav"}}
|
|
<nav class="nav">
|
|
{{/* The order of the blocks is the order they are read in, top to bottom of
|
|
the column: the mark, the panel's pages, the current page's own sections,
|
|
and the session last. The mark goes to /status, the page the panel opens
|
|
on — named outright rather than as "/", which is only a redirect to it. */}}
|
|
<a class="brand" href="/status"><img src="/static/logo-compact.svg" width="220" height="100" alt="SelfPost"></a>
|
|
<div class="links">
|
|
{{if eq .Active "status"}}<span aria-current="page">{{template "icon-status"}}Status</span>{{else}}<a href="/status">{{template "icon-status"}}Status</a>{{end}}
|
|
{{if eq .Active "domains"}}<span aria-current="page">{{template "icon-domains"}}Domains</span>{{else}}<a href="/domains">{{template "icon-domains"}}Domains</a>{{end}}
|
|
{{if eq .Active "deliveries"}}<span aria-current="page">{{template "icon-deliveries"}}Deliveries</span>{{else}}<a href="/deliveries">{{template "icon-deliveries"}}Deliveries</a>{{end}}
|
|
{{if eq .Active "mail_queue"}}<span aria-current="page">{{template "icon-mail-queue"}}Mail queue</span>{{else}}<a href="/mail-queue">{{template "icon-mail-queue"}}Mail queue</a>{{end}}
|
|
{{if eq .Active "system_log"}}<span aria-current="page">{{template "icon-system-log"}}System log</span>{{else}}<a href="/system-log">{{template "icon-system-log"}}System log</a>{{end}}
|
|
{{if eq .Active "backup"}}<span aria-current="page">{{template "icon-backup"}}Backup</span>{{else}}<a href="/backup">{{template "icon-backup"}}Backup</a>{{end}}
|
|
</div>
|
|
{{template "sections" .}}
|
|
<div class="session">
|
|
<span class="muted">{{.User}}</span>
|
|
{{if eq .Active "account"}}<span aria-current="page">{{template "icon-account"}}Account</span>{{else}}<a href="/account">{{template "icon-account"}}Account</a>{{end}}
|
|
<form class="inline" method="post" action="/logout">
|
|
<button type="submit" class="danger">{{template "icon-sign-out"}}Sign out</button>
|
|
</form>
|
|
</div>
|
|
</nav>
|
|
{{end}}
|
|
|
|
{{/* The index of the current page's own sections, shown in the navigation
|
|
column under the page entries. Most pages are one or two cards and need no
|
|
index, so the block is empty here and only the pages that are genuinely
|
|
long — the domain page, the status page — redefine it with their own list
|
|
(see .sections in panel.css for what it looks like, and panel.js for the
|
|
marking of the section in view). Each page's template file is parsed after
|
|
this one, so its definition replaces this empty one; a page that defines
|
|
nothing keeps it and renders no index. */}}
|
|
{{define "sections"}}{{end}}
|
|
|
|
{{/* Whether the page takes the column whole. Empty here, so a page is held to
|
|
the reading measure unless it says otherwise; the three pages that are
|
|
tables of data or raw log lines redefine this as the word "wide", which
|
|
lands in <main>'s class list (see main.wide in panel.css). Same mechanism
|
|
as "sections" above: each page's template file is parsed after this one,
|
|
so its definition replaces the empty one. */}}
|
|
{{define "wide"}}{{end}}
|
|
|
|
{{/* Navigation icons. Inline SVG rather than an icon font or sprite file: they
|
|
inherit the link's colour through currentColor, cost no extra request, and
|
|
need no exemption from the panel's "default-src 'self'" policy. Each is
|
|
aria-hidden because the entry's own text is already the accessible name;
|
|
the icon is a landmark for the eye, not a second label. Kept as separate
|
|
templates so the nav above stays one readable line per page. */}}
|
|
{{define "icon-status"}}<svg class="icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M1.25 8.5h2.9L6.2 3.4l3.1 9.4 1.9-4.3h3.55"/></svg>{{end}}
|
|
{{define "icon-domains"}}<svg class="icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="8" cy="8" r="6.25"/><path d="M1.9 8h12.2"/><path d="M8 1.75c1.85 1.8 2.8 4 2.8 6.25S9.85 12.45 8 14.25C6.15 12.45 5.2 10.25 5.2 8S6.15 3.55 8 1.75Z"/></svg>{{end}}
|
|
{{define "icon-deliveries"}}<svg class="icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14.25 1.75 1.6 6.6l5 2.05 2.05 5z"/><path d="M14.25 1.75 6.6 8.65"/></svg>{{end}}
|
|
{{define "icon-mail-queue"}}<svg class="icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M1.75 9.5h3.3l1 1.75h3.9l1-1.75h3.3v3.05a1.2 1.2 0 0 1-1.2 1.2H2.95a1.2 1.2 0 0 1-1.2-1.2z"/><path d="M1.75 9.5 3.4 3.2a1.25 1.25 0 0 1 1.2-.95h6.8a1.25 1.25 0 0 1 1.2.95l1.65 6.3"/></svg>{{end}}
|
|
{{define "icon-system-log"}}<svg class="icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3.75 1.75h5.1l3.4 3.4v8.05a1.05 1.05 0 0 1-1.05 1.05H3.75a1.05 1.05 0 0 1-1.05-1.05V2.8a1.05 1.05 0 0 1 1.05-1.05Z"/><path d="M8.85 1.75v3.4h3.4"/><path d="M5.35 8.6h5.3M5.35 11.1h3.5"/></svg>{{end}}
|
|
{{/* A filing cabinet rather than a storage box: the box read as a box, while
|
|
two drawers with handles is the picture of an archive. */}}
|
|
{{define "icon-backup"}}<svg class="icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2.75" y="1.75" width="10.5" height="12.5" rx="1.15"/><path d="M2.75 8h10.5"/><path d="M6.4 4.85h3.2M6.4 11.15h3.2"/></svg>{{end}}
|
|
{{define "icon-account"}}<svg class="icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="8" cy="5.4" r="2.75"/><path d="M2.9 14.25a5.1 5.1 0 0 1 10.2 0"/></svg>{{end}}
|
|
{{/* Sign out is the one icon here that marks an action rather than a page: an
|
|
open door with the arrow leading out of it. */}}
|
|
{{define "icon-sign-out"}}<svg class="icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6.1 14.25H3.65a1.15 1.15 0 0 1-1.15-1.15V2.9a1.15 1.15 0 0 1 1.15-1.15H6.1"/><path d="M10.6 11.15 13.75 8 10.6 4.85"/><path d="M13.75 8H6.35"/></svg>{{end}}
|