mirror of
https://github.com/ansible/awx.git
synced 2026-02-17 19:20:05 -03:30
Update detail components to use ActionButtonWrapper
This commit is contained in:
@@ -133,7 +133,7 @@ class Host extends Component {
|
|||||||
{host && (
|
{host && (
|
||||||
<Route
|
<Route
|
||||||
path="/hosts/:id/details"
|
path="/hosts/:id/details"
|
||||||
render={() => <HostDetail match={match} host={host} />}
|
render={() => <HostDetail host={host} />}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{host && (
|
{host && (
|
||||||
|
|||||||
@@ -1,23 +1,13 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link, withRouter } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import styled from 'styled-components';
|
|
||||||
import { Host } from '@types';
|
import { Host } from '@types';
|
||||||
import { Button } from '@patternfly/react-core';
|
import { Button } from '@patternfly/react-core';
|
||||||
import { CardBody } from '@components/Card';
|
import { CardBody, CardActionsRow } from '@components/Card';
|
||||||
import { DetailList, Detail, UserDateDetail } from '@components/DetailList';
|
import { DetailList, Detail, UserDateDetail } from '@components/DetailList';
|
||||||
import { VariablesDetail } from '@components/CodeMirrorInput';
|
import { VariablesDetail } from '@components/CodeMirrorInput';
|
||||||
|
|
||||||
const ActionButtonWrapper = styled.div`
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
margin-top: 20px;
|
|
||||||
& > :not(:first-child) {
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
function HostDetail({ host, i18n }) {
|
function HostDetail({ host, i18n }) {
|
||||||
const { created, description, id, modified, name, summary_fields } = host;
|
const { created, description, id, modified, name, summary_fields } = host;
|
||||||
|
|
||||||
@@ -58,7 +48,7 @@ function HostDetail({ host, i18n }) {
|
|||||||
rows={6}
|
rows={6}
|
||||||
/>
|
/>
|
||||||
</DetailList>
|
</DetailList>
|
||||||
<ActionButtonWrapper>
|
<CardActionsRow>
|
||||||
{summary_fields.user_capabilities &&
|
{summary_fields.user_capabilities &&
|
||||||
summary_fields.user_capabilities.edit && (
|
summary_fields.user_capabilities.edit && (
|
||||||
<Button
|
<Button
|
||||||
@@ -69,7 +59,7 @@ function HostDetail({ host, i18n }) {
|
|||||||
{i18n._(t`Edit`)}
|
{i18n._(t`Edit`)}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</ActionButtonWrapper>
|
</CardActionsRow>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -78,4 +68,4 @@ HostDetail.propTypes = {
|
|||||||
host: Host.isRequired,
|
host: Host.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withI18n()(withRouter(HostDetail));
|
export default withI18n()(HostDetail);
|
||||||
|
|||||||
@@ -16,9 +16,11 @@ GroupsAPI.readDetail.mockResolvedValue({
|
|||||||
variables: 'bizz: buzz',
|
variables: 'bizz: buzz',
|
||||||
summary_fields: {
|
summary_fields: {
|
||||||
inventory: { id: 1 },
|
inventory: { id: 1 },
|
||||||
created_by: { id: 1, name: 'Athena' },
|
created_by: { id: 1, username: 'Athena' },
|
||||||
modified_by: { id: 1, name: 'Apollo' },
|
modified_by: { id: 1, username: 'Apollo' },
|
||||||
},
|
},
|
||||||
|
created: '2020-04-25T01:23:45.678901Z',
|
||||||
|
modified: '2020-04-25T01:23:45.678901Z',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
describe('<InventoryGroup />', () => {
|
describe('<InventoryGroup />', () => {
|
||||||
|
|||||||
@@ -3,27 +3,16 @@ import { t } from '@lingui/macro';
|
|||||||
|
|
||||||
import { Button } from '@patternfly/react-core';
|
import { Button } from '@patternfly/react-core';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { withRouter } from 'react-router-dom';
|
import { useHistory, useParams } from 'react-router-dom';
|
||||||
import styled from 'styled-components';
|
|
||||||
import { VariablesDetail } from '@components/CodeMirrorInput';
|
import { VariablesDetail } from '@components/CodeMirrorInput';
|
||||||
import { CardBody } from '@components/Card';
|
import { CardBody, CardActionsRow } from '@components/Card';
|
||||||
import ErrorDetail from '@components/ErrorDetail';
|
import ErrorDetail from '@components/ErrorDetail';
|
||||||
import AlertModal from '@components/AlertModal';
|
import AlertModal from '@components/AlertModal';
|
||||||
import { DetailList, Detail, UserDateDetail } from '@components/DetailList';
|
import { DetailList, Detail, UserDateDetail } from '@components/DetailList';
|
||||||
import InventoryGroupsDeleteModal from '../shared/InventoryGroupsDeleteModal';
|
import InventoryGroupsDeleteModal from '../shared/InventoryGroupsDeleteModal';
|
||||||
import { GroupsAPI, InventoriesAPI } from '@api';
|
import { GroupsAPI, InventoriesAPI } from '@api';
|
||||||
|
|
||||||
// TODO: extract this into a component for use in all relevant Detail views
|
function InventoryGroupDetail({ i18n, inventoryGroup }) {
|
||||||
const ActionButtonWrapper = styled.div`
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
margin-top: 20px;
|
|
||||||
& > :not(:first-child) {
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
function InventoryGroupDetail({ i18n, history, match, inventoryGroup }) {
|
|
||||||
const {
|
const {
|
||||||
summary_fields: { created_by, modified_by },
|
summary_fields: { created_by, modified_by },
|
||||||
created,
|
created,
|
||||||
@@ -34,15 +23,21 @@ function InventoryGroupDetail({ i18n, history, match, inventoryGroup }) {
|
|||||||
} = inventoryGroup;
|
} = inventoryGroup;
|
||||||
const [error, setError] = useState(false);
|
const [error, setError] = useState(false);
|
||||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||||
|
const history = useHistory();
|
||||||
|
const params = useParams();
|
||||||
|
|
||||||
const handleDelete = async option => {
|
const handleDelete = async option => {
|
||||||
|
const inventoryId = parseInt(params.id, 10);
|
||||||
|
const groupId = parseInt(params.groupId, 10);
|
||||||
setIsDeleteModalOpen(false);
|
setIsDeleteModalOpen(false);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (option === 'delete') {
|
if (option === 'delete') {
|
||||||
await GroupsAPI.destroy(inventoryGroup.id);
|
await GroupsAPI.destroy(groupId);
|
||||||
} else {
|
} else {
|
||||||
await InventoriesAPI.promoteGroup(match.params.id, inventoryGroup.id);
|
await InventoriesAPI.promoteGroup(inventoryId, groupId);
|
||||||
}
|
}
|
||||||
history.push(`/inventories/inventory/${match.params.id}/groups`);
|
history.push(`/inventories/inventory/${inventoryId}/groups`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err);
|
setError(err);
|
||||||
}
|
}
|
||||||
@@ -69,13 +64,13 @@ function InventoryGroupDetail({ i18n, history, match, inventoryGroup }) {
|
|||||||
user={modified_by}
|
user={modified_by}
|
||||||
/>
|
/>
|
||||||
</DetailList>
|
</DetailList>
|
||||||
<ActionButtonWrapper>
|
<CardActionsRow>
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
aria-label={i18n._(t`Edit`)}
|
aria-label={i18n._(t`Edit`)}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
history.push(
|
history.push(
|
||||||
`/inventories/inventory/${match.params.id}/groups/${inventoryGroup.id}/edit`
|
`/inventories/inventory/${params.id}/groups/${params.groupId}/edit`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -88,7 +83,7 @@ function InventoryGroupDetail({ i18n, history, match, inventoryGroup }) {
|
|||||||
>
|
>
|
||||||
{i18n._(t`Delete`)}
|
{i18n._(t`Delete`)}
|
||||||
</Button>
|
</Button>
|
||||||
</ActionButtonWrapper>
|
</CardActionsRow>
|
||||||
{isDeleteModalOpen && (
|
{isDeleteModalOpen && (
|
||||||
<InventoryGroupsDeleteModal
|
<InventoryGroupsDeleteModal
|
||||||
groups={[inventoryGroup]}
|
groups={[inventoryGroup]}
|
||||||
@@ -111,4 +106,4 @@ function InventoryGroupDetail({ i18n, history, match, inventoryGroup }) {
|
|||||||
</CardBody>
|
</CardBody>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export default withI18n()(withRouter(InventoryGroupDetail));
|
export default withI18n()(InventoryGroupDetail);
|
||||||
|
|||||||
@@ -2,13 +2,12 @@ import React from 'react';
|
|||||||
import { GroupsAPI } from '@api';
|
import { GroupsAPI } from '@api';
|
||||||
import { Route } from 'react-router-dom';
|
import { Route } from 'react-router-dom';
|
||||||
import { createMemoryHistory } from 'history';
|
import { createMemoryHistory } from 'history';
|
||||||
|
|
||||||
import { act } from 'react-dom/test-utils';
|
import { act } from 'react-dom/test-utils';
|
||||||
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
|
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
|
||||||
|
|
||||||
import InventoryGroupDetail from './InventoryGroupDetail';
|
import InventoryGroupDetail from './InventoryGroupDetail';
|
||||||
|
|
||||||
jest.mock('@api');
|
jest.mock('@api');
|
||||||
|
|
||||||
const inventoryGroup = {
|
const inventoryGroup = {
|
||||||
name: 'Foo',
|
name: 'Foo',
|
||||||
description: 'Bar',
|
description: 'Bar',
|
||||||
@@ -27,6 +26,7 @@ const inventoryGroup = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('<InventoryGroupDetail />', () => {
|
describe('<InventoryGroupDetail />', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
let history;
|
let history;
|
||||||
@@ -86,7 +86,7 @@ describe('<InventoryGroupDetail />', () => {
|
|||||||
'/inventories/inventory/1/groups/1/edit'
|
'/inventories/inventory/1/groups/1/edit'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
test('details shoudld render with the proper values', () => {
|
test('details should render with the proper values', () => {
|
||||||
expect(wrapper.find('Detail[label="Name"]').prop('value')).toBe('Foo');
|
expect(wrapper.find('Detail[label="Name"]').prop('value')).toBe('Foo');
|
||||||
expect(wrapper.find('Detail[label="Description"]').prop('value')).toBe(
|
expect(wrapper.find('Detail[label="Description"]').prop('value')).toBe(
|
||||||
'Bar'
|
'Bar'
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Link, withRouter } from 'react-router-dom';
|
import { Link, useHistory } from 'react-router-dom';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Button } from '@patternfly/react-core';
|
import { Button } from '@patternfly/react-core';
|
||||||
@@ -7,9 +7,10 @@ import styled from 'styled-components';
|
|||||||
|
|
||||||
import AlertModal from '@components/AlertModal';
|
import AlertModal from '@components/AlertModal';
|
||||||
import { DetailList, Detail } from '@components/DetailList';
|
import { DetailList, Detail } from '@components/DetailList';
|
||||||
import { CardBody } from '@components/Card';
|
import { CardBody, CardActionsRow } from '@components/Card';
|
||||||
import { ChipGroup, Chip, CredentialChip } from '@components/Chip';
|
import { ChipGroup, Chip, CredentialChip } from '@components/Chip';
|
||||||
import { VariablesInput as _VariablesInput } from '@components/CodeMirrorInput';
|
import { VariablesInput as _VariablesInput } from '@components/CodeMirrorInput';
|
||||||
|
import DeleteButton from '@components/DeleteButton';
|
||||||
import ErrorDetail from '@components/ErrorDetail';
|
import ErrorDetail from '@components/ErrorDetail';
|
||||||
import LaunchButton from '@components/LaunchButton';
|
import LaunchButton from '@components/LaunchButton';
|
||||||
import { StatusIcon } from '@components/Sparkline';
|
import { StatusIcon } from '@components/Sparkline';
|
||||||
@@ -24,16 +25,6 @@ import {
|
|||||||
InventoriesAPI,
|
InventoriesAPI,
|
||||||
AdHocCommandsAPI,
|
AdHocCommandsAPI,
|
||||||
} from '@api';
|
} from '@api';
|
||||||
import { JOB_TYPE_URL_SEGMENTS } from '../../../constants';
|
|
||||||
|
|
||||||
const ActionButtonWrapper = styled.div`
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
margin-top: 20px;
|
|
||||||
& > :not(:first-child) {
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const VariablesInput = styled(_VariablesInput)`
|
const VariablesInput = styled(_VariablesInput)`
|
||||||
.pf-c-form__label {
|
.pf-c-form__label {
|
||||||
@@ -86,7 +77,7 @@ const getLaunchedByDetails = ({ summary_fields = {}, related = {} }) => {
|
|||||||
return { link, value };
|
return { link, value };
|
||||||
};
|
};
|
||||||
|
|
||||||
function JobDetail({ job, i18n, history }) {
|
function JobDetail({ job, i18n }) {
|
||||||
const {
|
const {
|
||||||
credentials,
|
credentials,
|
||||||
instance_group: instanceGroup,
|
instance_group: instanceGroup,
|
||||||
@@ -95,8 +86,8 @@ function JobDetail({ job, i18n, history }) {
|
|||||||
labels,
|
labels,
|
||||||
project,
|
project,
|
||||||
} = job.summary_fields;
|
} = job.summary_fields;
|
||||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
|
||||||
const [errorMsg, setErrorMsg] = useState();
|
const [errorMsg, setErrorMsg] = useState();
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
const { value: launchedByValue, link: launchedByLink } =
|
const { value: launchedByValue, link: launchedByLink } =
|
||||||
getLaunchedByDetails(job) || {};
|
getLaunchedByDetails(job) || {};
|
||||||
@@ -125,7 +116,6 @@ function JobDetail({ job, i18n, history }) {
|
|||||||
history.push('/jobs');
|
history.push('/jobs');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setErrorMsg(err);
|
setErrorMsg(err);
|
||||||
setIsDeleteModalOpen(false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -262,7 +252,7 @@ function JobDetail({ job, i18n, history }) {
|
|||||||
label={i18n._(t`Artifacts`)}
|
label={i18n._(t`Artifacts`)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<ActionButtonWrapper>
|
<CardActionsRow>
|
||||||
{job.type !== 'system_job' &&
|
{job.type !== 'system_job' &&
|
||||||
job.summary_fields.user_capabilities.start && (
|
job.summary_fields.user_capabilities.start && (
|
||||||
<LaunchButton resource={job} aria-label={i18n._(t`Relaunch`)}>
|
<LaunchButton resource={job} aria-label={i18n._(t`Relaunch`)}>
|
||||||
@@ -274,45 +264,15 @@ function JobDetail({ job, i18n, history }) {
|
|||||||
</LaunchButton>
|
</LaunchButton>
|
||||||
)}
|
)}
|
||||||
{job.summary_fields.user_capabilities.delete && (
|
{job.summary_fields.user_capabilities.delete && (
|
||||||
<Button
|
<DeleteButton
|
||||||
variant="danger"
|
name={job.name}
|
||||||
aria-label={i18n._(t`Delete`)}
|
modalTitle={i18n._(t`Delete Job`)}
|
||||||
onClick={() => setIsDeleteModalOpen(true)}
|
onConfirm={deleteJob}
|
||||||
>
|
>
|
||||||
{i18n._(t`Delete`)}
|
{i18n._(t`Delete`)}
|
||||||
</Button>
|
</DeleteButton>
|
||||||
)}
|
)}
|
||||||
</ActionButtonWrapper>
|
</CardActionsRow>
|
||||||
{isDeleteModalOpen && (
|
|
||||||
<AlertModal
|
|
||||||
isOpen={isDeleteModalOpen}
|
|
||||||
title={i18n._(t`Delete Job`)}
|
|
||||||
variant="danger"
|
|
||||||
onClose={() => setIsDeleteModalOpen(false)}
|
|
||||||
>
|
|
||||||
{i18n._(t`Are you sure you want to delete:`)}
|
|
||||||
<br />
|
|
||||||
<strong>{job.name}</strong>
|
|
||||||
<ActionButtonWrapper>
|
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
aria-label={i18n._(t`Close`)}
|
|
||||||
component={Link}
|
|
||||||
to={`/jobs/${JOB_TYPE_URL_SEGMENTS[job.type]}/${job.id}`}
|
|
||||||
>
|
|
||||||
{i18n._(t`Cancel`)}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
variant="danger"
|
|
||||||
aria-label={i18n._(t`Delete`)}
|
|
||||||
onClick={deleteJob}
|
|
||||||
>
|
|
||||||
{i18n._(t`Delete`)}
|
|
||||||
</Button>
|
|
||||||
</ActionButtonWrapper>
|
|
||||||
</AlertModal>
|
|
||||||
)}
|
|
||||||
{errorMsg && (
|
{errorMsg && (
|
||||||
<AlertModal
|
<AlertModal
|
||||||
isOpen={errorMsg}
|
isOpen={errorMsg}
|
||||||
@@ -330,4 +290,4 @@ JobDetail.propTypes = {
|
|||||||
job: Job.isRequired,
|
job: Job.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withI18n()(withRouter(JobDetail));
|
export default withI18n()(JobDetail);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { t } from '@lingui/macro';
|
|||||||
import { Button } from '@patternfly/react-core';
|
import { Button } from '@patternfly/react-core';
|
||||||
import { OrganizationsAPI } from '@api';
|
import { OrganizationsAPI } from '@api';
|
||||||
import { DetailList, Detail, UserDateDetail } from '@components/DetailList';
|
import { DetailList, Detail, UserDateDetail } from '@components/DetailList';
|
||||||
import { CardBody } from '@components/Card';
|
import { CardBody, CardActionsRow } from '@components/Card';
|
||||||
import { ChipGroup, Chip } from '@components/Chip';
|
import { ChipGroup, Chip } from '@components/Chip';
|
||||||
import ContentError from '@components/ContentError';
|
import ContentError from '@components/ContentError';
|
||||||
import ContentLoading from '@components/ContentLoading';
|
import ContentLoading from '@components/ContentLoading';
|
||||||
@@ -92,13 +92,13 @@ function OrganizationDetail({ i18n, organization }) {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</DetailList>
|
</DetailList>
|
||||||
{summary_fields.user_capabilities.edit && (
|
<CardActionsRow>
|
||||||
<div css="margin-top: 10px; text-align: right;">
|
{summary_fields.user_capabilities.edit && (
|
||||||
<Button component={Link} to={`/organizations/${id}/edit`}>
|
<Button component={Link} to={`/organizations/${id}/edit`}>
|
||||||
{i18n._(t`Edit`)}
|
{i18n._(t`Edit`)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
)}
|
||||||
)}
|
</CardActionsRow>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ class Project extends Component {
|
|||||||
{project && (
|
{project && (
|
||||||
<Route
|
<Route
|
||||||
path="/projects/:id/details"
|
path="/projects/:id/details"
|
||||||
render={() => <ProjectDetail match={match} project={project} />}
|
render={() => <ProjectDetail project={project} />}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{project && (
|
{project && (
|
||||||
|
|||||||
@@ -1,25 +1,15 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link, withRouter } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import styled from 'styled-components';
|
|
||||||
import { Project } from '@types';
|
import { Project } from '@types';
|
||||||
import { Config } from '@contexts/Config';
|
import { Config } from '@contexts/Config';
|
||||||
import { Button, List, ListItem } from '@patternfly/react-core';
|
import { Button, List, ListItem } from '@patternfly/react-core';
|
||||||
import { CardBody } from '@components/Card';
|
import { CardBody, CardActionsRow } from '@components/Card';
|
||||||
import { DetailList, Detail, UserDateDetail } from '@components/DetailList';
|
import { DetailList, Detail, UserDateDetail } from '@components/DetailList';
|
||||||
import { CredentialChip } from '@components/Chip';
|
import { CredentialChip } from '@components/Chip';
|
||||||
import { toTitleCase } from '@util/strings';
|
import { toTitleCase } from '@util/strings';
|
||||||
|
|
||||||
const ActionButtonWrapper = styled.div`
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
margin-top: 20px;
|
|
||||||
& > :not(:first-child) {
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
function ProjectDetail({ project, i18n }) {
|
function ProjectDetail({ project, i18n }) {
|
||||||
const {
|
const {
|
||||||
allow_override,
|
allow_override,
|
||||||
@@ -137,7 +127,7 @@ function ProjectDetail({ project, i18n }) {
|
|||||||
user={summary_fields.modified_by}
|
user={summary_fields.modified_by}
|
||||||
/>
|
/>
|
||||||
</DetailList>
|
</DetailList>
|
||||||
<ActionButtonWrapper>
|
<CardActionsRow>
|
||||||
{summary_fields.user_capabilities &&
|
{summary_fields.user_capabilities &&
|
||||||
summary_fields.user_capabilities.edit && (
|
summary_fields.user_capabilities.edit && (
|
||||||
<Button
|
<Button
|
||||||
@@ -148,7 +138,7 @@ function ProjectDetail({ project, i18n }) {
|
|||||||
{i18n._(t`Edit`)}
|
{i18n._(t`Edit`)}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</ActionButtonWrapper>
|
</CardActionsRow>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -157,4 +147,4 @@ ProjectDetail.propTypes = {
|
|||||||
project: Project.isRequired,
|
project: Project.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withI18n()(withRouter(ProjectDetail));
|
export default withI18n()(ProjectDetail);
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ class Team extends Component {
|
|||||||
{team && (
|
{team && (
|
||||||
<Route
|
<Route
|
||||||
path="/teams/:id/details"
|
path="/teams/:id/details"
|
||||||
render={() => <TeamDetail match={match} team={team} />}
|
render={() => <TeamDetail team={team} />}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{team && (
|
{team && (
|
||||||
|
|||||||
@@ -1,57 +1,49 @@
|
|||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import { Link, withRouter } from 'react-router-dom';
|
import { Link, useParams } from 'react-router-dom';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Button } from '@patternfly/react-core';
|
import { Button } from '@patternfly/react-core';
|
||||||
|
|
||||||
import { CardBody } from '@components/Card';
|
import { CardBody, CardActionsRow } from '@components/Card';
|
||||||
import { DetailList, Detail } from '@components/DetailList';
|
import { DetailList, Detail } from '@components/DetailList';
|
||||||
import { formatDateString } from '@util/dates';
|
import { formatDateString } from '@util/dates';
|
||||||
|
|
||||||
class TeamDetail extends Component {
|
function TeamDetail({ team, i18n }) {
|
||||||
render() {
|
const { name, description, created, modified, summary_fields } = team;
|
||||||
const {
|
const { id } = useParams();
|
||||||
team: { name, description, created, modified, summary_fields },
|
|
||||||
match,
|
|
||||||
i18n,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<DetailList>
|
<DetailList>
|
||||||
<Detail
|
<Detail
|
||||||
label={i18n._(t`Name`)}
|
label={i18n._(t`Name`)}
|
||||||
value={name}
|
value={name}
|
||||||
dataCy="team-detail-name"
|
dataCy="team-detail-name"
|
||||||
/>
|
/>
|
||||||
<Detail label={i18n._(t`Description`)} value={description} />
|
<Detail label={i18n._(t`Description`)} value={description} />
|
||||||
<Detail
|
<Detail
|
||||||
label={i18n._(t`Organization`)}
|
label={i18n._(t`Organization`)}
|
||||||
value={
|
value={
|
||||||
<Link to={`/organizations/${summary_fields.organization.id}`}>
|
<Link to={`/organizations/${summary_fields.organization.id}`}>
|
||||||
{summary_fields.organization.name}
|
{summary_fields.organization.name}
|
||||||
</Link>
|
</Link>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Detail
|
<Detail label={i18n._(t`Created`)} value={formatDateString(created)} />
|
||||||
label={i18n._(t`Created`)}
|
<Detail
|
||||||
value={formatDateString(created)}
|
label={i18n._(t`Last Modified`)}
|
||||||
/>
|
value={formatDateString(modified)}
|
||||||
<Detail
|
/>
|
||||||
label={i18n._(t`Last Modified`)}
|
</DetailList>
|
||||||
value={formatDateString(modified)}
|
<CardActionsRow>
|
||||||
/>
|
|
||||||
</DetailList>
|
|
||||||
{summary_fields.user_capabilities.edit && (
|
{summary_fields.user_capabilities.edit && (
|
||||||
<div css="margin-top: 10px; text-align: right;">
|
<Button component={Link} to={`/teams/${id}/edit`}>
|
||||||
<Button component={Link} to={`/teams/${match.params.id}/edit`}>
|
{i18n._(t`Edit`)}
|
||||||
{i18n._(t`Edit`)}
|
</Button>
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</CardBody>
|
</CardActionsRow>
|
||||||
);
|
</CardBody>
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withI18n()(withRouter(TeamDetail));
|
export default withI18n()(TeamDetail);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
|
|
||||||
import { CardBody } from '@components/Card';
|
import { CardBody, CardActionsRow } from '@components/Card';
|
||||||
import ContentError from '@components/ContentError';
|
import ContentError from '@components/ContentError';
|
||||||
import LaunchButton from '@components/LaunchButton';
|
import LaunchButton from '@components/LaunchButton';
|
||||||
import ContentLoading from '@components/ContentLoading';
|
import ContentLoading from '@components/ContentLoading';
|
||||||
@@ -19,20 +19,12 @@ import { ChipGroup, Chip, CredentialChip } from '@components/Chip';
|
|||||||
import { DetailList, Detail, UserDateDetail } from '@components/DetailList';
|
import { DetailList, Detail, UserDateDetail } from '@components/DetailList';
|
||||||
import { JobTemplatesAPI } from '@api';
|
import { JobTemplatesAPI } from '@api';
|
||||||
|
|
||||||
const ButtonGroup = styled.div`
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
margin-top: 20px;
|
|
||||||
& > :not(:first-child) {
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const MissingDetail = styled(Detail)`
|
const MissingDetail = styled(Detail)`
|
||||||
dd& {
|
dd& {
|
||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
class JobTemplateDetail extends Component {
|
class JobTemplateDetail extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -317,7 +309,7 @@ class JobTemplateDetail extends Component {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</DetailList>
|
</DetailList>
|
||||||
<ButtonGroup>
|
<CardActionsRow>
|
||||||
{summary_fields.user_capabilities.edit && (
|
{summary_fields.user_capabilities.edit && (
|
||||||
<Button
|
<Button
|
||||||
component={Link}
|
component={Link}
|
||||||
@@ -340,7 +332,7 @@ class JobTemplateDetail extends Component {
|
|||||||
)}
|
)}
|
||||||
</LaunchButton>
|
</LaunchButton>
|
||||||
)}
|
)}
|
||||||
</ButtonGroup>
|
</CardActionsRow>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ describe('<JobTemplateDetail />', () => {
|
|||||||
verbosity: 1,
|
verbosity: 1,
|
||||||
summary_fields: {
|
summary_fields: {
|
||||||
user_capabilities: { edit: true },
|
user_capabilities: { edit: true },
|
||||||
created_by: { username: 'Joe' },
|
created_by: { id: 1, username: 'Joe' },
|
||||||
modified_by: { username: 'Joe' },
|
modified_by: { id: 1, username: 'Joe' },
|
||||||
credentials: [
|
credentials: [
|
||||||
{ id: 1, kind: 'ssh', name: 'Credential 1' },
|
{ id: 1, kind: 'ssh', name: 'Credential 1' },
|
||||||
{ id: 2, kind: 'awx', name: 'Credential 2' },
|
{ id: 2, kind: 'awx', name: 'Credential 2' },
|
||||||
@@ -28,6 +28,8 @@ describe('<JobTemplateDetail />', () => {
|
|||||||
inventory: { name: 'Inventory' },
|
inventory: { name: 'Inventory' },
|
||||||
project: { name: 'Project' },
|
project: { name: 'Project' },
|
||||||
},
|
},
|
||||||
|
created: '2020-04-25T01:23:45.678901Z',
|
||||||
|
modified: '2020-04-25T01:23:45.678901Z',
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockInstanceGroups = {
|
const mockInstanceGroups = {
|
||||||
@@ -100,12 +102,14 @@ describe('<JobTemplateDetail />', () => {
|
|||||||
skip_tags: 'coffe,tea',
|
skip_tags: 'coffe,tea',
|
||||||
summary_fields: {
|
summary_fields: {
|
||||||
user_capabilities: { edit: false },
|
user_capabilities: { edit: false },
|
||||||
created_by: { username: 'Joe' },
|
created_by: { id: 1, username: 'Joe' },
|
||||||
modified_by: { username: 'Joe' },
|
modified_by: { id: 1, username: 'Joe' },
|
||||||
inventory: { name: 'Inventory' },
|
inventory: { name: 'Inventory' },
|
||||||
project: { name: 'Project' },
|
project: { name: 'Project' },
|
||||||
labels: { count: 1, results: [{ name: 'Label', id: 1 }] },
|
labels: { count: 1, results: [{ name: 'Label', id: 1 }] },
|
||||||
},
|
},
|
||||||
|
created: '2020-04-25T01:23:45.678901Z',
|
||||||
|
modified: '2020-04-25T01:23:45.678901Z',
|
||||||
};
|
};
|
||||||
const wrapper = mountWithContexts(
|
const wrapper = mountWithContexts(
|
||||||
<JobTemplateDetail template={regularUser} />
|
<JobTemplateDetail template={regularUser} />
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ class User extends Component {
|
|||||||
{user && (
|
{user && (
|
||||||
<Route
|
<Route
|
||||||
path="/users/:id/details"
|
path="/users/:id/details"
|
||||||
render={() => <UserDetail match={match} user={user} />}
|
render={() => <UserDetail user={user} />}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Route
|
<Route
|
||||||
|
|||||||
@@ -1,77 +1,69 @@
|
|||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import { Link, withRouter } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Button } from '@patternfly/react-core';
|
import { Button } from '@patternfly/react-core';
|
||||||
|
|
||||||
import { CardBody } from '@components/Card';
|
import { CardBody, CardActionsRow } from '@components/Card';
|
||||||
import { DetailList, Detail } from '@components/DetailList';
|
import { DetailList, Detail } from '@components/DetailList';
|
||||||
import { formatDateString } from '@util/dates';
|
import { formatDateString } from '@util/dates';
|
||||||
|
|
||||||
class UserDetail extends Component {
|
function UserDetail({ user, i18n }) {
|
||||||
render() {
|
const {
|
||||||
const {
|
id,
|
||||||
user: {
|
username,
|
||||||
id,
|
email,
|
||||||
username,
|
first_name,
|
||||||
email,
|
last_name,
|
||||||
first_name,
|
last_login,
|
||||||
last_name,
|
created,
|
||||||
last_login,
|
is_superuser,
|
||||||
created,
|
is_system_auditor,
|
||||||
is_superuser,
|
summary_fields,
|
||||||
is_system_auditor,
|
} = user;
|
||||||
summary_fields,
|
|
||||||
},
|
|
||||||
i18n,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
let user_type;
|
let user_type;
|
||||||
if (is_superuser) {
|
if (is_superuser) {
|
||||||
user_type = i18n._(t`System Administrator`);
|
user_type = i18n._(t`System Administrator`);
|
||||||
} else if (is_system_auditor) {
|
} else if (is_system_auditor) {
|
||||||
user_type = i18n._(t`System Auditor`);
|
user_type = i18n._(t`System Auditor`);
|
||||||
} else {
|
} else {
|
||||||
user_type = i18n._(t`Normal User`);
|
user_type = i18n._(t`Normal User`);
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CardBody>
|
|
||||||
<DetailList>
|
|
||||||
<Detail
|
|
||||||
label={i18n._(t`Username`)}
|
|
||||||
value={username}
|
|
||||||
dataCy="user-detail-username"
|
|
||||||
/>
|
|
||||||
<Detail label={i18n._(t`Email`)} value={email} />
|
|
||||||
<Detail label={i18n._(t`First Name`)} value={`${first_name}`} />
|
|
||||||
<Detail label={i18n._(t`Last Name`)} value={`${last_name}`} />
|
|
||||||
<Detail label={i18n._(t`User Type`)} value={`${user_type}`} />
|
|
||||||
{last_login && (
|
|
||||||
<Detail
|
|
||||||
label={i18n._(t`Last Login`)}
|
|
||||||
value={formatDateString(last_login)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Detail
|
|
||||||
label={i18n._(t`Created`)}
|
|
||||||
value={formatDateString(created)}
|
|
||||||
/>
|
|
||||||
</DetailList>
|
|
||||||
{summary_fields.user_capabilities.edit && (
|
|
||||||
<div css="margin-top: 10px; text-align: right;">
|
|
||||||
<Button
|
|
||||||
aria-label={i18n._(t`edit`)}
|
|
||||||
component={Link}
|
|
||||||
to={`/users/${id}/edit`}
|
|
||||||
>
|
|
||||||
{i18n._(t`Edit`)}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</CardBody>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CardBody>
|
||||||
|
<DetailList>
|
||||||
|
<Detail
|
||||||
|
label={i18n._(t`Username`)}
|
||||||
|
value={username}
|
||||||
|
dataCy="user-detail-username"
|
||||||
|
/>
|
||||||
|
<Detail label={i18n._(t`Email`)} value={email} />
|
||||||
|
<Detail label={i18n._(t`First Name`)} value={`${first_name}`} />
|
||||||
|
<Detail label={i18n._(t`Last Name`)} value={`${last_name}`} />
|
||||||
|
<Detail label={i18n._(t`User Type`)} value={`${user_type}`} />
|
||||||
|
{last_login && (
|
||||||
|
<Detail
|
||||||
|
label={i18n._(t`Last Login`)}
|
||||||
|
value={formatDateString(last_login)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Detail label={i18n._(t`Created`)} value={formatDateString(created)} />
|
||||||
|
</DetailList>
|
||||||
|
<CardActionsRow>
|
||||||
|
{summary_fields.user_capabilities.edit && (
|
||||||
|
<Button
|
||||||
|
aria-label={i18n._(t`edit`)}
|
||||||
|
component={Link}
|
||||||
|
to={`/users/${id}/edit`}
|
||||||
|
>
|
||||||
|
{i18n._(t`Edit`)}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</CardActionsRow>
|
||||||
|
</CardBody>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withI18n()(withRouter(UserDetail));
|
export default withI18n()(UserDetail);
|
||||||
|
|||||||
Reference in New Issue
Block a user