Sync GovOPlaN module state
This commit is contained in:
@@ -242,6 +242,17 @@ export type ApiKeyAdminItem = {
|
||||
created_at: string;
|
||||
};
|
||||
|
||||
export type ExternalFunctionRoleMappingItem = {
|
||||
id: string;
|
||||
tenant_id: string;
|
||||
source_module: string;
|
||||
function_id: string;
|
||||
role_id: string;
|
||||
settings: Record<string, unknown>;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
export type AuditAdminItem = {
|
||||
id: string;
|
||||
scope: "tenant" | "system";
|
||||
@@ -268,6 +279,7 @@ export type SystemAccountListDeltaResponse = { accounts: SystemAccountItem[]; ro
|
||||
export type ApiKeyListDeltaResponse = { api_keys: ApiKeyAdminItem[] } & DeltaResponseFields;
|
||||
export type TenantListDeltaResponse = { tenants: TenantAdminItem[] } & DeltaResponseFields;
|
||||
export type GovernanceTemplateListDeltaResponse = { templates: GovernanceTemplateItem[] } & DeltaResponseFields;
|
||||
export type ExternalFunctionRoleMappingListDeltaResponse = { mappings: ExternalFunctionRoleMappingItem[] } & DeltaResponseFields;
|
||||
export type TenantSettingsDeltaResponse = {
|
||||
item?: TenantSettingsItem | null;
|
||||
sections: TenantSettingsDeltaSections;
|
||||
@@ -451,6 +463,31 @@ export function deleteRole(settings: ApiSettings, roleId: string): Promise<void>
|
||||
return apiFetch(settings, `/api/v1/admin/roles/${roleId}`, { method: "DELETE" });
|
||||
}
|
||||
|
||||
export function fetchExternalFunctionRoleMappingsDelta(settings: ApiSettings, options: { since?: string | null; limit?: number } = {}): Promise<ExternalFunctionRoleMappingListDeltaResponse> {
|
||||
const suffix = deltaSuffix(options);
|
||||
return apiFetch(settings, `/api/v1/admin/external-function-role-mappings/delta${suffix}`);
|
||||
}
|
||||
|
||||
export function createExternalFunctionRoleMapping(settings: ApiSettings, payload: {
|
||||
source_module: string;
|
||||
function_id: string;
|
||||
role_id: string;
|
||||
settings?: Record<string, unknown>;
|
||||
}): Promise<ExternalFunctionRoleMappingItem> {
|
||||
return apiFetch(settings, "/api/v1/admin/external-function-role-mappings", { method: "POST", body: JSON.stringify(payload) });
|
||||
}
|
||||
|
||||
export function updateExternalFunctionRoleMapping(settings: ApiSettings, mappingId: string, payload: {
|
||||
role_id?: string;
|
||||
settings?: Record<string, unknown>;
|
||||
}): Promise<ExternalFunctionRoleMappingItem> {
|
||||
return apiFetch(settings, `/api/v1/admin/external-function-role-mappings/${mappingId}`, { method: "PATCH", body: JSON.stringify(payload) });
|
||||
}
|
||||
|
||||
export function deleteExternalFunctionRoleMapping(settings: ApiSettings, mappingId: string): Promise<void> {
|
||||
return apiFetch(settings, `/api/v1/admin/external-function-role-mappings/${mappingId}`, { method: "DELETE" });
|
||||
}
|
||||
|
||||
export async function fetchSystemRoles(settings: ApiSettings): Promise<RoleSummary[]> {
|
||||
const response = await apiFetch<{ roles: RoleSummary[] }>(settings, "/api/v1/admin/system/roles");
|
||||
return response.roles;
|
||||
|
||||
@@ -6,7 +6,8 @@ import type {
|
||||
ApiSettings,
|
||||
AuthInfo,
|
||||
FilesConnectorsUiCapability,
|
||||
MailProfilesUiCapability
|
||||
MailProfilesUiCapability,
|
||||
OrganizationFunctionPickerUiCapability
|
||||
} from "@govoplan/core-webui";
|
||||
import { fetchMe } from "@govoplan/core-webui";
|
||||
import { Card } from "@govoplan/core-webui";
|
||||
@@ -19,6 +20,7 @@ import TenantsPanel from "./TenantsPanel";
|
||||
import UsersPanel from "./UsersPanel";
|
||||
import GroupsPanel from "./GroupsPanel";
|
||||
import RolesPanel from "./RolesPanel";
|
||||
import ExternalFunctionRoleMappingsPanel from "./ExternalFunctionRoleMappingsPanel";
|
||||
import ApiKeysPanel from "./ApiKeysPanel";
|
||||
import AdminAuditPanel from "./AdminAuditPanel";
|
||||
import FileConnectorsPanel from "./FileConnectorsPanel";
|
||||
@@ -46,6 +48,7 @@ const handledAdminSectionIds = new Set<string>([
|
||||
"system-retention",
|
||||
"tenant-settings",
|
||||
"tenant-roles",
|
||||
"tenant-function-role-mappings",
|
||||
"tenant-groups",
|
||||
"tenant-users",
|
||||
"tenant-file-connectors",
|
||||
@@ -72,6 +75,7 @@ export default function AdminPage({
|
||||
}) {
|
||||
const mailProfilesUi = usePlatformUiCapability<MailProfilesUiCapability>("mail.profiles");
|
||||
const fileConnectorsUi = usePlatformUiCapability<FilesConnectorsUiCapability>("files.connectors");
|
||||
const organizationFunctionPicker = usePlatformUiCapability<OrganizationFunctionPickerUiCapability>("organizations.functionPicker");
|
||||
const adminSectionCapabilities = usePlatformUiCapabilities<AdminSectionsUiCapability>("admin.sections");
|
||||
const mailProfilesAvailable = Boolean(mailProfilesUi);
|
||||
const fileConnectorsAvailable = Boolean(fileConnectorsUi);
|
||||
@@ -108,6 +112,7 @@ export default function AdminPage({
|
||||
if (hasScope(auth, "admin:users:read")) sections.add("tenant-users");
|
||||
if (hasScope(auth, "admin:groups:read")) sections.add("tenant-groups");
|
||||
if (hasScope(auth, "admin:roles:read")) sections.add("tenant-roles");
|
||||
if (organizationFunctionPicker && hasAnyScope(auth, ["admin:roles:read", "access:function:read", "access:role:read"])) sections.add("tenant-function-role-mappings");
|
||||
if (hasScope(auth, "admin:api_keys:read")) sections.add("tenant-api-keys");
|
||||
if (mailProfilesAvailable && hasAnyScope(auth, ["mail_servers:read", "admin:policies:read"])) {
|
||||
sections.add("tenant-mail-servers");
|
||||
@@ -126,7 +131,7 @@ export default function AdminPage({
|
||||
if (hasScope(auth, "admin:settings:read")) sections.add("tenant-settings");
|
||||
if (auditAvailable && hasScope(auth, "audit:read")) sections.add("tenant-audit");
|
||||
return sections;
|
||||
}, [auth, auditAvailable, contributedSections, fileConnectorsAvailable, mailProfilesAvailable, policyAvailable]);
|
||||
}, [auth, auditAvailable, contributedSections, fileConnectorsAvailable, mailProfilesAvailable, organizationFunctionPicker, policyAvailable]);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const requestedSection = searchParams.get("section") as AdminSection | null;
|
||||
const fallbackSection = available.has("overview") ? "overview" : (Array.from(available)[0] ?? "overview");
|
||||
@@ -198,14 +203,15 @@ export default function AdminPage({
|
||||
items: asSubnavItems(
|
||||
sortNavItems([
|
||||
visibleNavItem(available, "tenant-roles", "i18n:govoplan-access.roles.47dcc27d", 10),
|
||||
visibleNavItem(available, "tenant-groups", "i18n:govoplan-access.groups.ae9629f4", 20),
|
||||
visibleNavItem(available, "tenant-users", "i18n:govoplan-access.users.57f2b181", 30),
|
||||
visibleNavItem(available, "tenant-file-connectors", "i18n:govoplan-access.file_connections.1e362326", 40),
|
||||
visibleNavItem(available, "tenant-mail-servers", "i18n:govoplan-access.mail_servers.d627326a", 50),
|
||||
visibleNavItem(available, "tenant-api-keys", "i18n:govoplan-access.api_keys.94fcf3c2", 60),
|
||||
visibleNavItem(available, "tenant-retention", "i18n:govoplan-access.retention.c7199d9e", 70),
|
||||
visibleNavItem(available, "tenant-settings", "i18n:govoplan-access.general.9239ee2c", 80),
|
||||
visibleNavItem(available, "tenant-audit", "i18n:govoplan-access.audit.fa1703dd", 90),
|
||||
visibleNavItem(available, "tenant-function-role-mappings", "i18n:govoplan-access.function_role_mappings.2b64e9c3", 20),
|
||||
visibleNavItem(available, "tenant-groups", "i18n:govoplan-access.groups.ae9629f4", 30),
|
||||
visibleNavItem(available, "tenant-users", "i18n:govoplan-access.users.57f2b181", 40),
|
||||
visibleNavItem(available, "tenant-file-connectors", "i18n:govoplan-access.file_connections.1e362326", 50),
|
||||
visibleNavItem(available, "tenant-mail-servers", "i18n:govoplan-access.mail_servers.d627326a", 60),
|
||||
visibleNavItem(available, "tenant-api-keys", "i18n:govoplan-access.api_keys.94fcf3c2", 70),
|
||||
visibleNavItem(available, "tenant-retention", "i18n:govoplan-access.retention.c7199d9e", 80),
|
||||
visibleNavItem(available, "tenant-settings", "i18n:govoplan-access.general.9239ee2c", 90),
|
||||
visibleNavItem(available, "tenant-audit", "i18n:govoplan-access.audit.fa1703dd", 100),
|
||||
...contributedNavItems(contributedSections, available, "TENANT", handledAdminSectionIds)
|
||||
])
|
||||
)
|
||||
@@ -265,6 +271,7 @@ export default function AdminPage({
|
||||
{!contributedSection && active === "tenant-users" && <UsersPanel settings={settings} auth={auth} canCreate={hasScope(auth, "admin:users:create")} canUpdate={hasScope(auth, "admin:users:update")} canSuspend={hasScope(auth, "admin:users:suspend")} canManageGroups={hasScope(auth, "admin:groups:manage_members")} canAssignRoles={hasScope(auth, "admin:roles:assign")} onAuthRefresh={refreshAuth} />}
|
||||
{!contributedSection && active === "tenant-groups" && <GroupsPanel settings={settings} auth={auth} canDefine={hasScope(auth, "admin:groups:write")} canManageMembers={hasScope(auth, "admin:groups:manage_members")} canAssignRoles={hasScope(auth, "admin:roles:assign")} onAuthRefresh={refreshAuth} />}
|
||||
{!contributedSection && active === "tenant-roles" && <RolesPanel settings={settings} auth={auth} canDefine={hasScope(auth, "admin:roles:write")} onAuthRefresh={refreshAuth} />}
|
||||
{!contributedSection && active === "tenant-function-role-mappings" && organizationFunctionPicker && <ExternalFunctionRoleMappingsPanel settings={settings} auth={auth} functionPicker={organizationFunctionPicker} canWrite={hasAnyScope(auth, ["admin:roles:write", "access:function:write", "access:role:assign"])} onAuthRefresh={refreshAuth} />}
|
||||
{!contributedSection && active === "tenant-api-keys" && <ApiKeysPanel settings={settings} auth={auth} canCreate={hasScope(auth, "admin:api_keys:create")} canRevoke={hasScope(auth, "admin:api_keys:revoke")} />}
|
||||
{!contributedSection && active === "tenant-mail-servers" && <MailProfilesPanel settings={settings} scopeType="tenant" canWriteProfiles={hasScope(auth, "mail_servers:write")} canManageCredentials={hasScope(auth, "mail_servers:manage_credentials")} canWritePolicy={hasScope(auth, "admin:policies:write")} />}
|
||||
{!contributedSection && active === "tenant-user-mail-servers" && <MailProfilesPanel settings={settings} scopeType="user" canWriteProfiles={hasScope(auth, "mail_servers:write")} canManageCredentials={hasScope(auth, "mail_servers:manage_credentials")} canWritePolicy={hasAnyScope(auth, ["admin:policies:write", "mail_servers:write"])} />}
|
||||
|
||||
377
webui/src/features/admin/ExternalFunctionRoleMappingsPanel.tsx
Normal file
377
webui/src/features/admin/ExternalFunctionRoleMappingsPanel.tsx
Normal file
@@ -0,0 +1,377 @@
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Pencil, Plus, Trash2 } from "lucide-react";
|
||||
import type { ApiSettings, AuthInfo, OrganizationFunctionPickerUiCapability, OrganizationFunctionSelection } from "@govoplan/core-webui";
|
||||
import {
|
||||
createExternalFunctionRoleMapping,
|
||||
deleteExternalFunctionRoleMapping,
|
||||
fetchExternalFunctionRoleMappingsDelta,
|
||||
fetchRolesDelta,
|
||||
updateExternalFunctionRoleMapping,
|
||||
type ExternalFunctionRoleMappingItem,
|
||||
type RoleSummary
|
||||
} from "../../api/admin";
|
||||
import { Button } from "@govoplan/core-webui";
|
||||
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, adminErrorMessage, formatAdminDateTime as formatDateTime } from "@govoplan/core-webui";
|
||||
import { i18nMessage, useDeltaWatermarks, useUnsavedDraftGuard } from "@govoplan/core-webui";
|
||||
import { loadDeltaRows } from "./utils/deltaRows";
|
||||
|
||||
const emptyDraft = {
|
||||
sourceModule: "organizations",
|
||||
functionId: "",
|
||||
functionLabel: "",
|
||||
organizationUnitLabel: "",
|
||||
roleId: ""
|
||||
};
|
||||
|
||||
export default function ExternalFunctionRoleMappingsPanel({
|
||||
settings,
|
||||
auth,
|
||||
functionPicker,
|
||||
canWrite,
|
||||
onAuthRefresh
|
||||
}: {
|
||||
settings: ApiSettings;
|
||||
auth: AuthInfo;
|
||||
functionPicker: OrganizationFunctionPickerUiCapability;
|
||||
canWrite: boolean;
|
||||
onAuthRefresh: () => Promise<void>;
|
||||
}) {
|
||||
const [mappings, setMappings] = useState<ExternalFunctionRoleMappingItem[]>([]);
|
||||
const [roles, setRoles] = useState<RoleSummary[]>([]);
|
||||
const mappingsRef = useRef<ExternalFunctionRoleMappingItem[]>([]);
|
||||
const rolesRef = useRef<RoleSummary[]>([]);
|
||||
const { getDeltaWatermark, setDeltaWatermark, resetDeltaWatermark } = useDeltaWatermarks();
|
||||
const [editing, setEditing] = useState<ExternalFunctionRoleMappingItem | "new" | null>(null);
|
||||
const [draft, setDraft] = useState(emptyDraft);
|
||||
const [savedDraftKey, setSavedDraftKey] = useState(draftKey(emptyDraft));
|
||||
const [deleting, setDeleting] = useState<ExternalFunctionRoleMappingItem | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
const [success, setSuccess] = useState("");
|
||||
const tenantId = (auth.active_tenant ?? auth.tenant).id;
|
||||
const dirty = editing !== null && draftKey(draft) !== savedDraftKey;
|
||||
|
||||
useUnsavedDraftGuard({
|
||||
dirty,
|
||||
onSave: save,
|
||||
onDiscard: closeEditor
|
||||
});
|
||||
|
||||
async function load() {
|
||||
setLoading(true);
|
||||
setError("");
|
||||
try {
|
||||
const [nextMappings, nextRoles] = await Promise.all([
|
||||
loadDeltaRows(
|
||||
mappingsRef.current,
|
||||
"access:external-function-role-mappings",
|
||||
getDeltaWatermark,
|
||||
setDeltaWatermark,
|
||||
(since) => fetchExternalFunctionRoleMappingsDelta(settings, { since }),
|
||||
(response) => response.mappings,
|
||||
(mapping) => mapping.id,
|
||||
"access_external_function_role_mapping",
|
||||
sortMappings
|
||||
),
|
||||
loadDeltaRows(
|
||||
rolesRef.current,
|
||||
"access:roles-for-external-function-mappings",
|
||||
getDeltaWatermark,
|
||||
setDeltaWatermark,
|
||||
(since) => fetchRolesDelta(settings, { since }),
|
||||
(response) => response.roles,
|
||||
(role) => role.id,
|
||||
"access_role",
|
||||
sortRoles
|
||||
)
|
||||
]);
|
||||
mappingsRef.current = nextMappings;
|
||||
rolesRef.current = nextRoles;
|
||||
setMappings(nextMappings);
|
||||
setRoles(nextRoles.filter((role) => role.level === "tenant"));
|
||||
} catch (err) {
|
||||
setError(adminErrorMessage(err));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
mappingsRef.current = [];
|
||||
rolesRef.current = [];
|
||||
resetDeltaWatermark();
|
||||
void load();
|
||||
}, [settings.accessToken, settings.apiBaseUrl, tenantId, resetDeltaWatermark]);
|
||||
|
||||
const roleById = useMemo(() => new Map(roles.map((role) => [role.id, role])), [roles]);
|
||||
const assignableRoles = useMemo(() => roles.filter((role) => role.is_assignable), [roles]);
|
||||
|
||||
function openCreate() {
|
||||
const initial = { ...emptyDraft, roleId: assignableRoles[0]?.id ?? "" };
|
||||
setDraft(initial);
|
||||
setSavedDraftKey(draftKey(initial));
|
||||
setEditing("new");
|
||||
setError("");
|
||||
}
|
||||
|
||||
function openEdit(mapping: ExternalFunctionRoleMappingItem) {
|
||||
const nextDraft = {
|
||||
sourceModule: mapping.source_module,
|
||||
functionId: mapping.function_id,
|
||||
functionLabel: "",
|
||||
organizationUnitLabel: "",
|
||||
roleId: mapping.role_id
|
||||
};
|
||||
setDraft(nextDraft);
|
||||
setSavedDraftKey(draftKey(nextDraft));
|
||||
setEditing(mapping);
|
||||
setError("");
|
||||
}
|
||||
|
||||
function closeEditor() {
|
||||
setEditing(null);
|
||||
setDraft(emptyDraft);
|
||||
setSavedDraftKey(draftKey(emptyDraft));
|
||||
}
|
||||
|
||||
async function save(): Promise<boolean> {
|
||||
setBusy(true);
|
||||
setError("");
|
||||
try {
|
||||
if (editing === "new") {
|
||||
await createExternalFunctionRoleMapping(settings, {
|
||||
source_module: functionPicker.sourceModule,
|
||||
function_id: draft.functionId.trim(),
|
||||
role_id: draft.roleId
|
||||
});
|
||||
setSuccess(i18nMessage("i18n:govoplan-access.function_role_mapping_created.7a25eb5a"));
|
||||
} else if (editing) {
|
||||
await updateExternalFunctionRoleMapping(settings, editing.id, { role_id: draft.roleId });
|
||||
setSuccess(i18nMessage("i18n:govoplan-access.function_role_mapping_updated.76020443"));
|
||||
}
|
||||
closeEditor();
|
||||
await onAuthRefresh();
|
||||
mappingsRef.current = [];
|
||||
resetDeltaWatermark("access:external-function-role-mappings");
|
||||
await load();
|
||||
return true;
|
||||
} catch (err) {
|
||||
setError(adminErrorMessage(err));
|
||||
return false;
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function remove() {
|
||||
if (!deleting) return;
|
||||
setBusy(true);
|
||||
setError("");
|
||||
try {
|
||||
await deleteExternalFunctionRoleMapping(settings, deleting.id);
|
||||
setSuccess(i18nMessage("i18n:govoplan-access.function_role_mapping_deleted.fb180786"));
|
||||
setDeleting(null);
|
||||
await onAuthRefresh();
|
||||
mappingsRef.current = [];
|
||||
resetDeltaWatermark("access:external-function-role-mappings");
|
||||
await load();
|
||||
} catch (err) {
|
||||
setError(adminErrorMessage(err));
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
const columns = useMemo<DataGridColumn<ExternalFunctionRoleMappingItem>[]>(
|
||||
() => [
|
||||
{
|
||||
id: "function",
|
||||
header: "i18n:govoplan-access.function_fact.4f7435e4",
|
||||
width: "minmax(260px, 1fr)",
|
||||
minWidth: 220,
|
||||
resizable: true,
|
||||
sticky: "start",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
value: (row) => `${row.source_module} ${row.function_id}`,
|
||||
render: (row) => (
|
||||
<>
|
||||
{functionPicker.renderLabel?.({
|
||||
settings,
|
||||
auth,
|
||||
sourceModule: row.source_module,
|
||||
functionId: row.function_id,
|
||||
fallback: (
|
||||
<div>
|
||||
<strong>{row.source_module}</strong>
|
||||
<div className="muted small-note">{row.function_id}</div>
|
||||
</div>
|
||||
)
|
||||
}) ?? (
|
||||
<div>
|
||||
<strong>{row.source_module}</strong>
|
||||
<div className="muted small-note">{row.function_id}</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
},
|
||||
{
|
||||
id: "role",
|
||||
header: "i18n:govoplan-access.role.c3f104d1",
|
||||
width: "minmax(220px, 1fr)",
|
||||
minWidth: 190,
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
value: (row) => roleById.get(row.role_id)?.name ?? row.role_id,
|
||||
render: (row) => {
|
||||
const role = roleById.get(row.role_id);
|
||||
return (
|
||||
<div>
|
||||
<strong>{role?.name ?? row.role_id}</strong>
|
||||
{role && <div className="muted small-note">{role.slug}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "updated",
|
||||
header: "i18n:govoplan-access.updated.f2f8570d",
|
||||
width: 180,
|
||||
minWidth: 150,
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
value: (row) => row.updated_at,
|
||||
render: (row) => formatDateTime(row.updated_at)
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
header: "i18n:govoplan-access.actions.c3cd636a",
|
||||
width: 130,
|
||||
sticky: "end",
|
||||
resizable: false,
|
||||
align: "right",
|
||||
render: (row) => (
|
||||
<div className="admin-icon-actions">
|
||||
<AdminIconButton label={i18nMessage("i18n:govoplan-access.edit_value.fad75899", { value0: row.function_id })} icon={<Pencil />} onClick={() => openEdit(row)} disabled={!canWrite} />
|
||||
<AdminIconButton label={i18nMessage("i18n:govoplan-access.delete_value.4d18989e", { value0: row.function_id })} icon={<Trash2 />} variant="danger" onClick={() => setDeleting(row)} disabled={!canWrite} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
],
|
||||
[auth, canWrite, functionPicker, roleById, settings]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<AdminPageLayout
|
||||
title="i18n:govoplan-access.function_role_mappings.2b64e9c3"
|
||||
description="i18n:govoplan-access.map_accepted_function_facts_to_tenant_roles_.7581e5cf"
|
||||
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_function_role_mapping.1bc376ac" icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canWrite || !assignableRoles.length} />
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div className="admin-table-surface">
|
||||
<DataGrid
|
||||
id="admin-external-function-role-mappings-v1"
|
||||
rows={mappings}
|
||||
columns={columns}
|
||||
initialFit="container"
|
||||
getRowKey={(row) => row.id}
|
||||
emptyText="i18n:govoplan-access.no_function_role_mappings_found.f735ff54"
|
||||
/>
|
||||
</div>
|
||||
</AdminPageLayout>
|
||||
|
||||
<Dialog
|
||||
open={editing !== null}
|
||||
title={editing === "new" ? "i18n:govoplan-access.create_function_role_mapping.3718168d" : "i18n:govoplan-access.edit_function_role_mapping.91ee75af"}
|
||||
onClose={() => !busy && closeEditor()}
|
||||
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}>
|
||||
{busy ? "i18n:govoplan-access.saving.56a2285c" : "i18n:govoplan-access.save_mapping.a4ac90e9"}
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div className="admin-form-grid">
|
||||
<FormField label="i18n:govoplan-access.function_id.e5e08937">
|
||||
{functionPicker.renderPicker({
|
||||
settings,
|
||||
auth,
|
||||
disabled: editing !== "new",
|
||||
value: draft.functionId ? draftSelection(draft) : null,
|
||||
onChange: (selection) => setDraft({ ...draft, ...selectionDraft(selection) })
|
||||
})}
|
||||
</FormField>
|
||||
<FormField label="i18n:govoplan-access.role.c3f104d1">
|
||||
<select value={draft.roleId} onChange={(event) => setDraft({ ...draft, roleId: event.target.value })}>
|
||||
<option value="">i18n:govoplan-access.select_role.c543f191</option>
|
||||
{assignableRoles.map((role) => (
|
||||
<option key={role.id} value={role.id}>{role.name} ({role.slug})</option>
|
||||
))}
|
||||
</select>
|
||||
</FormField>
|
||||
</div>
|
||||
<p className="muted small-note">i18n:govoplan-access.function_role_mapping_help.0cf9996a</p>
|
||||
</Dialog>
|
||||
|
||||
<ConfirmDialog
|
||||
open={Boolean(deleting)}
|
||||
title="i18n:govoplan-access.delete_function_role_mapping.0c0eec6e"
|
||||
message={i18nMessage("i18n:govoplan-access.delete_function_role_mapping_value.419da2aa", { value0: deleting?.function_id })}
|
||||
confirmLabel="i18n:govoplan-access.delete_mapping.0d27d92a"
|
||||
tone="danger"
|
||||
busy={busy}
|
||||
onCancel={() => setDeleting(null)}
|
||||
onConfirm={() => void remove()}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function draftKey(draft: typeof emptyDraft): string {
|
||||
return JSON.stringify(draft);
|
||||
}
|
||||
|
||||
function draftSelection(draft: typeof emptyDraft): OrganizationFunctionSelection {
|
||||
return {
|
||||
sourceModule: "organizations",
|
||||
functionId: draft.functionId,
|
||||
label: draft.functionLabel || null,
|
||||
organizationUnitLabel: draft.organizationUnitLabel || null
|
||||
};
|
||||
}
|
||||
|
||||
function selectionDraft(selection: OrganizationFunctionSelection | null): Pick<typeof emptyDraft, "sourceModule" | "functionId" | "functionLabel" | "organizationUnitLabel"> {
|
||||
return {
|
||||
sourceModule: selection?.sourceModule ?? "organizations",
|
||||
functionId: selection?.functionId ?? "",
|
||||
functionLabel: selection?.label ?? "",
|
||||
organizationUnitLabel: selection?.organizationUnitLabel ?? ""
|
||||
};
|
||||
}
|
||||
|
||||
function sortMappings(left: ExternalFunctionRoleMappingItem, right: ExternalFunctionRoleMappingItem): number {
|
||||
const sourceDelta = left.source_module.localeCompare(right.source_module);
|
||||
return sourceDelta !== 0 ? sourceDelta : left.function_id.localeCompare(right.function_id);
|
||||
}
|
||||
|
||||
function sortRoles(left: RoleSummary, right: RoleSummary): number {
|
||||
return left.name.localeCompare(right.name);
|
||||
}
|
||||
@@ -16,6 +16,7 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-access.add_api_key.725d9988": "Add API key",
|
||||
"i18n:govoplan-access.add_global_account.18e4df22": "Add global account",
|
||||
"i18n:govoplan-access.add_group.2fca464f": "Add group",
|
||||
"i18n:govoplan-access.add_function_role_mapping.1bc376ac": "Add function role mapping",
|
||||
"i18n:govoplan-access.add_role.d8d5d55c": "Add role",
|
||||
"i18n:govoplan-access.add_system_role.f9ef262b": "Add system role",
|
||||
"i18n:govoplan-access.add_tenant_user.36f37ce7": "Add tenant user",
|
||||
@@ -52,6 +53,7 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-access.create_api_key.d7b30388": "Create API key",
|
||||
"i18n:govoplan-access.create_global_account.e821f016": "Create global account",
|
||||
"i18n:govoplan-access.create_group.5a0b1c17": "Create group",
|
||||
"i18n:govoplan-access.create_function_role_mapping.3718168d": "Create function role mapping",
|
||||
"i18n:govoplan-access.create_key.e028cb09": "Create key",
|
||||
"i18n:govoplan-access.create_role.db859bad": "Create role",
|
||||
"i18n:govoplan-access.create_system_role.a1e40b25": "Create system role",
|
||||
@@ -76,6 +78,9 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-access.deactivate_value.a276a667": "Deactivate {value0}",
|
||||
"i18n:govoplan-access.default_locale.b99d021f": "Default locale",
|
||||
"i18n:govoplan-access.delete_role.fbf0667e": "Delete role",
|
||||
"i18n:govoplan-access.delete_function_role_mapping.0c0eec6e": "Delete function role mapping",
|
||||
"i18n:govoplan-access.delete_function_role_mapping_value.419da2aa": "Delete the mapping for {value0}? Accepted assignments will no longer grant the mapped role.",
|
||||
"i18n:govoplan-access.delete_mapping.0d27d92a": "Delete mapping",
|
||||
"i18n:govoplan-access.delete_system_role.e2d84a56": "Delete system role",
|
||||
"i18n:govoplan-access.delete_value_only_unassigned_tenant_defined_role.e48b13e7": "Delete {value0}? Only unassigned tenant-defined roles can be deleted.",
|
||||
"i18n:govoplan-access.delete_value_the_role_must_have_no_account_assig.020eb657": "Delete {value0}? The role must have no account assignments.",
|
||||
@@ -87,6 +92,7 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-access.dry_run.485a3d15": "Dry run",
|
||||
"i18n:govoplan-access.edit_global_account.d13b8485": "Edit global account",
|
||||
"i18n:govoplan-access.edit_group.edb57d8e": "Edit group",
|
||||
"i18n:govoplan-access.edit_function_role_mapping.91ee75af": "Edit function role mapping",
|
||||
"i18n:govoplan-access.edit_role.61dd63e9": "Edit role",
|
||||
"i18n:govoplan-access.edit_system_role.6ebb7cb0": "Edit system role",
|
||||
"i18n:govoplan-access.edit_tenant_user.99121a61": "Edit tenant user",
|
||||
@@ -101,6 +107,13 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-access.file_connections.1e362326": "File connections",
|
||||
"i18n:govoplan-access.files_module_unavailable.0ee90db1": "Files module unavailable",
|
||||
"i18n:govoplan-access.files.6ce6c512": "Files",
|
||||
"i18n:govoplan-access.function_fact.4f7435e4": "Function fact",
|
||||
"i18n:govoplan-access.function_id.e5e08937": "Function ID",
|
||||
"i18n:govoplan-access.function_role_mapping_created.7a25eb5a": "Function role mapping created.",
|
||||
"i18n:govoplan-access.function_role_mapping_deleted.fb180786": "Function role mapping deleted.",
|
||||
"i18n:govoplan-access.function_role_mapping_help.0cf9996a": "The source module and function ID identify an accepted external function fact. Access grants the selected tenant role only while IDM reports an active accepted assignment for that fact.",
|
||||
"i18n:govoplan-access.function_role_mapping_updated.76020443": "Function role mapping updated.",
|
||||
"i18n:govoplan-access.function_role_mappings.2b64e9c3": "Function role mappings",
|
||||
"i18n:govoplan-access.general.9239ee2c": "General",
|
||||
"i18n:govoplan-access.global_account_details.0a0cf240": "Global account details",
|
||||
"i18n:govoplan-access.global_account_value_created.5100e467": "Global account {value0} created.",
|
||||
@@ -163,6 +176,7 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-access.no_api_keys_found.1f377128": "No API keys found.",
|
||||
"i18n:govoplan-access.no_assignable_roles_exist.a4c268c2": "No assignable roles exist.",
|
||||
"i18n:govoplan-access.no_expiry.39d436aa": "No expiry",
|
||||
"i18n:govoplan-access.no_function_role_mappings_found.f735ff54": "No function role mappings found.",
|
||||
"i18n:govoplan-access.no_global_accounts_found.29d96a9e": "No global accounts found.",
|
||||
"i18n:govoplan-access.no_groups_exist_yet.9cd029f6": "No groups exist yet.",
|
||||
"i18n:govoplan-access.no_groups_found.627ca913": "No groups found.",
|
||||
@@ -207,6 +221,7 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-access.save_account.0b761f5c": "Save account",
|
||||
"i18n:govoplan-access.save_general_settings.5c90f8c4": "Save general settings",
|
||||
"i18n:govoplan-access.save_group.36ca6865": "Save group",
|
||||
"i18n:govoplan-access.save_mapping.a4ac90e9": "Save mapping",
|
||||
"i18n:govoplan-access.save_role.16fe10d1": "Save role",
|
||||
"i18n:govoplan-access.save_tenant.9eb2ac74": "Save tenant",
|
||||
"i18n:govoplan-access.save_user.0d071b89": "Save user",
|
||||
@@ -214,11 +229,13 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-access.saving.ae7e8875": "Saving...",
|
||||
"i18n:govoplan-access.scope.4651a34e": "Scope",
|
||||
"i18n:govoplan-access.scopes.c23540e5": "Scopes",
|
||||
"i18n:govoplan-access.select_role.c543f191": "Select role",
|
||||
"i18n:govoplan-access.select_user.b8a1d9de": "Select user",
|
||||
"i18n:govoplan-access.set_concrete_system_retention_values_the_allow_o.02e1fd13": "Set concrete system retention values. The Allow override toggles decide which fields tenants, owners and campaigns may override.",
|
||||
"i18n:govoplan-access.settings_for_the_active_tenant_context.ad267b86": "Settings for the active tenant context.",
|
||||
"i18n:govoplan-access.show_revoked.b4265807": "Show revoked",
|
||||
"i18n:govoplan-access.slug.094da9b9": "Slug",
|
||||
"i18n:govoplan-access.source_module.62b7241c": "Source module",
|
||||
"i18n:govoplan-access.status.bae7d5be": "Status",
|
||||
"i18n:govoplan-access.store_it_in_a_secret_manager_only_its_prefix_and.796ac588": "Store it in a secret manager. Only its prefix and hash remain in Multi Seal Mail.",
|
||||
"i18n:govoplan-access.suspend_tenant.151d283a": "Suspend tenant",
|
||||
@@ -329,6 +346,7 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-access.add_api_key.725d9988": "Add API key",
|
||||
"i18n:govoplan-access.add_global_account.18e4df22": "Add global account",
|
||||
"i18n:govoplan-access.add_group.2fca464f": "Gruppe hinzufügen",
|
||||
"i18n:govoplan-access.add_function_role_mapping.1bc376ac": "Funktions-Rollenzuordnung hinzufügen",
|
||||
"i18n:govoplan-access.add_role.d8d5d55c": "Rolle hinzufügen",
|
||||
"i18n:govoplan-access.add_system_role.f9ef262b": "Add system role",
|
||||
"i18n:govoplan-access.add_tenant_user.36f37ce7": "Mandantenbenutzer hinzufügen",
|
||||
@@ -365,6 +383,7 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-access.create_api_key.d7b30388": "API-Schlüssel erstellen",
|
||||
"i18n:govoplan-access.create_global_account.e821f016": "Create global account",
|
||||
"i18n:govoplan-access.create_group.5a0b1c17": "Gruppe erstellen",
|
||||
"i18n:govoplan-access.create_function_role_mapping.3718168d": "Funktions-Rollenzuordnung erstellen",
|
||||
"i18n:govoplan-access.create_key.e028cb09": "Create key",
|
||||
"i18n:govoplan-access.create_role.db859bad": "Rolle erstellen",
|
||||
"i18n:govoplan-access.create_system_role.a1e40b25": "Create system role",
|
||||
@@ -389,6 +408,9 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-access.deactivate_value.a276a667": "Deactivate {value0}",
|
||||
"i18n:govoplan-access.default_locale.b99d021f": "Standardsprache",
|
||||
"i18n:govoplan-access.delete_role.fbf0667e": "Delete role",
|
||||
"i18n:govoplan-access.delete_function_role_mapping.0c0eec6e": "Funktions-Rollenzuordnung löschen",
|
||||
"i18n:govoplan-access.delete_function_role_mapping_value.419da2aa": "Zuordnung für {value0} löschen? Akzeptierte Zuweisungen gewähren die zugeordnete Rolle dann nicht mehr.",
|
||||
"i18n:govoplan-access.delete_mapping.0d27d92a": "Zuordnung löschen",
|
||||
"i18n:govoplan-access.delete_system_role.e2d84a56": "Delete system role",
|
||||
"i18n:govoplan-access.delete_value_only_unassigned_tenant_defined_role.e48b13e7": "Delete {value0}? Only unassigned tenant-defined roles can be deleted.",
|
||||
"i18n:govoplan-access.delete_value_the_role_must_have_no_account_assig.020eb657": "Delete {value0}? The role must have no account assignments.",
|
||||
@@ -400,6 +422,7 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-access.dry_run.485a3d15": "Trockenlauf",
|
||||
"i18n:govoplan-access.edit_global_account.d13b8485": "Edit global account",
|
||||
"i18n:govoplan-access.edit_group.edb57d8e": "Gruppe bearbeiten",
|
||||
"i18n:govoplan-access.edit_function_role_mapping.91ee75af": "Funktions-Rollenzuordnung bearbeiten",
|
||||
"i18n:govoplan-access.edit_role.61dd63e9": "Rolle bearbeiten",
|
||||
"i18n:govoplan-access.edit_system_role.6ebb7cb0": "Edit system role",
|
||||
"i18n:govoplan-access.edit_tenant_user.99121a61": "Mandantenbenutzer bearbeiten",
|
||||
@@ -414,6 +437,13 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-access.file_connections.1e362326": "Dateiverbindungen",
|
||||
"i18n:govoplan-access.files_module_unavailable.0ee90db1": "Dateimodul nicht verfügbar",
|
||||
"i18n:govoplan-access.files.6ce6c512": "Dateien",
|
||||
"i18n:govoplan-access.function_fact.4f7435e4": "Funktionsfakt",
|
||||
"i18n:govoplan-access.function_id.e5e08937": "Funktions-ID",
|
||||
"i18n:govoplan-access.function_role_mapping_created.7a25eb5a": "Funktions-Rollenzuordnung erstellt.",
|
||||
"i18n:govoplan-access.function_role_mapping_deleted.fb180786": "Funktions-Rollenzuordnung gelöscht.",
|
||||
"i18n:govoplan-access.function_role_mapping_help.0cf9996a": "Quellmodul und Funktions-ID identifizieren einen akzeptierten externen Funktionsfakt. Access gewährt die ausgewählte Mandantenrolle nur, solange IDM eine aktive akzeptierte Zuweisung für diesen Fakt meldet.",
|
||||
"i18n:govoplan-access.function_role_mapping_updated.76020443": "Funktions-Rollenzuordnung aktualisiert.",
|
||||
"i18n:govoplan-access.function_role_mappings.2b64e9c3": "Funktions-Rollenzuordnungen",
|
||||
"i18n:govoplan-access.general.9239ee2c": "Allgemein",
|
||||
"i18n:govoplan-access.global_account_details.0a0cf240": "Global account details",
|
||||
"i18n:govoplan-access.global_account_value_created.5100e467": "Global account {value0} created.",
|
||||
@@ -476,6 +506,7 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-access.no_api_keys_found.1f377128": "No API keys found.",
|
||||
"i18n:govoplan-access.no_assignable_roles_exist.a4c268c2": "Es gibt keine zuweisbaren Rollen.",
|
||||
"i18n:govoplan-access.no_expiry.39d436aa": "No expiry",
|
||||
"i18n:govoplan-access.no_function_role_mappings_found.f735ff54": "Keine Funktions-Rollenzuordnungen gefunden.",
|
||||
"i18n:govoplan-access.no_global_accounts_found.29d96a9e": "No global accounts found.",
|
||||
"i18n:govoplan-access.no_groups_exist_yet.9cd029f6": "Es gibt noch keine Gruppen.",
|
||||
"i18n:govoplan-access.no_groups_found.627ca913": "No groups found.",
|
||||
@@ -520,6 +551,7 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-access.save_account.0b761f5c": "Save account",
|
||||
"i18n:govoplan-access.save_general_settings.5c90f8c4": "Save general settings",
|
||||
"i18n:govoplan-access.save_group.36ca6865": "Gruppe speichern",
|
||||
"i18n:govoplan-access.save_mapping.a4ac90e9": "Zuordnung speichern",
|
||||
"i18n:govoplan-access.save_role.16fe10d1": "Rolle speichern",
|
||||
"i18n:govoplan-access.save_tenant.9eb2ac74": "Mandant speichern",
|
||||
"i18n:govoplan-access.save_user.0d071b89": "Benutzer speichern",
|
||||
@@ -527,11 +559,13 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-access.saving.ae7e8875": "Saving...",
|
||||
"i18n:govoplan-access.scope.4651a34e": "Geltungsbereich",
|
||||
"i18n:govoplan-access.scopes.c23540e5": "Scopes",
|
||||
"i18n:govoplan-access.select_role.c543f191": "Rolle auswählen",
|
||||
"i18n:govoplan-access.select_user.b8a1d9de": "Select user",
|
||||
"i18n:govoplan-access.set_concrete_system_retention_values_the_allow_o.02e1fd13": "Set concrete system retention values. The Allow override toggles decide which fields tenants, owners and campaigns may override.",
|
||||
"i18n:govoplan-access.settings_for_the_active_tenant_context.ad267b86": "Settings for the active tenant context.",
|
||||
"i18n:govoplan-access.show_revoked.b4265807": "Show revoked",
|
||||
"i18n:govoplan-access.slug.094da9b9": "Slug",
|
||||
"i18n:govoplan-access.source_module.62b7241c": "Quellmodul",
|
||||
"i18n:govoplan-access.status.bae7d5be": "Status",
|
||||
"i18n:govoplan-access.store_it_in_a_secret_manager_only_its_prefix_and.796ac588": "Store it in a secret manager. Only its prefix and hash remain in Multi Seal Mail.",
|
||||
"i18n:govoplan-access.suspend_tenant.151d283a": "Suspend tenant",
|
||||
|
||||
Reference in New Issue
Block a user