From a01c4eaa3d2b60b390d9e8f12f335fce41192a16 Mon Sep 17 00:00:00 2001 From: Carla Martinez Date: Wed, 31 Jan 2024 09:59:40 +0100 Subject: [PATCH] Replace dummy data by real data from API The dummy data must be replaced by the data from the API for the 'User groups' only. Signed-off-by: Carla Martinez --- src/hooks/useUserMemberOfData.tsx | 65 +++ src/pages/ActiveUsers/UserMemberOf.tsx | 716 +++++++++++++------------ src/services/rpc.ts | 83 +++ src/store/Identity/userGroups-slice.ts | 7 +- src/store/Identity/userGroups.json | 22 - src/utils/data/GroupRepositories.ts | 74 --- 6 files changed, 528 insertions(+), 439 deletions(-) create mode 100644 src/hooks/useUserMemberOfData.tsx delete mode 100644 src/store/Identity/userGroups.json diff --git a/src/hooks/useUserMemberOfData.tsx b/src/hooks/useUserMemberOfData.tsx new file mode 100644 index 000000000..79e3eb2c1 --- /dev/null +++ b/src/hooks/useUserMemberOfData.tsx @@ -0,0 +1,65 @@ +// RPC +import React from "react"; +import { BatchRPCResponse, useGetUserGroupsQuery } from "src/services/rpc"; +// Data types +import { UserGroupNew } from "src/utils/datatypes/globalDataTypes"; +// Utils +import { API_VERSION_BACKUP, normalizeString } from "src/utils/utils"; + +type MemberOfData = { + isLoading: boolean; + isFetching: boolean; + refetch: () => void; + userGroupsFullList: UserGroupNew[]; +}; + +const useUserMemberOfData = (): MemberOfData => { + // [API call] User groups + // TODO: Normalize data to prevent array of arrays + const userGroupsQuery = useGetUserGroupsQuery({ + searchValue: "", + sizeLimit: 0, + apiVersion: API_VERSION_BACKUP, + }); + + const [userGroupsFullList, setUserGroupsFullList] = React.useState< + UserGroupNew[] + >([]); + const userGroupsData = userGroupsQuery.data || {}; + const isUserGroupsLoading = userGroupsQuery.isLoading; + + React.useEffect(() => { + if (userGroupsData !== undefined && !userGroupsQuery.isFetching) { + const dataParsed = userGroupsData as BatchRPCResponse; + const count = dataParsed.result.count; + const results = dataParsed.result.results; + + const userGroupsTempList: UserGroupNew[] = []; + + for (let i = 0; i < count; i++) { + userGroupsTempList.push({ + cn: normalizeString(results[i].result.cn), + gidnumber: normalizeString(results[i].result.gidnumber), + description: normalizeString(results[i].result.description), + dn: results[i].result.dn, + }); + } + setUserGroupsFullList(userGroupsTempList); + } + }, [userGroupsData, userGroupsQuery.isFetching]); + + // [API call] Refresh + const refetch = () => { + userGroupsQuery.refetch(); + }; + + // Return data + return { + isFetching: userGroupsQuery.isFetching, + isLoading: isUserGroupsLoading, + refetch, + userGroupsFullList, + } as MemberOfData; +}; + +export { useUserMemberOfData }; diff --git a/src/pages/ActiveUsers/UserMemberOf.tsx b/src/pages/ActiveUsers/UserMemberOf.tsx index 79ef46568..b9efadac9 100644 --- a/src/pages/ActiveUsers/UserMemberOf.tsx +++ b/src/pages/ActiveUsers/UserMemberOf.tsx @@ -12,22 +12,22 @@ import { } from "@patternfly/react-core"; // Others import MemberOfToolbar from "src/components/MemberOf/MemberOfToolbar"; +import MemberOfToolbarNew from "src/components/MemberOf/MemberOfToolbarNew"; import MemberOfTable from "src/components/MemberOf/MemberOfTable"; +import MemberOfTableNew from "src/components/MemberOf/MemberOfTableNew"; // Data types import { - UserGroup, Netgroup, Roles, HBACRules, SudoRules, User, + UserGroupNew, } from "src/utils/datatypes/globalDataTypes"; // Redux import { useAppSelector } from "src/store/hooks"; - // Repositories import { - userGroupsInitialData, netgroupsInitialData, rolesInitialData, hbacRulesInitialData, @@ -36,6 +36,8 @@ import { // Modals import MemberOfAddModal from "src/components/MemberOf/MemberOfAddModal"; import MemberOfDeleteModal from "src/components/MemberOf/MemberOfDeleteModal"; +import { useGetUserByUidQuery } from "src/services/rpc"; +import { useUserMemberOfData } from "src/hooks/useUserMemberOfData"; interface PropsToUserMemberOf { user: User; @@ -43,18 +45,12 @@ interface PropsToUserMemberOf { const UserMemberOf = (props: PropsToUserMemberOf) => { // Retrieve each group list from Redux: - let userGroupsList = useAppSelector( - (state) => state.usergroups.userGroupList - ); let netgroupsList = useAppSelector((state) => state.netgroups.netgroupList); let rolesList = useAppSelector((state) => state.roles.roleList); let hbacRulesList = useAppSelector((state) => state.hbacrules.hbacRulesList); let sudoRulesList = useAppSelector((state) => state.sudorules.sudoRulesList); // Alter the available options list to keep the state of the recently added / removed items - const updateUserGroupsList = (newAvOptionsList: unknown[]) => { - userGroupsList = newAvOptionsList as UserGroup[]; - }; const updateNetgroupsList = (newAvOptionsList: unknown[]) => { netgroupsList = newAvOptionsList as Netgroup[]; }; @@ -68,10 +64,42 @@ const UserMemberOf = (props: PropsToUserMemberOf) => { sudoRulesList = newAvOptionsList as SudoRules[]; }; - // List of default dummy data (for each tab option) - const [userGroupsRepository, setUserGroupsRepository] = useState( - userGroupsInitialData - ); + // API call to get user's info + const userQuery = useGetUserByUidQuery(props.user.uid[0]); + + const userData = userQuery.data || {}; + + const [user, setUser] = React.useState>({}); + + // Member groups associated to user (string[] | UserGroupNew[]) + const [userGroupsFromUser, setUserGroupsFromUser] = React.useState< + UserGroupNew[] + >([]); + + React.useEffect(() => { + if (!userQuery.isFetching && userData) { + setUser({ ...userData }); + } + }, [userData, userQuery.isFetching]); + + // API call to get full lists of Member data + const { userGroupsFullList } = useUserMemberOfData(); + + // Parse into UserGroupNew[] format by getting the full info from the available data + React.useEffect(() => { + const userGroupsParsed: UserGroupNew[] = []; + user.memberof_group?.map((group) => { + userGroupsFullList.map((g) => { + if (g.cn === group) { + userGroupsParsed.push(g); + } + }); + }); + setUserGroupsFromUser(userGroupsParsed); + }, [user, userGroupsFullList]); + + // List of default dummy data (for the still-non-adapted tabs) + // TODO: Remove this once all the tabs are adapted with the C.L. const [netgroupsRepository, setNetgroupsRepository] = useState(netgroupsInitialData); const [rolesRepository, setRolesRepository] = useState(rolesInitialData); @@ -90,14 +118,6 @@ const UserMemberOf = (props: PropsToUserMemberOf) => { // Filter functions to compare the available data with the data that // the user is already member of. This is done to prevent duplicates // (e.g: adding the same element twice). - const filterUserGroupsData = () => { - // User groups - return userGroupsList.filter((item) => { - return !userGroupsRepository.some((itm) => { - return item.name === itm.name; - }); - }); - }; const filterNetgroupsData = () => { // Netgroups return netgroupsList.filter((item) => { @@ -132,7 +152,6 @@ const UserMemberOf = (props: PropsToUserMemberOf) => { }; // Available data to be added as member of - const userGroupsFilteredData: UserGroup[] = filterUserGroupsData(); const netgroupsFilteredData: Netgroup[] = filterNetgroupsData(); const rolesFilteredData: Roles[] = filterRolesData(); const hbacRulesFilteredData: HBACRules[] = filterHbacRulesData(); @@ -140,7 +159,8 @@ const UserMemberOf = (props: PropsToUserMemberOf) => { // Number of items on the list for each repository const [userGroupsRepoLength, setUserGroupsRepoLength] = useState( - userGroupsRepository.length + // userGroupsFullList.length + userGroupsFromUser.length ); const [netgroupsRepoLength, setNetgroupsRepoLength] = useState( netgroupsRepository.length @@ -161,7 +181,7 @@ const UserMemberOf = (props: PropsToUserMemberOf) => { // - Number of items for a specific list const updateGroupRepository = ( groupRepository: - | UserGroup[] + | UserGroupNew[] | Netgroup[] | Roles[] | HBACRules[] @@ -169,9 +189,9 @@ const UserMemberOf = (props: PropsToUserMemberOf) => { ) => { switch (tabName) { case "User groups": - setUserGroupsRepository(groupRepository as UserGroup[]); - setShownUserGroupsList(userGroupsRepository.slice(0, perPage)); - setUserGroupsRepoLength(userGroupsRepository.length); + setUserGroupsFromUser(groupRepository as UserGroupNew[]); + setShownUserGroupsList(userGroupsFromUser.slice(0, perPage)); + setUserGroupsRepoLength(userGroupsFromUser.length); break; case "Netgroups": setNetgroupsRepository(groupRepository as Netgroup[]); @@ -238,7 +258,7 @@ const UserMemberOf = (props: PropsToUserMemberOf) => { // Member groups displayed on the first page const [shownUserGroupsList, setShownUserGroupsList] = useState( - userGroupsRepository.slice(0, perPage) + userGroupsFromUser.slice(0, perPage) ); const [shownNetgroupsList, setShownNetgroupsList] = useState( netgroupsRepository.slice(0, perPage) @@ -253,13 +273,19 @@ const UserMemberOf = (props: PropsToUserMemberOf) => { sudoRulesRepository.slice(0, perPage) ); + // Update shown items based on data updates + React.useEffect(() => { + setShownUserGroupsList(userGroupsFromUser.slice(0, perPage)); + // Omitting the rest of the shown lists because they are not adapted yet to the C.L. + }, [userGroupsFromUser]); + // Update pagination const changeMemberGroupsList = ( - value: UserGroup[] | Netgroup[] | Roles[] | HBACRules[] | SudoRules[] + value: UserGroupNew[] | Netgroup[] | Roles[] | HBACRules[] | SudoRules[] ) => { switch (activeTabKey) { case 0: - setShownUserGroupsList(value as UserGroup[]); + setShownUserGroupsList(value as UserGroupNew[]); break; case 1: setShownNetgroupsList(value as Netgroup[]); @@ -287,7 +313,7 @@ const UserMemberOf = (props: PropsToUserMemberOf) => { setPage(newPage); switch (activeTabKey) { case 0: - setShownUserGroupsList(userGroupsRepository.slice(startIdx, endIdx)); + setShownUserGroupsList(userGroupsFromUser.slice(startIdx, endIdx)); break; case 1: setShownNetgroupsList(netgroupsRepository.slice(startIdx, endIdx)); @@ -314,7 +340,7 @@ const UserMemberOf = (props: PropsToUserMemberOf) => { setPerPage(newPerPage); switch (activeTabKey) { case 0: - setShownUserGroupsList(userGroupsRepository.slice(startIdx, endIdx)); + setShownUserGroupsList(userGroupsFromUser.slice(startIdx, endIdx)); break; case 1: setShownNetgroupsList(netgroupsRepository.slice(startIdx, endIdx)); @@ -341,7 +367,7 @@ const UserMemberOf = (props: PropsToUserMemberOf) => { setPage(newPage); switch (activeTabKey) { case 0: - setShownUserGroupsList(userGroupsRepository.slice(startIdx, endIdx)); + setShownUserGroupsList(userGroupsFromUser.slice(startIdx, endIdx)); break; case 1: setShownNetgroupsList(netgroupsRepository.slice(startIdx, endIdx)); @@ -367,7 +393,7 @@ const UserMemberOf = (props: PropsToUserMemberOf) => { setPerPage(newPerPage); switch (activeTabKey) { case 0: - setShownUserGroupsList(userGroupsRepository.slice(startIdx, endIdx)); + setShownUserGroupsList(userGroupsFromUser.slice(startIdx, endIdx)); break; case 1: setShownNetgroupsList(netgroupsRepository.slice(startIdx, endIdx)); @@ -388,7 +414,7 @@ const UserMemberOf = (props: PropsToUserMemberOf) => { const numberOfItems = () => { switch (activeTabKey) { case 0: - return userGroupsRepository.length; + return userGroupsFromUser.length; case 1: return netgroupsRepository.length; case 2: @@ -430,12 +456,16 @@ const UserMemberOf = (props: PropsToUserMemberOf) => { useEffect(() => { setPage(1); if (showTableRows) setShowTableRows(false); + switch (activeTabKey) { + case 0: + setShownUserGroupsList(userGroupsFromUser.slice(0, perPage)); + setUserGroupsRepoLength(userGroupsFromUser.length); + break; + } + + // TODO: Remove the timeout once the remaining elements are adapted into the C.L. setTimeout(() => { switch (activeTabKey) { - case 0: - setShownUserGroupsList(userGroupsRepository.slice(0, perPage)); - setUserGroupsRepoLength(userGroupsRepository.length); - break; case 1: setShownNetgroupsList(netgroupsRepository.slice(0, perPage)); setNetgroupsRepoLength(netgroupsRepository.length); @@ -456,7 +486,7 @@ const UserMemberOf = (props: PropsToUserMemberOf) => { setShowTableRows(true); }, 1000); }, [ - userGroupsRepository, + userGroupsFromUser, netgroupsRepository, rolesRepository, hbacRulesRepository, @@ -526,306 +556,314 @@ const UserMemberOf = (props: PropsToUserMemberOf) => { // Render 'ActiveUsersIsMemberOf' return ( - - - - - User groups{" "} - - {userGroupsRepoLength} - - - } - > - - - - - Netgroups{" "} - - {netgroupsRepoLength} - - - } - > - - - - - Roles{" "} - - {rolesRepoLength} - - - } - > - - - - - HBAC rules{" "} - - {hbacRulesRepoLength} - - - } - > - - - - - Sudo rules{" "} - - {sudoRulesRepoLength} - - - } + <> + + + - - - - - - - {tabName === "User groups" && ( - <> - {showAddModal && ( - - )} - {showDeleteModal && groupsNamesSelected.length !== 0 && ( - - )} - - )} - {tabName === "Netgroups" && ( - <> - {showAddModal && ( - - )} - {showDeleteModal && groupsNamesSelected.length !== 0 && ( - - )} - - )} - {tabName === "Roles" && ( - <> - {showAddModal && ( - - )} - {showDeleteModal && groupsNamesSelected.length !== 0 && ( - - )} - - )} - {tabName === "HBAC rules" && ( - <> - {showAddModal && ( - - )} - {showDeleteModal && groupsNamesSelected.length !== 0 && ( - - )} - - )} - {tabName === "Sudo rules" && ( - <> - {showAddModal && ( - - )} - {showDeleteModal && groupsNamesSelected.length !== 0 && ( - - )} - - )} - + + User groups{" "} + + {userGroupsRepoLength} + + + } + > + + + + + Netgroups{" "} + + {netgroupsRepoLength} + + + } + > + + + + + Roles{" "} + + {rolesRepoLength} + + + } + > + + + + + HBAC rules{" "} + + {hbacRulesRepoLength} + + + } + > + + + + + Sudo rules{" "} + + {sudoRulesRepoLength} + + + } + > + + + + + + + {/* This will remain commented until the 'Add' and 'Delete' + functionality is adapted */} + {/* {tabName === "User groups" && ( + <> + {showAddModal && ( + + )} + {showDeleteModal && groupsNamesSelected.length !== 0 && ( + + )} + + )} */} + {tabName === "Netgroups" && ( + <> + {showAddModal && ( + + )} + {showDeleteModal && groupsNamesSelected.length !== 0 && ( + + )} + + )} + {tabName === "Roles" && ( + <> + {showAddModal && ( + + )} + {showDeleteModal && groupsNamesSelected.length !== 0 && ( + + )} + + )} + {tabName === "HBAC rules" && ( + <> + {showAddModal && ( + + )} + {showDeleteModal && groupsNamesSelected.length !== 0 && ( + + )} + + )} + {tabName === "Sudo rules" && ( + <> + {showAddModal && ( + + )} + {showDeleteModal && groupsNamesSelected.length !== 0 && ( + + )} + + )} + + ); }; diff --git a/src/services/rpc.ts b/src/services/rpc.ts index 49ef84060..69875f610 100644 --- a/src/services/rpc.ts +++ b/src/services/rpc.ts @@ -20,6 +20,7 @@ import { RadiusServer, UIDType, User, + cnType, } from "src/utils/datatypes/globalDataTypes"; import { apiToUser } from "src/utils/userUtils"; import { apiToHost } from "src/utils/hostUtils"; @@ -918,6 +919,86 @@ export const api = createApi({ }); }, }), + getUserByUid: build.query({ + query: (uid) => { + return getCommand({ + method: "user_show", + params: [[uid], { version: API_VERSION_BACKUP }], + }); + }, + transformResponse: (response: FindRPCResponse): User => + response.result.result as unknown as User, + }), + getUserGroups: build.query({ + async queryFn(payloadData, _queryApi, _extraOptions, fetchWithBQ) { + const { searchValue, sizeLimit, apiVersion } = payloadData; + + if (apiVersion === undefined) { + return { + error: { + status: "CUSTOM_ERROR", + data: "", + error: "API version not available", + } as FetchBaseQueryError, + }; + } + + // Prepare search parameters + const params = { + pkey_only: true, + sizelimit: sizeLimit, + version: apiVersion, + }; + + // Prepare payload + const payloadUserGroups: Command = { + method: "group_find", + params: [[searchValue], params], + }; + + // Make call using 'fetchWithBQ' + const getUserGroupsIdsResult = await fetchWithBQ( + getCommand(payloadUserGroups) + ); + // Return possible errors + if (getUserGroupsIdsResult.error) { + return { error: getUserGroupsIdsResult.error as FetchBaseQueryError }; + } + // If no error: cast and assign 'uids' + const userGroupsIdsResponseData = + getUserGroupsIdsResult.data as FindRPCResponse; + + const userGroupsIds: string[] = []; + const itemsCount = userGroupsIdsResponseData.result.result + .length as number; + for (let i = 0; i < itemsCount; i++) { + const hostId = userGroupsIdsResponseData.result.result[i] as cnType; + const { cn } = hostId; + userGroupsIds.push(cn[0] as string); + } + + // 2ND CALL - GET USER GROUPS INFO + // Prepare payload + const payloadUserGroupsBatch: Command[] = userGroupsIds.map( + (userGroupId) => ({ + method: "group_show", + params: [[userGroupId], { no_members: true }], + }) + ); + + // Make call using 'fetchWithBQ' + const userGroupsResult = await fetchWithBQ( + getBatchCommand(payloadUserGroupsBatch as Command[], apiVersion) + ); + + // Return results + return userGroupsResult.data + ? { data: userGroupsResult.data as BatchRPCResponse } + : { + error: userGroupsResult.error as unknown as FetchBaseQueryError, + }; + }, + }), }), }); @@ -997,4 +1078,6 @@ export const { useGetDNSZonesQuery, useSaveHostMutation, useGetHostsFullDataQuery, + useGetUserByUidQuery, + useGetUserGroupsQuery, } = api; diff --git a/src/store/Identity/userGroups-slice.ts b/src/store/Identity/userGroups-slice.ts index d51f786c3..1ca5a388b 100644 --- a/src/store/Identity/userGroups-slice.ts +++ b/src/store/Identity/userGroups-slice.ts @@ -1,15 +1,14 @@ import { createSlice } from "@reduxjs/toolkit"; import type { RootState } from "../store"; -import userGroupsJson from "./userGroups.json"; // Data type -import { UserGroup } from "src/utils/datatypes/globalDataTypes"; +import { UserGroupNew } from "src/utils/datatypes/globalDataTypes"; interface UserGroupState { - userGroupList: UserGroup[]; + userGroupList: UserGroupNew[]; } const initialState: UserGroupState = { - userGroupList: userGroupsJson, + userGroupList: [], }; const userGroupsSlice = createSlice({ diff --git a/src/store/Identity/userGroups.json b/src/store/Identity/userGroups.json deleted file mode 100644 index a88099d2b..000000000 --- a/src/store/Identity/userGroups.json +++ /dev/null @@ -1,22 +0,0 @@ -[ - { - "name": "admins", - "gid": "613800000", - "description": "Account administrators group" - }, - { - "name": "editors", - "gid": "613800002", - "description": "Limited admins who can edit other users" - }, - { - "name": "employees", - "gid": "613800005", - "description": "Test Employees" - }, - { - "name": "group123", - "gid": "613800008", - "description": "The best group ever" - } -] diff --git a/src/utils/data/GroupRepositories.ts b/src/utils/data/GroupRepositories.ts index f9d4ed8cc..69fdb7efb 100644 --- a/src/utils/data/GroupRepositories.ts +++ b/src/utils/data/GroupRepositories.ts @@ -8,7 +8,6 @@ */ import { - UserGroup, Netgroup, Roles, HBACRules, @@ -17,79 +16,6 @@ import { } from "../datatypes/globalDataTypes"; // USERS -// 'User groups' initial data -export let userGroupsInitialData: UserGroup[] = [ - { - name: "Initial admins", - gid: "12345678", - description: "This is a description for initial admins", - }, - { - name: "Other admins", - gid: "234456567", - description: "This is a description for other admins", - }, - { - name: "Other admins 1", - gid: "234456567", - description: "This is a description for other admins 1", - }, - { - name: "Other admins 2", - gid: "234456567", - description: "This is a description for other admins 2", - }, - { - name: "Other admins 3", - gid: "234456567", - description: "This is a description for other admins 3", - }, - { - name: "Other admins 4", - gid: "234456567", - description: "This is a description for other admins 4", - }, - { - name: "Other admins 5", - gid: "234456567", - description: "This is a description for other admins 5", - }, - { - name: "Other admins 6", - gid: "234456567", - description: "This is a description for other admins 6", - }, - { - name: "Other admins 7", - gid: "234456567", - description: "This is a description for other admins 7", - }, - { - name: "Other admins 8", - gid: "234456567", - description: "This is a description for other admins 8", - }, - { - name: "Other admins 9", - gid: "234456567", - description: "This is a description for other admins 9", - }, - { - name: "Other admins 10", - gid: "234456567", - description: "This is a description for other admins 10", - }, - { - name: "Other admins 11", - gid: "234456567", - description: "This is a description for other admins 11", - }, - { - name: "Other admins 12", - gid: "234456567", - description: "This is a description for other admins 12", - }, -]; // 'Netgroups' initial data export let netgroupsInitialData: Netgroup[] = [