Follows the design: commit before it. panel.css gains the same four rules theme.css did (TestPanelCSSIsTheMockupStylesheet holds them equal): the form stretches the button across the user menu and the button is handed Bulma's hover / focus / active variables. layout.html already had the form; only its comment changes. Checked in the browser on the kit page at 1440: Sign out is as wide as Account and Help (147 px, 36 px tall), shows the pointer cursor, and is tinted under the pointer like they are. go test ./internal/web/... ok github.com/mixeme/selfpost/internal/web ok github.com/mixeme/selfpost/internal/web/auth ok github.com/mixeme/selfpost/internal/web/handlers ok github.com/mixeme/selfpost/internal/web/validate ok github.com/mixeme/selfpost/internal/web/view Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
149 lines
10 KiB
HTML
149 lines
10 KiB
HTML
{{/* The shell of every redesigned page (docs/plans/panel-redesign.md § Three
|
|
layers): a brick navbar with the wordmark and hover menus, the stamp's
|
|
perforated edge, the strip of sibling pages, the page, the footer. It is
|
|
built exactly like the mockups' build.py builds it, and it is the only
|
|
place besides components.html where sp- classes are written. A page never
|
|
writes any of it.
|
|
|
|
layout.html is executed with a Frame (components.go): .Shell is what the
|
|
shell shows, .Page is the page's own data, handed to the page's "content"
|
|
template. Which entries are visible comes from .Shell (Engine.shell derives
|
|
it from the role, the feature flags and the user's reach); the paths are
|
|
written out here, so the menu can be read, and checked against the router
|
|
(guard_routes_test.go), without running anything. */}}
|
|
|
|
{{/* layout_head is the head of both documents; the stylesheets, in this order,
|
|
and the two scripts are all a page ever loads. */}}
|
|
{{define "layout_head"}}<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>{{.Title}} · SelfPost</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 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/bulma.min.css">
|
|
<link rel="stylesheet" href="/static/tabler-icons.css">
|
|
<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>{{end}}
|
|
|
|
{{/* layout.html is the signed-in document. Engine.Render uses
|
|
layout_signed_out instead when there is no user. */}}
|
|
{{define "layout.html"}}<!DOCTYPE html>
|
|
<html lang="en" data-theme="light">
|
|
{{template "layout_head" .Shell}}
|
|
<body>
|
|
{{template "nav" .Shell}}
|
|
{{template "subnav" .Shell}}
|
|
<main>
|
|
<div class="container sp-page">
|
|
{{template "content" .Page}}
|
|
{{/* Appropriate Legal Notices (AGPL-3.0): copyright, how to read the
|
|
licence, where the Corresponding Source is, and that there is no
|
|
warranty. The running version is shown signed in only: it is what a
|
|
backup manifest is checked against on restore, and must not be
|
|
advertised to the internet. */}}
|
|
<footer class="sp-foot">SelfPost {{.Shell.Version}} · {{.Shell.Copyright}} · <a href="/license">License (AGPL-3.0)</a> · <a href="{{.Shell.SourceURL}}">Source</a> · No warranty</footer>
|
|
</div>
|
|
</main>
|
|
</body>
|
|
</html>{{end}}
|
|
|
|
{{/* layout_signed_out is the split brand / form screen of sign-in and setup:
|
|
the stamp on the brick, the page's own form beside it, and the legal
|
|
notice without the version. */}}
|
|
{{define "layout_signed_out"}}<!DOCTYPE html>
|
|
<html lang="en" data-theme="light">
|
|
{{template "layout_head" .Shell}}
|
|
<body>
|
|
<div class="columns is-gapless sp-signin">
|
|
<div class="column is-half sp-signin-brand">
|
|
<div>
|
|
<img src="/static/logo.svg" alt="SelfPost">
|
|
<p>YOUR SMTP. YOUR RULES.</p>
|
|
</div>
|
|
</div>
|
|
<div class="column is-half sp-signin-form">
|
|
{{template "content" .Page}}
|
|
<p class="sp-foot">{{.Shell.Copyright}} · <a href="/license">License (AGPL-3.0)</a> · <a href="{{.Shell.SourceURL}}">Source</a> · No warranty</p>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>{{end}}
|
|
|
|
{{/* nav is the navbar and the perforated edge under it (input: Shell). A group
|
|
with nothing in it for this user is absent; the group of the current page
|
|
carries sp-current and its current entry is-active. */}}
|
|
{{define "nav"}}<nav class="navbar is-primary" aria-label="main navigation">
|
|
<div class="container">
|
|
<div class="navbar-brand"><a class="navbar-item" href="{{.Home}}"><img src="/static/wordmark.svg" alt="SelfPost"></a></div>
|
|
<div class="navbar-menu is-active">
|
|
<div class="navbar-start">
|
|
{{if .ShowOverview}} <a class="navbar-item{{if eq .Section "overview"}} sp-current{{end}}" href="/overview">Overview</a>
|
|
{{end}}{{if .ShowOutbound}} <div class="navbar-item has-dropdown is-hoverable{{if eq .Section "outbound"}} sp-current{{end}}">
|
|
<a class="navbar-link" href="/outbound/domains">Outbound</a>
|
|
<div class="navbar-dropdown">
|
|
<a class="navbar-item{{if and (eq .Section "outbound") (eq .Page "domains")}} is-active{{end}}" href="/outbound/domains"><i class="ti ti-world-upload"></i>Domains</a>
|
|
<a class="navbar-item{{if and (eq .Section "outbound") (eq .Page "log")}} is-active{{end}}" href="/outbound/log"><i class="ti ti-list-details"></i>Log</a>
|
|
{{if .ShowQueue}} <a class="navbar-item{{if and (eq .Section "outbound") (eq .Page "queue")}} is-active{{end}}" href="/outbound/queue"><i class="ti ti-stack-2"></i>Queue</a>
|
|
{{end}}{{if .ShowDMARC}} <a class="navbar-item{{if and (eq .Section "outbound") (eq .Page "dmarc")}} is-active{{end}}" href="/outbound/dmarc"><i class="ti ti-report-analytics"></i>DMARC reports</a>
|
|
{{end}} </div>
|
|
</div>
|
|
{{end}}{{if .ShowInbound}} <div class="navbar-item has-dropdown is-hoverable{{if eq .Section "inbound"}} sp-current{{end}}">
|
|
<a class="navbar-link" href="/inbound/domains">Inbound</a>
|
|
<div class="navbar-dropdown">
|
|
<a class="navbar-item{{if and (eq .Section "inbound") (eq .Page "domains")}} is-active{{end}}" href="/inbound/domains"><i class="ti ti-world-download"></i>Domains</a>
|
|
</div>
|
|
</div>
|
|
{{end}}{{if .ShowServer}} <div class="navbar-item has-dropdown is-hoverable{{if eq .Section "server"}} sp-current{{end}}">
|
|
<a class="navbar-link" href="/server/health">Server</a>
|
|
<div class="navbar-dropdown">
|
|
<a class="navbar-item{{if and (eq .Section "server") (eq .Page "health")}} is-active{{end}}" href="/server/health"><i class="ti ti-heartbeat"></i>Health</a>
|
|
<a class="navbar-item{{if and (eq .Section "server") (eq .Page "log")}} is-active{{end}}" href="/server/log"><i class="ti ti-file-text"></i>System log</a>
|
|
<a class="navbar-item{{if and (eq .Section "server") (eq .Page "backup")}} is-active{{end}}" href="/server/backup"><i class="ti ti-archive"></i>Backup</a>
|
|
<a class="navbar-item{{if and (eq .Section "server") (eq .Page "users")}} is-active{{end}}" href="/server/users"><i class="ti ti-users"></i>Users</a>
|
|
<a class="navbar-item{{if and (eq .Section "server") (eq .Page "settings")}} is-active{{end}}" href="/server/settings"><i class="ti ti-settings"></i>Settings</a>
|
|
</div>
|
|
</div>
|
|
{{end}} </div>
|
|
<div class="navbar-end">
|
|
<div class="navbar-item has-dropdown is-hoverable{{if eq .Section "user"}} sp-current{{end}}">
|
|
<a class="navbar-link" href="/account"><i class="ti ti-user-circle"></i>{{.User}}</a>
|
|
<div class="navbar-dropdown is-right">
|
|
<a class="navbar-item" href="/account"><i class="ti ti-key"></i>Account</a>
|
|
<a class="navbar-item" href="/help"><i class="ti ti-help"></i>Help</a>
|
|
<hr class="navbar-divider">
|
|
{{/* Signing out is a POST (the handler answers nothing else), so it
|
|
stays a form that works without scripts. The submit button
|
|
carries navbar-item: Bulma resets a button to plain text, so
|
|
it takes the dropdown's entry styling like the links around
|
|
it, and panel.css hands it the hover tint Bulma gives to
|
|
a.navbar-item alone. */}}
|
|
<form method="post" action="/logout"><button type="submit" class="navbar-item"><i class="ti ti-logout"></i>Sign out</button></form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
<div class="sp-perf" aria-hidden="true"></div>{{end}}
|
|
|
|
{{/* subnav is the strip of sibling pages under the edge (input: Shell): the
|
|
pages of the current group, the current one marked. A page outside every
|
|
group (the user menu, the kit page's neighbours) has none. */}}
|
|
{{define "subnav"}}{{if and .ShowOutbound (eq .Section "outbound")}}<div class="sp-subnav"><div class="container"><a href="/outbound/domains"{{if eq .Page "domains"}} aria-current="page"{{end}}><i class="ti ti-world-upload"></i>Domains</a><a href="/outbound/log"{{if eq .Page "log"}} aria-current="page"{{end}}><i class="ti ti-list-details"></i>Log</a>{{if .ShowQueue}}<a href="/outbound/queue"{{if eq .Page "queue"}} aria-current="page"{{end}}><i class="ti ti-stack-2"></i>Queue</a>{{end}}{{if .ShowDMARC}}<a href="/outbound/dmarc"{{if eq .Page "dmarc"}} aria-current="page"{{end}}><i class="ti ti-report-analytics"></i>DMARC reports</a>{{end}}</div></div>
|
|
{{else if and .ShowInbound (eq .Section "inbound")}}<div class="sp-subnav"><div class="container"><a href="/inbound/domains"{{if eq .Page "domains"}} aria-current="page"{{end}}><i class="ti ti-world-download"></i>Domains</a></div></div>
|
|
{{else if and .ShowServer (eq .Section "server")}}<div class="sp-subnav"><div class="container"><a href="/server/health"{{if eq .Page "health"}} aria-current="page"{{end}}><i class="ti ti-heartbeat"></i>Health</a><a href="/server/log"{{if eq .Page "log"}} aria-current="page"{{end}}><i class="ti ti-file-text"></i>System log</a><a href="/server/backup"{{if eq .Page "backup"}} aria-current="page"{{end}}><i class="ti ti-archive"></i>Backup</a><a href="/server/users"{{if eq .Page "users"}} aria-current="page"{{end}}><i class="ti ti-users"></i>Users</a><a href="/server/settings"{{if eq .Page "settings"}} aria-current="page"{{end}}><i class="ti ti-settings"></i>Settings</a></div></div>
|
|
{{end}}{{end}}
|