add params for component routing

This commit is contained in:
Jake McDermott
2019-01-02 01:11:13 -05:00
parent 8bd85193ab
commit 18505b35b8
5 changed files with 337 additions and 207 deletions

View File

@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { MemoryRouter } from 'react-router-dom'; import { HashRouter as Router } from 'react-router-dom';
import { shallow, mount } from 'enzyme'; import { shallow, mount } from 'enzyme';
import App from '../src/App'; import App from '../src/App';
import api from '../src/api'; import api from '../src/api';
@@ -7,6 +7,16 @@ import { API_LOGOUT, API_CONFIG } from '../src/endpoints';
import Dashboard from '../src/pages/Dashboard'; import Dashboard from '../src/pages/Dashboard';
import Login from '../src/pages/Login'; import Login from '../src/pages/Login';
import { asyncFlush } from '../jest.setup';
const DEFAULT_ACTIVE_GROUP = 'views_group';
const DEFAULT_ACTIVE_ITEM = 'views_group_dashboard';
const routeGroups = [{
groupId: DEFAULT_ACTIVE_GROUP,
title: 'test',
routes: [{ path: '/home', title: 'Dashboard', component: Dashboard }],
}];
describe('<App />', () => { describe('<App />', () => {
test('renders without crashing', () => { test('renders without crashing', () => {
@@ -18,7 +28,7 @@ describe('<App />', () => {
api.isAuthenticated = jest.fn(); api.isAuthenticated = jest.fn();
api.isAuthenticated.mockReturnValue(false); api.isAuthenticated.mockReturnValue(false);
const appWrapper = mount(<MemoryRouter><App /></MemoryRouter>); const appWrapper = mount(<Router><App /></Router>);
const login = appWrapper.find(Login); const login = appWrapper.find(Login);
expect(login.length).toBe(1); expect(login.length).toBe(1);
@@ -30,7 +40,7 @@ describe('<App />', () => {
api.isAuthenticated = jest.fn(); api.isAuthenticated = jest.fn();
api.isAuthenticated.mockReturnValue(true); api.isAuthenticated.mockReturnValue(true);
const appWrapper = mount(<MemoryRouter><App /></MemoryRouter>); const appWrapper = mount(<Router><App routeGroups={routeGroups} /></Router>);
const dashboard = appWrapper.find(Dashboard); const dashboard = appWrapper.find(Dashboard);
expect(dashboard.length).toBe(1); expect(dashboard.length).toBe(1);
@@ -39,21 +49,30 @@ describe('<App />', () => {
}); });
test('onNavToggle sets state.isNavOpen to opposite', () => { test('onNavToggle sets state.isNavOpen to opposite', () => {
const appWrapper = shallow(<App.WrappedComponent />); const appWrapper = shallow(<App />);
expect(appWrapper.state().isNavOpen).toBe(true); expect(appWrapper.state().isNavOpen).toBe(true);
appWrapper.instance().onNavToggle(); appWrapper.instance().onNavToggle();
expect(appWrapper.state().isNavOpen).toBe(false); expect(appWrapper.state().isNavOpen).toBe(false);
}); });
test('onLogoClick sets selected nav back to defaults', () => {
const appWrapper = shallow(<App />);
appWrapper.setState({ activeGroup: 'foo', activeItem: 'bar' });
expect(appWrapper.state().activeItem).toBe('bar');
expect(appWrapper.state().activeGroup).toBe('foo');
appWrapper.instance().onLogoClick();
expect(appWrapper.state().activeGroup).toBe(DEFAULT_ACTIVE_GROUP);
});
test('api.logout called from logout button', async () => { test('api.logout called from logout button', async () => {
const logOutButtonSelector = 'button[aria-label="Logout"]';
api.get = jest.fn().mockImplementation(() => Promise.resolve({})); api.get = jest.fn().mockImplementation(() => Promise.resolve({}));
const appWrapper = mount(<MemoryRouter><App /></MemoryRouter>); const appWrapper = shallow(<App />);
const logOutButton = appWrapper.find(logOutButtonSelector); appWrapper.instance().onDevLogout();
expect(logOutButton.length).toBe(1);
logOutButton.simulate('click');
appWrapper.setState({ activeGroup: 'foo', activeItem: 'bar' }); appWrapper.setState({ activeGroup: 'foo', activeItem: 'bar' });
expect(api.get).toHaveBeenCalledWith(API_LOGOUT); expect(api.get).toHaveBeenCalledWith(API_LOGOUT);
await asyncFlush();
expect(appWrapper.state().activeItem).toBe(DEFAULT_ACTIVE_ITEM);
expect(appWrapper.state().activeGroup).toBe(DEFAULT_ACTIVE_GROUP);
}); });
test('Componenet makes REST call to API_CONFIG endpoint when mounted', () => { test('Componenet makes REST call to API_CONFIG endpoint when mounted', () => {

View File

@@ -3,7 +3,7 @@ import ReactDOM from 'react-dom';
import api from '../src/api'; import api from '../src/api';
import indexToRender from '../src/index'; import { main } from '../src/index';
const custom_logo = (<div>logo</div>); const custom_logo = (<div>logo</div>);
const custom_login_info = 'custom login info'; const custom_login_info = 'custom login info';
@@ -15,7 +15,7 @@ describe('index.jsx', () => {
api.getRoot = jest.fn().mockImplementation(() => Promise api.getRoot = jest.fn().mockImplementation(() => Promise
.resolve({ data: { custom_logo, custom_login_info } })); .resolve({ data: { custom_logo, custom_login_info } }));
await indexToRender(); await main();
expect(ReactDOM.render).toHaveBeenCalled(); expect(ReactDOM.render).toHaveBeenCalled();
}); });

View File

@@ -1,14 +1,12 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import { ConfigContext } from './context'; import { ConfigContext } from './context';
import { I18nProvider, I18n } from '@lingui/react';
import { t } from '@lingui/macro';
import { import {
HashRouter as Router,
Redirect, Redirect,
Switch, Switch,
withRouter withRouter
} from 'react-router-dom'; } from 'react-router-dom';
import { import {
BackgroundImage, BackgroundImage,
BackgroundImageSrc, BackgroundImageSrc,
@@ -22,64 +20,56 @@ import {
ToolbarItem ToolbarItem
} from '@patternfly/react-core'; } from '@patternfly/react-core';
import { global_breakpoint_md as breakpointMd } from '@patternfly/react-tokens'; import { global_breakpoint_md as breakpointMd } from '@patternfly/react-tokens';
import { I18nProvider, I18n } from '@lingui/react';
import { t } from '@lingui/macro';
import api from './api'; import api from './api';
import { API_LOGOUT, API_CONFIG } from './endpoints'; import { API_LOGOUT, API_CONFIG } from './endpoints';
import ja from '../build/locales/ja/messages';
import en from '../build/locales/en/messages';
import Login from './pages/Login';
import HelpDropdown from './components/HelpDropdown'; import HelpDropdown from './components/HelpDropdown';
import LogoutButton from './components/LogoutButton'; import LogoutButton from './components/LogoutButton';
import TowerLogo from './components/TowerLogo'; import TowerLogo from './components/TowerLogo';
import ConditionalRedirect from './components/ConditionalRedirect'; import ConditionalRedirect from './components/ConditionalRedirect';
import NavExpandableGroup from './components/NavExpandableGroup'; import NavExpandableGroup from './components/NavExpandableGroup';
import Applications from './pages/Applications';
import Credentials from './pages/Credentials';
import CredentialTypes from './pages/CredentialTypes';
import Dashboard from './pages/Dashboard';
import InstanceGroups from './pages/InstanceGroups';
import Inventories from './pages/Inventories';
import InventoryScripts from './pages/InventoryScripts';
import Jobs from './pages/Jobs';
import Login from './pages/Login';
import ManagementJobs from './pages/ManagementJobs';
import NotificationTemplates from './pages/NotificationTemplates';
import Organizations from './pages/Organizations';
import Portal from './pages/Portal';
import Projects from './pages/Projects';
import Schedules from './pages/Schedules';
import AuthSettings from './pages/AuthSettings';
import JobsSettings from './pages/JobsSettings';
import SystemSettings from './pages/SystemSettings';
import UISettings from './pages/UISettings';
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';
const catalogs = { en, ja }; const catalogs = { en, ja };
// Derive the language and the region from global user agent data. Example: es-US
// This spits out the language and the region. Example: es-US // https://developer.mozilla.org/en-US/docs/Web/API/Navigator
const language = (navigator.languages && navigator.languages[0]) const language = (navigator.languages && navigator.languages[0])
|| navigator.language || navigator.language
|| navigator.userLanguage; || navigator.userLanguage;
const languageWithoutRegionCode = language.toLowerCase().split(/[_-]+/)[0]; const languageWithoutRegionCode = language.toLowerCase().split(/[_-]+/)[0];
// define background src image config
const backgroundConfig = {
[BackgroundImageSrc.lg]: '/assets/images/pfbg_1200.jpg',
[BackgroundImageSrc.md]: '/assets/images/pfbg_992.jpg',
[BackgroundImageSrc.md2x]: '/assets/images/pfbg_992@2x.jpg',
[BackgroundImageSrc.sm]: '/assets/images/pfbg_768.jpg',
[BackgroundImageSrc.sm2x]: '/assets/images/pfbg_768@2x.jpg',
[BackgroundImageSrc.xl]: '/assets/images/pfbg_2000.jpg',
[BackgroundImageSrc.xs]: '/assets/images/pfbg_576.jpg',
[BackgroundImageSrc.xs2x]: '/assets/images/pfbg_576@2x.jpg',
[BackgroundImageSrc.filter]: '/assets/images/background-filter.svg'
};
class App extends React.Component { class App extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
const isNavOpen = typeof window !== 'undefined' && window.innerWidth >= parseInt(breakpointMd.value, 10); const isNavOpen = typeof window !== 'undefined'
&& window.innerWidth >= parseInt(breakpointMd.value, 10);
this.state = { this.state = {
isNavOpen, isNavOpen,
config: {}, config: {},
error: false, error: false,
}; };
} };
onNavToggle = () => { onNavToggle = () => {
this.setState(({ isNavOpen }) => ({ isNavOpen: !isNavOpen })); this.setState(({ isNavOpen }) => ({ isNavOpen: !isNavOpen }));
@@ -104,158 +94,102 @@ class App extends React.Component {
} }
} }
render() { render () {
const { isNavOpen, config } = this.state; const { isNavOpen } = this.state;
const { logo, loginInfo, history } = this.props; const { logo, loginInfo, history, routeConfig = [] } = this.props;
// extract a flattened array of all routes from the provided route config
const PageToolbar = ( const allRoutes = routeConfig.reduce((flattened, { routes }) => flattened.concat(routes), []);
<Toolbar>
<ToolbarGroup>
<ToolbarItem>
<HelpDropdown />
</ToolbarItem>
<ToolbarItem>
<LogoutButton onDevLogout={() => this.onDevLogout()} />
</ToolbarItem>
</ToolbarGroup>
</Toolbar>
);
return ( return (
<I18nProvider language={languageWithoutRegionCode} catalogs={catalogs}> <Router>
<Fragment> <I18nProvider
<BackgroundImage language={languageWithoutRegionCode}
src={{ catalogs={catalogs}
[BackgroundImageSrc.lg]: '/assets/images/pfbg_1200.jpg', >
[BackgroundImageSrc.md]: '/assets/images/pfbg_992.jpg', <I18n>
[BackgroundImageSrc.md2x]: '/assets/images/pfbg_992@2x.jpg', {({ i18n }) => (
[BackgroundImageSrc.sm]: '/assets/images/pfbg_768.jpg',
[BackgroundImageSrc.sm2x]: '/assets/images/pfbg_768@2x.jpg',
[BackgroundImageSrc.xl]: '/assets/images/pfbg_2000.jpg',
[BackgroundImageSrc.xs]: '/assets/images/pfbg_576.jpg',
[BackgroundImageSrc.xs2x]: '/assets/images/pfbg_576@2x.jpg',
[BackgroundImageSrc.filter]: '/assets/images/background-filter.svg'
}}
/>
<ConfigContext.Provider value={config}>
<Switch>
<ConditionalRedirect
shouldRedirect={() => api.isAuthenticated()}
redirectPath="/"
path="/login"
component={() => <Login logo={logo} loginInfo={loginInfo} />}
/>
<Fragment> <Fragment>
<Page <ConfigContext.Provider>
header={( <BackgroundImage src={backgroundConfig} />
<PageHeader <Switch>
logo={<TowerLogo onClick={this.onLogoClick} />} <ConditionalRedirect
toolbar={PageToolbar} shouldRedirect={() => api.isAuthenticated()}
showNavToggle redirectPath="/"
onNavToggle={this.onNavToggle} path="/login"
component={() => <Login logo={logo} loginInfo={loginInfo} />}
/> />
)} <Fragment>
sidebar={( <Page
<PageSidebar usecondensed="True"
isNavOpen={isNavOpen} header={(
nav={( <PageHeader
<I18n> logo={<TowerLogo onClick={this.onLogoClick} />}
{({ i18n }) => ( toolbar={(
<Nav aria-label={i18n._(t`Primary Navigation`)}> <Toolbar>
<NavList> <ToolbarGroup>
<NavExpandableGroup <ToolbarItem>
groupId="views_group" <HelpDropdown />
title={i18n._("Views")} </ToolbarItem>
routes={[ <ToolbarItem>
{ path: '/home', title: i18n._('Dashboard') }, <LogoutButton onDevLogout={() => this.onDevLogout()} />
{ path: '/jobs', title: i18n._('Jobs') }, </ToolbarItem>
{ path: '/schedules', title: i18n._('Schedules') }, </ToolbarGroup>
{ path: '/portal', title: i18n._('Portal Mode') }, </Toolbar>
]} )}
/> showNavToggle
<NavExpandableGroup onNavToggle={this.onNavToggle}
groupId="resources_group" />
title={i18n._("Resources")} )}
routes={[ sidebar={(
{ path: '/templates', title: i18n._('Templates') }, <PageSidebar
{ path: '/credentials', title: i18n._('Credentials') }, isNavOpen={isNavOpen}
{ path: '/projects', title: i18n._('Projects') }, nav={(
{ path: '/inventories', title: i18n._('Inventories') }, <Nav aria-label={i18n._("Primary Navigation")}>
{ path: '/inventory_scripts', title: i18n._('Inventory Scripts') } <NavList>
]} { routeConfig.map(({ groupId, routes, title }) => (
/> <NavExpandableGroup
<NavExpandableGroup key={groupId}
groupId="access_group" groupId={groupId}
title={i18n._("Access")} title={i18n._(title)}
routes={[ routes={routes.map(route => ({
{ path: '/organizations', title: i18n._('Organizations') }, path: route.path,
{ path: '/users', title: i18n._('Users') }, component: route.component,
{ path: '/teams', title: i18n._('Teams') } title: i18n._(route.title)
]} }))}
/> />
<NavExpandableGroup ))}
groupId="administration_group" </NavList>
title={i18n._("Administration")} </Nav>
routes={[ )}
{ path: '/credential_types', title: i18n._('Credential Types') }, />
{ path: '/notification_templates', title: i18n._('Notifications') }, )}
{ path: '/management_jobs', title: i18n._('Management Jobs') }, >
{ path: '/instance_groups', title: i18n._('Instance Groups') }, <ConditionalRedirect
{ path: '/applications', title: i18n._('Integrations') } shouldRedirect={() => !api.isAuthenticated()}
]} redirectPath="/login"
/> exact path="/"
<NavExpandableGroup component={() => (<Redirect to="/home" />)}
groupId="settings_group" />
title={i18n._("Settings")} { allRoutes.map(({ component, path }) => (
routes={[ <ConditionalRedirect
{ path: '/auth_settings', title: i18n._('Authentication') }, key={path}
{ path: '/jobs_settings', title: i18n._('Jobs') }, path={path}
{ path: '/system_settings', title: i18n._('System') }, redirectPath="/login"
{ path: '/ui_settings', title: i18n._('User Interface') }, shouldRedirect={() => !api.isAuthenticated()}
{ path: '/license', title: i18n._('License') } component={component}
]} />
/> ))}
</NavList> </Page>
</Nav> </Fragment>
)} </Switch>
</I18n> </ConfigContext.Provider>
)}
/>
)}
useCondensed
>
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" exact path="/" component={() => (<Redirect to="/home" />)} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/home" component={Dashboard} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/jobs" component={Jobs} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/schedules" component={Schedules} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/portal" component={Portal} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/templates" component={Templates} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/credentials" component={Credentials} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/projects" component={Projects} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/inventories" component={Inventories} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/inventory_scripts" component={InventoryScripts} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/organizations" component={Organizations} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/users" component={Users} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/teams" component={Teams} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/credential_types" component={CredentialTypes} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/notification_templates" component={NotificationTemplates} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/management_jobs" component={ManagementJobs} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/instance_groups" component={InstanceGroups} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/applications" component={Applications} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/auth_settings" component={AuthSettings} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/jobs_settings" component={JobsSettings} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/system_settings" component={SystemSettings} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/ui_settings" component={UISettings} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/license" component={License} />
</Page>
</Fragment> </Fragment>
</Switch> )}
</ConfigContext.Provider> </I18n>
</Fragment> </I18nProvider>
</I18nProvider> </Router>
); );
} }
} }
export default withRouter(App); export default App;

View File

@@ -1,6 +1,10 @@
import axios from 'axios'; import axios from 'axios';
import * as endpoints from './endpoints'; import {
API_CONFIG,
API_LOGIN,
API_ROOT,
} from './endpoints';
const CSRF_COOKIE_NAME = 'csrftoken'; const CSRF_COOKIE_NAME = 'csrftoken';
const CSRF_HEADER_NAME = 'X-CSRFToken'; const CSRF_HEADER_NAME = 'X-CSRFToken';
@@ -32,7 +36,11 @@ class APIClient {
return authenticated; return authenticated;
} }
async login (username, password, redirect = endpoints.API_CONFIG) { getRoot () {
return this.http.get(API_ROOT);
}
async login (username, password, redirect = API_CONFIG) {
const un = encodeURIComponent(username); const un = encodeURIComponent(username);
const pw = encodeURIComponent(password); const pw = encodeURIComponent(password);
const next = encodeURIComponent(redirect); const next = encodeURIComponent(redirect);
@@ -40,8 +48,8 @@ class APIClient {
const data = `username=${un}&password=${pw}&next=${next}`; const data = `username=${un}&password=${pw}&next=${next}`;
const headers = { 'Content-Type': LOGIN_CONTENT_TYPE }; const headers = { 'Content-Type': LOGIN_CONTENT_TYPE };
await this.http.get(endpoints.API_LOGIN, { headers }); await this.http.get(API_LOGIN, { headers });
await this.http.post(endpoints.API_LOGIN, data, { headers }); await this.http.post(API_LOGIN, data, { headers });
} }
get = (endpoint, params = {}) => this.http.get(endpoint, { params }); get = (endpoint, params = {}) => this.http.get(endpoint, { params });

View File

@@ -1,12 +1,8 @@
import React from 'react'; import React from 'react';
import { render } from 'react-dom'; import { render } from 'react-dom';
import {
HashRouter as Router
} from 'react-router-dom';
import App from './App'; import App from './App';
import api from './api'; import api from './api';
import { API_ROOT } from './endpoints';
import '@patternfly/react-core/dist/styles/base.css'; import '@patternfly/react-core/dist/styles/base.css';
import '@patternfly/patternfly-next/patternfly.css'; import '@patternfly/patternfly-next/patternfly.css';
@@ -15,13 +11,186 @@ import './app.scss';
import './components/Pagination/styles.scss'; import './components/Pagination/styles.scss';
import './components/DataListToolbar/styles.scss'; import './components/DataListToolbar/styles.scss';
const el = document.getElementById('app'); import Applications from './pages/Applications';
import Credentials from './pages/Credentials';
import CredentialTypes from './pages/CredentialTypes';
import Dashboard from './pages/Dashboard';
import InstanceGroups from './pages/InstanceGroups';
import Inventories from './pages/Inventories';
import InventoryScripts from './pages/InventoryScripts';
import Jobs from './pages/Jobs';
import Login from './pages/Login';
import ManagementJobs from './pages/ManagementJobs';
import NotificationTemplates from './pages/NotificationTemplates';
import Organizations from './pages/Organizations';
import Portal from './pages/Portal';
import Projects from './pages/Projects';
import Schedules from './pages/Schedules';
import AuthSettings from './pages/AuthSettings';
import JobsSettings from './pages/JobsSettings';
import SystemSettings from './pages/SystemSettings';
import UISettings from './pages/UISettings';
import License from './pages/License';
import Teams from './pages/Teams';
import Templates from './pages/Templates';
import Users from './pages/Users';
const main = async () => { const routeGroups = [
const { custom_logo, custom_login_info } = await api.get(API_ROOT); {
render(<Router><App logo={custom_logo} loginInfo={custom_login_info} /></Router>, el); groupId: 'views_group',
title: 'Views',
routes: [
{
path: '/home',
title: 'Dashboard',
component: Dashboard
},
{
path: '/jobs',
title: 'Jobs',
component: Jobs
},
{
path: '/schedules',
title: 'Schedules',
component: Schedules
},
{
path: '/portal',
title: 'Portal Mode',
component: Portal
},
],
},
{
groupId: 'resources_group',
title: "Resources",
routes: [
{
path: '/templates',
title: 'Templates',
component: Templates
},
{
path: '/credentials',
title: 'Credentials',
component: Credentials
},
{
path: '/projects',
title: 'Projects',
component: Projects
},
{
path: '/inventories',
title: 'Inventories',
component: Inventories
},
{
path: '/inventory_scripts',
title: 'Inventory Scripts',
component: InventoryScripts
},
],
},
{
groupId: 'access_group',
title: 'Access',
routes: [
{
path: '/organizations',
title: 'Organizations',
component: Organizations
},
{
path: '/users',
title: 'Users',
component: Users
},
{
path: '/teams',
title: 'Teams',
component: Teams
},
],
},
{
groupId: 'administration_group',
title: 'Administration',
routes: [
{
path: '/credential_types',
title: 'Credential Types',
component: CredentialTypes
},
{
path: '/notification_templates',
title: 'Notifications',
component: NotificationTemplates
},
{
path: '/management_jobs',
title: 'Management Jobs',
component: ManagementJobs
},
{
path: '/instance_groups',
title: 'Instance Groups',
component: InstanceGroups
},
{
path: '/applications',
title: 'Integrations',
component: Applications
},
],
},
{
groupId: 'settings_group',
title: 'Settings',
routes: [
{
path: '/auth_settings',
title: 'Authentication',
component: AuthSettings
},
{
path: '/jobs_settings',
title: 'Jobs',
component: JobsSettings
},
{
path: '/system_settings',
title: 'System',
component: SystemSettings
},
{
path: '/ui_settings',
title: 'User Interface',
component: UISettings
},
{
path: '/license',
title: 'License',
component: License
},
],
},
];
export async function main () {
const el = document.getElementById('app');
// fetch additional config from server
const { data } = await api.getRoot();
const { custom_logo, custom_login_info } = data;
render(
<App
logo={custom_logo}
loginInfo={custom_login_info}
routeGroups={routeGroups}
/>, el);
}; };
main(); export default main();
export default main;