diff --git a/src/components/Members/AddModalBySelector.tsx b/src/components/Members/AddModalBySelector.tsx new file mode 100644 index 00000000..cfcfd409 --- /dev/null +++ b/src/components/Members/AddModalBySelector.tsx @@ -0,0 +1,235 @@ +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[] + ) => { + 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) + ); + 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 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..479d6f09 --- /dev/null +++ b/src/components/Members/MembersUserIDOverrides.tsx @@ -0,0 +1,384 @@ +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]); + + // Computed "states" + const someItemSelected = idOverridesSelected.length > 0; + const showTableRows = idOverrides.length > 0; + 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 User group " + 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 User group '" + 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 User group " + props.id} + ariaLabel={"Add User group of User ID Override modal"} + spinning={spinning} + /> + )} + {showDeleteModal && someItemSelected && ( + setShowDeleteModal(false)} + title={"Delete User group 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 9f8dc0ba..0a90c3ed 100644 --- a/src/components/tables/MembershipTable.tsx +++ b/src/components/tables/MembershipTable.tsx @@ -13,6 +13,7 @@ import { Role, SudoRule, SubId, + UserIDOverride, } from "src/utils/datatypes/globalDataTypes"; // Components import SkeletonOnTableLayout from "../layouts/Skeleton/SkeletonOnTableLayout"; @@ -36,7 +37,8 @@ type EntryDataTypes = | SudoRule | User | UserGroup - | string; // external + | string // external + | UserIDOverride; // idoverrideuser type FromTypes = | "active-users" @@ -48,7 +50,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 87277991..0730ca74 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 306c26a9..3528bcba 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; @@ -58,6 +59,9 @@ const UserGroupsMembers = (props: PropsToUserGroupsMembers) => { const [serviceDirection, setServiceDirection] = React.useState( "direct" as MembershipDirection ); + const [overrideDirection, setOverrideDirection] = React.useState( + "direct" as MembershipDirection + ); const updateUserDirection = (direction: MembershipDirection) => { if (direction === "direct") { @@ -103,6 +107,22 @@ const UserGroupsMembers = (props: PropsToUserGroupsMembers) => { } setServiceDirection(direction); }; + const updateUserIdOverrideDirection = (direction: MembershipDirection) => { + if (direction === "direct") { + setOverrideCount( + userGroup && userGroup.member_idoverrideuser + ? userGroup.member_idoverrideuser.length + : 0 + ); + } else { + setOverrideCount( + userGroup && userGroup.memberindirect_idoverrideuser + ? userGroup.memberindirect_idoverrideuser.length + : 0 + ); + } + setOverrideDirection(direction); + }; React.useEffect(() => { if (userDirection === "direct") { @@ -271,7 +291,7 @@ const UserGroupsMembers = (props: PropsToUserGroupsMembers) => { /> @@ -281,7 +301,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 af3f8871..0c95f8cb 100644 --- a/src/utils/datatypes/globalDataTypes.ts +++ b/src/utils/datatypes/globalDataTypes.ts @@ -528,3 +528,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; +}