mirror of
https://github.com/ansible/awx.git
synced 2026-02-02 01:58:09 -03:30
Add roles modal to org access list
This commit is contained in:
258
src/components/AddRole/AddResourceRole.jsx
Normal file
258
src/components/AddRole/AddResourceRole.jsx
Normal file
@@ -0,0 +1,258 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { I18n, i18nMark } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import {
|
||||
BackgroundImageSrc,
|
||||
Wizard
|
||||
} from '@patternfly/react-core';
|
||||
|
||||
import SelectResourceStep from './SelectResourceStep';
|
||||
import SelectRoleStep from './SelectRoleStep';
|
||||
import SelectableCard from './SelectableCard';
|
||||
|
||||
class AddResourceRole extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
selectedResource: null,
|
||||
selectedResourceRows: [],
|
||||
selectedRoleRows: []
|
||||
};
|
||||
|
||||
this.handleResourceCheckboxClick = this.handleResourceCheckboxClick.bind(this);
|
||||
this.handleResourceSelect = this.handleResourceSelect.bind(this);
|
||||
this.handleRoleCheckboxClick = this.handleRoleCheckboxClick.bind(this);
|
||||
this.handleWizardSave = this.handleWizardSave.bind(this);
|
||||
this.readTeams = this.readTeams.bind(this);
|
||||
this.readUsers = this.readUsers.bind(this);
|
||||
}
|
||||
|
||||
handleResourceCheckboxClick (user) {
|
||||
const { selectedResourceRows } = this.state;
|
||||
|
||||
const selectedIndex = selectedResourceRows
|
||||
.findIndex(selectedRow => selectedRow.id === user.id);
|
||||
|
||||
if (selectedIndex > -1) {
|
||||
selectedResourceRows.splice(selectedIndex, 1);
|
||||
this.setState({ selectedResourceRows });
|
||||
} else {
|
||||
this.setState(prevState => ({
|
||||
selectedResourceRows: [...prevState.selectedResourceRows, user]
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
handleRoleCheckboxClick (role) {
|
||||
const { selectedRoleRows } = this.state;
|
||||
|
||||
const selectedIndex = selectedRoleRows
|
||||
.findIndex(selectedRow => selectedRow.id === role.id);
|
||||
|
||||
if (selectedIndex > -1) {
|
||||
selectedRoleRows.splice(selectedIndex, 1);
|
||||
this.setState({ selectedRoleRows });
|
||||
} else {
|
||||
this.setState(prevState => ({
|
||||
selectedRoleRows: [...prevState.selectedRoleRows, role]
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
handleResourceSelect (resourceType) {
|
||||
this.setState({
|
||||
selectedResource: resourceType,
|
||||
selectedResourceRows: [],
|
||||
selectedRoleRows: []
|
||||
});
|
||||
}
|
||||
|
||||
async handleWizardSave () {
|
||||
const {
|
||||
onSave,
|
||||
api
|
||||
} = this.props;
|
||||
const {
|
||||
selectedResourceRows,
|
||||
selectedRoleRows,
|
||||
selectedResource
|
||||
} = this.state;
|
||||
|
||||
try {
|
||||
const roleRequests = [];
|
||||
|
||||
for (let i = 0; i < selectedResourceRows.length; i++) {
|
||||
for (let j = 0; j < selectedRoleRows.length; j++) {
|
||||
if (selectedResource === 'users') {
|
||||
roleRequests.push(
|
||||
api.createUserRole(selectedResourceRows[i].id, selectedRoleRows[j].id)
|
||||
);
|
||||
} else if (selectedResource === 'teams') {
|
||||
roleRequests.push(
|
||||
api.createTeamRole(selectedResourceRows[i].id, selectedRoleRows[j].id)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(roleRequests);
|
||||
onSave();
|
||||
} catch (err) {
|
||||
// TODO: handle this error
|
||||
}
|
||||
}
|
||||
|
||||
async readUsers (queryParams) {
|
||||
const { api } = this.props;
|
||||
return api.readUsers(queryParams);
|
||||
}
|
||||
|
||||
async readTeams (queryParams) {
|
||||
const { api } = this.props;
|
||||
return api.readTeams(queryParams);
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
selectedResource,
|
||||
selectedResourceRows,
|
||||
selectedRoleRows
|
||||
} = this.state;
|
||||
const {
|
||||
onClose,
|
||||
roles
|
||||
} = this.props;
|
||||
|
||||
const images = {
|
||||
[BackgroundImageSrc.xs]: '/assets/images/pfbg_576.jpg',
|
||||
[BackgroundImageSrc.xs2x]: '/assets/images/pfbg_576@2x.jpg',
|
||||
[BackgroundImageSrc.sm]: '/assets/images/pfbg_768.jpg',
|
||||
[BackgroundImageSrc.sm2x]: '/assets/images/pfbg_768@2x.jpg',
|
||||
[BackgroundImageSrc.lg]: '/assets/images/pfbg_2000.jpg',
|
||||
[BackgroundImageSrc.filter]: '/assets/images/background-filter.svg#image_overlay'
|
||||
};
|
||||
|
||||
const userColumns = [
|
||||
{ name: i18nMark('Username'), key: 'username', isSortable: true }
|
||||
];
|
||||
|
||||
const teamColumns = [
|
||||
{ name: i18nMark('Name'), key: 'name', isSortable: true }
|
||||
];
|
||||
|
||||
const steps = [
|
||||
{
|
||||
name: i18nMark('Select Users Or Teams'),
|
||||
component: (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<div style={{ display: 'flex' }}>
|
||||
<SelectableCard
|
||||
isSelected={selectedResource === 'users'}
|
||||
label={i18n._(t`Users`)}
|
||||
onClick={() => this.handleResourceSelect('users')}
|
||||
/>
|
||||
<SelectableCard
|
||||
isSelected={selectedResource === 'teams'}
|
||||
label={i18n._(t`Teams`)}
|
||||
onClick={() => this.handleResourceSelect('teams')}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</I18n>
|
||||
),
|
||||
enableNext: selectedResource !== null
|
||||
},
|
||||
{
|
||||
name: i18nMark('Select items from list'),
|
||||
component: (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<Fragment>
|
||||
{selectedResource === 'users' && (
|
||||
<SelectResourceStep
|
||||
columns={userColumns}
|
||||
defaultSearchParams={{
|
||||
is_superuser: false
|
||||
}}
|
||||
displayKey="username"
|
||||
emptyListBody={i18n._(t`Please add users to populate this list`)}
|
||||
emptyListTitle={i18n._(t`No Users Found`)}
|
||||
onRowClick={this.handleResourceCheckboxClick}
|
||||
onSearch={this.readUsers}
|
||||
selectedLabel={i18n._(t`Selected Users`)}
|
||||
selectedResourceRows={selectedResourceRows}
|
||||
sortedColumnKey="username"
|
||||
title={i18n._(t`Users`)}
|
||||
/>
|
||||
)}
|
||||
{selectedResource === 'teams' && (
|
||||
<SelectResourceStep
|
||||
columns={teamColumns}
|
||||
emptyListBody={i18n._(t`Please add teams to populate this list`)}
|
||||
emptyListTitle={i18n._(t`No Teams Found`)}
|
||||
onRowClick={this.handleResourceCheckboxClick}
|
||||
onSearch={this.readTeams}
|
||||
selectedLabel={i18n._(t`Selected Teams`)}
|
||||
selectedResourceRows={selectedResourceRows}
|
||||
title={i18n._(t`Teams`)}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
)}
|
||||
</I18n>
|
||||
),
|
||||
enableNext: selectedResourceRows.length > 0
|
||||
},
|
||||
{
|
||||
name: i18nMark('Apply roles'),
|
||||
component: (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<SelectRoleStep
|
||||
onRolesClick={this.handleRoleCheckboxClick}
|
||||
roles={roles}
|
||||
selectedListKey={selectedResource === 'users' ? 'username' : 'name'}
|
||||
selectedListLabel={selectedResource === 'users' ? i18n._(t`Selected Users`) : i18n._(t`Selected Teams`)}
|
||||
selectedResourceRows={selectedResourceRows}
|
||||
selectedRoleRows={selectedRoleRows}
|
||||
/>
|
||||
)}
|
||||
</I18n>
|
||||
),
|
||||
enableNext: selectedRoleRows.length > 0
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<Wizard
|
||||
backgroundImgSrc={images}
|
||||
footerRightAlign
|
||||
isOpen
|
||||
lastStepButtonText={i18n._(t`Save`)}
|
||||
onClose={onClose}
|
||||
onSave={this.handleWizardSave}
|
||||
steps={steps}
|
||||
title={i18n._(t`Add Roles`)}
|
||||
/>
|
||||
)}
|
||||
</I18n>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AddResourceRole.propTypes = {
|
||||
onClose: PropTypes.func.isRequired,
|
||||
onSave: PropTypes.func.isRequired,
|
||||
roles: PropTypes.shape()
|
||||
};
|
||||
|
||||
AddResourceRole.defaultProps = {
|
||||
roles: {}
|
||||
};
|
||||
|
||||
export default AddResourceRole;
|
||||
50
src/components/AddRole/CheckboxCard.jsx
Normal file
50
src/components/AddRole/CheckboxCard.jsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Checkbox
|
||||
} from '@patternfly/react-core';
|
||||
|
||||
class CheckboxCard extends Component {
|
||||
render () {
|
||||
const { name, description, isSelected, onSelect, itemId } = this.props;
|
||||
return (
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
border: '1px solid var(--pf-global--BorderColor)',
|
||||
borderRadius: 'var(--pf-global--BorderRadius--sm)',
|
||||
padding: '10px'
|
||||
}}
|
||||
>
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
onChange={onSelect}
|
||||
aria-label={name}
|
||||
id={`checkbox-card-${itemId}`}
|
||||
label={(
|
||||
<Fragment>
|
||||
<div style={{ fontWeight: 'bold' }}>{name}</div>
|
||||
<div>{description}</div>
|
||||
</Fragment>
|
||||
)}
|
||||
value={itemId}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
CheckboxCard.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
description: PropTypes.string,
|
||||
isSelected: PropTypes.bool,
|
||||
onSelect: PropTypes.func,
|
||||
itemId: PropTypes.number.isRequired
|
||||
};
|
||||
|
||||
CheckboxCard.defaultProps = {
|
||||
description: '',
|
||||
isSelected: false,
|
||||
onSelect: null
|
||||
};
|
||||
|
||||
export default CheckboxCard;
|
||||
216
src/components/AddRole/SelectResourceStep.jsx
Normal file
216
src/components/AddRole/SelectResourceStep.jsx
Normal file
@@ -0,0 +1,216 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { i18nMark } from '@lingui/react';
|
||||
|
||||
import {
|
||||
EmptyState,
|
||||
EmptyStateBody,
|
||||
EmptyStateIcon,
|
||||
Title,
|
||||
} from '@patternfly/react-core';
|
||||
|
||||
import { CubesIcon } from '@patternfly/react-icons';
|
||||
|
||||
import CheckboxListItem from '../ListItem';
|
||||
import DataListToolbar from '../DataListToolbar';
|
||||
import Pagination from '../Pagination';
|
||||
import SelectedList from '../SelectedList';
|
||||
|
||||
class SelectResourceStep extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
const { sortedColumnKey } = this.props;
|
||||
|
||||
this.state = {
|
||||
count: null,
|
||||
error: false,
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
resources: [],
|
||||
sortOrder: 'ascending',
|
||||
sortedColumnKey
|
||||
};
|
||||
|
||||
this.handleSetPage = this.handleSetPage.bind(this);
|
||||
this.handleSort = this.handleSort.bind(this);
|
||||
this.readResourceList = this.readResourceList.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const { page_size, page, sortedColumnKey } = this.state;
|
||||
|
||||
this.readResourceList({ page_size, page, order_by: sortedColumnKey });
|
||||
}
|
||||
|
||||
handleSetPage (pageNumber) {
|
||||
const { page_size, sortedColumnKey, sortOrder } = this.state;
|
||||
const page = parseInt(pageNumber, 10);
|
||||
|
||||
let order_by = sortedColumnKey;
|
||||
|
||||
if (sortOrder === 'descending') {
|
||||
order_by = `-${order_by}`;
|
||||
}
|
||||
|
||||
this.readResourceList({ page_size, page, order_by });
|
||||
}
|
||||
|
||||
handleSort (sortedColumnKey, sortOrder) {
|
||||
const { page_size } = this.state;
|
||||
|
||||
let order_by = sortedColumnKey;
|
||||
|
||||
if (sortOrder === 'descending') {
|
||||
order_by = `-${order_by}`;
|
||||
}
|
||||
|
||||
this.readResourceList({ page: 1, page_size, order_by });
|
||||
}
|
||||
|
||||
async readResourceList (queryParams) {
|
||||
const { onSearch, defaultSearchParams } = this.props;
|
||||
const { page, order_by } = queryParams;
|
||||
|
||||
let sortOrder = 'ascending';
|
||||
let sortedColumnKey = order_by;
|
||||
|
||||
if (order_by.startsWith('-')) {
|
||||
sortOrder = 'descending';
|
||||
sortedColumnKey = order_by.substring(1);
|
||||
}
|
||||
|
||||
this.setState({ error: false });
|
||||
|
||||
try {
|
||||
const { data } = await onSearch(Object.assign(queryParams, defaultSearchParams));
|
||||
const { count, results } = data;
|
||||
|
||||
const stateToUpdate = {
|
||||
count,
|
||||
page,
|
||||
resources: results,
|
||||
sortOrder,
|
||||
sortedColumnKey
|
||||
};
|
||||
|
||||
this.setState(stateToUpdate);
|
||||
} catch (err) {
|
||||
this.setState({ error: true });
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
count,
|
||||
error,
|
||||
page,
|
||||
page_size,
|
||||
resources,
|
||||
sortOrder,
|
||||
sortedColumnKey
|
||||
} = this.state;
|
||||
|
||||
const {
|
||||
columns,
|
||||
displayKey,
|
||||
emptyListBody,
|
||||
emptyListTitle,
|
||||
onRowClick,
|
||||
selectedLabel,
|
||||
selectedResourceRows,
|
||||
title
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Fragment>
|
||||
{(resources.length === 0) ? (
|
||||
<EmptyState>
|
||||
<EmptyStateIcon icon={CubesIcon} />
|
||||
<Title size="lg">
|
||||
{emptyListTitle}
|
||||
</Title>
|
||||
<EmptyStateBody>
|
||||
{emptyListBody}
|
||||
</EmptyStateBody>
|
||||
</EmptyState>
|
||||
) : (
|
||||
<Fragment>
|
||||
<Title size="lg">
|
||||
{title}
|
||||
</Title>
|
||||
<DataListToolbar
|
||||
columns={columns}
|
||||
noLeftMargin
|
||||
onSearch={this.onSearch}
|
||||
handleSort={this.handleSort}
|
||||
sortOrder={sortOrder}
|
||||
sortedColumnKey={sortedColumnKey}
|
||||
/>
|
||||
<ul className="pf-c-data-list awx-c-list">
|
||||
{resources.map(i => (
|
||||
<CheckboxListItem
|
||||
isSelected={selectedResourceRows.some(item => item.id === i.id)}
|
||||
itemId={i.id}
|
||||
key={i.id}
|
||||
name={i[displayKey]}
|
||||
onSelect={() => onRowClick(i)}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
<Pagination
|
||||
count={count}
|
||||
onSetPage={this.handleSetPage}
|
||||
page={page}
|
||||
pageCount={Math.ceil(count / page_size)}
|
||||
pageSizeOptions={null}
|
||||
page_size={page_size}
|
||||
showPageSizeOptions={false}
|
||||
/>
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
{selectedResourceRows.length > 0 && (
|
||||
<SelectedList
|
||||
displayKey={displayKey}
|
||||
label={selectedLabel}
|
||||
onRemove={onRowClick}
|
||||
selected={selectedResourceRows}
|
||||
showOverflowAfter={5}
|
||||
/>
|
||||
)}
|
||||
{ error ? <div>error</div> : '' }
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SelectResourceStep.propTypes = {
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
defaultSearchParams: PropTypes.shape(),
|
||||
displayKey: PropTypes.string,
|
||||
emptyListBody: PropTypes.string,
|
||||
emptyListTitle: PropTypes.string,
|
||||
onRowClick: PropTypes.func,
|
||||
onSearch: PropTypes.func.isRequired,
|
||||
selectedLabel: PropTypes.string,
|
||||
selectedResourceRows: PropTypes.arrayOf(PropTypes.object),
|
||||
sortedColumnKey: PropTypes.string,
|
||||
title: PropTypes.string
|
||||
};
|
||||
|
||||
SelectResourceStep.defaultProps = {
|
||||
defaultSearchParams: {},
|
||||
displayKey: 'name',
|
||||
emptyListBody: i18nMark('Please add items to populate this list'),
|
||||
emptyListTitle: i18nMark('No Items Found'),
|
||||
onRowClick: () => {},
|
||||
selectedLabel: i18nMark('Selected Items'),
|
||||
selectedResourceRows: [],
|
||||
sortedColumnKey: 'name',
|
||||
title: ''
|
||||
};
|
||||
|
||||
export default SelectResourceStep;
|
||||
69
src/components/AddRole/SelectRoleStep.jsx
Normal file
69
src/components/AddRole/SelectRoleStep.jsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { i18nMark } from '@lingui/react';
|
||||
|
||||
import CheckboxCard from './CheckboxCard';
|
||||
import SelectedList from '../SelectedList';
|
||||
|
||||
class RolesStep extends React.Component {
|
||||
render () {
|
||||
const {
|
||||
onRolesClick,
|
||||
roles,
|
||||
selectedListKey,
|
||||
selectedListLabel,
|
||||
selectedResourceRows,
|
||||
selectedRoleRows
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div>
|
||||
{selectedResourceRows.length > 0 && (
|
||||
<SelectedList
|
||||
displayKey={selectedListKey}
|
||||
isReadOnly
|
||||
label={selectedListLabel}
|
||||
selected={selectedResourceRows}
|
||||
showOverflowAfter={5}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px 20px', marginTop: '20px' }}>
|
||||
{Object.keys(roles).map(role => (
|
||||
<CheckboxCard
|
||||
description={roles[role].description}
|
||||
itemId={roles[role].id}
|
||||
isSelected={
|
||||
selectedRoleRows.some(item => item.id === roles[role].id)
|
||||
}
|
||||
key={roles[role].id}
|
||||
name={roles[role].name}
|
||||
onSelect={() => onRolesClick(roles[role])}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
RolesStep.propTypes = {
|
||||
onRolesClick: PropTypes.func,
|
||||
roles: PropTypes.objectOf(PropTypes.object).isRequired,
|
||||
selectedListKey: PropTypes.string,
|
||||
selectedListLabel: PropTypes.string,
|
||||
selectedResourceRows: PropTypes.arrayOf(PropTypes.object),
|
||||
selectedRoleRows: PropTypes.arrayOf(PropTypes.object)
|
||||
};
|
||||
|
||||
RolesStep.defaultProps = {
|
||||
onRolesClick: () => {},
|
||||
selectedListKey: 'name',
|
||||
selectedListLabel: i18nMark('Selected'),
|
||||
selectedResourceRows: [],
|
||||
selectedRoleRows: []
|
||||
};
|
||||
|
||||
export default RolesStep;
|
||||
39
src/components/AddRole/SelectableCard.jsx
Normal file
39
src/components/AddRole/SelectableCard.jsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
class SelectableCard extends Component {
|
||||
render () {
|
||||
const {
|
||||
label,
|
||||
onClick,
|
||||
isSelected
|
||||
} = this.props;
|
||||
return (
|
||||
<div
|
||||
className={isSelected ? 'awx-selectableCard awx-selectableCard__selected' : 'awx-selectableCard'}
|
||||
onClick={onClick}
|
||||
onKeyPress={onClick}
|
||||
role="button"
|
||||
tabIndex="0"
|
||||
>
|
||||
<div
|
||||
className="awx-selectableCard__indicator"
|
||||
/>
|
||||
<div className="awx-selectableCard__label">{label}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SelectableCard.propTypes = {
|
||||
label: PropTypes.string,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
isSelected: PropTypes.bool
|
||||
};
|
||||
|
||||
SelectableCard.defaultProps = {
|
||||
label: '',
|
||||
isSelected: false
|
||||
};
|
||||
|
||||
export default SelectableCard;
|
||||
28
src/components/AddRole/styles.scss
Normal file
28
src/components/AddRole/styles.scss
Normal file
@@ -0,0 +1,28 @@
|
||||
.awx-selectableCard {
|
||||
min-width: 200px;
|
||||
border: 1px solid var(--pf-global--BorderColor);
|
||||
border-radius: var(--pf-global--BorderRadius--sm);
|
||||
margin-right: 20px;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
|
||||
.awx-selectableCard__indicator {
|
||||
display: flex;
|
||||
flex: 0 0 10px;
|
||||
}
|
||||
|
||||
.awx-selectableCard__label {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.awx-selectableCard.awx-selectableCard__selected {
|
||||
border-color: var(--pf-global--active-color--100);
|
||||
|
||||
.awx-selectableCard__indicator {
|
||||
background-color: var(--pf-global--active-color--100);
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,7 @@ class DataListToolbar extends React.Component {
|
||||
isAllSelected,
|
||||
isLookup,
|
||||
isCompact,
|
||||
noLeftMargin,
|
||||
onSort,
|
||||
onSearch,
|
||||
onCompact,
|
||||
@@ -54,7 +55,7 @@ class DataListToolbar extends React.Component {
|
||||
<div className="awx-toolbar">
|
||||
<Level>
|
||||
<LevelItem style={{ display: 'flex', flexBasis: '700px' }}>
|
||||
<Toolbar style={{ marginLeft: isLookup ? '0px' : '20px', flexGrow: '1' }}>
|
||||
<Toolbar style={{ marginLeft: noLeftMargin ? '0px' : '20px', flexGrow: '1' }}>
|
||||
{ showSelectAll && (
|
||||
<ToolbarGroup>
|
||||
<ToolbarItem>
|
||||
@@ -152,6 +153,7 @@ DataListToolbar.propTypes = {
|
||||
addUrl: PropTypes.string,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
isAllSelected: PropTypes.bool,
|
||||
noLeftMargin: PropTypes.bool,
|
||||
onSearch: PropTypes.func,
|
||||
onSelectAll: PropTypes.func,
|
||||
onSort: PropTypes.func,
|
||||
@@ -178,7 +180,8 @@ DataListToolbar.defaultProps = {
|
||||
onCompact: null,
|
||||
onExpand: null,
|
||||
isCompact: false,
|
||||
add: null
|
||||
add: null,
|
||||
noLeftMargin: false
|
||||
};
|
||||
|
||||
export default DataListToolbar;
|
||||
|
||||
@@ -218,7 +218,7 @@ class Lookup extends React.Component {
|
||||
columns={columns}
|
||||
onSearch={this.onSearch}
|
||||
onSort={this.onSort}
|
||||
isLookup
|
||||
noLeftMargin
|
||||
/>
|
||||
<ul className="pf-c-data-list awx-c-list">
|
||||
{results.map(i => (
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
--awx-pagination--disabled-Color: #C2C2CA;
|
||||
|
||||
border-top: 1px solid var(--awx-pagination--BorderColor);
|
||||
border-bottom: 1px solid var(--awx-pagination--BorderColor);
|
||||
background-color: var(--awx-pagination--BackgroundColor);
|
||||
height: 55px;
|
||||
display: flex;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import React, { Component } from 'react';
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Chip
|
||||
} from '@patternfly/react-core';
|
||||
|
||||
import BasicChip from '../BasicChip/BasicChip';
|
||||
import VerticalSeparator from '../VerticalSeparator';
|
||||
|
||||
const selectedRowStyling = {
|
||||
@@ -35,7 +36,14 @@ class SelectedList extends Component {
|
||||
};
|
||||
|
||||
render () {
|
||||
const { label, selected, showOverflowAfter, onRemove } = this.props;
|
||||
const {
|
||||
label,
|
||||
selected,
|
||||
showOverflowAfter,
|
||||
onRemove,
|
||||
displayKey,
|
||||
isReadOnly
|
||||
} = this.props;
|
||||
const { showOverflow } = this.state;
|
||||
return (
|
||||
<div className="awx-selectedList">
|
||||
@@ -46,16 +54,33 @@ class SelectedList extends Component {
|
||||
<VerticalSeparator />
|
||||
<div className="pf-l-split__item">
|
||||
<div className="pf-c-chip-group">
|
||||
{selected
|
||||
.slice(0, showOverflow ? selected.length : showOverflowAfter)
|
||||
.map(selectedItem => (
|
||||
<Chip
|
||||
key={selectedItem.id}
|
||||
onClick={() => onRemove(selectedItem)}
|
||||
>
|
||||
{selectedItem.name}
|
||||
</Chip>
|
||||
))}
|
||||
{isReadOnly ? (
|
||||
<Fragment>
|
||||
{selected
|
||||
.slice(0, showOverflow ? selected.length : showOverflowAfter)
|
||||
.map(selectedItem => (
|
||||
<BasicChip
|
||||
key={selectedItem.id}
|
||||
text={selectedItem[displayKey]}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</Fragment>
|
||||
) : (
|
||||
<Fragment>
|
||||
{selected
|
||||
.slice(0, showOverflow ? selected.length : showOverflowAfter)
|
||||
.map(selectedItem => (
|
||||
<Chip
|
||||
key={selectedItem.id}
|
||||
onClick={() => onRemove(selectedItem)}
|
||||
>
|
||||
{selectedItem[displayKey]}
|
||||
</Chip>
|
||||
))
|
||||
}
|
||||
</Fragment>
|
||||
)}
|
||||
{(
|
||||
!showOverflow
|
||||
&& selected.length > showOverflowAfter
|
||||
@@ -76,15 +101,20 @@ class SelectedList extends Component {
|
||||
}
|
||||
|
||||
SelectedList.propTypes = {
|
||||
displayKey: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
onRemove: PropTypes.func.isRequired,
|
||||
onRemove: PropTypes.func,
|
||||
selected: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
showOverflowAfter: PropTypes.number,
|
||||
isReadOnly: PropTypes.bool
|
||||
};
|
||||
|
||||
SelectedList.defaultProps = {
|
||||
displayKey: 'name',
|
||||
label: 'Selected',
|
||||
onRemove: () => null,
|
||||
showOverflowAfter: 5,
|
||||
isReadOnly: false
|
||||
};
|
||||
|
||||
export default SelectedList;
|
||||
|
||||
Reference in New Issue
Block a user