Add Administration, starting with accounts
An account whose Stalwart role manages accounts now finds Administration in the account menu. It lists, searches, creates and edits accounts -- display name, other addresses, role, storage limit -- sets a new password, and deletes, each offered only when the role holds the matching permission. The server keeps the permissions list from GET /api/account, which it already called for the edition and threw the rest away. Everything else is JMAP x:Account, x:Domain and x:Role calls through the existing /api/jmap proxy, so nothing new is stored and Stalwart decides every call. Stalwart checks a grant against the caller's permissions but not a password change or a delete, so an account that outranks the viewer is shown read-only. Your own password is changed in Settings, which re-seals the session; changing it here would strand it. The mock server gains a directory behind the same permission names, with MOCK_ROLE choosing admin, tenant-admin, helpdesk or user. 68 new strings, translated in all nine catalogues; strings falling back to English stay at 16.
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
import { Link, Redirect, useLocation } from "wouter";
|
||||
import { ArrowLeft, User } from "lucide-react";
|
||||
import { hasAdministration } from "@/lib/adminAccess";
|
||||
import { t } from "@/lib/i18n";
|
||||
import { AccountsAdmin } from "./AccountsAdmin";
|
||||
import { usePermissions } from "./usePermissions";
|
||||
|
||||
/**
|
||||
* Administration: what the signed-in account's Stalwart role lets it manage.
|
||||
*
|
||||
* Laid out like Settings, because it is the same kind of place -- a list of
|
||||
* sections and the one that is open -- and on a phone it behaves the same way,
|
||||
* the list first and a section on its own. Accounts is the only section so
|
||||
* far; the nav is written as a list so the next one is an entry, not a rework.
|
||||
*/
|
||||
export function AdminView({ section, id }: { section?: string; id?: string }) {
|
||||
const [, navigate] = useLocation();
|
||||
const perms = usePermissions();
|
||||
// Typed in by hand, or a role taken away since the menu was drawn. Stalwart
|
||||
// would refuse every call anyway; this spares the page of refusals.
|
||||
if (!hasAdministration(perms)) return <Redirect to="/mail" />;
|
||||
return (
|
||||
<div className={`settings-layout admin-layout ${section ? "section" : "root"}`}>
|
||||
<nav className="settings-nav" aria-label={t("Administration")}>
|
||||
<div className="nav-section" style={{ paddingLeft: 8 }}><span>{t("Directory")}</span></div>
|
||||
<Link href="/admin/accounts" className={`nav-item ${!section || section === "accounts" ? "active" : ""}`}>
|
||||
<User size={18} />
|
||||
<span className="nav-label">{t("Accounts")}</span>
|
||||
</Link>
|
||||
</nav>
|
||||
<div className="settings-content admin-content">
|
||||
{section && (
|
||||
<button className="btn btn-ghost btn-sm admin-back" style={{ marginBottom: 8, marginLeft: -8 }} onClick={() => navigate("/admin")}>
|
||||
<ArrowLeft size={16} /> {t("Administration")}
|
||||
</button>
|
||||
)}
|
||||
<AccountsAdmin selectedId={id} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user