4a54af15cc
test / test (push) Has been cancelled
Co-Authored-By: Claude Sonnet 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: {{.User}}</span>
|
|
{{if eq .Active "account"}}<span aria-current="page">{{template "icon-account"}}Settings</span>{{else}}<a href="/account">{{template "icon-account"}}Settings</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}}
|