import { useEffect, useMemo } from "react"; import { useSearchParams } from "react-router"; import type { AdminSectionContribution, AdminSectionsUiCapability, ApiSettings, AuthInfo, AuthUpdate, FilesConnectorsUiCapability, MailProfilesUiCapability, OrganizationFunctionPickerUiCapability } from "@govoplan/core-webui"; import { fetchShellAuth } from "@govoplan/core-webui"; import { ActionBlockerHint } from "@govoplan/core-webui"; import { PageLayout, WorkspaceLayout } from "@govoplan/core-webui"; import { PageScrollViewport } from "@govoplan/core-webui"; import { TreeSubnav, type TreeSubnavNode } from "@govoplan/core-webui"; import { adminReadScopes, hasAnyScope, hasScope } from "@govoplan/core-webui"; import SystemUsersPanel from "./SystemUsersPanel"; import SystemRolesPanel from "./SystemRolesPanel"; import UsersPanel from "./UsersPanel"; import GroupsPanel from "./GroupsPanel"; import RolesPanel from "./RolesPanel"; import ExternalFunctionRoleMappingsPanel from "./ExternalFunctionRoleMappingsPanel"; import ApiKeysPanel from "./ApiKeysPanel"; import ServiceAccountsPanel from "./ServiceAccountsPanel"; import FileConnectorsPanel from "./FileConnectorsPanel"; import MailProfilesPanel from "./MailProfilesPanel"; import CredentialEnvelopesPanel from "./CredentialEnvelopesPanel"; import { ACCESS_INTERFACE_I18N, ACCESS_WORKFLOW_DOCUMENTATION } from "./interfacePatterns"; import { isViewSurfaceVisible, useEffectiveView, usePlatformUiCapabilities, usePlatformUiCapability, useViewSurfaces } from "@govoplan/core-webui"; type AdminSection = string; type OrderedAdminNavItem = { id: AdminSection; label: string; order: number; moduleId?: string; kind?: "management" | "settings"; }; type AdminNavGroup = { id: string; title: string; items: OrderedAdminNavItem[]; }; const handledAdminSectionIds = new Set([ "overview", "system-settings", "system-configuration-changes", "system-configuration-packages", "system-modules", "system-roles", "system-role-templates", "system-groups", "system-users", "system-file-connectors", "system-mail-servers", "system-credentials", "tenant-roles", "tenant-function-role-mappings", "tenant-groups", "tenant-users", "tenant-file-connectors", "tenant-mail-servers", "tenant-credentials", "tenant-api-keys", "tenant-service-accounts", "tenant-group-file-connectors", "tenant-group-mail-servers", "tenant-group-credentials", "tenant-user-file-connectors", "tenant-user-mail-servers", "tenant-user-credentials" ]); const builtInAdminSurfaceIds: Record = { "system-roles": "access.admin.system-roles", "system-users": "access.admin.system-users", "system-credentials": "access.admin.system-credentials", "tenant-roles": "access.admin.tenant-roles", "tenant-function-role-mappings": "access.admin.tenant-function-mappings", "tenant-groups": "access.admin.tenant-groups", "tenant-users": "access.admin.tenant-users", "tenant-credentials": "access.admin.tenant-credentials", "tenant-api-keys": "access.admin.tenant-api-keys", "tenant-service-accounts": "access.admin.tenant-service-accounts", "tenant-group-credentials": "access.admin.group-credentials", "tenant-user-credentials": "access.admin.user-credentials", "system-mail-servers": "mail.admin.system-servers", "tenant-mail-servers": "mail.admin.tenant-servers", "tenant-group-mail-servers": "mail.admin.group-servers", "tenant-user-mail-servers": "mail.admin.user-servers", "tenant-group-file-connectors": "files.admin.group-connectors", "tenant-user-file-connectors": "files.admin.user-connectors" }; const builtInAdminSectionMetadata: Record< string, Pick > = { "system-settings": { moduleId: "admin", kind: "settings" }, "system-file-connectors": { moduleId: "files", kind: "settings" }, "system-mail-servers": { moduleId: "mail", kind: "settings" }, "system-credentials": { moduleId: "access", kind: "settings" }, "tenant-file-connectors": { moduleId: "files", kind: "settings" }, "tenant-mail-servers": { moduleId: "mail", kind: "settings" }, "tenant-credentials": { moduleId: "access", kind: "settings" }, "tenant-group-file-connectors": { moduleId: "files", kind: "settings" }, "tenant-group-mail-servers": { moduleId: "mail", kind: "settings" }, "tenant-group-credentials": { moduleId: "access", kind: "settings" }, "tenant-user-file-connectors": { moduleId: "files", kind: "settings" }, "tenant-user-mail-servers": { moduleId: "mail", kind: "settings" }, "tenant-user-credentials": { moduleId: "access", kind: "settings" } }; export default function AdminPage({ settings, auth, onAuthChange }: { settings: ApiSettings; auth: AuthInfo; onAuthChange: (auth: AuthUpdate | 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 effectiveView = useEffectiveView(); const viewSurfaces = useViewSurfaces(); const mailProfilesAvailable = Boolean(mailProfilesUi); const fileConnectorsAvailable = Boolean(fileConnectorsUi); const contributedSections = useMemo( () => adminSectionCapabilities .flatMap((capability) => capability.sections) .filter((section) => isViewSurfaceVisible( effectiveView, section.surfaceId, viewSurfaces ) ) .sort((left, right) => (left.order ?? 100) - (right.order ?? 100)), [adminSectionCapabilities, effectiveView, viewSurfaces] ); 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 (hasAnyScope(auth, ["system:settings:read", "access:system_credential:read"])) { sections.add("system-credentials"); } 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 (hasScope(auth, "access:service_account:read")) sections.add("tenant-service-accounts"); 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 (hasAnyScope(auth, ["admin:settings:read", "access:credential:read"])) { sections.add("tenant-credentials"); if (hasScope(auth, "admin:users:read")) sections.add("tenant-user-credentials"); if (hasScope(auth, "admin:groups:read")) sections.add("tenant-group-credentials"); } return new Set( [...sections].filter((sectionId) => isViewSurfaceVisible( effectiveView, builtInAdminSurfaceIds[sectionId], viewSurfaces ) ) ); }, [auth, contributedSections, effectiveView, fileConnectorsAvailable, mailProfilesAvailable, organizationFunctionPicker, viewSurfaces]); 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 fetchShellAuth(settings)); } if (!hasAnyScope(auth, adminReadScopes)) { return (
); } const adminNavGroups: AdminNavGroup[] = [ { id: "administration", title: "i18n:govoplan-access.admin.4e7afebc", items: 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) ]) }, { id: "global", title: "i18n:govoplan-access.global", items: sortNavItems([ 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), visibleNavItem(available, "system-credentials", "i18n:govoplan-core.credentials.dd097a22", 80), ...contributedNavItems(contributedSections, available, "GLOBAL", handledAdminSectionIds), ...contributedNavItems(contributedSections, available, "SYSTEM", handledAdminSectionIds) ]) }, { id: "tenant", title: "i18n:govoplan-access.tenant.3ca93c78", items: 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-credentials", "i18n:govoplan-core.credentials.dd097a22", 70), visibleNavItem(available, "tenant-api-keys", "i18n:govoplan-access.api_keys.94fcf3c2", 80), visibleNavItem(available, "tenant-service-accounts", "Service accounts", 90), ...contributedNavItems(contributedSections, available, "TENANT", handledAdminSectionIds) ]) }, { id: "group", title: "i18n:govoplan-access.group.171a0606", items: 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), visibleNavItem(available, "tenant-group-credentials", "i18n:govoplan-core.credentials.dd097a22", 30), ...contributedNavItems(contributedSections, available, "GROUP", handledAdminSectionIds) ]) }, { id: "user", title: "i18n:govoplan-access.user.9f8a2389", items: 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), visibleNavItem(available, "tenant-user-credentials", "i18n:govoplan-core.credentials.dd097a22", 30), ...contributedNavItems(contributedSections, available, "USER", handledAdminSectionIds) ]) } ].filter((group) => group.items.length > 0); const adminTree = adminNavigationTree(adminNavGroups); const contributedSection = contributionById.get(active); const contributionContext = { settings, auth, onAuthChange, refreshAuth, availableSections: available, selectSection }; return ( )} primaryLabel="i18n:govoplan-access.admin.4e7afebc" contentLabel="i18n:govoplan-access.admin.4e7afebc" documentationType="admin" > {contributedSection && contributedSection.render(contributionContext)} {!contributedSection && active === "system-mail-servers" && ( )} {!contributedSection && active === "system-credentials" && ( )} {!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-service-accounts" && } {!contributedSection && active === "tenant-mail-servers" && } {!contributedSection && active === "tenant-credentials" && } {!contributedSection && active === "tenant-user-mail-servers" && } {!contributedSection && active === "tenant-group-mail-servers" && } {!contributedSection && active === "tenant-user-credentials" && } {!contributedSection && active === "tenant-group-credentials" && } {!contributedSection && active === "tenant-user-file-connectors" && } {!contributedSection && active === "tenant-group-file-connectors" && } ); } 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, moduleId: section.moduleId, kind: section.kind })); } function visibleNavItem(available: ReadonlySet, id: AdminSection, label: string, order: number): OrderedAdminNavItem | null { return available.has(id) ? { id, label, order, ...builtInAdminSectionMetadata[id] } : null; } function sortNavItems(items: Array): OrderedAdminNavItem[] { return items.filter((item): item is OrderedAdminNavItem => item !== null).sort((left, right) => left.order - right.order); } function adminNavigationTree( groups: AdminNavGroup[] ): TreeSubnavNode[] { return groups.map((group) => { const managementItems = group.items.filter( (item) => item.kind !== "settings" ); const settingsItems = group.items.filter( (item) => item.kind === "settings" ); const children: TreeSubnavNode[] = managementItems.map( ({ id, label }) => ({ id, label }) ); if (settingsItems.length > 0) { const byModule = new Map(); for (const item of settingsItems) { const moduleId = item.moduleId ?? "platform"; byModule.set(moduleId, [...(byModule.get(moduleId) ?? []), item]); } children.push({ branchId: `admin-${group.id}-settings`, label: "i18n:govoplan-core.settings.c7f73bb5", defaultExpanded: false, children: [...byModule.entries()] .sort(([left], [right]) => left.localeCompare(right)) .map(([moduleId, items]) => ({ branchId: `admin-${group.id}-settings-${moduleId}`, label: moduleLabel(moduleId), defaultExpanded: items.some( (item) => item.id === "system-settings" ), children: items.map(({ id, label }) => ({ id, label })) })) }); } return { branchId: `admin-${group.id}`, label: group.title, defaultExpanded: group.id === "administration", children }; }); } function moduleLabel(moduleId: string): string { if (moduleId === "platform") return "i18n:govoplan-access.platform_administration"; if (moduleId === "access") return "i18n:govoplan-access.access.2f81a22d"; if (moduleId === "admin") return "i18n:govoplan-access.admin.4e7afebc"; if (moduleId === "files") return "i18n:govoplan-access.files.6ce6c512"; if (moduleId === "mail") return "i18n:govoplan-access.mail_servers.d627326a"; return moduleId .split(/[-_]/) .filter(Boolean) .map((part) => part[0].toUpperCase() + part.slice(1)) .join(" "); }