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';