134 lines
11 KiB
HTML
134 lines
11 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" .}}
|
|
{{/* Appropriate Legal Notices (AGPL-3.0): copyright, how to read the licence,
|
|
where the Corresponding Source is, and that there is no warranty. Shown
|
|
on every page, including login/setup — those are interactive UIs too.
|
|
The running version stays signed-in only: it is what a backup manifest is
|
|
checked against on restore, and must not be advertised to the internet. */}}
|
|
<footer class="version">
|
|
{{if .User}}SelfPost {{.Version}} · {{end}}{{.Copyright}} ·
|
|
<a href="/license">License (AGPL-3.0)</a> ·
|
|
<a href="{{.SourceURL}}">Source</a> ·
|
|
No warranty
|
|
</footer>
|
|
</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, 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="{{if .IsGlobal}}/status{{else}}/domains{{end}}"><img src="/static/logo-compact.svg" width="220" height="100" alt="SelfPost"></a>
|
|
<div class="links">
|
|
{{if .IsGlobal}}
|
|
{{if eq .Active "status"}}<span aria-current="page">{{template "icon-status"}}Status</span>{{else}}<a href="/status">{{template "icon-status"}}Status</a>{{end}}
|
|
{{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 .IsGlobal}}
|
|
{{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}}
|
|
{{if eq .Active "users"}}<span aria-current="page">{{template "icon-users"}}Users</span>{{else}}<a href="/users">{{template "icon-users"}}Users</a>{{end}}
|
|
{{end}}
|
|
</div>
|
|
<div class="session">
|
|
<span class="session-user muted">{{template "icon-account"}}User: {{.User}}</span>
|
|
{{if eq .Active "settings"}}<span aria-current="page">{{template "icon-settings"}}Settings</span>{{else}}<a href="/settings">{{template "icon-settings"}}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}}
|
|
|
|
{{/* Whether the page takes the column whole. Empty here, so a page is held to
|
|
the reading measure unless it says otherwise; the pages that are tables of
|
|
data, raw log lines or side-by-side cards redefine this as the word "wide",
|
|
which lands in <main>'s class list (see main.wide in panel.css). Each
|
|
page's template file is parsed after this one, so its definition replaces
|
|
the empty one. */}}
|
|
{{define "wide"}}{{end}}
|
|
|
|
{{/* back_link — up-navigation on drill-down pages. Invoke with the "back"
|
|
function, e.g. {{template "back_link" (back "/users" "Back to users")}}.
|
|
Place it directly under the page <h1> and above the cards (see .back in
|
|
panel.css). TestDrillDownPagesPlaceBackLinkAboveContent guards this. */}}
|
|
{{define "back_link"}}
|
|
<a class="back" href="{{.Href}}">← {{.Label}}</a>
|
|
{{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}}
|
|
{{/* Two people: rear as a right-side crescent (head + shoulder) so it peeks
|
|
from behind; front is a full silhouette matching icon-account. Heads share
|
|
one baseline; shoulder arcs share one floor — otherwise the pair looks
|
|
lopsided at 16 px. */}}
|
|
{{define "icon-users"}}<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="M10.9 3.1a2.1 2.1 0 0 1 0 4.2"/><path d="M14.7 14.25a3.8 3.8 0 0 0-3.9-3.65"/><circle cx="5.5" cy="5.2" r="2.5"/><path d="M1.4 14.25a4.8 4.8 0 0 1 8.2 0"/></svg>{{end}}
|
|
{{/* A gear (Feather "settings"), not a sun-with-rays: at 16 px spokes alone
|
|
read as brightness, while the toothed rim is the picture of configuration.
|
|
viewBox 0 0 24 matches the source path; CSS sizes the icon to 1 rem. */}}
|
|
{{define "icon-settings"}}<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.25" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></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}}
|