diff --git a/awx/ui_next/src/components/ContentError/ContentError.jsx b/awx/ui_next/src/components/ContentError/ContentError.jsx
index 6aa43a68e0..e5f8fbbbb1 100644
--- a/awx/ui_next/src/components/ContentError/ContentError.jsx
+++ b/awx/ui_next/src/components/ContentError/ContentError.jsx
@@ -9,6 +9,7 @@ import {
EmptyStateBody,
} from '@patternfly/react-core';
import { ExclamationTriangleIcon } from '@patternfly/react-icons';
+import { RootAPI } from '@api';
import ErrorDetail from '@components/ErrorDetail';
import NotFoundError from './NotFoundError';
@@ -16,23 +17,33 @@ const EmptyState = styled(PFEmptyState)`
width: var(--pf-c-empty-state--m-lg--MaxWidth);
`;
+async function logout() {
+ await RootAPI.logout();
+ window.location.replace('/#/login');
+}
+
class ContentError extends React.Component {
+
render() {
- const { error, i18n } = this.props;
+ const { error, children, i18n } = this.props;
if (error && error.response && error.response.status === 401) {
- // TODO: check for session timeout & redirect to /login
+ if (!error.response.headers['session-timeout']) {
+ logout();
+ return null;
+ }
}
if (error && error.response && error.response.status === 404) {
- return ;
+ return {children};
}
return (
{i18n._(t`Something went wrong...`)}
- {i18n._(
- t`There was an error loading this content. Please reload the page.`
- )}
+ {children ||
+ i18n._(
+ t`There was an error loading this content. Please reload the page.`
+ )}
{error && }
diff --git a/awx/ui_next/src/components/ContentError/NotFoundError.jsx b/awx/ui_next/src/components/ContentError/NotFoundError.jsx
index 022cf8e927..a8ae056ec6 100644
--- a/awx/ui_next/src/components/ContentError/NotFoundError.jsx
+++ b/awx/ui_next/src/components/ContentError/NotFoundError.jsx
@@ -15,15 +15,13 @@ const EmptyState = styled(PFEmptyState)`
width: var(--pf-c-empty-state--m-lg--MaxWidth);
`;
-function NotFoundError ({ i18n, error }) {
+function NotFoundError({ i18n, error, children }) {
return (
-
- {i18n._(t`Not Found`)}
-
+ {i18n._(t`Not Found`)}
- {i18n._(`The page you requested could not be found.`)}
+ {children || i18n._(`The page you requested could not be found.`)}
{error && }
diff --git a/awx/ui_next/src/screens/Job/Job.jsx b/awx/ui_next/src/screens/Job/Job.jsx
index 313f051cad..95e084c409 100644
--- a/awx/ui_next/src/screens/Job/Job.jsx
+++ b/awx/ui_next/src/screens/Job/Job.jsx
@@ -1,5 +1,5 @@
import React, { Component } from 'react';
-import { Route, withRouter, Switch, Redirect } from 'react-router-dom';
+import { Route, withRouter, Switch, Redirect, Link } from 'react-router-dom';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import styled from 'styled-components';
@@ -9,7 +9,7 @@ import {
PageSection,
} from '@patternfly/react-core';
import { JobsAPI } from '@api';
-import ContentError from '@components/ContentError';
+import ContentError, { NotFoundError } from '@components/ContentError';
import CardCloseButton from '@components/CardCloseButton';
import RoutedTabs from '@components/RoutedTabs';
@@ -99,7 +99,14 @@ class Job extends Component {
return (
-
+
+ {contentError.response.status === 404 && (
+
+ {i18n._(`The page you requested could not be found.`)}{' '}
+ {i18n._(`View all Jobs.`)}
+
+ )}
+
);
@@ -139,6 +146,20 @@ class Job extends Component {
path="/jobs/:type/:id/output"
render={() => }
/>,
+ (
+
+ {i18n._(`The page you requested could not be found.`)}{' '}
+
+ {i18n._(`View Job Details`)}
+
+
+ )}
+ />,
]}
diff --git a/awx/ui_next/src/screens/Job/JobTypeRedirect.jsx b/awx/ui_next/src/screens/Job/JobTypeRedirect.jsx
index 20ba8b1bc0..0a1e3266f0 100644
--- a/awx/ui_next/src/screens/Job/JobTypeRedirect.jsx
+++ b/awx/ui_next/src/screens/Job/JobTypeRedirect.jsx
@@ -1,8 +1,12 @@
import React, { Component } from 'react';
-import { Redirect } from 'react-router-dom';
+import { Redirect, Link } from 'react-router-dom';
+import { PageSection, Card } from '@patternfly/react-core';
import { UnifiedJobsAPI } from '@api';
+import ContentError, { NotFoundError } from '@components/ContentError';
import { JOB_TYPE_URL_SEGMENTS } from '../../constants';
+const NOT_FOUND = 'not found';
+
class JobTypeRedirect extends Component {
static defaultProps = {
view: 'details',
@@ -12,8 +16,9 @@ class JobTypeRedirect extends Component {
super(props);
this.state = {
- hasError: false,
+ error: null,
job: null,
+ isLoading: true,
};
this.loadJob = this.loadJob.bind(this);
}
@@ -24,24 +29,44 @@ class JobTypeRedirect extends Component {
async loadJob() {
const { id } = this.props;
+ this.setState({ isLoading: true });
try {
const { data } = await UnifiedJobsAPI.read({ id });
+ const job = data.results[0];
this.setState({
- job: data.results[0],
+ job,
+ isLoading: false,
+ error: job ? null : NOT_FOUND,
+ });
+ } catch (error) {
+ this.setState({
+ error,
+ isLoading: false,
});
- } catch (err) {
- this.setState({ hasError: true });
}
}
render() {
const { path, view } = this.props;
- const { hasError, job } = this.state;
+ const { error, job, isLoading } = this.state;
- if (hasError) {
- return Error
;
+ if (error) {
+ return (
+
+
+ {error === NOT_FOUND ? (
+
+ The requested job could not be found. View all Jobs.
+
+ ) : (
+
+ )}
+
+
+ );
}
- if (!job) {
+ if (isLoading) {
+ // TODO show loading state
return Loading...
;
}
const type = JOB_TYPE_URL_SEGMENTS[job.type];
diff --git a/awx/ui_next/src/screens/Organization/Organization.jsx b/awx/ui_next/src/screens/Organization/Organization.jsx
index 9727bdc411..47086b79d1 100644
--- a/awx/ui_next/src/screens/Organization/Organization.jsx
+++ b/awx/ui_next/src/screens/Organization/Organization.jsx
@@ -1,7 +1,7 @@
import React, { Component } from 'react';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
-import { Switch, Route, withRouter, Redirect } from 'react-router-dom';
+import { Switch, Route, withRouter, Redirect, Link } from 'react-router-dom';
import {
Card,
CardHeader as PFCardHeader,
@@ -10,7 +10,7 @@ import {
import styled from 'styled-components';
import CardCloseButton from '@components/CardCloseButton';
import RoutedTabs from '@components/RoutedTabs';
-import ContentError from '@components/ContentError';
+import ContentError, { NotFoundError } from '@components/ContentError';
import { OrganizationAccess } from './OrganizationAccess';
import OrganizationDetail from './OrganizationDetail';
import OrganizationEdit from './OrganizationEdit';
@@ -168,7 +168,16 @@ class Organization extends Component {
return (
-
+
+ {contentError.response.status === 404 && (
+
+ {i18n._(`Organization not found.`)}{' '}
+
+ {i18n._(`View all Organizations.`)}
+
+
+ )}
+
);
@@ -226,6 +235,21 @@ class Organization extends Component {
)}
/>
)}
+ (
+
+ {i18n._(`The page you requested could not be found.`)}{' '}
+ {match.params.id && (
+
+ {i18n._(`View Organization Details`)}
+
+ )}
+
+ )}
+ />
+ ,
diff --git a/awx/ui_next/src/screens/Template/Template.jsx b/awx/ui_next/src/screens/Template/Template.jsx
index adead1c6c0..67848f2bde 100644
--- a/awx/ui_next/src/screens/Template/Template.jsx
+++ b/awx/ui_next/src/screens/Template/Template.jsx
@@ -2,9 +2,9 @@ import React, { Component } from 'react';
import { t } from '@lingui/macro';
import { withI18n } from '@lingui/react';
import { Card, CardHeader, PageSection } from '@patternfly/react-core';
-import { Switch, Route, Redirect, withRouter } from 'react-router-dom';
+import { Switch, Route, Redirect, withRouter, Link } from 'react-router-dom';
import CardCloseButton from '@components/CardCloseButton';
-import ContentError from '@components/ContentError';
+import ContentError, { NotFoundError } from '@components/ContentError';
import RoutedTabs from '@components/RoutedTabs';
import JobTemplateDetail from './JobTemplateDetail';
import { JobTemplatesAPI } from '@api';
@@ -77,7 +77,14 @@ class Template extends Component {
return (
-
+
+ {contentError.response.status === 404 && (
+
+ {i18n._(`Template not found.`)}{' '}
+ {i18n._(`View all Templates.`)}
+
+ )}
+
);
@@ -92,8 +99,9 @@ class Template extends Component {
to="/templates/:templateType/:id/details"
exact
/>
- {template && (
+ {template && [
(
)}
- />
- )}
- {template && (
+ />,
}
- />
- )}
+ />,
+ (
+
+ {i18n._(`The page you requested could not be found.`)}{' '}
+ {match.params.id && (
+
+ {i18n._(`View Template Details`)}
+
+ )}
+
+ )}
+ />,
+ ]}
diff --git a/awx/ui_next/src/screens/Template/Templates.jsx b/awx/ui_next/src/screens/Template/Templates.jsx
index 55643616cd..d9460d0ac3 100644
--- a/awx/ui_next/src/screens/Template/Templates.jsx
+++ b/awx/ui_next/src/screens/Template/Templates.jsx
@@ -1,11 +1,11 @@
import React, { Component, Fragment } from 'react';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
-import { Route, withRouter, Switch } from 'react-router-dom';
+import { Route, withRouter, Switch, Link } from 'react-router-dom';
import { Config } from '@contexts/Config';
import Breadcrumbs from '@components/Breadcrumbs/Breadcrumbs';
-
+import { NotFoundError } from '@components/ContentError';
import { TemplateList } from './TemplateList';
import Template from './Template';
import JobTemplateAdd from './JobTemplateAdd';