Migrate Access administration patterns

This commit is contained in:
2026-08-03 11:01:22 +02:00
parent fe247999e9
commit 1409dbf94d
20 changed files with 498 additions and 84 deletions
+43 -16
View File
@@ -11,7 +11,7 @@ import type {
OrganizationFunctionPickerUiCapability
} from "@govoplan/core-webui";
import { fetchShellAuth } from "@govoplan/core-webui";
import { Card } from "@govoplan/core-webui";
import { ActionBlockerHint } from "@govoplan/core-webui";
import { PageScrollViewport } from "@govoplan/core-webui";
import {
TreeSubnav,
@@ -28,6 +28,10 @@ import ApiKeysPanel from "./ApiKeysPanel";
import FileConnectorsPanel from "./FileConnectorsPanel";
import MailProfilesPanel from "./MailProfilesPanel";
import CredentialEnvelopesPanel from "./CredentialEnvelopesPanel";
import {
ACCESS_INTERFACE_I18N,
ACCESS_WORKFLOW_DOCUMENTATION
} from "./interfacePatterns";
import {
isViewSurfaceVisible,
useEffectiveView,
@@ -45,6 +49,7 @@ type OrderedAdminNavItem = {
kind?: "management" | "settings";
};
type AdminNavGroup = {
id: string;
title: string;
items: OrderedAdminNavItem[];
};
@@ -221,18 +226,32 @@ export default function AdminPage({
if (!hasAnyScope(auth, adminReadScopes)) {
return (
<PageScrollViewport>
<div className="content-pad">
<Card title="i18n:govoplan-access.administration_unavailable.b86d4cb5">
<p>i18n:govoplan-access.your_current_roles_do_not_grant_administrative_a.6eafee69</p>
</Card>
</div>
<div className="content-pad">
<ActionBlockerHint
tone="warning"
reason={{
summary: "i18n:govoplan-access.administration_unavailable.b86d4cb5",
details: "i18n:govoplan-access.your_current_roles_do_not_grant_administrative_a.6eafee69",
requiredAction: ACCESS_INTERFACE_I18N.requestAdministrationAccess,
actor: ACCESS_INTERFACE_I18N.accessAdministrator,
target: ACCESS_INTERFACE_I18N.accessAdministration
}}
labels={{
requiredAction: ACCESS_INTERFACE_I18N.requiredAction,
actor: ACCESS_INTERFACE_I18N.actor,
target: ACCESS_INTERFACE_I18N.destinationLabel
}}
documentation={ACCESS_WORKFLOW_DOCUMENTATION}
/>
</div>
</PageScrollViewport>
);
}
const adminNavGroups: AdminNavGroup[] = [
{
title: "ADMINISTRATION",
id: "administration",
title: "i18n:govoplan-access.admin.4e7afebc",
items: sortNavItems([
...contributedNavItems(contributedSections, available, "ROOT"),
visibleNavItem(available, "system-modules", "i18n:govoplan-access.modules.04e9462c", 10),
@@ -243,7 +262,8 @@ export default function AdminPage({
])
},
{
title: "GLOBAL",
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),
@@ -257,7 +277,8 @@ export default function AdminPage({
])
},
{
title: "TENANT",
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),
@@ -271,7 +292,8 @@ export default function AdminPage({
])
},
{
title: "GROUP",
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),
@@ -280,7 +302,8 @@ export default function AdminPage({
])
},
{
title: "USER",
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),
@@ -398,13 +421,13 @@ function adminNavigationTree(
byModule.set(moduleId, [...(byModule.get(moduleId) ?? []), item]);
}
children.push({
branchId: `admin-${group.title.toLowerCase()}-settings`,
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.title.toLowerCase()}-settings-${moduleId}`,
branchId: `admin-${group.id}-settings-${moduleId}`,
label: moduleLabel(moduleId),
defaultExpanded: items.some(
(item) => item.id === "system-settings"
@@ -414,16 +437,20 @@ function adminNavigationTree(
});
}
return {
branchId: `admin-${group.title.toLowerCase()}`,
branchId: `admin-${group.id}`,
label: group.title,
defaultExpanded: group.title === "ADMINISTRATION",
defaultExpanded: group.id === "administration",
children
};
});
}
function moduleLabel(moduleId: string): string {
if (moduleId === "platform") return "Platform";
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)
+5 -4
View File
@@ -10,9 +10,10 @@ import { DateTimeField } from "@govoplan/core-webui";
import { StatusBadge } from "@govoplan/core-webui";
import { ToggleSwitch } from "@govoplan/core-webui";
import { ConfirmDialog } from "@govoplan/core-webui";
import { AdminIconButton, AdminPageLayout, AdminSelectionList, TableActionGroup, adminErrorMessage, formatAdminDateTime as formatDateTime, useDeltaWatermarks } from "@govoplan/core-webui";
import { AdminIconButton, AdminPageLayout, AdminSelectionList, DocumentationHelpLink, TableActionGroup, adminErrorMessage, formatAdminDateTime as formatDateTime, useDeltaWatermarks } from "@govoplan/core-webui";
import { scopeGrants, i18nMessage, useUnsavedDraftGuard } from "@govoplan/core-webui";
import { loadDeltaRows } from "./utils/deltaRows";
import { ACCESS_INTERFACE_I18N, ACCESS_REFERENCE_DOCUMENTATION, saveDisabledReason } from "./interfacePatterns";
function defaultDraft(userId: string) {
return { name: "", userId, scopes: ["campaign:read"], expiresAt: "" };
@@ -102,7 +103,7 @@ export default function ApiKeysPanel({ settings, auth, canCreate, canRevoke }: {
{ id: "expires", header: "i18n:govoplan-access.expires.a99be3da", width: 180, minWidth: 150, resizable: true, sortable: true, value: (row) => row.expires_at || "", render: (row) => row.expires_at ? formatDateTime(row.expires_at) : "i18n:govoplan-access.no_expiry.39d436aa" },
{ id: "actions", header: "i18n:govoplan-access.actions.c3cd636a", width: 108, sticky: "end", resizable: false, align: "right", render: (row) => <TableActionGroup actions={[
{ id: "inspect", label: i18nMessage("i18n:govoplan-access.inspect_value.9d5d1071", { value0: row.name }), icon: <Search />, onClick: () => setViewing(row) },
{ id: "revoke", label: i18nMessage("i18n:govoplan-access.revoke_value.34640d6a", { value0: row.name }), icon: <Trash2 />, variant: "danger", applicable: !row.revoked_at, disabled: !canRevoke, onClick: () => setRevoking(row) }
{ id: "revoke", label: i18nMessage("i18n:govoplan-access.revoke_value.34640d6a", { value0: row.name }), icon: <Trash2 />, variant: "danger", applicable: !row.revoked_at, disabled: !canRevoke, disabledReason: row.revoked_at ? "i18n:govoplan-access.revoked.85f17ac0" : !canRevoke ? ACCESS_INTERFACE_I18N.writePermissionRequired : undefined, onClick: () => setRevoking(row) }
]} /> }],
[canRevoke]);
@@ -151,11 +152,11 @@ export default function ApiKeysPanel({ settings, auth, canCreate, canRevoke }: {
return (
<>
<AdminPageLayout title="i18n:govoplan-access.tenant_api_keys.4b1d81f8" description="i18n:govoplan-access.tenant_scoped_automation_credentials_are_capped_.9059dcae" loading={loading} error={error} success={success} actions={<><ToggleSwitch label="i18n:govoplan-access.show_revoked.b4265807" checked={showRevoked} onChange={setShowRevoked} /><Button onClick={() => void load()} disabled={loading}>i18n:govoplan-access.reload.cce71553</Button><AdminIconButton label="i18n:govoplan-access.add_api_key.725d9988" icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canCreate || !users.length} /></>}>
<AdminPageLayout title="i18n:govoplan-access.tenant_api_keys.4b1d81f8" description="i18n:govoplan-access.tenant_scoped_automation_credentials_are_capped_.9059dcae" loading={loading} error={error} success={success} actions={<><DocumentationHelpLink reference={ACCESS_REFERENCE_DOCUMENTATION} /><ToggleSwitch label="i18n:govoplan-access.show_revoked.b4265807" checked={showRevoked} onChange={setShowRevoked} /><Button onClick={() => void load()} disabled={loading} disabledReason={loading ? ACCESS_INTERFACE_I18N.loading : undefined}>i18n:govoplan-access.reload.cce71553</Button><AdminIconButton label="i18n:govoplan-access.add_api_key.725d9988" icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canCreate || !users.length} disabledReason={!canCreate ? ACCESS_INTERFACE_I18N.createPermissionRequired : !users.length ? ACCESS_INTERFACE_I18N.selectUserAndScopes : undefined} /></>}>
<div className="admin-table-surface"><DataGrid id="admin-api-keys-v3" rows={keys} columns={columns} initialFit="container" getRowKey={(row) => row.id} emptyText="i18n:govoplan-access.no_api_keys_found.1f377128" /></div>
</AdminPageLayout>
<Dialog open={creating} title="i18n:govoplan-access.create_api_key.d7b30388" onClose={() => !busy && setCreating(false)} className="admin-dialog admin-dialog-wide" footer={<><Button onClick={() => setCreating(false)} disabled={busy}>i18n:govoplan-access.cancel.77dfd213</Button><Button variant="primary" onClick={() => void save()} disabled={busy || !draft.name.trim() || !draft.userId || !draft.scopes.length}>{busy ? "i18n:govoplan-access.creating.94d7d8ee" : "i18n:govoplan-access.create_key.e028cb09"}</Button></>}>
<Dialog open={creating} title="i18n:govoplan-access.create_api_key.d7b30388" onClose={() => !busy && setCreating(false)} className="admin-dialog admin-dialog-wide" footer={<><Button onClick={() => setCreating(false)} disabled={busy} disabledReason={busy ? ACCESS_INTERFACE_I18N.operationInProgress : undefined}>i18n:govoplan-access.cancel.77dfd213</Button><Button variant="primary" onClick={() => void save()} disabledReason={saveDisabledReason({ busy, permitted: canCreate, complete: Boolean(draft.name.trim() && draft.userId && draft.scopes.length) })}>{busy ? "i18n:govoplan-access.creating.94d7d8ee" : "i18n:govoplan-access.create_key.e028cb09"}</Button></>}>
<div className="admin-form-grid two-columns">
<FormField label="i18n:govoplan-access.name.709a2322"><input value={draft.name} onChange={(event) => setDraft({ ...draft, name: event.target.value })} /></FormField>
<FormField label="i18n:govoplan-access.owner.89ff3122"><select value={draft.userId} onChange={(event) => {const userId = event.target.value;const user = users.find((item) => item.id === userId);const allowed = new Set(permissions.filter((permission) => user?.effective_scopes.some((scope) => scopeGrants(scope, permission.scope))).map((permission) => permission.scope));setDraft({ ...draft, userId, scopes: draft.scopes.filter((scope) => allowed.has(scope)) });}}><option value="">i18n:govoplan-access.select_user.b8a1d9de</option>{users.map((user) => <option key={user.id} value={user.id}>{user.display_name || user.email} {user.email}</option>)}</select></FormField>
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from "react";
import {
AdminPageLayout,
CredentialEnvelopeManager,
DocumentationHelpLink,
adminErrorMessage,
useDeltaWatermarks,
type ApiSettings,
@@ -15,6 +16,10 @@ import {
type UserAdminItem
} from "../../api/admin";
import { loadDeltaRows } from "./utils/deltaRows";
import {
ACCESS_INTERFACE_I18N,
CREDENTIAL_DOCUMENTATION
} from "./interfacePatterns";
type ScopeType = Extract<MailProfileScope, "system" | "tenant" | "user" | "group">;
@@ -116,13 +121,14 @@ export default function CredentialEnvelopesPanel({
description={scopeDescription(scopeType)}
loading={loadingTargets}
error={targetError}
actions={<DocumentationHelpLink reference={CREDENTIAL_DOCUMENTATION} />}
>
<CredentialEnvelopeManager
settings={settings}
scopeType={scopeType}
targetOptions={targets}
targetLabel={scopeType === "group" ? "Group" : "User"}
title="Reusable credentials"
targetLabel={scopeType === "group" ? "i18n:govoplan-access.group.171a0606" : "i18n:govoplan-access.user.9f8a2389"}
title={ACCESS_INTERFACE_I18N.reusableCredentials}
canWrite={canWrite}
/>
</AdminPageLayout>
@@ -130,18 +136,20 @@ export default function CredentialEnvelopesPanel({
}
function scopeTitle(scopeType: ScopeType): string {
if (scopeType === "system") return "System credentials";
if (scopeType === "tenant") return "Tenant credentials";
if (scopeType === "group") return "Group credentials";
return "User credentials";
if (scopeType === "system") return ACCESS_INTERFACE_I18N.systemCredentials;
if (scopeType === "tenant") return ACCESS_INTERFACE_I18N.tenantCredentials;
if (scopeType === "group") return ACCESS_INTERFACE_I18N.groupCredentials;
return ACCESS_INTERFACE_I18N.userCredentials;
}
function scopeDescription(scopeType: ScopeType): string {
if (scopeType === "system") {
return "Instance credentials that can be inherited by tenants and limited to selected modules or servers.";
return ACCESS_INTERFACE_I18N.systemCredentialDescription;
}
if (scopeType === "tenant") {
return "Tenant credentials shared by Mail, Files, Calendar, Addresses, and other permitted modules.";
return ACCESS_INTERFACE_I18N.tenantCredentialDescription;
}
return `Reusable credentials owned by the selected ${scopeType}.`;
return scopeType === "group"
? ACCESS_INTERFACE_I18N.groupCredentialDescription
: ACCESS_INTERFACE_I18N.userCredentialDescription;
}
@@ -15,9 +15,10 @@ import { DataGrid, type DataGridColumn } from "@govoplan/core-webui";
import { Dialog } from "@govoplan/core-webui";
import { FormField } from "@govoplan/core-webui";
import { ConfirmDialog } from "@govoplan/core-webui";
import { AdminIconButton, AdminPageLayout, TableActionGroup, adminErrorMessage, formatAdminDateTime as formatDateTime } from "@govoplan/core-webui";
import { AdminIconButton, AdminPageLayout, DocumentationHelpLink, TableActionGroup, adminErrorMessage, formatAdminDateTime as formatDateTime } from "@govoplan/core-webui";
import { i18nMessage, useDeltaWatermarks, useUnsavedDraftGuard } from "@govoplan/core-webui";
import { loadDeltaRows } from "./utils/deltaRows";
import { ACCESS_INTERFACE_I18N, FUNCTION_MAPPING_DOCUMENTATION, saveDisabledReason } from "./interfacePatterns";
const emptyDraft = {
sourceModule: "organizations",
@@ -258,8 +259,8 @@ export default function ExternalFunctionRoleMappingsPanel({
resizable: false,
align: "right",
render: (row) => <TableActionGroup actions={[
{ id: "edit", label: i18nMessage("i18n:govoplan-access.edit_value.fad75899", { value0: row.function_id }), icon: <Pencil />, disabled: !canWrite, onClick: () => openEdit(row) },
{ id: "delete", label: i18nMessage("i18n:govoplan-access.delete_value.4d18989e", { value0: row.function_id }), icon: <Trash2 />, variant: "danger", disabled: !canWrite, onClick: () => setDeleting(row) }
{ id: "edit", label: i18nMessage("i18n:govoplan-access.edit_value.fad75899", { value0: row.function_id }), icon: <Pencil />, disabled: !canWrite, disabledReason: !canWrite ? ACCESS_INTERFACE_I18N.writePermissionRequired : undefined, onClick: () => openEdit(row) },
{ id: "delete", label: i18nMessage("i18n:govoplan-access.delete_value.4d18989e", { value0: row.function_id }), icon: <Trash2 />, variant: "danger", disabled: !canWrite, disabledReason: !canWrite ? ACCESS_INTERFACE_I18N.writePermissionRequired : undefined, onClick: () => setDeleting(row) }
]} />
}
],
@@ -276,8 +277,9 @@ export default function ExternalFunctionRoleMappingsPanel({
success={success}
actions={
<>
<Button onClick={() => void load()} disabled={loading}>i18n:govoplan-access.reload.cce71553</Button>
<AdminIconButton label="i18n:govoplan-access.add_function_role_mapping.1bc376ac" icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canWrite || !assignableRoles.length} />
<DocumentationHelpLink reference={FUNCTION_MAPPING_DOCUMENTATION} />
<Button onClick={() => void load()} disabled={loading} disabledReason={loading ? ACCESS_INTERFACE_I18N.loading : undefined}>i18n:govoplan-access.reload.cce71553</Button>
<AdminIconButton label="i18n:govoplan-access.add_function_role_mapping.1bc376ac" icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canWrite || !assignableRoles.length} disabledReason={!canWrite ? ACCESS_INTERFACE_I18N.createPermissionRequired : !assignableRoles.length ? ACCESS_INTERFACE_I18N.selectAssignableRole : undefined} />
</>
}
>
@@ -300,8 +302,8 @@ export default function ExternalFunctionRoleMappingsPanel({
className="admin-dialog"
footer={
<>
<Button onClick={closeEditor} disabled={busy}>i18n:govoplan-access.cancel.77dfd213</Button>
<Button variant="primary" onClick={() => void save()} disabled={!canWrite || busy || !draft.functionId.trim() || !draft.roleId}>
<Button onClick={closeEditor} disabled={busy} disabledReason={busy ? ACCESS_INTERFACE_I18N.operationInProgress : undefined}>i18n:govoplan-access.cancel.77dfd213</Button>
<Button variant="primary" onClick={() => void save()} disabledReason={saveDisabledReason({ busy, permitted: canWrite, complete: Boolean(draft.functionId.trim() && draft.roleId) })}>
{busy ? "i18n:govoplan-access.saving.56a2285c" : "i18n:govoplan-access.save_mapping.a4ac90e9"}
</Button>
</>
@@ -1,8 +1,12 @@
import { useEffect, useRef, useState } from "react";
import type { ApiSettings, FileConnectorScope, FileConnectorTargetOption, FilesConnectorsUiCapability } from "@govoplan/core-webui";
import { AdminPageLayout, Card, adminErrorMessage, useDeltaWatermarks, usePlatformUiCapability } from "@govoplan/core-webui";
import { ActionBlockerHint, AdminPageLayout, adminErrorMessage, useDeltaWatermarks, usePlatformUiCapability } from "@govoplan/core-webui";
import { fetchGroupsDelta, fetchUsersDelta, type GroupSummary, type UserAdminItem } from "../../api/admin";
import { loadDeltaRows } from "./utils/deltaRows";
import {
ACCESS_INTERFACE_I18N,
FILE_CONNECTOR_DOCUMENTATION
} from "./interfacePatterns";
type Props = {
settings: ApiSettings;
@@ -82,9 +86,21 @@ export default function FileConnectorsPanel({ settings, scopeType, canWrite }: P
if (!FileConnectorScopeManager) {
return (
<AdminPageLayout title={labels.title} description={labels.description}>
<Card title="i18n:govoplan-access.files_module_unavailable.0ee90db1">
<p className="muted">i18n:govoplan-access.install_and_enable_the_files_module_to_manage_fi.f842c153</p>
</Card>
<ActionBlockerHint
reason={{
summary: "i18n:govoplan-access.files_module_unavailable.0ee90db1",
details: "i18n:govoplan-access.install_and_enable_the_files_module_to_manage_fi.f842c153",
requiredAction: ACCESS_INTERFACE_I18N.installFiles,
actor: ACCESS_INTERFACE_I18N.systemModuleAdministrator,
target: ACCESS_INTERFACE_I18N.moduleManagement
}}
labels={{
requiredAction: ACCESS_INTERFACE_I18N.requiredAction,
actor: ACCESS_INTERFACE_I18N.actor,
target: ACCESS_INTERFACE_I18N.destinationLabel
}}
documentation={FILE_CONNECTOR_DOCUMENTATION}
/>
</AdminPageLayout>);
}
+10 -5
View File
@@ -8,8 +8,13 @@ import { Dialog } from "@govoplan/core-webui";
import { FormField } from "@govoplan/core-webui";
import { StatusBadge } from "@govoplan/core-webui";
import { ConfirmDialog } from "@govoplan/core-webui";
import { AdminIconButton, AdminPageLayout, AdminSelectionList, TableActionGroup, adminErrorMessage, formatAdminDateTime as formatDateTime, joinLabels, i18nMessage, useDeltaWatermarks, useUnsavedDraftGuard } from "@govoplan/core-webui";
import { AdminIconButton, AdminPageLayout, AdminSelectionList, DocumentationHelpLink, TableActionGroup, adminErrorMessage, formatAdminDateTime as formatDateTime, joinLabels, i18nMessage, useDeltaWatermarks, useUnsavedDraftGuard } from "@govoplan/core-webui";
import { loadDeltaRows } from "./utils/deltaRows";
import {
ACCESS_INTERFACE_I18N,
ACCESS_WORKFLOW_DOCUMENTATION,
saveDisabledReason
} from "./interfacePatterns";
const emptyDraft = { slug: "", name: "", description: "", isActive: true, memberIds: [] as string[], roleIds: [] as string[] };
@@ -133,18 +138,18 @@ export default function GroupsPanel({ settings, auth, canDefine, canManageMember
{ id: "status", header: "i18n:govoplan-access.status.bae7d5be", width: 120, resizable: false, sortable: true, filterable: true, value: (row) => row.is_active ? "active" : "inactive", render: (row) => <StatusBadge status={row.is_active ? "active" : "inactive"} /> },
{ id: "actions", header: "i18n:govoplan-access.actions.c3cd636a", width: 150, sticky: "end", resizable: false, align: "right", render: (row) => <TableActionGroup actions={[
{ id: "inspect", label: i18nMessage("i18n:govoplan-access.inspect_value.9d5d1071", { value0: row.name }), icon: <Search />, onClick: () => setViewing(row) },
{ id: "edit", label: i18nMessage("i18n:govoplan-access.edit_value.fad75899", { value0: row.name }), icon: <Pencil />, disabled: !(canDefine || canManageMembers || canAssignRoles), onClick: () => openEdit(row) },
{ id: "deactivate", label: i18nMessage("i18n:govoplan-access.deactivate_value.a276a667", { value0: row.name }), icon: <Trash2 />, variant: "danger", applicable: row.is_active, disabled: !canDefine || Boolean(row.system_required), onClick: () => setDeactivating(row) }
{ id: "edit", label: i18nMessage("i18n:govoplan-access.edit_value.fad75899", { value0: row.name }), icon: <Pencil />, disabled: !(canDefine || canManageMembers || canAssignRoles), disabledReason: !(canDefine || canManageMembers || canAssignRoles) ? ACCESS_INTERFACE_I18N.updatePermissionRequired : undefined, onClick: () => openEdit(row) },
{ id: "deactivate", label: i18nMessage("i18n:govoplan-access.deactivate_value.a276a667", { value0: row.name }), icon: <Trash2 />, variant: "danger", applicable: row.is_active, disabled: !canDefine || Boolean(row.system_required), disabledReason: !row.is_active ? "i18n:govoplan-access.inactive.09af574c" : !canDefine ? ACCESS_INTERFACE_I18N.writePermissionRequired : row.system_required ? ACCESS_INTERFACE_I18N.systemManagedObject : undefined, onClick: () => setDeactivating(row) }
]} /> }],
[canAssignRoles, canDefine, canManageMembers]);
return (
<>
<AdminPageLayout title="i18n:govoplan-access.tenant_groups.47e6cc05" description="i18n:govoplan-access.groups_provide_shared_file_spaces_and_inherited_.27f05309" loading={loading} error={error} success={success} actions={<><Button onClick={() => void load()} disabled={loading}>i18n:govoplan-access.reload.cce71553</Button><AdminIconButton label="i18n:govoplan-access.add_group.2fca464f" icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canDefine} /></>}>
<AdminPageLayout title="i18n:govoplan-access.tenant_groups.47e6cc05" description="i18n:govoplan-access.groups_provide_shared_file_spaces_and_inherited_.27f05309" loading={loading} error={error} success={success} actions={<><DocumentationHelpLink reference={ACCESS_WORKFLOW_DOCUMENTATION} /><Button onClick={() => void load()} disabled={loading} disabledReason={loading ? ACCESS_INTERFACE_I18N.loading : undefined}>i18n:govoplan-access.reload.cce71553</Button><AdminIconButton label="i18n:govoplan-access.add_group.2fca464f" icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canDefine} disabledReason={!canDefine ? ACCESS_INTERFACE_I18N.createPermissionRequired : undefined} /></>}>
<div className="admin-table-surface"><DataGrid id="admin-groups-v3" rows={groups} columns={columns} initialFit="container" getRowKey={(row) => row.id} emptyText="i18n:govoplan-access.no_groups_found.627ca913" /></div>
</AdminPageLayout>
<Dialog open={editing !== null} title={editing === "new" ? "i18n:govoplan-access.create_group.5a0b1c17" : "i18n:govoplan-access.edit_group.edb57d8e"} onClose={() => !busy && setEditing(null)} className="admin-dialog admin-dialog-wide" footer={<><Button onClick={() => setEditing(null)} disabled={busy}>i18n:govoplan-access.cancel.77dfd213</Button><Button variant="primary" onClick={() => void save()} disabled={busy || !draft.name.trim() || !draft.slug.trim() || (editing === "new" ? !canDefine : !(canDefine || canManageMembers || canAssignRoles))}>{busy ? "i18n:govoplan-access.saving.56a2285c" : "i18n:govoplan-access.save_group.36ca6865"}</Button></>}>
<Dialog open={editing !== null} title={editing === "new" ? "i18n:govoplan-access.create_group.5a0b1c17" : "i18n:govoplan-access.edit_group.edb57d8e"} onClose={() => !busy && setEditing(null)} className="admin-dialog admin-dialog-wide" footer={<><Button onClick={() => setEditing(null)} disabled={busy} disabledReason={busy ? ACCESS_INTERFACE_I18N.operationInProgress : undefined}>i18n:govoplan-access.cancel.77dfd213</Button><Button variant="primary" onClick={() => void save()} disabledReason={saveDisabledReason({ busy, permitted: editing === "new" ? canDefine : canDefine || canManageMembers || canAssignRoles, complete: Boolean(draft.name.trim() && draft.slug.trim()) })}>{busy ? "i18n:govoplan-access.saving.56a2285c" : "i18n:govoplan-access.save_group.36ca6865"}</Button></>}>
{editing !== "new" && editing?.system_template_id && <p className="admin-managed-notice">i18n:govoplan-access.this_group_definition_is_managed_by_the_system_n.640b235e</p>}
<div className="admin-form-grid two-columns">
<FormField label="i18n:govoplan-access.name.709a2322"><input value={draft.name} disabled={!canDefine || editing !== "new" && Boolean(editing?.system_template_id)} onChange={(event) => setDraft({ ...draft, name: event.target.value })} /></FormField>
+20 -5
View File
@@ -1,10 +1,13 @@
import { useEffect, useRef, useState } from "react";
import type { ApiSettings, MailProfileScope, MailProfilesUiCapability, MailProfileTargetOption } from "@govoplan/core-webui";
import { fetchGroupsDelta, fetchUsersDelta, type GroupSummary, type UserAdminItem } from "../../api/admin";
import { Card } from "@govoplan/core-webui";
import { AdminPageLayout, adminErrorMessage, useDeltaWatermarks } from "@govoplan/core-webui";
import { ActionBlockerHint, AdminPageLayout, adminErrorMessage, useDeltaWatermarks } from "@govoplan/core-webui";
import { usePlatformUiCapability } from "@govoplan/core-webui";
import { loadDeltaRows } from "./utils/deltaRows";
import {
ACCESS_INTERFACE_I18N,
MAIL_PROFILE_DOCUMENTATION
} from "./interfacePatterns";
type Props = {
settings: ApiSettings;
@@ -106,9 +109,21 @@ export default function MailProfilesPanel({ settings, scopeType, canWriteProfile
if (!MailProfileScopeManager) {
return (
<AdminPageLayout title={labels.title} description={labels.description}>
<Card title="i18n:govoplan-access.mail_module_unavailable.b4e95104">
<p className="muted">i18n:govoplan-access.install_and_enable_the_mail_module_to_manage_mai.a8ad5b3a</p>
</Card>
<ActionBlockerHint
reason={{
summary: "i18n:govoplan-access.mail_module_unavailable.b4e95104",
details: "i18n:govoplan-access.install_and_enable_the_mail_module_to_manage_mai.a8ad5b3a",
requiredAction: ACCESS_INTERFACE_I18N.installMail,
actor: ACCESS_INTERFACE_I18N.systemModuleAdministrator,
target: ACCESS_INTERFACE_I18N.moduleManagement
}}
labels={{
requiredAction: ACCESS_INTERFACE_I18N.requiredAction,
actor: ACCESS_INTERFACE_I18N.actor,
target: ACCESS_INTERFACE_I18N.destinationLabel
}}
documentation={MAIL_PROFILE_DOCUMENTATION}
/>
</AdminPageLayout>);
}
+6 -5
View File
@@ -8,9 +8,10 @@ import { Dialog } from "@govoplan/core-webui";
import { FormField } from "@govoplan/core-webui";
import { StatusBadge } from "@govoplan/core-webui";
import { ConfirmDialog } from "@govoplan/core-webui";
import { AdminIconButton, AdminPageLayout, AdminSelectionList, TableActionGroup, adminErrorMessage } from "@govoplan/core-webui";
import { AdminIconButton, AdminPageLayout, AdminSelectionList, DocumentationHelpLink, TableActionGroup, adminErrorMessage } from "@govoplan/core-webui";
import { hasTenantWildcard, i18nMessage, useDeltaWatermarks, useUnsavedDraftGuard } from "@govoplan/core-webui";
import { loadDeltaRows } from "./utils/deltaRows";
import { ACCESS_INTERFACE_I18N, ACCESS_WORKFLOW_DOCUMENTATION, saveDisabledReason } from "./interfacePatterns";
const emptyDraft = { slug: "", name: "", description: "", permissions: [] as string[], isAssignable: true };
@@ -126,18 +127,18 @@ export default function RolesPanel({ settings, auth, canDefine, onAuthRefresh }:
{ id: "type", header: "i18n:govoplan-access.type.3deb7456", width: 140, resizable: false, sortable: true, filterable: true, value: (row) => row.is_builtin ? "built-in" : row.system_template_id ? "system-managed" : "custom", render: (row) => <StatusBadge status={row.is_builtin ? "built" : "active"} label={row.is_builtin ? "i18n:govoplan-access.built_in.20f409cc" : row.system_template_id ? "i18n:govoplan-access.system.bc0792d8" : "i18n:govoplan-access.custom.081ae3fd"} /> },
{ id: "actions", header: "i18n:govoplan-access.actions.c3cd636a", width: 150, sticky: "end", resizable: false, align: "right", render: (row) => <TableActionGroup actions={[
{ id: "inspect", label: i18nMessage("i18n:govoplan-access.inspect_value.9d5d1071", { value0: row.name }), icon: <Search />, onClick: () => setViewing(row) },
{ id: "edit", label: i18nMessage("i18n:govoplan-access.edit_value.fad75899", { value0: row.name }), icon: <Pencil />, applicable: !row.is_builtin && !row.system_template_id, disabled: !canDefine, onClick: () => openEdit(row) },
{ id: "delete", label: i18nMessage("i18n:govoplan-access.delete_value.4d18989e", { value0: row.name }), icon: <Trash2 />, variant: "danger", applicable: !row.is_builtin && !row.system_template_id, disabled: !canDefine || row.user_assignments + row.group_assignments > 0, onClick: () => setDeleting(row) }
{ id: "edit", label: i18nMessage("i18n:govoplan-access.edit_value.fad75899", { value0: row.name }), icon: <Pencil />, applicable: !row.is_builtin && !row.system_template_id, disabled: !canDefine, disabledReason: row.is_builtin || row.system_template_id ? ACCESS_INTERFACE_I18N.systemManagedObject : !canDefine ? ACCESS_INTERFACE_I18N.writePermissionRequired : undefined, onClick: () => openEdit(row) },
{ id: "delete", label: i18nMessage("i18n:govoplan-access.delete_value.4d18989e", { value0: row.name }), icon: <Trash2 />, variant: "danger", applicable: !row.is_builtin && !row.system_template_id, disabled: !canDefine || row.user_assignments + row.group_assignments > 0, disabledReason: row.is_builtin || row.system_template_id ? ACCESS_INTERFACE_I18N.systemManagedObject : !canDefine ? ACCESS_INTERFACE_I18N.writePermissionRequired : row.user_assignments + row.group_assignments > 0 ? ACCESS_INTERFACE_I18N.assignedObjectCannotBeDeleted : undefined, onClick: () => setDeleting(row) }
]} /> }],
[canDefine, permissions]);
return (
<>
<AdminPageLayout title="i18n:govoplan-access.tenant_roles.51aca82d" description="i18n:govoplan-access.roles_are_explicit_tenant_permission_bundles_bui.ce55fcaa" loading={loading} error={error} success={success} actions={<><Button onClick={() => void load()} disabled={loading}>i18n:govoplan-access.reload.cce71553</Button><AdminIconButton label="i18n:govoplan-access.add_role.d8d5d55c" icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canDefine} /></>}>
<AdminPageLayout title="i18n:govoplan-access.tenant_roles.51aca82d" description="i18n:govoplan-access.roles_are_explicit_tenant_permission_bundles_bui.ce55fcaa" loading={loading} error={error} success={success} actions={<><DocumentationHelpLink reference={ACCESS_WORKFLOW_DOCUMENTATION} /><Button onClick={() => void load()} disabled={loading} disabledReason={loading ? ACCESS_INTERFACE_I18N.loading : undefined}>i18n:govoplan-access.reload.cce71553</Button><AdminIconButton label="i18n:govoplan-access.add_role.d8d5d55c" icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canDefine} disabledReason={!canDefine ? ACCESS_INTERFACE_I18N.createPermissionRequired : undefined} /></>}>
<div className="admin-table-surface"><DataGrid id="admin-roles-v3" rows={roles} columns={columns} initialFit="container" getRowKey={(row) => row.id} emptyText="i18n:govoplan-access.no_roles_found.70f7c0c9" /></div>
</AdminPageLayout>
<Dialog open={editing !== null} title={editing === "new" ? "i18n:govoplan-access.create_role.db859bad" : "i18n:govoplan-access.edit_role.61dd63e9"} onClose={() => !busy && setEditing(null)} className="admin-dialog admin-dialog-wide" footer={<><Button onClick={() => setEditing(null)} disabled={busy}>i18n:govoplan-access.cancel.77dfd213</Button><Button variant="primary" onClick={() => void save()} disabled={!canDefine || busy || !draft.name.trim() || !draft.slug.trim()}>{busy ? "i18n:govoplan-access.saving.56a2285c" : "i18n:govoplan-access.save_role.16fe10d1"}</Button></>}>
<Dialog open={editing !== null} title={editing === "new" ? "i18n:govoplan-access.create_role.db859bad" : "i18n:govoplan-access.edit_role.61dd63e9"} onClose={() => !busy && setEditing(null)} className="admin-dialog admin-dialog-wide" footer={<><Button onClick={() => setEditing(null)} disabled={busy} disabledReason={busy ? ACCESS_INTERFACE_I18N.operationInProgress : undefined}>i18n:govoplan-access.cancel.77dfd213</Button><Button variant="primary" onClick={() => void save()} disabledReason={saveDisabledReason({ busy, permitted: canDefine, complete: Boolean(draft.name.trim() && draft.slug.trim()) })}>{busy ? "i18n:govoplan-access.saving.56a2285c" : "i18n:govoplan-access.save_role.16fe10d1"}</Button></>}>
<div className="admin-form-grid two-columns">
<FormField label="i18n:govoplan-access.name.709a2322"><input value={draft.name} onChange={(event) => setDraft({ ...draft, name: event.target.value })} /></FormField>
<FormField label="i18n:govoplan-access.slug.094da9b9"><input value={draft.slug} disabled={editing !== "new"} onChange={(event) => setDraft({ ...draft, slug: event.target.value })} /></FormField>
@@ -16,8 +16,9 @@ import { DataGrid, type DataGridColumn } from "@govoplan/core-webui";
import { Dialog } from "@govoplan/core-webui";
import { FormField } from "@govoplan/core-webui";
import { StatusBadge } from "@govoplan/core-webui";
import { AdminIconButton, AdminPageLayout, AdminSelectionList, TableActionGroup, adminErrorMessage, joinLabels, i18nMessage, useDeltaWatermarks, useUnsavedDraftGuard } from "@govoplan/core-webui";
import { AdminIconButton, AdminPageLayout, AdminSelectionList, DocumentationHelpLink, TableActionGroup, adminErrorMessage, joinLabels, i18nMessage, useDeltaWatermarks, useUnsavedDraftGuard } from "@govoplan/core-webui";
import { loadDeltaRows } from "./utils/deltaRows";
import { ACCESS_INTERFACE_I18N, ACCESS_REFERENCE_DOCUMENTATION, saveDisabledReason } from "./interfacePatterns";
const emptyDraft = {
slug: "",
@@ -225,8 +226,8 @@ export default function SystemRolesPanel({
const protectedOwner = row.slug === "system_owner";
return <TableActionGroup actions={[
{ id: "inspect", label: i18nMessage("i18n:govoplan-access.inspect_value.9d5d1071", { value0: row.name }), icon: <Search />, onClick: () => setViewing(row) },
{ id: "edit", label: i18nMessage("i18n:govoplan-access.edit_value.fad75899", { value0: row.name }), icon: <Pencil />, applicable: !protectedOwner, disabled: !canWrite, onClick: () => openEdit(row) },
{ id: "delete", label: i18nMessage("i18n:govoplan-access.delete_value.4d18989e", { value0: row.name }), icon: <Trash2 />, variant: "danger", applicable: !protectedOwner, disabled: !canWrite || row.user_assignments > 0, onClick: () => setDeleting(row) }
{ id: "edit", label: i18nMessage("i18n:govoplan-access.edit_value.fad75899", { value0: row.name }), icon: <Pencil />, applicable: !protectedOwner, disabled: !canWrite, disabledReason: protectedOwner ? ACCESS_INTERFACE_I18N.systemManagedObject : !canWrite ? ACCESS_INTERFACE_I18N.writePermissionRequired : undefined, onClick: () => openEdit(row) },
{ id: "delete", label: i18nMessage("i18n:govoplan-access.delete_value.4d18989e", { value0: row.name }), icon: <Trash2 />, variant: "danger", applicable: !protectedOwner, disabled: !canWrite || row.user_assignments > 0, disabledReason: protectedOwner ? ACCESS_INTERFACE_I18N.systemManagedObject : !canWrite ? ACCESS_INTERFACE_I18N.writePermissionRequired : row.user_assignments > 0 ? ACCESS_INTERFACE_I18N.assignedObjectCannotBeDeleted : undefined, onClick: () => setDeleting(row) }
]} />;
}
}],
@@ -240,7 +241,7 @@ export default function SystemRolesPanel({
loading={loading}
error={error}
success={success}
actions={<><Button onClick={() => void load()} disabled={loading}>i18n:govoplan-access.reload.cce71553</Button><AdminIconButton label="i18n:govoplan-access.add_system_role.f9ef262b" icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canWrite} /></>}>
actions={<><DocumentationHelpLink reference={ACCESS_REFERENCE_DOCUMENTATION} /><Button onClick={() => void load()} disabled={loading} disabledReason={loading ? ACCESS_INTERFACE_I18N.loading : undefined}>i18n:govoplan-access.reload.cce71553</Button><AdminIconButton label="i18n:govoplan-access.add_system_role.f9ef262b" icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canWrite} disabledReason={!canWrite ? ACCESS_INTERFACE_I18N.createPermissionRequired : undefined} /></>}>
<div className="admin-table-surface">
<DataGrid id="admin-system-role-definitions-v4" rows={roles} columns={columns} initialFit="container" getRowKey={(row) => row.id} emptyText="i18n:govoplan-access.no_system_roles_found.051cf727" />
@@ -252,7 +253,7 @@ export default function SystemRolesPanel({
title={editing === "new" ? "i18n:govoplan-access.create_system_role.a1e40b25" : "i18n:govoplan-access.edit_system_role.6ebb7cb0"}
onClose={() => !busy && setEditing(null)}
className="admin-dialog admin-dialog-wide"
footer={<><Button onClick={() => setEditing(null)} disabled={busy}>i18n:govoplan-access.cancel.77dfd213</Button><Button variant="primary" onClick={() => void save()} disabled={busy || !draft.name.trim() || !draft.slug.trim()}>{busy ? "i18n:govoplan-access.saving.56a2285c" : "i18n:govoplan-access.save_role.16fe10d1"}</Button></>}>
footer={<><Button onClick={() => setEditing(null)} disabled={busy} disabledReason={busy ? ACCESS_INTERFACE_I18N.operationInProgress : undefined}>i18n:govoplan-access.cancel.77dfd213</Button><Button variant="primary" onClick={() => void save()} disabledReason={saveDisabledReason({ busy, permitted: canWrite, complete: Boolean(draft.name.trim() && draft.slug.trim()) })}>{busy ? "i18n:govoplan-access.saving.56a2285c" : "i18n:govoplan-access.save_role.16fe10d1"}</Button></>}>
<div className="admin-form-grid two-columns">
<FormField label="i18n:govoplan-access.name.709a2322"><input value={draft.name} onChange={(event) => setDraft({ ...draft, name: event.target.value })} /></FormField>
@@ -20,7 +20,8 @@ import {
type SystemMembershipDraft,
type TenantAdminItem
} from "../../api/admin";
import { AdminIconButton, AdminPageLayout, AdminSelectionList, TableActionGroup, adminErrorMessage, formatAdminDateTime as formatDateTime, joinLabels, i18nMessage, mergeDeltaRows, useDeltaWatermarks, useUnsavedDraftGuard } from "@govoplan/core-webui";
import { AdminIconButton, AdminPageLayout, AdminSelectionList, DocumentationHelpLink, TableActionGroup, adminErrorMessage, formatAdminDateTime as formatDateTime, joinLabels, i18nMessage, mergeDeltaRows, useDeltaWatermarks, useUnsavedDraftGuard } from "@govoplan/core-webui";
import { ACCESS_INTERFACE_I18N, ACCESS_REFERENCE_DOCUMENTATION, saveDisabledReason } from "./interfacePatterns";
const emptyDraft = {
email: "",
@@ -214,8 +215,8 @@ export default function SystemUsersPanel({
{ id: "last_login", header: "i18n:govoplan-access.last_login.43dab84f", width: 180, minWidth: 150, resizable: true, sortable: true, value: (row) => row.last_login_at || "", render: (row) => formatDateTime(row.last_login_at) },
{ id: "actions", header: "i18n:govoplan-access.actions.c3cd636a", width: 150, sticky: "end", resizable: false, align: "right", render: (row) => <TableActionGroup actions={[
{ id: "inspect", label: i18nMessage("i18n:govoplan-access.inspect_value.9d5d1071", { value0: row.email }), icon: <Search />, onClick: () => setViewing(row) },
{ id: "edit", label: i18nMessage("i18n:govoplan-access.edit_value.fad75899", { value0: row.email }), icon: <Pencil />, disabled: !(canUpdate || canSuspend || canAssignRoles || canManageMemberships), onClick: () => openEdit(row) },
{ id: "deactivate", label: i18nMessage("i18n:govoplan-access.deactivate_value.a276a667", { value0: row.email }), icon: <Trash2 />, variant: "danger", applicable: row.is_active, disabled: !canSuspend || row.memberships.some((membership) => membership.is_last_active_owner), onClick: () => setDeactivating(row) }
{ id: "edit", label: i18nMessage("i18n:govoplan-access.edit_value.fad75899", { value0: row.email }), icon: <Pencil />, disabled: !(canUpdate || canSuspend || canAssignRoles || canManageMemberships), disabledReason: !(canUpdate || canSuspend || canAssignRoles || canManageMemberships) ? ACCESS_INTERFACE_I18N.updatePermissionRequired : undefined, onClick: () => openEdit(row) },
{ id: "deactivate", label: i18nMessage("i18n:govoplan-access.deactivate_value.a276a667", { value0: row.email }), icon: <Trash2 />, variant: "danger", applicable: row.is_active, disabled: !canSuspend || row.memberships.some((membership) => membership.is_last_active_owner), disabledReason: !row.is_active ? "i18n:govoplan-access.inactive.09af574c" : !canSuspend ? ACCESS_INTERFACE_I18N.updatePermissionRequired : row.memberships.some((membership) => membership.is_last_active_owner) ? ACCESS_INTERFACE_I18N.lastOwnerCannotBeDeactivated : undefined, onClick: () => setDeactivating(row) }
]} /> }],
[canAssignRoles, canManageMemberships, canSuspend, canUpdate]);
@@ -227,12 +228,12 @@ export default function SystemUsersPanel({
loading={loading}
error={error}
success={success}
actions={<><Button onClick={() => void load()} disabled={loading}>i18n:govoplan-access.reload.cce71553</Button><AdminIconButton label="i18n:govoplan-access.add_global_account.18e4df22" icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canCreate} /></>}>
actions={<><DocumentationHelpLink reference={ACCESS_REFERENCE_DOCUMENTATION} /><Button onClick={() => void load()} disabled={loading} disabledReason={loading ? ACCESS_INTERFACE_I18N.loading : undefined}>i18n:govoplan-access.reload.cce71553</Button><AdminIconButton label="i18n:govoplan-access.add_global_account.18e4df22" icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canCreate} disabledReason={!canCreate ? ACCESS_INTERFACE_I18N.createPermissionRequired : undefined} /></>}>
<div className="admin-table-surface"><DataGrid id="admin-system-users-v3" rows={accounts} columns={columns} initialFit="container" getRowKey={(row) => row.account_id} emptyText="i18n:govoplan-access.no_global_accounts_found.29d96a9e" /></div>
</AdminPageLayout>
<Dialog open={editing !== null} title={editing === "new" ? "i18n:govoplan-access.create_global_account.e821f016" : "i18n:govoplan-access.edit_global_account.d13b8485"} onClose={() => !busy && setEditing(null)} className="admin-dialog admin-dialog-wide" footer={<><Button onClick={() => setEditing(null)} disabled={busy}>i18n:govoplan-access.cancel.77dfd213</Button><Button variant="primary" onClick={() => void save()} disabled={busy || !draft.email.trim() || (editing === "new" ? !canCreate : !(canUpdate || canSuspend || canAssignRoles || canManageMemberships))}>{busy ? "i18n:govoplan-access.saving.56a2285c" : "i18n:govoplan-access.save_account.0b761f5c"}</Button></>}>
<Dialog open={editing !== null} title={editing === "new" ? "i18n:govoplan-access.create_global_account.e821f016" : "i18n:govoplan-access.edit_global_account.d13b8485"} onClose={() => !busy && setEditing(null)} className="admin-dialog admin-dialog-wide" footer={<><Button onClick={() => setEditing(null)} disabled={busy} disabledReason={busy ? ACCESS_INTERFACE_I18N.operationInProgress : undefined}>i18n:govoplan-access.cancel.77dfd213</Button><Button variant="primary" onClick={() => void save()} disabledReason={saveDisabledReason({ busy, permitted: editing === "new" ? canCreate : canUpdate || canSuspend || canAssignRoles || canManageMemberships, complete: Boolean(draft.email.trim()) })}>{busy ? "i18n:govoplan-access.saving.56a2285c" : "i18n:govoplan-access.save_account.0b761f5c"}</Button></>}>
<div className="admin-form-grid two-columns">
<FormField label="i18n:govoplan-access.email.84add5b2"><input value={draft.email} disabled={editing !== "new"} onChange={(event) => setDraft({ ...draft, email: event.target.value })} /></FormField>
<FormField label="i18n:govoplan-access.display_name.c7874aaa"><input value={draft.displayName} disabled={editing !== "new" && !canUpdate} onChange={(event) => setDraft({ ...draft, displayName: event.target.value })} /></FormField>
+11 -6
View File
@@ -10,9 +10,14 @@ import { PasswordField } from "@govoplan/core-webui";
import { StatusBadge } from "@govoplan/core-webui";
import { ToggleSwitch } from "@govoplan/core-webui";
import { ConfirmDialog } from "@govoplan/core-webui";
import { AdminIconButton, AdminPageLayout, AdminSelectionList, TableActionGroup, adminErrorMessage, formatAdminDateTime as formatDateTime, joinLabels } from "@govoplan/core-webui";
import { AdminIconButton, AdminPageLayout, AdminSelectionList, DocumentationHelpLink, TableActionGroup, adminErrorMessage, formatAdminDateTime as formatDateTime, joinLabels } from "@govoplan/core-webui";
import { hasTenantWildcard, i18nMessage, useDeltaWatermarks, useUnsavedDraftGuard } from "@govoplan/core-webui";
import { loadDeltaRows } from "./utils/deltaRows";
import {
ACCESS_INTERFACE_I18N,
ACCESS_WORKFLOW_DOCUMENTATION,
saveDisabledReason
} from "./interfacePatterns";
const emptyDraft = {
email: "",
@@ -190,18 +195,18 @@ export default function UsersPanel({ settings, auth, canCreate, canUpdate, canSu
{ id: "actions", header: "i18n:govoplan-access.actions.c3cd636a", width: 190, sticky: "end", resizable: false, align: "right", render: (row) => <TableActionGroup actions={[
{ id: "inspect", label: i18nMessage("i18n:govoplan-access.inspect_value.9d5d1071", { value0: row.email }), icon: <Search />, onClick: () => setViewing(row) },
{ id: "explain", label: i18nMessage("i18n:govoplan-access.explain_access_for_value.3af96e47", { value0: row.email }), icon: <KeyRound />, onClick: () => void openAccessExplanation(row) },
{ id: "edit", label: i18nMessage("i18n:govoplan-access.edit_value.fad75899", { value0: row.email }), icon: <Pencil />, disabled: !(canUpdate || canSuspend || canManageGroups || canAssignRoles), onClick: () => openEdit(row) },
{ id: "deactivate", label: i18nMessage("i18n:govoplan-access.deactivate_value.a276a667", { value0: row.email }), icon: <Trash2 />, variant: "danger", applicable: row.is_active, disabled: !canSuspend || row.is_last_active_owner, onClick: () => setDeactivating(row) }
{ id: "edit", label: i18nMessage("i18n:govoplan-access.edit_value.fad75899", { value0: row.email }), icon: <Pencil />, disabled: !(canUpdate || canSuspend || canManageGroups || canAssignRoles), disabledReason: !(canUpdate || canSuspend || canManageGroups || canAssignRoles) ? ACCESS_INTERFACE_I18N.updatePermissionRequired : undefined, onClick: () => openEdit(row) },
{ id: "deactivate", label: i18nMessage("i18n:govoplan-access.deactivate_value.a276a667", { value0: row.email }), icon: <Trash2 />, variant: "danger", applicable: row.is_active, disabled: !canSuspend || row.is_last_active_owner, disabledReason: !row.is_active ? "i18n:govoplan-access.inactive.09af574c" : !canSuspend ? ACCESS_INTERFACE_I18N.updatePermissionRequired : row.is_last_active_owner ? ACCESS_INTERFACE_I18N.lastOwnerCannotBeDeactivated : undefined, onClick: () => setDeactivating(row) }
]} /> }],
[canAssignRoles, canManageGroups, canSuspend, canUpdate, settings]);
return (
<>
<AdminPageLayout title="i18n:govoplan-access.tenant_users.cb800b38" description="i18n:govoplan-access.manage_memberships_groups_and_direct_roles_in_th.25af86bb" loading={loading} error={error} success={success} actions={<><Button onClick={() => void load()} disabled={loading}>i18n:govoplan-access.reload.cce71553</Button><AdminIconButton label="i18n:govoplan-access.add_tenant_user.36f37ce7" icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canCreate} /></>}>
<AdminPageLayout title="i18n:govoplan-access.tenant_users.cb800b38" description="i18n:govoplan-access.manage_memberships_groups_and_direct_roles_in_th.25af86bb" loading={loading} error={error} success={success} actions={<><DocumentationHelpLink reference={ACCESS_WORKFLOW_DOCUMENTATION} /><Button onClick={() => void load()} disabled={loading} disabledReason={loading ? ACCESS_INTERFACE_I18N.loading : undefined}>i18n:govoplan-access.reload.cce71553</Button><AdminIconButton label="i18n:govoplan-access.add_tenant_user.36f37ce7" icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canCreate} disabledReason={!canCreate ? ACCESS_INTERFACE_I18N.createPermissionRequired : undefined} /></>}>
<div className="admin-table-surface"><DataGrid id="admin-users-v3" rows={users} columns={columns} initialFit="container" getRowKey={(row) => row.id} emptyText="i18n:govoplan-access.no_tenant_users_found.74bb615f" /></div>
</AdminPageLayout>
<Dialog open={editing !== null} title={editing === "new" ? "i18n:govoplan-access.add_tenant_user.36f37ce7" : "i18n:govoplan-access.edit_tenant_user.99121a61"} onClose={() => !busy && setEditing(null)} className="admin-dialog admin-dialog-wide" footer={<><Button onClick={() => setEditing(null)} disabled={busy}>i18n:govoplan-access.cancel.77dfd213</Button><Button variant="primary" onClick={() => void save()} disabled={busy || !draft.email.trim() || (editing === "new" ? !canCreate : !(canUpdate || canSuspend || canManageGroups || canAssignRoles))}>{busy ? "i18n:govoplan-access.saving.56a2285c" : "i18n:govoplan-access.save_user.0d071b89"}</Button></>}>
<Dialog open={editing !== null} title={editing === "new" ? "i18n:govoplan-access.add_tenant_user.36f37ce7" : "i18n:govoplan-access.edit_tenant_user.99121a61"} onClose={() => !busy && setEditing(null)} className="admin-dialog admin-dialog-wide" footer={<><Button onClick={() => setEditing(null)} disabled={busy} disabledReason={busy ? ACCESS_INTERFACE_I18N.operationInProgress : undefined}>i18n:govoplan-access.cancel.77dfd213</Button><Button variant="primary" onClick={() => void save()} disabledReason={saveDisabledReason({ busy, permitted: editing === "new" ? canCreate : canUpdate || canSuspend || canManageGroups || canAssignRoles, complete: Boolean(draft.email.trim()) })}>{busy ? "i18n:govoplan-access.saving.56a2285c" : "i18n:govoplan-access.save_user.0d071b89"}</Button></>}>
<div className="admin-form-grid two-columns">
<FormField label="i18n:govoplan-access.email.84add5b2"><input value={draft.email} disabled={editing !== "new"} onChange={(event) => setDraft({ ...draft, email: event.target.value })} /></FormField>
<FormField label="i18n:govoplan-access.display_name.c7874aaa"><input value={draft.displayName} disabled={editing !== "new" && !canUpdate} onChange={(event) => setDraft({ ...draft, displayName: event.target.value })} /></FormField>
@@ -235,7 +240,7 @@ export default function UsersPanel({ settings, auth, canCreate, canUpdate, canSu
</dl>}
</Dialog>
<Dialog open={Boolean(explaining)} title="i18n:govoplan-access.access_explanation.75ee7f62" onClose={() => { if (!accessExplanationLoading) { setExplaining(null); setAccessExplanation(null); } }} className="admin-dialog admin-dialog-wide" footer={<Button onClick={() => { setExplaining(null); setAccessExplanation(null); }} disabled={accessExplanationLoading}>i18n:govoplan-access.close.bbfa773e</Button>}>
<Dialog open={Boolean(explaining)} title="i18n:govoplan-access.access_explanation.75ee7f62" onClose={() => { if (!accessExplanationLoading) { setExplaining(null); setAccessExplanation(null); } }} className="admin-dialog admin-dialog-wide" footer={<Button onClick={() => { setExplaining(null); setAccessExplanation(null); }} disabled={accessExplanationLoading} disabledReason={accessExplanationLoading ? ACCESS_INTERFACE_I18N.loading : undefined}>i18n:govoplan-access.close.bbfa773e</Button>}>
{accessExplanationLoading && <p className="muted small-note">i18n:govoplan-access.loading_access_explanation.04a7c934</p>}
{accessExplanation && <>
<dl className="admin-details-grid">
@@ -0,0 +1,79 @@
import type { DocumentationHelpReference } from "@govoplan/core-webui";
export const ACCESS_WORKFLOW_DOCUMENTATION = {
topicId: "access.workflow.grant-user-access",
documentationType: "admin"
} satisfies DocumentationHelpReference;
export const ACCESS_REFERENCE_DOCUMENTATION = {
topicId: "access.reference.admin-access-fields",
documentationType: "admin"
} satisfies DocumentationHelpReference;
export const FUNCTION_MAPPING_DOCUMENTATION = {
topicId: "access.reference.external-function-role-mappings",
documentationType: "admin"
} satisfies DocumentationHelpReference;
export const CREDENTIAL_DOCUMENTATION = {
contextId: "access.credentials",
documentationType: "admin"
} satisfies DocumentationHelpReference;
export const FILE_CONNECTOR_DOCUMENTATION = {
topicId: "files.governed-connectors-and-provenance",
documentationType: "admin"
} satisfies DocumentationHelpReference;
export const MAIL_PROFILE_DOCUMENTATION = {
topicId: "mail.profiles-and-policy",
documentationType: "admin"
} satisfies DocumentationHelpReference;
export const ACCESS_INTERFACE_I18N = {
loading: "i18n:govoplan-access.administration_data_is_loading.4af2c001",
createPermissionRequired: "i18n:govoplan-access.create_permission_is_required.4af2c002",
updatePermissionRequired: "i18n:govoplan-access.update_or_assignment_permission_is_required.4af2c003",
writePermissionRequired: "i18n:govoplan-access.write_permission_is_required.4af2c004",
completeRequiredFields: "i18n:govoplan-access.complete_the_required_fields_before_saving.4af2c005",
selectUserAndScopes: "i18n:govoplan-access.select_an_account_and_at_least_one_scope.4af2c006",
selectAssignableRole: "i18n:govoplan-access.select_an_assignable_role_before_creating_a_mapping.4af2c007",
assignedObjectCannotBeDeleted: "i18n:govoplan-access.remove_existing_assignments_before_deleting.4af2c008",
lastOwnerCannotBeDeactivated: "i18n:govoplan-access.assign_another_operational_owner_before_deactivating.4af2c009",
systemManagedObject: "i18n:govoplan-access.this_definition_is_managed_by_the_system.4af2c010",
operationInProgress: "i18n:govoplan-access.an_access_administration_operation_is_in_progress.4af2c011",
requiredAction: "i18n:govoplan-access.required_action.4af2c012",
actor: "i18n:govoplan-access.who_can_fix_it.4af2c013",
destinationLabel: "i18n:govoplan-access.where_to_go.4af2c014",
requestAdministrationAccess: "i18n:govoplan-access.request_an_administrative_role_for_the_required_scope.4af2c015",
accessAdministrator: "i18n:govoplan-access.a_tenant_owner_or_system_access_administrator.4af2c016",
accessAdministration: "i18n:govoplan-access.admin_users_groups_and_roles.4af2c017",
installFiles: "i18n:govoplan-access.install_and_enable_the_files_module.4af2c018",
installMail: "i18n:govoplan-access.install_and_enable_the_mail_module.4af2c019",
systemModuleAdministrator: "i18n:govoplan-access.a_system_module_administrator.4af2c020",
moduleManagement: "i18n:govoplan-access.admin_modules.4af2c021",
reusableCredentials: "i18n:govoplan-access.reusable_credentials.4af2c022",
systemCredentials: "i18n:govoplan-access.system_credentials.4af2c023",
tenantCredentials: "i18n:govoplan-access.tenant_credentials.4af2c024",
groupCredentials: "i18n:govoplan-access.group_credentials.4af2c025",
userCredentials: "i18n:govoplan-access.user_credentials.4af2c026",
systemCredentialDescription: "i18n:govoplan-access.instance_credentials_can_be_inherited_and_governed.4af2c027",
tenantCredentialDescription: "i18n:govoplan-access.tenant_credentials_can_be_shared_with_permitted_modules.4af2c028",
groupCredentialDescription: "i18n:govoplan-access.reusable_credentials_owned_by_the_selected_group.4af2c029",
userCredentialDescription: "i18n:govoplan-access.reusable_credentials_owned_by_the_selected_user.4af2c030"
} as const;
export function saveDisabledReason({
busy,
permitted,
complete
}: {
busy: boolean;
permitted: boolean;
complete: boolean;
}): string | undefined {
if (busy) return ACCESS_INTERFACE_I18N.operationInProgress;
if (!permitted) return ACCESS_INTERFACE_I18N.writePermissionRequired;
if (!complete) return ACCESS_INTERFACE_I18N.completeRequiredFields;
return undefined;
}
+60
View File
@@ -2,6 +2,36 @@ import type { PlatformTranslations } from "@govoplan/core-webui";
export const generatedTranslations: PlatformTranslations = {
"en": {
"i18n:govoplan-access.administration_data_is_loading.4af2c001": "Administration data is loading.",
"i18n:govoplan-access.create_permission_is_required.4af2c002": "Create permission is required for this action.",
"i18n:govoplan-access.update_or_assignment_permission_is_required.4af2c003": "Update or assignment permission is required for this action.",
"i18n:govoplan-access.write_permission_is_required.4af2c004": "Write permission is required for this action.",
"i18n:govoplan-access.complete_the_required_fields_before_saving.4af2c005": "Complete the required fields before saving.",
"i18n:govoplan-access.select_an_account_and_at_least_one_scope.4af2c006": "Select an account and at least one allowed scope.",
"i18n:govoplan-access.select_an_assignable_role_before_creating_a_mapping.4af2c007": "Create an assignable role before adding a function mapping.",
"i18n:govoplan-access.remove_existing_assignments_before_deleting.4af2c008": "Remove existing assignments before deleting this definition.",
"i18n:govoplan-access.assign_another_operational_owner_before_deactivating.4af2c009": "Assign another operational owner before deactivating this account or membership.",
"i18n:govoplan-access.this_definition_is_managed_by_the_system.4af2c010": "This protected definition is managed by the system.",
"i18n:govoplan-access.an_access_administration_operation_is_in_progress.4af2c011": "An access administration operation is in progress.",
"i18n:govoplan-access.required_action.4af2c012": "Required action",
"i18n:govoplan-access.who_can_fix_it.4af2c013": "Who can fix it",
"i18n:govoplan-access.where_to_go.4af2c014": "Where to go",
"i18n:govoplan-access.request_an_administrative_role_for_the_required_scope.4af2c015": "Request an administrative role for the required scope.",
"i18n:govoplan-access.a_tenant_owner_or_system_access_administrator.4af2c016": "A tenant owner or system access administrator",
"i18n:govoplan-access.admin_users_groups_and_roles.4af2c017": "Admin > Users, Groups, or Roles",
"i18n:govoplan-access.install_and_enable_the_files_module.4af2c018": "Install and enable the Files module.",
"i18n:govoplan-access.install_and_enable_the_mail_module.4af2c019": "Install and enable the Mail module.",
"i18n:govoplan-access.a_system_module_administrator.4af2c020": "A system module administrator",
"i18n:govoplan-access.admin_modules.4af2c021": "Admin > Modules",
"i18n:govoplan-access.reusable_credentials.4af2c022": "Reusable credentials",
"i18n:govoplan-access.system_credentials.4af2c023": "System credentials",
"i18n:govoplan-access.tenant_credentials.4af2c024": "Tenant credentials",
"i18n:govoplan-access.group_credentials.4af2c025": "Group credentials",
"i18n:govoplan-access.user_credentials.4af2c026": "User credentials",
"i18n:govoplan-access.instance_credentials_can_be_inherited_and_governed.4af2c027": "Instance credentials can be inherited by tenants and limited to selected modules or servers.",
"i18n:govoplan-access.tenant_credentials_can_be_shared_with_permitted_modules.4af2c028": "Tenant credentials can be shared with Mail, Files, Calendar, Addresses, and other permitted modules.",
"i18n:govoplan-access.reusable_credentials_owned_by_the_selected_group.4af2c029": "Reusable credentials owned by the selected group.",
"i18n:govoplan-access.reusable_credentials_owned_by_the_selected_user.4af2c030": "Reusable credentials owned by the selected user.",
"i18n:govoplan-access.a_role_may_contain_only_permissions_held_by_the_.a7ee5e45": "A role may contain only permissions held by the administrator defining it. The protected system:* wildcard is reserved for System owner.",
"i18n:govoplan-access.access_updated_for_value.87f22245": "Access updated for {value0}.",
"i18n:govoplan-access.access_explanation.75ee7f62": "Access explanation",
@@ -352,6 +382,36 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-access.your_current_roles_do_not_grant_administrative_a.6eafee69": "Your current roles do not grant administrative access."
},
"de": {
"i18n:govoplan-access.administration_data_is_loading.4af2c001": "Administrationsdaten werden geladen.",
"i18n:govoplan-access.create_permission_is_required.4af2c002": "Für diese Aktion ist die Berechtigung zum Erstellen erforderlich.",
"i18n:govoplan-access.update_or_assignment_permission_is_required.4af2c003": "Für diese Aktion ist eine Berechtigung zum Ändern oder Zuweisen erforderlich.",
"i18n:govoplan-access.write_permission_is_required.4af2c004": "Für diese Aktion ist eine Schreibberechtigung erforderlich.",
"i18n:govoplan-access.complete_the_required_fields_before_saving.4af2c005": "Füllen Sie vor dem Speichern die Pflichtfelder aus.",
"i18n:govoplan-access.select_an_account_and_at_least_one_scope.4af2c006": "Wählen Sie ein Konto und mindestens einen zulässigen Geltungsbereich aus.",
"i18n:govoplan-access.select_an_assignable_role_before_creating_a_mapping.4af2c007": "Erstellen Sie vor dem Hinzufügen einer Funktionszuordnung eine zuweisbare Rolle.",
"i18n:govoplan-access.remove_existing_assignments_before_deleting.4af2c008": "Entfernen Sie bestehende Zuweisungen, bevor Sie diese Definition löschen.",
"i18n:govoplan-access.assign_another_operational_owner_before_deactivating.4af2c009": "Weisen Sie einen anderen betriebsfähigen Eigentümer zu, bevor Sie dieses Konto oder diese Mitgliedschaft deaktivieren.",
"i18n:govoplan-access.this_definition_is_managed_by_the_system.4af2c010": "Diese geschützte Definition wird vom System verwaltet.",
"i18n:govoplan-access.an_access_administration_operation_is_in_progress.4af2c011": "Eine Zugriffsverwaltungsaktion wird gerade ausgeführt.",
"i18n:govoplan-access.required_action.4af2c012": "Erforderliche Aktion",
"i18n:govoplan-access.who_can_fix_it.4af2c013": "Wer kann das beheben",
"i18n:govoplan-access.where_to_go.4af2c014": "Zuständige Stelle",
"i18n:govoplan-access.request_an_administrative_role_for_the_required_scope.4af2c015": "Fordern Sie eine administrative Rolle für den benötigten Bereich an.",
"i18n:govoplan-access.a_tenant_owner_or_system_access_administrator.4af2c016": "Ein Mandanteneigentümer oder System-Zugriffsadministrator",
"i18n:govoplan-access.admin_users_groups_and_roles.4af2c017": "Administration > Benutzer, Gruppen oder Rollen",
"i18n:govoplan-access.install_and_enable_the_files_module.4af2c018": "Installieren und aktivieren Sie das Dateimodul.",
"i18n:govoplan-access.install_and_enable_the_mail_module.4af2c019": "Installieren und aktivieren Sie das Mailmodul.",
"i18n:govoplan-access.a_system_module_administrator.4af2c020": "Ein System-Moduladministrator",
"i18n:govoplan-access.admin_modules.4af2c021": "Administration > Module",
"i18n:govoplan-access.reusable_credentials.4af2c022": "Wiederverwendbare Zugangsdaten",
"i18n:govoplan-access.system_credentials.4af2c023": "System-Zugangsdaten",
"i18n:govoplan-access.tenant_credentials.4af2c024": "Mandanten-Zugangsdaten",
"i18n:govoplan-access.group_credentials.4af2c025": "Gruppen-Zugangsdaten",
"i18n:govoplan-access.user_credentials.4af2c026": "Benutzer-Zugangsdaten",
"i18n:govoplan-access.instance_credentials_can_be_inherited_and_governed.4af2c027": "Instanzweite Zugangsdaten können von Mandanten geerbt und auf ausgewählte Module oder Server begrenzt werden.",
"i18n:govoplan-access.tenant_credentials_can_be_shared_with_permitted_modules.4af2c028": "Mandanten-Zugangsdaten können mit Mail, Dateien, Kalender, Adressen und anderen erlaubten Modulen geteilt werden.",
"i18n:govoplan-access.reusable_credentials_owned_by_the_selected_group.4af2c029": "Wiederverwendbare Zugangsdaten der ausgewählten Gruppe.",
"i18n:govoplan-access.reusable_credentials_owned_by_the_selected_user.4af2c030": "Wiederverwendbare Zugangsdaten des ausgewählten Benutzers.",
"i18n:govoplan-access.a_role_may_contain_only_permissions_held_by_the_.a7ee5e45": "A role may contain only permissions held by the administrator defining it. The protected system:* wildcard is reserved for System owner.",
"i18n:govoplan-access.access_updated_for_value.87f22245": "Access updated for {value0}.",
"i18n:govoplan-access.access_explanation.75ee7f62": "Zugriffserklaerung",
+13 -13
View File
@@ -12,18 +12,18 @@ const translations = {
};
const accessAdminSurfaces = [
{ 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.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 }
{ id: "access.admin.system-roles", moduleId: "access", kind: "section" as const, label: "i18n:govoplan-access.system_roles.a9461aa6", order: 20 },
{ id: "access.admin.system-users", moduleId: "access", kind: "section" as const, label: "i18n:govoplan-access.central_users.91ac1b51", order: 50 },
{ id: "access.admin.system-credentials", moduleId: "access", kind: "section" as const, label: "i18n:govoplan-access.system_credentials.4af2c023", order: 80 },
{ id: "access.admin.tenant-roles", moduleId: "access", kind: "section" as const, label: "i18n:govoplan-access.tenant_roles.51aca82d", order: 10 },
{ id: "access.admin.tenant-function-mappings", moduleId: "access", kind: "section" as const, label: "i18n:govoplan-access.function_role_mappings.2b64e9c3", order: 20 },
{ id: "access.admin.tenant-groups", moduleId: "access", kind: "section" as const, label: "i18n:govoplan-access.tenant_groups.47e6cc05", order: 30 },
{ id: "access.admin.tenant-users", moduleId: "access", kind: "section" as const, label: "i18n:govoplan-access.tenant_users.cb800b38", order: 40 },
{ id: "access.admin.tenant-credentials", moduleId: "access", kind: "section" as const, label: "i18n:govoplan-access.tenant_credentials.4af2c024", order: 70 },
{ id: "access.admin.tenant-api-keys", moduleId: "access", kind: "section" as const, label: "i18n:govoplan-access.tenant_api_keys.4b1d81f8", order: 80 },
{ id: "access.admin.group-credentials", moduleId: "access", kind: "section" as const, label: "i18n:govoplan-access.group_credentials.4af2c025", order: 30 },
{ id: "access.admin.user-credentials", moduleId: "access", kind: "section" as const, label: "i18n:govoplan-access.user_credentials.4af2c026", order: 30 },
{ id: "access.settings.credentials", moduleId: "access", kind: "section" as const, label: "i18n:govoplan-access.reusable_credentials.4af2c022", order: 30 }
];
function renderAdminRoute({ settings, auth, onAuthChange }: PlatformRouteContext) {
@@ -36,7 +36,7 @@ function renderAdminRoute({ settings, auth, onAuthChange }: PlatformRouteContext
export const accessModule: PlatformWebModule = {
id: "access",
label: "i18n:govoplan-access.access.2f81a22d",
version: "1.0.0",
version: "0.1.11",
translations,
viewSurfaces: accessAdminSurfaces,
navItems: [