Skip to content

Commit

Permalink
Add Sudo rules wrapper to hold real data
Browse files Browse the repository at this point in the history
The `MemberOfSudoRules` component will
hold all the logic related to the Sudo
Rules tab (i.e., table elements, action
buttons, and other components).

Signed-off-by: Carla Martinez <[email protected]>
  • Loading branch information
carma12 committed May 21, 2024
1 parent aa00e28 commit 5e52776
Show file tree
Hide file tree
Showing 6 changed files with 422 additions and 296 deletions.
152 changes: 152 additions & 0 deletions src/components/MemberOf/MemberOfSudoRules.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import React from "react";
// PatternFly
import { Pagination, PaginationVariant } from "@patternfly/react-core";
// Data types
import { User, SudoRule } from "src/utils/datatypes/globalDataTypes";
// Components
import MemberOfToolbar, { MembershipDirection } from "./MemberOfToolbar";
import MemberOfTableSudoRules from "./MemberOfTableSudoRules";
// Hooks
import useAlerts from "src/hooks/useAlerts";
// RPC
import { useGetSudoRulesInfoByNameQuery } from "src/services/rpcSudoRules";
// Utils
import { API_VERSION_BACKUP, paginate } from "src/utils/utils";

interface MemberOfSudoRulesProps {
user: Partial<User>;
isUserDataLoading: boolean;
onRefreshUserData: () => void;
}

const MemberOfSudoRules = (props: MemberOfSudoRulesProps) => {
// Alerts to show in the UI
const alerts = useAlerts();

// Page indexes
const [page, setPage] = React.useState(1);
const [perPage, setPerPage] = React.useState(10);

// Other states
const [sudoRulesSelected, setSudoRulesSelected] = React.useState<string[]>(
[]
);
const [searchValue, setSearchValue] = React.useState("");

// Loaded Sudo rules based on paging and member attributes
const [sudoRules, setSudoRules] = React.useState<SudoRule[]>([]);

// Membership direction and Sudo rules
const [membershipDirection, setMembershipDirection] =
React.useState<MembershipDirection>("direct");

const memberof_sudorule = props.user.memberof_sudorule || [];
const memberofindirect_sudorule = props.user.memberofindirect_sudorule || [];
let sudoRuleNames =
membershipDirection === "direct"
? memberof_sudorule
: memberofindirect_sudorule;
sudoRuleNames = [...sudoRuleNames];

const getSudoRulesNameToLoad = (): string[] => {
let toLoad = [...sudoRuleNames];
toLoad.sort();

// Filter by search
if (searchValue) {
toLoad = toLoad.filter((name) =>
name.toLowerCase().includes(searchValue.toLowerCase())
);
}

// Apply paging
toLoad = paginate(toLoad, page, perPage);
return toLoad;
};

const [sudoRulesNamesToLoad, setSudoRulesNamesToLoad] = React.useState<
string[]
>(getSudoRulesNameToLoad());

// Load Sudo rules
const fullSudoRulesQuery = useGetSudoRulesInfoByNameQuery({
sudoRuleNamesList: sudoRulesNamesToLoad,
no_members: true,
version: API_VERSION_BACKUP,
});

// Reset page on direction change
React.useEffect(() => {
setPage(1);
}, [membershipDirection]);

// Refresh Sudo rules
React.useEffect(() => {
const sudoRulesNames = getSudoRulesNameToLoad();
setSudoRulesNamesToLoad(sudoRulesNames);
}, [props.user, membershipDirection, searchValue, page, perPage]);

React.useEffect(() => {
if (sudoRulesNamesToLoad.length > 0) {
fullSudoRulesQuery.refetch();
}
}, [sudoRulesNamesToLoad]);

// Update Sudo rules
React.useEffect(() => {
if (fullSudoRulesQuery.data && !fullSudoRulesQuery.isFetching) {
setSudoRules(fullSudoRulesQuery.data);
}
}, [fullSudoRulesQuery.data, fullSudoRulesQuery.isFetching]);

// Computed "states"
const someItemSelected = sudoRulesSelected.length > 0;
const showTableRows = sudoRules.length > 0;

return (
<>
<alerts.ManagedAlerts />
<MemberOfToolbar
searchText={searchValue}
onSearchTextChange={setSearchValue}
// eslint-disable-next-line @typescript-eslint/no-empty-function
onSearch={() => {}}
refreshButtonEnabled={true}
onRefreshButtonClick={props.onRefreshUserData}
deleteButtonEnabled={someItemSelected}
// eslint-disable-next-line @typescript-eslint/no-empty-function
onDeleteButtonClick={() => {}}
addButtonEnabled={true}
// eslint-disable-next-line @typescript-eslint/no-empty-function
onAddButtonClick={() => {}}
membershipDirectionEnabled={true}
membershipDirection={membershipDirection}
onMembershipDirectionChange={setMembershipDirection}
helpIconEnabled={true}
totalItems={sudoRuleNames.length}
perPage={perPage}
page={page}
onPerPageChange={setPerPage}
onPageChange={setPage}
/>
<MemberOfTableSudoRules
sudoRules={sudoRules}
checkedItems={sudoRulesSelected}
onCheckItemsChange={setSudoRulesSelected}
showTableRows={showTableRows}
/>
<Pagination
className="pf-v5-u-pb-0 pf-v5-u-pr-md"
itemCount={sudoRuleNames.length}
widgetId="pagination-options-menu-bottom"
perPage={perPage}
page={page}
variant={PaginationVariant.bottom}
onSetPage={(_e, page) => setPage(page)}
onPerPageSelect={(_e, perPage) => setPerPage(perPage)}
/>
</>
);
};

export default MemberOfSudoRules;
116 changes: 116 additions & 0 deletions src/components/MemberOf/MemberOfTableSudoRules.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import React from "react";
// PatternFly
import { Table, Tr, Th, Td, Thead, Tbody } from "@patternfly/react-table";
// Data types
import { SudoRule } 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 MemberOfSudoRulesTableProps {
sudoRules: SudoRule[];
checkedItems?: string[];
onCheckItemsChange?: (checkedItems: string[]) => void;
showTableRows: boolean;
}

// Body
const SudoRulesTableBody = (props: {
sudoRules: SudoRule[];
showCheckboxColumn: boolean;
checkedItems: string[];
onCheckboxChange: (checked: boolean, sudoRuleName: string) => void;
}) => {
const { sudoRules } = props;
return (
<>
{sudoRules.map((sudoRule, index) => (
<Tr key={index} id={sudoRule.cn}>
{props.showCheckboxColumn && (
<Td
select={{
rowIndex: index,
onSelect: (_e, isSelected) =>
props.onCheckboxChange(isSelected, sudoRule.cn),
isSelected: props.checkedItems.includes(sudoRule.cn),
}}
/>
)}
<Td>{sudoRule.cn}</Td>
<Td>{sudoRule.ipaenabledflag ? "Enabled" : "Disabled"}</Td>
<Td>{parseEmptyString(sudoRule.description)}</Td>
</Tr>
))}
</>
);
};

// Define skeleton
const skeleton = (
<SkeletonOnTableLayout
rows={4}
colSpan={4}
screenreaderText={"Loading table rows"}
/>
);

export default function MemberOfSudoRulesTable(
props: MemberOfSudoRulesTableProps
) {
const { sudoRules } = props;
if (!sudoRules || sudoRules.length <= 0) {
return null; // return empty placeholder
}

const showCheckboxColumn = props.onCheckItemsChange !== undefined;

const onCheckboxChange = (checked: boolean, groupName: string) => {
const originalItems = props.checkedItems || [];
let newItems: string[] = [];
if (checked) {
newItems = [...originalItems, groupName];
} else {
newItems = originalItems.filter((name) => name !== groupName);
}
if (props.onCheckItemsChange) {
props.onCheckItemsChange(newItems);
}
};

return (
<Table
aria-label="member of table"
name="cn"
variant="compact"
borders
className={"pf-v5-u-mt-md"}
id="member-of-table"
isStickyHeader
>
<Thead>
<Tr>
{props.onCheckItemsChange && <Th />}
<Th modifier="wrap">Rule name</Th>
<Th modifier="wrap">Status</Th>
<Th modifier="wrap">Description</Th>
</Tr>
</Thead>
<Tbody>
{!props.showTableRows ? (
skeleton
) : sudoRules.length === 0 ? (
<EmptyBodyTable />
) : (
<SudoRulesTableBody
sudoRules={sudoRules}
showCheckboxColumn={showCheckboxColumn}
onCheckboxChange={onCheckboxChange}
checkedItems={props.checkedItems || []}
/>
)}
</Tbody>
</Table>
);
}
Loading

0 comments on commit 5e52776

Please sign in to comment.