Add Domains to Administration

A role that can read domains now finds a Domains section beside Accounts:
list and search with each domain's account count and whether its DNS, DKIM
and certificate are managed automatically; add a domain; edit its
description, other names, catch-all address and plus addressing; copy its
DNS records one at a time or as a zone file; see its DKIM keys and their
stage; and remove it once no accounts use it.

The records come from the zone file Stalwart computes per domain. A long
DKIM record, which the BIND serialiser splits into quoted chunks, is joined
back into the single value a DNS provider's form wants.

Removing a domain takes its DKIM keys first, in the same request, because the
server will not remove a domain its keys still name. Removal is not offered
while accounts use the domain, or when the role cannot remove the keys.

The Administration nav is now built from the sections the role can read, and
the menu appears when there is at least one. The mock gains domains, DKIM
keys and zone files.

61 new strings, translated in all nine catalogues; strings falling back to
English stay at 16.
This commit is contained in:
2026-09-13 15:39:16 -07:00
parent d279fe8f90
commit 1dafb4bc79
23 changed files with 1783 additions and 30 deletions
+3 -1
View File
@@ -32,7 +32,9 @@ works the same way — and dropped where 0.15 was the whole subject. Support for
0.15 was removed on 2026-08-26; the last release that runs on it is tagged
[`stalwart-0.15-support`](https://github.com/Coffey-Labs/ihasmail/releases/tag/stalwart-0.15-support).
- **Administration has not yet been exercised against a live Stalwart.** It was built on 2026-09-13 against the 0.16.22 source and the mock, which reproduces the shapes read there — lists as index-keyed objects, sets as `{"id": true}`, masked secrets, AND-only filters — and it has not touched a real server. Four things are read from source rather than proved: that `/api/account` lists permissions in camelCase (`sysAccountGet`) as the enum serialises them, where the documentation shows kebab-case — both are accepted, so the menu works either way; that a new password written to `credentials/<index>/secret` is hashed and keeps the credential's id; that the Basic credential ihasmail proxies with reaches the admin `x:` methods as it already reaches the self-service ones; and that Stalwart skips its grant check when only a password changes, which is the reason the outranking guard exists at all. The last is worth reproducing rather than trusting in either direction. Query and get are sent as two requests rather than one with a back-reference, because whether the registry methods resolve references was not checked.
- **Administration has not yet been exercised against a live Stalwart.** Accounts were built on 2026-09-13 against the 0.16.22 source and the mock, which reproduces the shapes read there — lists as index-keyed objects, sets as `{"id": true}`, masked secrets, AND-only filters — and it has not touched a real server. Four things are read from source rather than proved: that `/api/account` lists permissions in camelCase (`sysAccountGet`) as the enum serialises them, where the documentation shows kebab-case — both are accepted, so the menu works either way; that a new password written to `credentials/<index>/secret` is hashed and keeps the credential's id; that the Basic credential ihasmail proxies with reaches the admin `x:` methods as it already reaches the self-service ones; and that Stalwart skips its grant check when only a password changes, which is the reason the outranking guard exists at all. The last is worth reproducing rather than trusting in either direction. Query and get are sent as two requests rather than one with a back-reference, because whether the registry methods resolve references was not checked.
- **The Domains section is read from source as well, on the same terms.** Five things are worth confirming on a live server before trusting them: that `dnsZoneFile` is written as `name IN TYPE value` with long TXT records split into a parenthesised run, which is what `dns-update`'s BIND serialiser does on its main branch while Stalwart pins 0.5; that `catchAllAddress` takes a whole address rather than a local part; that `x:DkimSignature/query` accepts a `domainId` filter, as the server's own record builder queries by it; that a refused delete's `linkedObjects` name each object by type as `{object, id}`; and that removing a domain's DKIM keys and then the domain in one request succeeds, since automatic DKIM gives every new domain keys and a domain its keys name cannot be removed. The zone-file reader keeps any line it cannot parse as a row of its own, so a format that differs shows up as an odd row rather than a missing record.
- **All nine translations have never been read by anybody who speaks them.** They were produced by AI against standard dictionaries on 2026-08-31 — German, Spanish, French, Dutch, Portuguese (Brazil), Russian, Ukrainian, Simplified Chinese and Japanese, which with English makes ten languages in the picker — and every one of the nine is marked **Beta** in the picker, with that stated in Settings beside a link for reporting anything that reads wrongly. This is the entry that matters most on this page, because it is the one thing here that cannot be closed by testing: a translation can be complete, consistent, pass every check, and still read like a machine wrote it, and nobody on this project can tell which. What *is* verified is the machinery around them. A missing key renders its English source, so a bad line can simply be deleted; a stale key — one whose English no longer exists — is caught by `npm run i18n:check` rather than sitting in the file looking correct and never being looked up. Plurals are asked of `Intl.PluralRules` rather than assumed, which is why Russian and Ukrainian carry three forms and Japanese and Chinese carry one; supplying `one` for Japanese would have been filling in a distinction the language does not draw. Confirmed live on the deployed instance (2026-08-31) against a 6,289-message mailbox: role folders localise and the ~20 custom folders keep the names their owner gave them, dates and the calendar follow the language, and 6,289 renders as *6289 листувань* — the genitive plural a number ending in nine takes, which is the first time the plural machinery ran on anything but a hand-picked value.