mirror of
https://github.com/ansible/awx.git
synced 2026-01-13 19:10:07 -03:30
Abstract out Access List as shared component.
This commit is contained in:
parent
3cd54c45eb
commit
1443625d89
40
src/api.js
40
src/api.js
@ -66,10 +66,10 @@ class APIClient {
|
||||
return this.http.post(API_ORGANIZATIONS, data);
|
||||
}
|
||||
|
||||
getOrganzationAccessList (id) {
|
||||
getOrganzationAccessList (id, params = {}) {
|
||||
const endpoint = `${API_ORGANIZATIONS}${id}/access_list/`;
|
||||
|
||||
return this.http.get(endpoint);
|
||||
return this.http.get(endpoint, { params });
|
||||
}
|
||||
|
||||
getOrganizationDetails (id) {
|
||||
@ -84,24 +84,6 @@ class APIClient {
|
||||
return this.http.get(endpoint, { params });
|
||||
}
|
||||
|
||||
getOrganizationUserRoles (id) {
|
||||
const endpoint = `${API_USERS}${id}/roles/`;
|
||||
|
||||
return this.http.get(endpoint);
|
||||
}
|
||||
|
||||
getUserTeams (id) {
|
||||
const endpoint = `${API_USERS}${id}/teams/`;
|
||||
|
||||
return this.http.get(endpoint);
|
||||
}
|
||||
|
||||
getTeamRoles (id) {
|
||||
const endpoint = `${API_TEAMS}${id}/roles/`;
|
||||
|
||||
return this.http.get(endpoint);
|
||||
}
|
||||
|
||||
getOrganizationNotifications (id, params = {}) {
|
||||
const endpoint = `${API_ORGANIZATIONS}${id}/notification_templates/`;
|
||||
|
||||
@ -139,6 +121,24 @@ class APIClient {
|
||||
createInstanceGroups (url, id) {
|
||||
return this.http.post(url, { id });
|
||||
}
|
||||
|
||||
getUserRoles (id) {
|
||||
const endpoint = `${API_USERS}${id}/roles/`;
|
||||
|
||||
return this.http.get(endpoint);
|
||||
}
|
||||
|
||||
getUserTeams (id) {
|
||||
const endpoint = `${API_USERS}${id}/teams/`;
|
||||
|
||||
return this.http.get(endpoint);
|
||||
}
|
||||
|
||||
getTeamRoles (id) {
|
||||
const endpoint = `${API_TEAMS}${id}/roles/`;
|
||||
|
||||
return this.http.get(endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
export default APIClient;
|
||||
|
||||
343
src/components/AccessList/Access.list.jsx
Normal file
343
src/components/AccessList/Access.list.jsx
Normal file
@ -0,0 +1,343 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {
|
||||
DataList, DataListItem, DataListCell, Text,
|
||||
TextContent, TextVariants, Badge
|
||||
} from '@patternfly/react-core';
|
||||
|
||||
import { I18n, i18nMark } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
|
||||
import {
|
||||
Link
|
||||
} from 'react-router-dom';
|
||||
|
||||
import BasicChip from '../BasicChip/BasicChip';
|
||||
import Pagination from '../Pagination/Pagination';
|
||||
import DataListToolbar from '../DataListToolbar/DataListToolbar';
|
||||
|
||||
import {
|
||||
parseQueryString,
|
||||
} from '../../qs';
|
||||
|
||||
const userRolesWrapperStyle = {
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
};
|
||||
|
||||
const detailWrapperStyle = {
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'minmax(70px, max-content) minmax(60px, max-content)',
|
||||
};
|
||||
|
||||
const detailLabelStyle = {
|
||||
fontWeight: '700',
|
||||
lineHeight: '24px',
|
||||
marginRight: '20px',
|
||||
};
|
||||
|
||||
const detailValueStyle = {
|
||||
lineHeight: '28px',
|
||||
overflow: 'visible',
|
||||
};
|
||||
|
||||
const hiddenStyle = {
|
||||
display: 'none',
|
||||
};
|
||||
|
||||
const Detail = ({ label, value, url, isBadge, customStyles }) => {
|
||||
let detail = null;
|
||||
if (value) {
|
||||
detail = (
|
||||
<TextContent style={{ ...detailWrapperStyle, ...customStyles }}>
|
||||
{url ? (
|
||||
<Link to={{ pathname: url }}>
|
||||
<Text component={TextVariants.h6} style={detailLabelStyle}>{label}</Text>
|
||||
</Link>) : (<Text component={TextVariants.h6} style={detailLabelStyle}>{label}</Text>
|
||||
)}
|
||||
{isBadge ? (
|
||||
<Badge isRead>
|
||||
<Text component={TextVariants.p} style={detailValueStyle}>{value}</Text>
|
||||
</Badge>
|
||||
) : (
|
||||
<Text component={TextVariants.p} style={detailValueStyle}>{value}</Text>
|
||||
)}
|
||||
</TextContent>
|
||||
);
|
||||
}
|
||||
return detail;
|
||||
};
|
||||
|
||||
class AccessList extends React.Component {
|
||||
columns = [
|
||||
{ name: i18nMark('Username'), key: 'username', isSortable: true },
|
||||
{ name: i18nMark('First Name'), key: 'first_name', isSortable: true, isNumeric: true },
|
||||
{ name: i18nMark('Last Name'), key: 'last_name', isSortable: true, isNumeric: true },
|
||||
];
|
||||
|
||||
defaultParams = {
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
order_by: 'username',
|
||||
};
|
||||
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
const { page, page_size } = this.getQueryParams();
|
||||
|
||||
this.state = {
|
||||
page,
|
||||
page_size,
|
||||
count: null,
|
||||
results: [],
|
||||
sortOrder: 'ascending',
|
||||
sortedColumnKey: 'username',
|
||||
isCompact: true,
|
||||
};
|
||||
|
||||
this.fetchOrgAccessList = this.fetchOrgAccessList.bind(this);
|
||||
this.onSetPage = this.onSetPage.bind(this);
|
||||
this.onExpand = this.onExpand.bind(this);
|
||||
this.onCompact = this.onCompact.bind(this);
|
||||
this.onSort = this.onSort.bind(this);
|
||||
this.getQueryParams = this.getQueryParams.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const queryParams = this.getQueryParams();
|
||||
|
||||
this.fetchOrgAccessList(queryParams);
|
||||
}
|
||||
|
||||
onExpand = () => {
|
||||
this.setState({ isCompact: false });
|
||||
}
|
||||
|
||||
onCompact = () => {
|
||||
this.setState({ isCompact: true });
|
||||
}
|
||||
|
||||
onSetPage = (pageNumber, pageSize) => {
|
||||
const { sortOrder, sortedColumnKey } = this.state;
|
||||
const page = parseInt(pageNumber, 10);
|
||||
const page_size = parseInt(pageSize, 10);
|
||||
let order_by = sortedColumnKey;
|
||||
|
||||
if (sortOrder === 'descending') {
|
||||
order_by = `-${order_by}`;
|
||||
}
|
||||
|
||||
const queryParams = this.getQueryParams({ page, page_size, order_by });
|
||||
|
||||
this.fetchOrgAccessList(queryParams);
|
||||
};
|
||||
|
||||
getQueryParams (overrides = {}) {
|
||||
const { location } = this.props;
|
||||
const { search } = location;
|
||||
|
||||
const searchParams = parseQueryString(search.substring(1));
|
||||
|
||||
return Object.assign({}, this.defaultParams, searchParams, overrides);
|
||||
}
|
||||
|
||||
onSort = (sortedColumnKey, sortOrder) => {
|
||||
const { page_size } = this.state;
|
||||
|
||||
let order_by = sortedColumnKey;
|
||||
|
||||
if (sortOrder === 'descending') {
|
||||
order_by = `-${order_by}`;
|
||||
}
|
||||
|
||||
const queryParams = this.getQueryParams({ order_by, page_size });
|
||||
|
||||
this.fetchOrgAccessList(queryParams);
|
||||
};
|
||||
|
||||
async fetchOrgAccessList (queryParams) {
|
||||
const { match, getAccessList, getUserRoles, getTeamRoles, getUserTeams } = this.props;
|
||||
|
||||
const { page, page_size, order_by } = queryParams;
|
||||
|
||||
let sortOrder = 'ascending';
|
||||
let sortedColumnKey = order_by;
|
||||
|
||||
if (order_by.startsWith('-')) {
|
||||
sortOrder = 'descending';
|
||||
sortedColumnKey = order_by.substring(1);
|
||||
}
|
||||
|
||||
try {
|
||||
const { data:
|
||||
{ count = null, results = null }
|
||||
} = await getAccessList(match.params.id, queryParams);
|
||||
const pageCount = Math.ceil(count / page_size);
|
||||
|
||||
const stateToUpdate = {
|
||||
count,
|
||||
page,
|
||||
pageCount,
|
||||
page_size,
|
||||
sortOrder,
|
||||
sortedColumnKey,
|
||||
results,
|
||||
};
|
||||
|
||||
results.map(async result => {
|
||||
result.user_roles = [];
|
||||
result.team_roles = [];
|
||||
// Grab each Role Type and set as a top-level value
|
||||
Object.values(result.summary_fields).filter(value => value.length > 0).forEach(roleType => {
|
||||
result.roleType = roleType[0].role.name;
|
||||
});
|
||||
|
||||
// Grab User Roles and set as a top-level value
|
||||
try {
|
||||
const resp = await getUserRoles(result.id);
|
||||
const roles = resp.data.results;
|
||||
roles.forEach(role => {
|
||||
result.user_roles.push(role);
|
||||
});
|
||||
this.setState(stateToUpdate);
|
||||
} catch (error) {
|
||||
this.setState({ error });
|
||||
}
|
||||
|
||||
// Grab Team Roles and set as a top-level value
|
||||
try {
|
||||
const team_data = await getUserTeams(result.id);
|
||||
const teams = team_data.data.results;
|
||||
teams.forEach(async team => {
|
||||
let team_roles = await getTeamRoles(team.id);
|
||||
team_roles = team_roles.data.results;
|
||||
team_roles.forEach(role => {
|
||||
result.team_roles.push(role);
|
||||
});
|
||||
this.setState(stateToUpdate);
|
||||
});
|
||||
} catch (error) {
|
||||
this.setState({ error });
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
this.setState({ error });
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
results,
|
||||
error,
|
||||
count,
|
||||
page_size,
|
||||
pageCount,
|
||||
page,
|
||||
sortedColumnKey,
|
||||
sortOrder,
|
||||
isCompact,
|
||||
} = this.state;
|
||||
|
||||
if (!results) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<Fragment>
|
||||
<DataListToolbar
|
||||
sortedColumnKey={sortedColumnKey}
|
||||
sortOrder={sortOrder}
|
||||
columns={this.columns}
|
||||
onSearch={() => {}}
|
||||
onSort={this.onSort}
|
||||
onCompact={this.onCompact}
|
||||
onExpand={this.onExpand}
|
||||
isCompact={isCompact}
|
||||
showExpandCollapse
|
||||
/>
|
||||
<DataList aria-label={i18n._(t`Access List`)}>
|
||||
{results.map(result => (
|
||||
<DataListItem aria-labelledby={i18n._(t`access-list-item`)} key={result.id}>
|
||||
<DataListCell>
|
||||
<Detail
|
||||
label={result.username}
|
||||
value={result.roleType}
|
||||
url={result.url}
|
||||
isBadge
|
||||
/>
|
||||
{result.first_name || result.last_name ? (
|
||||
<Detail
|
||||
label={i18n._(t`Name`)}
|
||||
value={`${result.first_name} ${result.last_name}`}
|
||||
url={null}
|
||||
customStyles={isCompact ? hiddenStyle : null}
|
||||
/>
|
||||
) : (
|
||||
null
|
||||
)}
|
||||
</DataListCell>
|
||||
<DataListCell>
|
||||
<Detail
|
||||
label=" "
|
||||
value=" "
|
||||
url={null}
|
||||
customStyles={isCompact ? hiddenStyle : null}
|
||||
/>
|
||||
{result.user_roles.length > 0 && (
|
||||
<ul style={isCompact
|
||||
? { ...userRolesWrapperStyle, ...hiddenStyle }
|
||||
: userRolesWrapperStyle}
|
||||
>
|
||||
<Text component={TextVariants.h6} style={detailLabelStyle}>{i18n._(t`User Roles`)}</Text>
|
||||
{result.user_roles.map(role => (
|
||||
<BasicChip
|
||||
key={role.id}
|
||||
text={role.name}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
{result.team_roles.length > 0 && (
|
||||
<ul style={isCompact
|
||||
? { ...userRolesWrapperStyle, ...hiddenStyle }
|
||||
: userRolesWrapperStyle}
|
||||
>
|
||||
<Text component={TextVariants.h6} style={detailLabelStyle}>{i18n._(t`Team Roles`)}</Text>
|
||||
{result.team_roles.map(role => (
|
||||
<BasicChip
|
||||
key={role.id}
|
||||
text={role.name}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</DataListCell>
|
||||
</DataListItem>
|
||||
))}
|
||||
</DataList>
|
||||
<Pagination
|
||||
count={count}
|
||||
page={page}
|
||||
pageCount={pageCount}
|
||||
page_size={page_size}
|
||||
onSetPage={this.onSetPage}
|
||||
/>
|
||||
{ error ? <div>{error}</div> : '' }
|
||||
</Fragment>
|
||||
)}
|
||||
</I18n>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AccessList.propTypes = {
|
||||
getAccessList: PropTypes.func.isRequired,
|
||||
getUserRoles: PropTypes.func.isRequired,
|
||||
getUserTeams: PropTypes.func.isRequired,
|
||||
getTeamRoles: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default AccessList;
|
||||
3
src/components/AccessList/index.js
Normal file
3
src/components/AccessList/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
import AccessList from './Access.list';
|
||||
|
||||
export default AccessList;
|
||||
@ -24,7 +24,7 @@ import {
|
||||
SortNumericDownIcon,
|
||||
SortNumericUpIcon,
|
||||
TrashAltIcon,
|
||||
PlusIcon
|
||||
PlusIcon,
|
||||
} from '@patternfly/react-icons';
|
||||
import {
|
||||
Link
|
||||
@ -37,6 +37,12 @@ const flexGrowStyling = {
|
||||
flexGrow: '1'
|
||||
};
|
||||
|
||||
const ToolbarActiveStyle = {
|
||||
backgroundColor: 'rgb(0, 123, 186)',
|
||||
color: 'white',
|
||||
padding: '0 5px',
|
||||
};
|
||||
|
||||
class DataListToolbar extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
@ -56,6 +62,18 @@ class DataListToolbar extends React.Component {
|
||||
this.onSearchDropdownSelect = this.onSearchDropdownSelect.bind(this);
|
||||
this.onSearch = this.onSearch.bind(this);
|
||||
this.onSort = this.onSort.bind(this);
|
||||
this.onExpand = this.onExpand.bind(this);
|
||||
this.onCompact = this.onCompact.bind(this);
|
||||
}
|
||||
|
||||
onExpand () {
|
||||
const { onExpand } = this.props;
|
||||
onExpand();
|
||||
}
|
||||
|
||||
onCompact () {
|
||||
const { onCompact } = this.props;
|
||||
onCompact();
|
||||
}
|
||||
|
||||
onSortDropdownToggle (isSortDropdownOpen) {
|
||||
@ -114,7 +132,8 @@ class DataListToolbar extends React.Component {
|
||||
showExpandCollapse,
|
||||
showDelete,
|
||||
showSelectAll,
|
||||
isLookup
|
||||
isLookup,
|
||||
isCompact,
|
||||
} = this.props;
|
||||
const {
|
||||
isSearchDropdownOpen,
|
||||
@ -246,16 +265,20 @@ class DataListToolbar extends React.Component {
|
||||
<Button
|
||||
variant="plain"
|
||||
aria-label={i18n._(t`Expand`)}
|
||||
onClick={this.onExpand}
|
||||
style={!isCompact ? ToolbarActiveStyle : null}
|
||||
>
|
||||
<BarsIcon />
|
||||
<EqualsIcon />
|
||||
</Button>
|
||||
</ToolbarItem>
|
||||
<ToolbarItem>
|
||||
<Button
|
||||
variant="plain"
|
||||
aria-label={i18n._(t`Collapse`)}
|
||||
onClick={this.onCompact}
|
||||
style={isCompact ? ToolbarActiveStyle : null}
|
||||
>
|
||||
<EqualsIcon />
|
||||
<BarsIcon />
|
||||
</Button>
|
||||
</ToolbarItem>
|
||||
{ (showDelete || addUrl) && (
|
||||
@ -306,6 +329,7 @@ DataListToolbar.propTypes = {
|
||||
onSelectAll: PropTypes.func,
|
||||
onSort: PropTypes.func,
|
||||
showDelete: PropTypes.bool,
|
||||
showExpandCollapse: PropTypes.bool,
|
||||
showSelectAll: PropTypes.bool,
|
||||
sortOrder: PropTypes.string,
|
||||
sortedColumnKey: PropTypes.string,
|
||||
@ -317,6 +341,7 @@ DataListToolbar.defaultProps = {
|
||||
onSelectAll: null,
|
||||
onSort: null,
|
||||
showDelete: false,
|
||||
showExpandCollapse: false,
|
||||
showSelectAll: false,
|
||||
sortOrder: 'ascending',
|
||||
sortedColumnKey: 'name',
|
||||
|
||||
@ -335,7 +335,7 @@ class Notifications extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
Notifications.propType = {
|
||||
Notifications.propTypes = {
|
||||
getError: PropTypes.func.isRequired,
|
||||
getNotifications: PropTypes.func.isRequired,
|
||||
getSuccess: PropTypes.func.isRequired,
|
||||
|
||||
@ -0,0 +1,55 @@
|
||||
import React from 'react';
|
||||
import AccessList from '../../../../components/AccessList/Access.list';
|
||||
|
||||
class OrganizationAccess extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
this.getOrgAccessList = this.getOrgAccessList.bind(this);
|
||||
this.getUserRoles = this.getUserRoles.bind(this);
|
||||
this.getUserTeams = this.getUserTeams.bind(this);
|
||||
this.getTeamRoles = this.getTeamRoles.bind(this);
|
||||
}
|
||||
|
||||
getOrgAccessList (id, params) {
|
||||
const { api } = this.props;
|
||||
return api.getOrganzationAccessList(id, params);
|
||||
}
|
||||
|
||||
getUserRoles (id) {
|
||||
const { api } = this.props;
|
||||
return api.getUserRoles(id);
|
||||
}
|
||||
|
||||
getUserTeams (id) {
|
||||
const { api } = this.props;
|
||||
return api.getUserTeams(id);
|
||||
}
|
||||
|
||||
getTeamRoles (id) {
|
||||
const { api } = this.props;
|
||||
return api.getTeamRoles(id);
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
location,
|
||||
match,
|
||||
history,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<AccessList
|
||||
getAccessList={this.getOrgAccessList}
|
||||
getUserRoles={this.getUserRoles}
|
||||
getUserTeams={this.getUserTeams}
|
||||
getTeamRoles={this.getTeamRoles}
|
||||
match={match}
|
||||
location={location}
|
||||
history={history}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default OrganizationAccess;
|
||||
Loading…
x
Reference in New Issue
Block a user