From eb873fa8c1431368501d3c05d150e924f1633a63 Mon Sep 17 00:00:00 2001 From: Carla Martinez Date: Wed, 31 Jan 2024 09:44:54 +0100 Subject: [PATCH 1/3] Add missing User parameters The `User` data type needs the following parameters that will be managed in the 'Is a member of' section: - `memberof_group` - `memberof_netgroup` - `memberof_role` - `memberof_hbacrule` - `memberof_sudorule` - `memberof_subid` Signed-off-by: Carla Martinez --- src/utils/datatypes/globalDataTypes.ts | 6 +++++- src/utils/userUtils.tsx | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/utils/datatypes/globalDataTypes.ts b/src/utils/datatypes/globalDataTypes.ts index 0a21cca9..6d7b9603 100644 --- a/src/utils/datatypes/globalDataTypes.ts +++ b/src/utils/datatypes/globalDataTypes.ts @@ -67,7 +67,11 @@ export interface User { ipanthomedirectorydrive: string; // 'Member of' data memberof_group: string[]; // multivalue - memberof_subid?: string[]; // multivalue + memberof_netgroup: string[]; // multivalue + memberof_role: string[]; // multivalue + memberof_hbacrule: string[]; // multivalue + memberof_sudorule: string[]; // multivalue + memberof_subid: string[]; // multivalue // 'Managed by' data mepmanagedentry: string[]; // other diff --git a/src/utils/userUtils.tsx b/src/utils/userUtils.tsx index d2d9f20d..d307671e 100644 --- a/src/utils/userUtils.tsx +++ b/src/utils/userUtils.tsx @@ -161,6 +161,10 @@ export function createEmptyUser(): User { ipanthomedirectorydrive: "", // 'Member of' data memberof_group: [], + memberof_netgroup: [], + memberof_role: [], + memberof_hbacrule: [], + memberof_sudorule: [], memberof_subid: [], // 'Managed by' data mepmanagedentry: [], From 1550b9c4b907ee75a85810343fbd8c945cd150ea Mon Sep 17 00:00:00 2001 From: Carla Martinez Date: Wed, 31 Jan 2024 09:53:45 +0100 Subject: [PATCH 2/3] Define new components, data types, and utils As other components are using the `MemberOfTable` and `MemberOfToolbar` components, there are many dependencies to correct and adapt. The new `MemberOfTableNew` and `MemberOfToolbarNew` have been defined to be used when adapting the 'User groups' data only. The same has been done in the data types by creating a new `UserGroupNew` type that will be used later. The `normalizeString` utility function is meant to normalize LDAP values from `string[]` to `string`. Signed-off-by: Carla Martinez --- src/components/MemberOf/MemberOfTableNew.tsx | 430 ++++++++++++ .../MemberOf/MemberOfToolbarNew.tsx | 629 ++++++++++++++++++ src/utils/datatypes/globalDataTypes.ts | 8 + src/utils/utils.tsx | 9 + 4 files changed, 1076 insertions(+) create mode 100644 src/components/MemberOf/MemberOfTableNew.tsx create mode 100644 src/components/MemberOf/MemberOfToolbarNew.tsx diff --git a/src/components/MemberOf/MemberOfTableNew.tsx b/src/components/MemberOf/MemberOfTableNew.tsx new file mode 100644 index 00000000..bbcfbb6e --- /dev/null +++ b/src/components/MemberOf/MemberOfTableNew.tsx @@ -0,0 +1,430 @@ +import React, { useEffect, useState } from "react"; +import { + Bullseye, + EmptyState, + EmptyStateIcon, + EmptyStateVariant, + EmptyStateBody, + Button, + EmptyStateHeader, + EmptyStateFooter, +} from "@patternfly/react-core"; +import { Td, Th, Tr } from "@patternfly/react-table"; +// Icons +import SearchIcon from "@patternfly/react-icons/dist/esm/icons/search-icon"; +// Layouts +import TableLayout from "../layouts/TableLayout"; +import SkeletonOnTableLayout from "../layouts/Skeleton/SkeletonOnTableLayout"; + +interface ColumnNames { + name: string; + gid?: string; + status?: string; + description: string; +} + +// Although tabs data types habe been already defined, it is not possible to access to all +// its variables. Just the mandatory ones ('name' and 'description') are accessible at this point. +// To display all the possible data types for all the tabs (and not only the mandatory ones) +// an extra interface 'MemberOfElement' will be defined. This will be called in the 'PropsToTable' +// // interface instead of each type (UserGroup | Netgroup | Roles | HBACRules | SudoRules | HostGroup). +interface MemberOfElement { + cn?: string; + name?: string; + gid?: string; + gidnumber?: string; + status?: string; + description: string; +} + +// As we are still adapting this section, there will be some +// types that are not defined respecting the 'MemberOfElement' interface. +// This is a helper function to get the `cn` or the `name` value +export const getNameValue = (group: MemberOfElement) => { + let name = group.name as string; + if (group.cn !== undefined) { + name = group.cn as string; + } + return name; +}; + +// Some values might not have a specific value defined (i.e.: +// empty string ""). This is not allowed by the table component. +// Therefore, this function will return "-" instead of "". +export const parseEmptyString = (str: string) => { + if (str === "") { + return "-"; + } + return str; +}; + +interface ButtonData { + isDeletion: boolean; + updateIsDeletion: (option: boolean) => void; + changeIsDeleteButtonDisabled: (updatedDeleteButton: boolean) => void; +} + +export interface PropsToTable { + listOfElements: MemberOfElement[]; + tableName: string; + activeTabKey: number; + changeSelectedGroups: (groups: string[]) => void; + buttonData: ButtonData; + showTableRows: boolean; + searchValue: string; + fullGroupList: MemberOfElement[]; +} + +const MemberOfTable = (props: PropsToTable) => { + // Define column names + const userGroupsColumnNames: ColumnNames = { + name: "Group name", + gid: "GID", + description: "Description", + }; + const netgroupsColumnNames: ColumnNames = { + name: "Netgroup name", + description: "Description", + }; + const rolesColumnNames: ColumnNames = { + name: "Role name", + description: "Description", + }; + const hbacRulesColumnNames: ColumnNames = { + name: "Rule name", + status: "Status", + description: "Description", + }; + const sudoRulesColumnNames: ColumnNames = { + name: "Rule name", + status: "Status", + description: "Description", + }; + const hostGroupsColumnNames: ColumnNames = { + name: "Host name", + description: "Description", + }; + + // State for column names + const [columnNames, setColumnNames] = useState( + userGroupsColumnNames + ); + + // Filter (SearchInput) + const onFilter = (group: MemberOfElement) => { + if (props.searchValue === "") { + return true; + } + + let input: RegExp; + try { + input = new RegExp(props.searchValue, "i"); + } catch (err) { + input = new RegExp( + props.searchValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), + "i" + ); + } + if (group.cn !== undefined) { + return group.cn.search(input) >= 0; + } else if (group.name !== undefined) { + return group.name.search(input) >= 0; + } + // return group.cn.search(input) >= 0; + }; + + const filteredShownGroups = + props.searchValue === "" + ? props.listOfElements + : props.fullGroupList.filter(onFilter); + + // When moving to another tab, the column names must change + useEffect(() => { + switch (props.activeTabKey) { + case 0: + if (props.tableName === "User groups") { + setColumnNames(userGroupsColumnNames); + } + if (props.tableName === "Host groups") { + setColumnNames(hostGroupsColumnNames); + } + break; + case 1: + setColumnNames(netgroupsColumnNames); + break; + case 2: + setColumnNames(rolesColumnNames); + break; + case 3: + setColumnNames(hbacRulesColumnNames); + break; + case 4: + setColumnNames(sudoRulesColumnNames); + break; + } + }, [props.activeTabKey]); + + // selected user ids state + const [selectedGroupNameIds, setSelectedGroupNameIds] = useState( + [] + ); + + // Selectable checkboxes on table + const isGroupSelectable = (group: MemberOfElement) => + group.cn !== "" || group.name !== ""; + const selectableGroups = props.listOfElements.filter(isGroupSelectable); + + // Selected rows are tracked. Primary key: userLogin + const [selectedGroupNames, setSelectedGroupNames] = useState([]); + + const setGroupSelected = (group: MemberOfElement, isSelecting = true) => { + const name = getNameValue(group); + + setSelectedGroupNames((prevSelected) => { + const otherSelectedGroupNames = prevSelected.filter((r) => r !== name); + return isSelecting && isGroupSelectable(group) + ? [...otherSelectedGroupNames, name] + : otherSelectedGroupNames; + }); + }; + + const areAllGroupsSelected = + selectedGroupNames.length === selectableGroups.length; + + const isGroupSelected = (group: MemberOfElement) => { + const name = getNameValue(group); + return selectedGroupNames.includes(name); + }; + + // Multiple selection + const selectAllGroups = (isSelecting = true) => { + const selected: string[] = []; + if (isSelecting) { + selectableGroups.map((group) => { + const name = getNameValue(group); + selected.push(name); + }); + } + setSelectedGroupNames(selected); + + // Enable/disable 'Delete' button + if (isSelecting) { + const groupsNameArray: string[] = []; + selectableGroups.map((group) => + groupsNameArray.push(getNameValue(group)) + ); + setSelectedGroupNameIds(groupsNameArray); + props.changeSelectedGroups(groupsNameArray); + props.buttonData.changeIsDeleteButtonDisabled(false); + } else { + props.changeSelectedGroups([]); + props.buttonData.changeIsDeleteButtonDisabled(true); + } + }; + + // To allow shift+click to select/deselect multiple rows + const [recentSelectedRowIndex, setRecentSelectedRowIndex] = useState< + number | null + >(null); + const [shifting, setShifting] = useState(false); + + // On selecting one single row + const onSelectGroup = ( + group: MemberOfElement, + rowIndex: number, + isSelecting: boolean + ) => { + const name = getNameValue(group); + // If the group is shift + selecting the checkboxes, then all intermediate checkboxes should be selected + if (shifting && recentSelectedRowIndex !== null) { + const numberSelected = rowIndex - recentSelectedRowIndex; + const intermediateIndexes = + numberSelected > 0 + ? Array.from( + new Array(numberSelected + 1), + (_x, i) => i + recentSelectedRowIndex + ) + : Array.from( + new Array(Math.abs(numberSelected) + 1), + (_x, i) => i + rowIndex + ); + intermediateIndexes.forEach((index) => + setGroupSelected(props.listOfElements[index], isSelecting) + ); + } else { + setGroupSelected(group, isSelecting); + } + setRecentSelectedRowIndex(rowIndex); + + // Update selectedGroups array + let selectedGroupsArray = selectedGroupNames; + if (isSelecting) { + selectedGroupsArray.push(name); + setSelectedGroupNameIds(selectedGroupsArray); + props.changeSelectedGroups(selectedGroupsArray); + } else { + selectedGroupsArray = selectedGroupsArray.filter( + (groupName) => groupName !== name + ); + setSelectedGroupNameIds(selectedGroupsArray); + props.changeSelectedGroups(selectedGroupsArray); + } + }; + + // Reset 'updateIsDeletion' when a delete operation has been done + useEffect(() => { + if (props.buttonData.isDeletion) { + setSelectedGroupNameIds([]); + setSelectedGroupNames([]); + props.buttonData.updateIsDeletion(false); + } + }, [props.buttonData.isDeletion]); + + // Enable 'Delete' button if any group on the table is selected + useEffect(() => { + if (selectedGroupNameIds.length > 0) { + props.buttonData.changeIsDeleteButtonDisabled(false); + } + if (selectedGroupNameIds.length === 0) { + props.buttonData.changeIsDeleteButtonDisabled(true); + } + }, [selectedGroupNameIds]); + + // When the tableName changes, the selected checkboxes are reset + useEffect(() => { + setSelectedGroupNameIds([]); + setSelectedGroupNames([]); + }, [props.activeTabKey]); + + // Keyboard event + useEffect(() => { + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === "Shift") { + setShifting(true); + } + }; + const onKeyUp = (e: KeyboardEvent) => { + if (e.key === "Shift") { + setShifting(false); + } + }; + + document.addEventListener("keydown", onKeyDown); + document.addEventListener("keyup", onKeyUp); + + return () => { + document.removeEventListener("keydown", onKeyDown); + document.removeEventListener("keyup", onKeyUp); + }; + }, []); + + // Define body for empty tables + const emptyBody = ( + + + + + } + headingLevel="h2" + /> + Clear all filters and try again. + + + + + + + + ); + + // Define table header + const header = ( + + selectAllGroups(isSelecting), + isSelected: areAllGroupsSelected, + }} + /> + + {columnNames.name} + + {columnNames.gid && ( + + {columnNames.gid} + + )} + {columnNames.status && ( + + {columnNames.status} + + )} + {columnNames.description && ( + + {columnNames.description} + + )} + + ); + + // Define table body + const body = filteredShownGroups.map((group, rowIndex) => ( + + + onSelectGroup(group, rowIndex, isSelecting), + isSelected: isGroupSelected(group), + isDisabled: !isGroupSelectable(group), + }} + /> + {getNameValue(group)} + {group.gidnumber !== undefined && ( + + {parseEmptyString(group.gidnumber)} + + )} + {group.gid && {group.gid}} + {group.status && {group.status}} + {group.description && ( + {group.description} + )} + + )); + + // Define skeleton + const skeleton = ( + + ); + + // Render 'MemberOfTable' + return ( + + ); +}; + +export default MemberOfTable; diff --git a/src/components/MemberOf/MemberOfToolbarNew.tsx b/src/components/MemberOf/MemberOfToolbarNew.tsx new file mode 100644 index 00000000..e0468b8e --- /dev/null +++ b/src/components/MemberOf/MemberOfToolbarNew.tsx @@ -0,0 +1,629 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import React, { useState } from "react"; +import { + ToggleGroup, + Button, + Form, + FormGroup, + ToggleGroupItem, + Text, + ToolbarItemVariant, + TextContent, + TextVariants, + Pagination, +} from "@patternfly/react-core"; +// Icons +import OutlinedQuestionCircleIcon from "@patternfly/react-icons/dist/esm/icons/outlined-question-circle-icon"; +// Toolbar layout +import ToolbarLayout, { + ToolbarItemAlignment, + ToolbarItemSpacer, +} from "src/components/layouts/ToolbarLayout"; +// Layout +import SearchInputLayout from "src/components/layouts/SearchInputLayout"; +import { + HBACRules, + HostGroup, + Netgroup, + Roles, + SudoRules, + UserGroupNew, +} from "src/utils/datatypes/globalDataTypes"; + +interface PageData { + page: number; + changeSetPage: ( + newPage: number, + perPage: number | undefined, + startIdx: number | undefined, + endIdx: number | undefined + ) => void; + perPage: number; + changePerPageSelect: ( + newPerPage: number, + newPage: number, + startIdx: number | undefined, + endIdx: number | undefined + ) => void; +} + +interface ButtonData { + onClickAddHandler: () => void; + onClickDeleteHandler: () => void; + isDeleteButtonDisabled: boolean; +} + +interface SettersData { + changeMemberGroupsList: ( + arg: + | UserGroupNew[] + | Netgroup[] + | Roles[] + | HBACRules[] + | SudoRules[] + | HostGroup[] + ) => void; + changeTabName: (name: string) => void; +} + +interface SearchValueData { + searchValue: string; + updateSearchValue: (value: string) => void; +} + +export interface PropsToToolbar { + pageRepo: + | UserGroupNew[] + | Netgroup[] + | Roles[] + | HBACRules[] + | SudoRules[] + | HostGroup[]; + shownItems: + | UserGroupNew[] + | Netgroup[] + | Roles[] + | HBACRules[] + | SudoRules[] + | HostGroup[]; + toolbar: + | "user groups" + | "netgroups" + | "roles" + | "HBAC rules" + | "sudo rules" + | "host groups"; + settersData: SettersData; + pageData: PageData; + buttonData: ButtonData; + searchValueData: SearchValueData; +} + +const MemberOfToolbar = (props: PropsToToolbar) => { + // -- Pagination + const [page, setPage] = useState(1); + const [perPage, setPerPage] = useState(10); + + // - Page setters + const onSetPage = ( + _event: React.MouseEvent | React.KeyboardEvent | MouseEvent, + newPage: number, + perPage: number | undefined, + startIdx: number | undefined, + endIdx: number | undefined + ) => { + setPage(newPage); + props.settersData.changeMemberGroupsList( + props.pageRepo.slice(startIdx, endIdx) + ); + props.pageData.changeSetPage(newPage, perPage, startIdx, endIdx); + }; + + const onPerPageSelect = ( + _event: React.MouseEvent | React.KeyboardEvent | MouseEvent, + newPerPage: number, + newPage: number, + startIdx: number | undefined, + endIdx: number | undefined + ) => { + setPerPage(newPerPage); + props.settersData.changeMemberGroupsList( + props.pageRepo.slice(startIdx, endIdx) + ); + props.pageData.changePerPageSelect(newPerPage, newPage, startIdx, endIdx); + }; + + // Toggle group + // - User groups + const [isTGUserGroupsSelected, setIsTGUserGroupsSelected] = + useState("direct"); + + const TGUserGroupsHandler = ( + event: React.MouseEvent | React.KeyboardEvent | MouseEvent, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + isSelected: boolean + ) => { + const id = event.currentTarget.id; + setIsTGUserGroupsSelected(id); + }; + + const userGroupsOnClickAddHandler = () => { + props.settersData.changeTabName("User groups"); + props.buttonData.onClickAddHandler(); + }; + + const userGroupsOnDeleteClickHandler = () => { + props.settersData.changeTabName("User groups"); + props.buttonData.onClickDeleteHandler(); + }; + + // - Netgroups + const [isTGNetgroupsSelected, setIsTGNetgroupsSelected] = useState("direct"); + + const TGNetgroupsHandler = ( + event: React.MouseEvent | React.KeyboardEvent | MouseEvent, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + isSelected: boolean + ) => { + const id = event.currentTarget.id; + setIsTGNetgroupsSelected(id); + }; + + const netgroupsOnClickAddHandler = () => { + props.settersData.changeTabName("Netgroups"); + props.buttonData.onClickAddHandler(); + }; + + const netgroupsOnDeleteClickHandler = () => { + props.settersData.changeTabName("Netgroups"); + props.buttonData.onClickDeleteHandler(); + }; + + // - Roles + const [isTGRolesSelected, setIsTGRolesSelected] = useState("direct"); + + const TGRolesHandler = ( + event: React.MouseEvent | React.KeyboardEvent | MouseEvent, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + isSelected: boolean + ) => { + const id = event.currentTarget.id; + setIsTGRolesSelected(id); + }; + + const rolesOnClickAddHandler = () => { + props.settersData.changeTabName("Roles"); + props.buttonData.onClickAddHandler(); + }; + + const rolesOnDeleteClickHandler = () => { + props.settersData.changeTabName("Roles"); + props.buttonData.onClickDeleteHandler(); + }; + + // - HBAC rules + const [isTGHbacRulesSelected, setIsTGHbacRulesSelected] = useState("direct"); + + const TGHbacRulesHandler = ( + event: React.MouseEvent | React.KeyboardEvent | MouseEvent, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + isSelected: boolean + ) => { + const id = event.currentTarget.id; + setIsTGHbacRulesSelected(id); + }; + + const hbacRulesOnClickAddHandler = () => { + props.settersData.changeTabName("HBAC rules"); + props.buttonData.onClickAddHandler(); + }; + + const hbacRulesOnDeleteClickHandler = () => { + props.settersData.changeTabName("HBAC rules"); + props.buttonData.onClickDeleteHandler(); + }; + + // - Sudo rules + const [isTGSudoRulesSelected, setIsTGSudoRulesSelected] = useState("direct"); + + const TGSudoRulesHandler = ( + event: React.MouseEvent | React.KeyboardEvent | MouseEvent, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + isSelected: boolean + ) => { + const id = event.currentTarget.id; + setIsTGSudoRulesSelected(id); + }; + + const sudoRulesOnClickAddHandler = () => { + props.settersData.changeTabName("Sudo rules"); + props.buttonData.onClickAddHandler(); + }; + + const sudoRulesOnDeleteClickHandler = () => { + props.settersData.changeTabName("Sudo rules"); + props.buttonData.onClickDeleteHandler(); + }; + + // - Host groups + const [isTGHostGroupsSelected, setIsTGHostGroupsSelected] = + useState("direct"); + + const TGHostGroupsHandler = ( + event: React.MouseEvent | React.KeyboardEvent | MouseEvent, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + isSelected: boolean + ) => { + const id = event.currentTarget.id; + setIsTGHostGroupsSelected(id); + }; + + const hostGroupsOnClickAddHandler = () => { + props.settersData.changeTabName("Host groups"); + props.buttonData.onClickAddHandler(); + }; + + const hostGroupsOnDeleteClickHandler = () => { + props.settersData.changeTabName("Host groups"); + props.buttonData.onClickDeleteHandler(); + }; + + // 'User groups' toolbar elements data + const userGroupsToolbarData = { + searchId: "userGroups-search", + separator1Id: "userGroups-separator-1", + refreshButton: { + id: "userGroups-button-refresh", + }, + deleteButton: { + id: "userGroups-button-delete", + isDisabledHandler: props.buttonData.isDeleteButtonDisabled, + onClickHandler: userGroupsOnDeleteClickHandler, + }, + addButton: { + id: "userGroups-button-add", + onClickHandler: userGroupsOnClickAddHandler, + }, + separator2Id: "userGroups-separator-2", + membership: { + formId: "userGroups-form", + toggleGroupId: "userGroups-toggle-group", + isDirectSelected: isTGUserGroupsSelected === "direct", + onDirectChange: TGUserGroupsHandler, + isIndirectSelected: isTGUserGroupsSelected === "indirect", + onIndirectChange: TGUserGroupsHandler, + }, + separator3Id: "userGroups-separator-3", + helpIcon: { + id: "userGroups-help-icon", + // href: TDB + }, + paginationId: "userGroups-pagination", + }; + + // 'Netgroups' toolbar elements data + const netgroupsToolbarData = { + searchId: "netgroups-search", + separator1Id: "netgroups-separator-1", + refreshButton: { + id: "netgroups-button-refresh", + }, + deleteButton: { + id: "netgroups-button-delete", + isDisabledHandler: props.buttonData.isDeleteButtonDisabled, + onClickHandler: netgroupsOnDeleteClickHandler, + }, + addButton: { + id: "netgroups-button-add", + onClickHandler: netgroupsOnClickAddHandler, + }, + separator2Id: "netgroups-separator-2", + membership: { + formId: "netgroups-form", + toggleGroupId: "netgroups-toggle-group", + isDirectSelected: isTGNetgroupsSelected === "direct", + onDirectChange: TGNetgroupsHandler, + isIndirectSelected: isTGNetgroupsSelected === "indirect", + onIndirectChange: TGNetgroupsHandler, + }, + separator3Id: "netgroups-separator-3", + helpIcon: { + id: "netgroups-help-icon", + // href: TDB + }, + paginationId: "netgroups-pagination", + }; + + // 'Roles' toolbar elements data + const rolesToolbarData = { + searchId: "roles-search", + separator1Id: "roles-separator-1", + refreshButton: { + id: "roles-button-refresh", + }, + deleteButton: { + id: "roles-button-delete", + isDisabledHandler: props.buttonData.isDeleteButtonDisabled, + onClickHandler: rolesOnDeleteClickHandler, + }, + addButton: { + id: "roles-button-add", + onClickHandler: rolesOnClickAddHandler, + }, + separator2Id: "roles-separator-2", + membership: { + formId: "roles-form", + toggleGroupId: "roles-toggle-group", + isDirectSelected: isTGRolesSelected === "direct", + onDirectChange: TGRolesHandler, + isIndirectSelected: isTGRolesSelected === "indirect", + onIndirectChange: TGRolesHandler, + }, + separator3Id: "roles-separator-3", + helpIcon: { + id: "roles-help-icon", + // href: TDB + }, + paginationId: "roles-pagination", + }; + + // 'HBAC rules' toolbar elements data + const hbacRulesToolbarData = { + searchId: "hbacRules-search", + separator1Id: "hbacRules-separator-1", + refreshButton: { + id: "hbacRules-button-refresh", + }, + deleteButton: { + id: "hbacRules-button-delete", + isDisabledHandler: props.buttonData.isDeleteButtonDisabled, + onClickHandler: hbacRulesOnDeleteClickHandler, + }, + addButton: { + id: "hbacRules-button-add", + onClickHandler: hbacRulesOnClickAddHandler, + }, + separator2Id: "hbacRules-separator-2", + membership: { + formId: "hbacRules-form", + toggleGroupId: "hbacRules-toggle-group", + isDirectSelected: isTGHbacRulesSelected === "direct", + onDirectChange: TGHbacRulesHandler, + isIndirectSelected: isTGHbacRulesSelected === "indirect", + onIndirectChange: TGHbacRulesHandler, + }, + separator3Id: "hbacRules-separator-3", + helpIcon: { + id: "hbacRules-help-icon", + // href: TDB + }, + paginationId: "hbacRules-pagination", + }; + + // 'Sudo rules' toolbar elements data + const sudoRulesToolbarData = { + searchId: "sudoRules-search", + separator1Id: "sudoRules-separator-1", + refreshButton: { + id: "sudoRules-button-refresh", + }, + deleteButton: { + id: "sudoRules-button-delete", + isDisabledHandler: props.buttonData.isDeleteButtonDisabled, + onClickHandler: sudoRulesOnDeleteClickHandler, + }, + addButton: { + id: "sudoRules-button-add", + onClickHandler: sudoRulesOnClickAddHandler, + }, + separator2Id: "sudoRules-separator-2", + membership: { + formId: "sudoRules-form", + toggleGroupId: "sudoRules-toggle-group", + isDirectSelected: isTGSudoRulesSelected === "direct", + onDirectChange: TGSudoRulesHandler, + isIndirectSelected: isTGSudoRulesSelected === "indirect", + onIndirectChange: TGSudoRulesHandler, + }, + separator3Id: "sudoRules-separator-3", + helpIcon: { + id: "sudoRules-help-icon", + // href: TDB + }, + paginationId: "sudoRules-pagination", + }; + + // 'Host groups' toolbar elements data + const hostGroupsToolbarData = { + searchId: "hostGroups-search", + separator1Id: "hostGroups-separator-1", + refreshButton: { + id: "hostGroups-button-refresh", + }, + deleteButton: { + id: "hostGroups-button-delete", + isDisabledHandler: props.buttonData.isDeleteButtonDisabled, + onClickHandler: hostGroupsOnDeleteClickHandler, + }, + addButton: { + id: "hostGroups-button-add", + onClickHandler: hostGroupsOnClickAddHandler, + }, + separator2Id: "hostGroups-separator-2", + membership: { + formId: "hostGroups-form", + toggleGroupId: "hostGroups-toggle-group", + isDirectSelected: isTGHostGroupsSelected === "direct", + onDirectChange: TGHostGroupsHandler, + isIndirectSelected: isTGHostGroupsSelected === "indirect", + onIndirectChange: TGHostGroupsHandler, + }, + separator3Id: "hostGroups-separator-3", + helpIcon: { + id: "hostGroups-help-icon", + // href: TDB + }, + paginationId: "hostGroups-pagination", + }; + + // Specify which toolbar to show + const toolbarData = () => { + switch (props.toolbar) { + case "user groups": + return userGroupsToolbarData; + case "netgroups": + return netgroupsToolbarData; + case "roles": + return rolesToolbarData; + case "HBAC rules": + return hbacRulesToolbarData; + case "sudo rules": + return sudoRulesToolbarData; + case "host groups": + return hostGroupsToolbarData; + } + }; + + // Toolbar fields data structure + // - Depending on the 'toolbarData' response, a + // specific set of data will be shown. + const toolbarFields = [ + { + id: toolbarData().searchId, + key: 0, + element: ( + + ), + toolbarItemVariant: ToolbarItemVariant["search-filter"], + toolbarItemSpacer: { + default: "spacerMd", + } as ToolbarItemSpacer, + }, + { + id: toolbarData().separator1Id, + key: 1, + toolbarItemVariant: ToolbarItemVariant.separator, + }, + { + id: toolbarData().refreshButton.id, + key: 2, + element: ( + + ), + }, + { + id: toolbarData().deleteButton.id, + key: 3, + element: ( + + ), + }, + { + id: toolbarData().addButton.id, + key: 4, + element: ( + + ), + }, + { + id: toolbarData().separator2Id, + key: 5, + toolbarItemVariant: ToolbarItemVariant.separator, + }, + { + id: "membership-form", + key: 6, + element: ( +
+ +
+ ), + }, + { + id: toolbarData().membership.formId, + key: 7, + element: ( + + + + + ), + }, + { + id: toolbarData().separator3Id, + key: 8, + toolbarItemVariant: ToolbarItemVariant.separator, + }, + { + id: toolbarData().helpIcon.id, + key: 9, + element: ( + + + + + Help + + + + ), + }, + { + id: toolbarData().paginationId, + key: 10, + element: ( + + ), + toolbarItemAlignment: { default: "alignRight" } as ToolbarItemAlignment, + }, + ]; + + // Render toolbar content + return ; +}; + +export default MemberOfToolbar; diff --git a/src/utils/datatypes/globalDataTypes.ts b/src/utils/datatypes/globalDataTypes.ts index 6d7b9603..d61f5cea 100644 --- a/src/utils/datatypes/globalDataTypes.ts +++ b/src/utils/datatypes/globalDataTypes.ts @@ -127,6 +127,14 @@ export interface KrbPolicy { usercertificatebinary: string[]; } +export interface UserGroupNew { + cn: string; + gidnumber: string; + description: string; + dn: string; +} + +// TODO: Remove this data type (old) when is not needed anymore export interface UserGroup { name: string; gid: string; diff --git a/src/utils/utils.tsx b/src/utils/utils.tsx index 41ebedb5..5b1e4c38 100644 --- a/src/utils/utils.tsx +++ b/src/utils/utils.tsx @@ -294,3 +294,12 @@ export const isValidIpAddress = (ipAddress: string) => { return regexIPv4.test(ipAddress); } }; + +// Normalize string LDAP values (string[] --> string) +export const normalizeString = (entry: string[]) => { + let newValue = ""; + if (entry !== undefined) { + newValue = entry[0]; + } + return newValue; +}; From b944a7d0379282b0ef466f3f5e55d83b50039da4 Mon Sep 17 00:00:00 2001 From: Carla Martinez Date: Wed, 31 Jan 2024 09:59:40 +0100 Subject: [PATCH 3/3] 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 00000000..79e3eb2c --- /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 79ef4656..b9efadac 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 49ef8406..69875f61 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 d51f786c..1ca5a388 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 a88099d2..00000000 --- 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 f9d4ed8c..69fdb7ef 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[] = [