diff --git a/src/components/MemberOf/MemberOfAddModal.tsx b/src/components/MemberOf/MemberOfAddModal.tsx deleted file mode 100644 index 8a878ed80..000000000 --- a/src/components/MemberOf/MemberOfAddModal.tsx +++ /dev/null @@ -1,245 +0,0 @@ -import React, { ReactNode, useEffect, useState } from "react"; -// PatternFly -import { Button, DualListSelector } from "@patternfly/react-core"; -// Modals -import ModalWithFormLayout from "src/components/layouts/ModalWithFormLayout"; -// Data types -import { - UserGroup, - Netgroup, - Roles, - HBACRules, - SudoRules, - HostGroup, -} from "src/utils/datatypes/globalDataTypes"; - -interface ModalData { - showModal: boolean; - handleModalToggle: () => void; -} - -interface TabData { - tabName: string; - userName: string; // TODO: Change to a more generalistic name -} - -export interface PropsToAdd { - modalData: ModalData; - availableData: - | UserGroup[] - | Netgroup[] - | Roles[] - | HBACRules[] - | SudoRules[] - | HostGroup[]; - groupRepository: unknown[]; - updateGroupRepository: ( - args: - | UserGroup[] - | Netgroup[] - | Roles[] - | HBACRules[] - | SudoRules[] - | HostGroup[] - ) => void; - updateAvOptionsList: (args: unknown[]) => void; - tabData: TabData; -} - -// 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 when assigning -// a new group instead of refering to each type (UserGroup | Netgroup | Roles | HBACRules | -// SudoRules | HostGroup). -interface MemberOfElement { - hostGroup?: string; - name: string; - gid?: string; - status?: string; - description: string; -} - -const MemberOfAddModal = (props: PropsToAdd) => { - // Dual list data - const data = props.availableData.map((d) => d.name); - - // Dual list selector - const [availableOptions, setAvailableOptions] = useState(data); - const [chosenOptions, setChosenOptions] = useState([]); - - const listChange = ( - newAvailableOptions: ReactNode[], - newChosenOptions: ReactNode[] - ) => { - setAvailableOptions(newAvailableOptions.sort()); - setChosenOptions(newChosenOptions.sort()); - props.updateAvOptionsList(newAvailableOptions.sort()); - }; - - const fields = [ - { - id: "dual-list-selector", - pfComponent: ( - listChange(newAvailableOptions, newChosenOptions)} - id="basicSelectorWithSearch" - /> - ), - }, - ]; - - // When clean data, set to original values - const cleanData = () => { - setAvailableOptions(data); - setChosenOptions([]); - }; - - // Clean fields and close modal (To prevent data persistence when reopen modal) - const cleanAndCloseModal = () => { - cleanData(); - props.modalData.handleModalToggle(); - }; - - // Buttons are disabled until the user fills the required fields - const [buttonDisabled, setButtonDisabled] = useState(true); - useEffect(() => { - if (chosenOptions.length > 0) { - setButtonDisabled(false); - } else { - setButtonDisabled(true); - } - }, [chosenOptions]); - - // Get all info from a chosen option - const getInfoFromGroupData = (option: unknown) => { - return props.availableData.find((d) => option === d.name); - }; - - // Add group option - const onClickAddGroupHandler = () => { - chosenOptions.map((opt) => { - const optionData: MemberOfElement | undefined = getInfoFromGroupData(opt); - if (optionData !== undefined) { - // User groups - if (props.tabData.tabName === "User groups") { - props.groupRepository.push({ - name: optionData.name !== undefined && optionData.name, - description: - optionData.description !== undefined && optionData.description, - gid: optionData.gid !== undefined && optionData.gid, - status: optionData.status !== undefined && optionData.status, - } as UserGroup); - // Send updated data to table - props.updateGroupRepository(props.groupRepository as UserGroup[]); - } - // Netgroups - if (props.tabData.tabName === "Netgroups") { - props.groupRepository.push({ - name: optionData.name !== undefined && optionData.name, - description: - optionData.description !== undefined && optionData.description, - gid: optionData.gid !== undefined && optionData.gid, - status: optionData.status !== undefined && optionData.status, - } as Netgroup); - // Send updated data to table - props.updateGroupRepository(props.groupRepository as Netgroup[]); - } - // Roles - if (props.tabData.tabName === "Roles") { - props.groupRepository.push({ - name: optionData.name !== undefined && optionData.name, - description: - optionData.description !== undefined && optionData.description, - gid: optionData.gid !== undefined && optionData.gid, - status: optionData.status !== undefined && optionData.status, - } as Roles); - // Send updated data to table - props.updateGroupRepository(props.groupRepository as Roles[]); - } - // HBAC rules - if (props.tabData.tabName === "HBAC rules") { - props.groupRepository.push({ - name: optionData.name !== undefined && optionData.name, - description: - optionData.description !== undefined && optionData.description, - gid: optionData.gid !== undefined && optionData.gid, - status: optionData.status !== undefined && optionData.status, - } as HBACRules); - // Send updated data to table - props.updateGroupRepository(props.groupRepository as HBACRules[]); - } - // Sudo rules - if (props.tabData.tabName === "Sudo rules") { - props.groupRepository.push({ - name: optionData.name !== undefined && optionData.name, - description: - optionData.description !== undefined && optionData.description, - gid: optionData.gid !== undefined && optionData.gid, - status: optionData.status !== undefined && optionData.status, - } as SudoRules); - // Send updated data to table - props.updateGroupRepository(props.groupRepository as SudoRules[]); - } - // Host groups - if (props.tabData.tabName === "Host groups") { - props.groupRepository.push({ - name: optionData.name !== undefined && optionData.name, - description: - optionData.description !== undefined && optionData.description, - } as HostGroup); - // Send updated data to table - props.updateGroupRepository(props.groupRepository as HostGroup[]); - } - } - }); - // Clean chosen options and close modal - setChosenOptions([]); - props.modalData.handleModalToggle(); - }; - - // Buttons that will be shown at the end of the form - const modalActions = [ - , - , - ]; - - // Render 'MemberOfaddModal' - return ( - - ); -}; - -export default MemberOfAddModal; diff --git a/src/components/MemberOf/MemberOfAddModalUserGroups.tsx b/src/components/MemberOf/MemberOfAddModalUserGroups.tsx new file mode 100644 index 000000000..b5e171d04 --- /dev/null +++ b/src/components/MemberOf/MemberOfAddModalUserGroups.tsx @@ -0,0 +1,141 @@ +import React, { ReactNode, useEffect, useState } from "react"; +// PatternFly +import { + Button, + DualListSelector, + Form, + FormGroup, + Modal, +} from "@patternfly/react-core"; +// Data types +import { UserGroup } from "src/utils/datatypes/globalDataTypes"; + +export interface PropsToAdd { + showModal: boolean; + onCloseModal: () => void; + availableData: UserGroup[]; + groupRepository: unknown[]; + updateGroupRepository: (newList: UserGroup[]) => void; +} + +const MemberOfAddModal = (props: PropsToAdd) => { + // Dual list data + const data = props.availableData.map((d) => d.name); + + // Dual list selector + const [availableOptions, setAvailableOptions] = useState(data); + const [chosenOptions, setChosenOptions] = useState([]); + + const listChange = ( + newAvailableOptions: ReactNode[], + newChosenOptions: ReactNode[] + ) => { + setAvailableOptions(newAvailableOptions.sort()); + setChosenOptions(newChosenOptions.sort()); + }; + + const fields = [ + { + id: "dual-list-selector", + name: "Available options", + pfComponent: ( + listChange(newAvailableOptions, newChosenOptions)} + id="basicSelectorWithSearch" + /> + ), + }, + ]; + + // When clean data, set to original values + const cleanData = () => { + setAvailableOptions(data); + setChosenOptions([]); + }; + + // Clean fields and close modal (To prevent data persistence when reopen modal) + const cleanAndCloseModal = () => { + cleanData(); + props.onCloseModal(); + }; + + // Buttons are disabled until the user fills the required fields + const [buttonDisabled, setButtonDisabled] = useState(true); + useEffect(() => { + if (chosenOptions.length > 0) { + setButtonDisabled(false); + } else { + setButtonDisabled(true); + } + }, [chosenOptions]); + + // Get all info from a chosen option + const getInfoFromGroupData = (option: unknown) => { + return props.availableData.find((d) => option === d.name); + }; + + // Add group option + const onClickAddGroupHandler = () => { + chosenOptions.map((opt) => { + const optionData: UserGroup | undefined = getInfoFromGroupData(opt); + if (optionData !== undefined) { + props.groupRepository.push({ + name: optionData.name !== undefined && optionData.name, + description: + optionData.description !== undefined && optionData.description, + gid: optionData.gid !== undefined && optionData.gid, + } as UserGroup); + // Send updated data to table + props.updateGroupRepository(props.groupRepository as UserGroup[]); + } + }); + // Clean chosen options and close modal + setChosenOptions([]); + props.onCloseModal(); + }; + + // Buttons that will be shown at the end of the form + const modalActions = [ + , + , + ]; + + return ( + +
+ {fields.map((field) => ( + + {field.pfComponent} + + ))} +
+
+ ); +}; + +export default MemberOfAddModal; diff --git a/src/components/MemberOf/MemberOfUserGroups.tsx b/src/components/MemberOf/MemberOfUserGroups.tsx index 9b5ea5826..5aebce8e8 100644 --- a/src/components/MemberOf/MemberOfUserGroups.tsx +++ b/src/components/MemberOf/MemberOfUserGroups.tsx @@ -1,19 +1,16 @@ import React from "react"; -// Repositories -import { userGroupsInitialData } from "src/utils/data/GroupRepositories"; +// PatternFly +import { Pagination, PaginationVariant } from "@patternfly/react-core"; // Data types import { UserGroup } from "src/utils/datatypes/globalDataTypes"; +// Redux +import { useAppSelector } from "src/store/hooks"; // Components import MemberOfToolbarUserGroups, { MembershipDirection, } from "./MemberOfToolbar"; import MemberOfUserGroupsTable from "./MemberOfTableUserGroups"; -import { Pagination, PaginationVariant } from "@patternfly/react-core"; - -interface MemberOfUserGroupsProps { - showAddModal: () => void; - showDeleteModal: () => void; -} +import MemberOfAddModal from "./MemberOfAddModalUserGroups"; function paginate(array: Type[], page: number, perPage: number): Type[] { const startIdx = (page - 1) * perPage; @@ -21,7 +18,35 @@ function paginate(array: Type[], page: number, perPage: number): Type[] { return array.slice(startIdx, endIdx); } +interface TypeWithName { + name: string; +} + +// 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). +function filterUserGroupsData( + list1: Array, + list2: Array +): Type[] { + // User groups + return list1.filter((item) => { + return !list2.some((itm) => { + return item.name === itm.name; + }); + }); +} + +interface MemberOfUserGroupsProps { + usersGroupsFromUser: UserGroup[]; + updateUsersGroupsFromUser: (newList: UserGroup[]) => void; +} + const MemberOfUserGroups = (props: MemberOfUserGroupsProps) => { + const userGroupsFullList = useAppSelector( + (state) => state.usergroups.userGroupList + ); + const [groupsNamesSelected, setGroupsNamesSelected] = React.useState< string[] >([]); @@ -31,17 +56,22 @@ const MemberOfUserGroups = (props: MemberOfUserGroupsProps) => { const [searchValue, setSearchValue] = React.useState(""); - const [usersGroupsFromUser] = React.useState( - userGroupsInitialData - ); - const [membershipDirection, setMembershipDirection] = React.useState("direct"); + const [showAddModal, setShowAddModal] = React.useState(false); + const [showDeleteModal, setShowDeleteModal] = React.useState(false); + // Computed "states" const someItemSelected = groupsNamesSelected.length > 0; - const shownUserGroups = paginate(usersGroupsFromUser, page, perPage); - const showTableRows = usersGroupsFromUser.length > 0; + const shownUserGroups = paginate(props.usersGroupsFromUser, page, perPage); + const showTableRows = props.usersGroupsFromUser.length > 0; + + // Available data to be added as member of + const userGroupsFilteredData: UserGroup[] = filterUserGroupsData( + userGroupsFullList, + props.usersGroupsFromUser + ); return ( <> @@ -50,14 +80,14 @@ const MemberOfUserGroups = (props: MemberOfUserGroupsProps) => { onSearchTextChange={setSearchValue} refreshButtonEnabled={true} deleteButtonEnabled={someItemSelected} - onDeleteButtonClick={props.showDeleteModal} + onDeleteButtonClick={() => setShowDeleteModal(true)} addButtonEnabled={true} - onAddButtonClick={props.showAddModal} + onAddButtonClick={() => setShowAddModal(true)} membershipDirectionEnabled={true} membershipDirection={membershipDirection} onMembershipDirectionChange={setMembershipDirection} helpIconEnabled={true} - totalItems={usersGroupsFromUser.length} + totalItems={props.usersGroupsFromUser.length} perPage={perPage} page={page} onPerPageChange={setPerPage} @@ -71,7 +101,7 @@ const MemberOfUserGroups = (props: MemberOfUserGroupsProps) => { /> { onSetPage={(_e, page) => setPage(page)} onPerPageSelect={(_e, perPage) => setPerPage(perPage)} /> + {showAddModal && ( + setShowAddModal(false)} + availableData={userGroupsFilteredData} + groupRepository={props.usersGroupsFromUser} + updateGroupRepository={(newList: UserGroup[]) => + props.updateUsersGroupsFromUser(newList) + } + /> + )} ); }; diff --git a/src/pages/ActiveUsers/UserMemberOf.tsx b/src/pages/ActiveUsers/UserMemberOf.tsx index fcd716b21..def780509 100644 --- a/src/pages/ActiveUsers/UserMemberOf.tsx +++ b/src/pages/ActiveUsers/UserMemberOf.tsx @@ -43,18 +43,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[]; }; @@ -69,7 +63,9 @@ const UserMemberOf = (props: PropsToUserMemberOf) => { }; // List of default dummy data (for each tab option) - const [userGroupsRepository] = useState(userGroupsInitialData); + const [userGroupsRepository, setUserGroupsRepository] = useState( + userGroupsInitialData + ); const [netgroupsRepository, setNetgroupsRepository] = useState(netgroupsInitialData); const [rolesRepository, setRolesRepository] = useState(rolesInitialData); @@ -88,14 +84,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) => { @@ -130,7 +118,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(); @@ -155,12 +142,7 @@ const UserMemberOf = (props: PropsToUserMemberOf) => { // - The slice of data to show (considering the pagination) // - Number of items for a specific list const updateGroupRepository = ( - groupRepository: - | UserGroup[] - | Netgroup[] - | Roles[] - | HBACRules[] - | SudoRules[] + groupRepository: Netgroup[] | Roles[] | HBACRules[] | SudoRules[] ) => { switch (tabName) { case "Netgroups": @@ -316,7 +298,7 @@ const UserMemberOf = (props: PropsToUserMemberOf) => { const onClickAddHandler = () => { setShowAddModal(true); }; - const onModalToggle = () => { + const onAddModalToggle = () => { setShowAddModal(!showAddModal); }; @@ -397,7 +379,7 @@ const UserMemberOf = (props: PropsToUserMemberOf) => { // - MemberOfAddModal const addModalData = { showModal: showAddModal, - handleModalToggle: onModalToggle, + handleModalToggle: onAddModalToggle, }; const tabData = { @@ -450,8 +432,8 @@ const UserMemberOf = (props: PropsToUserMemberOf) => { } > { - {tabName === "User groups" && ( + {/* {tabName === "User groups" && ( <> {showAddModal && ( { /> )} - )} + )} */} {tabName === "Netgroups" && ( <> {showAddModal && (