From b1325e8efe1bc835b3c9eca893f5dd2150cd4338 Mon Sep 17 00:00:00 2001 From: Carla Martinez Date: Tue, 10 Sep 2024 16:51:18 +0200 Subject: [PATCH] Implement User ID Overrides The 'User groups' > 'Members' > 'User ID Overrides' section needs to be implemented and its components adapted to reflect the same behavior as in the modern WebUI (e.g., its 'Add' modal is slightly different than in other sections). Signed-off-by: Carla Martinez --- src/components/Members/AddModalBySelector.tsx | 234 +++++++++++ .../Members/MembersUserIDOverrides.tsx | 395 ++++++++++++++++++ src/components/tables/MembershipTable.tsx | 7 +- src/navigation/AppRoutes.tsx | 4 + src/pages/UserGroups/UserGroupsMembers.tsx | 19 +- src/services/rpcIDViews.ts | 18 + src/services/rpcUserGroups.ts | 8 +- src/services/rpcUserIdOverrides.tsx | 102 +++++ src/utils/datatypes/globalDataTypes.ts | 19 + src/utils/userIdOverrideUtils.tsx | 63 +++ 10 files changed, 863 insertions(+), 6 deletions(-) create mode 100644 src/components/Members/AddModalBySelector.tsx create mode 100644 src/components/Members/MembersUserIDOverrides.tsx create mode 100644 src/services/rpcUserIdOverrides.tsx create mode 100644 src/utils/userIdOverrideUtils.tsx diff --git a/src/components/Members/AddModalBySelector.tsx b/src/components/Members/AddModalBySelector.tsx new file mode 100644 index 00000000..330d066c --- /dev/null +++ b/src/components/Members/AddModalBySelector.tsx @@ -0,0 +1,234 @@ +import React, { ReactNode } from "react"; +// PatternFly +import { + Button, + DualListSelector, + Form, + FormGroup, + MenuToggle, + MenuToggleElement, + Modal, + Select, + SelectOption, +} from "@patternfly/react-core"; +// Utils +import { AvailableItems } from "../MemberOf/MemberOfAddModal"; +import { UserIDOverride } from "src/utils/datatypes/globalDataTypes"; +import { useGetUserIdOverridesInfoByIdViewMutation } from "src/services/rpcUserIdOverrides"; + +export interface PropsToAdd { + showModal: boolean; + onCloseModal: () => void; + availableItemsSelector: AvailableItems[]; + onAdd: (items: AvailableItems[]) => void; + onSearchTextChange: (searchText: string) => void; + title: string; + ariaLabel: string; + spinning: boolean; +} + +const AddModalBySelector = (props: PropsToAdd) => { + // API call + const [getUidOverrides] = useGetUserIdOverridesInfoByIdViewMutation(); + + // List of User Id Overrides associated to the selected id view + const [uidOverridesList, setUidOverridesList] = React.useState< + UserIDOverride[] + >([]); + + // Selector data + const availableOptionsSelector = props.availableItemsSelector.map( + (d) => d.key + ); + + // Selector + const [isSelectorOpen, setIsSelectorOpen] = React.useState(false); + const [idViewSelected, setIdViewSelected] = React.useState( + props.availableItemsSelector[0].title || "" + ); // By default: first item + + const serviceOnToggle = () => { + setIsSelectorOpen(!isSelectorOpen); + }; + + const toggleIDView = (toggleRef: React.Ref) => ( + + {idViewSelected} + + ); + + const onChangeSelector = (_event, value) => { + setIdViewSelected(value as string); + setIsSelectorOpen(false); + }; + + // Perform API call to get available idOverride data when the selector's value changes + React.useEffect(() => { + getUidOverrides(idViewSelected).then((result) => { + if ("data" in result) { + setUidOverridesList(result.data); + setAvailableOptionsDualList(result.data); + setChosenOptionsDualList([]); + } + }); + }, [idViewSelected]); + + // reset dialog on close + React.useEffect(() => { + if (!props.showModal) { + cleanData(); + } + }, [props.showModal]); + + const listChange = ( + newAvailableOptions: ReactNode[], + newChosenOptions: ReactNode[] + ) => { + console.log("newAvailableOptions: ", newAvailableOptions); + console.log("newChosenOptions: ", newChosenOptions); + setAvailableOptionsDualList(newAvailableOptions.sort()); + setChosenOptionsDualList(newChosenOptions.sort()); + }; + + // Manage data shown in Dual list selector + const getAvailableOptionsDualListSelector = () => { + const result = uidOverridesList.map((item) => item.ipaoriginaluid); + return result as ReactNode[]; + }; + + // Dual list data + const [availableOptionsDualList, setAvailableOptionsDualList] = + React.useState(getAvailableOptionsDualListSelector()); + const [chosenOptionsDualList, setChosenOptionsDualList] = React.useState< + ReactNode[] + >([]); + + const cleanData = () => { + setAvailableOptionsDualList(getAvailableOptionsDualListSelector()); + setChosenOptionsDualList([]); + }; + + // Update available and chosen options when props.availableItemsSelector changes + React.useEffect(() => { + const newAval = uidOverridesList.filter( + (d) => !chosenOptionsDualList.includes(d.ipaoriginaluid) + ); + console.log("newAval: ", newAval); + setAvailableOptionsDualList(newAval.map((item) => item.ipaoriginaluid)); + }, [uidOverridesList]); + + const fields = [ + { + id: "selector", + name: "Selector", + pfComponent: ( + + ), + }, + { + id: "dual-list-selector", + name: "Available options", + pfComponent: ( + + props.onSearchTextChange(searchText) + } + onListChange={( + _event, + newAvailableOptions: ReactNode[], + newChosenOptions: ReactNode[] + ) => listChange(newAvailableOptions, newChosenOptions)} + id="basicSelectorWithSearch" + /> + ), + }, + ]; + + // Buttons are disabled until the user fills the required fields + const [buttonDisabled, setButtonDisabled] = React.useState(true); + + React.useEffect(() => { + if (chosenOptionsDualList.length > 0) { + setButtonDisabled(false); + } else { + setButtonDisabled(true); + } + }, [chosenOptionsDualList]); + + // Add group option + const onClickAddHandler = () => { + const optionsToAdd: AvailableItems[] = []; + chosenOptionsDualList.map((opt) => { + optionsToAdd.push({ + key: opt as string, + title: opt as string, + }); + }); + props.onAdd(optionsToAdd); + setChosenOptionsDualList([]); + props.onCloseModal(); + }; + + // Buttons that will be shown at the end of the form + const modalActions = [ + , + , + ]; + + return ( + +
+ {fields.map((field) => ( + + {field.pfComponent} + + ))} +
+
+ ); +}; + +export default AddModalBySelector; diff --git a/src/components/Members/MembersUserIDOverrides.tsx b/src/components/Members/MembersUserIDOverrides.tsx new file mode 100644 index 00000000..11390412 --- /dev/null +++ b/src/components/Members/MembersUserIDOverrides.tsx @@ -0,0 +1,395 @@ +import React from "react"; +// PatternFly +import { Pagination, PaginationVariant } from "@patternfly/react-core"; +// Components +import MemberOfToolbar, { + MembershipDirection, +} from "../MemberOf/MemberOfToolbar"; +import { AvailableItems } from "../MemberOf/MemberOfAddModal"; +import AddModalBySelector from "./AddModalBySelector"; +import MemberOfDeleteModal from "../MemberOf/MemberOfDeleteModal"; +import MemberTable from "src/components/tables/MembershipTable"; +// Data types +import { UserGroup, UserIDOverride } from "src/utils/datatypes/globalDataTypes"; +// Hooks +import useAlerts from "src/hooks/useAlerts"; +import useListPageSearchParams from "src/hooks/useListPageSearchParams"; +// RPC +import { ErrorResult } from "src/services/rpc"; +import { + MemberPayload, + useAddAsMemberMutation, + useRemoveAsMemberMutation, +} from "src/services/rpcUserGroups"; +import { useGetUserIdOverridesInfoByUidQuery } from "src/services/rpcUserIdOverrides"; +import { useGetIDViewsQuery } from "src/services/rpcIDViews"; +// Utils +import { paginate } from "src/utils/utils"; + +interface PropsToUserIDOverrides { + entity: Partial; + id: string; + from: string; + isDataLoading: boolean; + onRefreshData: () => void; + member_idoverrideuser: string[]; + memberindirect_idoverrideuser: string[]; + membershipDisabled?: boolean; + setDirection: (direction: MembershipDirection) => void; + direction: MembershipDirection; +} + +const MembersUserIDOverrides = (props: PropsToUserIDOverrides) => { + // Alerts to show in the UI + const alerts = useAlerts(); + + const membershipDisabled = + props.membershipDisabled === undefined ? false : props.membershipDisabled; + + // Get parameters from URL + const { + page, + setPage, + perPage, + setPerPage, + searchValue, + setSearchValue, + membershipDirection, + setMembershipDirection, + } = useListPageSearchParams(); + + // Other states + const [idOverridesSelected, setIdOverridesSelected] = React.useState< + string[] + >([]); + const [indirectIdOverridesSelected, setindirectIdOverridesSelected] = + React.useState([]); + + // Loaded ID overrides based on paging and member attributes + const [idOverrides, setIdOverrides] = React.useState([]); + + // Choose the correct ID overrides based on the membership direction + const member_idoverrideuser = props.member_idoverrideuser || []; + const memberindirect_idoverrideuser = + props.memberindirect_idoverrideuser || []; + let idoverrideNames = + membershipDirection === "direct" + ? member_idoverrideuser + : memberindirect_idoverrideuser; + idoverrideNames = [...idoverrideNames]; + + const getIdOverridesNameToLoad = (): string[] => { + let toLoad = [...idoverrideNames]; + toLoad.sort(); + + // Filter by search + if (searchValue) { + toLoad = toLoad.filter((name) => + name.toLowerCase().includes(searchValue.toLowerCase()) + ); + } + + // Apply paging + toLoad = paginate(toLoad, page, perPage); + + return toLoad; + }; + + const [idOverrideNamesToLoad, setIdOverrideNamesToLoad] = React.useState< + string[] + >(getIdOverridesNameToLoad()); + + // Load idOverrides + const fullIdOverridesQuery = useGetUserIdOverridesInfoByUidQuery( + idOverrideNamesToLoad + ); + + // Refresh ID Overrides + React.useEffect(() => { + const idOverridesNames = getIdOverridesNameToLoad(); + setIdOverrideNamesToLoad(idOverridesNames); + props.setDirection(membershipDirection); + }, [props.entity, membershipDirection, searchValue, page, perPage]); + + React.useEffect(() => { + setMembershipDirection(props.direction); + }, [props.entity]); + + React.useEffect(() => { + if (idOverrideNamesToLoad.length > 0) { + fullIdOverridesQuery.refetch(); + } + }, [idOverrideNamesToLoad]); + + // Update ID Overrides + React.useEffect(() => { + if (fullIdOverridesQuery.data && !fullIdOverridesQuery.isFetching) { + setIdOverrides(fullIdOverridesQuery.data); + } + }, [fullIdOverridesQuery.data, fullIdOverridesQuery.isFetching]); + + // Get type of the entity to show as text + const getEntityType = () => { + if (props.from === "user-groups") { + return "user group"; + } else { + // Return 'group' as default + return "group"; + } + }; + + // Computed "states" + const someItemSelected = idOverridesSelected.length > 0; + const showTableRows = idOverrides.length > 0; + const entityType = getEntityType(); + const idOverrideColumnNames = ["User to override"]; + const idOverrideProperties = ["uid"]; + + // Dialogs and actions + const [showAddModal, setShowAddModal] = React.useState(false); + const [showDeleteModal, setShowDeleteModal] = React.useState(false); + const [spinning, setSpinning] = React.useState(false); + + // Buttons functionality + const isRefreshButtonEnabled = + !fullIdOverridesQuery.isFetching && !props.isDataLoading; + const isDeleteEnabled = + someItemSelected && membershipDirection !== "indirect"; + const isAddButtonEnabled = + membershipDirection !== "indirect" && isRefreshButtonEnabled; + + // Add new member to 'IdOverride' + // API calls + const [addMemberToIdOverride] = useAddAsMemberMutation(); + const [removeMembersFromIdOverrides] = useRemoveAsMemberMutation(); + const [selectorValue, setSelectorValue] = React.useState(""); + const [availableIdViews, setAvailableIdViews] = React.useState([]); + const [availableItems, setAvailableItems] = React.useState( + [] + ); + + // Load available ID Overrides, delay the search for opening the modal + const idViewsQuery = useGetIDViewsQuery(); + + // Trigger available ID Views + React.useEffect(() => { + if (showAddModal) { + idViewsQuery.refetch(); + } + }, [showAddModal, selectorValue, props.entity]); + + // Update available ID Overrides + React.useEffect(() => { + if (idViewsQuery.data && !idViewsQuery.isFetching) { + // Transform data to AvailableItems data type + const count = idViewsQuery.data.length; + const results = idViewsQuery.data; + let items: AvailableItems[] = []; + const avalIdViews: string[] = []; + for (let i = 0; i < count; i++) { + const idView = results[i]; + avalIdViews.push(results[i]); + items.push({ + key: idView, + title: idView, + }); + } + + items = items.filter((item) => !idoverrideNames.includes(item.key)); + + setAvailableIdViews(avalIdViews); + setAvailableItems(items); + } + }, [idViewsQuery.data, idViewsQuery.isFetching]); + + // Add + const onAddIdOverride = (items: AvailableItems[]) => { + const newIdOverrideNames = items.map((item) => item.key); + if (props.id === undefined || newIdOverrideNames.length == 0) { + return; + } + + const payload = { + userGroup: props.id, + entityType: "idoverrideuser", + idsToAdd: newIdOverrideNames, + } as MemberPayload; + + setSpinning(true); + addMemberToIdOverride(payload).then((response) => { + if ("data" in response) { + if (response.data.result) { + // Set alert: success + alerts.addAlert( + "add-member-success", + "Assigned new User IDs to " + entityType + " " + props.id, + "success" + ); + // Refresh data + props.onRefreshData(); + // Close modal + setShowAddModal(false); + } else if (response.data.error) { + // Set alert: error + const errorMessage = response.data.error as unknown as ErrorResult; + alerts.addAlert("add-member-error", errorMessage.message, "danger"); + } + } + setSpinning(false); + }); + }; + + // Delete + const onDeleteIdOverride = () => { + const payload = { + userGroup: props.id, + entityType: "idoverrideuser", + idsToAdd: idOverridesSelected, + } as MemberPayload; + + setSpinning(true); + removeMembersFromIdOverrides(payload).then((response) => { + if ("data" in response) { + if (response.data.result) { + // Set alert: success + alerts.addAlert( + "remove-id-overrides-success", + "Removed User IDs from " + entityType + " '" + props.id + "'", + "success" + ); + // Refresh + props.onRefreshData(); + // Close modal + setShowDeleteModal(false); + // Back to page 1 + setPage(1); + } else if (response.data.error) { + // Set alert: error + const errorMessage = response.data.error as unknown as ErrorResult; + alerts.addAlert( + "remove-id-overrides-error", + errorMessage.message, + "danger" + ); + } + } + setSpinning(false); + }); + }; + + return ( + <> + + {membershipDisabled ? ( + {}} + refreshButtonEnabled={isRefreshButtonEnabled} + onRefreshButtonClick={props.onRefreshData} + deleteButtonEnabled={isDeleteEnabled} + onDeleteButtonClick={() => setShowDeleteModal(true)} + addButtonEnabled={isAddButtonEnabled} + onAddButtonClick={() => setShowAddModal(true)} + helpIconEnabled={true} + totalItems={idoverrideNames.length} + perPage={perPage} + page={page} + onPerPageChange={setPerPage} + onPageChange={setPage} + /> + ) : ( + {}} + refreshButtonEnabled={isRefreshButtonEnabled} + onRefreshButtonClick={props.onRefreshData} + deleteButtonEnabled={ + membershipDirection === "direct" + ? idOverridesSelected.length > 0 + : indirectIdOverridesSelected.length > 0 + } + onDeleteButtonClick={() => setShowDeleteModal(true)} + addButtonEnabled={isAddButtonEnabled} + onAddButtonClick={() => setShowAddModal(true)} + membershipDirectionEnabled={true} + membershipDirection={membershipDirection} + onMembershipDirectionChange={setMembershipDirection} + helpIconEnabled={true} + totalItems={idoverrideNames.length} + perPage={perPage} + page={page} + onPerPageChange={setPerPage} + onPageChange={setPage} + /> + )} + + setPage(page)} + onPerPageSelect={(_e, perPage) => setPerPage(perPage)} + /> + {showAddModal && ( + setShowAddModal(false)} + availableItemsSelector={availableItems} + onAdd={onAddIdOverride} + onSearchTextChange={setSelectorValue} + title={"Assign User ID Overrides to " + entityType + " " + props.id} + ariaLabel={"Add " + entityType + " of User ID Override modal"} + spinning={spinning} + /> + )} + {showDeleteModal && someItemSelected && ( + setShowDeleteModal(false)} + title={"Delete " + entityType + " from User ID Overrides"} + onDelete={onDeleteIdOverride} + spinning={spinning} + > + + membershipDirection === "direct" + ? idOverridesSelected.includes(idOverride) + : indirectIdOverridesSelected.includes(idOverride) + )} + idKey="uid" + from="idoverrideuser" + columnNamesToShow={idOverrideColumnNames} + propertiesToShow={idOverrideProperties} + showTableRows + /> + + )} + + ); +}; + +export default MembersUserIDOverrides; diff --git a/src/components/tables/MembershipTable.tsx b/src/components/tables/MembershipTable.tsx index 1406aefe..389a4c1e 100644 --- a/src/components/tables/MembershipTable.tsx +++ b/src/components/tables/MembershipTable.tsx @@ -12,6 +12,7 @@ import { Role, SudoRule, SubId, + UserIDOverride, } from "src/utils/datatypes/globalDataTypes"; // Components import SkeletonOnTableLayout from "../layouts/Skeleton/SkeletonOnTableLayout"; @@ -34,7 +35,8 @@ type EntryDataTypes = | SudoRule | User | UserGroup - | string; // external + | string // external + | UserIDOverride; // idoverrideuser type FromTypes = | "active-users" @@ -45,7 +47,8 @@ type FromTypes = | "services" | "sudo-rules" | "user-groups" - | "external"; + | "external" + | "idoverrideuser"; export interface MemberTableProps { entityList: EntryDataTypes[]; // More types can be added here diff --git a/src/navigation/AppRoutes.tsx b/src/navigation/AppRoutes.tsx index 1e164dad..b38cc05b 100644 --- a/src/navigation/AppRoutes.tsx +++ b/src/navigation/AppRoutes.tsx @@ -173,6 +173,10 @@ export const AppRoutes = ({ isInitialDataLoaded }): React.ReactElement => { path="member_external" element={} /> + } + /> } diff --git a/src/pages/UserGroups/UserGroupsMembers.tsx b/src/pages/UserGroups/UserGroupsMembers.tsx index eb4611c5..914f7858 100644 --- a/src/pages/UserGroups/UserGroupsMembers.tsx +++ b/src/pages/UserGroups/UserGroupsMembers.tsx @@ -17,6 +17,7 @@ import MembersUsers from "src/components/Members/MembersUsers"; import MembersUserGroups from "src/components/Members/MembersUserGroups"; import MembersServices from "src/components/Members/MembersServices"; import MembersExternal from "src/components/Members/MembersExternal"; +import MembersUserIDOverrides from "src/components/Members/MembersUserIDOverrides"; interface PropsToUserGroupsMembers { userGroup: UserGroup; @@ -271,7 +272,7 @@ const UserGroupsMembers = (props: PropsToUserGroupsMembers) => { /> @@ -281,7 +282,21 @@ const UserGroupsMembers = (props: PropsToUserGroupsMembers) => { } - > + > + + ); diff --git a/src/services/rpcIDViews.ts b/src/services/rpcIDViews.ts index 77b68440..aa123cd7 100644 --- a/src/services/rpcIDViews.ts +++ b/src/services/rpcIDViews.ts @@ -41,8 +41,25 @@ export type ViewFullData = { idView?: Partial; }; +export interface FindIdViewResult { + cn: string; + dn: string; +} + const extendedApi = api.injectEndpoints({ endpoints: (build) => ({ + getIDViews: build.query({ + query: () => { + return getCommand({ + method: "idview_find", + params: [[], { version: API_VERSION_BACKUP }], + }); + }, + transformResponse: (response: FindRPCResponse): string[] => { + const views = response.result.result as unknown as FindIdViewResult[]; + return views.map((view) => view.cn[0]); + }, + }), getIDViewsFullData: build.query({ query: (viewId) => { // Prepare search parameters @@ -200,6 +217,7 @@ export const useGettingIDViewsQuery = (payloadData) => { }; export const { + useGetIDViewsQuery, useAddIDViewMutation, useRemoveIDViewsMutation, useGetIDViewInfoByNameQuery, diff --git a/src/services/rpcUserGroups.ts b/src/services/rpcUserGroups.ts index d73a127f..f5abef95 100644 --- a/src/services/rpcUserGroups.ts +++ b/src/services/rpcUserGroups.ts @@ -352,14 +352,18 @@ const extendedApi = api.injectEndpoints({ removeAsMember: build.mutation({ query: (payload) => { const userGroup = payload.userGroup; - const idsToAdd = payload.idsToAdd; + const idsToRemove = payload.idsToAdd; const memberType = payload.entityType; return getCommand({ method: "group_remove_member", params: [ [userGroup], - { all: true, [memberType]: idsToAdd, version: API_VERSION_BACKUP }, + { + all: true, + [memberType]: idsToRemove, + version: API_VERSION_BACKUP, + }, ], }); }, diff --git a/src/services/rpcUserIdOverrides.tsx b/src/services/rpcUserIdOverrides.tsx new file mode 100644 index 00000000..7a978288 --- /dev/null +++ b/src/services/rpcUserIdOverrides.tsx @@ -0,0 +1,102 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { + api, + Command, + getBatchCommand, + getCommand, + BatchRPCResponse, + FindRPCResponse, +} from "./rpc"; +import { API_VERSION_BACKUP } from "../utils/utils"; +import { apiToUserIDOverride } from "src/utils/userIdOverrideUtils"; +import { UserIDOverride } from "src/utils/datatypes/globalDataTypes"; + +/** + * User ID override-related endpoints: getUserIdOverride, addUserIdOverride, removeUserIdOverride + * + * API commands: + * - user_idoverride_show: https://freeipa.readthedocs.io/en/latest/api/user_idoverride_show.html + * - user_idoverride_add: https://freeipa.readthedocs.io/en/latest/api/user_idoverride_add.html + * - user_idoverride_del: https://freeipa.readthedocs.io/en/latest/api/user_idoverride_del.html + * - user_idoverride_find: https://freeipa.readthedocs.io/en/latest/api/user_idoverride_find.html + */ +const extendedApi = api.injectEndpoints({ + endpoints: (build) => ({ + /** + * Given a list of User ID overrides, show the full data of those services + * @param {string[]} - Payload with service IDs and options + * @returns {BatchRPCResponse} - Batch response + */ + getUserIdOverridesInfoByUid: build.query({ + query: (userIDOverridesList) => { + const serviceShowCommands: Command[] = userIDOverridesList.map( + (userIDOverride) => ({ + method: "idoverrideuser_show", + params: [[userIDOverride], { no_members: true }], + }) + ); + return getBatchCommand(serviceShowCommands, API_VERSION_BACKUP); + }, + transformResponse: (response: BatchRPCResponse): UserIDOverride[] => { + const serviceList: UserIDOverride[] = []; + const results = response.result.results; + const count = response.result.count; + for (let i = 0; i < count; i++) { + const serviceData = apiToUserIDOverride(results[i].result); + serviceList.push(serviceData); + } + return serviceList; + }, + }), + /** + * Given a group ID, show the User ID overrides associated with it + * @param {string} - Group ID + * @returns {FindRPCResponse} - Find response + */ + getUserIdOverridesInfoByGroup: build.query({ + query: (groupId) => { + return getCommand({ + method: "idoverrideuser_find", + params: [ + [groupId], + { no_members: true, version: API_VERSION_BACKUP }, + ], + }); + }, + }), + /** + * Given a ID View, show the User ID overrides associated with it + * @param {string} - ID View name + * @returns {FindRPCResponse} - Find response + */ + getUserIdOverridesInfoByIdView: build.mutation({ + query: (IDView) => { + return getCommand({ + method: "idoverrideuser_find", + params: [[IDView], { no_members: true, version: API_VERSION_BACKUP }], + }); + }, + transformResponse: (response: FindRPCResponse): UserIDOverride[] => { + const userIdOverrideList: UserIDOverride[] = []; + if (response.result.result !== undefined) { + const results = response.result.result; + const count = response.result.count; + for (let i = 0; i < count; i++) { + const idOverrideData = apiToUserIDOverride( + results[i] as Record + ); + userIdOverrideList.push(idOverrideData); + } + } + return userIdOverrideList; + }, + }), + }), + overrideExisting: false, +}); + +export const { + useGetUserIdOverridesInfoByUidQuery, + useGetUserIdOverridesInfoByGroupQuery, + useGetUserIdOverridesInfoByIdViewMutation, +} = extendedApi; diff --git a/src/utils/datatypes/globalDataTypes.ts b/src/utils/datatypes/globalDataTypes.ts index bca88acf..1d0abaa6 100644 --- a/src/utils/datatypes/globalDataTypes.ts +++ b/src/utils/datatypes/globalDataTypes.ts @@ -522,3 +522,22 @@ export interface SubId { export interface DNSZone { idnsname: string; } + +export interface UserIDOverride { + description: string; + gecos: string; + gidnumber: string; + homedirectory: string; + ipaanchoruuid: string; + ipaoriginaluid: string; + ipasshpubkey: string[]; + loginshell: string; + memberof_group: string[]; + memberofindirect_group: string[]; + memberof_role: string[]; + memberofindirect_role: string[]; + objectclass: string[]; + uid: string; + uidnumber: string; + usercertificate: string[]; +} diff --git a/src/utils/userIdOverrideUtils.tsx b/src/utils/userIdOverrideUtils.tsx new file mode 100644 index 00000000..7a615594 --- /dev/null +++ b/src/utils/userIdOverrideUtils.tsx @@ -0,0 +1,63 @@ +// Data types +import { UserIDOverride } from "src/utils/datatypes/globalDataTypes"; +// Utils +import { convertApiObj } from "./ipaObjectUtils"; + +const simpleValues = new Set([ + "description", + "dn", + "gecos", + "gidnumber", + "homedirectory", + "ipaanchoruuid", + "ipaoriginaluid", + "loginshell", + "uid", + "uidnumber", +]); + +const dateValues = new Set([]); + +export function apiToUserIDOverride( + apiRecord: Record +): UserIDOverride { + const converted = convertApiObj( + apiRecord, + simpleValues, + dateValues + ) as Partial; + return partialUserIDOverrideToUserIDOverride(converted) as UserIDOverride; +} + +export function partialUserIDOverrideToUserIDOverride( + partialUserIDOverride: Partial +): UserIDOverride { + return { + ...createEmptyUserIDOverride(), + ...partialUserIDOverride, + }; +} + +// Get empty User object initialized with default values +export function createEmptyUserIDOverride(): UserIDOverride { + const userIdOverride: UserIDOverride = { + description: "", + gecos: "", + gidnumber: "", + homedirectory: "", + ipaanchoruuid: "", + ipaoriginaluid: "", + ipasshpubkey: [], + loginshell: "", + memberof_group: [], + memberofindirect_group: [], + memberof_role: [], + memberofindirect_role: [], + objectclass: [], + uid: "", + uidnumber: "", + usercertificate: [], + }; + + return userIdOverride; +}