@patternfly/react-core#EmptyState JavaScript Examples
The following examples show how to use
@patternfly/react-core#EmptyState.
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: no-access.js From sed-frontend with Apache License 2.0 | 6 votes |
NoAccessView = () => (
<Bullseye>
<EmptyState>
<EmptyStateIcon icon={LockIcon} />
<Title headingLevel="h4" size="lg">
Activation keys can only be accessed by organization administrators.
</Title>
<EmptyStateBody>
If you already know your organization ID and activation key, you can
register systems with RHC.
</EmptyStateBody>
</EmptyState>
</Bullseye>
)
Example #2
Source File: RoutingTab.js From cockpit-wicked with GNU General Public License v2.0 | 6 votes |
RoutingTab = () => {
const dispatch = useNetworkDispatch();
const { routes } = useNetworkState();
useEffect(() => { fetchRoutes(dispatch) }, [dispatch]);
const routesList = routes ? Object.values(routes) : [];
const routesNotFound = () => (
<EmptyState>
<EmptyStateIcon icon={InfoCircleIcon} />
<Title headingLevel="h4" size="lg">
{_('No user-defined routes were found.')}
</Title>
<AddRoute />
</EmptyState>
);
if (routesList.length === 0) {
return routesNotFound();
}
return (
<Card>
<CardHeader>
<CardActions>
<AddRoute />
</CardActions>
<CardTitle>
<Text component={TextVariants.h2}>{_("User-defined Routes")}</Text>
</CardTitle>
</CardHeader>
<CardBody>
<RoutesList routes={routesList} />
</CardBody>
</Card>
);
}
Example #3
Source File: InactiveServicePage.js From cockpit-wicked with GNU General Public License v2.0 | 6 votes |
InactiveServicePage = () => {
return (
<EmptyState>
<EmptyStateIcon icon={AlertIcon} />
<Title headingLevel="h4" size="lg">
{_("Wicked service is not active")}
</Title>
<EmptyStateBody>
<p>
{_(`Seems that wicked service is not active. It could be either, the service is
not running or wicked is not installed.`)}
</p>
<p>
{_("For more help, please check the documentation linked below.")}
</p>
</EmptyStateBody>
<EmptyStateSecondaryActions>
<ExternalLink href="https://en.opensuse.org/Portal:Wicked">
openSUSE Wicked Portal
</ExternalLink>
<ExternalLink href="https://github.com/openSUSE/wicked/wiki/FAQ">
Wicked FAQ
</ExternalLink>
<ExternalLink href="https://github.com/openSUSE/wicked">
Public Wicked Repository
</ExternalLink>
</EmptyStateSecondaryActions>
</EmptyState>
);
}
Example #4
Source File: tablestates.js From ibutsu-server with MIT License | 6 votes |
render() {
return (
<Bullseye>
<EmptyState>
<EmptyStateIcon icon={ErrorCircleOIcon} />
<Title headingLevel="h5" size="lg">Error occurred fetching results</Title>
{!!this.props.onClearFilters &&
<React.Fragment>
<EmptyStateBody>
An error occurred while fetching results. Try a different set of filters.
</EmptyStateBody>
<EmptyStateSecondaryActions>
<Button variant="link" onClick={this.props.onClearFilters}>Clear all filters</Button>
</EmptyStateSecondaryActions>
</React.Fragment>
}
</EmptyState>
</Bullseye>
);
}
Example #5
Source File: tablestates.js From ibutsu-server with MIT License | 6 votes |
render() {
return (
<Bullseye>
<EmptyState>
<EmptyStateIcon icon={SearchIcon} />
<Title headingLevel="h5" size="lg">No results found</Title>
{!!this.props.onClearFilters &&
<React.Fragment>
<EmptyStateBody>
No results match this filter criteria. Clear all filters to show results.
</EmptyStateBody>
<EmptyStateSecondaryActions>
<Button variant="link" onClick={this.props.onClearFilters}>Clear all filters</Button>
</EmptyStateSecondaryActions>
</React.Fragment>
}
</EmptyState>
</Bullseye>
);
}
Example #6
Source File: empty-object.js From ibutsu-server with MIT License | 6 votes |
render() {
return (
<React.Fragment>
<EmptyState>
<EmptyStateIcon icon={SearchIcon} />
<Text component="h1" size="lg">
{this.props.headingText ? this.props.headingText : "This object couldn't be found."}
</Text>
<EmptyStateBody>
{this.props.bodyText ? this.props.bodyText : "Either the object doesn't exist or the ID is invalid."}
</EmptyStateBody>
<NavLink style={{ color: 'white' }} to={!this.props.returnLink ? '' : this.props.returnLink}>
<Button variant="primary" style = {{ margin: '25px' }}>
{this.props.returnLinkText ? this.props.returnLinkText : "Return to dashboard"}
</Button>
</NavLink>
</EmptyState>
</React.Fragment>
);
}
Example #7
Source File: NoResultsTable.js From tasks-frontend with Apache License 2.0 | 6 votes |
NoResultsTable = ({ type }) => (
<EmptyTable>
<Bullseye>
<EmptyState variant={EmptyStateVariant.full}>
<Title headingLevel="h5" size="lg">
{`No matching ${type} found`}
</Title>
<EmptyStateBody>
To continue, edit your filter settings and search again.
</EmptyStateBody>
</EmptyState>
</Bullseye>
</EmptyTable>
)
Example #8
Source File: EmptyStateDisplay.js From tasks-frontend with Apache License 2.0 | 6 votes |
EmptyStateDisplay = ({
button,
color,
error,
icon,
isSmall,
text,
title,
}) => {
return (
<EmptyState
variant={isSmall ? EmptyStateVariant.small : EmptyStateVariant.large}
>
{icon ? (
<EmptyStateIcon
icon={icon}
color={color ? color : null}
className={isSmall ? 'small-empty-state-icon' : null}
/>
) : null}
<br></br>
<Title headingLevel={isSmall ? 'h5' : 'h1'} size={isSmall ? 'md' : 'lg'}>
{title}
</Title>
<EmptyStateBody>
{text
? text.map((line, index) => (
<React.Fragment key={`line-${index}`}>
{line}
<br />
</React.Fragment>
))
: null}
{error ? error : null}
</EmptyStateBody>
{button}
</EmptyState>
);
}
Example #9
Source File: NoActivationKeysFound.js From sed-frontend with Apache License 2.0 | 6 votes |
NoActivationKeysFound = (props) => {
const { handleModalToggle } = props;
return (
<>
<EmptyState>
<EmptyStateIcon icon={AddCircleOIcon} />
<Title headingLevel="h5" size="lg">
No activation keys
</Title>
<EmptyStateBody>
You currently have no activation keys to display. Activation keys
allow you to register a system with system purpose, role and usage
attached.
</EmptyStateBody>
<CreateActivationKeyButton onClick={handleModalToggle} />
</EmptyState>
</>
);
}
Example #10
Source File: Routes.js From ocp-advisor-frontend with Apache License 2.0 | 6 votes |
Routes = () => (
<Suspense
fallback={
<Bullseye>
<Spinner />
</Bullseye>
}
>
<Switch>
{paths.map((path) => (
<Route key={path.title} path={path.path} component={path.component} />
))}
<Redirect exact from="/" to="/recommendations" />
{/* Finally, catch all unmatched routes */}
<Route
path="*"
component={() => (
<EmptyState>
<EmptyStateBody>
<InvalidObject />
</EmptyStateBody>
</EmptyState>
)}
/>
</Switch>
</Suspense>
)
Example #11
Source File: MessageState.js From ocp-advisor-frontend with Apache License 2.0 | 6 votes |
MessageState = ({ className, children, icon, iconClass, iconStyle, text, title, variant, }) => ( <EmptyState className={className} variant={variant} data-ouia-component-id="empty-state" data-ouia-component-type="PF4/EmptyState" data-ouia-safe={true} > {icon && ( <EmptyStateIcon className={iconClass} style={iconStyle} icon={icon} /> )} <Title headingLevel="h5" size="lg"> {title} </Title> <EmptyStateBody style={{ marginBottom: '16px' }}>{text}</EmptyStateBody> {children} </EmptyState> )
Example #12
Source File: EmptyAccount.js From malware-detection-frontend with Apache License 2.0 | 6 votes |
EmptyAccount = ({ message, className }) => {
const intl = useIntl();
return (
<EmptyState className={className} variant='large'>
<EmptyStateIcon icon={WrenchIcon} />
<Title headingLevel="h4" size="lg">
{intl.formatMessage(messages.emptyAccountTitle)}
</Title>
<EmptyStateBody>
{message}
</EmptyStateBody>
<Button
variant="primary"
component="a"
href={'https://access.redhat.com/documentation/en-us/red_hat_insights/2022/html/' +
'assessing_and_reporting_malware_signatures_on_rhel_systems_with_the_insights_for' +
'_rhel_malware_service/'}
target="_blank" >
{intl.formatMessage(messages.emptyAccountButton)}
</Button>
</EmptyState>
);
}
Example #13
Source File: Loading.js From content-preview with Apache License 2.0 | 6 votes |
Loading = () => {
const Spinner = () => (
<span className="pf-c-spinner" role="progressbar" aria-valuetext="Loading...">
<span className="pf-c-spinner__clipper" />
<span className="pf-c-spinner__lead-ball" />
<span className="pf-c-spinner__tail-ball" />
</span>
);
return (
<EmptyState>
<EmptyStateIcon variant="container" component={Spinner} />
<Title size="lg" headingLevel="h4">
Loading
</Title>
</EmptyState>
);
}
Example #14
Source File: LoadError.js From content-preview with Apache License 2.0 | 6 votes |
LoadDetailError = (props) => {
let bodyMessage;
if (props.bodyMessage === 'List') {
bodyMessage = 'Try again later. Or report issue to @insights-rule-dev';
} else if (props.bodyMessage === 'Detail') {
bodyMessage = 'Use a right PluginName|ErrorKey in URL and try again... Or report issue to @insights-rule-dev';
}
return (
<EmptyState>
<EmptyStateIcon icon={BanIcon} />
<Title size="lg" headingLevel="h4">
Loading Error of {props.bodyMessage}
</Title>
<EmptyStateBody>
{bodyMessage}
</EmptyStateBody>
<EmptyStatePrimary>
Back to <Link to='/preview'>Content Preview</Link>
</EmptyStatePrimary>
</EmptyState>
);
}
Example #15
Source File: Empty.js From edge-frontend with Apache License 2.0 | 5 votes |
Empty = ({ bgColor, icon, title, body, primaryAction, secondaryActions, }) => ( <EmptyState variant="large" style={{ backgroundColor: bgColor || '' }}> {icon && <EmptyStateIcon icon={iconMapper[icon]} />} <Title headingLevel="h4" size="lg"> {title} </Title> <EmptyStateBody>{body}</EmptyStateBody> {primaryAction && ( <> {primaryAction.href ? ( <Button component={Link} to={primaryAction.href}> {primaryAction.text} </Button> ) : ( <Button onClick={primaryAction.click} variant="primary"> {primaryAction.text} </Button> )} </> )} <EmptyStateSecondaryActions> {secondaryActions.map(({ type, title, link, onClick }, index) => ( <Button component={type === 'link' ? 'a' : 'button'} href={link} variant="link" target={type === 'link' ? '_blank' : ''} key={index} onClick={onClick} > {title} {link && <ExternalLinkAltIcon className="pf-u-ml-sm" />} </Button> ))} </EmptyStateSecondaryActions> </EmptyState> )
Example #16
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 #17
Source File: dashboard.js From ibutsu-server with MIT License | 4 votes |
render() {
document.title = 'Dashboard | Ibutsu';
const { widgets } = this.state;
const project = getActiveProject();
const dashboard = getActiveDashboard();
return (
<React.Fragment>
<PageSection variant={PageSectionVariants.light}>
<Flex justifyContent={{ default: 'justifyContentSpaceBetween' }}>
<Flex>
<FlexItem spacer={{ default: 'spacerLg' }}>
<TextContent>
<Text component="h1">Dashboard</Text>
</TextContent>
</FlexItem>
<FlexItem id="dashboard-selector" spacer={{ default: 'spacerNone' }}>
<Select
ariaLabelTypeAhead="Select a dashboard"
placeholderText="No active dashboard"
variant={SelectVariant.typeahead}
isOpen={this.state.isDashboardSelectorOpen}
isDisabled={!project}
selections={this.state.selectedDashboard}
onToggle={this.onDashboardToggle}
onSelect={this.onDashboardSelect}
onClear={this.onDashboardClear}
onTypeaheadInputChanged={this.onDashboardChanged}
footer={this.state.dashboards.length == 10 && "Search for more..."}
isPlain
>
{this.state.dashboards.map(dash => (
<SelectOption key={dash.id} value={dashboardToSelect(dash)} />
))}
</Select>
</FlexItem>
<FlexItem spacer={{ default: 'spacerNone' }}>
<Button
aria-label="New dashboard"
variant="plain"
title="New dashboard"
isDisabled={!project}
onClick={this.onNewDashboardClick}
>
<PlusCircleIcon />
</Button>
</FlexItem>
<FlexItem>
<Button
aria-label="Delete dashboard"
variant="plain"
title="Delete dashboard"
isDisabled={!dashboard}
onClick={this.onDeleteDashboardClick}
>
<TimesCircleIcon />
</Button>
</FlexItem>
</Flex>
<Flex>
<FlexItem>
<Button
aria-label="Add widget"
variant="secondary"
title="Add widget"
isDisabled={!this.state.selectedDashboard}
onClick={this.onAddWidgetClick}
>
<PlusCircleIcon /> Add Widget
</Button>
</FlexItem>
</Flex>
</Flex>
</PageSection>
<PageSection>
{!!project && !!dashboard && !!widgets &&
<Grid hasGutter>
{widgets.map(widget => {
if (KNOWN_WIDGETS.includes(widget.widget)) {
return (
<GridItem xl={4} lg={6} md={12} key={widget.id}>
{(widget.type === "widget" && widget.widget === "jenkins-heatmap") &&
<JenkinsHeatmapWidget
title={widget.title}
params={widget.params}
includeAnalysisLink={true}
onDeleteClick={() => this.onDeleteWidgetClick(widget.id)}
/>
}
{(widget.type === "widget" && widget.widget === "run-aggregator") &&
<GenericBarWidget
title={widget.title}
params={widget.params}
horizontal={true}
percentData={true}
barWidth={20}
onDeleteClick={() => this.onDeleteWidgetClick(widget.id)}
/>
}
{(widget.type === "widget" && widget.widget === "result-summary") &&
<ResultSummaryWidget
title={widget.title}
params={widget.params}
onDeleteClick={() => this.onDeleteWidgetClick(widget.id)}
/>
}
{(widget.type === "widget" && widget.widget === "result-aggregator") &&
<ResultAggregatorWidget
title={widget.title}
params={widget.params}
onDeleteClick={() => this.onDeleteWidgetClick(widget.id)}
/>
}
</GridItem>
);
}
else {
return '';
}
})}
</Grid>
}
{!project &&
<EmptyState>
<EmptyStateIcon icon={ArchiveIcon} />
<Title headingLevel="h4" size="lg">
No Project Selected
</Title>
<EmptyStateBody>
There is currently no project selected. Please select a project from the dropdown in
order to view the dashboard.
</EmptyStateBody>
</EmptyState>
}
{!!project && !dashboard &&
<EmptyState>
<EmptyStateIcon icon={TachometerAltIcon} />
<Title headingLevel="h4" size="lg">
No Dashboard Selected
</Title>
<EmptyStateBody>
There is currently no dashboard selected. Please select a dashboard from the dropdown
in order to view widgets, or create a new dashboard.
</EmptyStateBody>
<Button variant="primary" onClick={this.onNewDashboardClick}>New Dashboard</Button>
</EmptyState>
}
{(!!project && !!dashboard && widgets.length === 0) &&
<EmptyState>
<EmptyStateIcon icon={CubesIcon} />
<Title headingLevel="h4" size="lg">
No Widgets
</Title>
<EmptyStateBody>
This dashboard currently has no widgets defined.<br />Click on the "Add Widget" button
below to add a widget to this dashboard.
</EmptyStateBody>
<Button variant="primary" onClick={this.onAddWidgetClick}>Add Widget</Button>
</EmptyState>
}
</PageSection>
<NewDashboardModal
project={project}
isOpen={this.state.isNewDashboardOpen}
onSave={this.onNewDashboardSave}
onClose={this.onNewDashboardClose}
/>
<NewWidgetWizard
dashboard={dashboard}
isOpen={this.state.isWidgetWizardOpen}
onSave={this.onNewWidgetSave}
onClose={this.onNewWidgetClose}
/>
<DeleteModal
title="Delete dashboard"
body={<>Would you like to delete the current dashboard? <strong>ALL WIDGETS</strong> on the dashboard will also be deleted.</>}
isOpen={this.state.isDeleteDashboardOpen}
onDelete={this.onDeleteDashboard}
onClose={this.onDeleteDashboardClose}
/>
<DeleteModal
title="Delete widget"
body="Would you like to delete the selected widget?"
isOpen={this.state.isDeleteWidgetOpen}
onDelete={this.onDeleteWidget}
onClose={this.onDeleteWidgetClose}
/>
</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>
);
}
Example #19
Source File: EditRequestModal.js From access-requests-frontend with Apache License 2.0 | 4 votes |
ReviewStep = ({
targetAccount,
start,
end,
roles,
isLoading,
error,
onClose,
}) => {
let content = null;
if (isLoading) {
content = (
<EmptyState>
<EmptyStateIcon icon={() => <Spinner size="lg" />} />
<Title headingLevel="h2" size="lg">
Submitting access request
</Title>
<Button variant="link" onClick={onClose}>
Close
</Button>
</EmptyState>
);
} else if (error) {
const context = React.useContext(WizardContextConsumer);
content = (
<EmptyState>
<EmptyStateIcon icon={ExclamationCircleIcon} color="#C9190B" />
<Title headingLevel="h2" size="lg">
{error.title}
</Title>
<EmptyStateBody>{error.description}</EmptyStateBody>
{error.title === invalidAccountTitle && (
<Button variant="primary" onClick={() => context.goToStepById(1)}>
Return to Step 1
</Button>
)}
</EmptyState>
);
} else {
content = (
<React.Fragment>
<Title headingLevel="h2" style={spaceUnderStyle}>
Review details
</Title>
<table>
<tr>
<td style={spaceUnderStyle}>
<b>Account number</b>
</td>
<td style={spaceUnderStyle}>{targetAccount}</td>
</tr>
<tr>
<td style={{ paddingRight: '32px' }}>
<b>Access duration</b>
</td>
<td></td>
</tr>
<tr>
<td>From</td>
<td>{start}</td>
</tr>
<tr>
<td style={spaceUnderStyle}>To</td>
<td style={spaceUnderStyle}>{end}</td>
</tr>
<tr>
<td>
<b>Roles</b>
</td>
<td>{roles[0]}</td>
</tr>
{roles.slice(1).map((role) => (
<tr key={role}>
<td></td>
<td>{role}</td>
</tr>
))}
</table>
</React.Fragment>
);
}
return <React.Fragment>{content}</React.Fragment>;
}