forked from freeipa/freeipa-webui
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Sudo rules wrapper to hold real data
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
Showing
6 changed files
with
422 additions
and
296 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} |
Oops, something went wrong.