mirror of
https://github.com/ansible/awx.git
synced 2026-05-19 14:57:39 -02:30
Improves NestedTabs, Refactors PR, Adds Delete/DeleteError Functionality to HostDetail
This commit is contained in:
@@ -116,6 +116,7 @@ class SelectResourceStep extends React.Component {
|
|||||||
name={item[displayKey]}
|
name={item[displayKey]}
|
||||||
label={item[displayKey]}
|
label={item[displayKey]}
|
||||||
onSelect={() => onRowClick(item)}
|
onSelect={() => onRowClick(item)}
|
||||||
|
onDeselect={() => {}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
renderToolbar={props => <DataListToolbar {...props} fillWidth />}
|
renderToolbar={props => <DataListToolbar {...props} fillWidth />}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shape, string, number, arrayOf } from 'prop-types';
|
import { shape, string, number, arrayOf, node, oneOfType } from 'prop-types';
|
||||||
import { Tab, Tabs as PFTabs } from '@patternfly/react-core';
|
import { Tab, Tabs as PFTabs } from '@patternfly/react-core';
|
||||||
import { withRouter } from 'react-router-dom';
|
import { withRouter } from 'react-router-dom';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { CaretLeftIcon } from '@patternfly/react-icons';
|
|
||||||
|
|
||||||
const Tabs = styled(PFTabs)`
|
const Tabs = styled(PFTabs)`
|
||||||
--pf-c-tabs__button--PaddingLeft: 20px;
|
--pf-c-tabs__button--PaddingLeft: 20px;
|
||||||
@@ -57,25 +56,15 @@ function RoutedTabs(props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs activeKey={getActiveTabId()} onSelect={handleTabSelect}>
|
<Tabs activeKey={getActiveTabId()} onSelect={handleTabSelect}>
|
||||||
{tabsArray
|
{tabsArray.map(tab => (
|
||||||
.filter(tab => tab.isNestedTab || !tab.name.startsWith('Return'))
|
<Tab
|
||||||
.map(tab => (
|
aria-label={`${tab.name}`}
|
||||||
<Tab
|
eventKey={tab.id}
|
||||||
aria-label={`${tab.name}`}
|
key={tab.id}
|
||||||
eventKey={tab.id}
|
link={tab.link}
|
||||||
key={tab.id}
|
title={tab.name}
|
||||||
link={tab.link}
|
/>
|
||||||
title={
|
))}
|
||||||
tab.isNestedTab ? (
|
|
||||||
<>
|
|
||||||
<CaretLeftIcon /> {tab.name}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
tab.name
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Tabs>
|
</Tabs>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -89,7 +78,7 @@ RoutedTabs.propTypes = {
|
|||||||
shape({
|
shape({
|
||||||
id: number.isRequired,
|
id: number.isRequired,
|
||||||
link: string.isRequired,
|
link: string.isRequired,
|
||||||
name: string.isRequired,
|
name: oneOfType([string.isRequired, node.isRequired]),
|
||||||
})
|
})
|
||||||
).isRequired,
|
).isRequired,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,15 +2,16 @@ import React, { Component } from 'react';
|
|||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Switch, Route, withRouter, Redirect, Link } from 'react-router-dom';
|
import { Switch, Route, withRouter, Redirect, Link } from 'react-router-dom';
|
||||||
import { Card, PageSection } from '@patternfly/react-core';
|
import { Card } from '@patternfly/react-core';
|
||||||
|
import { CaretLeftIcon } from '@patternfly/react-icons';
|
||||||
|
|
||||||
import { TabbedCardHeader } from '@components/Card';
|
import { TabbedCardHeader } from '@components/Card';
|
||||||
import CardCloseButton from '@components/CardCloseButton';
|
import CardCloseButton from '@components/CardCloseButton';
|
||||||
import RoutedTabs from '@components/RoutedTabs';
|
import RoutedTabs from '@components/RoutedTabs';
|
||||||
import ContentError from '@components/ContentError';
|
import ContentError from '@components/ContentError';
|
||||||
|
import ContentLoading from '@components/ContentLoading';
|
||||||
import HostFacts from './HostFacts';
|
import HostFacts from './HostFacts';
|
||||||
import HostDetail from './HostDetail';
|
import HostDetail from './HostDetail';
|
||||||
import AlertModal from '@components/AlertModal';
|
|
||||||
import ErrorDetail from '@components/ErrorDetail';
|
|
||||||
|
|
||||||
import HostEdit from './HostEdit';
|
import HostEdit from './HostEdit';
|
||||||
import HostGroups from './HostGroups';
|
import HostGroups from './HostGroups';
|
||||||
@@ -26,16 +27,8 @@ class Host extends Component {
|
|||||||
hasContentLoading: true,
|
hasContentLoading: true,
|
||||||
contentError: null,
|
contentError: null,
|
||||||
isInitialized: false,
|
isInitialized: false,
|
||||||
toggleLoading: false,
|
|
||||||
toggleError: null,
|
|
||||||
deletionError: false,
|
|
||||||
isDeleteModalOpen: false,
|
|
||||||
};
|
};
|
||||||
this.loadHost = this.loadHost.bind(this);
|
this.loadHost = this.loadHost.bind(this);
|
||||||
this.handleHostToggle = this.handleHostToggle.bind(this);
|
|
||||||
this.handleToggleError = this.handleToggleError.bind(this);
|
|
||||||
this.handleHostDelete = this.handleHostDelete.bind(this);
|
|
||||||
this.toggleDeleteModal = this.toggleDeleteModal.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
@@ -56,40 +49,6 @@ class Host extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleDeleteModal() {
|
|
||||||
const { isDeleteModalOpen } = this.state;
|
|
||||||
this.setState({ isDeleteModalOpen: !isDeleteModalOpen });
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleHostToggle() {
|
|
||||||
const { host } = this.state;
|
|
||||||
this.setState({ toggleLoading: true });
|
|
||||||
try {
|
|
||||||
const { data } = await HostsAPI.update(host.id, {
|
|
||||||
enabled: !host.enabled,
|
|
||||||
});
|
|
||||||
this.setState({ host: data });
|
|
||||||
} catch (err) {
|
|
||||||
this.setState({ toggleError: err });
|
|
||||||
} finally {
|
|
||||||
this.setState({ toggleLoading: null });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleHostDelete() {
|
|
||||||
const { host } = this.state;
|
|
||||||
const { match, history } = this.props;
|
|
||||||
|
|
||||||
this.setState({ hasContentLoading: true });
|
|
||||||
try {
|
|
||||||
await HostsAPI.destroy(host.id);
|
|
||||||
this.setState({ hasContentLoading: false });
|
|
||||||
history.push(`/inventories/inventory/${match.params.id}/hosts`);
|
|
||||||
} catch (err) {
|
|
||||||
this.setState({ deletionError: err });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadHost() {
|
async loadHost() {
|
||||||
const { match, setBreadcrumb, history, inventory } = this.props;
|
const { match, setBreadcrumb, history, inventory } = this.props;
|
||||||
|
|
||||||
@@ -102,8 +61,9 @@ class Host extends Component {
|
|||||||
|
|
||||||
if (history.location.pathname.startsWith('/hosts')) {
|
if (history.location.pathname.startsWith('/hosts')) {
|
||||||
setBreadcrumb(data);
|
setBreadcrumb(data);
|
||||||
|
} else {
|
||||||
|
setBreadcrumb(inventory, data);
|
||||||
}
|
}
|
||||||
setBreadcrumb(inventory, data);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.setState({ contentError: err });
|
this.setState({ contentError: err });
|
||||||
} finally {
|
} finally {
|
||||||
@@ -111,29 +71,10 @@ class Host extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleToggleError() {
|
|
||||||
this.setState({ toggleError: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { location, match, history, i18n } = this.props;
|
const { location, match, history, i18n } = this.props;
|
||||||
const {
|
const { host, hasContentLoading, isInitialized, contentError } = this.state;
|
||||||
deletionError,
|
|
||||||
host,
|
|
||||||
isDeleteModalOpen,
|
|
||||||
toggleError,
|
|
||||||
hasContentLoading,
|
|
||||||
toggleLoading,
|
|
||||||
isInitialized,
|
|
||||||
contentError,
|
|
||||||
} = this.state;
|
|
||||||
const tabsArray = [
|
const tabsArray = [
|
||||||
{
|
|
||||||
name: i18n._(t`Return to Hosts`),
|
|
||||||
link: `/inventories/inventory/${match.params.id}/hosts`,
|
|
||||||
id: 99,
|
|
||||||
isNestedTab: !history.location.pathname.startsWith('/hosts'),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: i18n._(t`Details`),
|
name: i18n._(t`Details`),
|
||||||
link: `${match.url}/details`,
|
link: `${match.url}/details`,
|
||||||
@@ -155,7 +96,18 @@ class Host extends Component {
|
|||||||
id: 3,
|
id: 3,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
if (!history.location.pathname.startsWith('/hosts')) {
|
||||||
|
tabsArray.unshift({
|
||||||
|
name: (
|
||||||
|
<>
|
||||||
|
<CaretLeftIcon />
|
||||||
|
{i18n._(t`Back to Hosts`)}
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
link: `/inventories/inventory/${match.params.id}/hosts`,
|
||||||
|
id: 99,
|
||||||
|
});
|
||||||
|
}
|
||||||
let cardHeader = (
|
let cardHeader = (
|
||||||
<TabbedCardHeader>
|
<TabbedCardHeader>
|
||||||
<RoutedTabs
|
<RoutedTabs
|
||||||
@@ -180,115 +132,98 @@ class Host extends Component {
|
|||||||
cardHeader = null;
|
cardHeader = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasContentLoading) {
|
||||||
|
return <ContentLoading />;
|
||||||
|
}
|
||||||
|
|
||||||
if (!hasContentLoading && contentError) {
|
if (!hasContentLoading && contentError) {
|
||||||
return (
|
return (
|
||||||
<PageSection>
|
<Card className="awx-c-card">
|
||||||
<Card className="awx-c-card">
|
<ContentError error={contentError}>
|
||||||
<ContentError error={contentError}>
|
{contentError.response.status === 404 && (
|
||||||
{contentError.response.status === 404 && (
|
<span>
|
||||||
<span>
|
{i18n._(`Host not found.`)}{' '}
|
||||||
{i18n._(`Host not found.`)}{' '}
|
<Link to="/hosts">{i18n._(`View all Hosts.`)}</Link>
|
||||||
<Link to="/hosts">{i18n._(`View all Hosts.`)}</Link>
|
</span>
|
||||||
</span>
|
)}
|
||||||
)}
|
</ContentError>
|
||||||
</ContentError>
|
</Card>
|
||||||
</Card>
|
|
||||||
</PageSection>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const redirect = location.pathname.startsWith('/hosts') ? (
|
||||||
|
<Redirect from="/hosts/:id" to="/hosts/:id/details" exact />
|
||||||
|
) : (
|
||||||
|
<Redirect
|
||||||
|
from="/inventories/inventory/:id/hosts/:hostId"
|
||||||
|
to="/inventories/inventory/:id/hosts/:hostId/details"
|
||||||
|
exact
|
||||||
|
/>
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<>
|
<Card className="awx-c-card">
|
||||||
<PageSection
|
{cardHeader}
|
||||||
css={`
|
<Switch>
|
||||||
${location.pathname.startsWith('/inventories')
|
{redirect}
|
||||||
? 'padding: 0'
|
{host && (
|
||||||
: 'null'}
|
<Route
|
||||||
`}
|
path={[
|
||||||
>
|
'/hosts/:id/details',
|
||||||
<Card className="awx-c-card">
|
'/inventories/inventory/:id/hosts/:hostId/details',
|
||||||
{cardHeader}
|
]}
|
||||||
<Switch>
|
render={() => (
|
||||||
<Redirect from="/hosts/:id" to="/hosts/:id/details" exact />
|
<HostDetail
|
||||||
{host && (
|
host={host}
|
||||||
<Route
|
onUpdateHost={newHost => this.setState({ host: newHost })}
|
||||||
path={[
|
|
||||||
'/hosts/:id/edit',
|
|
||||||
'/inventories/inventory/:id/hosts/:hostId/edit',
|
|
||||||
]}
|
|
||||||
render={() => <HostEdit match={match} host={host} />}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{host && (
|
/>
|
||||||
<Route
|
)}
|
||||||
path={[
|
))
|
||||||
'/hosts/:id/details',
|
{host && (
|
||||||
'/inventories/inventory/:id/hosts/:hostId/details',
|
<Route
|
||||||
]}
|
path={[
|
||||||
render={() => (
|
'/hosts/:id/edit',
|
||||||
<HostDetail
|
'/inventories/inventory/:id/hosts/:hostId/edit',
|
||||||
match={match}
|
]}
|
||||||
host={host}
|
render={() => <HostEdit match={match} host={host} />}
|
||||||
history={history}
|
/>
|
||||||
onToggleDeleteModal={this.toggleDeleteModal}
|
)}
|
||||||
isDeleteModalOpen={isDeleteModalOpen}
|
{host && (
|
||||||
onHandleHostToggle={this.handleHostToggle}
|
<Route
|
||||||
toggleError={toggleError}
|
path="/hosts/:id/facts"
|
||||||
toggleLoading={toggleLoading}
|
render={() => <HostFacts host={host} />}
|
||||||
onToggleError={this.handleToggleError}
|
/>
|
||||||
onHostDelete={this.handleHostDelete}
|
)}
|
||||||
/>
|
{host && (
|
||||||
|
<Route
|
||||||
|
path="/hosts/:id/groups"
|
||||||
|
render={() => <HostGroups host={host} />}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{host && (
|
||||||
|
<Route
|
||||||
|
path="/hosts/:id/completed_jobs"
|
||||||
|
render={() => <HostCompletedJobs host={host} />}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Route
|
||||||
|
key="not-found"
|
||||||
|
path="*"
|
||||||
|
render={() =>
|
||||||
|
!hasContentLoading && (
|
||||||
|
<ContentError isNotFound>
|
||||||
|
{match.params.id && (
|
||||||
|
<Link to={`/hosts/${match.params.id}/details`}>
|
||||||
|
{i18n._(`View Host Details`)}
|
||||||
|
</Link>
|
||||||
)}
|
)}
|
||||||
/>
|
</ContentError>
|
||||||
)}
|
)
|
||||||
{host && (
|
}
|
||||||
<Route
|
/>
|
||||||
path="/hosts/:id/facts"
|
,
|
||||||
render={() => <HostFacts host={host} />}
|
</Switch>
|
||||||
/>
|
</Card>
|
||||||
)}
|
|
||||||
{host && (
|
|
||||||
<Route
|
|
||||||
path="/hosts/:id/groups"
|
|
||||||
render={() => <HostGroups host={host} />}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{host && (
|
|
||||||
<Route
|
|
||||||
path="/hosts/:id/completed_jobs"
|
|
||||||
render={() => <HostCompletedJobs host={host} />}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Route
|
|
||||||
key="not-found"
|
|
||||||
path="*"
|
|
||||||
render={() =>
|
|
||||||
!hasContentLoading && (
|
|
||||||
<ContentError isNotFound>
|
|
||||||
{match.params.id && (
|
|
||||||
<Link to={`/hosts/${match.params.id}/details`}>
|
|
||||||
{i18n._(`View Host Details`)}
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
</ContentError>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
,
|
|
||||||
</Switch>
|
|
||||||
</Card>
|
|
||||||
</PageSection>
|
|
||||||
{deletionError && (
|
|
||||||
<AlertModal
|
|
||||||
isOpen={deletionError}
|
|
||||||
variant="danger"
|
|
||||||
title={i18n._(t`Error!`)}
|
|
||||||
onClose={() => this.setState({ deletionError: false })}
|
|
||||||
>
|
|
||||||
{i18n._(t`Failed to delete ${host.name}.`)}
|
|
||||||
<ErrorDetail error={deletionError} />
|
|
||||||
</AlertModal>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link, withRouter } 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 { Host } from '@types';
|
import { Host } from '@types';
|
||||||
import { Button } from '@patternfly/react-core';
|
import { Button } from '@patternfly/react-core';
|
||||||
import { CardBody, CardActionsRow } from '@components/Card';
|
import { CardBody, CardActionsRow } from '@components/Card';
|
||||||
|
import AlertModal from '@components/AlertModal';
|
||||||
|
import ErrorDetail from '@components/ErrorDetail';
|
||||||
import { DetailList, Detail, UserDateDetail } from '@components/DetailList';
|
import { DetailList, Detail, UserDateDetail } from '@components/DetailList';
|
||||||
import { VariablesDetail } from '@components/CodeMirrorInput';
|
import { VariablesDetail } from '@components/CodeMirrorInput';
|
||||||
import { Sparkline } from '@components/Sparkline';
|
import { Sparkline } from '@components/Sparkline';
|
||||||
|
import DeleteButton from '@components/DeleteButton';
|
||||||
import Switch from '@components/Switch';
|
import Switch from '@components/Switch';
|
||||||
|
import { HostsAPI } from '@api';
|
||||||
|
|
||||||
function HostDetail({ host, i18n }) {
|
function HostDetail({ host, i18n }) {
|
||||||
const ActionButtonWrapper = styled.div`
|
const ActionButtonWrapper = styled.div`
|
||||||
@@ -20,92 +24,62 @@ const ActionButtonWrapper = styled.div`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function HostDetail({
|
function HostDetail({ host, history, match, i18n, onUpdateHost }) {
|
||||||
host,
|
|
||||||
history,
|
|
||||||
isDeleteModalOpen,
|
|
||||||
match,
|
|
||||||
i18n,
|
|
||||||
toggleError,
|
|
||||||
toggleLoading,
|
|
||||||
onHostDelete,
|
|
||||||
onToggleDeleteModal,
|
|
||||||
onToggleError,
|
|
||||||
onHandleHostToggle,
|
|
||||||
}) {
|
|
||||||
const { created, description, id, modified, name, summary_fields } = host;
|
const { created, description, id, modified, name, summary_fields } = host;
|
||||||
let createdBy = '';
|
|
||||||
if (created) {
|
|
||||||
if (summary_fields.created_by && summary_fields.created_by.username) {
|
|
||||||
createdBy = (
|
|
||||||
<span>
|
|
||||||
{i18n._(t`${formatDateString(created)} by `)}{' '}
|
|
||||||
<Link to={`/users/${summary_fields.created_by.id}`}>
|
|
||||||
{summary_fields.created_by.username}
|
|
||||||
</Link>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
createdBy = formatDateString(created);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let modifiedBy = '';
|
const [isLoading, setIsloading] = useState(false);
|
||||||
if (modified) {
|
const [deletionError, setDeletionError] = useState(false);
|
||||||
if (summary_fields.modified_by && summary_fields.modified_by.username) {
|
const [toggleLoading, setToggleLoading] = useState(false);
|
||||||
modifiedBy = (
|
const [toggleError, setToggleError] = useState(false);
|
||||||
<span>
|
|
||||||
{i18n._(t`${formatDateString(modified)} by`)}{' '}
|
const handleHostToggle = async () => {
|
||||||
<Link to={`/users/${summary_fields.modified_by.id}`}>
|
setToggleLoading(true);
|
||||||
{summary_fields.modified_by.username}
|
try {
|
||||||
</Link>
|
const { data } = await HostsAPI.update(host.id, {
|
||||||
</span>
|
enabled: !host.enabled,
|
||||||
);
|
});
|
||||||
} else {
|
onUpdateHost(data);
|
||||||
modifiedBy = formatDateString(modified);
|
} catch (err) {
|
||||||
|
setToggleError(err);
|
||||||
|
} finally {
|
||||||
|
setToggleLoading(false);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const handleHostDelete = async () => {
|
||||||
|
setIsloading(true);
|
||||||
|
try {
|
||||||
|
await HostsAPI.destroy(host.id);
|
||||||
|
setIsloading(false);
|
||||||
|
history.push(`/inventories/inventory/${match.params.id}/hosts`);
|
||||||
|
} catch (err) {
|
||||||
|
setDeletionError(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (toggleError && !toggleLoading) {
|
if (toggleError && !toggleLoading) {
|
||||||
return (
|
return (
|
||||||
<AlertModal
|
<AlertModal
|
||||||
variant="danger"
|
variant="danger"
|
||||||
title={i18n._(t`Error!`)}
|
title={i18n._(t`Error!`)}
|
||||||
isOpen={toggleError && !toggleLoading}
|
isOpen={toggleError && !toggleLoading}
|
||||||
onClose={onToggleError}
|
onClose={() => setToggleError(false)}
|
||||||
>
|
>
|
||||||
{i18n._(t`Failed to toggle host.`)}
|
{i18n._(t`Failed to toggle host.`)}
|
||||||
<ErrorDetail error={toggleError} />
|
<ErrorDetail error={toggleError} />
|
||||||
</AlertModal>
|
</AlertModal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (isDeleteModalOpen) {
|
if (!isLoading && deletionError) {
|
||||||
return (
|
return (
|
||||||
<AlertModal
|
<AlertModal
|
||||||
isOpen={isDeleteModalOpen}
|
isOpen={deletionError}
|
||||||
title={i18n._(t`Delete Host`)}
|
|
||||||
variant="danger"
|
variant="danger"
|
||||||
onClose={() => onToggleDeleteModal()}
|
title={i18n._(t`Error!`)}
|
||||||
|
onClose={() => setDeletionError(false)}
|
||||||
>
|
>
|
||||||
{i18n._(t`Are you sure you want to delete:`)}
|
{i18n._(t`Failed to delete ${host.name}.`)}
|
||||||
<br />
|
<ErrorDetail error={deletionError} />
|
||||||
<strong>{host.name}</strong>
|
|
||||||
<ActionButtonWrapper>
|
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
aria-label={i18n._(t`Close`)}
|
|
||||||
onClick={() => onToggleDeleteModal()}
|
|
||||||
>
|
|
||||||
{i18n._(t`Cancel`)}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
variant="danger"
|
|
||||||
aria-label={i18n._(t`Delete`)}
|
|
||||||
onClick={() => onHostDelete()}
|
|
||||||
>
|
|
||||||
{i18n._(t`Delete`)}
|
|
||||||
</Button>
|
|
||||||
</ActionButtonWrapper>
|
|
||||||
</AlertModal>
|
</AlertModal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -118,7 +92,7 @@ function HostDetail({
|
|||||||
labelOff={i18n._(t`Off`)}
|
labelOff={i18n._(t`Off`)}
|
||||||
isChecked={host.enabled}
|
isChecked={host.enabled}
|
||||||
isDisabled={!host.summary_fields.user_capabilities.edit}
|
isDisabled={!host.summary_fields.user_capabilities.edit}
|
||||||
onChange={onHandleHostToggle}
|
onChange={() => handleHostToggle()}
|
||||||
aria-label={i18n._(t`Toggle Host`)}
|
aria-label={i18n._(t`Toggle Host`)}
|
||||||
/>
|
/>
|
||||||
<DetailList gutter="sm">
|
<DetailList gutter="sm">
|
||||||
@@ -145,8 +119,16 @@ function HostDetail({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Detail label={i18n._(t`Created`)} value={createdBy} />
|
<UserDateDetail
|
||||||
<Detail label={i18n._(t`Last Modified`)} value={modifiedBy} />
|
date={created}
|
||||||
|
label={i18n._(t`Created`)}
|
||||||
|
user={summary_fields.created_by}
|
||||||
|
/>
|
||||||
|
<UserDateDetail
|
||||||
|
label={i18n._(t`Last Modified`)}
|
||||||
|
user={summary_fields.modified_by}
|
||||||
|
date={modified}
|
||||||
|
/>
|
||||||
<VariablesDetail
|
<VariablesDetail
|
||||||
value={host.variables}
|
value={host.variables}
|
||||||
rows={4}
|
rows={4}
|
||||||
@@ -169,6 +151,14 @@ function HostDetail({
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</CardActionsRow>
|
</CardActionsRow>
|
||||||
|
{summary_fields.user_capabilities &&
|
||||||
|
summary_fields.user_capabilities.delete && (
|
||||||
|
<DeleteButton
|
||||||
|
onConfirm={() => handleHostDelete()}
|
||||||
|
modalTitle={i18n._(t`Delete Host`)}
|
||||||
|
name={host.name}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</CardBody>
|
</CardBody>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React, { Component, Fragment } from 'react';
|
|||||||
import { Route, withRouter, Switch } from 'react-router-dom';
|
import { Route, withRouter, Switch } 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 { PageSection } from '@patternfly/react-core';
|
||||||
|
|
||||||
import { Config } from '@contexts/Config';
|
import { Config } from '@contexts/Config';
|
||||||
import Breadcrumbs from '@components/Breadcrumbs/Breadcrumbs';
|
import Breadcrumbs from '@components/Breadcrumbs/Breadcrumbs';
|
||||||
@@ -46,35 +47,31 @@ class Hosts extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { match, history, location, inventory } = this.props;
|
const { match } = this.props;
|
||||||
const { breadcrumbConfig } = this.state;
|
const { breadcrumbConfig } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Breadcrumbs breadcrumbConfig={breadcrumbConfig} />
|
<Breadcrumbs breadcrumbConfig={breadcrumbConfig} />
|
||||||
<Switch>
|
<PageSection>
|
||||||
<Route
|
<Switch>
|
||||||
path={`${match.path}/add`}
|
<Route path={`${match.path}/add`} render={() => <HostAdd />} />
|
||||||
render={() => <HostAdd history={history} />}
|
<Route
|
||||||
/>
|
path={`${match.path}/:id`}
|
||||||
<Route
|
render={() => (
|
||||||
path={`${match.path}/:id`}
|
<Config>
|
||||||
render={() => (
|
{({ me }) => (
|
||||||
<Config>
|
<Host
|
||||||
{({ me }) => (
|
setBreadcrumb={this.setBreadcrumbConfig}
|
||||||
<Host
|
me={me || {}}
|
||||||
history={history}
|
/>
|
||||||
location={location}
|
)}
|
||||||
setBreadcrumb={this.setBreadcrumbConfig}
|
</Config>
|
||||||
me={me || {}}
|
)}
|
||||||
inventory={inventory}
|
/>
|
||||||
/>
|
<Route path={`${match.path}`} render={() => <HostList />} />
|
||||||
)}
|
</Switch>
|
||||||
</Config>
|
</PageSection>
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Route path={`${match.path}`} render={() => <HostList />} />
|
|
||||||
</Switch>
|
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,51 +39,46 @@ class Inventories extends Component {
|
|||||||
'/inventories/inventory/add': i18n._(t`Create New Inventory`),
|
'/inventories/inventory/add': i18n._(t`Create New Inventory`),
|
||||||
'/inventories/smart_inventory/add': i18n._(t`Create New Smart Inventory`),
|
'/inventories/smart_inventory/add': i18n._(t`Create New Smart Inventory`),
|
||||||
[`/inventories/${inventoryKind}/${inventory.id}`]: `${inventory.name}`,
|
[`/inventories/${inventoryKind}/${inventory.id}`]: `${inventory.name}`,
|
||||||
[`/inventories/${inventoryKind}/${inventory.id}/details`]: i18n._(
|
|
||||||
t`Details`
|
|
||||||
),
|
|
||||||
[`/inventories/${inventoryKind}/${inventory.id}/edit`]: i18n._(
|
|
||||||
t`Edit Details`
|
|
||||||
),
|
|
||||||
[`/inventories/${inventoryKind}/${inventory.id}/access`]: i18n._(
|
[`/inventories/${inventoryKind}/${inventory.id}/access`]: i18n._(
|
||||||
t`Access`
|
t`Access`
|
||||||
),
|
),
|
||||||
[`/inventories/${inventoryKind}/${inventory.id}/completed_jobs`]: i18n._(
|
[`/inventories/${inventoryKind}/${inventory.id}/completed_jobs`]: i18n._(
|
||||||
t`Completed Jobs`
|
t`Completed Jobs`
|
||||||
),
|
),
|
||||||
|
[`/inventories/${inventoryKind}/${inventory.id}/details`]: i18n._(
|
||||||
|
t`Details`
|
||||||
|
),
|
||||||
|
[`/inventories/${inventoryKind}/${inventory.id}/edit`]: i18n._(
|
||||||
|
t`Edit Details`
|
||||||
|
),
|
||||||
|
[`/inventories/${inventoryKind}/${inventory.id}/groups`]: i18n._(
|
||||||
|
t`Groups`
|
||||||
|
),
|
||||||
|
[`/inventories/${inventoryKind}/${inventory.id}/hosts`]: i18n._(t`Hosts`),
|
||||||
|
|
||||||
|
[`/inventories/${inventoryKind}/${inventory.id}/sources`]: i18n._(
|
||||||
|
t`Sources`
|
||||||
|
),
|
||||||
|
|
||||||
|
[`/inventories/${inventoryKind}/${inventory.id}/hosts/${nestedResource &&
|
||||||
|
nestedResource.id}/edit`]: i18n._(t`Edit Details`),
|
||||||
|
[`/inventories/${inventoryKind}/${inventory.id}/hosts/${nestedResource &&
|
||||||
|
nestedResource.id}/details`]: i18n._(t`Host Details`),
|
||||||
[`/inventories/${inventoryKind}/${inventory.id}/hosts/${nestedResource &&
|
[`/inventories/${inventoryKind}/${inventory.id}/hosts/${nestedResource &&
|
||||||
nestedResource.id}`]: i18n._(
|
nestedResource.id}`]: i18n._(
|
||||||
t`${nestedResource && nestedResource.name}`
|
t`${nestedResource && nestedResource.name}`
|
||||||
),
|
),
|
||||||
[`/inventories/${inventoryKind}/${inventory.id}/hosts/${nestedResource &&
|
|
||||||
nestedResource.id}/details`]: i18n._(t`Details`),
|
|
||||||
[`/inventories/${inventoryKind}/${inventory.id}/hosts/${nestedResource &&
|
|
||||||
nestedResource.id}/edit`]: i18n._(t`Edit Details`),
|
|
||||||
[`/inventories/${inventoryKind}/${inventory.id}/hosts/add`]: i18n._(
|
|
||||||
t`Create New Host`
|
|
||||||
),
|
|
||||||
[`/inventories/${inventoryKind}/${inventory.id}/sources`]: i18n._(
|
|
||||||
t`Sources`
|
|
||||||
),
|
|
||||||
[`/inventories/${inventoryKind}/${inventory.id}/groups`]: i18n._(
|
|
||||||
t`Groups`
|
|
||||||
),
|
|
||||||
[`/inventories/${inventoryKind}/${inventory.id}/groups/add`]: i18n._(
|
[`/inventories/${inventoryKind}/${inventory.id}/groups/add`]: i18n._(
|
||||||
t`Create New Group`
|
t`Create New Group`
|
||||||
),
|
),
|
||||||
[`/inventories/${inventoryKind}/${inventory.id}/groups/${nestedResource &&
|
[`/inventories/${inventoryKind}/${inventory.id}/groups/${nestedResource &&
|
||||||
nestedResource.id}`]: `${nestedResource && nestedResource.name}`,
|
nestedResource.id}/edit`]: i18n._(t`Edit Details`),
|
||||||
[`/inventories/${inventoryKind}/${inventory.id}/groups/${nestedResource &&
|
[`/inventories/${inventoryKind}/${inventory.id}/groups/${nestedResource &&
|
||||||
nestedResource.id}/details`]: i18n._(t`Group Details`),
|
nestedResource.id}/details`]: i18n._(t`Group Details`),
|
||||||
[`/inventories/${inventoryKind}/${inventory.id}/groups/${nestedResource &&
|
[`/inventories/${inventoryKind}/${inventory.id}/groups/${nestedResource &&
|
||||||
nestedResource.id}/edit`]: i18n._(t`Edit Details`),
|
nestedResource.id}`]: `${nestedResource && nestedResource.name}`,
|
||||||
[`/inventories/${inventoryKind}/${inventory.id}/hosts`]: i18n._(t`Hosts`),
|
|
||||||
[`/inventories/${inventoryKind}/${inventory.id}/sources`]: i18n._(
|
|
||||||
t`Sources`
|
|
||||||
),
|
|
||||||
[`/inventories/${inventoryKind}/${inventory.id}/groups`]: i18n._(
|
|
||||||
t`Groups`
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
this.setState({ breadcrumbConfig });
|
this.setState({ breadcrumbConfig });
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ function Inventory({ history, i18n, location, match, setBreadcrumb }) {
|
|||||||
location.pathname.endsWith('edit') ||
|
location.pathname.endsWith('edit') ||
|
||||||
location.pathname.endsWith('add') ||
|
location.pathname.endsWith('add') ||
|
||||||
location.pathname.includes('groups/') ||
|
location.pathname.includes('groups/') ||
|
||||||
history.location.pathname.includes(`/hosts/`)
|
location.pathname.includes('hosts/')
|
||||||
) {
|
) {
|
||||||
cardHeader = null;
|
cardHeader = null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
|
|
||||||
import { Switch, Route, withRouter, Link, Redirect } from 'react-router-dom';
|
import { Switch, Route, withRouter, Link, Redirect } from 'react-router-dom';
|
||||||
|
import { CaretLeftIcon } from '@patternfly/react-icons';
|
||||||
|
|
||||||
import { GroupsAPI } from '@api';
|
import { GroupsAPI } from '@api';
|
||||||
import CardCloseButton from '@components/CardCloseButton';
|
import CardCloseButton from '@components/CardCloseButton';
|
||||||
import RoutedTabs from '@components/RoutedTabs';
|
import RoutedTabs from '@components/RoutedTabs';
|
||||||
@@ -40,7 +41,12 @@ function InventoryGroups({ i18n, match, setBreadcrumb, inventory, history }) {
|
|||||||
|
|
||||||
const tabsArray = [
|
const tabsArray = [
|
||||||
{
|
{
|
||||||
name: i18n._(t`Return to Groups`),
|
name: (
|
||||||
|
<>
|
||||||
|
<CaretLeftIcon />
|
||||||
|
{i18n._(t`Back to Groups`)}
|
||||||
|
</>
|
||||||
|
),
|
||||||
link: `/inventories/inventory/${inventory.id}/groups`,
|
link: `/inventories/inventory/${inventory.id}/groups`,
|
||||||
id: 99,
|
id: 99,
|
||||||
isNestedTab: true,
|
isNestedTab: true,
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ GroupsAPI.readDetail.mockResolvedValue({
|
|||||||
name: 'Foo',
|
name: 'Foo',
|
||||||
description: 'Bar',
|
description: 'Bar',
|
||||||
variables: 'bizz: buzz',
|
variables: 'bizz: buzz',
|
||||||
|
created: '1/12/2019',
|
||||||
|
modified: '1/13/2019',
|
||||||
summary_fields: {
|
summary_fields: {
|
||||||
inventory: { id: 1 },
|
inventory: { id: 1 },
|
||||||
created_by: { id: 1, username: 'Athena' },
|
created_by: { id: 1, username: 'Athena' },
|
||||||
@@ -62,10 +64,10 @@ describe('<InventoryGroup />', () => {
|
|||||||
test('renders successfully', async () => {
|
test('renders successfully', async () => {
|
||||||
expect(wrapper.length).toBe(1);
|
expect(wrapper.length).toBe(1);
|
||||||
});
|
});
|
||||||
test('expect all tabs to exist, including Return to Groups', async () => {
|
test('expect all tabs to exist, including Back to Groups', async () => {
|
||||||
expect(wrapper.find('button[aria-label="Return to Groups"]').length).toBe(
|
expect(
|
||||||
1
|
wrapper.find('button[link="/inventories/inventory/1/groups"]').length
|
||||||
);
|
).toBe(1);
|
||||||
expect(wrapper.find('button[aria-label="Details"]').length).toBe(1);
|
expect(wrapper.find('button[aria-label="Details"]').length).toBe(1);
|
||||||
expect(wrapper.find('button[aria-label="Related Groups"]').length).toBe(1);
|
expect(wrapper.find('button[aria-label="Related Groups"]').length).toBe(1);
|
||||||
expect(wrapper.find('button[aria-label="Hosts"]').length).toBe(1);
|
expect(wrapper.find('button[aria-label="Hosts"]').length).toBe(1);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Switch, Route, withRouter } from 'react-router-dom';
|
|||||||
|
|
||||||
import Host from '../../Host/Host';
|
import Host from '../../Host/Host';
|
||||||
import InventoryHostList from './InventoryHostList';
|
import InventoryHostList from './InventoryHostList';
|
||||||
import HostAdd from '../InventoryHostAdd';
|
import InventoryHostAdd from '../InventoryHostAdd';
|
||||||
|
|
||||||
function InventoryHosts({ match, setBreadcrumb, i18n, inventory }) {
|
function InventoryHosts({ match, setBreadcrumb, i18n, inventory }) {
|
||||||
return (
|
return (
|
||||||
@@ -11,11 +11,11 @@ function InventoryHosts({ match, setBreadcrumb, i18n, inventory }) {
|
|||||||
<Route
|
<Route
|
||||||
key="host-add"
|
key="host-add"
|
||||||
path="/inventories/inventory/:id/hosts/add"
|
path="/inventories/inventory/:id/hosts/add"
|
||||||
render={() => <HostAdd match={match} />}
|
render={() => <InventoryHostAdd match={match} />}
|
||||||
/>
|
/>
|
||||||
,
|
,
|
||||||
<Route
|
<Route
|
||||||
key="details and edit"
|
key="host"
|
||||||
path="/inventories/inventory/:id/hosts/:hostId"
|
path="/inventories/inventory/:id/hosts/:hostId"
|
||||||
render={() => (
|
render={() => (
|
||||||
<Host
|
<Host
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ describe('<JobTemplateDetail />', () => {
|
|||||||
playbook: '',
|
playbook: '',
|
||||||
id: 1,
|
id: 1,
|
||||||
verbosity: 1,
|
verbosity: 1,
|
||||||
|
created: '1/12/2019',
|
||||||
|
modified: '1/13/2019',
|
||||||
summary_fields: {
|
summary_fields: {
|
||||||
user_capabilities: { edit: true },
|
user_capabilities: { edit: true },
|
||||||
created_by: { id: 1, username: 'Joe' },
|
created_by: { id: 1, username: 'Joe' },
|
||||||
|
|||||||
Reference in New Issue
Block a user