Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Is a member of] User groups sync data #289

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Button, DualListSelector } from "@patternfly/react-core";
import ModalWithFormLayout from "src/components/layouts/ModalWithFormLayout";
// Data types
import {
UserGroup,
UserGroupOld,
Netgroup,
Roles,
HBACRules,
Expand All @@ -26,7 +26,7 @@ interface TabData {
export interface PropsToAdd {
modalData: ModalData;
availableData:
| UserGroup[]
| UserGroupOld[]
| Netgroup[]
| Roles[]
| HBACRules[]
Expand All @@ -35,7 +35,7 @@ export interface PropsToAdd {
groupRepository: unknown[];
updateGroupRepository: (
args:
| UserGroup[]
| UserGroupOld[]
| Netgroup[]
| Roles[]
| HBACRules[]
Expand All @@ -50,7 +50,7 @@ export interface PropsToAdd {
// 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 |
// a new group instead of refering to each type (UserGroupOld | Netgroup | Roles | HBACRules |
// SudoRules | HostGroup).
interface MemberOfElement {
hostGroup?: string;
Expand Down Expand Up @@ -136,9 +136,9 @@ const MemberOfAddModal = (props: PropsToAdd) => {
optionData.description !== undefined && optionData.description,
gid: optionData.gid !== undefined && optionData.gid,
status: optionData.status !== undefined && optionData.status,
} as UserGroup);
} as UserGroupOld);
// Send updated data to table
props.updateGroupRepository(props.groupRepository as UserGroup[]);
props.updateGroupRepository(props.groupRepository as UserGroupOld[]);
}
// Netgroups
if (props.tabData.tabName === "Netgroups") {
Expand Down
143 changes: 143 additions & 0 deletions src/components/MemberOf/MemberOfAddModalUserGroups.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
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.cn);

// Dual list selector
const [availableOptions, setAvailableOptions] = useState<ReactNode[]>(data);
const [chosenOptions, setChosenOptions] = useState<ReactNode[]>([]);

const listChange = (
newAvailableOptions: ReactNode[],
newChosenOptions: ReactNode[]
) => {
setAvailableOptions(newAvailableOptions.sort());
setChosenOptions(newChosenOptions.sort());
};

const fields = [
{
id: "dual-list-selector",
name: "Available options",
pfComponent: (
<DualListSelector
isSearchable
availableOptions={availableOptions}
chosenOptions={chosenOptions}
onListChange={(
_event,
newAvailableOptions: ReactNode[],
newChosenOptions: ReactNode[]
) => 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.cn);
};

// Add group option
const onClickAddGroupHandler = () => {
const groupRepository = [...props.groupRepository];
chosenOptions.map((opt) => {
const optionData: UserGroup | undefined = getInfoFromGroupData(opt);
if (optionData !== undefined) {
groupRepository.push({
cn: optionData.cn !== undefined && optionData.cn,
description:
optionData.description !== undefined && optionData.description,
gidnumber: optionData.gidnumber !== undefined && optionData.gidnumber,
dn: optionData.dn !== undefined && optionData.dn,
} as UserGroup);
// Send updated data to table
props.updateGroupRepository(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 = [
<Button
key="add-new-user"
variant="secondary"
isDisabled={buttonDisabled}
form="modal-form"
onClick={onClickAddGroupHandler}
>
Add
</Button>,
<Button key="cancel-new-user" variant="link" onClick={cleanAndCloseModal}>
Cancel
</Button>,
];

return (
<Modal
variant={"medium"}
position={"top"}
positionOffset={"76px"}
isOpen={props.showModal}
onClose={props.onCloseModal}
actions={modalActions}
aria-label="Add member of user group modal"
>
<Form id={"is-member-of-add-modal"}>
{fields.map((field) => (
<FormGroup key={field.id} label={field.name} fieldId={field.id}>
{field.pfComponent}
</FormGroup>
))}
</Form>
</Modal>
);
};

export default MemberOfAddModal;
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import MemberOfDeletedGroupsTable from "src/components/MemberOf/MemberOfDeletedG
// 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).
// interface instead of each type (UserGroupOld | Netgroup | Roles | HBACRules | SudoRules).
interface MemberOfElement {
name: string;
gid?: string;
Expand Down
113 changes: 113 additions & 0 deletions src/components/MemberOf/MemberOfDeleteModalUserGroups.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import React from "react";
// PatternFly
import {
TextContent,
Text,
TextVariants,
Button,
Modal,
Form,
FormGroup,
} from "@patternfly/react-core";
// Tables
import MemberOfDeletedGroupsTable from "src/components/MemberOf/MemberOfDeletedGroupsTable";
// Data types
import { UserGroup } from "src/utils/datatypes/globalDataTypes";

interface PropsToDelete {
showModal: boolean;
onCloseModal: () => void;
tabName: string;
groupNamesToDelete: string[];
groupRepository: UserGroup[];
updateGroupRepository: (args: UserGroup[]) => void;
updateGroupNamesToDelete: (args: string[]) => void;
}

const MemberOfDeleteModal = (props: PropsToDelete) => {
// Given a single group name, obtain full info to be sent and shown on the deletion table
const getGroupInfoByName = (groupName: string) => {
const res = props.groupRepository.filter((group) => group.cn === groupName);
return res[0];
};

// Obtain full info of groups to delete
const getListOfGroupsToDelete = () => {
const groupsToDelete: UserGroup[] = [];
props.groupNamesToDelete.map((groupName) =>
groupsToDelete.push(getGroupInfoByName(groupName))
);
return groupsToDelete;
};

// Groups to delete list
const groupsToDelete: UserGroup[] = getListOfGroupsToDelete();

// Delete groups
const deleteGroups = () => {
let generalUpdatedGroupList = props.groupRepository;
props.groupNamesToDelete.map((groupName) => {
const updatedGroupList = generalUpdatedGroupList.filter(
(grp) => grp.cn !== groupName
);
// If not empty, replace groupList by new array
if (updatedGroupList) {
generalUpdatedGroupList = updatedGroupList;
}
});
props.updateGroupRepository(generalUpdatedGroupList);
props.updateGroupNamesToDelete([]);
props.onCloseModal();
};

// Modal actions
const modalActionsDelete: JSX.Element[] = [
<Button
key="delete-groups"
variant="danger"
onClick={deleteGroups}
form="active-users-remove-groups-modal"
>
Delete
</Button>,
<Button
key="cancel-remove-group"
variant="link"
onClick={props.onCloseModal}
>
Cancel
</Button>,
];

return (
<Modal
variant={"medium"}
position={"top"}
positionOffset={"76px"}
title={"Remove " + props.tabName}
isOpen={props.showModal}
onClose={props.onCloseModal}
actions={modalActionsDelete}
aria-label="Delete member modal"
>
<Form id={"is-member-of-delete-modal"}>
<FormGroup key={"question-text"} fieldId={"question-text"}>
<TextContent>
<Text component={TextVariants.p}>
Are you sure you want to remove the selected entries from the
list?
</Text>
</TextContent>
</FormGroup>
<FormGroup key={"deleted-users-table"} fieldId={"deleted-users-table"}>
<MemberOfDeletedGroupsTable
groupsToDelete={groupsToDelete}
tabName={props.tabName}
/>
</FormGroup>
</Form>
</Modal>
);
};

export default MemberOfDeleteModal;
38 changes: 20 additions & 18 deletions src/components/MemberOf/MemberOfDeletedGroupsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,14 @@ import React, { useState } from "react";
import { Td, Th, Tr } from "@patternfly/react-table";
// Layout
import TableLayout from "src/components/layouts/TableLayout";

// 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 assigned in the
// 'PropsToDeleteOnTable' interface instead of each type (UserGroup | Netgroup | Roles
// | HBACRules | SudoRules).
interface MemberOfElement {
name: string;
gid?: string;
status?: string;
description: string;
}
// Data types
import {
UserGroup,
Netgroup,
Roles,
HBACRules,
SudoRules,
} from "src/utils/datatypes/globalDataTypes";

// Interface for Column names
// All variables are defined as optional as it won't need to be explicitely defined when
Expand All @@ -28,7 +23,12 @@ interface ColumnNames {
}

interface PropsToDeleteOnTable {
groupsToDelete: MemberOfElement[];
groupsToDelete:
| UserGroup[]
| Netgroup[]
| Roles[]
| HBACRules[]
| SudoRules[];
tabName: string;
}

Expand Down Expand Up @@ -85,10 +85,12 @@ const MemberOfDeletedGroupsTable = (props: PropsToDeleteOnTable) => {
);

const body = props.groupsToDelete.map((group) => (
<Tr key={group.name} id={group.name}>
{group.name && <Td dataLabel={group.name}>{group.name}</Td>}
{group.gid && <Td dataLabel={group.gid}>{group.gid}</Td>}
{group.status && <Td dataLabel={group.status}>{group.status}</Td>}
<Tr key={group.cn} id={group.cn}>
{group.cn && <Td dataLabel={group.cn}>{group.cn}</Td>}
{group.gidnumber && (
<Td dataLabel={group.gidnumber}>{group.gidnumber}</Td>
)}
{group.dn && <Td dataLabel={group.dn}>{group.dn}</Td>}
{group.description && (
<Td dataLabel={group.description}>{group.description}</Td>
)}
Expand Down
10 changes: 6 additions & 4 deletions src/components/MemberOf/MemberOfTableUserGroups.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { UserGroup } from "src/utils/datatypes/globalDataTypes";
// Components
import SkeletonOnTableLayout from "../layouts/Skeleton/SkeletonOnTableLayout";
import EmptyBodyTable from "../tables/EmptyBodyTable";
// Utils
import { parseEmptyString } from "src/utils/utils";

export interface MemberOfUserGroupsTableProps {
userGroups: UserGroup[];
Expand All @@ -29,12 +31,12 @@ const UserGroupsTableBody = (props: {
select={{
rowIndex: index,
onSelect: (_e, isSelected) =>
props.onCheckboxChange(isSelected, userGroup.name),
isSelected: props.checkedItems.includes(userGroup.name),
props.onCheckboxChange(isSelected, userGroup.cn),
isSelected: props.checkedItems.includes(userGroup.cn),
}}
/>
<Td>{userGroup.name}</Td>
<Td>{userGroup.gid}</Td>
<Td>{userGroup.cn}</Td>
<Td>{parseEmptyString(userGroup.gidnumber)}</Td>
<Td>{userGroup.description}</Td>
</Tr>
))}
Expand Down
Loading
Loading