From 1b57df775323587b27557091702ba83aaf89f336 Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Tue, 28 Jul 2026 21:04:54 +0200 Subject: [PATCH] Expose administration View surfaces --- src/govoplan_access/backend/manifest.py | 21 ++++++++++ webui/src/features/admin/AdminPage.tsx | 53 +++++++++++++++++++++++-- webui/src/module.ts | 20 +++++++++- 3 files changed, 89 insertions(+), 5 deletions(-) diff --git a/src/govoplan_access/backend/manifest.py b/src/govoplan_access/backend/manifest.py index 3c5ed9f..613a90b 100644 --- a/src/govoplan_access/backend/manifest.py +++ b/src/govoplan_access/backend/manifest.py @@ -42,6 +42,7 @@ from govoplan_core.core.modules import ( RoleTemplate, ) from govoplan_core.core.people import CAPABILITY_ACCESS_PEOPLE_SEARCH +from govoplan_core.core.views import ViewSurface def _permission(scope: str, label: str, description: str, category: str, level: str) -> PermissionDefinition: @@ -254,6 +255,10 @@ ADMIN_READ_SCOPES = ( "access:account:read", "access:governance:read", "access:function:read", + "views:definition:read", + "views:assignment:read", + "views:system_definition:read", + "views:system_assignment:read", ) ACCESS_DOCUMENTATION: tuple[DocumentationTopic, ...] = ( @@ -676,6 +681,22 @@ manifest = ModuleManifest( package_name="@govoplan/access-webui", routes=(FrontendRoute(path="/admin", component="AdminPage", required_any=ADMIN_READ_SCOPES, order=900),), nav_items=(NavItem(path="/admin", label="Admin", icon="admin", required_any=ADMIN_READ_SCOPES, order=900),), + view_surfaces=( + ViewSurface(id="access.admin.system-tenants", module_id="access", kind="section", label="System tenants", order=10), + ViewSurface(id="access.admin.system-roles", module_id="access", kind="section", label="System roles", order=20), + ViewSurface(id="access.admin.system-users", module_id="access", kind="section", label="System users", order=50), + ViewSurface(id="access.admin.system-credentials", module_id="access", kind="section", label="System credentials", order=80), + ViewSurface(id="access.admin.tenant-roles", module_id="access", kind="section", label="Tenant roles", order=10), + ViewSurface(id="access.admin.tenant-function-mappings", module_id="access", kind="section", label="Function mappings", order=20), + ViewSurface(id="access.admin.tenant-groups", module_id="access", kind="section", label="Tenant groups", order=30), + ViewSurface(id="access.admin.tenant-users", module_id="access", kind="section", label="Tenant users", order=40), + ViewSurface(id="access.admin.tenant-credentials", module_id="access", kind="section", label="Tenant credentials", order=70), + ViewSurface(id="access.admin.tenant-api-keys", module_id="access", kind="section", label="Tenant API keys", order=80), + ViewSurface(id="access.admin.tenant-settings", module_id="access", kind="section", label="Tenant settings", order=90), + ViewSurface(id="access.admin.group-credentials", module_id="access", kind="section", label="Group credentials", order=30), + ViewSurface(id="access.admin.user-credentials", module_id="access", kind="section", label="User credentials", order=30), + ViewSurface(id="access.settings.credentials", module_id="access", kind="section", label="Personal credentials", order=30), + ), ), capability_factories={ CAPABILITY_AUTH_API_PRINCIPAL_PROVIDER: _api_principal_provider, diff --git a/webui/src/features/admin/AdminPage.tsx b/webui/src/features/admin/AdminPage.tsx index 4ee9b18..c009c6f 100644 --- a/webui/src/features/admin/AdminPage.tsx +++ b/webui/src/features/admin/AdminPage.tsx @@ -26,7 +26,13 @@ import ApiKeysPanel from "./ApiKeysPanel"; import FileConnectorsPanel from "./FileConnectorsPanel"; import MailProfilesPanel from "./MailProfilesPanel"; import CredentialEnvelopesPanel from "./CredentialEnvelopesPanel"; -import { usePlatformUiCapabilities, usePlatformUiCapability } from "@govoplan/core-webui"; +import { + isViewSurfaceVisible, + useEffectiveView, + usePlatformUiCapabilities, + usePlatformUiCapability, + useViewSurfaces +} from "@govoplan/core-webui"; type AdminSection = string; type OrderedAdminNavItem = { id: AdminSection; label: string; order: number }; @@ -62,6 +68,28 @@ const handledAdminSectionIds = new Set([ "tenant-user-credentials" ]); +const builtInAdminSurfaceIds: Record = { + "system-tenants": "access.admin.system-tenants", + "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-settings": "access.admin.tenant-settings", + "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" +}; + export default function AdminPage({ settings, auth, @@ -75,14 +103,23 @@ export default function AdminPage({ 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] + [adminSectionCapabilities, effectiveView, viewSurfaces] ); const contributionById = useMemo(() => { const mapped = new Map(); @@ -126,8 +163,16 @@ export default function AdminPage({ if (hasScope(auth, "admin:users:read")) sections.add("tenant-user-credentials"); if (hasScope(auth, "admin:groups:read")) sections.add("tenant-group-credentials"); } - return sections; - }, [auth, contributedSections, fileConnectorsAvailable, mailProfilesAvailable, organizationFunctionPicker]); + 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"); diff --git a/webui/src/module.ts b/webui/src/module.ts index 5b28a32..741f4a6 100644 --- a/webui/src/module.ts +++ b/webui/src/module.ts @@ -10,6 +10,23 @@ const translations = { de: generatedTranslations.de }; +const accessAdminSurfaces = [ + { id: "access.admin.system-tenants", moduleId: "access", kind: "section" as const, label: "System tenants", order: 10 }, + { id: "access.admin.system-roles", moduleId: "access", kind: "section" as const, label: "System roles", order: 20 }, + { id: "access.admin.system-users", moduleId: "access", kind: "section" as const, label: "System users", order: 50 }, + { id: "access.admin.system-credentials", moduleId: "access", kind: "section" as const, label: "System credentials", order: 80 }, + { id: "access.admin.tenant-roles", moduleId: "access", kind: "section" as const, label: "Tenant roles", order: 10 }, + { id: "access.admin.tenant-function-mappings", moduleId: "access", kind: "section" as const, label: "Function mappings", order: 20 }, + { id: "access.admin.tenant-groups", moduleId: "access", kind: "section" as const, label: "Tenant groups", order: 30 }, + { id: "access.admin.tenant-users", moduleId: "access", kind: "section" as const, label: "Tenant users", order: 40 }, + { id: "access.admin.tenant-credentials", moduleId: "access", kind: "section" as const, label: "Tenant credentials", order: 70 }, + { id: "access.admin.tenant-api-keys", moduleId: "access", kind: "section" as const, label: "Tenant API keys", order: 80 }, + { id: "access.admin.tenant-settings", moduleId: "access", kind: "section" as const, label: "Tenant settings", order: 90 }, + { id: "access.admin.group-credentials", moduleId: "access", kind: "section" as const, label: "Group credentials", order: 30 }, + { id: "access.admin.user-credentials", moduleId: "access", kind: "section" as const, label: "User credentials", order: 30 }, + { id: "access.settings.credentials", moduleId: "access", kind: "section" as const, label: "Personal credentials", order: 30 } +]; + function renderAdminRoute({ settings, auth, onAuthChange }: PlatformRouteContext) { if (!onAuthChange) { throw new Error("i18n:govoplan-access.the_access_admin_route_requires_the_platform_aut.0173a45f"); @@ -22,6 +39,7 @@ export const accessModule: PlatformWebModule = { label: "i18n:govoplan-access.access.2f81a22d", version: "1.0.0", translations, + viewSurfaces: accessAdminSurfaces, navItems: [ { to: "/admin", label: "i18n:govoplan-access.admin.4e7afebc", iconName: "admin", anyOf: adminReadScopes, order: 900 }], @@ -30,4 +48,4 @@ export const accessModule: PlatformWebModule = { }; -export default accessModule; \ No newline at end of file +export default accessModule;