mirror of
https://github.com/ansible/awx.git
synced 2026-05-23 08:37:48 -02:30
add RootDialog and Network contexts, update app bootstrapping
This commit is contained in:
71
src/App.jsx
71
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;
|
||||
|
||||
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,14 +76,25 @@ class App extends Component {
|
||||
navLabel = '',
|
||||
} = this.props;
|
||||
|
||||
const config = {
|
||||
ansible_version,
|
||||
custom_virtualenvs,
|
||||
version,
|
||||
};
|
||||
|
||||
return (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<RootDialog>
|
||||
{({ title, bodyText, variant = 'info', clearRootDialogMessage }) => (
|
||||
<Fragment>
|
||||
{(title || bodyText) && (
|
||||
<AlertModal
|
||||
variant={variant}
|
||||
isOpen={!!(title || bodyText)}
|
||||
onClose={clearRootDialogMessage}
|
||||
title={title}
|
||||
actions={[
|
||||
<Button key="close" variant="secondary" onClick={clearRootDialogMessage}>{i18n._(t`Close`)}</Button>
|
||||
]}
|
||||
>
|
||||
{bodyText}
|
||||
</AlertModal>
|
||||
)}
|
||||
<Page
|
||||
usecondensed="True"
|
||||
header={(
|
||||
@@ -130,9 +131,7 @@ class App extends Component {
|
||||
/>
|
||||
)}
|
||||
>
|
||||
<ConfigContext.Provider value={config}>
|
||||
{render && render({ routeGroups })}
|
||||
</ConfigContext.Provider>
|
||||
</Page>
|
||||
<About
|
||||
ansible_version={ansible_version}
|
||||
@@ -141,8 +140,12 @@ class App extends Component {
|
||||
onClose={this.onAboutModalClose}
|
||||
/>
|
||||
</Fragment>
|
||||
)}
|
||||
</RootDialog>
|
||||
)}
|
||||
</I18n>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default App;
|
||||
export default withNetwork(App);
|
||||
|
||||
44
src/RootProvider.jsx
Normal file
44
src/RootProvider.jsx
Normal file
@@ -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 (
|
||||
<I18nProvider
|
||||
language={language}
|
||||
catalogs={catalogs}
|
||||
>
|
||||
<RootDialogProvider>
|
||||
<NetworkProvider>
|
||||
<ConfigProvider>
|
||||
{children}
|
||||
</ConfigProvider>
|
||||
</NetworkProvider>
|
||||
</RootDialogProvider>
|
||||
</I18nProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default RootProvider;
|
||||
@@ -1,4 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export const ConfigContext = React.createContext({});
|
||||
89
src/contexts/Config.jsx
Normal file
89
src/contexts/Config.jsx
Normal file
@@ -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 (
|
||||
<ConfigContext.Provider value={value}>
|
||||
{children}
|
||||
</ConfigContext.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const ConfigProvider = withNetwork(provider);
|
||||
|
||||
export const Config = ({ children }) => (
|
||||
<ConfigContext.Consumer>
|
||||
{value => children(value)}
|
||||
</ConfigContext.Consumer>
|
||||
);
|
||||
84
src/contexts/Network.jsx
Normal file
84
src/contexts/Network.jsx
Normal file
@@ -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 (
|
||||
<NetworkContext.Provider value={value}>
|
||||
{children}
|
||||
</NetworkContext.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const NetworkProvider = withRootDialog(withRouter(prov));
|
||||
|
||||
export function withNetwork (Child) {
|
||||
return (props) => (
|
||||
<NetworkContext.Consumer>
|
||||
{context => <Child {...props} {...context} />}
|
||||
</NetworkContext.Consumer>
|
||||
);
|
||||
}
|
||||
|
||||
53
src/contexts/RootDialog.jsx
Normal file
53
src/contexts/RootDialog.jsx
Normal file
@@ -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 (
|
||||
<RootDialogContext.Provider value={value}>
|
||||
{children}
|
||||
</RootDialogContext.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const RootDialog = ({ children }) => (
|
||||
<RootDialogContext.Consumer>
|
||||
{value => children(value)}
|
||||
</RootDialogContext.Consumer>
|
||||
);
|
||||
|
||||
export function withRootDialog (Child) {
|
||||
return (props) => (
|
||||
<RootDialogContext.Consumer>
|
||||
{context => <Child {...props} {...context} />}
|
||||
</RootDialogContext.Consumer>
|
||||
);
|
||||
}
|
||||
@@ -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,72 +47,35 @@ 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 = () => (<Redirect to="/home" />);
|
||||
const loginRoutes = (
|
||||
return render(
|
||||
<HashRouter>
|
||||
<RootProvider>
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<Background>
|
||||
<Switch>
|
||||
<Route
|
||||
path="/login"
|
||||
render={() => (
|
||||
<Config>
|
||||
{({ custom_logo, custom_login_info }) => (
|
||||
<Login
|
||||
api={api}
|
||||
logo={custom_logo}
|
||||
loginInfo={custom_login_info}
|
||||
/>
|
||||
)}
|
||||
</Config>
|
||||
)}
|
||||
/>
|
||||
<Redirect to="/login" />
|
||||
</Switch>
|
||||
);
|
||||
|
||||
return render(
|
||||
<HashRouter>
|
||||
<I18nProvider
|
||||
language={language}
|
||||
catalogs={catalogs}
|
||||
>
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<Background>
|
||||
{!api.isAuthenticated() ? loginRoutes : (
|
||||
<Switch>
|
||||
<Route path="/login" render={defaultRedirect} />
|
||||
<Route exact path="/" render={defaultRedirect} />
|
||||
<Route exact path="/" render={() => <Redirect to="/home" />} />
|
||||
<Route
|
||||
render={() => (
|
||||
<App
|
||||
api={api}
|
||||
navLabel={i18n._(t`Primary Navigation`)}
|
||||
routeGroups={[
|
||||
{
|
||||
@@ -263,10 +227,7 @@ export async function main (render, api) {
|
||||
key={path}
|
||||
path={path}
|
||||
render={({ match }) => (
|
||||
<PageComponent
|
||||
api={api}
|
||||
match={match}
|
||||
/>
|
||||
<PageComponent match={match} />
|
||||
)}
|
||||
/>
|
||||
))
|
||||
@@ -275,13 +236,12 @@ export async function main (render, api) {
|
||||
)}
|
||||
/>
|
||||
</Switch>
|
||||
)}
|
||||
</Background>
|
||||
)}
|
||||
</I18n>
|
||||
</I18nProvider>
|
||||
</RootProvider>
|
||||
</HashRouter>, el
|
||||
);
|
||||
}
|
||||
|
||||
main(ReactDOM.render, new APIClient(http));
|
||||
main(ReactDOM.render);
|
||||
|
||||
Reference in New Issue
Block a user