From 3b0e8bc6000007d0db91d869d3045897b58b4b50 Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Sat, 11 Jul 2026 02:34:57 +0200 Subject: [PATCH] Release v0.1.7 --- package.json | 4 +- pyproject.toml | 8 +- src/govoplan_idm/backend/manifest.py | 2 +- webui/package.json | 4 +- webui/src/features/IdmPage.tsx | 256 ++++++++++++++------------- 5 files changed, 142 insertions(+), 132 deletions(-) diff --git a/package.json b/package.json index 92ddee8..e163513 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@govoplan/idm-webui", - "version": "0.1.6", + "version": "0.1.7", "private": true, "type": "module", "main": "webui/src/index.ts", @@ -19,7 +19,7 @@ "LICENSE" ], "peerDependencies": { - "@govoplan/core-webui": "^0.1.6", + "@govoplan/core-webui": "^0.1.7", "@vitejs/plugin-react": "^4.3.4", "lucide-react": "^1.23.0", "react": "^19.0.0", diff --git a/pyproject.toml b/pyproject.toml index 61d18af..6648d25 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,15 +4,15 @@ build-backend = "setuptools.build_meta" [project] name = "govoplan-idm" -version = "0.1.6" +version = "0.1.7" description = "GovOPlaN identity management bridge module." readme = "README.md" requires-python = ">=3.12" authors = [{ name = "GovOPlaN" }] dependencies = [ - "govoplan-core>=0.1.6", - "govoplan-identity>=0.1.6", - "govoplan-organizations>=0.1.6", + "govoplan-core>=0.1.7", + "govoplan-identity>=0.1.7", + "govoplan-organizations>=0.1.7", ] [tool.setuptools.packages.find] diff --git a/src/govoplan_idm/backend/manifest.py b/src/govoplan_idm/backend/manifest.py index 6fe66c3..4012964 100644 --- a/src/govoplan_idm/backend/manifest.py +++ b/src/govoplan_idm/backend/manifest.py @@ -102,7 +102,7 @@ def _idm_directory(context: ModuleContext) -> object: manifest = ModuleManifest( id="idm", name="IDM", - version="0.1.6", + version="0.1.7", dependencies=("identity", "organizations"), optional_dependencies=("access", "audit"), required_capabilities=(CAPABILITY_AUTH_PRINCIPAL_RESOLVER, CAPABILITY_AUTH_PERMISSION_EVALUATOR), diff --git a/webui/package.json b/webui/package.json index c6c37df..f8eda85 100644 --- a/webui/package.json +++ b/webui/package.json @@ -1,6 +1,6 @@ { "name": "@govoplan/idm-webui", - "version": "0.1.6", + "version": "0.1.7", "private": true, "type": "module", "main": "src/index.ts", @@ -14,7 +14,7 @@ "./styles/idm.css": "./src/styles/idm.css" }, "peerDependencies": { - "@govoplan/core-webui": "^0.1.6", + "@govoplan/core-webui": "^0.1.7", "@vitejs/plugin-react": "^4.3.4", "lucide-react": "^1.23.0", "react": "^19.0.0", diff --git a/webui/src/features/IdmPage.tsx b/webui/src/features/IdmPage.tsx index b48e859..9866162 100644 --- a/webui/src/features/IdmPage.tsx +++ b/webui/src/features/IdmPage.tsx @@ -1,10 +1,12 @@ import { useCallback, useEffect, useMemo, useRef, useState, type FormEvent } from "react"; -import { Edit3, RefreshCw, Save, XCircle } from "lucide-react"; +import { Edit3, Plus, RefreshCw } from "lucide-react"; import { + AdminIconButton, ApiError, Button, Card, DataGrid, + Dialog, DismissibleAlert, FormField, LoadingFrame, @@ -242,6 +244,8 @@ export default function IdmPage({ settings, auth }: IdmPageProps) { const [assignmentDraft, setAssignmentDraft] = useState(() => emptyAssignmentDraft()); const [settingsDraft, setSettingsDraft] = useState(() => settingsDraftFrom(null)); const [editingAssignmentId, setEditingAssignmentId] = useState(null); + const [assignmentEditorOpen, setAssignmentEditorOpen] = useState(false); + const [assignmentChangeRequestId, setAssignmentChangeRequestId] = useState(""); const [loading, setLoading] = useState(true); const [busy, setBusy] = useState(false); const [error, setError] = useState(""); @@ -298,7 +302,7 @@ export default function IdmPage({ settings, auth }: IdmPageProps) { return Array.from(values).sort((left, right) => left.localeCompare(right)); }, [actingForOptionById, actingForOptions, assignmentDraft.acting_for_account_id, identityOptionById, sourceAssignment]); const initialFunctionFilter = initialQuery.functionId; - const hasDirtyAssignmentDraft = isAssignmentDirty(assignmentDraft); + const hasDirtyAssignmentDraft = assignmentEditorOpen && isAssignmentDirty(assignmentDraft); const hasDirtySettingsDraft = canReadSettings && isSettingsDirty(settingsDraft, idmSettings); const hasDirtyDraft = hasDirtyAssignmentDraft || hasDirtySettingsDraft; @@ -321,12 +325,9 @@ export default function IdmPage({ settings, auth }: IdmPageProps) { appliedInitialQueryRef.current = true; setEditingAssignmentId(initialAssignment.id); setAssignmentDraft(assignmentDraftFrom(initialAssignment)); + setAssignmentEditorOpen(true); } } - if (!appliedInitialQueryRef.current && initialFunctionFilter && nextModel.functions.some((item) => item.id === initialFunctionFilter)) { - appliedInitialQueryRef.current = true; - setAssignmentDraft((draft) => draft.function_id ? draft : { ...draft, function_id: initialFunctionFilter }); - } if (canSearchIdentities) { try { const identities = await searchOrganizationIdentityOptions(settings, "", 100); @@ -410,6 +411,8 @@ export default function IdmPage({ settings, auth }: IdmPageProps) { const discardAssignmentDraft = useCallback(() => { setAssignmentDraft(emptyAssignmentDraft()); setEditingAssignmentId(null); + setAssignmentEditorOpen(false); + setAssignmentChangeRequestId(""); }, []); const discardDrafts = useCallback(() => { @@ -444,14 +447,15 @@ export default function IdmPage({ settings, auth }: IdmPageProps) { setError("i18n:govoplan-idm.function_is_required.5cce5b41"); return false; } - const payload = assignmentPayload(assignmentDraft); + const requestId = textOrNull(assignmentChangeRequestId); + const payload = requestId ? { ...assignmentPayload(assignmentDraft), change_request_id: requestId } : assignmentPayload(assignmentDraft); const ok = await runAction( () => editingAssignmentId ? patchOrganizationFunctionAssignment(settings, editingAssignmentId, payload) : createOrganizationFunctionAssignment(settings, payload), editingAssignmentId ? "i18n:govoplan-idm.assignment_updated.fbbf9bd6" : "i18n:govoplan-idm.assignment_added.91f1ee42" ); if (ok) discardAssignmentDraft(); return ok; - }, [assignmentDraft, canManage, discardAssignmentDraft, editingAssignmentId, runAction, settings]); + }, [assignmentChangeRequestId, assignmentDraft, canManage, discardAssignmentDraft, editingAssignmentId, runAction, settings]); const saveDrafts = useCallback(async (): Promise => { if (hasDirtyAssignmentDraft && !(await submitAssignment())) return false; @@ -467,15 +471,20 @@ export default function IdmPage({ settings, auth }: IdmPageProps) { message: "i18n:govoplan-core.this_page_has_unsaved_changes_save_them_before_l.419a9d8b" }); + const openCreateAssignment = useCallback(() => { + setEditingAssignmentId(null); + setAssignmentDraft({ ...emptyAssignmentDraft(), function_id: initialFunctionFilter && functionById.has(initialFunctionFilter) ? initialFunctionFilter : "" }); + setAssignmentChangeRequestId(""); + setAssignmentEditorOpen(true); + }, [functionById, initialFunctionFilter]); + const editAssignment = useCallback((item: OrganizationFunctionAssignmentItem) => { setEditingAssignmentId(item.id); setAssignmentDraft(assignmentDraftFrom(item)); + setAssignmentChangeRequestId(""); + setAssignmentEditorOpen(true); }, []); - const toggleAssignment = useCallback((item: OrganizationFunctionAssignmentItem) => { - void runAction(() => patchOrganizationFunctionAssignment(settings, item.id, { is_active: !item.is_active })); - }, [runAction, settings]); - function onIdentityChange(identityId: string) { const identity = identityOptionById.get(identityId); setAssignmentDraft({ @@ -551,16 +560,11 @@ export default function IdmPage({ settings, auth }: IdmPageProps) { { id: "actions", header: "", - width: 220, + width: 88, sticky: "end", render: (row) => (
- - +
) } @@ -577,16 +581,6 @@ export default function IdmPage({ settings, auth }: IdmPageProps) { - {hasDirtyDraft && ( - <> - - - - )} @@ -595,98 +589,9 @@ export default function IdmPage({ settings, auth }: IdmPageProps) { {!canManage && i18n:govoplan-idm.write_permission_required.c7dde7c6} -
-
- -
void submitAssignment(event)}> - - setIdentitySearch(event.target.value)} - /> - - - - - - - - - - - - - - {(assignmentDraft.source === "delegated" || assignmentDraft.source === "acting_for") && ( - - - - )} - {assignmentDraft.source === "acting_for" && ( - <> - - setActingForSearch(event.target.value)} - /> - - - - - - )} -
- - -
- {!identityLookupAvailable &&

i18n:govoplan-idm.identity_lookup_unavailable.b76f7714

} - {identityLoading &&

i18n:govoplan-idm.loading_identities.f3b84693

} - {actingForLoading &&

i18n:govoplan-idm.loading_acting_for_accounts.c9894b1e

} - {!model.functions.length &&

i18n:govoplan-idm.no_functions_available.51ba08eb

} -
- - {editingAssignmentId && ( - - )} -
-
-
- - {canReadSettings && ( - +
+ {canReadSettings && ( +
{ event.preventDefault(); void submitSettings(); }}>
- +
+ {renderAssignmentDialog()}
); + + function renderAssignmentDialog() { + const formId = "idm-assignment-editor"; + return ( + !busy && discardAssignmentDraft()} + closeDisabled={busy} + className="admin-dialog admin-dialog-wide idm-editor-dialog" + footer={( + <> + + + + )} + > +
void submitAssignment(event)}> + + setIdentitySearch(event.target.value)} + /> + + + + + + + + + + + + + + {(assignmentDraft.source === "delegated" || assignmentDraft.source === "acting_for") && ( + + + + )} + {assignmentDraft.source === "acting_for" && ( + <> + + setActingForSearch(event.target.value)} + /> + + + + + + )} +
+ + +
+
+ + setAssignmentChangeRequestId(event.target.value)} placeholder="cfgreq-..." disabled={busy} /> + +

i18n:govoplan-idm.change_request_id_help.cc7de508

+
+ {!identityLookupAvailable &&

i18n:govoplan-idm.identity_lookup_unavailable.b76f7714

} + {identityLoading &&

i18n:govoplan-idm.loading_identities.f3b84693

} + {actingForLoading &&

i18n:govoplan-idm.loading_acting_for_accounts.c9894b1e

} + {!model.functions.length &&

i18n:govoplan-idm.no_functions_available.51ba08eb

} +
+
+ ); + } }