@patternfly/react-core#DropdownItem JavaScript Examples
The following examples show how to use
@patternfly/react-core#DropdownItem.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: AddConnectionMenu.js From cockpit-wicked with GNU General Public License v2.0 | 6 votes |
AddConnectionMenu = () => { const [isOpen, setOpen] = useState(false); const [formComponent, setFormComponent] = useState(null); const Component = formComponents[formComponent]; const toggle = () => { setOpen(!isOpen); if (isOpen) { const toggle = document.getElementById('add-buttons-toggle'); toggle.focus(); } }; const dropdownItems = [ <DropdownItem key="bond" component="button" onClick={() => setFormComponent('BondForm')}> {_("Bond")} </DropdownItem>, <DropdownItem key="bridge" component="button" onClick={() => setFormComponent('BridgeForm')}> {_("Bridge")} </DropdownItem>, <DropdownItem key="vlan" component="button" onClick={() => setFormComponent('VlanForm')}> {_("VLAN")} </DropdownItem>, ]; return ( <> <Dropdown onSelect={toggle} toggle={<DropdownToggle id="add-buttons-toggle" onToggle={toggle}>{_("Add")}</DropdownToggle>} isOpen={isOpen} dropdownItems={dropdownItems} /> { Component && <Component isOpen onClose={() => setFormComponent(null)} /> } </> ); }
Example #2
Source File: widget-components.js From ibutsu-server with MIT License | 6 votes |
render() {
const { isOpen } = this.state;
const { showDescription } = this.state;
let dropdownItems = [];
this.props.dropdownItems.forEach( (item) => {
dropdownItems.push(<DropdownItem onClick={this.onSelect} key={item}> {item} </DropdownItem>)
});
return (
<div data-id='widget-param-dropdown'>
{showDescription &&
<Text component='h3'>{this.props.description || this.props.tooltip || ""}</Text>
}
<Tooltip content={this.props.tooltip}>
<Dropdown
direction={this.direction}
isOpen={isOpen}
dropdownItems={dropdownItems}
toggle={
<DropdownToggle id="toggle-dropdown" onToggle={this.onToggle}>
{this.state.value}
</DropdownToggle>
}
/>
</Tooltip>
</div>
);
}
Example #3
Source File: DetailsHeader.js From edge-frontend with Apache License 2.0 | 6 votes |
dropdownItems = (data, imageVersion, openUpdateWizard) => {
const imageData = imageVersion ? imageVersion : data?.images?.[0];
const actionsArray = [];
imageData?.image?.ID &&
actionsArray.push(
<DropdownItem
key="create-new-version-button"
component="button"
onClick={() => openUpdateWizard(imageData?.image?.ID)}
>
Create new version
</DropdownItem>
);
imageData?.image?.Installer?.ImageBuildISOURL &&
actionsArray.push(
<DropdownItem key="download-button" component="button">
<Text
className="force-text-black remove-underline"
component="a"
href={imageData?.image?.Installer?.ImageBuildISOURL}
rel="noopener noreferrer"
target="_blank"
>
Download installable .iso for newest image
</Text>
</DropdownItem>
);
return actionsArray;
}
Example #4
Source File: ToolbarKebab.js From edge-frontend with Apache License 2.0 | 6 votes |
ToolbarKebab = ({ kebabItems }) => {
const [kebabIsOpen, setKebabIsOpen] = useState(false);
const dropdownItems = kebabItems.map(
({ title, isDisabled, onClick }, index) => (
<DropdownItem
key={index}
onClick={onClick ? onClick : () => {}}
isDisabled={isDisabled}
>
{title}
</DropdownItem>
)
);
return (
<ToolbarItem>
<Dropdown
toggle={
<KebabToggle onToggle={() => setKebabIsOpen((prev) => !prev)} />
}
isOpen={kebabIsOpen}
isPlain
dropdownItems={dropdownItems}
/>
</ToolbarItem>
);
}
Example #5
Source File: user-dropdown.js From ibutsu-server with MIT License | 6 votes |
render() {
const dropdownItems = [
<DropdownItem key="profile" component={<Link to="/profile">Profile</Link>} />,
<DropdownItem key="logout" component="button" onClick={this.logout}>Logout</DropdownItem>
];
if (this.state.isSuperAdmin) {
dropdownItems.splice(1, 0, <DropdownItem key="admin" component={<Link to="/admin">Administration</Link>} />);
}
return (
<Dropdown
onSelect={this.onDropdownSelect}
toggle={
<DropdownToggle
id="user-dropdown-toggle"
onToggle={this.onDropdownToggle}
toggleIndicator={CaretDownIcon}
icon={<UserIcon />}
isPlain={true}
>
{this.state.displayName}
</DropdownToggle>
}
isOpen={this.state.isDropdownOpen}
dropdownItems={dropdownItems}
/>
);
}
Example #6
Source File: classification-dropdown.js From ibutsu-server with MIT License | 6 votes |
render() {
const testResult = this.state.testResult;
return (
<Dropdown
toggle={<DropdownToggle onToggle={this.onClassificationToggle}>{CLASSIFICATION[testResult.metadata && testResult.metadata.classification] || '(unset)'}</DropdownToggle>}
onSelect={this.onClassificationSelect}
isOpen={this.state.isClassificationOpen}
dropdownItems={Object.keys(CLASSIFICATION).map((key) => <DropdownItem key={key} value={key}>{CLASSIFICATION[key]}</DropdownItem>)}
/>
)
}
Example #7
Source File: classification-dropdown.js From ibutsu-server with MIT License | 6 votes |
render() {
const { selectedResults } = this.props;
return (
<Dropdown
toggle={<DropdownToggle isDisabled={selectedResults.length === 0} onToggle={this.onClassificationToggle}>{'Classify Selected Failures'}</DropdownToggle>}
onSelect={this.onClassificationSelect}
isOpen={this.state.isClassificationOpen}
dropdownItems={Object.keys(CLASSIFICATION).map((key) => <DropdownItem key={key} value={key}>{CLASSIFICATION[key]}</DropdownItem>)}
/>
)
}
Example #8
Source File: WirelessEssidSelect.js From cockpit-wicked with GNU General Public License v2.0 | 5 votes |
WirelessEssidSelect = ({ essid, setEssid, iface }) => {
const [isOpen, setIsOpen] = useState(false);
const [essidList, setEssidList] = useState(undefined);
const refreshList = (name) => {
fetchEssidList(name)
.then(result => {
const list = [...new Set([...result])];
setEssidList(list.sort());
})
.catch(console.error);
};
const onToggle = isOpen => {
if (isOpen) {
setEssidList(undefined);
refreshList(iface.name);
}
setIsOpen(isOpen);
};
const onSelect = (selection) => {
setEssid(selection);
setIsOpen(false);
};
const renderOptions = () => {
if (!essidList) {
return [
<DropdownItem isDisabled key="scanning" icon={<Spinner size="md" />}>
{_("Scanning...")}
</DropdownItem>
];
}
if (essidList.length === 0) {
return [
<DropdownItem isDisabled key="no-networks-found" icon={<ExclamationIcon />}>
{_("No networks found")}
</DropdownItem>
];
}
return essidList.map(value => <DropdownItem key={value} onClick={() => onSelect(value)}>{value}</DropdownItem>);
};
return (
<InputGroup>
<TextInput id="essid" value={essid} onChange={setEssid} type="text" aria-label="Essid" />
<Dropdown
position={DropdownPosition.right}
isOpen={isOpen}
dropdownItems={renderOptions()}
toggle={
<DropdownToggle id="essid-scanned-list" toggleIndicator={null} onToggle={onToggle} aria-label="Essid scanned list">
<SearchIcon />
</DropdownToggle>
}
/>
</InputGroup>
);
}
Example #9
Source File: ImageDetailActions.js From edge-frontend with Apache License 2.0 | 5 votes |
ImageActions = ({ imageData, openUpdateWizard }) => {
const [isOpen, setIsOpen] = useState(false);
const dropdownItems = [
<DropdownItem href={imageData?.Installer?.ImageBuildISOURL} key="link">
<Text className="force-text-black">Download</Text>
</DropdownItem>,
];
const handleToggle = (isOpen) => setIsOpen(isOpen);
const handleSelect = () => {
setIsOpen((prevState) => !prevState);
};
const handleUpdate = () => {
openUpdateWizard(imageData.ID);
};
return (
<>
<SplitItem>
<Button onClick={handleUpdate} variant="secondary">
Update
</Button>
{imageData?.Installer?.ImageBuildISOURL ? (
<Dropdown
position="right"
onSelect={handleSelect}
toggle={
<KebabToggle onToggle={handleToggle} id="image-detail-kebab" />
}
isOpen={isOpen}
isPlain
dropdownItems={dropdownItems}
/>
) : null}
</SplitItem>
</>
);
}
Example #10
Source File: BulkSelect.js From edge-frontend with Apache License 2.0 | 5 votes |
BulkSelect = ({
checkedRows,
handleBulkSelect,
handlePageSelect,
handleNoneSelect,
displayedRowsLength,
}) => {
const isAllSelected = checkedRows.length === displayedRowsLength;
const isPartiallySelected = checkedRows.length > 0 ? null : false;
const [selectAllToggle, setSelectAllToggle] = useState(false);
return (
<>
<ToolbarItem variant="bulk-select">
<Dropdown
toggle={
<DropdownToggle
id="stacked-example-toggle"
splitButtonItems={[
<DropdownToggleCheckbox
id="example-checkbox-2"
key="split-checkbox"
aria-label="Select all"
isChecked={isAllSelected ? true : isPartiallySelected}
onChange={isAllSelected ? handleNoneSelect : handlePageSelect}
>
{checkedRows.length > 0 && `${checkedRows.length} selected`}
</DropdownToggleCheckbox>,
]}
onToggle={() => setSelectAllToggle((prevState) => !prevState)}
/>
}
isOpen={selectAllToggle}
dropdownItems={[
<DropdownItem key="all" onClick={handleBulkSelect}>
Select all
</DropdownItem>,
<DropdownItem
key="page"
onClick={handlePageSelect}
isDisabled={isAllSelected}
>
Select page
</DropdownItem>,
<DropdownItem
key="none"
onClick={handleNoneSelect}
isDisabled={checkedRows.length === 0}
>
Select none
</DropdownItem>,
]}
/>
</ToolbarItem>
</>
);
}
Example #11
Source File: certificateActions.jsx From cockpit-certificates with GNU Lesser General Public License v2.1 | 5 votes |
CertificateActions = ({ cas, certs, cert, certPath, addAlert, appOnValueChanged, idPrefix }) => { const [dropdownOpen, setDropdownOpen] = useState(false); const [showRemoveModal, setShowRemoveModal] = useState(false); const [showResubmitModal, setShowResubmitModal] = useState(false); const dropdownItems = [ <DropdownItem key={`${idPrefix}-resubmit`} id={`${idPrefix}-resubmit`} onClick={() => setShowResubmitModal(true)}> {_("Resubmit")} </DropdownItem>, <DropdownItem className="pf-m-danger" key={`${idPrefix}-remove`} id={`${idPrefix}-remove`} onClick={() => setShowRemoveModal(true)}> {_("Remove")} </DropdownItem>, ]; return ( <> <Dropdown onSelect={() => setDropdownOpen(!dropdownOpen)} id={`${idPrefix}-action-kebab`} toggle={ <KebabToggle key={`${idPrefix}-action-kebab-toggle`} onToggle={() => setDropdownOpen(!dropdownOpen)} /> } isOpen={dropdownOpen} position="right" dropdownItems={dropdownItems} isPlain /> {showRemoveModal && <RemoveModal onClose={() => setShowRemoveModal(false)} certs={certs} cert={cert} certPath={certPath} addAlert={addAlert} appOnValueChanged={appOnValueChanged} idPrefix={idPrefix} />} {showResubmitModal && <ResubmitCertificateModal onClose={() => setShowResubmitModal(false)} cas={cas} addAlert={addAlert} cert={cert} certPath={certPath} />} </> ); }
Example #12
Source File: Recommendation.js From ocp-advisor-frontend with Apache License 2.0 | 4 votes |
Recommendation = ({ rule, ack, clusters, match }) => {
const intl = useIntl();
const dispatch = useDispatch();
const notify = (data) => dispatch(addNotification(data));
const recId = match.params.recommendationId;
const [disableRuleModalOpen, setDisableRuleModalOpen] = useState(false);
const [actionsDropdownOpen, setActionsDropdownOpen] = useState(false);
const [viewSystemsModalOpen, setViewSystemsModalOpen] = useState(false);
// rule's info
const {
isError,
isUninitialized,
isLoading,
isFetching,
isSuccess,
data,
refetch,
} = rule;
// justification note, last time acknowledged, etc.
const { data: ackData, isFetching: ackIsFetching, refetch: refetchAck } = ack;
const ruleDate = new Date(ackData?.updated_at || ackData?.created_at);
// affected and acked clusters lists
const {
data: clustersData,
isFetching: clustersIsFetching,
refetch: refetchClusters,
} = clusters;
const content =
isSuccess && data ? adjustOCPRule(data.content, recId) : undefined;
const ackedClusters =
!clustersIsFetching && clustersData ? clustersData.disabled : undefined;
const afterDisableFn = async () => {
refetch();
refetchAck();
refetchClusters();
};
const handleModalToggle = (disableRuleModalOpen) => {
setDisableRuleModalOpen(disableRuleModalOpen);
};
const enableRecForHosts = async ({ uuids }) => {
try {
const requests = uuids.map((uuid) =>
enableRuleForCluster({ uuid, recId })
);
await Promise.all(requests);
refetch();
refetchAck();
refetchClusters();
notify({
variant: 'success',
timeout: true,
dismissable: true,
title: intl.formatMessage(messages.recSuccessfullyEnabledForCluster),
});
} catch (error) {
notify({
variant: 'danger',
dismissable: true,
title: intl.formatMessage(messages.error),
description: `${error}`,
});
}
};
const enableRule = async (rule) => {
try {
await Delete(`${BASE_URL}/v2/ack/${rule.data.content.rule_id}/`);
notify({
variant: 'success',
timeout: true,
dismissable: true,
title: intl.formatMessage(messages.recSuccessfullyEnabled),
});
refetch();
} catch (error) {
handleModalToggle(false);
notify({
variant: 'danger',
dismissable: true,
title: intl.formatMessage(messages.error),
description: `${error}`,
});
}
};
const messagesValues = useMemo(
() => (content ? mapContentToValues(intl, content) : {}),
[intl, content]
);
return (
<React.Fragment>
{viewSystemsModalOpen && (
<ViewHostAcks
handleModalToggle={(toggleModal) =>
setViewSystemsModalOpen(toggleModal)
}
isModalOpen={viewSystemsModalOpen}
clusters={clusters}
afterFn={() => refetchClusters()}
recId={recId}
/>
)}
{disableRuleModalOpen && (
<DisableRule
handleModalToggle={handleModalToggle}
isModalOpen={disableRuleModalOpen}
rule={content}
afterFn={afterDisableFn}
/>
)}
<PageHeader className="pageHeaderOverride">
<Breadcrumbs current={content?.description || recId} />
</PageHeader>
{(isUninitialized || isLoading || isFetching) && (
<Main>
<Loading />
</Main>
)}
{isError && (
<Main>
<ErrorState />
</Main>
)}
{!(isUninitialized || isLoading || isFetching) && isSuccess && (
<React.Fragment>
<Main className="pf-m-light pf-u-pt-sm">
<RuleDetails
messages={formatMessages(
intl,
RuleDetailsMessagesKeys,
messagesValues
)}
product={AdvisorProduct.ocp}
rule={content}
isDetailsPage
header={
<React.Fragment>
<PageHeaderTitle
title={
<React.Fragment>
{content.description} <RuleLabels rule={content} />
</React.Fragment>
}
/>
<p>
{intl.formatMessage(messages.rulesDetailsPubishdate, {
date: (
<DateFormat
date={new Date(content.publish_date)}
type="onlyDate"
/>
),
})}
{content.tags &&
(Array.isArray(content.tags) ? (
<LabelGroup
className="categoryLabels"
numLabels={1}
isCompact
>
{content.tags.reduce((labels, tag) => {
if (RULE_CATEGORIES[tag]) {
labels.push(
<Label
key={`label-${tag}`}
color="blue"
isCompact
>
{
FILTER_CATEGORIES.category.values[
RULE_CATEGORIES[tag] - 1
].label
}
</Label>
);
}
return labels;
}, [])}
</LabelGroup>
) : (
<Label isCompact>{content.tags}</Label>
))}
</p>
</React.Fragment>
}
onVoteClick={async (rule, rating) =>
await Post(`${BASE_URL}/v2/rating`, {}, { rule, rating })
}
>
<Flex>
<FlexItem align={{ default: 'alignRight' }}>
<Dropdown
className="ins-c-rec-details__actions_dropdown"
onSelect={() =>
setActionsDropdownOpen(!actionsDropdownOpen)
}
position="right"
ouiaId="actions"
toggle={
<DropdownToggle
onToggle={(actionsDropdownOpen) =>
setActionsDropdownOpen(actionsDropdownOpen)
}
toggleIndicator={CaretDownIcon}
>
{intl.formatMessage(messages.actions)}
</DropdownToggle>
}
isOpen={actionsDropdownOpen}
dropdownItems={
content?.disabled
? [
<DropdownItem
key="link"
ouiaId="enable"
onClick={() => {
enableRule(rule);
}}
>
{intl.formatMessage(messages.enableRule)}
</DropdownItem>,
]
: [
<DropdownItem
key="link"
ouiaId="disable"
onClick={() => {
handleModalToggle(true);
}}
>
{intl.formatMessage(messages.disableRule)}
</DropdownItem>,
]
}
/>
</FlexItem>
</Flex>
</RuleDetails>
</Main>
<Main>
<React.Fragment>
{(content?.hosts_acked_count ||
ackedClusters?.length > 0 ||
content?.disabled) && (
<Card className="cardOverride" ouiaId="hosts-acked">
<CardHeader>
<Title headingLevel="h4" size="xl">
<BellSlashIcon size="sm" />
{intl.formatMessage(
(content?.hosts_acked_count ||
ackedClusters?.length > 0) &&
!content?.disabled
? messages.ruleIsDisabledForClusters
: messages.ruleIsDisabled
)}
</Title>
</CardHeader>
<CardBody>
{(content?.hosts_acked_count ||
ackedClusters?.length > 0) &&
!content?.disabled ? (
<React.Fragment>
{intl.formatMessage(
messages.ruleIsDisabledForClustersBody,
{
clusters: ackedClusters?.length,
}
)}
{!clustersIsFetching && ackedClusters?.length > 0 ? (
<React.Fragment>
<Button
isInline
variant="link"
onClick={() => setViewSystemsModalOpen(true)}
ouiaId="view-clusters"
>
{intl.formatMessage(messages.viewClusters)}
</Button>
</React.Fragment>
) : (
<OneLineLoader />
)}
</React.Fragment>
) : (
!ackIsFetching &&
ackData && (
<React.Fragment>
{ackData?.justification
? intl.formatMessage(
messages.ruleIsDisabledWithJustificaiton,
{
date: (
<span>
<DateFormat
date={ruleDate}
type="onlyDate"
/>
</span>
),
reason: ackData.justification,
}
)
: intl.formatMessage(
messages.ruleIsDisabledWithoutJustificaiton,
{
date: (
<span>
<DateFormat
date={ruleDate}
type="onlyDate"
/>
</span>
),
}
)}
</React.Fragment>
)
)}
</CardBody>
<CardFooter>
{(content?.hosts_acked_count ||
ackedClusters?.length > 0) &&
!content?.disabled ? (
!clustersIsFetching && ackedClusters ? (
<Button
isInline
variant="link"
onClick={() =>
enableRecForHosts({
uuids: ackedClusters.map((c) => c.cluster_id),
})
}
ouiaId="enable"
>
{intl.formatMessage(messages.enableRuleForClusters)}
</Button>
) : (
<OneLineLoader />
)
) : (
<Button
isInline
variant="link"
onClick={() => enableRule(rule)}
ouiaId="enable"
>
{intl.formatMessage(messages.enableRule)}
</Button>
)}
</CardFooter>
</Card>
)}
{!content?.disabled && (
<React.Fragment>
<Title className="titleOverride" headingLevel="h3" size="2xl">
{intl.formatMessage(messages.affectedClusters)}
</Title>
<AffectedClustersTable
query={clusters}
rule={content}
afterDisableFn={afterDisableFn}
/>
</React.Fragment>
)}
{content?.disabled && (
<MessageState
icon={BellSlashIcon}
title={intl.formatMessage(messages.ruleIsDisabled)}
text={intl.formatMessage(messages.ruleIsDisabledBody)}
/>
)}
</React.Fragment>
</Main>
</React.Fragment>
)}
</React.Fragment>
);
}
Example #13
Source File: test-history.js From ibutsu-server with MIT License | 4 votes |
render() {
const {
columns,
rows,
onlyFailures,
historySummary,
dropdownSelection
} = this.state;
const pagination = {
pageSize: this.state.pageSize,
page: this.state.page,
totalItems: this.state.totalItems
}
const dropdownValues = Object.assign({
"1 Week": 0.25,
"2 Weeks": 0.5,
"1 Month": 1.0,
"2 Months": 2.0,
"3 Months": 3.0,
"5 Months": 5.0
})
let dropdownItems = [];
Object.keys(dropdownValues).forEach(key => {
dropdownItems.push(
<DropdownItem key={key} value={dropdownValues[key]} autoFocus={key === dropdownSelection}>
{key}
</DropdownItem>
)
});
return (
<Card className="pf-u-mt-lg">
<CardHeader>
<Flex style={{ width: '100%' }}>
<FlexItem grow={{ default: 'grow' }}>
<TextContent>
<Text component="h2" className="pf-c-title pf-m-xl">
Test History
</Text>
</TextContent>
</FlexItem>
<FlexItem>
<TextContent>
<Checkbox id="only-failures" label="Only show failures/errors" isChecked={onlyFailures} aria-label="only-failures-checkbox" onChange={this.onFailuresCheck}/>
</TextContent>
</FlexItem>
<FlexItem>
<Dropdown
toggle={<DropdownToggle isDisabled={false} onToggle={this.onDropdownToggle}>Time range</DropdownToggle>}
onSelect={this.onDropdownSelect}
isOpen={this.state.isDropdownOpen}
dropdownItems={dropdownItems}
/>
</FlexItem>
<FlexItem>
<Button variant="secondary" onClick={this.refreshResults}>Refresh results</Button>
</FlexItem>
</Flex>
</CardHeader>
<CardBody>
<FilterTable
columns={columns}
rows={rows}
pagination={pagination}
isEmpty={this.state.isEmpty}
isError={this.state.isError}
onCollapse={this.onCollapse}
onSetPage={this.setPage}
onSetPageSize={this.pageSizeSelect}
canSelectAll={false}
variant={TableVariant.compact}
activeFilters={this.state.filters}
filters={[
<Text key="summary" component="h4">
Summary:
{historySummary &&
<RunSummary summary={historySummary}/>
}
</Text>,
<Text key="last-passed" component="h4">Last passed: {this.state.lastPassedDate}</Text>,
]}
onRemoveFilter={this.removeFilter}
hideFilters={["project_id", "result", "test_id"]}
/>
</CardBody>
</Card>
);
}
Example #14
Source File: AccessRequestsTable.js From access-requests-frontend with Apache License 2.0 | 4 votes |
AccessRequestsTable = ({ isInternal }) => {
const columns = isInternal
? [
'Request ID',
'Account number',
'Start date',
'End date',
'Created',
'Status',
]
: [
'Request ID',
'First name',
'Last name',
'Start date',
'End date',
'Created',
'Decision',
];
// Sorting
const [activeSortIndex, setActiveSortIndex] = React.useState(
isInternal ? 4 : 5
);
const [activeSortDirection, setActiveSortDirection] = React.useState('desc');
const onSort = (_ev, index, direction) => {
setActiveSortIndex(index);
setActiveSortDirection(direction);
};
// Pagination
const [page, setPage] = React.useState(1);
const [perPage, setPerPage] = React.useState(20);
const AccessRequestsPagination = ({ id }) => (
<Pagination
itemCount={numRows}
perPage={perPage}
page={page}
onSetPage={(_ev, pageNumber) => setPage(pageNumber)}
id={'access-requests-table-pagination-' + id}
variant={id}
perPageOptions={[5, 10, 20, 50].map((n) => ({ title: n, value: n }))}
onPerPageSelect={(_ev, perPage) => {
setPage(1);
setPerPage(perPage);
}}
isCompact={id === 'top'}
/>
);
AccessRequestsPagination.propTypes = {
id: PropTypes.string,
};
// Filtering
const [isDropdownOpen, setIsDropdownOpen] = React.useState(false);
const [filterColumn, setFilterColumn] = React.useState(
columns[isInternal ? 1 : 6]
);
const [isSelectOpen, setIsSelectOpen] = React.useState(false);
const [statusSelections, setStatusSelections] = React.useState([]);
// Harder than it needs to be to match rest of RBAC which doesn't wait
// for user to click a button or press enter.
const [accountFilter, setAccountFilter] = React.useState('');
const [filtersDirty, setFiltersDirty] = React.useState(false);
const hasFilters = statusSelections.length > 0 || accountFilter;
// Row loading
const [isLoading, setIsLoading] = React.useState(true);
const [numRows, setNumRows] = React.useState(0);
const [rows, setRows] = React.useState([]);
const dispatch = useDispatch();
const fetchAccessRequests = () => {
setIsLoading(true);
const listUrl = new URL(
`${window.location.origin}${API_BASE}/cross-account-requests/`
);
isInternal
? listUrl.searchParams.append('query_by', 'user_id')
: listUrl.searchParams.append('query_by', 'target_account');
listUrl.searchParams.append('offset', (page - 1) * perPage);
listUrl.searchParams.append('limit', perPage);
// https://github.com/RedHatInsights/insights-rbac/blob/master/rbac/api/cross_access/view.py
if (accountFilter) {
listUrl.searchParams.append('account', accountFilter);
}
if (statusSelections.length > 0) {
listUrl.searchParams.append('status', statusSelections.join(','));
}
const orderBy = `${activeSortDirection === 'desc' ? '-' : ''}${columns[
activeSortIndex
]
.toLowerCase()
.replace(' ', '_')}`;
listUrl.searchParams.append('order_by', orderBy);
apiInstance
.get(listUrl.href, { headers: { Accept: 'application/json' } })
.then((res) => {
setNumRows(res.meta.count);
setRows(
res.data.map((d) =>
isInternal
? [
d.request_id,
d.target_account,
d.start_date,
d.end_date,
d.created,
d.status,
]
: [
d.request_id,
d.first_name,
d.last_name,
d.start_date,
d.end_date,
d.created,
d.status,
]
)
);
setIsLoading(false);
})
.catch((err) => {
setIsLoading(false);
dispatch(
addNotification({
variant: 'danger',
title: 'Could not list access requests',
description: err.message,
})
);
});
};
const debouncedAccountFilter = useDebounce(accountFilter, 400);
React.useEffect(() => {
fetchAccessRequests();
}, [
debouncedAccountFilter,
statusSelections,
activeSortIndex,
activeSortDirection,
perPage,
page,
]);
// Modal actions
const [openModal, setOpenModal] = React.useState({ type: null });
const onModalClose = (isChanged) => {
setOpenModal({ type: null });
if (isChanged) {
fetchAccessRequests();
}
};
const modals = (
<React.Fragment>
{openModal.type === 'cancel' && (
<CancelRequestModal
requestId={openModal.requestId}
onClose={onModalClose}
/>
)}
{['edit', 'create'].includes(openModal.type) && (
<EditRequestModal
variant={openModal.type}
requestId={openModal.requestId}
onClose={onModalClose}
/>
)}
</React.Fragment>
);
// Rendering
const createButton = isInternal && (
<Button variant="primary" onClick={() => setOpenModal({ type: 'create' })}>
Create request
</Button>
);
if (rows.length === 0 && !isLoading && !filtersDirty) {
return (
<Bullseye style={{ height: 'auto' }} className="pf-u-mt-lg">
<EmptyState variant="large">
<EmptyStateIcon icon={PlusCircleIcon} />
<Title headingLevel="h3" size="lg">
{isInternal ? 'No access requests' : 'You have no access requests'}
</Title>
<EmptyStateBody>
{isInternal
? 'Click the button below to create an access request.'
: 'You have no pending Red Hat access requests.'}
</EmptyStateBody>
{createButton}
</EmptyState>
{modals}
</Bullseye>
);
}
const selectLabelId = 'filter-status';
const selectPlaceholder = `Filter by ${uncapitalize(
columns[columns.length - 1]
)}`;
const clearFiltersButton = (
<Button
variant="link"
onClick={() => {
setStatusSelections([]);
setAccountFilter('');
setPage(1);
}}
>
Clear filters
</Button>
);
const toolbar = (
<Toolbar id="access-requests-table-toolbar">
<ToolbarContent>
<ToolbarItem>
<InputGroup>
<Dropdown
isOpen={isDropdownOpen}
onSelect={(ev) => {
setIsDropdownOpen(false);
setFilterColumn(ev.target.value);
setIsSelectOpen(false);
setFiltersDirty(true);
}}
toggle={
<DropdownToggle
onToggle={(isOpen) => setIsDropdownOpen(isOpen)}
>
<FilterIcon /> {filterColumn}
</DropdownToggle>
}
// https://marvelapp.com/prototype/257je526/screen/74764732
dropdownItems={(isInternal ? [1, 5] : [6])
.map((i) => columns[i])
.map((colName) => (
// Filterable columns are RequestID, AccountID, and Status
<DropdownItem
key={colName}
value={colName}
component="button"
>
{capitalize(colName)}
</DropdownItem>
))}
/>
{['Status', 'Decision'].includes(filterColumn) && (
<React.Fragment>
<span id={selectLabelId} hidden>
{selectPlaceholder}
</span>
<Select
aria-labelledby={selectLabelId}
variant="checkbox"
aria-label="Select statuses"
onToggle={(isOpen) => setIsSelectOpen(isOpen)}
onSelect={(_ev, selection) => {
setFiltersDirty(true);
if (statusSelections.includes(selection)) {
setStatusSelections(
statusSelections.filter((s) => s !== selection)
);
} else {
setStatusSelections([...statusSelections, selection]);
}
setPage(1);
}}
isOpen={isSelectOpen}
selections={Array.from(statusSelections)}
isCheckboxSelectionBadgeHidden
placeholderText={selectPlaceholder}
>
{statuses.map((status) => (
<SelectOption key={status} value={status}>
{capitalize(status)}
</SelectOption>
))}
</Select>
</React.Fragment>
)}
{filterColumn === 'Account number' && (
<form
style={{ display: 'flex' }}
onSubmit={(ev) => ev.preventDefault()}
>
<TextInput
name={`${filterColumn}-filter`}
id={`${filterColumn}-filter`}
type="search"
iconVariant="search"
placeholder={`Filter by ${uncapitalize(filterColumn)}`}
aria-label={`${filterColumn} search input`}
value={accountFilter}
onChange={(val) => {
setAccountFilter(val), setFiltersDirty(true), setPage(1);
}}
/>
</form>
)}
</InputGroup>
</ToolbarItem>
<ToolbarItem>{createButton}</ToolbarItem>
<ToolbarItem variant="pagination" align={{ default: 'alignRight' }}>
<AccessRequestsPagination id="top" />
</ToolbarItem>
</ToolbarContent>
<ToolbarContent>
<ChipGroup categoryName="Status">
{statusSelections.map((status) => (
<Chip
key={status}
onClick={() => {
setStatusSelections(
statusSelections.filter((s) => s !== status)
);
setPage(1);
}}
>
{status}
</Chip>
))}
</ChipGroup>
{accountFilter && (
<ChipGroup categoryName="Account number">
<Chip
onClick={() => {
setAccountFilter(''), setPage(1);
}}
>
{accountFilter}
</Chip>
</ChipGroup>
)}
{hasFilters && clearFiltersButton}
</ToolbarContent>
</Toolbar>
);
function getColumnWidth(columnIndex) {
if (isInternal) {
return columnIndex === 0 ? 30 : 15;
}
return [0, 6].includes(columnIndex) ? 20 : 10;
}
const { url } = useRouteMatch();
const table = (
<TableComposable aria-label="Access requests table" variant="compact">
<Thead>
<Tr>
{columns.map((column, columnIndex) => (
<Th
key={columnIndex}
{...(!column.includes('name') &&
column !== 'Decision' && {
sort: {
sortBy: {
index: activeSortIndex,
direction: activeSortDirection,
},
onSort,
columnIndex,
},
})}
width={getColumnWidth(columnIndex)}
>
{column}
</Th>
))}
{isInternal && <Th />}
</Tr>
</Thead>
<Tbody>
{isLoading
? [...Array(rows.length || perPage).keys()].map((i) => (
<Tr key={i}>
{columns.map((name, j) => (
<Td key={j} dataLabel={name}>
<div
style={{ height: '30px' }}
className="ins-c-skeleton ins-c-skeleton__md"
>
{' '}
</div>
</Td>
))}
</Tr>
))
: rows.map((row, rowIndex) => (
<Tr key={rowIndex}>
<Td dataLabel={columns[0]}>
<Link to={`${url}${url.endsWith('/') ? '' : '/'}${row[0]}`}>
{row[0]}
</Link>
</Td>
<Td dataLabel={columns[1]}>{row[1]}</Td>
<Td dataLabel={columns[2]}>{row[2]}</Td>
<Td dataLabel={columns[3]}>{row[3]}</Td>
<Td dataLabel={columns[4]}>{row[4]}</Td>
{isInternal ? (
<Td dataLabel={columns[5]}>
<StatusLabel
requestId={row[0]}
status={row[5]}
onLabelClick={() => {
setStatusSelections([
...statusSelections.filter((s) => s !== status),
status,
]);
setPage(1);
}}
hideActions
/>
</Td>
) : (
<Td dataLabel={columns[5]}>{row[5]}</Td>
)}
{isInternal ? (
// Different actions based on status
<Td
actions={getInternalActions(row[5], row[0], setOpenModal)}
/>
) : (
<Td dataLabel={columns[6]}>
<StatusLabel requestId={row[0]} status={row[6]} />
</Td>
)}
</Tr>
))}
{rows.length === 0 && hasFilters && (
<Tr>
<Td colSpan={columns.length}>
<EmptyState variant="small">
<EmptyStateIcon icon={SearchIcon} />
<Title headingLevel="h2" size="lg">
No matching requests found
</Title>
<EmptyStateBody>
No results match the filter criteria. Remove all filters or
clear all filters to show results.
</EmptyStateBody>
{clearFiltersButton}
</EmptyState>
</Td>
</Tr>
)}
</Tbody>
</TableComposable>
);
return (
<React.Fragment>
{toolbar}
{table}
<AccessRequestsPagination id="bottom" />
{modals}
</React.Fragment>
);
}
Example #15
Source File: ClusterHeader.js From ocp-advisor-frontend with Apache License 2.0 | 4 votes |
ClusterHeader = ({ clusterId, clusterData, clusterInfo }) => {
const location = window.location;
const [isOpen, setIsOpen] = useState(false);
const intl = useIntl();
// subscribe to the cluster data query
const {
isUninitialized: isUninitializedCluster,
isFetching: isFetchingCluster,
data: cluster,
} = clusterData;
const {
isUninitialized: isUninitializedInfo,
isFetching: isFetchingInfo,
data: info,
} = clusterInfo;
const redirectOCM = (clusterId) => {
location.assign(
location.origin +
(location.pathname.includes('beta') ? `/beta` : '') +
`/openshift/details/${clusterId}`
);
};
const dropDownItems = [
<DropdownItem key="link" onClick={() => redirectOCM(clusterId)}>
<snap>{intl.formatMessage(messages.clusterDetailsRedirect)}</snap>
</DropdownItem>,
];
return (
<Grid id="cluster-header" md={12} hasGutter>
<GridItem span={8}>
<Title
size="2xl"
headingLevel="h1"
id="cluster-header-title"
ouiaId="cluster-name"
>
{isUninitializedInfo || isFetchingInfo ? (
<Skeleton size="sm" />
) : (
info?.display_name || clusterId
)}
</Title>
</GridItem>
<GridItem span={4} id="cluster-header-dropdown">
<Dropdown
position="right"
onSelect={() => setIsOpen(!isOpen)}
autoFocus={false}
isOpen={isOpen}
toggle={
<DropdownToggle
id="toggle-id-2"
onToggle={(isOpen) => setIsOpen(isOpen)}
>
{intl.formatMessage(messages.dropDownActionSingleCluster)}
</DropdownToggle>
}
dropdownItems={dropDownItems}
/>
</GridItem>
<GridItem>
<Stack>
<StackItem id="cluster-header-uuid">
<span>UUID:</span> <span>{clusterId}</span>
</StackItem>
<StackItem id="cluster-header-last-seen">
<span>{intl.formatMessage(messages.lastSeen)}: </span>
<span>
{isUninitializedCluster || isFetchingCluster ? (
<OneLineLoader />
) : cluster?.report?.meta?.last_checked_at ? (
<DateFormat
date={cluster?.report?.meta?.last_checked_at}
type="exact"
/>
) : (
intl.formatMessage(messages.unknown)
)}
</span>
</StackItem>
</Stack>
</GridItem>
</Grid>
);
}
Example #16
Source File: GroupsDetail.js From edge-frontend with Apache License 2.0 | 4 votes |
GroupsDetail = () => {
const dispatch = useDispatch();
const params = useParams();
const history = useHistory();
const { groupId } = params;
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
const [removeModal, setRemoveModal] = useState({
isOpen: false,
name: '',
deviceId: null,
});
const [updateModal, setUpdateModal] = useState({
isOpen: false,
deviceData: null,
imageData: null,
});
const [response, fetchDevices] = useApi({
api: getGroupById,
id: groupId,
tableReload: true,
});
const { data, isLoading, hasError } = response;
const groupName = data?.DeviceGroup?.Name;
const [deviceIds, getDeviceIds] = useState([]);
const [hasModalSubmitted, setHasModalSubmitted] = useState(false);
const [modalState, setModalState] = useState({ id: null, name: '' });
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [isRenameModalOpen, setIsRenameModalOpen] = useState(false);
const handleDeleteModal = (id, name) => {
setModalState({ id, name });
setIsDeleteModalOpen(true);
};
const handleRenameModal = (id, name) => {
setModalState({ id, name });
setIsRenameModalOpen(true);
};
const removeDeviceLabel = () =>
`Do you want to remove ${
deviceIds.length > 0
? `${deviceIds.length} system${deviceIds.length === 1 ? '' : 's'}`
: `${removeModal.name}`
} from ${groupName}?`;
useEffect(() => {
history.push({
pathname: history.location.pathname,
search: stateToUrlSearch('add_system_modal=true', isAddModalOpen),
});
}, [isAddModalOpen]);
const handleSingleDeviceRemoval = () => {
const statusMessages = {
onSuccess: {
title: 'Success',
description: `${removeModal.name} has been removed successfully`,
},
onError: { title: 'Error', description: 'Failed to remove device' },
};
apiWithToast(
dispatch,
() => removeDeviceFromGroupById(groupId, removeModal.deviceId),
statusMessages
);
setTimeout(() => setHasModalSubmitted(true), 800);
};
const handleBulkDeviceRemoval = () => {
const statusMessages = {
onSuccess: {
title: 'Success',
description: `${deviceIds.length} systems have been removed successfully`,
},
onError: { title: 'Error', description: 'failed to remove systems' },
};
apiWithToast(
dispatch,
() =>
removeDevicesFromGroup(
parseInt(groupId),
deviceIds.map((device) => ({ ID: device.deviceID }))
),
statusMessages
);
setTimeout(() => setHasModalSubmitted(true), 800);
};
return (
<>
<PageHeader className="pf-m-light">
{groupName ? (
<Breadcrumb>
<BreadcrumbItem>
<Link to={`${paths['fleet-management']}`}>Groups</Link>
</BreadcrumbItem>
<BreadcrumbItem>{groupName}</BreadcrumbItem>
</Breadcrumb>
) : (
<Breadcrumb isActive>
<Skeleton width="100px" />
</Breadcrumb>
)}
<Flex justifyContent={{ default: 'justifyContentSpaceBetween' }}>
<FlexItem>
{groupName ? (
<PageHeaderTitle title={groupName} />
) : (
<Skeleton width="150px" />
)}
</FlexItem>
<FlexItem>
<Dropdown
position={DropdownPosition.right}
toggle={
<DropdownToggle
id="image-set-details-dropdown"
toggleIndicator={CaretDownIcon}
onToggle={(newState) => setIsDropdownOpen(newState)}
isDisabled={false}
>
Actions
</DropdownToggle>
}
isOpen={isDropdownOpen}
dropdownItems={[
<DropdownItem
key="delete-device-group"
onClick={() => handleDeleteModal(groupId, groupName)}
>
Delete group
</DropdownItem>,
<DropdownItem
key="rename-device-group"
onClick={() => handleRenameModal(groupId, groupName)}
>
Rename group
</DropdownItem>,
<DropdownItem
key="update-all-devices"
isDisabled={canUpdateSelectedDevices({
deviceData: data?.DevicesView?.devices?.map((device) => ({
imageSetId: device?.ImageSetID,
})),
imageData: data?.DevicesView?.devices?.some(
(device) => device.ImageID
),
})}
onClick={() => {
setIsDropdownOpen(false);
setUpdateModal((prevState) => ({
...prevState,
isOpen: true,
deviceData: data?.DevicesView?.devices?.map((device) => ({
id: device?.DeviceUUID,
display_name:
device?.DeviceName === ''
? 'localhost'
: device?.DeviceName,
})),
imageSetId: data?.DevicesView?.devices.find(
(device) => device.ImageSetID
)?.ImageSetID,
}));
}}
>
Update
</DropdownItem>,
]}
/>
</FlexItem>
</Flex>
</PageHeader>
<Main className="edge-devices">
{!emptyStateNoFliters(
isLoading,
data?.DeviceGroup?.Devices.length,
history
) ? (
<DeviceTable
data={data?.DevicesView?.devices || []}
count={data?.DevicesView?.total}
isLoading={isLoading}
hasError={hasError}
hasCheckbox={true}
handleSingleDeviceRemoval={handleSingleDeviceRemoval}
kebabItems={[
{
isDisabled: !(deviceIds.length > 0),
title: 'Remove from group',
onClick: () =>
setRemoveModal({
name: '',
deviceId: null,
isOpen: true,
}),
},
{
isDisabled: canUpdateSelectedDevices({
deviceData: deviceIds,
imageData: deviceIds[0]?.updateImageData,
}),
title: 'Update selected',
onClick: () =>
setUpdateModal((prevState) => ({
...prevState,
isOpen: true,
deviceData: [...deviceIds],
imageSetId: deviceIds.find((device) => device?.imageSetId)
.imageSetId,
})),
},
]}
selectedItems={getDeviceIds}
setRemoveModal={setRemoveModal}
setIsAddModalOpen={setIsAddModalOpen}
setUpdateModal={setUpdateModal}
hasModalSubmitted={hasModalSubmitted}
setHasModalSubmitted={setHasModalSubmitted}
fetchDevices={fetchDevices}
isAddSystemsView={true}
/>
) : (
<Flex justifyContent={{ default: 'justifyContentCenter' }}>
<Empty
icon="plus"
title="Add systems to the group"
body="Create groups to help manage your systems more effectively."
primaryAction={{
text: 'Add systems',
click: () => setIsAddModalOpen(true),
}}
secondaryActions={[
{
type: 'link',
title: 'Learn more about system groups',
link: 'https://access.redhat.com/documentation/en-us/edge_management/2022/html-single/working_with_systems_in_the_edge_management_application/index',
},
]}
/>
</Flex>
)}
</Main>
{isAddModalOpen && (
<AddSystemsToGroupModal
groupId={groupId}
closeModal={() => setIsAddModalOpen(false)}
isOpen={isAddModalOpen}
reloadData={fetchDevices}
groupName={data?.DeviceGroup?.Name}
/>
)}
{removeModal.isOpen && (
<Modal
isOpen={removeModal.isOpen}
openModal={() => setRemoveModal(false)}
title={'Remove from group'}
submitLabel={'Remove'}
variant="danger"
schema={{
fields: [
{
component: componentTypes.PLAIN_TEXT,
name: 'warning-text',
label: removeDeviceLabel(),
},
],
}}
onSubmit={
removeModal.deviceId
? handleSingleDeviceRemoval
: handleBulkDeviceRemoval
}
reloadData={fetchDevices}
/>
)}
{updateModal.isOpen && (
<Suspense
fallback={
<Bullseye>
<Spinner />
</Bullseye>
}
>
<UpdateDeviceModal
navigateBack={() => {
history.push({ pathname: history.location.pathname });
setUpdateModal((prevState) => {
return {
...prevState,
isOpen: false,
};
});
}}
setUpdateModal={setUpdateModal}
updateModal={updateModal}
refreshTable={fetchDevices}
/>
</Suspense>
)}
{isDeleteModalOpen && (
<DeleteGroupModal
isModalOpen={isDeleteModalOpen}
setIsModalOpen={setIsDeleteModalOpen}
reloadData={() => history.push(paths['fleet-management'])}
modalState={modalState}
/>
)}
{isRenameModalOpen && (
<RenameGroupModal
isModalOpen={isRenameModalOpen}
setIsModalOpen={setIsRenameModalOpen}
reloadData={() => fetchDevices()}
modalState={modalState}
/>
)}
</>
);
}
Example #17
Source File: AccessRequestDetailsPage.js From access-requests-frontend with Apache License 2.0 | 4 votes |
BaseAccessRequestDetailsPage = ({ isInternal }) => {
const [request, setRequest] = React.useState();
const { requestId } = useParams();
const dispatch = useDispatch();
React.useEffect(() => {
apiInstance
.get(
`${API_BASE}/cross-account-requests/${requestId}/${
isInternal ? '?query_by=user_id' : '?query_by=target_account'
}`,
{ headers: { Accept: 'application/json' } }
)
.then((res) => {
if (res.errors) {
throw Error(res.errors.map((e) => e.detail).join('\n'));
}
setRequest(res);
})
.catch((err) => {
dispatch(
addNotification({
variant: 'danger',
title: 'Could not load access request',
description: err.message,
})
);
});
}, []);
// Modal actions
const [openModal, setOpenModal] = React.useState({ type: null });
const onModalClose = () => setOpenModal({ type: null });
const actions = getInternalActions(
request && request.status,
requestId,
setOpenModal
);
const [isDropdownOpen, setIsDropdownOpen] = React.useState(false);
const requestDisplayProps = [
...(isInternal
? ['request_id', 'target_account']
: ['first_name', 'last_name']),
'start_date',
'end_date',
'created',
];
return (
<React.Fragment>
<PageSection variant="light">
<Breadcrumb>
<BreadcrumbItem
render={() => (
<Link to={isInternal ? '/' : '/access-requests'}>
{!isInternal && 'Red Hat '}Access Requests
</Link>
)}
/>
<BreadcrumbItem>{requestId}</BreadcrumbItem>
</Breadcrumb>
<Flex direction={{ default: 'column', md: 'row' }}>
<FlexItem grow={{ default: 'grow' }}>
<Title headingLevel="h1" size="2xl" className="pf-u-pt-md">
{requestId}
</Title>
</FlexItem>
{isInternal && actions.items.length > 0 && (
<FlexItem alignSelf={{ default: 'alignRight' }}>
<Dropdown
position="right"
toggle={
<KebabToggle
onToggle={() => setIsDropdownOpen(!isDropdownOpen)}
id="actions-toggle"
/>
}
isOpen={isDropdownOpen}
isPlain
dropdownItems={actions.items.map(({ title, onClick }) => (
<DropdownItem
key={title}
component="button"
onClick={onClick}
>
{title}
</DropdownItem>
))}
isDisabled={actions.disable}
/>
</FlexItem>
)}
</Flex>
</PageSection>
<PageSection>
<Flex
spaceItems={{ xl: 'spaceItemsLg' }}
direction={{ default: 'column', lg: 'row' }}
>
<FlexItem
flex={{ default: 'flex_1' }}
alignSelf={{ default: 'alignSelfStretch' }}
>
<Card ouiaId="request-details" style={{ height: '100%' }}>
<CardTitle>
<Title headingLevel="h2" size="xl">
Request details
</Title>
</CardTitle>
<CardBody>
{!request ? (
<Spinner size="xl" />
) : (
<React.Fragment>
<div className="pf-u-pb-md">
{isInternal ? (
<div>
<label>
<b>Request status</b>
</label>
<br />
<Label
className="pf-u-mt-sm"
{...getLabelProps(request.status)}
>
{capitalize(request.status)}
</Label>
</div>
) : (
<React.Fragment>
<label>
<b>Request decision</b>
</label>
<br />
<StatusLabel
requestId={requestId}
status={request.status}
/>
</React.Fragment>
)}
</div>
{requestDisplayProps.map((prop, key) => (
<div className="pf-u-pb-md" key={key}>
<label>
<b>
{capitalize(
prop.replace(/_/g, ' ').replace('id', 'ID')
)}
</b>
</label>
<br />
<div>{request[prop]}</div>
</div>
))}
</React.Fragment>
)}
</CardBody>
</Card>
</FlexItem>
<FlexItem
flex={{ default: 'flex_3' }}
grow={{ default: 'grow' }}
alignSelf={{ default: 'alignSelfStretch' }}
>
<Card ouiaId="request-roles" style={{ height: '100%' }}>
<CardTitle>
<Title headingLevel="h2" size="xl">
Roles requested
</Title>
</CardTitle>
<CardBody>
{!request ? (
<Spinner size="xl" />
) : (
<MUARolesTable roles={request.roles} />
)}
</CardBody>
</Card>
</FlexItem>
</Flex>
</PageSection>
{openModal.type === 'cancel' && (
<CancelRequestModal requestId={requestId} onClose={onModalClose} />
)}
{openModal.type === 'edit' && (
<EditRequestModal
variant="edit"
requestId={requestId}
onClose={onModalClose}
/>
)}
</React.Fragment>
);
}
Example #18
Source File: MUARolesTable.js From access-requests-frontend with Apache License 2.0 | 4 votes |
MUARolesTable = ({
roles: selectedRoles,
setRoles: setSelectedRoles,
}) => {
const isReadOnly = setSelectedRoles === undefined;
const columns = ['Role name', 'Role description', 'Permissions'];
const [rows, setRows] = React.useState(Array.from(rolesCache));
const [applications, setApplications] = React.useState(applicationsCache);
React.useEffect(() => {
if (rolesCache.length === 0 || applicationsCache.length === 0) {
apiInstance
.get(
`${API_BASE}/roles/?limit=9999&order_by=display_name&add_fields=groups_in_count`,
{ headers: { Accept: 'application/json' } }
)
.then(({ data }) => {
data.forEach((role) => {
role.isExpanded = false;
role.permissions = role.accessCount;
});
rolesCache = data.map((role) => Object.assign({}, role));
setRows(data);
// Build application filter from data
const apps = Array.from(
data
.map((role) => role.applications)
.flat()
.reduce((acc, cur) => {
acc.add(cur);
return acc;
}, new Set())
).sort();
applicationsCache = apps;
setApplications(apps);
})
.catch((err) =>
dispatch(
addNotification({
variant: 'danger',
title: 'Could not fetch roles list',
description: err.message,
})
)
);
}
}, []);
// Sorting
const [activeSortIndex, setActiveSortIndex] = React.useState('name');
const [activeSortDirection, setActiveSortDirection] = React.useState('asc');
const onSort = (_ev, index, direction) => {
setActiveSortIndex(index);
setActiveSortDirection(direction);
};
// Filtering
const [isDropdownOpen, setIsDropdownOpen] = React.useState(false);
const [filterColumn, setFilterColumn] = React.useState(columns[0]);
const [isSelectOpen, setIsSelectOpen] = React.useState(false);
const [appSelections, setAppSelections] = React.useState([]);
const [nameFilter, setNameFilter] = React.useState('');
const hasFilters = appSelections.length > 0 || nameFilter;
const selectLabelId = 'filter-application';
const selectPlaceholder = 'Filter by application';
const selectedNames = selectedRoles.map((role) => role.display_name);
const filteredRows = rows
.filter((row) =>
appSelections.length > 0
? row.applications.find((app) => appSelections.includes(app))
: true
)
.filter((row) => row.name.toLowerCase().includes(nameFilter))
.filter((row) =>
isReadOnly ? selectedNames.includes(row.display_name) : true
);
// Pagination
const [page, setPage] = React.useState(1);
const [perPage, setPerPage] = React.useState(10);
const AccessRequestsPagination = ({ id }) => (
<Pagination
itemCount={filteredRows.length}
perPage={perPage}
page={page}
onSetPage={(_ev, pageNumber) => setPage(pageNumber)}
id={'access-requests-roles-table-pagination-' + id}
variant={id}
onPerPageSelect={(_ev, perPage) => {
setPage(1);
setPerPage(perPage);
}}
isCompact={id === 'top'}
/>
);
AccessRequestsPagination.propTypes = {
id: PropTypes.string,
};
const pagedRows = filteredRows
.sort((a, b) => {
if (typeof a[activeSortIndex] === 'number') {
// numeric sort
if (activeSortDirection === 'asc') {
return a[activeSortIndex] - b[activeSortIndex];
}
return b[activeSortIndex] - a[activeSortIndex];
} else {
// string sort
if (activeSortDirection === 'asc') {
return (a[activeSortIndex] + '').localeCompare(b[activeSortIndex]);
}
return (b[activeSortIndex] + '').localeCompare(a[activeSortIndex]);
}
})
.slice((page - 1) * perPage, page * perPage);
// Selecting
const [isBulkSelectOpen, setIsBulkSelectOpen] = React.useState(false);
const anySelected = selectedRoles.length > 0;
const someChecked = anySelected ? null : false;
const isChecked =
selectedRoles.length === filteredRows.length && selectedRoles.length > 0
? true
: someChecked;
const onSelect = (_ev, isSelected, rowId) => {
const changed = pagedRows[rowId].display_name;
if (isSelected) {
setSelectedRoles(selectedRoles.concat(changed));
} else {
setSelectedRoles(selectedRoles.filter((role) => role !== changed));
}
};
const onSelectAll = (_ev, isSelected) => {
if (isSelected) {
setSelectedRoles(filteredRows.map((row) => row.display_name));
} else {
setSelectedRoles([]);
}
};
const clearFiltersButton = (
<Button
variant="link"
onClick={() => {
setAppSelections([]);
setNameFilter('');
}}
>
Clear filters
</Button>
);
const roleToolbar = isReadOnly ? null : (
<Toolbar id="access-requests-roles-table-toolbar">
<ToolbarContent>
<ToolbarItem>
<Dropdown
onSelect={() => setIsBulkSelectOpen(!isBulkSelectOpen)}
position="left"
toggle={
<DropdownToggle
splitButtonItems={[
<DropdownToggleCheckbox
key="a"
id="example-checkbox-2"
aria-label={anySelected ? 'Deselect all' : 'Select all'}
isChecked={isChecked}
onClick={() => onSelectAll(null, !anySelected)}
/>,
]}
onToggle={(isOpen) => setIsBulkSelectOpen(isOpen)}
isDisabled={rows.length === 0}
>
{selectedRoles.length !== 0 && (
<React.Fragment>
{selectedRoles.length} selected
</React.Fragment>
)}
</DropdownToggle>
}
isOpen={isBulkSelectOpen}
dropdownItems={[
<DropdownItem key="0" onClick={() => onSelectAll(null, false)}>
Select none (0 items)
</DropdownItem>,
<DropdownItem
key="1"
onClick={() =>
setSelectedRoles(
selectedRoles.concat(pagedRows.map((r) => r.display_name))
)
}
>
Select page ({Math.min(pagedRows.length, perPage)} items)
</DropdownItem>,
<DropdownItem key="2" onClick={() => onSelectAll(null, true)}>
Select all ({filteredRows.length} items)
</DropdownItem>,
]}
/>
</ToolbarItem>
<ToolbarItem>
<InputGroup>
<Dropdown
isOpen={isDropdownOpen}
onSelect={(ev) => {
setIsDropdownOpen(false);
setFilterColumn(ev.target.value);
setIsSelectOpen(false);
}}
toggle={
<DropdownToggle
onToggle={(isOpen) => setIsDropdownOpen(isOpen)}
>
<FilterIcon /> {filterColumn}
</DropdownToggle>
}
dropdownItems={['Role name', 'Application'].map((colName) => (
// Filterable columns are RequestID, AccountID, and Status
<DropdownItem key={colName} value={colName} component="button">
{capitalize(colName)}
</DropdownItem>
))}
/>
{filterColumn === 'Application' ? (
<React.Fragment>
<span id={selectLabelId} hidden>
{selectPlaceholder}
</span>
<Select
aria-labelledby={selectLabelId}
variant="checkbox"
aria-label="Select applications"
onToggle={(isOpen) => setIsSelectOpen(isOpen)}
onSelect={(_ev, selection) => {
if (appSelections.includes(selection)) {
setAppSelections(
appSelections.filter((s) => s !== selection)
);
} else {
setAppSelections([...appSelections, selection]);
}
}}
isOpen={isSelectOpen}
selections={appSelections}
isCheckboxSelectionBadgeHidden
placeholderText={selectPlaceholder}
style={{ maxHeight: '400px', overflowY: 'auto' }}
>
{applications.map((app) => (
<SelectOption key={app} value={app}>
{capitalize(app.replace(/-/g, ' '))}
</SelectOption>
))}
</Select>
</React.Fragment>
) : (
<TextInput
name="rolesSearch"
id="rolesSearch"
type="search"
iconVariant="search"
aria-label="Search input"
placeholder="Filter by role name"
value={nameFilter}
onChange={(val) => setNameFilter(val)}
/>
)}
</InputGroup>
</ToolbarItem>
<ToolbarItem variant="pagination" align={{ default: 'alignRight' }}>
<AccessRequestsPagination id="top" />
</ToolbarItem>
</ToolbarContent>
{hasFilters && (
<ToolbarContent>
{nameFilter && (
<ChipGroup categoryName="Role name">
<Chip onClick={() => setNameFilter('')}>{nameFilter}</Chip>
</ChipGroup>
)}
{appSelections.length > 0 && (
<ChipGroup categoryName="Status">
{appSelections.map((status) => (
<Chip
key={status}
onClick={() =>
setAppSelections(appSelections.filter((s) => s !== status))
}
>
{status}
</Chip>
))}
</ChipGroup>
)}
{clearFiltersButton}
</ToolbarContent>
)}
</Toolbar>
);
const expandedColumns = ['Application', 'Resource type', 'Operation'];
const dispatch = useDispatch();
const onExpand = (row) => {
row.isExpanded = !row.isExpanded;
setRows([...rows]);
if (!row.access) {
apiInstance
.get(`${API_BASE}/roles/${row.uuid}/`, {
headers: { Accept: 'application/json' },
})
.then((res) => {
row.access = res.access.map((a) => a.permission.split(':'));
setRows([...rows]);
})
.catch((err) =>
dispatch(
addNotification({
variant: 'danger',
title: `Could not fetch permission list for ${row.name}.`,
description: err.message,
})
)
);
}
};
const roleTable = (
<TableComposable aria-label="My user access roles" variant="compact">
<Thead>
<Tr>
{!isReadOnly && <Th />}
<Th
width={30}
sort={{
sortBy: {
index: activeSortIndex,
direction: activeSortDirection,
},
onSort,
columnIndex: 'name',
}}
>
{columns[0]}
</Th>
<Th
width={50}
sort={{
sortBy: {
index: activeSortIndex,
direction: activeSortDirection,
},
onSort,
columnIndex: 'description',
}}
>
{columns[1]}
</Th>
<Th
width={10}
sort={{
sortBy: {
index: activeSortIndex,
direction: activeSortDirection,
},
onSort,
columnIndex: 'permissions',
}}
modifier="nowrap"
>
{columns[2]}
</Th>
</Tr>
</Thead>
{rows.length === 0 &&
[...Array(perPage).keys()].map((i) => (
<Tbody key={i}>
<Tr>
{!isReadOnly && <Td />}
{columns.map((col, key) => (
<Td dataLabel={col} key={key}>
<div
style={{ height: '22px' }}
className="ins-c-skeleton ins-c-skeleton__md"
>
{' '}
</div>
</Td>
))}
</Tr>
</Tbody>
))}
{pagedRows.map((row, rowIndex) => (
<Tbody key={rowIndex}>
<Tr>
{!isReadOnly && (
<Td
select={{
rowIndex,
onSelect,
isSelected: selectedRoles.find((r) => r === row.display_name),
}}
/>
)}
<Td dataLabel={columns[0]}>{row.display_name}</Td>
<Td dataLabel={columns[1]} className="pf-m-truncate">
<Tooltip entryDelay={1000} content={row.description}>
<span className="pf-m-truncate pf-c-table__text">
{row.description}
</span>
</Tooltip>
</Td>
<Td
dataLabel={columns[2]}
className={css(
'pf-c-table__compound-expansion-toggle',
row.isExpanded && 'pf-m-expanded'
)}
>
<button
type="button"
className="pf-c-table__button"
onClick={() => onExpand(row)}
>
{row.permissions}
</button>
</Td>
</Tr>
<Tr isExpanded={row.isExpanded} borders={false}>
{!isReadOnly && <Td />}
<Td className="pf-u-p-0" colSpan={3}>
<TableComposable isCompact className="pf-m-no-border-rows">
<Thead>
<Tr>
{expandedColumns.map((col) => (
<Th key={col}>{col}</Th>
))}
</Tr>
</Thead>
<Tbody>
{Array.isArray(row.access)
? row.access.map((permissions) => (
<Tr key={permissions.join(':')}>
<Td dataLabel={expandedColumns[0]}>
{permissions[0]}
</Td>
<Td dataLabel={expandedColumns[1]}>
{permissions[1]}
</Td>
<Td dataLabel={expandedColumns[2]}>
{permissions[2]}
</Td>
</Tr>
))
: [...Array(row.permissions).keys()].map((i) => (
<Tr key={i}>
{expandedColumns.map((val) => (
<Td key={val} dataLabel={val}>
<div
style={{ height: '22px' }}
className="ins-c-skeleton ins-c-skeleton__sm"
>
{' '}
</div>
</Td>
))}
</Tr>
))}
</Tbody>
</TableComposable>
</Td>
</Tr>
</Tbody>
))}
{pagedRows.length === 0 && hasFilters && (
<Tr>
<Td colSpan={columns.length}>
<EmptyState variant="small">
<EmptyStateIcon icon={SearchIcon} />
<Title headingLevel="h2" size="lg">
No matching requests found
</Title>
<EmptyStateBody>
No results match the filter criteria. Remove all filters or
clear all filters to show results.
</EmptyStateBody>
{clearFiltersButton}
</EmptyState>
</Td>
</Tr>
)}
</TableComposable>
);
return (
<React.Fragment>
{!isReadOnly && (
<React.Fragment>
<Title headingLevel="h2">Select roles</Title>
<p>Select the roles you would like access to.</p>
</React.Fragment>
)}
{roleToolbar}
{roleTable}
{isReadOnly && <AccessRequestsPagination id="bottom" />}
</React.Fragment>
);
}