@patternfly/react-core#Tooltip JavaScript Examples
The following examples show how to use
@patternfly/react-core#Tooltip.
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: 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 #2
Source File: ImageSetsTable.js From edge-frontend with Apache License 2.0 | 6 votes |
TooltipSelectorRef = ({ index }) => (
<div>
<Tooltip
content={<div>More options</div>}
reference={() =>
document.getElementById(`pf-dropdown-toggle-id-${index}`)
}
/>
</div>
)
Example #3
Source File: Status.js From edge-frontend with Apache License 2.0 | 6 votes |
Status = ({
type,
isLabel = false,
toolTipContent = '',
className = '',
}) => {
const { text, Icon, color, labelColor } =
Object.prototype.hasOwnProperty.call(statusMapper, type)
? statusMapper[type]
: statusMapper['default'];
return (
<>
{isLabel ? (
<Label color={labelColor} icon={<Icon />} className={className}>
{text}
</Label>
) : (
<Split style={{ color }} className={className}>
<SplitItem className="pf-u-mr-sm">
{toolTipContent ? (
<Tooltip content="blargh">
<Icon />
</Tooltip>
) : (
<Icon />
)}
</SplitItem>
<SplitItem>
<p>{text}</p>
</SplitItem>
</Split>
)}
</>
);
}
Example #4
Source File: RegisterWithActivationKey.js From sed-frontend with Apache License 2.0 | 6 votes |
RegisterWithActivationKey = () => (
<FormGroup
label={
<span>
Register with an activation key
<Tooltip
content={
<div>
Organization administrators can view, create, and edit activation
keys on the "Activation keys" section of
console.redhat.com. The organization ID is a Candlepin-specific
identifier, which can be accessed from the activation keys page.
</div>
}
>
<OutlinedQuestionCircleIcon />
</Tooltip>
</span>
}
helperText={<CopyHelperText />}
>
<ClipboardCopy>
rhc connect -a <activation-key> -o <organization-id>
</ClipboardCopy>
</FormGroup>
)
Example #5
Source File: RuleLabels.js From ocp-advisor-frontend with Apache License 2.0 | 6 votes |
RuleLabels = ({ rule }) => {
const intl = useIntl();
return (
<React.Fragment>
{rule.disabled && (
<Tooltip
content={intl.formatMessage(messages.ruleIsDisabledTooltip)}
position={TooltipPosition.right}
>
<Label color="gray" isCompact>
{intl.formatMessage(messages.disabled)}
</Label>
</Tooltip>
)}
</React.Fragment>
);
}
Example #6
Source File: ImageVersionsTab.js From edge-frontend with Apache License 2.0 | 5 votes |
createRows = (data, imageSetId, latestImageVersion) => {
return data?.map(({ image }) => ({
rowInfo: {
id: image?.ID,
imageStatus: image?.Status,
isoURL: image?.Installer?.ImageBuildISOURL,
latestImageVersion,
currentImageVersion: image.Version,
},
noApiSortFilter: [
image?.Version,
imageTypeMapper[image?.ImageType],
image?.CreatedAt,
image?.Status,
],
cells: [
{
title: (
<Link
to={`${paths['manage-images']}/${imageSetId}/versions/${image.ID}/details`}
>
{image?.Version}
</Link>
),
},
{
title: imageTypeMapper[image?.ImageType],
},
{
title: image?.Commit?.OSTreeCommit ? (
<Tooltip content={<div>{image?.Commit?.OSTreeCommit}</div>}>
<span>{truncateString(image?.Commit?.OSTreeCommit, 5, 5)}</span>
</Tooltip>
) : (
<Text>Unavailable</Text>
),
},
{
title: <DateFormat date={image?.CreatedAt} />,
},
{
title: <Status type={image?.Status.toLowerCase()} />,
},
],
}));
}
Example #7
Source File: StatusIcon.js From edge-frontend with Apache License 2.0 | 5 votes |
StatusIcon = ({ status, ...props }) => {
const Icon = statusToIcon?.[status]?.icon || UnknownIcon;
return (
<Tooltip content={statusToIcon?.[status]?.title}>
<Icon {...props} color={statusToIcon?.[status]?.color} />
</Tooltip>
);
}
Example #8
Source File: AffectedClustersTable.js From ocp-advisor-frontend with Apache License 2.0 | 4 votes |
AffectedClustersTable = ({ query, rule, afterDisableFn }) => { const intl = useIntl(); const dispatch = useDispatch(); const [filteredRows, setFilteredRows] = useState([]); const [displayedRows, setDisplayedRows] = useState([]); const [disableRuleModalOpen, setDisableRuleModalOpen] = useState(false); const [selected, setSelected] = useState([]); const [host, setHost] = useState(undefined); const { isError, isUninitialized, isFetching, isSuccess, /* the response contains two lists: `disabled` has clusters for which the rec is disabled (acked), and `enable` contains clusters that are affected by the rec */ data = { disabled: [], enabled: [] }, } = query; const rows = data?.enabled || []; const filters = useSelector(({ filters }) => filters.affectedClustersState); const perPage = filters.limit; const page = filters.offset / filters.limit + 1; const allSelected = filteredRows.length !== 0 && selected.length === filteredRows.length; const loadingState = isUninitialized || isFetching; const errorState = isError; const successState = isSuccess; const noInput = successState && rows.length === 0; const noMatch = rows.length > 0 && filteredRows.length === 0; const updateFilters = (filters) => dispatch(updateAffectedClustersFilters(filters)); const removeFilterParam = (param) => _removeFilterParam(filters, updateFilters, param); const addFilterParam = (param, values) => _addFilterParam(filters, updateFilters, param, values); const filterConfig = { items: [ { label: intl.formatMessage(messages.name), placeholder: intl.formatMessage(messages.filterByName), type: conditionalFilterType.text, filterValues: { id: 'name-filter', key: 'name-filter', onChange: (event, value) => addFilterParam('text', value), value: filters.text, }, }, { label: intl.formatMessage(messages.version), placeholder: intl.formatMessage(messages.filterByVersion), type: conditionalFilterType.checkbox, filterValues: { id: 'version-filter', key: 'version-filter', onChange: (event, value) => addFilterParam('version', value), value: filters.version, items: uniqBy( rows .filter((r) => r.meta.cluster_version !== '') .map((r) => ({ value: r.meta.cluster_version, })) .sort((a, b) => compareSemVer(a.value, b.value, 1)) .reverse(), // should start from the latest version 'value' ), }, }, ], isDisabled: isError || (rows && rows.length === 0), }; const onSort = (_e, index, direction) => { updateFilters({ ...filters, sortIndex: index, sortDirection: direction }); }; const onSetPage = (_e, pageNumber) => { const newOffset = pageNumber * filters.limit - filters.limit; updateFilters({ ...filters, offset: newOffset }); }; const onSetPerPage = (_e, perPage) => { updateFilters({ ...filters, limit: perPage }); }; // constructs array of rows (from the initial data) checking currently applied filters const buildFilteredRows = (allRows, filters) => { const rows = allRows.map((r) => { if (r.meta.cluster_version !== '' && !valid(r.meta.cluster_version)) { console.error( `Cluster version ${r.meta.cluster_version} has invalid format!` ); } return { id: r.cluster, cells: [ '', r.cluster_name || r.cluster, r.meta.cluster_version, r.last_checked_at, r.impacted, ], }; }); return rows .filter((row) => { return ( row?.cells[AFFECTED_CLUSTERS_NAME_CELL].toLowerCase().includes( filters.text.toLowerCase() ) && (filters.version.length === 0 || filters.version.includes(row.cells[AFFECTED_CLUSTERS_VERSION_CELL])) ); }) .sort((a, b) => { let fst, snd; const d = filters.sortDirection === 'asc' ? 1 : -1; switch (filters.sortIndex) { case AFFECTED_CLUSTERS_NAME_CELL: return ( d * a?.cells[AFFECTED_CLUSTERS_NAME_CELL].localeCompare( b?.cells[AFFECTED_CLUSTERS_NAME_CELL] ) ); case AFFECTED_CLUSTERS_VERSION_CELL: return compareSemVer( a.cells[AFFECTED_CLUSTERS_VERSION_CELL] || '0.0.0', b.cells[AFFECTED_CLUSTERS_VERSION_CELL] || '0.0.0', d ); case AFFECTED_CLUSTERS_LAST_SEEN_CELL: fst = new Date(a.cells[AFFECTED_CLUSTERS_LAST_SEEN_CELL] || 0); snd = new Date(b.cells[AFFECTED_CLUSTERS_LAST_SEEN_CELL] || 0); return fst > snd ? d : snd > fst ? -d : 0; case AFFECTED_CLUSTERS_IMPACTED_CELL: fst = new Date(a.cells[AFFECTED_CLUSTERS_IMPACTED_CELL] || 0); snd = new Date(b.cells[AFFECTED_CLUSTERS_IMPACTED_CELL] || 0); return fst > snd ? d : snd > fst ? -d : 0; } }); }; const buildDisplayedRows = (rows) => { return rows .slice(perPage * (page - 1), perPage * (page - 1) + perPage) .map((r) => ({ ...r, cells: [ <span key={r.id}> <Link to={`/clusters/${r.id}?first=${rule.rule_id}`}> {r.cells[AFFECTED_CLUSTERS_NAME_CELL]} </Link> </span>, <span key={r.id}> {r.cells[AFFECTED_CLUSTERS_VERSION_CELL] || intl.formatMessage(messages.notAvailable)} </span>, <span key={r.id}> {r.cells[AFFECTED_CLUSTERS_LAST_SEEN_CELL] ? ( <DateFormat extraTitle={`${intl.formatMessage(messages.lastSeen)}: `} date={r.cells[AFFECTED_CLUSTERS_LAST_SEEN_CELL]} variant="relative" /> ) : ( <Tooltip key={r.id} content={ <span> {intl.formatMessage(messages.lastSeen) + ': '} {intl.formatMessage(messages.nA)} </span> } > <span>{intl.formatMessage(messages.nA)}</span> </Tooltip> )} </span>, <span key={r.id}> {r.cells[AFFECTED_CLUSTERS_IMPACTED_CELL] ? ( <DateFormat extraTitle={`${intl.formatMessage(messages.impacted)}: `} date={r.cells[AFFECTED_CLUSTERS_IMPACTED_CELL]} variant="relative" /> ) : ( <Tooltip key={r.id} content={ <span> {intl.formatMessage(messages.impacted) + ': '} {intl.formatMessage(messages.nA)} </span> } > <span>{intl.formatMessage(messages.nA)}</span> </Tooltip> )} </span>, ], })); }; // if rowId === -1, then select all rows const onSelect = (event, isSelected, rowId) => { let rows; rowId === -1 ? (rows = filteredRows.map((r) => ({ ...r, selected: isSelected }))) : (rows = filteredRows.map((r, i) => ({ ...r, selected: i === rowId ? isSelected : r.selected, }))); setSelected(rows.filter((r) => r.selected)); setFilteredRows(rows); setDisplayedRows(buildDisplayedRows(rows)); }; useEffect(() => { const newFilteredRows = buildFilteredRows(rows, filters); const newDisplayedRows = buildDisplayedRows(newFilteredRows); setFilteredRows(newFilteredRows); setDisplayedRows(newDisplayedRows); }, [query, filters]); const handleModalToggle = (disableRuleModalOpen, host = undefined) => { setDisableRuleModalOpen(disableRuleModalOpen); setHost(host); }; return ( <div id="affected-list-table"> {disableRuleModalOpen && ( <DisableRule handleModalToggle={handleModalToggle} isModalOpen={disableRuleModalOpen} rule={rule} afterFn={afterDisableFn} hosts={host !== undefined ? [] : selected} host={host} /> )} <PrimaryToolbar filterConfig={filterConfig} pagination={{ itemCount: filteredRows.length, page, perPage, onSetPage: onSetPage, onPerPageSelect: onSetPerPage, ouiaId: 'pager', }} activeFiltersConfig={ isError || (rows && rows.length === 0) ? undefined : { filters: buildFilterChips(filters, FILTER_CATEGORIES), deleteTitle: intl.formatMessage(messages.resetFilters), onDelete: (event, itemsToRemove, isAll) => { if (isAll) { updateFilters(AFFECTED_CLUSTERS_INITIAL_STATE); } else { itemsToRemove.map((item) => { const newFilter = { [item.urlParam]: Array.isArray(filters[item.urlParam]) ? filters[item.urlParam].filter( (value) => String(value) !== String(item.chips[0].value) ) : '', }; newFilter[item.urlParam].length > 0 ? updateFilters({ ...filters, ...newFilter }) : removeFilterParam(item.urlParam); }); } }, } } bulkSelect={{ count: selected.length, items: [ { title: intl.formatMessage(messages.selectNone), onClick: (event) => onSelect(event, false, -1), }, { title: intl.formatMessage(messages.selectAll, { items: filteredRows?.length || 0, }), onClick: (event) => onSelect(event, true, -1), }, ], checked: allSelected, onSelect: (event) => allSelected ? onSelect(event, false, -1) : onSelect(event, true, -1), ouiaId: 'clusters-selector', }} actionsConfig={{ actions: [ '', { label: intl.formatMessage(messages.disableRuleForClusters), props: { isDisabled: selected.length === 0 }, onClick: () => handleModalToggle(true), }, ], }} /> <Table aria-label="Table of affected clusters" ouiaId="clusters" variant="compact" cells={AFFECTED_CLUSTERS_COLUMNS} rows={ errorState || loadingState || noMatch || noInput ? ( [ { fullWidth: true, cells: [ { props: { colSpan: AFFECTED_CLUSTERS_COLUMNS.length + 1, }, title: errorState ? ( <ErrorState /> ) : loadingState ? ( <Loading /> ) : noInput ? ( <NoAffectedClusters /> ) : ( <NoMatchingClusters /> ), }, ], }, ] ) : successState ? ( displayedRows ) : ( <ErrorState /> ) } sortBy={{ index: filters.sortIndex, direction: filters.sortDirection, }} onSort={onSort} canSelectAll={false} onSelect={displayedRows?.length > 0 ? onSelect : undefined} actions={[ { title: 'Disable recommendation for cluster', onClick: (event, rowIndex) => handleModalToggle(true, filteredRows[rowIndex].id), }, ]} > <TableHeader /> <TableBody /> </Table> <TableToolbar isFooter className="ins-c-inventory__table--toolbar"> <Pagination variant={PaginationVariant.bottom} itemCount={filteredRows.length} page={page} perPage={perPage} onSetPage={onSetPage} onPerPageSelect={onSetPerPage} onPageInput={onSetPage} ouiaId="pager" /> </TableToolbar> </div> ); }
Example #9
Source File: InterfaceActions.js From cockpit-wicked with GNU General Public License v2.0 | 4 votes |
InterfaceActions = ({ iface, connection }) => {
const [showDeleteConfirmation, setShowDeleteConfirmation] = useState(false);
const dispatch = useNetworkDispatch();
const DeleteIcon = connection.virtual ? TrashIcon : ResetIcon;
const deleteTooltip = connection.virtual ? _("Delete") : _("Reset");
const changeStatusLabel = _("Enabled");
const changeStatusLabelOff = _("Disabled");
const changeStatusAction = iface.link ? _("Disable") : _("Enable");
const changeStatusTooltip = cockpit.format(
_("Click to $action it"),
{ action: changeStatusAction.toLowerCase() }
);
const openDeleteConfirmation = () => setShowDeleteConfirmation(true);
const closeDeleteConfirmation = () => setShowDeleteConfirmation(false);
const onDeleteConfirmation = () => {
deleteConnection(dispatch, connection);
closeDeleteConfirmation();
};
const renderDeleteConfirmation = () => {
if (!showDeleteConfirmation) return null;
const confirmationTitle = cockpit.format(
_("$action '$iface' configuration"),
{ action: deleteTooltip, iface: connection?.name }
);
const confirmationMessage = cockpit.format(
_("Please, confirm that you really want to $action the interface configuration."),
{ action: deleteTooltip.toLowerCase() }
);
return (
<ModalConfirm
title={confirmationTitle}
isOpen={showDeleteConfirmation}
onCancel={closeDeleteConfirmation}
onConfirm={onDeleteConfirmation}
>
{confirmationMessage}
</ModalConfirm>
);
};
const renderDeleteAction = () => {
if (!connection.exists) return null;
return (
<>
<Tooltip content={deleteTooltip}>
<Button
variant="link"
aria-label={`${deleteTooltip} ${iface.name}`}
className="delete-action"
onClick={openDeleteConfirmation}
>
<DeleteIcon />
</Button>
</Tooltip>
{ renderDeleteConfirmation() }
</>
);
};
return (
<>
<Tooltip content={changeStatusTooltip}>
<Switch
id={`${iface.name}-status-switcher}`}
aria-label={`${changeStatusAction} ${iface.name}`}
label={changeStatusLabel}
labelOff={changeStatusLabelOff}
className="interface-status-switcher"
isChecked={iface.link}
onChange={() => changeConnectionState(dispatch, connection, !iface.link)}
/>
</Tooltip>
{ renderDeleteAction() }
</>
);
}
Example #10
Source File: certificateList.jsx From cockpit-certificates with GNU Lesser General Public License v2.1 | 4 votes |
generalDetails = ({ idPrefix, cas, cert, certPath, onAutorenewChanged }) => {
const caName = getCAName(cas, cert);
return (<Flex justifyContent={{ default: "justifyContentCenter" }}>
<Flex direction={{ default:"column" }} flex={{ default: 'flex_1' }}>
<DescriptionList isHorizontal>
{cert.status && cert.status.v && <DescriptionListGroup>
<DescriptionListTerm>{_("Status")}</DescriptionListTerm>
<DescriptionListDescription id={`${idPrefix}-general-status`}>
{cert.stuck.v && (
<Flex alignItems={{ default: 'alignItemsCenter' }} spaceItems={{ default: 'spaceItemsSm' }}>
<ExclamationTriangleIcon className="ct-icon-exclamation-triangle" />
<span id={`${idPrefix}-general-stuck`}>{_("Stuck: ")}</span>
</Flex>
)}
<Flex alignItems={{ default: 'alignItemsCenter' }} spaceItems={{ default: 'spaceItemsSm' }}>
<FlexItem>
{cert.status.v.includes('_')
? cert.status.v
: cert.status.v.charAt(0) + cert.status.v.slice(1).toLowerCase()}
</FlexItem>
<Tooltip position={TooltipPosition.top}
entryDelay={0}
content={certificateStates[cert.status.v]}>
<span className="info-circle">
<InfoAltIcon />
</span>
</Tooltip>
</Flex>
</DescriptionListDescription>
</DescriptionListGroup>}
{cert.ca && cert.ca.v && <DescriptionListGroup>
<DescriptionListTerm>{_("Certificate authority")}</DescriptionListTerm>
<DescriptionListDescription id={`${idPrefix}-general-ca`}>{caName == "SelfSign" ? _("Self-signed") : caName}</DescriptionListDescription>
</DescriptionListGroup>}
{cert["not-valid-after"] && cert["not-valid-after"].v !== 0 && <DescriptionListGroup>
<DescriptionListTerm>
{_("Valid")}
</DescriptionListTerm>
<DescriptionListDescription id={`${idPrefix}-general-validity`}>
{prettyTime(cert["not-valid-before"].v) +
_(" to ") + prettyTime(cert["not-valid-after"].v)}
</DescriptionListDescription>
</DescriptionListGroup>}
{cert.autorenew && <DescriptionListGroup>
<DescriptionListTerm>
{_("Auto-renewal")}
</DescriptionListTerm>
<DescriptionListDescription>
<Checkbox id={`${idPrefix}-general-autorenewal`}
isChecked={cert.autorenew.v}
label={_("Renew before expiration")}
onChange={() => onAutorenewChanged(cert, certPath)} />
</DescriptionListDescription>
</DescriptionListGroup>}
</DescriptionList>
</Flex>
<Flex direction={{ default:"column" }} flex={{ default: 'flex_1' }}>
<DescriptionList isHorizontal>
{cert.subject && cert.subject.v && <DescriptionListGroup>
<DescriptionListTerm>
{_("Subject name")}
</DescriptionListTerm>
<DescriptionListDescription>
<span id={`${idPrefix}-general-subject`}>{cert.subject.v}</span>
</DescriptionListDescription>
</DescriptionListGroup>}
{cert.principal && cert.principal.v.length > 0 && <DescriptionListGroup>
<DescriptionListTerm>
{_("Principal name")}
</DescriptionListTerm>
<DescriptionListDescription>
<span id={`${idPrefix}-general-principal`}>{cert.principal.v.join(", ")}</span>
</DescriptionListDescription>
</DescriptionListGroup>}
{cert.hostname && cert.hostname.v.length > 0 && <DescriptionListGroup>
<DescriptionListTerm>
{_("DNS name")}
</DescriptionListTerm>
<DescriptionListDescription>
<span id={`${idPrefix}-general-dns`}>{cert.hostname.v.join(", ")}</span>
</DescriptionListDescription>
</DescriptionListGroup>}
</DescriptionList>
</Flex>
</Flex>);
}
Example #11
Source File: Services.js From sed-frontend with Apache License 2.0 | 4 votes |
Services = ({
defaults,
setConfirmChangesOpen,
onChange,
isEditing,
setIsEditing,
}) => {
const initState = {
enableCloudConnector: {
value: defaults.enableCloudConnector,
isDisabled: false,
},
useOpenSCAP: { value: defaults.useOpenSCAP, isDisabled: false },
};
const [formState, setFormState] = useState(initState);
const [madeChanges, setMadeChanges] = useState(false);
const { hasAccess, isLoading } = usePermissions(
'',
[
'config-manager:activation_keys:*',
'config-manager:state:read',
'config-manager:state:write',
'config-manager:state-changes:read',
'inventory:*:read',
'playbook-dispatcher:run:read',
],
false,
true
);
const cancelEditing = () => {
setFormState(initState);
setIsEditing(false);
};
useEffect(() => {
setMadeChanges(
formState.useOpenSCAP.value !== defaults.useOpenSCAP ||
formState.enableCloudConnector.value != defaults.enableCloudConnector
);
onChange({
useOpenSCAP: formState.useOpenSCAP.value,
enableCloudConnector: formState.enableCloudConnector.value,
});
}, [formState]);
const getStatusIcon = (row) => {
if (formState[row.id].value) {
return (
<Flex style={{ color: 'var(--pf-global--success-color--200)' }}>
<FlexItem spacer={{ default: 'spacerXs' }}>
<CheckCircleIcon />
</FlexItem>
<FlexItem className="status">
<b>Enabled</b>
</FlexItem>
</Flex>
);
}
return (
<Flex style={{ color: 'var(--pf-global--default-color--300)' }}>
<FlexItem spacer={{ default: 'spacerXs' }}>
<BanIcon />
</FlexItem>
<FlexItem className="status">
<b>Disabled</b>
</FlexItem>
</Flex>
);
};
return (
<Stack hasGutter className="pf-u-p-md">
<StackItem>
<Toolbar id="toolbar-items">
<ToolbarContent>
{!isEditing && (
<ToolbarItem>
{!hasAccess ? (
<Tooltip
content={
<div>
To perform this action, you must be granted the
"System Administrator" role by your
Organization Administrator in your Setting's User
Access area.
</div>
}
>
{changeSettingsButton(isLoading, hasAccess, setIsEditing)}
</Tooltip>
) : (
changeSettingsButton(isLoading, hasAccess, setIsEditing)
)}
</ToolbarItem>
)}
{isEditing && (
<>
<ToolbarItem>
<Button
ouiaId="primary-save-button"
onClick={() => setConfirmChangesOpen(true)}
isDisabled={!madeChanges}
>
Save changes
</Button>
</ToolbarItem>
<ToolbarItem>
<Button
ouiaId="secondary-cancel-button"
onClick={() => cancelEditing()}
variant="secondary"
>
Cancel
</Button>
</ToolbarItem>
<ToolbarItem>
<Alert
variant="info"
isInline
isPlain
title="Changes will affect all systems connected with Red Hat connector"
/>
</ToolbarItem>
</>
)}
</ToolbarContent>
</Toolbar>
</StackItem>
<StackItem>
<TableComposable aria-label="Settings table">
<Thead>
<Tr>
<Th>Permission</Th>
<Th>Status</Th>
</Tr>
</Thead>
<Tbody>
{permissions.map((row) => (
<Tr key={row.name}>
<Td
dataLabel="Permission"
width={80}
style={row.secondary && { paddingLeft: 70, fontSize: 14 }}
>
<Stack>
<StackItem>
<Flex>
<FlexItem>
<b>{row.name}</b>
</FlexItem>
{row.additionalInfo && (
<FlexItem
style={{ color: 'var(--pf-global--Color--100)' }}
>
<i>{row.additionalInfo}</i>
</FlexItem>
)}
</Flex>
</StackItem>
<StackItem style={{ fontSize: 14 }}>
{row.description}
</StackItem>
{row.links && (
<StackItem className="stack-item">
<Flex>
{row.links.map((link) => (
<FlexItem key={link.name}>
<a
href={link.link}
target="_blank"
rel="noopener noreferrer"
>
{link.name}
<ExternalLinkAltIcon className="pf-u-ml-sm" />
</a>
</FlexItem>
))}
</Flex>
</StackItem>
)}
</Stack>
</Td>
{!isEditing && <Td dataLabel="Status">{getStatusIcon(row)}</Td>}
{isEditing && (
<Td dataLabel="Status">
<ToggleGroup aria-label="Default with single selectable">
<ToggleGroupItem
text="Enabled"
isSelected={formState[row.id].value}
onChange={() =>
setFormState({
...formState,
[row.id]: { ...formState[row.id], value: true },
})
}
isDisabled={formState[row.id].isDisabled}
/>
<ToggleGroupItem
text="Disabled"
isSelected={!formState[row.id].value}
onChange={() =>
setFormState({
...formState,
[row.id]: { ...formState[row.id], value: false },
})
}
isDisabled={formState[row.id].isDisabled}
/>
</ToggleGroup>
</Td>
)}
</Tr>
))}
</Tbody>
</TableComposable>
</StackItem>
</Stack>
);
}
Example #12
Source File: RecsListTable.js From ocp-advisor-frontend with Apache License 2.0 | 4 votes |
RecsListTable = ({ query }) => {
const intl = useIntl();
const dispatch = useDispatch();
const filters = useSelector(({ filters }) => filters.recsListState);
const { isError, isUninitialized, isFetching, isSuccess, data, refetch } =
query;
const recs = data?.recommendations || [];
const page = filters.offset / filters.limit + 1;
const [filteredRows, setFilteredRows] = useState([]);
const [displayedRows, setDisplayedRows] = useState([]);
const [disableRuleOpen, setDisableRuleOpen] = useState(false);
const [selectedRule, setSelectedRule] = useState({});
const [isAllExpanded, setIsAllExpanded] = useState(false);
const notify = (data) => dispatch(addNotification(data));
const { search } = useLocation();
const [filterBuilding, setFilterBuilding] = useState(true);
// helps to distinguish the state when the API data received but not yet filtered
const [rowsFiltered, setRowsFiltered] = useState(false);
// helps to distinguish if the component safe to test
const testSafe = rowsFiltered && !(isFetching || isUninitialized);
const updateFilters = (filters) => dispatch(updateRecsListFilters(filters));
const searchText = filters?.text || '';
const loadingState = isUninitialized || isFetching || !rowsFiltered;
const errorState = isError || (isSuccess && recs.length === 0);
const successState = isSuccess && recs.length > 0;
const noMatch = recs.length > 0 && filteredRows.length === 0;
const removeFilterParam = (param) =>
_removeFilterParam(filters, updateFilters, param);
const addFilterParam = (param, values) =>
_addFilterParam(filters, updateFilters, param, values);
useEffect(() => {
setDisplayedRows(
buildDisplayedRows(filteredRows, filters.sortIndex, filters.sortDirection)
);
if (isSuccess && !rowsFiltered) {
setRowsFiltered(true);
}
}, [
filteredRows,
filters.limit,
filters.offset,
filters.sortIndex,
filters.sortDirection,
]);
useEffect(() => {
setFilteredRows(buildFilteredRows(recs, filters));
}, [data, filters]);
useEffect(() => {
if (search && filterBuilding) {
const paramsObject = paramParser(search);
if (paramsObject.sort) {
const sortObj = translateSortParams(paramsObject.sort);
paramsObject.sortIndex = RECS_LIST_COLUMNS_KEYS.indexOf(sortObj.name);
paramsObject.sortDirection = sortObj.direction;
}
paramsObject.offset &&
(paramsObject.offset = Number(paramsObject.offset[0]));
paramsObject.limit &&
(paramsObject.limit = Number(paramsObject.limit[0]));
paramsObject.impacting &&
!Array.isArray(paramsObject.impacting) &&
(paramsObject.impacting = [`${paramsObject.impacting}`]);
updateFilters({ ...filters, ...paramsObject });
}
setFilterBuilding(false);
}, []);
useEffect(() => {
if (!filterBuilding) {
updateSearchParams(filters, RECS_LIST_COLUMNS_KEYS);
}
}, [filters, filterBuilding]);
// constructs array of rows (from the initial data) checking currently applied filters
const buildFilteredRows = (allRows, filters) => {
setRowsFiltered(false);
return allRows
.filter((rule) => passFilters(rule, filters))
.map((value, key) => [
{
isOpen: isAllExpanded,
rule: value,
cells: [
{
title: (
<span key={key}>
<Link key={key} to={`/recommendations/${value.rule_id}`}>
{' '}
{value?.description || value?.rule_id}{' '}
</Link>
<RuleLabels rule={value} />
</span>
),
},
{
title: value?.publish_date ? (
<DateFormat
key={key}
date={value.publish_date}
variant="relative"
/>
) : (
intl.formatMessage(messages.nA)
),
},
{
title: <CategoryLabel key={key} tags={value.tags} />,
},
{
title: (
<div key={key}>
<Tooltip
key={key}
position={TooltipPosition.bottom}
content={intl.formatMessage(
messages.rulesDetailsTotalRiskBody,
{
risk:
TOTAL_RISK_LABEL_LOWER[value.total_risk] ||
intl.formatMessage(messages.undefined),
strong,
}
)}
>
{value?.total_risk ? (
<InsightsLabel
value={value.total_risk}
rest={{ isCompact: true }}
/>
) : (
intl.formatMessage(messages.nA)
)}
</Tooltip>
</div>
),
},
{
title: (
<div key={key}>{`${
value?.impacted_clusters_count !== undefined
? value.impacted_clusters_count.toLocaleString()
: intl.formatMessage(messages.nA)
}`}</div>
),
},
],
},
{
fullWidth: true,
cells: [
{
title: (
<section className="pf-m-light pf-l-page__main-section pf-c-page__main-section">
<Stack hasGutter>
<RuleDetails
messages={formatMessages(
intl,
RuleDetailsMessagesKeys,
mapContentToValues(intl, adjustOCPRule(value))
)}
product={AdvisorProduct.ocp}
rule={adjustOCPRule(value)}
isDetailsPage={false}
showViewAffected
linkComponent={Link}
/>
</Stack>
</section>
),
},
],
},
]);
};
/* the category sorting compares only the first element of the array.
Could be refactored later when we assign a priority numbers to each of the category
and sort them in the array based on the priority.
*/
const buildDisplayedRows = (rows, index, direction) => {
const sortingRows = [...rows].sort((firstItem, secondItem) => {
const d = direction === SortByDirection.asc ? 1 : -1;
const fst = firstItem[0].rule[RECS_LIST_COLUMNS_KEYS[index]];
const snd = secondItem[0].rule[RECS_LIST_COLUMNS_KEYS[index]];
if (index === 3) {
return (
d * extractCategories(fst)[0].localeCompare(extractCategories(snd)[0])
);
}
return fst > snd ? d : snd > fst ? -d : 0;
});
return sortingRows
.slice(
filters.limit * (page - 1),
filters.limit * (page - 1) + filters.limit
)
.flatMap((row, index) => {
const updatedRow = [...row];
row[1].parent = index * 2;
return updatedRow;
});
};
const toggleRulesDisabled = (rule_status) =>
updateFilters({
...filters,
rule_status,
offset: 0,
});
const filterConfigItems = [
{
label: intl.formatMessage(messages.name).toLowerCase(),
filterValues: {
key: 'text-filter',
onChange: (_event, value) =>
updateFilters({ ...filters, offset: 0, text: value }),
value: searchText,
placeholder: intl.formatMessage(messages.filterByName),
},
},
{
label: FILTER_CATEGORIES.total_risk.title,
type: FILTER_CATEGORIES.total_risk.type,
id: FILTER_CATEGORIES.total_risk.urlParam,
value: `checkbox-${FILTER_CATEGORIES.total_risk.urlParam}`,
filterValues: {
key: `${FILTER_CATEGORIES.total_risk.urlParam}-filter`,
onChange: (_event, values) =>
addFilterParam(FILTER_CATEGORIES.total_risk.urlParam, values),
value: filters.total_risk,
items: FILTER_CATEGORIES.total_risk.values,
},
},
{
label: FILTER_CATEGORIES.impact.title,
type: FILTER_CATEGORIES.impact.type,
id: FILTER_CATEGORIES.impact.urlParam,
value: `checkbox-${FILTER_CATEGORIES.impact.urlParam}`,
filterValues: {
key: `${FILTER_CATEGORIES.impact.urlParam}-filter`,
onChange: (_event, values) =>
addFilterParam(FILTER_CATEGORIES.impact.urlParam, values),
value: filters.impact,
items: FILTER_CATEGORIES.impact.values,
},
},
{
label: FILTER_CATEGORIES.likelihood.title,
type: FILTER_CATEGORIES.likelihood.type,
id: FILTER_CATEGORIES.likelihood.urlParam,
value: `checkbox-${FILTER_CATEGORIES.likelihood.urlParam}`,
filterValues: {
key: `${FILTER_CATEGORIES.likelihood.urlParam}-filter`,
onChange: (_event, values) =>
addFilterParam(FILTER_CATEGORIES.likelihood.urlParam, values),
value: filters.likelihood,
items: FILTER_CATEGORIES.likelihood.values,
},
},
{
label: FILTER_CATEGORIES.category.title,
type: FILTER_CATEGORIES.category.type,
id: FILTER_CATEGORIES.category.urlParam,
value: `checkbox-${FILTER_CATEGORIES.category.urlParam}`,
filterValues: {
key: `${FILTER_CATEGORIES.category.urlParam}-filter`,
onChange: (_event, values) =>
addFilterParam(FILTER_CATEGORIES.category.urlParam, values),
value: filters.category,
items: FILTER_CATEGORIES.category.values,
},
},
{
label: FILTER_CATEGORIES.rule_status.title,
type: FILTER_CATEGORIES.rule_status.type,
id: FILTER_CATEGORIES.rule_status.urlParam,
value: `radio-${FILTER_CATEGORIES.rule_status.urlParam}`,
filterValues: {
key: `${FILTER_CATEGORIES.rule_status.urlParam}-filter`,
onChange: (_event, value) => toggleRulesDisabled(value),
value: `${filters.rule_status}`,
items: FILTER_CATEGORIES.rule_status.values,
},
},
{
label: FILTER_CATEGORIES.impacting.title,
type: FILTER_CATEGORIES.impacting.type,
id: FILTER_CATEGORIES.impacting.urlParam,
value: `checkbox-${FILTER_CATEGORIES.impacting.urlParam}`,
filterValues: {
key: `${FILTER_CATEGORIES.impacting.urlParam}-filter`,
onChange: (e, values) =>
addFilterParam(FILTER_CATEGORIES.impacting.urlParam, values),
value: filters.impacting,
items: FILTER_CATEGORIES.impacting.values,
},
},
];
const onSort = (_e, index, direction) => {
setRowsFiltered(false);
return updateFilters({
...filters,
sortIndex: index,
sortDirection: direction,
});
};
const pruneFilters = (localFilters, filterCategories) => {
const prunedFilters = Object.entries(localFilters);
return prunedFilters.length > 0
? prunedFilters.reduce((arr, item) => {
if (filterCategories[item[0]]) {
const category = filterCategories[item[0]];
const chips = Array.isArray(item[1])
? item[1].map((value) => {
const selectedCategoryValue = category.values.find(
(values) => values.value === String(value)
);
return selectedCategoryValue
? {
name:
selectedCategoryValue.text ||
selectedCategoryValue.label,
value,
}
: { name: value, value };
})
: [
{
name: category.values.find(
(values) => values.value === String(item[1])
).label,
value: item[1],
},
];
return [
...arr,
{
category: capitalize(category.title),
chips,
urlParam: category.urlParam,
},
];
} else if (item[0] === 'text') {
return [
...arr,
...(item[1].length > 0
? [
{
category: 'Name',
chips: [{ name: item[1], value: item[1] }],
urlParam: item[0],
},
]
: []),
];
} else {
return arr;
}
}, [])
: [];
};
// TODO: use the function from Common/Tables.js
const buildFilterChips = () => {
const localFilters = { ...filters };
delete localFilters.sortIndex;
delete localFilters.sortDirection;
delete localFilters.offset;
delete localFilters.limit;
return pruneFilters(localFilters, FILTER_CATEGORIES);
};
const activeFiltersConfig = {
showDeleteButton: true,
deleteTitle: intl.formatMessage(messages.resetFilters),
filters: buildFilterChips(),
onDelete: (_event, itemsToRemove, isAll) => {
if (isAll) {
if (isEqual(filters, RECS_LIST_INITIAL_STATE)) {
refetch();
} else {
updateFilters(RECS_LIST_INITIAL_STATE);
}
} else {
itemsToRemove.map((item) => {
const newFilter = {
[item.urlParam]: Array.isArray(filters[item.urlParam])
? filters[item.urlParam].filter(
(value) => String(value) !== String(item.chips[0].value)
)
: '',
};
newFilter[item.urlParam].length > 0
? updateFilters({ ...filters, ...newFilter })
: removeFilterParam(item.urlParam);
});
}
},
};
const handleOnCollapse = (_e, rowId, isOpen) => {
if (rowId === undefined) {
// if undefined, all rows are affected
setIsAllExpanded(isOpen);
setDisplayedRows(
displayedRows.map((row) => ({
...row,
isOpen: isOpen,
}))
);
} else {
setDisplayedRows(
displayedRows.map((row, index) =>
index === rowId ? { ...row, isOpen } : row
)
);
}
};
const ackRule = async (rowId) => {
const rule = displayedRows[rowId].rule;
try {
if (!rule.disabled) {
// show disable rule modal
setSelectedRule(rule);
setDisableRuleOpen(true);
} else {
try {
await Delete(`${BASE_URL}/v2/ack/${rule.rule_id}/`);
notify({
variant: 'success',
timeout: true,
dismissable: true,
title: intl.formatMessage(messages.recSuccessfullyEnabled),
});
refetch();
} catch (error) {
notify({
variant: 'danger',
dismissable: true,
title: intl.formatMessage(messages.error),
description: `${error}`,
});
}
}
} catch (error) {
notify({
variant: 'danger',
dismissable: true,
title: rule.disabled
? intl.formatMessage(messages.rulesTableErrorEnabled)
: intl.formatMessage(messages.rulesTableErrorDisabled),
description: `${error}`,
});
}
};
const actionResolver = (rowData, { rowIndex }) => {
const rule = displayedRows?.[rowIndex]?.rule
? displayedRows[rowIndex].rule
: null;
if (rowIndex % 2 !== 0 || !rule) {
return null;
}
return rule && !rule.disabled
? [
{
title: intl.formatMessage(messages.disableRule),
onClick: (_event, rowId) => ackRule(rowId),
},
]
: [
{
title: intl.formatMessage(messages.enableRule),
onClick: (_event, rowId) => ackRule(rowId),
},
];
};
return (
<div id="recs-list-table" data-ouia-safe={testSafe}>
{disableRuleOpen && (
<DisableRule
handleModalToggle={setDisableRuleOpen}
isModalOpen={disableRuleOpen}
rule={selectedRule}
afterFn={refetch}
/>
)}
<PrimaryToolbar
pagination={{
itemCount: filteredRows.length,
page: filters.offset / filters.limit + 1,
perPage: Number(filters.limit),
onSetPage(_event, page) {
setRowsFiltered(false);
updateFilters({
...filters,
offset: filters.limit * (page - 1),
});
},
onPerPageSelect(_event, perPage) {
setRowsFiltered(false);
updateFilters({ ...filters, limit: perPage, offset: 0 });
},
isCompact: true,
ouiaId: 'pager',
}}
filterConfig={{
items: filterConfigItems,
isDisabled: loadingState || errorState,
}}
activeFiltersConfig={errorState ? undefined : activeFiltersConfig}
/>
<Table
aria-label="Table of recommendations"
ouiaId="recommendations"
variant={TableVariant.compact}
cells={RECS_LIST_COLUMNS}
rows={
errorState || loadingState || noMatch ? (
[
{
fullWidth: true,
cells: [
{
props: {
colSpan: RECS_LIST_COLUMNS.length + 1,
},
title: errorState ? (
<ErrorState />
) : loadingState ? (
<Loading />
) : (
<NoMatchingRecs />
),
},
],
},
]
) : successState ? (
displayedRows
) : (
<ErrorState />
)
}
onCollapse={handleOnCollapse} // TODO: set undefined when there is an empty state
sortBy={{
index: filters.sortIndex,
direction: filters.sortDirection,
}}
onSort={onSort}
actionResolver={actionResolver}
isStickyHeader
ouiaSafe={testSafe}
canCollapseAll
>
<TableHeader />
<TableBody />
</Table>
<Pagination
ouiaId="pager"
itemCount={filteredRows.length}
page={filters.offset / filters.limit + 1}
perPage={Number(filters.limit)}
onSetPage={(_e, page) =>
updateFilters({
...filters,
offset: filters.limit * (page - 1),
})
}
onPerPageSelect={(_e, perPage) =>
updateFilters({ ...filters, limit: perPage, offset: 0 })
}
widgetId={`pagination-options-menu-bottom`}
variant={PaginationVariant.bottom}
/>
</div>
);
}
Example #13
Source File: ClustersListTable.js From ocp-advisor-frontend with Apache License 2.0 | 4 votes |
ClustersListTable = ({
query: { isError, isUninitialized, isFetching, isSuccess, data, refetch },
}) => {
const intl = useIntl();
const dispatch = useDispatch();
const updateFilters = (payload) =>
dispatch(updateClustersListFilters(payload));
const filters = useSelector(({ filters }) => filters.clustersListState);
const clusters = data?.data || [];
const page = filters.offset / filters.limit + 1;
const [filteredRows, setFilteredRows] = useState([]);
const [displayedRows, setDisplayedRows] = useState([]);
// helps to distinguish the state when the API data received but not yet filtered
const [rowsFiltered, setRowsFiltered] = useState(false);
const [filterBuilding, setFilterBuilding] = useState(true);
const { search } = useLocation();
const loadingState = isUninitialized || isFetching || !rowsFiltered;
const errorState = isError;
const noMatch = clusters.length > 0 && filteredRows.length === 0;
const successState = isSuccess;
const removeFilterParam = (param) =>
_removeFilterParam(filters, updateFilters, param);
const addFilterParam = (param, values) =>
_addFilterParam(filters, updateFilters, param, values);
useEffect(() => {
setDisplayedRows(buildDisplayedRows(filteredRows));
}, [filteredRows, filters.limit, filters.offset]);
useEffect(() => {
setFilteredRows(buildFilteredRows(clusters));
if (isSuccess && !rowsFiltered) {
setRowsFiltered(true);
}
}, [data, filters]);
useEffect(() => {
if (search && filterBuilding) {
const paramsObject = paramParser(search);
if (paramsObject.sort) {
const sortObj = translateSortParams(paramsObject.sort);
paramsObject.sortIndex = CLUSTERS_LIST_COLUMNS_KEYS.indexOf(
sortObj.name
);
paramsObject.sortDirection = sortObj.direction;
}
paramsObject.offset &&
(paramsObject.offset = Number(paramsObject.offset[0]));
paramsObject.limit &&
(paramsObject.limit = Number(paramsObject.limit[0]));
paramsObject.impacting &&
!Array.isArray(paramsObject.impacting) &&
(paramsObject.impacting = [`${paramsObject.impacting}`]);
updateFilters({ ...filters, ...paramsObject });
}
setFilterBuilding(false);
}, []);
useEffect(() => {
if (!filterBuilding) {
updateSearchParams(filters, CLUSTERS_LIST_COLUMNS_KEYS);
}
}, [filters, filterBuilding]);
const buildFilteredRows = (items) => {
const filtered = items.filter((it) => {
return passFiltersCluster(it, filters);
});
const mapped = filtered.map((it, index) => {
if (
it.cluster_version !== undefined &&
it.cluster_version !== '' &&
!valid(coerce(it.cluster_version))
) {
console.error(
`Cluster version ${it.cluster_version} has invalid format!`
);
}
const ver = toValidSemVer(it.cluster_version);
return {
entity: it,
cells: [
<span key={index}>
<Link to={`clusters/${it.cluster_id}`}>
{it.cluster_name || it.cluster_id}
</Link>
</span>,
ver === '0.0.0' ? intl.formatMessage(messages.notAvailable) : ver,
it.total_hit_count,
it.hits_by_total_risk?.[4] || 0,
it.hits_by_total_risk?.[3] || 0,
it.hits_by_total_risk?.[2] || 0,
it.hits_by_total_risk?.[1] || 0,
<span key={index}>
{it.last_checked_at ? (
<DateFormat
extraTitle={`${intl.formatMessage(messages.lastSeen)}: `}
date={it.last_checked_at}
variant="relative"
/>
) : (
<Tooltip
key={index}
content={
<span>
{intl.formatMessage(messages.lastSeen) + ': '}
{intl.formatMessage(messages.nA)}
</span>
}
>
<span>{intl.formatMessage(messages.nA)}</span>
</Tooltip>
)}
</span>,
],
};
});
const sorted =
filters.sortIndex === -1
? mapped
: mapped.sort((a, b) => {
let fst, snd;
const d = filters.sortDirection === SortByDirection.asc ? 1 : -1;
switch (filters.sortIndex) {
case CLUSTERS_TABLE_CELL_NAME:
fst = a.entity.cluster_name || a.entity.cluster_id;
snd = b.entity.cluster_name || b.entity.cluster_id;
return fst.localeCompare(snd) ? fst.localeCompare(snd) * d : 0;
case CLUSTERS_TABLE_CELL_VERSION:
return compareSemVer(
toValidSemVer(a.entity.cluster_version),
toValidSemVer(b.entity.cluster_version),
d
);
case CLUSTERS_TABLE_CELL_LAST_SEEN:
fst = new Date(a.entity.last_checked_at || 0);
snd = new Date(b.entity.last_checked_at || 0);
return fst > snd ? d : snd > fst ? -d : 0;
default:
fst = a.cells[filters.sortIndex];
snd = b.cells[filters.sortIndex];
return fst > snd ? d : snd > fst ? -d : 0;
}
});
return sorted;
};
const buildDisplayedRows = (items) =>
items.slice(
filters.limit * (page - 1),
filters.limit * (page - 1) + filters.limit
);
const filterConfigItems = [
{
label: intl.formatMessage(messages.name).toLowerCase(),
filterValues: {
key: 'text-filter',
onChange: (_event, value) => updateFilters({ ...filters, text: value }),
value: filters.text,
placeholder: intl.formatMessage(messages.filterByName),
},
},
{
label: intl.formatMessage(messages.version),
placeholder: intl.formatMessage(messages.filterByVersion),
type: conditionalFilterType.checkbox,
filterValues: {
id: 'version-filter',
key: 'version-filter',
onChange: (event, value) => addFilterParam('version', value),
value: filters.version,
items: uniqBy(
clusters
.filter(
(c) => c.cluster_version !== undefined && c.cluster_version !== ''
)
.map((c) => ({
value: toValidSemVer(c.cluster_version),
}))
.sort((a, b) =>
compareSemVer(
toValidSemVer(a.cluster_version),
toValidSemVer(b.cluster_version),
1
)
)
.reverse(), // should start from the latest version
'value'
),
},
},
{
label: CLUSTER_FILTER_CATEGORIES.hits.title,
type: CLUSTER_FILTER_CATEGORIES.hits.type,
id: CLUSTER_FILTER_CATEGORIES.hits.urlParam,
value: `checkbox-${CLUSTER_FILTER_CATEGORIES.hits.urlParam}`,
filterValues: {
key: `${CLUSTER_FILTER_CATEGORIES.hits.urlParam}-filter`,
onChange: (_event, values) =>
addFilterParam(CLUSTER_FILTER_CATEGORIES.hits.urlParam, values),
value: filters.hits,
items: CLUSTER_FILTER_CATEGORIES.hits.values,
},
},
];
const activeFiltersConfig = {
showDeleteButton: true,
deleteTitle: intl.formatMessage(messages.resetFilters),
filters: buildFilterChips(filters, CLUSTER_FILTER_CATEGORIES),
onDelete: (_event, itemsToRemove, isAll) => {
if (isAll) {
if (isEqual(filters, CLUSTERS_LIST_INITIAL_STATE)) {
refetch();
} else {
updateFilters(CLUSTERS_LIST_INITIAL_STATE);
}
} else {
itemsToRemove.map((item) => {
const newFilter = {
[item.urlParam]: Array.isArray(filters[item.urlParam])
? filters[item.urlParam].filter(
(value) => String(value) !== String(item.chips[0].value)
)
: '',
};
newFilter[item.urlParam].length > 0
? updateFilters({ ...filters, ...newFilter })
: removeFilterParam(item.urlParam);
});
}
},
};
const onSort = (_e, index, direction) => {
updateFilters({ ...filters, sortIndex: index, sortDirection: direction });
};
return (
<>
{isSuccess && clusters.length === 0 ? (
<NoRecsForClusters /> // TODO: do not mix this logic in the table component
) : (
<div id="clusters-list-table">
<PrimaryToolbar
pagination={{
itemCount: filteredRows.length,
page,
perPage: filters.limit,
onSetPage: (_event, page) =>
updateFilters({
...filters,
offset: filters.limit * (page - 1),
}),
onPerPageSelect: (_event, perPage) =>
updateFilters({ ...filters, limit: perPage, offset: 0 }),
isCompact: true,
ouiaId: 'pager',
}}
filterConfig={{ items: filterConfigItems }}
activeFiltersConfig={activeFiltersConfig}
/>
<Table
aria-label="Table of clusters"
ouiaId="clusters"
variant={TableVariant.compact}
cells={CLUSTERS_LIST_COLUMNS}
rows={
errorState || loadingState || noMatch ? (
[
{
fullWidth: true,
cells: [
{
props: {
colSpan: CLUSTERS_LIST_COLUMNS.length + 1,
},
title: errorState ? (
<ErrorState />
) : loadingState ? (
<Loading />
) : (
<NoMatchingClusters />
),
},
],
},
]
) : successState ? (
displayedRows
) : (
<ErrorState />
)
}
sortBy={{
index: filters.sortIndex,
direction: filters.sortDirection,
}}
onSort={onSort}
isStickyHeader
>
<TableHeader />
<TableBody />
</Table>
<Pagination
ouiaId="pager"
itemCount={filteredRows.length}
page={filters.offset / filters.limit + 1}
perPage={Number(filters.limit)}
onSetPage={(_e, page) =>
updateFilters({
...filters,
offset: filters.limit * (page - 1),
})
}
onPerPageSelect={(_e, perPage) =>
updateFilters({ ...filters, limit: perPage, offset: 0 })
}
widgetId={`pagination-options-menu-bottom`}
variant={PaginationVariant.bottom}
/>
</div>
)}
</>
);
}
Example #14
Source File: ClusterRules.js From ocp-advisor-frontend with Apache License 2.0 | 4 votes |
ClusterRules = ({ cluster }) => {
const intl = useIntl();
const dispatch = useDispatch();
const updateFilters = (filters) =>
dispatch(updateClusterRulesFilters(filters));
const filters = useSelector(({ filters }) => filters.clusterRulesState);
const { isError, isUninitialized, isFetching, isSuccess, data, error } =
cluster;
const reports = data?.report?.data || [];
const [filteredRows, setFilteredRows] = useState([]);
const [displayedRows, setDisplayedRows] = useState([]);
const [isAllExpanded, setIsAllExpanded] = useState(false);
const [expandFirst, setExpandFirst] = useState(true);
const [firstRule, setFirstRule] = useState(''); // show a particular rule first
const results = filteredRows.length;
const { search } = useLocation();
const [rowsUpdating, setRowsUpdating] = useState(true);
const [rowsFiltered, setRowsFiltered] = useState(false);
const loadingState = isUninitialized || isFetching || rowsUpdating;
const errorState = isError;
const successState = isSuccess;
const noInput = successState && reports.length === 0;
const noMatch = reports.length > 0 && filteredRows.length === 0;
const removeFilterParam = (param) =>
_removeFilterParam(filters, updateFilters, param);
const addFilterParam = (param, values) => {
setExpandFirst(false);
setFirstRule('');
return _addFilterParam(filters, updateFilters, param, values);
};
useEffect(() => {
if (search) {
const paramsObject = paramParser(search);
if (paramsObject.sort) {
const sortObj = translateSortParams(paramsObject.sort);
paramsObject.sortIndex = CLUSTER_RULES_COLUMNS_KEYS.indexOf(
sortObj.name
);
paramsObject.sortDirection = sortObj.direction;
}
if (paramsObject.first) {
setFirstRule(paramsObject.first);
delete paramsObject.first;
}
updateFilters({ ...filters, ...paramsObject });
}
}, []);
useEffect(() => {
setFilteredRows(buildFilteredRows(reports, filters));
}, [data, filters]);
useEffect(() => {
setDisplayedRows(
buildDisplayedRows(filteredRows, filters.sortIndex, filters.sortDirection)
);
setRowsFiltered(true);
}, [
filteredRows,
filters.limit,
filters.offset,
filters.sortIndex,
filters.sortDirection,
]);
useEffect(() => {
if (rowsFiltered) {
setRowsUpdating(false);
}
}, [rowsFiltered]);
const handleOnCollapse = (_e, rowId, isOpen) => {
if (rowId === undefined) {
// if undefined, all rows are affected
setIsAllExpanded(isOpen);
setDisplayedRows(
displayedRows.map((row) => ({
...row,
isOpen: isOpen,
}))
);
} else {
setDisplayedRows(
displayedRows.map((row, index) =>
index === rowId ? { ...row, isOpen } : row
)
);
}
};
const buildFilteredRows = (allRows, filters) => {
const expandedRowsSet = new Set(
displayedRows
.filter((ruleExpanded) => ruleExpanded?.isOpen)
.map((object) => object?.rule?.rule_id)
);
return allRows
.filter((rule) => passFilters(rule, filters))
.map((value, key) => [
{
rule: value,
isOpen: isAllExpanded || expandedRowsSet?.has(value?.rule_id),
cells: [
{
title: (
<div>
{value?.description || value?.rule_id}{' '}
<RuleLabels rule={value} />
</div>
),
},
{
title: (
<div key={key}>
<DateFormat
date={value.created_at}
type="relative"
tooltipProps={{ position: TooltipPosition.bottom }}
/>
</div>
),
},
{
title: (
<div key={key} style={{ verticalAlign: 'top' }}>
{value?.likelihood && value?.impact ? (
<Tooltip
key={key}
position={TooltipPosition.bottom}
content={
// TODO: refine fields lookup
<span>
The <strong>likelihood</strong> that this will be a
problem is{' '}
{value.likelihood
? LIKELIHOOD_LABEL[value.likelihood]
: 'unknown'}
.The <strong>impact</strong> of the problem would be{' '}
{value.impact
? IMPACT_LABEL[value.impact]
: 'unknown'}{' '}
if it occurred.
</span>
}
>
<InsightsLabel
value={value.total_risk}
rest={{ isCompact: true }}
/>
</Tooltip>
) : (
<InsightsLabel
value={value.total_risk}
rest={{ isCompact: true }}
/>
)}
</div>
),
},
],
},
{
fullWidth: true,
cells: [
{
title: (
<ReportDetails
key={`child-${key}`}
report={{
rule: value,
resolution: value.resolution,
details: value.extra_data,
}}
/>
),
},
],
},
]);
};
const buildDisplayedRows = (rows, index, direction) => {
let sortingRows = [...rows];
if (index >= 0 && !firstRule) {
const d = direction === SortByDirection.asc ? 1 : -1;
sortingRows = [...rows].sort((firstItem, secondItem) => {
const fst = firstItem[0].rule[CLUSTER_RULES_COLUMNS_KEYS[index]];
const snd = secondItem[0].rule[CLUSTER_RULES_COLUMNS_KEYS[index]];
return fst > snd ? d : snd > fst ? -d : 0;
});
} else if (firstRule) {
const i = rows.findIndex((row) => {
const rule = row[0].rule;
/* rule_id is given with the plugin name only,
thus we need to look at extra_data for the error key */
return (
rule.rule_id.split('.report')[0] === getPluginName(firstRule) &&
rule.extra_data.error_key === getErrorKey(firstRule)
);
});
i !== -1 && sortingRows.unshift(sortingRows.splice(i, 1)[0]);
}
return sortingRows.flatMap((row, index) => {
const updatedRow = [...row];
if (expandFirst && index === 0) {
row[0].isOpen = true;
}
row[1].parent = index * 2;
return updatedRow;
});
};
const onSort = (_e, index, direction) => {
setExpandFirst(false);
setFirstRule('');
return updateFilters({
...filters,
sortIndex: index,
sortDirection: direction,
});
};
const filterConfigItems = [
{
label: 'description',
filterValues: {
key: 'text-filter',
onChange: (_e, value) => addFilterParam('text', value),
value: filters.text,
},
},
{
label: FC.total_risk.title,
type: FC.total_risk.type,
id: FC.total_risk.urlParam,
value: `checkbox-${FC.total_risk.urlParam}`,
filterValues: {
key: `${FC.total_risk.urlParam}-filter`,
onChange: (_e, values) =>
addFilterParam(FILTER_CATEGORIES.total_risk.urlParam, values),
value: filters.total_risk,
items: FC.total_risk.values,
},
},
{
label: FC.category.title,
type: FC.category.type,
id: FC.category.urlParam,
value: `checkbox-${FC.category.urlParam}`,
filterValues: {
key: `${FC.category.urlParam}-filter`,
onChange: (_e, values) =>
addFilterParam(FILTER_CATEGORIES.category.urlParam, values),
value: filters.category,
items: FC.category.values,
},
},
];
const pruneFilters = (localFilters, filterCategories) => {
const prunedFilters = Object.entries(localFilters);
return prunedFilters.length > 0
? prunedFilters.reduce((arr, item) => {
if (filterCategories[item[0]]) {
const category = filterCategories[item[0]];
const chips = Array.isArray(item[1])
? item[1].map((value) => {
const selectedCategoryValue = category.values.find(
(values) => values.value === String(value)
);
return selectedCategoryValue
? {
name:
selectedCategoryValue.text ||
selectedCategoryValue.label,
value,
}
: { name: value, value };
})
: [
{
name: category.values.find(
(values) => values.value === String(item[1])
).label,
value: item[1],
},
];
return [
...arr,
{
category: capitalize(category.title),
chips,
urlParam: category.urlParam,
},
];
} else if (item[0] === 'text') {
return [
...arr,
...(item[1].length > 0
? [
{
category: intl.formatMessage(messages.description),
chips: [{ name: item[1], value: item[1] }],
urlParam: item[0],
},
]
: []),
];
} else {
return arr;
}
}, [])
: [];
};
const buildFilterChips = () => {
const localFilters = { ...filters };
delete localFilters.sortIndex;
delete localFilters.sortDirection;
delete localFilters.offset;
delete localFilters.limit;
return pruneFilters(localFilters, FILTER_CATEGORIES);
};
const activeFiltersConfig = {
deleteTitle: intl.formatMessage(messages.resetFilters),
filters: buildFilterChips(),
onDelete: (_event, itemsToRemove, isAll) => {
if (isAll) {
updateFilters(CLUSTER_RULES_INITIAL_STATE);
} else {
itemsToRemove.map((item) => {
const newFilter = {
[item.urlParam]: Array.isArray(filters[item.urlParam])
? filters[item.urlParam].filter(
(value) => String(value) !== String(item.chips[0].value)
)
: '',
};
newFilter[item.urlParam].length > 0
? updateFilters({ ...filters, ...newFilter })
: removeFilterParam(item.urlParam);
});
}
},
};
return (
<div id="cluster-recs-list-table">
<PrimaryToolbar
filterConfig={{
items: filterConfigItems,
isDisabled: loadingState || errorState || reports.length === 0,
}}
pagination={
<React.Fragment>
{results === 1
? `${results} ${intl.formatMessage(messages.recommendation)}`
: `${results} ${intl.formatMessage(messages.recommendations)}`}
</React.Fragment>
}
activeFiltersConfig={
loadingState || errorState || reports.length === 0
? undefined
: activeFiltersConfig
}
/>
<Table
aria-label={'Cluster recommendations table'}
ouiaId="recommendations"
onCollapse={handleOnCollapse} // TODO: set undefined when there is an empty state
rows={
errorState || loadingState || noMatch || noInput ? (
[
{
fullWidth: true,
cells: [
{
props: {
colSpan: CLUSTER_RULES_COLUMNS.length + 1,
},
title: errorState ? (
error?.status === 404 ? (
<NoInsightsResults /> // no Insights results received yet
) : (
<NoRecsError /> // any other problem
)
) : loadingState ? (
<Loading />
) : noInput ? (
<NoRecsAffecting />
) : (
<NoMatchingRecs />
),
},
],
},
]
) : successState ? (
displayedRows
) : (
<ErrorState />
)
}
cells={CLUSTER_RULES_COLUMNS}
sortBy={{
index: filters.sortIndex,
direction: filters.sortDirection,
}}
onSort={onSort}
variant={TableVariant.compact}
isStickyHeader
canCollapseAll
>
<TableHeader />
<TableBody />
</Table>
</div>
);
}
Example #15
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 #16
Source File: DeviceDetail.js From edge-frontend with Apache License 2.0 | 4 votes |
GeneralInformationTab = () => {
const writePermissions = useSelector(
({ permissionsReducer }) => permissionsReducer?.writePermissions
);
const { greenbootStatus, rhcHealth } = useSelector(
({ systemProfileStore }) => ({
greenbootStatus: systemProfileStore?.systemProfile?.greenboot_status,
rhcHealth: null,
})
);
return (
<Suspense fallback="">
<GeneralInformation
store={useStore()}
writePermissions={writePermissions}
SystemCardWrapper={(props) => (
<Suspense fallback="">
<SystemCard
{...props}
hasCPUs={false}
hasSockets={false}
hasCores={false}
hasCPUFlags={false}
hasRAM={false}
hasSAP={false}
extra={[
{
title: (
<TitleWithPopover
title="GreenBoot Status"
content="This is a description about greenboot status"
/>
),
value: <GreenbootStatus status={greenbootStatus} />,
},
]}
/>
</Suspense>
)}
OperatingSystemCardWrapper={(props) => (
<Suspense fallback="">
{' '}
<InfrastructureCard {...props} />
</Suspense>
)}
BiosCardWrapper={(props) => (
<Suspense fallback="">
{' '}
<ImageInformationCard {...props} />
</Suspense>
)}
InfrastructureCardWrapper={(props) => (
<Suspense fallback="">
<BiosCard {...props} />
</Suspense>
)}
ConfigurationCardWrapper={(props) => (
<Suspense fallback="">
<OperatingSystemCard {...props} hasKernelModules={true} />
</Suspense>
)}
CollectionCardWrapper={(props) => (
<Suspense fallback="">
<CollectionCard
{...props}
extra={[
{
title: 'RHC Health (broker functioning)',
value: statusHelper[rhcHealth?.toUpperCase()] || (
<Tooltip content="Unknown service status">
<OutlinedQuestionCircleIcon className="ins-c-inventory__detail--unknown" />
</Tooltip>
),
},
]}
/>
</Suspense>
)}
/>
</Suspense>
);
}
Example #17
Source File: RepositoryTable.js From edge-frontend with Apache License 2.0 | 4 votes |
RepositoryTable = ({
data,
count,
isLoading,
hasError,
fetchRepos,
openModal,
}) => {
const actionResolver = (rowData) => {
if (!rowData.rowInfo) {
return [];
}
const { id, repoName, repoBaseURL } = rowData.rowInfo;
return [
{
title: 'Edit',
onClick: () =>
openModal({
type: 'edit',
id: id,
name: repoName,
baseURL: repoBaseURL,
}),
},
{
title: 'Remove',
onClick: () =>
openModal({
type: 'remove',
id: id,
name: repoName,
baseURL: repoBaseURL,
}),
},
];
};
const buildRows = data.map(({ ID, Name, URL }) => {
return {
rowInfo: {
id: ID,
repoName: Name,
repoBaseURL: URL,
},
cells: [
{
title: (
<>
<Text className="pf-u-mb-xs" component={TextVariants.p}>
<Tooltip content={<div>{Name}</div>}>
<span>{truncateString(Name, 20)}</span>
</Tooltip>
</Text>
<Text
component={TextVariants.a}
href={URL}
target="_blank"
rel="noopener noreferrer"
>
{URL} <ExternalLinkAltIcon className="pf-u-ml-sm" />
</Text>
</>
),
},
],
};
});
return (
<>
<GeneralTable
apiFilterSort={true}
isUseApi={true}
loadTableData={fetchRepos}
filters={filters}
tableData={{
count,
data,
isLoading,
hasError,
}}
columnNames={[{ title: 'Name', type: 'name', sort: true }]}
rows={buildRows}
actionResolver={actionResolver}
areActionsDisabled={() => false}
defaultSort={{ index: 0, direction: 'asc' }}
toolbarButtons={[
{
title: 'Add repository',
click: () => openModal({ type: 'add' }),
},
]}
/>
</>
);
}
Example #18
Source File: GroupTable.js From edge-frontend with Apache License 2.0 | 4 votes |
GroupTable = ({
data,
count,
isLoading,
hasError,
handleCreateModal,
handleRenameModal,
handleDeleteModal,
fetchGroups,
}) => {
const [updateModal, setUpdateModal] = useState({
isOpen: false,
deviceData: null,
imageData: null,
});
const actionResolver = (rowData) => {
if (!rowData?.rowInfo) return [];
const { id, title, devices, devicesImageInfo } = rowData?.rowInfo;
const hasUpdate = devicesImageInfo?.some((image) => image.UpdateAvailable);
return (
id && [
{
title: 'Rename',
onClick: () => handleRenameModal(id, title),
},
{
title: 'Delete',
onClick: () => handleDeleteModal(id, title),
},
{
title: 'Update',
onClick: () =>
setUpdateModal((prevState) => ({
...prevState,
deviceData: devices.map((device) => ({
id: device.UUID,
display_name: device.Name,
})),
imageId: devices.find((device) => device?.ImageID).ImageID,
isOpen: true,
})),
isDisabled:
devices.length > 0
? !(rowData?.rowInfo?.hasValidUpdate && hasUpdate)
: true,
},
]
);
};
const buildRows = data?.map((rowData) => {
const { ID, Name, Devices } = rowData?.DeviceGroup;
let { DevicesImageInfo } = rowData;
if (!DevicesImageInfo) {
DevicesImageInfo = [];
}
const systems = Devices ?? [];
const image = (
<div>
<Tooltip
content={
<div>
{DevicesImageInfo.map((device, index) => (
<p key={index}>{device.Name}</p>
))}
</div>
}
>
<span>Multiple images</span>
</Tooltip>
</div>
);
return {
rowInfo: {
id: ID,
title: Name,
image:
DevicesImageInfo.length === 0
? '-'
: DevicesImageInfo.length > 1
? 'Multiple images'
: DevicesImageInfo[0]?.Name,
devicesImageInfo: rowData.DevicesImageInfo,
devices: Devices,
hasValidUpdate: rowData?.DeviceGroup?.ValidUpdate,
},
cells: [
{
title: <Link to={`${paths['fleet-management']}/${ID}`}>{Name}</Link>,
},
{
title: systems.length,
},
{
title:
DevicesImageInfo.length === 0
? '-'
: DevicesImageInfo.length > 1
? image
: DevicesImageInfo[0]?.Name,
},
],
};
});
return (
<>
<GeneralTable
apiFilterSort={true}
isUseApi={true}
loadTableData={fetchGroups}
filters={filters}
tableData={{
count,
data,
isLoading,
hasError,
}}
columnNames={columns}
rows={buildRows}
actionResolver={actionResolver}
areActionsDisabled={() => false}
defaultSort={{ index: 0, direction: 'asc' }}
emptyFilterState={{
title: 'No matching groups found',
body: 'To continue, edit your filter settings and try again',
}}
toolbarButtons={[
{
title: 'Create group',
click: handleCreateModal,
},
]}
/>
{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={fetchGroups}
/>
</Suspense>
)}
</>
);
}
Example #19
Source File: DeviceTable.js From edge-frontend with Apache License 2.0 | 4 votes |
createRows = (devices, hasLinks) => {
return devices?.map((device) => {
let { DeviceName, DeviceGroups } = device;
const {
DeviceID,
DeviceUUID,
UpdateAvailable,
LastSeen,
ImageName,
ImageSetID,
// ImageID,
Status,
} = device;
if (DeviceName === '') {
// needs to be fixed with proper name in sync with inv
DeviceName = 'localhost';
}
if (DeviceGroups === null) {
DeviceGroups = [];
}
const deviceGroupTooltip = (
<div>
<Tooltip
content={
<div>
{DeviceGroups.map((group, index) => (
<p key={index}>{group.Name}</p>
))}
</div>
}
>
<span>Multiple groups</span>
</Tooltip>
</div>
);
return {
rowInfo: {
deviceID: DeviceID,
id: DeviceUUID,
display_name: DeviceName,
updateImageData: UpdateAvailable,
deviceStatus: getDeviceStatus(Status, UpdateAvailable),
imageSetId: ImageSetID,
imageName: ImageName,
deviceGroups: DeviceGroups,
},
noApiSortFilter: [
DeviceName || '',
ImageName || '',
'',
LastSeen || '',
getDeviceStatus(Status, UpdateAvailable),
],
cells: [
{
title: hasLinks ? (
<Link to={`${paths['inventory']}/${DeviceUUID}`}>{DeviceName}</Link>
) : (
DeviceName
),
},
{
title: ImageName ? (
hasLinks ? (
<Link to={`${paths['manage-images']}/${ImageSetID}/`}>
{ImageName}
</Link>
) : (
ImageName
)
) : (
'unavailable'
),
},
{
title:
DeviceGroups.length === 0
? '-'
: DeviceGroups.length === 1
? DeviceGroups[0].Name
: deviceGroupTooltip,
},
{
title: LastSeen ? <DateFormat date={LastSeen} /> : 'Unknown',
},
{
title: (
<DeviceStatus type={getDeviceStatus(Status, UpdateAvailable)} />
),
},
],
};
});
}