import { useEffect, useMemo } from "react"; import { useSearchParams } from "react-router-dom"; import type { AdminSectionContribution, AdminSectionsUiCapability, ApiSettings, AuthInfo, FilesConnectorsUiCapability, MailProfilesUiCapability, OrganizationFunctionPickerUiCapability } from "@govoplan/core-webui"; import { fetchMe } from "@govoplan/core-webui"; import { Card } from "@govoplan/core-webui"; import { ModuleSubnav, type ModuleSubnavGroup } from "@govoplan/core-webui"; import { adminReadScopes, hasAnyScope, hasScope } from "@govoplan/core-webui"; import SystemUsersPanel from "./SystemUsersPanel"; import TenantSettingsPanel from "./TenantSettingsPanel"; import SystemRolesPanel from "./SystemRolesPanel"; import TenantsPanel from "./TenantsPanel"; import UsersPanel from "./UsersPanel"; import GroupsPanel from "./GroupsPanel"; import RolesPanel from "./RolesPanel"; import ExternalFunctionRoleMappingsPanel from "./ExternalFunctionRoleMappingsPanel"; import ApiKeysPanel from "./ApiKeysPanel"; import FileConnectorsPanel from "./FileConnectorsPanel"; import MailProfilesPanel from "./MailProfilesPanel"; import { usePlatformUiCapabilities, usePlatformUiCapability } from "@govoplan/core-webui"; type AdminSection = string; type OrderedAdminNavItem = { id: AdminSection; label: string; order: number }; const handledAdminSectionIds = new Set([ "overview", "system-settings", "system-configuration-changes", "system-configuration-packages", "system-modules", "system-tenants", "system-roles", "system-role-templates", "system-groups", "system-users", "system-file-connectors", "system-mail-servers", "tenant-settings", "tenant-roles", "tenant-function-role-mappings", "tenant-groups", "tenant-users", "tenant-file-connectors", "tenant-mail-servers", "tenant-api-keys", "tenant-group-file-connectors", "tenant-group-mail-servers", "tenant-user-file-connectors", "tenant-user-mail-servers" ]); export default function AdminPage({ settings, auth, onAuthChange }: { settings: ApiSettings; auth: AuthInfo; onAuthChange: (auth: AuthInfo | null, accessToken?: string) => void; }) { const mailProfilesUi = usePlatformUiCapability("mail.profiles"); const fileConnectorsUi = usePlatformUiCapability("files.connectors"); const organizationFunctionPicker = usePlatformUiCapability("organizations.functionPicker"); const adminSectionCapabilities = usePlatformUiCapabilities("admin.sections"); const mailProfilesAvailable = Boolean(mailProfilesUi); const fileConnectorsAvailable = Boolean(fileConnectorsUi); const contributedSections = useMemo( () => adminSectionCapabilities .flatMap((capability) => capability.sections) .sort((left, right) => (left.order ?? 100) - (right.order ?? 100)), [adminSectionCapabilities] ); const contributionById = useMemo(() => { const mapped = new Map(); for (const section of contributedSections) { if (!mapped.has(section.id)) mapped.set(section.id, section); } return mapped; }, [contributedSections]); const available = useMemo(() => { const sections = new Set(); for (const section of contributedSections) { if (canUseContributedSection(auth, section)) sections.add(section.id); } if (hasScope(auth, "system:settings:read")) { 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 (hasAnyScope(auth, ["system:roles:read", "system:access:read"])) sections.add("system-roles"); 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 (organizationFunctionPicker && hasAnyScope(auth, ["admin:roles:read", "access:function:read", "access:role:read"])) sections.add("tenant-function-role-mappings"); 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 (fileConnectorsAvailable && hasAnyScope(auth, ["files:file:admin", "admin:settings:read"])) { if (hasScope(auth, "admin:users:read")) sections.add("tenant-user-file-connectors"); if (hasScope(auth, "admin:groups:read")) sections.add("tenant-group-file-connectors"); } if (hasScope(auth, "admin:settings:read")) sections.add("tenant-settings"); return sections; }, [auth, contributedSections, fileConnectorsAvailable, mailProfilesAvailable, organizationFunctionPicker]); const [searchParams, setSearchParams] = useSearchParams(); const requestedSection = searchParams.get("section") as AdminSection | null; const fallbackSection = available.has("overview") ? "overview" : (Array.from(available)[0] ?? "overview"); const active: AdminSection = requestedSection && available.has(requestedSection) ? requestedSection : fallbackSection; 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(fallbackSection); }, [requestedSection, available, fallbackSection]); async function refreshAuth() { onAuthChange(await fetchMe(settings)); } useEffect(() => { void refreshAuth().catch(() => undefined); }, [settings.accessToken, settings.apiBaseUrl]); if (!hasAnyScope(auth, adminReadScopes)) { return (

i18n:govoplan-access.your_current_roles_do_not_grant_administrative_a.6eafee69

); } const adminSubnav: ModuleSubnavGroup[] = [ { title: "ADMINISTRATION", items: asSubnavItems( sortNavItems([ ...contributedNavItems(contributedSections, available, "ROOT"), visibleNavItem(available, "system-modules", "i18n:govoplan-access.modules.04e9462c", 10), visibleNavItem(available, "system-configuration-packages", "i18n:govoplan-access.packages.0a999012", 20), visibleNavItem(available, "system-settings", "i18n:govoplan-access.maintenance.94de303b", 30), visibleNavItem(available, "system-configuration-changes", "i18n:govoplan-access.changes.8aa57de6", 40), ...contributedNavItems(contributedSections, available, "ADMINISTRATION", handledAdminSectionIds) ]) ) }, { title: "GLOBAL", items: asSubnavItems( sortNavItems([ visibleNavItem(available, "system-tenants", "i18n:govoplan-access.tenants.1f7ae776", 10), visibleNavItem(available, "system-roles", "i18n:govoplan-access.system_roles.a9461aa6", 20), visibleNavItem(available, "system-role-templates", "i18n:govoplan-access.tenant_role_templates", 30), visibleNavItem(available, "system-groups", "i18n:govoplan-access.group_templates", 40), visibleNavItem(available, "system-users", "i18n:govoplan-access.users.57f2b181", 50), visibleNavItem(available, "system-file-connectors", "i18n:govoplan-access.file_connections.1e362326", 60), visibleNavItem(available, "system-mail-servers", "i18n:govoplan-access.mail_servers.d627326a", 70), ...contributedNavItems(contributedSections, available, "GLOBAL", handledAdminSectionIds), ...contributedNavItems(contributedSections, available, "SYSTEM", handledAdminSectionIds) ]) ) }, { title: "TENANT", items: asSubnavItems( sortNavItems([ visibleNavItem(available, "tenant-roles", "i18n:govoplan-access.roles.47dcc27d", 10), visibleNavItem(available, "tenant-function-role-mappings", "i18n:govoplan-access.function_role_mappings.2b64e9c3", 20), visibleNavItem(available, "tenant-groups", "i18n:govoplan-access.groups.ae9629f4", 30), visibleNavItem(available, "tenant-users", "i18n:govoplan-access.users.57f2b181", 40), visibleNavItem(available, "tenant-file-connectors", "i18n:govoplan-access.file_connections.1e362326", 50), visibleNavItem(available, "tenant-mail-servers", "i18n:govoplan-access.mail_servers.d627326a", 60), visibleNavItem(available, "tenant-api-keys", "i18n:govoplan-access.api_keys.94fcf3c2", 70), visibleNavItem(available, "tenant-settings", "i18n:govoplan-access.general.9239ee2c", 90), ...contributedNavItems(contributedSections, available, "TENANT", handledAdminSectionIds) ]) ) }, { title: "GROUP", items: asSubnavItems( sortNavItems([ visibleNavItem(available, "tenant-group-file-connectors", "i18n:govoplan-access.file_connections.1e362326", 10), visibleNavItem(available, "tenant-group-mail-servers", "i18n:govoplan-access.mail_servers.d627326a", 20), ...contributedNavItems(contributedSections, available, "GROUP", handledAdminSectionIds) ]) ) }, { title: "USER", items: asSubnavItems( sortNavItems([ visibleNavItem(available, "tenant-user-file-connectors", "i18n:govoplan-access.file_connections.1e362326", 10), visibleNavItem(available, "tenant-user-mail-servers", "i18n:govoplan-access.mail_servers.d627326a", 20), ...contributedNavItems(contributedSections, available, "USER", handledAdminSectionIds) ]) ) } ].filter((group) => group.items.length > 0); const contributedSection = contributionById.get(active); const contributionContext = { settings, auth, onAuthChange, refreshAuth, availableSections: available, selectSection }; return (
{contributedSection && contributedSection.render(contributionContext)} {!contributedSection && active === "system-mail-servers" && ( )} {!contributedSection && active === "system-tenants" && ( )} {!contributedSection && active === "system-users" && ( )} {!contributedSection && active === "system-roles" && } {!contributedSection && active === "tenant-users" && } {!contributedSection && active === "tenant-groups" && } {!contributedSection && active === "tenant-roles" && } {!contributedSection && active === "tenant-function-role-mappings" && organizationFunctionPicker && } {!contributedSection && active === "tenant-api-keys" && } {!contributedSection && active === "tenant-mail-servers" && } {!contributedSection && active === "tenant-user-mail-servers" && } {!contributedSection && active === "tenant-group-mail-servers" && } {!contributedSection && active === "tenant-user-file-connectors" && } {!contributedSection && active === "tenant-group-file-connectors" && } {!contributedSection && active === "tenant-settings" && }
); } function canUseContributedSection(auth: AuthInfo, section: AdminSectionContribution): boolean { if (section.allOf?.length && !section.allOf.every((scope) => hasScope(auth, scope))) return false; if (section.anyOf?.length && !hasAnyScope(auth, section.anyOf)) return false; return true; } function contributedNavItems( sections: AdminSectionContribution[], available: ReadonlySet, group: string, excludedIds: ReadonlySet = new Set() ): OrderedAdminNavItem[] { return sections .filter((section) => (section.group ?? "SYSTEM") === group && available.has(section.id) && !excludedIds.has(section.id)) .map((section) => ({ id: section.id, label: section.label, order: section.order ?? 100 })); } function visibleNavItem(available: ReadonlySet, id: AdminSection, label: string, order: number): OrderedAdminNavItem | null { return available.has(id) ? { id, label, order } : null; } function sortNavItems(items: Array): OrderedAdminNavItem[] { return items.filter((item): item is OrderedAdminNavItem => item !== null).sort((left, right) => left.order - right.order); } function asSubnavItems(items: OrderedAdminNavItem[]) { return items.map(({ id, label }) => ({ id, label })); }