convert inventory access/hosts lists to tables

This commit is contained in:
Keith J. Grant 2021-04-23 11:00:16 -07:00
parent f8ecdbf287
commit 0ac6ba9c99
5 changed files with 117 additions and 194 deletions

View File

@ -47,7 +47,11 @@ function HostToggle({
return (
<Fragment>
<Tooltip content={tooltip} position="top">
<Tooltip
content={tooltip}
position="top"
css="white-space: nowrap; --pf-c-table--cell--WhiteSpace: nowrap"
>
<Switch
className={className}
css="display: inline-flex;"

View File

@ -6,7 +6,8 @@ import { RolesAPI, TeamsAPI, UsersAPI } from '../../api';
import AddResourceRole from '../AddRole/AddResourceRole';
import AlertModal from '../AlertModal';
import DataListToolbar from '../DataListToolbar';
import PaginatedDataList, { ToolbarAddButton } from '../PaginatedDataList';
import PaginatedTable, { HeaderRow, HeaderCell } from '../PaginatedTable';
import { ToolbarAddButton } from '../PaginatedDataList';
import { getQSConfig, parseQueryString } from '../../util/qs';
import useRequest, { useDeleteItems } from '../../util/useRequest';
import DeleteRoleConfirmationModal from './DeleteRoleConfirmationModal';
@ -148,7 +149,7 @@ function ResourceAccessList({ apiModel, resource }) {
return (
<>
<PaginatedDataList
<PaginatedTable
error={contentError}
hasContentLoading={isLoading || isDeleteLoading}
items={accessRecords}
@ -156,20 +157,6 @@ function ResourceAccessList({ apiModel, resource }) {
pluralizedItemName={t`Roles`}
qsConfig={QS_CONFIG}
toolbarSearchColumns={toolbarSearchColumns}
toolbarSortColumns={[
{
name: t`Username`,
key: 'username',
},
{
name: t`First Name`,
key: 'first_name',
},
{
name: t`Last Name`,
key: 'last_name',
},
]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
renderToolbar={props => (
@ -188,7 +175,15 @@ function ResourceAccessList({ apiModel, resource }) {
}
/>
)}
renderItem={accessRecord => (
headerRow={
<HeaderRow qsConfig={QS_CONFIG} isSelectable={false}>
<HeaderCell sortKey="username">{t`Username`}</HeaderCell>
<HeaderCell sortKey="first_name">{t`First name`}</HeaderCell>
<HeaderCell sortKey="last_name">{t`Last name`}</HeaderCell>
<HeaderCell>{t`Roles`}</HeaderCell>
</HeaderRow>
}
renderRow={(accessRecord, index) => (
<ResourceAccessListItem
key={accessRecord.id}
accessRecord={accessRecord}
@ -197,6 +192,7 @@ function ResourceAccessList({ apiModel, resource }) {
setDeletionRole(role);
setShowDeleteModal(true);
}}
rowIndex={index}
/>
)}
/>

View File

@ -3,27 +3,14 @@ import React from 'react';
import { func } from 'prop-types';
import { t } from '@lingui/macro';
import {
Chip,
DataListItem,
DataListItemRow,
DataListItemCells as PFDataListItemCells,
Text,
TextContent,
TextVariants,
} from '@patternfly/react-core';
import { Chip } from '@patternfly/react-core';
import { Tr, Td } from '@patternfly/react-table';
import { Link } from 'react-router-dom';
import styled from 'styled-components';
import DataListCell from '../DataListCell';
import ChipGroup from '../ChipGroup';
import { DetailList, Detail } from '../DetailList';
import { AccessRecord } from '../../types';
const DataListItemCells = styled(PFDataListItemCells)`
align-items: start;
`;
function ResourceAccessListItem({ accessRecord, onRoleDelete }) {
ResourceAccessListItem.propTypes = {
accessRecord: AccessRecord.isRequired,
@ -67,70 +54,43 @@ function ResourceAccessListItem({ accessRecord, onRoleDelete }) {
const [teamRoles, userRoles] = getRoleLists();
return (
<DataListItem
aria-labelledby="access-list-item"
key={accessRecord.id}
id={`${accessRecord.id}`}
>
<DataListItemRow>
<DataListItemCells
dataListCells={[
<DataListCell key="name">
{accessRecord.username && (
<TextContent>
{accessRecord.id ? (
<Text component={TextVariants.h6}>
<Link
to={{ pathname: `/users/${accessRecord.id}/details` }}
css="font-weight: bold"
>
{accessRecord.username}
</Link>
</Text>
) : (
<Text component={TextVariants.h6} css="font-weight: bold">
{accessRecord.username}
</Text>
)}
</TextContent>
)}
{accessRecord.first_name || accessRecord.last_name ? (
<DetailList stacked>
<Detail
label={t`Name`}
value={`${accessRecord.first_name} ${accessRecord.last_name}`}
/>
</DetailList>
) : null}
</DataListCell>,
<DataListCell key="roles">
<DetailList stacked>
{userRoles.length > 0 && (
<Detail
label={t`User Roles`}
value={
<ChipGroup numChips={5} totalChips={userRoles.length}>
{userRoles.map(renderChip)}
</ChipGroup>
}
/>
)}
{teamRoles.length > 0 && (
<Detail
label={t`Team Roles`}
value={
<ChipGroup numChips={5} totalChips={teamRoles.length}>
{teamRoles.map(renderChip)}
</ChipGroup>
}
/>
)}
</DetailList>
</DataListCell>,
]}
/>
</DataListItemRow>
</DataListItem>
<Tr id={`access-item-row-${accessRecord.id}`}>
<Td id={`access-record-${accessRecord.id}`} dataLabel={t`Name`}>
{accessRecord.id ? (
<Link to={{ pathname: `/users/${accessRecord.id}/details` }}>
{accessRecord.username}
</Link>
) : (
accessRecord.username
)}
</Td>
<Td dataLabel={t`First name`}>{accessRecord.first_name}</Td>
<Td dataLabel={t`Last name`}>{accessRecord.first_name}</Td>
<Td dataLabel={t`Roles`}>
<DetailList stacked>
{userRoles.length > 0 && (
<Detail
label={t`User Roles`}
value={
<ChipGroup numChips={5} totalChips={userRoles.length}>
{userRoles.map(renderChip)}
</ChipGroup>
}
/>
)}
{teamRoles.length > 0 && (
<Detail
label={t`Team Roles`}
value={
<ChipGroup numChips={5} totalChips={teamRoles.length}>
{teamRoles.map(renderChip)}
</ChipGroup>
}
/>
)}
</DetailList>
</Td>
</Tr>
);
}

View File

@ -2,83 +2,55 @@ import React from 'react';
import { string, bool, func } from 'prop-types';
import { t } from '@lingui/macro';
import {
Button,
DataListAction as _DataListAction,
DataListCheck,
DataListItem,
DataListItemCells,
DataListItemRow,
Tooltip,
} from '@patternfly/react-core';
import { Button, Tooltip } from '@patternfly/react-core';
import { Tr, Td } from '@patternfly/react-table';
import { Link } from 'react-router-dom';
import { PencilAltIcon } from '@patternfly/react-icons';
import styled from 'styled-components';
import DataListCell from '../../../components/DataListCell';
import { ActionsTd } from '../../../components/PaginatedTable';
import HostToggle from '../../../components/HostToggle';
import Sparkline from '../../../components/Sparkline';
import { Host } from '../../../types';
const DataListAction = styled(_DataListAction)`
align-items: center;
display: grid;
grid-gap: 24px;
grid-template-columns: min-content 40px;
`;
function InventoryHostItem(props) {
const { detailUrl, editUrl, host, isSelected, onSelect } = props;
const recentPlaybookJobs = host.summary_fields.recent_jobs.map(job => ({
...job,
type: 'job',
}));
function InventoryHostItem({
detailUrl,
editUrl,
host,
isSelected,
onSelect,
rowIndex,
}) {
const labelId = `check-action-${host.id}`;
return (
<DataListItem key={host.id} aria-labelledby={labelId} id={`${host.id}`}>
<DataListItemRow>
<DataListCheck
id={`select-host-${host.id}`}
checked={isSelected}
onChange={onSelect}
aria-labelledby={labelId}
/>
<DataListItemCells
dataListCells={[
<DataListCell key="divider">
<Link to={`${detailUrl}`}>
<b>{host.name}</b>
</Link>
</DataListCell>,
<DataListCell key="recentJobs">
<Sparkline jobs={recentPlaybookJobs} />
</DataListCell>,
]}
/>
<DataListAction
aria-label={t`actions`}
aria-labelledby={labelId}
id={labelId}
>
<HostToggle host={host} />
{host.summary_fields.user_capabilities?.edit && (
<Tooltip content={t`Edit Host`} position="top">
<Button
ouiaId={`${host.id}-edit-button`}
variant="plain"
component={Link}
to={`${editUrl}`}
>
<PencilAltIcon />
</Button>
</Tooltip>
)}
</DataListAction>
</DataListItemRow>
</DataListItem>
<Tr id={`host-row-${host.id}`}>
<Td
select={{
rowIndex,
isSelected,
onSelect,
}}
/>
<Td id={labelId} dataLabel={t`Name`}>
<Link to={`${detailUrl}`}>
<b>{host.name}</b>
</Link>
</Td>
<ActionsTd dataLabel={t`Actions`} gridColumns="auto 40px">
<HostToggle host={host} />
{host.summary_fields.user_capabilities?.edit && (
<Tooltip content={t`Edit Host`} position="top">
<Button
ouiaId={`${host.id}-edit-button`}
variant="plain"
component={Link}
to={`${editUrl}`}
>
<PencilAltIcon />
</Button>
</Tooltip>
)}
</ActionsTd>
</Tr>
);
}

View File

@ -8,7 +8,11 @@ import useRequest, { useDeleteItems } from '../../../util/useRequest';
import AlertModal from '../../../components/AlertModal';
import DataListToolbar from '../../../components/DataListToolbar';
import ErrorDetail from '../../../components/ErrorDetail';
import PaginatedDataList, {
import PaginatedTable, {
HeaderRow,
HeaderCell,
} from '../../../components/PaginatedTable';
import {
ToolbarAddButton,
ToolbarDeleteButton,
} from '../../../components/PaginatedDataList';
@ -105,35 +109,21 @@ function InventoryHostList() {
return (
<>
<PaginatedDataList
<PaginatedTable
contentError={contentError}
hasContentLoading={isLoading || isDeleteLoading || isAdHocLaunchLoading}
items={hosts}
itemCount={hostCount}
pluralizedItemName={t`Hosts`}
qsConfig={QS_CONFIG}
toolbarColumns={[
{
name: t`Name`,
key: 'name',
isSortable: true,
isSearchable: true,
},
{
name: t`Modified`,
key: 'modified',
isSortable: true,
isNumeric: true,
},
{
name: t`Created`,
key: 'created',
isSortable: true,
isNumeric: true,
},
]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
headerRow={
<HeaderRow qsConfig={QS_CONFIG}>
<HeaderCell sortKey="name">{t`Name`}</HeaderCell>
<HeaderCell>{t`Actions`}</HeaderCell>
</HeaderRow>
}
renderToolbar={props => (
<DataListToolbar
{...props}
@ -164,14 +154,15 @@ function InventoryHostList() {
]}
/>
)}
renderItem={o => (
renderRow={(host, index) => (
<InventoryHostItem
key={o.id}
host={o}
detailUrl={`/inventories/inventory/${id}/hosts/${o.id}/details`}
editUrl={`/inventories/inventory/${id}/hosts/${o.id}/edit`}
isSelected={selected.some(row => row.id === o.id)}
onSelect={() => handleSelect(o)}
key={host.id}
host={host}
detailUrl={`/inventories/inventory/${id}/hosts/${host.id}/details`}
editUrl={`/inventories/inventory/${id}/hosts/${host.id}/edit`}
isSelected={selected.some(row => row.id === host.id)}
onSelect={() => handleSelect(host)}
rowIndex={index}
/>
)}
emptyStateControls={