Files
ihasmail-inbuxa/web/src/views/admin/AdminView.tsx
T
jcoffey-dev e2a531b615 Add Groups to Administration
A group is a shared address and mailbox and the people who share it. To
Stalwart it is an x:Account of type Group, behind the same sysAccount*
permissions as a person, so it sits under Directory beside Accounts:
search, a page of fifty with each group's member count, and a panel to
create, edit and delete one.

Membership lives on the member, not the group. Members are the users whose
memberGroupIds name it, and adding or removing one is a single
memberGroupIds/<group> pointer on that user's account -- true or null --
which leaves their other groups alone. Changes apply straight away rather
than riding on Save, so the list is always what the server has. Nobody can
add or remove themselves, the same line the account panel draws at one's
own role.

A group's role is Default or Custom, not a person's User or Admin, and it
is what the group may do: in 0.16 a user's permissions come from their own
roles only, and a group gives its members what is shared with it. Only
roles the viewer could grant are offered.

Delete takes the members out first and then deletes the group, the order
a domain's keys go before the domain, because the registry keeps anything
another object names. A role that cannot change the members' accounts is
not offered a delete it could only half finish.

The mock's groups had a person's roles, accepted a memberGroupIds filter
without applying it, and answered a linked delete with the wrong shape;
all three follow the source now, and it refuses nested groups and
memberships of things that are not groups.

Nothing about groups has been run against a live server yet: production
has none, and every operation is a write. KNOWN-ISSUES says what was read
from source.

Thirty-five new strings, two of them plurals, in all nine catalogues.
2026-09-15 08:31:36 -07:00

38 lines
1.6 KiB
TypeScript

import type { ReactNode } from "react";
import { Redirect } from "wouter";
import { adminSections, type AdminSection } from "@/lib/adminAccess";
import { AccountsAdmin } from "./AccountsAdmin";
import { AdminDashboard } from "./AdminDashboard";
import { DomainsAdmin } from "./DomainsAdmin";
import { GroupsAdmin } from "./GroupsAdmin";
import { currentAdminSection } from "./AdminNav";
import { usePermissions } from "./usePermissions";
const RENDER: Record<AdminSection, (id?: string) => ReactNode> = {
dashboard: () => <AdminDashboard />,
accounts: (id) => <AccountsAdmin selectedId={id} />,
groups: (id) => <GroupsAdmin selectedId={id} />,
domains: (id) => <DomainsAdmin selectedId={id} />,
};
/**
* Administration: what the signed-in account's Stalwart role lets it manage.
*
* The page is only the open section. Its list of sections is in the folder
* pane (see AdminNav), so the tables here get the width Settings spends on a
* second column. A bare /admin opens the dashboard, and a section the role
* cannot read -- typed into the address bar, say -- opens the first one it can.
*/
export function AdminView({ section, id }: { section?: string; id?: string }) {
const allowed = adminSections(usePermissions());
// A role taken away since the menu was drawn. Stalwart would refuse every
// call anyway; this spares the page of refusals.
if (!allowed.length) return <Redirect to="/mail" />;
const current = currentAdminSection(allowed, section)!;
return (
<div className="admin-layout">
<div className="settings-content admin-content">{RENDER[current](section === current ? id : undefined)}</div>
</div>
);
}