Follow what the live server does with tenants and domains
A run on the production server with throwaway tenants, a role, lists and a domain, all removed, found three things the source reading had not: - Something in a tenant has to be on a domain in that tenant (a list in a tenant on an unassigned domain is invalidForeignKey), while something in no tenant may be on a tenant's domain. The account panel's tenant choice offered every tenant; it offers only the domain's now, and a new account starts in the tenant of the domain it is made on. The domain list reads memberTenantId for it. - A domain created in a tenant puts its DKIM keys there too, and they keep the tenant from being deleted. They are counted with the rest, so Delete is not offered while any remain. - Stalwart lets a domain leave a tenant while the tenant still has accounts on it, stranding them. The panel asks first and refuses while any are there. The refusal to delete a tenant that holds anything was confirmed, as were tenant create, quota pointers, logo and rename. The mock follows the domain rule, filters DKIM keys by tenant, and KNOWN-ISSUES records the run. The non-Enterprise notice is now just "Tenants are a Stalwart Enterprise feature." Two sentences were reworded and one plural added, in all nine catalogues, and the old sentences are gone.
This commit is contained in:
@@ -93,6 +93,11 @@ export function AccountSheet({ account, ctx, onClose, onChanged, onCreated, onDe
|
||||
if (!domainId && ctx.domains[0]) setDomainId(ctx.domains[0].id);
|
||||
}, [ctx.domains, domainId]);
|
||||
|
||||
// A new account starts in the tenant of the domain it is being made on.
|
||||
useEffect(() => {
|
||||
if (creating) setTenantId(ctx.domains.find((d) => d.id === domainId)?.memberTenantId ?? "");
|
||||
}, [creating, domainId, ctx.domains]);
|
||||
|
||||
useEffect(() => {
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape" && !document.querySelector(".dialog-backdrop")) onClose();
|
||||
@@ -102,6 +107,13 @@ export function AccountSheet({ account, ctx, onClose, onChanged, onCreated, onDe
|
||||
}, [onClose]);
|
||||
|
||||
const domainName = (id: string) => ctx.domains.find((d) => d.id === id)?.name ?? "";
|
||||
/*
|
||||
* Stalwart refuses an account in a tenant on a domain outside it (live,
|
||||
* 2026-09-15: invalidForeignKey naming the domain), and allows one in no
|
||||
* tenant on a tenant's domain. So the only tenant to offer is the domain's.
|
||||
*/
|
||||
const domainTenant = ctx.domains.find((d) => d.id === (account?.domainId ?? domainId))?.memberTenantId ?? null;
|
||||
const tenantName = (id: string) => ctx.tenants?.find((x) => x.id === id)?.name ?? id;
|
||||
const address = account?.emailAddress ?? `${name}@${domainName(domainId)}`;
|
||||
|
||||
const roleOptions = useMemo(() => {
|
||||
@@ -247,15 +259,19 @@ export function AccountSheet({ account, ctx, onClose, onChanged, onCreated, onDe
|
||||
{self ? t("You can't change your own role.") : t("Only roles whose permissions you hold yourself are offered. On an account inside a tenant, Administrator means administrator of that tenant.")}
|
||||
</p>
|
||||
|
||||
{ctx.tenants && (ctx.tenants.length > 0 || tenantId) && (
|
||||
{ctx.tenants && (domainTenant || tenantId) && (
|
||||
<>
|
||||
<h3>{t("Tenant")}</h3>
|
||||
<select className="input admin-wide" aria-label={t("Tenant")} value={tenantId} disabled={!editable || self} onChange={(e) => setTenantId(e.target.value)}>
|
||||
<option value="">{t("No tenant")}</option>
|
||||
{ctx.tenants.map((x) => <option key={x.id} value={x.id}>{x.name}</option>)}
|
||||
{tenantId && !ctx.tenants.some((x) => x.id === tenantId) && <option value={tenantId}>{tenantId}</option>}
|
||||
{domainTenant && <option value={domainTenant}>{tenantName(domainTenant)}</option>}
|
||||
{tenantId && tenantId !== domainTenant && <option value={tenantId}>{tenantName(tenantId)}</option>}
|
||||
</select>
|
||||
<p className="hint">{self ? t("You can't move your own account into a tenant.") : t("An account in a tenant is limited by the tenant's role and counts towards its limits, and Administrator means administrator of that tenant.")}</p>
|
||||
<p className="hint">
|
||||
{self
|
||||
? t("You can't move your own account into a tenant.")
|
||||
: t("An account can be in the tenant its domain is in. In a tenant it is limited by the tenant's role and counts towards its limits, and Administrator means administrator of that tenant.")}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user