207 lines
13 KiB
TypeScript
207 lines
13 KiB
TypeScript
import { useEffect, useMemo } from "react";
|
|
import { useSearchParams } from "react-router-dom";
|
|
import type { ApiSettings, AuthInfo, MailProfilesUiCapability } from "../../types";
|
|
import { fetchMe } from "../../api/auth";
|
|
import Card from "../../components/Card";
|
|
import ModuleSubnav, { type ModuleSubnavGroup } from "../../layout/ModuleSubnav";
|
|
import { adminReadScopes, hasAnyScope, hasScope } from "../../utils/permissions";
|
|
import AdminOverviewPanel from "./AdminOverviewPanel";
|
|
import SystemUsersPanel from "./SystemUsersPanel";
|
|
import SystemSettingsPanel from "./SystemSettingsPanel";
|
|
import TenantSettingsPanel from "./TenantSettingsPanel";
|
|
import GovernanceTemplatesPanel from "./GovernanceTemplatesPanel";
|
|
import SystemRolesPanel from "./SystemRolesPanel";
|
|
import TenantsPanel from "./TenantsPanel";
|
|
import UsersPanel from "./UsersPanel";
|
|
import GroupsPanel from "./GroupsPanel";
|
|
import RolesPanel from "./RolesPanel";
|
|
import ApiKeysPanel from "./ApiKeysPanel";
|
|
import AdminAuditPanel from "./AdminAuditPanel";
|
|
import MailProfilesPanel from "./MailProfilesPanel";
|
|
import RetentionPoliciesPanel from "./RetentionPoliciesPanel";
|
|
import { usePlatformUiCapability } from "../../platform/ModuleContext";
|
|
|
|
type AdminSection =
|
|
| "overview"
|
|
| "system-settings"
|
|
| "system-retention"
|
|
| "system-mail-servers"
|
|
| "system-tenants"
|
|
| "system-users"
|
|
| "system-groups"
|
|
| "system-roles"
|
|
| "system-role-templates"
|
|
| "system-audit"
|
|
| "tenant-users"
|
|
| "tenant-groups"
|
|
| "tenant-roles"
|
|
| "tenant-api-keys"
|
|
| "tenant-mail-servers"
|
|
| "tenant-user-mail-servers"
|
|
| "tenant-group-mail-servers"
|
|
| "tenant-retention"
|
|
| "tenant-user-retention"
|
|
| "tenant-group-retention"
|
|
| "tenant-settings"
|
|
| "tenant-audit";
|
|
|
|
export default function AdminPage({
|
|
settings,
|
|
auth,
|
|
onAuthChange
|
|
}: {
|
|
settings: ApiSettings;
|
|
auth: AuthInfo;
|
|
onAuthChange: (auth: AuthInfo | null, accessToken?: string) => void;
|
|
}) {
|
|
const mailProfilesUi = usePlatformUiCapability<MailProfilesUiCapability>("mail.profiles");
|
|
const mailProfilesAvailable = Boolean(mailProfilesUi);
|
|
|
|
const available = useMemo(() => {
|
|
const sections = new Set<AdminSection>(["overview"]);
|
|
if (hasScope(auth, "system:settings:read")) {
|
|
sections.add("system-settings");
|
|
sections.add("system-retention");
|
|
if (mailProfilesAvailable) sections.add("system-mail-servers");
|
|
}
|
|
if (hasScope(auth, "system:tenants:read")) sections.add("system-tenants");
|
|
if (hasAnyScope(auth, ["system:accounts:read", "system:access:read"])) sections.add("system-users");
|
|
if (hasScope(auth, "system:governance:read")) sections.add("system-groups");
|
|
if (hasAnyScope(auth, ["system:roles:read", "system:access:read"])) sections.add("system-roles");
|
|
if (hasScope(auth, "system:governance:read")) sections.add("system-role-templates");
|
|
if (hasScope(auth, "system:audit:read")) sections.add("system-audit");
|
|
if (hasScope(auth, "admin:users:read")) sections.add("tenant-users");
|
|
if (hasScope(auth, "admin:groups:read")) sections.add("tenant-groups");
|
|
if (hasScope(auth, "admin:roles:read")) sections.add("tenant-roles");
|
|
if (hasScope(auth, "admin:api_keys:read")) sections.add("tenant-api-keys");
|
|
if (mailProfilesAvailable && hasAnyScope(auth, ["mail_servers:read", "admin:policies:read"])) {
|
|
sections.add("tenant-mail-servers");
|
|
if (hasScope(auth, "admin:users:read")) sections.add("tenant-user-mail-servers");
|
|
if (hasScope(auth, "admin:groups:read")) sections.add("tenant-group-mail-servers");
|
|
}
|
|
if (hasScope(auth, "admin:policies:read")) {
|
|
sections.add("tenant-retention");
|
|
if (hasScope(auth, "admin:users:read")) sections.add("tenant-user-retention");
|
|
if (hasScope(auth, "admin:groups:read")) sections.add("tenant-group-retention");
|
|
}
|
|
if (hasScope(auth, "admin:settings:read")) sections.add("tenant-settings");
|
|
if (hasScope(auth, "audit:read")) sections.add("tenant-audit");
|
|
return sections;
|
|
}, [auth, mailProfilesAvailable]);
|
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
const requestedSection = searchParams.get("section") as AdminSection | null;
|
|
const active: AdminSection = requestedSection && available.has(requestedSection) ? requestedSection : "overview";
|
|
|
|
function selectSection(section: AdminSection) {
|
|
const next = new URLSearchParams(searchParams);
|
|
if (section === "overview") next.delete("section");
|
|
else next.set("section", section);
|
|
setSearchParams(next, { replace: true });
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (requestedSection && !available.has(requestedSection)) selectSection("overview");
|
|
}, [requestedSection, available]);
|
|
|
|
async function refreshAuth() {
|
|
onAuthChange(await fetchMe(settings));
|
|
}
|
|
|
|
useEffect(() => {
|
|
void refreshAuth().catch(() => undefined);
|
|
}, [settings.accessToken, settings.apiBaseUrl]);
|
|
|
|
if (!hasAnyScope(auth, adminReadScopes)) {
|
|
return <div className="content-pad"><Card title="Administration unavailable"><p>Your current roles do not grant administrative access.</p></Card></div>;
|
|
}
|
|
|
|
const adminSubnav: ModuleSubnavGroup<AdminSection>[] = [
|
|
{ items: [{ id: "overview" as const, label: "Overview" }] },
|
|
{
|
|
title: "SYSTEM",
|
|
items: [
|
|
...(available.has("system-settings") ? [{ id: "system-settings" as const, label: "General" }] : []),
|
|
...(available.has("system-tenants") ? [{ id: "system-tenants" as const, label: "Tenants" }] : []),
|
|
...(available.has("system-roles") ? [{ id: "system-roles" as const, label: "System roles" }] : []),
|
|
...(available.has("system-role-templates") ? [{ id: "system-role-templates" as const, label: "Tenant roles" }] : []),
|
|
...(available.has("system-groups") ? [{ id: "system-groups" as const, label: "Groups" }] : []),
|
|
...(available.has("system-users") ? [{ id: "system-users" as const, label: "Users" }] : []),
|
|
...(available.has("system-mail-servers") ? [{ id: "system-mail-servers" as const, label: "Mail servers" }] : []),
|
|
...(available.has("system-retention") ? [{ id: "system-retention" as const, label: "Retention" }] : []),
|
|
...(available.has("system-audit") ? [{ id: "system-audit" as const, label: "Audit" }] : [])
|
|
]
|
|
},
|
|
{
|
|
title: "TENANT",
|
|
items: [
|
|
...(available.has("tenant-settings") ? [{ id: "tenant-settings" as const, label: "General" }] : []),
|
|
...(available.has("tenant-roles") ? [{ id: "tenant-roles" as const, label: "Roles" }] : []),
|
|
...(available.has("tenant-groups") ? [{ id: "tenant-groups" as const, label: "Groups" }] : []),
|
|
...(available.has("tenant-users") ? [{ id: "tenant-users" as const, label: "Users" }] : []),
|
|
...(available.has("tenant-mail-servers") ? [{ id: "tenant-mail-servers" as const, label: "Mail servers" }] : []),
|
|
...(available.has("tenant-retention") ? [{ id: "tenant-retention" as const, label: "Retention" }] : []),
|
|
...(available.has("tenant-api-keys") ? [{ id: "tenant-api-keys" as const, label: "API keys" }] : []),
|
|
...(available.has("tenant-audit") ? [{ id: "tenant-audit" as const, label: "Audit" }] : [])
|
|
]
|
|
},
|
|
{
|
|
title: "GROUP",
|
|
items: [
|
|
...(available.has("tenant-group-mail-servers") ? [{ id: "tenant-group-mail-servers" as const, label: "Mail servers" }] : []),
|
|
...(available.has("tenant-group-retention") ? [{ id: "tenant-group-retention" as const, label: "Retention" }] : []),
|
|
]
|
|
},
|
|
{
|
|
title: "USER",
|
|
items: [
|
|
...(available.has("tenant-user-mail-servers") ? [{ id: "tenant-user-mail-servers" as const, label: "Mail servers" }] : []),
|
|
...(available.has("tenant-user-retention") ? [{ id: "tenant-user-retention" as const, label: "Retention" }] : []),
|
|
]
|
|
}
|
|
].filter((group) => group.items.length > 0);
|
|
|
|
return (
|
|
<div className="workspace module-workspace">
|
|
<ModuleSubnav active={active} groups={adminSubnav} onSelect={selectSection} />
|
|
<section className="workspace-content">
|
|
<div className="content-pad workspace-data-page">
|
|
{active === "overview" && <AdminOverviewPanel settings={settings} availableSections={available} onSelect={(section) => available.has(section as AdminSection) && selectSection(section as AdminSection)} />}
|
|
{active === "system-settings" && <SystemSettingsPanel settings={settings} canWrite={hasScope(auth, "system:settings:write")} />}
|
|
{active === "system-retention" && <RetentionPoliciesPanel settings={settings} scopeType="system" canWrite={hasScope(auth, "system:settings:write")} />}
|
|
{active === "system-mail-servers" && <MailProfilesPanel settings={settings} scopeType="system" canWriteProfiles={hasScope(auth, "system:settings:write")} canManageCredentials={hasScope(auth, "system:settings:write")} canWritePolicy={hasScope(auth, "system:settings:write")} />}
|
|
{active === "system-tenants" && <TenantsPanel settings={settings} auth={auth} canCreate={hasScope(auth, "system:tenants:create")} canUpdate={hasScope(auth, "system:tenants:update")} canSuspend={hasScope(auth, "system:tenants:suspend")} onAuthRefresh={refreshAuth} />}
|
|
{active === "system-users" && <SystemUsersPanel
|
|
settings={settings}
|
|
canCreate={hasScope(auth, "system:accounts:create")}
|
|
canUpdate={hasScope(auth, "system:accounts:update")}
|
|
canSuspend={hasScope(auth, "system:accounts:suspend")}
|
|
canAssignRoles={hasAnyScope(auth, ["system:roles:assign", "system:access:assign"])}
|
|
canManageMemberships={hasScope(auth, "system:accounts:update") && hasScope(auth, "system:access:assign")}
|
|
onAuthRefresh={refreshAuth}
|
|
/>}
|
|
{active === "system-groups" && <GovernanceTemplatesPanel settings={settings} kind="group" canWrite={hasScope(auth, "system:governance:write")} onAuthRefresh={refreshAuth} />}
|
|
{active === "system-roles" && <SystemRolesPanel
|
|
settings={settings}
|
|
canWrite={hasScope(auth, "system:roles:write")}
|
|
onAuthRefresh={refreshAuth}
|
|
/>}
|
|
{active === "system-role-templates" && <GovernanceTemplatesPanel settings={settings} kind="role" canWrite={hasScope(auth, "system:governance:write")} onAuthRefresh={refreshAuth} />}
|
|
{active === "system-audit" && <AdminAuditPanel settings={settings} auth={auth} systemMode />}
|
|
{active === "tenant-users" && <UsersPanel settings={settings} auth={auth} canCreate={hasScope(auth, "admin:users:create")} canUpdate={hasScope(auth, "admin:users:update")} canSuspend={hasScope(auth, "admin:users:suspend")} canManageGroups={hasScope(auth, "admin:groups:manage_members")} canAssignRoles={hasScope(auth, "admin:roles:assign")} onAuthRefresh={refreshAuth} />}
|
|
{active === "tenant-groups" && <GroupsPanel settings={settings} auth={auth} canDefine={hasScope(auth, "admin:groups:write")} canManageMembers={hasScope(auth, "admin:groups:manage_members")} canAssignRoles={hasScope(auth, "admin:roles:assign")} onAuthRefresh={refreshAuth} />}
|
|
{active === "tenant-roles" && <RolesPanel settings={settings} auth={auth} canDefine={hasScope(auth, "admin:roles:write")} onAuthRefresh={refreshAuth} />}
|
|
{active === "tenant-api-keys" && <ApiKeysPanel settings={settings} auth={auth} canCreate={hasScope(auth, "admin:api_keys:create")} canRevoke={hasScope(auth, "admin:api_keys:revoke")} />}
|
|
{active === "tenant-mail-servers" && <MailProfilesPanel settings={settings} scopeType="tenant" canWriteProfiles={hasScope(auth, "mail_servers:write")} canManageCredentials={hasScope(auth, "mail_servers:manage_credentials")} canWritePolicy={hasScope(auth, "admin:policies:write")} />}
|
|
{active === "tenant-user-mail-servers" && <MailProfilesPanel settings={settings} scopeType="user" canWriteProfiles={hasScope(auth, "mail_servers:write")} canManageCredentials={hasScope(auth, "mail_servers:manage_credentials")} canWritePolicy={hasAnyScope(auth, ["admin:policies:write", "mail_servers:write"])} />}
|
|
{active === "tenant-group-mail-servers" && <MailProfilesPanel settings={settings} scopeType="group" canWriteProfiles={hasScope(auth, "mail_servers:write")} canManageCredentials={hasScope(auth, "mail_servers:manage_credentials")} canWritePolicy={hasAnyScope(auth, ["admin:policies:write", "mail_servers:write"])} />}
|
|
{active === "tenant-retention" && <RetentionPoliciesPanel settings={settings} scopeType="tenant" canWrite={hasScope(auth, "admin:policies:write")} />}
|
|
{active === "tenant-user-retention" && <RetentionPoliciesPanel settings={settings} scopeType="user" canWrite={hasScope(auth, "admin:policies:write")} />}
|
|
{active === "tenant-group-retention" && <RetentionPoliciesPanel settings={settings} scopeType="group" canWrite={hasScope(auth, "admin:policies:write")} />}
|
|
{active === "tenant-settings" && <TenantSettingsPanel settings={settings} canWrite={hasScope(auth, "admin:settings:write")} onAuthRefresh={refreshAuth} />}
|
|
{active === "tenant-audit" && <AdminAuditPanel settings={settings} auth={auth} />}
|
|
</div>
|
|
</section>
|
|
</div>
|
|
);
|
|
}
|