release: 1.3.0: 1.3.0-1.3.1: security pass, queue persist, full backup layout
[1.3.0] Security and quality after 1.2.5: domain-admin send-log authorization, fail-closed sign-in and application delete, level-2 rate-limit race fix, restore Resync, expanded tests, operator docs, release CI, and OFL for IBM Plex. Upgrading from 1.2.x is a tag bump; no migration. Added: - licence: the SIL Open Font License 1.1 text now travels with the IBM Plex - docs: agreed roadmap item **queue-retries** — show this Postfix's retry Security: - The independent security review of the send-log authorization and Fixed: - test (e2e): send-log status scrapers follow the badge markup in - security (panel): the Deliveries list is scoped to a domain administrator's - mail (level-2 rate limit): the ceiling is no longer overshot by messages that - panel (sign-in): a session that cannot be written to the database no longer - panel (applications): deleting an application removes its SASL credentials … (2 more in CHANGELOG) Changed: - docs: operator and as-built docs aligned with the code after a full - docs: [guide.md](docs/guide.md) reorganised into **Installation**, **Instance - docs: the 2026-08-13 full-tree review plan is complete — every phase (P0–P7) - licence: [NOTICE](NOTICE) tells modifiers to update `SourceURL` in - ci: the release image is published only for a **published** GitHub Release … (9 more in CHANGELOG) [1.3.1] Retry policy in the panel, persistent Postfix queue, and self-contained full backups. Mail queue and delivery history show this Postfix's first retry, backoff cap, and queue lifetime. Deferred mail survives container recreate. Full backups now archive `data/`, compose, `.env`, and `certs/`; the archive layout breaks 1.3.0 flat backups (unpack those with `tar xzf backup.tar.gz -C ./data` as before). Upgrading from 1.3.0 is a tag bump; no schema migration. Added: - Mail queue and a delivery's history show this Postfix's retry policy - docs: **Plan checklists** in [development.md](docs/development.md) — format, - docs: **Implementation checklists** with model routing in - docs: roadmap candidates **send-log-retention** (panel Settings for delivery - Postfix queue under `/data/postfix/queue` — deferred and active mail survive … (1 more in CHANGELOG) Changed: - **Breaking:** full-backup archive layout — paths are prefixed with `data/`; - docs: operator guide, architecture, security, and backup UI updated for - docs: HTML mockups for a full panel UI refresh Changed: - docs: panel UI mockups after review — Status restored to live readings - docs: panel UI design system and mockups rebuilt as separate pages - docs: panel UI mockups — add-domain sits in the list card (Domains and Fixed: - ci (GHCR): per-arch package tags (`X.Y.Z-amd64`, `X.Y.Z-arm64`) are dropped Squashed CHANGELOG sections: [1.3.0], [1.3.1]. Full detail: CHANGELOG.md (1.3.0, 1.3.1).
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
/* Panel UI system primitives.
|
||||
Source of truth: system.html. Screens are separate HTML files composed from
|
||||
these classes. Do not “fix” empty columns, Host/Type height, or split
|
||||
actions by one-off rules — compose with these instead. */
|
||||
|
||||
.stack {
|
||||
display: flex; flex-direction: column; gap: 1rem;
|
||||
width: 100%; min-width: 0;
|
||||
}
|
||||
.stack > .card,
|
||||
.stack > .pair,
|
||||
.stack > .measure,
|
||||
.stack > .fill { margin-top: 0; }
|
||||
.stack > .card + .card { margin-top: 0; }
|
||||
|
||||
.measure { width: 100%; max-width: var(--form-max); min-width: 0; }
|
||||
|
||||
.fill { width: 100%; max-width: var(--ops-max); min-width: 0; }
|
||||
.fill > .card { margin-top: 0; }
|
||||
.fill .card:has(table) { overflow-x: auto; }
|
||||
.card > .measure { margin-bottom: 1rem; }
|
||||
|
||||
/* Two peer jobs. One child → reading measure (domain-admin Settings).
|
||||
Never a full-width lonely card on an ops page. */
|
||||
.pair {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||
gap: 1rem;
|
||||
width: 100%; max-width: var(--ops-max); min-width: 0;
|
||||
}
|
||||
.pair > * { min-width: 0; }
|
||||
.card > .stack { min-width: 0; }
|
||||
.pair > .card { margin-top: 0; }
|
||||
.pair > .card + .card { margin-top: 0; }
|
||||
.pair:has(> :only-child) {
|
||||
display: block;
|
||||
max-width: var(--form-max);
|
||||
}
|
||||
|
||||
.actions-row {
|
||||
display: flex; flex-wrap: wrap; gap: 0.6rem; align-items: center;
|
||||
margin-top: 1.1rem;
|
||||
}
|
||||
.actions-row > button,
|
||||
.actions-row > a.btn,
|
||||
.actions-row > a.danger { margin-top: 0; }
|
||||
|
||||
/* Two labelled controls on one row. Labels share row 1, values share row 2
|
||||
so they are the same height by construction — not by matching padding. */
|
||||
.field-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
grid-template-rows: auto minmax(2.5rem, auto);
|
||||
column-gap: 1rem;
|
||||
align-items: stretch;
|
||||
margin-top: 0.45rem;
|
||||
}
|
||||
.field-row.equal { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
|
||||
.field-row > .field { display: contents; }
|
||||
.field-row > .field:first-child > * { grid-column: 1; }
|
||||
.field-row > .field:last-child > * { grid-column: 2; }
|
||||
.field-row > .field > label { grid-row: 1; margin-top: 0.45rem; }
|
||||
.field-row > .field > :not(label) {
|
||||
grid-row: 2; align-self: stretch; min-width: 0;
|
||||
margin-top: 0.3rem; box-sizing: border-box;
|
||||
}
|
||||
.field-row > .field > .code,
|
||||
.field-row > .field > .code-row {
|
||||
height: 100%;
|
||||
}
|
||||
.field-row > .field > .code {
|
||||
display: flex; align-items: center;
|
||||
padding-top: 0.45rem; padding-bottom: 0.45rem;
|
||||
}
|
||||
.field-row > .field:last-child > .code {
|
||||
width: fit-content; min-width: 2.75rem;
|
||||
justify-content: center; text-align: center;
|
||||
}
|
||||
.field-row .code-row {
|
||||
display: flex; align-items: stretch; gap: 0.5rem; height: 100%;
|
||||
}
|
||||
.field-row .code-row .code {
|
||||
flex: 1; min-width: 0; margin-top: 0; height: auto;
|
||||
display: flex; align-items: center;
|
||||
padding-top: 0.45rem; padding-bottom: 0.45rem;
|
||||
}
|
||||
.field-row .code-row .copy { margin-top: 0; align-self: stretch; }
|
||||
|
||||
.card-head {
|
||||
display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.card-head h2 { margin: 0; }
|
||||
|
||||
@media (max-width: 52rem) {
|
||||
.pair { grid-template-columns: 1fr; }
|
||||
/* field-row stays two columns: Type is a token, not a second form. */
|
||||
}
|
||||
|
||||
/* Specimens on the system page */
|
||||
.sys-wrap { max-width: 70rem; }
|
||||
.sys-wrap > header p,
|
||||
.sys-wrap section > p,
|
||||
.sys-wrap li { max-width: 68ch; }
|
||||
.sys-toc {
|
||||
display: flex; flex-wrap: wrap; gap: 0.35rem 1.1rem;
|
||||
margin: 1rem 0 0; padding: 0; list-style: none;
|
||||
}
|
||||
.sys-toc a { font-size: 0.92rem; }
|
||||
.specimen {
|
||||
margin: 1rem 0 0; padding: 0.9rem 1rem 1.1rem;
|
||||
border: 1px dashed var(--border); border-radius: 6px; background: var(--code-bg);
|
||||
}
|
||||
.specimen > figcaption {
|
||||
font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.08em;
|
||||
text-transform: uppercase; color: var(--muted); margin: 0 0 0.7rem;
|
||||
}
|
||||
.specimen.bad {
|
||||
border-color: var(--danger-border); background: var(--danger-bg);
|
||||
}
|
||||
.specimen.bad > figcaption { color: var(--danger-fg); }
|
||||
.recipes {
|
||||
width: 100%; margin-top: 0.8rem; font-size: 0.92rem;
|
||||
}
|
||||
.recipes th, .recipes td { white-space: normal; vertical-align: top; }
|
||||
.recipes code { font-size: 0.82rem; }
|
||||
.forbid { margin: 0.4rem 0 0; padding-left: 1.1rem; }
|
||||
.forbid li { margin: 0.35rem 0; }
|
||||
Reference in New Issue
Block a user