diff --git a/src/App.jsx b/src/App.jsx
index e4fdd54243..7e4699e33d 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -6,13 +6,20 @@ import {
Page,
PageHeader,
PageSidebar,
+ Button
} from '@patternfly/react-core';
+import { I18n } from '@lingui/react';
+import { t } from '@lingui/macro';
+
+import { RootDialog } from './contexts/RootDialog';
+import { withNetwork } from './contexts/Network';
+
+import AlertModal from './components/AlertModal';
import About from './components/About';
import NavExpandableGroup from './components/NavExpandableGroup';
import TowerLogo from './components/TowerLogo';
import PageHeaderToolbar from './components/PageHeaderToolbar';
-import { ConfigContext } from './context';
class App extends Component {
constructor (props) {
@@ -23,29 +30,24 @@ class App extends Component {
&& window.innerWidth >= parseInt(global_breakpoint_md.value, 10);
this.state = {
- ansible_version: null,
- custom_virtualenvs: null,
isAboutModalOpen: false,
- isNavOpen,
- version: null,
+ isNavOpen
};
- this.fetchConfig = this.fetchConfig.bind(this);
this.onLogout = this.onLogout.bind(this);
this.onAboutModalClose = this.onAboutModalClose.bind(this);
this.onAboutModalOpen = this.onAboutModalOpen.bind(this);
this.onNavToggle = this.onNavToggle.bind(this);
}
- componentDidMount () {
- this.fetchConfig();
- }
-
async onLogout () {
- const { api } = this.props;
-
- await api.logout();
- window.location.replace('/#/login');
+ const { api, handleHttpError } = this.props;
+ try {
+ await api.logout();
+ window.location.replace('/#/login');
+ } catch (err) {
+ handleHttpError(err);
+ }
}
onAboutModalOpen () {
@@ -60,24 +62,12 @@ class App extends Component {
this.setState(({ isNavOpen }) => ({ isNavOpen: !isNavOpen }));
}
- async fetchConfig () {
- const { api } = this.props;
-
- try {
- const { data: { ansible_version, custom_virtualenvs, version } } = await api.getConfig();
- this.setState({ ansible_version, custom_virtualenvs, version });
- } catch (err) {
- this.setState({ ansible_version: null, custom_virtualenvs: null, version: null });
- }
- }
-
render () {
const {
ansible_version,
- custom_virtualenvs,
isAboutModalOpen,
isNavOpen,
- version,
+ version
} = this.state;
const {
@@ -86,63 +76,76 @@ class App extends Component {
navLabel = '',
} = this.props;
- const config = {
- ansible_version,
- custom_virtualenvs,
- version,
- };
-
return (
-
- }
- toolbar={(
-
+ {({ i18n }) => (
+
+ {({ title, bodyText, variant = 'info', clearRootDialogMessage }) => (
+
+ {(title || bodyText) && (
+ {i18n._(t`Close`)}
+ ]}
+ >
+ {bodyText}
+
+ )}
+ }
+ toolbar={(
+
+ )}
+ />
+ )}
+ sidebar={(
+
+
+ {routeGroups.map(({ groupId, groupTitle, routes }) => (
+
+ ))}
+
+
+ )}
+ />
+ )}
+ >
+ {render && render({ routeGroups })}
+
+
- )}
- />
- )}
- sidebar={(
-
-
- {routeGroups.map(({ groupId, groupTitle, routes }) => (
-
- ))}
-
-
- )}
- />
- )}
- >
-
- {render && render({ routeGroups })}
-
-
-
-
+
+ )}
+
+ )}
+
);
}
}
-export default App;
+export default withNetwork(App);
diff --git a/src/RootProvider.jsx b/src/RootProvider.jsx
new file mode 100644
index 0000000000..7a868e63a3
--- /dev/null
+++ b/src/RootProvider.jsx
@@ -0,0 +1,44 @@
+import React, { Component } from 'react';
+import {
+ I18nProvider,
+} from '@lingui/react';
+
+import { NetworkProvider } from './contexts/Network';
+import { RootDialogProvider } from './contexts/RootDialog';
+import { ConfigProvider } from './contexts/Config';
+
+import ja from '../build/locales/ja/messages';
+import en from '../build/locales/en/messages';
+
+export function getLanguage (nav) {
+ const language = (nav.languages && nav.languages[0]) || nav.language || nav.userLanguage;
+ const languageWithoutRegionCode = language.toLowerCase().split(/[_-]+/)[0];
+
+ return languageWithoutRegionCode;
+}
+
+class RootProvider extends Component {
+ render () {
+ const { children } = this.props;
+
+ const catalogs = { en, ja };
+ const language = getLanguage(navigator);
+
+ return (
+
+
+
+
+ {children}
+
+
+
+
+ );
+ }
+}
+
+export default RootProvider;
diff --git a/src/context.jsx b/src/context.jsx
deleted file mode 100644
index ab413dd313..0000000000
--- a/src/context.jsx
+++ /dev/null
@@ -1,4 +0,0 @@
-import React from 'react';
-
-// eslint-disable-next-line import/prefer-default-export
-export const ConfigContext = React.createContext({});
diff --git a/src/contexts/Config.jsx b/src/contexts/Config.jsx
new file mode 100644
index 0000000000..6129f80413
--- /dev/null
+++ b/src/contexts/Config.jsx
@@ -0,0 +1,89 @@
+
+import React, { Component } from 'react';
+
+import { withNetwork } from './Network';
+
+const ConfigContext = React.createContext({});
+
+class provider extends Component {
+ constructor (props) {
+ super(props);
+
+ this.state = {
+ value: {
+ ansible_version: null,
+ custom_virtualenvs: null,
+ version: null,
+ custom_logo: null,
+ custom_login_info: null
+ }
+ };
+
+ this.fetchConfig = this.fetchConfig.bind(this);
+ }
+
+ componentDidMount () {
+ this.fetchConfig();
+ }
+
+ async fetchConfig () {
+ const { api, handleHttpError } = this.props;
+
+ try {
+ const {
+ data: {
+ ansible_version,
+ custom_virtualenvs,
+ version
+ }
+ } = await api.getConfig();
+ const {
+ data: {
+ custom_logo,
+ custom_login_info
+ }
+ } = await api.getRoot();
+ this.setState({
+ value: {
+ ansible_version,
+ custom_virtualenvs,
+ version,
+ custom_logo,
+ custom_login_info
+ }
+ });
+ } catch (err) {
+ handleHttpError(err) || this.setState({
+ value: {
+ ansible_version: null,
+ custom_virtualenvs: null,
+ version: null,
+ custom_logo: null,
+ custom_login_info: null
+ }
+ });
+ }
+ }
+
+ render () {
+ const {
+ value
+ } = this.state;
+
+ const { children } = this.props;
+
+ return (
+
+ {children}
+
+ );
+ }
+}
+
+export const ConfigProvider = withNetwork(provider);
+
+export const Config = ({ children }) => (
+
+ {value => children(value)}
+
+);
diff --git a/src/contexts/Network.jsx b/src/contexts/Network.jsx
new file mode 100644
index 0000000000..c00d5b2895
--- /dev/null
+++ b/src/contexts/Network.jsx
@@ -0,0 +1,84 @@
+
+import axios from 'axios';
+import React, { Component } from 'react';
+
+import { withRouter } from 'react-router-dom';
+
+import { withRootDialog } from './RootDialog';
+
+import APIClient from '../api';
+
+const NetworkContext = React.createContext({});
+
+class prov extends Component {
+ constructor (props) {
+ super(props);
+
+ this.state = {
+ value: {
+ api: new APIClient(axios.create({ xsrfCookieName: 'csrftoken', xsrfHeaderName: 'X-CSRFToken' })),
+ handleHttpError: err => {
+ if (err.response.status === 401) {
+ this.handle401();
+ } else if (err.response.status === 404) {
+ this.handle404();
+ }
+ return (err.response.status === 401 || err.response.status === 404);
+ }
+ }
+ };
+ }
+
+ handle401 () {
+ const { handle401, history, setRootDialogMessage } = this.props;
+ if (handle401) {
+ handle401();
+ return;
+ }
+ history.replace('/login');
+ setRootDialogMessage({
+ bodyText: 'You have been logged out.',
+ });
+ }
+
+ handle404 () {
+ const { handle404, history, setRootDialogMessage } = this.props;
+ if (handle404) {
+ handle404();
+ return;
+ }
+ history.replace('/home');
+ setRootDialogMessage({
+ title: '404',
+ bodyText: 'Cannot find resource.',
+ variant: 'warning'
+ });
+ }
+
+ render () {
+ const {
+ children
+ } = this.props;
+
+ const {
+ value
+ } = this.state;
+
+ return (
+
+ {children}
+
+ );
+ }
+}
+
+export const NetworkProvider = withRootDialog(withRouter(prov));
+
+export function withNetwork (Child) {
+ return (props) => (
+
+ {context => }
+
+ );
+}
+
diff --git a/src/contexts/RootDialog.jsx b/src/contexts/RootDialog.jsx
new file mode 100644
index 0000000000..089c59087f
--- /dev/null
+++ b/src/contexts/RootDialog.jsx
@@ -0,0 +1,53 @@
+import React, { Component } from 'react';
+
+const RootDialogContext = React.createContext({});
+
+export class RootDialogProvider extends Component {
+ constructor (props) {
+ super(props);
+
+ this.state = {
+ value: {
+ title: null,
+ setRootDialogMessage: ({ title, bodyText, variant }) => {
+ const { value } = this.state;
+ this.setState({ value: { ...value, title, bodyText, variant } });
+ },
+ clearRootDialogMessage: () => {
+ const { value } = this.state;
+ this.setState({ value: { ...value, title: null, bodyText: null, variant: null } });
+ }
+ }
+ };
+ }
+
+ render () {
+ const {
+ children
+ } = this.props;
+
+ const {
+ value
+ } = this.state;
+
+ return (
+
+ {children}
+
+ );
+ }
+}
+
+export const RootDialog = ({ children }) => (
+
+ {value => children(value)}
+
+);
+
+export function withRootDialog (Child) {
+ return (props) => (
+
+ {context => }
+
+ );
+}
diff --git a/src/index.jsx b/src/index.jsx
index d3dd6407e6..9f542327a5 100644
--- a/src/index.jsx
+++ b/src/index.jsx
@@ -1,15 +1,13 @@
-import axios from 'axios';
import React from 'react';
import ReactDOM from 'react-dom';
import {
HashRouter,
- Redirect,
Route,
Switch,
+ Redirect
} from 'react-router-dom';
import {
- I18n,
- I18nProvider,
+ I18n
} from '@lingui/react';
import { t } from '@lingui/macro';
@@ -19,10 +17,13 @@ import './components/Pagination/styles.scss';
import './components/DataListToolbar/styles.scss';
import './components/SelectedList/styles.scss';
-import APIClient from './api';
+import { Config } from './contexts/Config';
-import App from './App';
import Background from './components/Background';
+
+import RootProvider from './RootProvider';
+import App from './App';
+
import Applications from './pages/Applications';
import Credentials from './pages/Credentials';
import CredentialTypes from './pages/CredentialTypes';
@@ -46,242 +47,201 @@ import License from './pages/License';
import Teams from './pages/Teams';
import Templates from './pages/Templates';
import Users from './pages/Users';
-import ja from '../build/locales/ja/messages';
-import en from '../build/locales/en/messages';
-
-//
-// Initialize http
-//
-
-const http = axios.create({ xsrfCookieName: 'csrftoken', xsrfHeaderName: 'X-CSRFToken' });
-
-//
-// Derive the language and region from global user agent data. Example: es-US
-// see: https://developer.mozilla.org/en-US/docs/Web/API/Navigator
-//
-
-export function getLanguage (nav) {
- const language = (nav.languages && nav.languages[0]) || nav.language || nav.userLanguage;
- const languageWithoutRegionCode = language.toLowerCase().split(/[_-]+/)[0];
-
- return languageWithoutRegionCode;
-}
-
-//
-// Function Main
-//
-
-export async function main (render, api) {
- const catalogs = { en, ja };
- const language = getLanguage(navigator);
+// eslint-disable-next-line import/prefer-default-export
+export async function main (render) {
const el = document.getElementById('app');
- const { data: { custom_logo, custom_login_info } } = await api.getRoot();
-
- const defaultRedirect = () => ();
- const loginRoutes = (
-
- (
-
- )}
- />
-
-
- );
return render(
-
+
{({ i18n }) => (
- {!api.isAuthenticated() ? loginRoutes : (
-
-
-
- (
- (
- routeGroups
- .reduce((allRoutes, { routes }) => allRoutes.concat(routes), [])
- .map(({ component: PageComponent, path }) => (
- (
-
- )}
- />
- ))
- )}
- />
- )}
- />
-
- )}
+
+ (
+
+ {({ custom_logo, custom_login_info }) => (
+
+ )}
+
+ )}
+ />
+ } />
+ (
+ (
+ routeGroups
+ .reduce((allRoutes, { routes }) => allRoutes.concat(routes), [])
+ .map(({ component: PageComponent, path }) => (
+ (
+
+ )}
+ />
+ ))
+ )}
+ />
+ )}
+ />
+
)}
-
+
, el
);
}
-main(ReactDOM.render, new APIClient(http));
+main(ReactDOM.render);