Merge pull request #51 from mabashian/43-lingui

Add support for i18n using lingui
This commit is contained in:
Michael Abashian 2018-12-13 16:56:16 -05:00 committed by GitHub
commit b2ba863569
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
60 changed files with 2429 additions and 647 deletions

3
.gitignore vendored
View File

@ -1,2 +1,3 @@
node_modules/
coverage/
coverage/
build/locales/_build

5
.linguirc Normal file
View File

@ -0,0 +1,5 @@
{
"localeDir": "build/locales/",
"srcPathDirs": ["src/"],
"format": "po"
}

View File

@ -12,3 +12,23 @@
* visit `https://127.0.0.1:3001/`
**note:** These instructions assume you have the [awx](https://github.com/ansible/awx/blob/devel/CONTRIBUTING.md#running-the-environment) development api server up and running at `localhost:8043`.
## Unit Tests
To run the unit tests on files that you've changed:
* npm test
To run a single test (in this case the login page test):
* npm test -- __tests__/pages/Login.jsx
**note:** Once the test watcher is up and running you can hit `a` to run all the tests
## Internationalization
Internationalization leans on the [lingui](https://github.com/lingui/js-lingui) project. [Official documentation here](https://lingui.js.org/). We use this libary to mark our strings for translation. For common React use cases see [this link](https://lingui.js.org/tutorials/react-patterns.html). If you want to see this in action you'll need to take the following steps:
1) `npm run add-locale` to add the language that you want to translate to (we should only have to do this once and the commit to repo afaik). Example: `npm run add-locale en es fr` # Add English, Spanish and French locale
2) `npm run extract-strings` to create .po files for each language specified. The .po files will be placed in src/locales but this is configurable.
3) Open up the .po file for the language you want to test and add some translations. In production we would pass this .po file off to the translation team.
4) Once you've edited your .po file (or we've gotten a .po file back from the translation team) run `npm run compile-strings`. This command takes the .po files and turns them into a minified JSON object and can be seen in the `messages.js` file in each locale directory. These files get loaded at the App root level (see: App.jsx).
5) Change the language in your browser and reload the page. You should see your specified translations in place of English strings.

View File

@ -1,5 +1,6 @@
import React from 'react';
import { mount } from 'enzyme';
import { I18nProvider } from '@lingui/react';
import api from '../../src/api';
import { API_CONFIG } from '../../src/endpoints';
import About from '../../src/components/About';
@ -9,14 +10,22 @@ describe('<About />', () => {
let closeButton;
test('initially renders without crashing', () => {
aboutWrapper = mount(<About isOpen />);
aboutWrapper = mount(
<I18nProvider>
<About isOpen />
</I18nProvider>
);
expect(aboutWrapper.length).toBe(1);
aboutWrapper.unmount();
});
test('close button calls onAboutModalClose', () => {
const onAboutModalClose = jest.fn();
aboutWrapper = mount(<About isOpen onAboutModalClose={onAboutModalClose} />);
aboutWrapper = mount(
<I18nProvider>
<About isOpen onAboutModalClose={onAboutModalClose} />
</I18nProvider>
);
closeButton = aboutWrapper.find('AboutModalBoxCloseButton Button');
closeButton.simulate('click');
expect(onAboutModalClose).toBeCalled();
@ -29,9 +38,15 @@ describe('<About />', () => {
err.response = { status: 404, message: 'problem' };
return Promise.reject(err);
});
aboutWrapper = mount(<About isOpen />);
await aboutWrapper.instance().componentDidMount();
expect(aboutWrapper.state('error').response.status).toBe(404);
aboutWrapper = mount(
<I18nProvider>
<About isOpen />
</I18nProvider>
);
const aboutComponentInstance = aboutWrapper.find(About).instance();
await aboutComponentInstance.componentDidMount();
expect(aboutComponentInstance.state.error.response.status).toBe(404);
aboutWrapper.unmount();
});

View File

@ -1,5 +1,6 @@
import React from 'react';
import { mount } from 'enzyme';
import { I18nProvider } from '@lingui/react';
import DataListToolbar from '../../src/components/DataListToolbar';
describe('<DataListToolbar />', () => {
@ -15,7 +16,7 @@ describe('<DataListToolbar />', () => {
test('it triggers the expected callbacks', () => {
const search = 'button[aria-label="Search"]';
const searchTextInput = 'input[aria-label="search text input"]';
const searchTextInput = 'input[aria-label="Search text input"]';
const selectAll = 'input[aria-label="Select all"]';
const sort = 'button[aria-label="Sort"]';
@ -24,15 +25,17 @@ describe('<DataListToolbar />', () => {
const onSelectAll = jest.fn();
toolbar = mount(
<DataListToolbar
isAllSelected={false}
sortedColumnKey="name"
sortOrder="ascending"
columns={columns}
onSearch={onSearch}
onSort={onSort}
onSelectAll={onSelectAll}
/>
<I18nProvider>
<DataListToolbar
isAllSelected={false}
sortedColumnKey="name"
sortOrder="ascending"
columns={columns}
onSearch={onSearch}
onSort={onSort}
onSelectAll={onSelectAll}
/>
</I18nProvider>
);
toolbar.find(sort).simulate('click');

View File

@ -1,15 +1,22 @@
import React from 'react';
import { mount } from 'enzyme';
import { I18nProvider } from '@lingui/react';
import HelpDropdown from '../../src/components/HelpDropdown';
let questionCircleIcon;
let dropdownWrapper;
let dropdownComponentInstance;
let dropdownToggle;
let dropdownItems;
let dropdownItem;
beforeEach(() => {
dropdownWrapper = mount(<HelpDropdown />);
dropdownWrapper = mount(
<I18nProvider>
<HelpDropdown />
</I18nProvider>
);
dropdownComponentInstance = dropdownWrapper.find(HelpDropdown).instance();
});
afterEach(() => {
@ -19,14 +26,15 @@ afterEach(() => {
describe('<HelpDropdown />', () => {
test('initially renders without crashing', () => {
expect(dropdownWrapper.length).toBe(1);
expect(dropdownWrapper.state('isOpen')).toEqual(false);
expect(dropdownWrapper.state('showAboutModal')).toEqual(false);
expect(dropdownComponentInstance.state.isOpen).toEqual(false);
expect(dropdownComponentInstance.state.showAboutModal).toEqual(false);
questionCircleIcon = dropdownWrapper.find('QuestionCircleIcon');
expect(questionCircleIcon.length).toBe(1);
});
test('renders two dropdown items', () => {
dropdownWrapper.setState({ isOpen: true });
dropdownComponentInstance.setState({ isOpen: true });
dropdownWrapper.update();
dropdownItems = dropdownWrapper.find('DropdownItem');
expect(dropdownItems.length).toBe(2);
const dropdownTexts = dropdownItems.map(item => item.text());
@ -34,24 +42,27 @@ describe('<HelpDropdown />', () => {
});
test('onToggle sets state.isOpen to opposite', () => {
dropdownWrapper.setState({ isOpen: true });
dropdownComponentInstance.setState({ isOpen: true });
dropdownWrapper.update();
dropdownToggle = dropdownWrapper.find('DropdownToggle > DropdownToggle');
dropdownToggle.simulate('click');
expect(dropdownWrapper.state('isOpen')).toEqual(false);
expect(dropdownComponentInstance.state.isOpen).toEqual(false);
});
test('about dropdown item sets state.showAboutModal to true', () => {
dropdownWrapper.setState({ isOpen: true });
dropdownComponentInstance.setState({ isOpen: true });
dropdownWrapper.update();
dropdownItem = dropdownWrapper.find('DropdownItem a').at(1);
dropdownItem.simulate('click');
expect(dropdownWrapper.state('showAboutModal')).toEqual(true);
expect(dropdownComponentInstance.state.showAboutModal).toEqual(true);
});
test('onAboutModalClose sets state.showAboutModal to false', () => {
dropdownWrapper.setState({ showAboutModal: true });
dropdownComponentInstance.setState({ showAboutModal: true });
dropdownWrapper.update();
const aboutModal = dropdownWrapper.find('AboutModal');
aboutModal.find('AboutModalBoxCloseButton Button').simulate('click');
expect(dropdownWrapper.state('showAboutModal')).toEqual(false);
expect(dropdownComponentInstance.state.showAboutModal).toEqual(false);
});
});

View File

@ -1,5 +1,6 @@
import React from 'react';
import { mount } from 'enzyme';
import { I18nProvider } from '@lingui/react';
import LogoutButton from '../../src/components/LogoutButton';
let buttonWrapper;
@ -14,7 +15,11 @@ const findChildren = () => {
describe('<LogoutButton />', () => {
test('initially renders without crashing', () => {
const onDevLogout = jest.fn();
buttonWrapper = mount(<LogoutButton onDevLogout={onDevLogout} />);
buttonWrapper = mount(
<I18nProvider>
<LogoutButton onDevLogout={onDevLogout} />
</I18nProvider>
);
findChildren();
expect(buttonWrapper.length).toBe(1);
expect(buttonElem.length).toBe(1);

View File

@ -1,34 +1,35 @@
import React from 'react';
import { mount } from 'enzyme';
import { I18nProvider } from '@lingui/react';
import Pagination from '../../src/components/Pagination';
describe('<Pagination />', () => {
const noop = () => {};
let pagination;
afterEach(() => {
if (toolbar) {
if (pagination) {
pagination.unmount();
pagination = null;
}
});
test('it triggers the expected callbacks on next and last', () => {
const next = 'button[aria-label="next"]';
const last = 'button[aria-label="last"]';
const next = 'button[aria-label="Next"]';
const last = 'button[aria-label="Last"]';
const onSetPage = jest.fn();
pagination = mount(
<Pagination
count={21}
page={1}
pageCount={5}
page_size={5}
pageSizeOptions={[5, 10, 25, 50]}
onSetPage={onSetPage}
/>
<I18nProvider>
<Pagination
count={21}
page={1}
pageCount={5}
page_size={5}
pageSizeOptions={[5, 10, 25, 50]}
onSetPage={onSetPage}
/>
</I18nProvider>
);
pagination.find(next).simulate('click');
@ -43,20 +44,22 @@ describe('<Pagination />', () => {
});
test('it triggers the expected callback on previous and first', () => {
const previous = 'button[aria-label="previous"]';
const first = 'button[aria-label="first"]';
const previous = 'button[aria-label="Previous"]';
const first = 'button[aria-label="First"]';
const onSetPage = jest.fn();
pagination = mount(
<Pagination
count={21}
page={5}
pageCount={5}
page_size={5}
pageSizeOptions={[5, 10, 25, 50]}
onSetPage={onSetPage}
/>
<I18nProvider>
<Pagination
count={21}
page={5}
pageCount={5}
page_size={5}
pageSizeOptions={[5, 10, 25, 50]}
onSetPage={onSetPage}
/>
</I18nProvider>
);
pagination.find(previous).simulate('click');

View File

@ -1,6 +1,7 @@
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { mount } from 'enzyme';
import { I18nProvider } from '@lingui/react';
import TowerLogo from '../../src/components/TowerLogo';
let logoWrapper;
@ -14,7 +15,13 @@ const findChildren = () => {
describe('<TowerLogo />', () => {
test('initially renders without crashing', () => {
logoWrapper = mount(<MemoryRouter><TowerLogo /></MemoryRouter>);
logoWrapper = mount(
<MemoryRouter>
<I18nProvider>
<TowerLogo />
</I18nProvider>
</MemoryRouter>
);
findChildren();
expect(logoWrapper.length).toBe(1);
expect(towerLogoElem.length).toBe(1);
@ -23,7 +30,13 @@ describe('<TowerLogo />', () => {
test('adds navigation to route history on click', () => {
const onLogoClick = jest.fn();
logoWrapper = mount(<MemoryRouter><TowerLogo onClick={onLogoClick} /></MemoryRouter>);
logoWrapper = mount(
<MemoryRouter>
<I18nProvider>
<TowerLogo onClick={onLogoClick} />
</I18nProvider>
</MemoryRouter>
);
findChildren();
expect(towerLogoElem.props().history.length).toBe(1);
logoWrapper.simulate('click');
@ -31,7 +44,13 @@ describe('<TowerLogo />', () => {
});
test('gracefully handles not being passed click handler', () => {
logoWrapper = mount(<MemoryRouter><TowerLogo /></MemoryRouter>);
logoWrapper = mount(
<MemoryRouter>
<I18nProvider>
<TowerLogo />
</I18nProvider>
</MemoryRouter>
);
findChildren();
expect(towerLogoElem.props().history.length).toBe(1);
logoWrapper.simulate('click');
@ -40,7 +59,13 @@ describe('<TowerLogo />', () => {
test('handles mouse over and out state.hover changes', () => {
const onLogoClick = jest.fn();
logoWrapper = mount(<MemoryRouter><TowerLogo onClick={onLogoClick} /></MemoryRouter>);
logoWrapper = mount(
<MemoryRouter>
<I18nProvider>
<TowerLogo onClick={onLogoClick} />
</I18nProvider>
</MemoryRouter>
);
findChildren();
findChildren();
expect(brandElem.props().src).toBe('tower-logo-header.svg');

View File

@ -1,6 +1,7 @@
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { mount, shallow } from 'enzyme';
import { I18nProvider } from '@lingui/react';
import { asyncFlush } from '../../jest.setup';
import AtLogin from '../../src/pages/Login';
import api from '../../src/api';
@ -26,7 +27,13 @@ describe('<Login />', () => {
};
beforeEach(() => {
loginWrapper = mount(<MemoryRouter><AtLogin /></MemoryRouter>);
loginWrapper = mount(
<MemoryRouter>
<I18nProvider>
<AtLogin />
</I18nProvider>
</MemoryRouter>
);
findChildren();
});
@ -49,7 +56,13 @@ describe('<Login />', () => {
});
test('custom logo renders Brand component with correct src and alt', () => {
loginWrapper = mount(<MemoryRouter><AtLogin logo="images/foo.jpg" alt="Foo Application" /></MemoryRouter>);
loginWrapper = mount(
<MemoryRouter>
<I18nProvider>
<AtLogin logo="images/foo.jpg" alt="Foo Application" />
</I18nProvider>
</MemoryRouter>
);
findChildren();
expect(loginHeaderLogo.length).toBe(1);
expect(loginHeaderLogo.props().src).toBe('data:image/jpeg;images/foo.jpg');
@ -57,7 +70,13 @@ describe('<Login />', () => {
});
test('default logo renders Brand component with correct src and alt', () => {
loginWrapper = mount(<MemoryRouter><AtLogin /></MemoryRouter>);
loginWrapper = mount(
<MemoryRouter>
<I18nProvider>
<AtLogin />
</I18nProvider>
</MemoryRouter>
);
findChildren();
expect(loginHeaderLogo.length).toBe(1);
expect(loginHeaderLogo.props().src).toBe('tower-logo-header.svg');

View File

@ -1,17 +1,20 @@
import React from 'react';
import { mount } from 'enzyme';
import { MemoryRouter } from 'react-router-dom';
import { I18nProvider } from '@lingui/react';
import OrganizationDetail from '../../../../src/pages/Organizations/components/OrganizationDetail';
describe('<OrganizationDetail />', () => {
test('initially renders succesfully', () => {
mount(
<MemoryRouter initialEntries={['/organizations/1']} initialIndex={0}>
<OrganizationDetail
match={{ path: '/organizations/:id', url: '/organizations/1' }}
location={{ search: '', pathname: '/organizations/1' }}
/>
</MemoryRouter>
<I18nProvider>
<MemoryRouter initialEntries={['/organizations/1']} initialIndex={0}>
<OrganizationDetail
match={{ path: '/organizations/:id', url: '/organizations/1' }}
location={{ search: '', pathname: '/organizations/1' }}
/>
</MemoryRouter>
</I18nProvider>
);
});
});

View File

@ -1,14 +1,17 @@
import React from 'react';
import { mount } from 'enzyme';
import { MemoryRouter } from 'react-router-dom';
import { I18nProvider } from '@lingui/react';
import OrganizationListItem from '../../../../src/pages/Organizations/components/OrganizationListItem';
describe('<OrganizationListItem />', () => {
test('initially renders succesfully', () => {
mount(
<MemoryRouter initialEntries={['/organizations']} initialIndex={0}>
<OrganizationListItem />
</MemoryRouter>
<I18nProvider>
<MemoryRouter initialEntries={['/organizations']} initialIndex={0}>
<OrganizationListItem />
</MemoryRouter>
</I18nProvider>
);
});
});

View File

@ -1,16 +1,19 @@
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { mount } from 'enzyme';
import { I18nProvider } from '@lingui/react';
import Organizations from '../../../src/pages/Organizations/index';
describe('<Organizations />', () => {
test('initially renders succesfully', () => {
mount(
<MemoryRouter initialEntries={['/organizations']} initialIndex={0}>
<Organizations
match={{ path: '/organizations', url: '/organizations' }}
location={{ search: '', pathname: '/organizations' }}
/>
<I18nProvider>
<Organizations
match={{ path: '/organizations', url: '/organizations' }}
location={{ search: '', pathname: '/organizations' }}
/>
</I18nProvider>
</MemoryRouter>
);
});

View File

@ -1,17 +1,20 @@
import React from 'react';
import { mount } from 'enzyme';
import { MemoryRouter } from 'react-router-dom';
import { I18nProvider } from '@lingui/react';
import OrganizationView from '../../../../src/pages/Organizations/views/Organization.view';
describe('<OrganizationView />', () => {
test('initially renders succesfully', () => {
mount(
<MemoryRouter initialEntries={['/organizations/1']} initialIndex={0}>
<OrganizationView
match={{ path: '/organizations/:id', url: '/organizations/1' }}
location={{ search: '', pathname: '/organizations/1' }}
/>
</MemoryRouter>
<I18nProvider>
<MemoryRouter initialEntries={['/organizations/1']} initialIndex={0}>
<OrganizationView
match={{ path: '/organizations/:id', url: '/organizations/1' }}
location={{ search: '', pathname: '/organizations/1' }}
/>
</MemoryRouter>
</I18nProvider>
);
});
});

View File

@ -1,16 +1,19 @@
import React from 'react';
import { mount } from 'enzyme';
import { MemoryRouter } from 'react-router-dom';
import { I18nProvider } from '@lingui/react';
import OrganizationsList from '../../../../src/pages/Organizations/views/Organizations.list';
describe('<OrganizationsList />', () => {
test('initially renders succesfully', () => {
mount(
<MemoryRouter initialEntries={['/organizations']} initialIndex={0}>
<OrganizationsList
match={{ path: '/organizations', url: '/organizations' }}
location={{ search: '', pathname: '/organizations' }}
/>
<I18nProvider>
<OrganizationsList
match={{ path: '/organizations', url: '/organizations' }}
location={{ search: '', pathname: '/organizations' }}
/>
</I18nProvider>
</MemoryRouter>
);
});

View File

@ -2,7 +2,8 @@ module.exports = api => {
api.cache(false);
return {
plugins: [
'@babel/plugin-proposal-class-properties'
'@babel/plugin-proposal-class-properties',
'macros'
],
presets: [
['@babel/preset-env', {

View File

@ -0,0 +1 @@
/* eslint-disable */module.exports={languageData:{"plurals":function(n,ord){var s=String(n).split("."),v0=!s[1],t0=Number(s[0])==n,n10=t0&&s[0].slice(-1),n100=t0&&s[0].slice(-2);if(ord)return n10==1&&n100!=11?"one":n10==2&&n100!=12?"two":n10==3&&n100!=13?"few":"other";return n==1&&v0?"one":"other"}},messages:{"> add":"> add","> edit":"> edit","About":"About","AboutModal Logo":"AboutModal Logo","Access":"Access","Add":"Add","Administration":"Administration","Admins":"Admins","Ansible Version":"Ansible Version","Applications":"Applications","Authentication":"Authentication","Authentication Settings":"Authentication Settings","Brand Image":"Brand Image","Collapse":"Collapse","Copyright 2018 Red Hat, Inc.":"Copyright 2018 Red Hat, Inc.","Created":"Created","Credential Types":"Credential Types","Credentials":"Credentials","Dashboard":"Dashboard","Delete":"Delete","Expand":"Expand","First":"First","Help":"Help","Instance Groups":"Instance Groups","Integrations":"Integrations","Invalid username or password. Please try again.":"Invalid username or password. Please try again.","Inventories":"Inventories","Inventory Scripts":"Inventory Scripts","Jobs":"Jobs","Jobs Settings":"Jobs Settings","Last":"Last","License":"License","Logout":"Logout","Management Jobs":"Management Jobs","Modified":"Modified","My View":"My View","Name":"Name","Next":"Next","Notification Templates":"Notification Templates","Notifications":"Notifications","Organization Add":"Organization Add","Organization detail tabs":"Organization detail tabs","Organizations":"Organizations","Organizations List":"Organizations List","Page <0/> of {pageCount}":function(a){return["Page <0/> of ",a("pageCount")]},"Page Number":"Page Number","Password":"Password","Per Page":"Per Page","Portal Mode":"Portal Mode","Previous":"Previous","Primary Navigation":"Primary Navigation","Projects":"Projects","Resources":"Resources","Schedules":"Schedules","Search":"Search","Search text input":"Search text input","Select all":"Select all","Settings":"Settings","Sort":"Sort","System":"System","System Settings":"System Settings","Teams":"Teams","Templates":"Templates","Tower Brand Image":"Tower Brand Image","User Interface":"User Interface","User Interface Settings":"User Interface Settings","Username":"Username","Users":"Users","Views":"Views","Welcome to Ansible Tower! Please Sign In.":"Welcome to Ansible Tower! Please Sign In.","add {currentTab}":function(a){return["add ",a("currentTab")]},"adding {currentTab}":function(a){return["adding ",a("currentTab")]},"confirm removal of {currentTab}/cancel and go back to {currentTab} view.":function(a){return["confirm removal of ",a("currentTab"),"/cancel and go back to ",a("currentTab")," view."]},"delete {currentTab}":function(a){return["delete ",a("currentTab")]},"deleting {currentTab} association with orgs":function(a){return["deleting ",a("currentTab")," association with orgs"]},"edit view":"edit view","save/cancel and go back to view":"save/cancel and go back to view","save/cancel and go back to {currentTab} view":function(a){return["save/cancel and go back to ",a("currentTab")," view"]},"select organization {itemId}":function(a){return["select organization ",a("itemId")]},"{currentTab} detail view":function(a){return[a("currentTab")," detail view"]},"{itemMin} - {itemMax} of {count}":function(a){return[a("itemMin")," - ",a("itemMax")," of ",a("count")]}}};

View File

@ -0,0 +1,360 @@
msgid ""
msgstr ""
"POT-Creation-Date: 2018-12-10 10:08-0500\n"
"Mime-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: @lingui/cli\n"
"Language: en\n"
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: \n"
"Plural-Forms: \n"
#: src/pages/Organizations/components/OrganizationBreadcrumb.jsx:60
msgid "> add"
msgstr ""
#: src/pages/Organizations/components/OrganizationBreadcrumb.jsx:53
msgid "> edit"
msgstr ""
#: src/components/HelpDropdown.jsx:32
msgid "About"
msgstr ""
#: src/components/About.jsx:82
msgid "AboutModal Logo"
msgstr ""
#: src/App.jsx:235
msgid "Access"
msgstr ""
#: src/components/DataListToolbar/DataListToolbar.jsx:227
#: src/components/DataListToolbar/DataListToolbar.jsx:228
msgid "Add"
msgstr ""
#: src/App.jsx:253
msgid "Administration"
msgstr ""
#: src/pages/Organizations/components/OrganizationListItem.jsx:66
msgid "Admins"
msgstr ""
#: src/components/About.jsx:100
msgid "Ansible Version"
msgstr ""
#: src/pages/Applications.jsx:17
msgid "Applications"
msgstr ""
#: src/App.jsx:283
msgid "Authentication"
msgstr ""
#: src/pages/AuthSettings.jsx:17
msgid "Authentication Settings"
msgstr ""
#: src/components/About.jsx:80
msgid "Brand Image"
msgstr ""
#: src/components/DataListToolbar/DataListToolbar.jsx:212
msgid "Collapse"
msgstr ""
#: src/components/About.jsx:78
msgid "Copyright 2018 Red Hat, Inc."
msgstr ""
#: src/pages/Organizations/views/Organizations.list.jsx:32
msgid "Created"
msgstr ""
#: src/App.jsx:257
#: src/pages/CredentialTypes.jsx:17
msgid "Credential Types"
msgstr ""
#: src/App.jsx:217
#: src/pages/Credentials.jsx:17
msgid "Credentials"
msgstr ""
#: src/App.jsx:191
#: src/pages/Dashboard.jsx:17
msgid "Dashboard"
msgstr ""
#: src/components/DataListToolbar/DataListToolbar.jsx:220
#: src/components/DataListToolbar/DataListToolbar.jsx:221
msgid "Delete"
msgstr ""
#: src/components/DataListToolbar/DataListToolbar.jsx:207
msgid "Expand"
msgstr ""
#: src/components/Pagination/Pagination.jsx:164
msgid "First"
msgstr ""
#: src/components/HelpDropdown.jsx:26
msgid "Help"
msgstr ""
#: src/App.jsx:269
#: src/pages/InstanceGroups.jsx:17
msgid "Instance Groups"
msgstr ""
#: src/App.jsx:273
msgid "Integrations"
msgstr ""
#: src/pages/Login.jsx:80
msgid "Invalid username or password. Please try again."
msgstr ""
#: src/App.jsx:225
#: src/pages/Inventories.jsx:17
msgid "Inventories"
msgstr ""
#: src/App.jsx:229
#: src/pages/InventoryScripts.jsx:17
msgid "Inventory Scripts"
msgstr ""
#: src/App.jsx:195
#: src/App.jsx:287
#: src/pages/Jobs.jsx:17
msgid "Jobs"
msgstr ""
#: src/pages/JobsSettings.jsx:17
msgid "Jobs Settings"
msgstr ""
#: src/components/Pagination/Pagination.jsx:216
msgid "Last"
msgstr ""
#: src/App.jsx:299
#: src/pages/License.jsx:17
msgid "License"
msgstr ""
#: src/components/LogoutButton.jsx:17
msgid "Logout"
msgstr ""
#: src/App.jsx:265
#: src/pages/ManagementJobs.jsx:17
msgid "Management Jobs"
msgstr ""
#: src/pages/Organizations/views/Organizations.list.jsx:31
msgid "Modified"
msgstr ""
#: src/pages/Portal.jsx:17
msgid "My View"
msgstr ""
#: src/pages/Organizations/views/Organizations.list.jsx:30
msgid "Name"
msgstr ""
#: src/components/Pagination/Pagination.jsx:207
msgid "Next"
msgstr ""
#: src/pages/NotificationTemplates.jsx:17
msgid "Notification Templates"
msgstr ""
#: src/App.jsx:261
msgid "Notifications"
msgstr ""
#: src/pages/Organizations/views/Organization.add.jsx:15
msgid "Organization Add"
msgstr ""
#: src/pages/Organizations/components/OrganizationDetail.jsx:99
msgid "Organization detail tabs"
msgstr ""
#: src/App.jsx:239
#: src/pages/Organizations/views/Organization.view.jsx:62
#: src/pages/Organizations/views/Organizations.list.jsx:189
#: src/pages/Organizations/views/Organizations.list.jsx:195
msgid "Organizations"
msgstr ""
#: src/pages/Organizations/views/Organizations.list.jsx:211
msgid "Organizations List"
msgstr ""
#: src/components/Pagination/Pagination.jsx:184
msgid "Page <0/> of {pageCount}"
msgstr ""
#: src/components/Pagination/Pagination.jsx:187
msgid "Page Number"
msgstr ""
#: src/pages/Login.jsx:76
msgid "Password"
msgstr ""
#: src/components/Pagination/Pagination.jsx:153
msgid "Per Page"
msgstr ""
#: src/App.jsx:203
msgid "Portal Mode"
msgstr ""
#: src/components/Pagination/Pagination.jsx:173
msgid "Previous"
msgstr ""
#: src/App.jsx:180
msgid "Primary Navigation"
msgstr ""
#: src/App.jsx:221
#: src/pages/Projects.jsx:17
msgid "Projects"
msgstr ""
#: src/App.jsx:209
msgid "Resources"
msgstr ""
#: src/App.jsx:199
#: src/pages/Schedules.jsx:17
msgid "Schedules"
msgstr ""
#: src/components/DataListToolbar/DataListToolbar.jsx:163
msgid "Search"
msgstr ""
#: src/components/DataListToolbar/DataListToolbar.jsx:157
msgid "Search text input"
msgstr ""
#: src/components/DataListToolbar/DataListToolbar.jsx:128
msgid "Select all"
msgstr ""
#: src/App.jsx:279
msgid "Settings"
msgstr ""
#: src/components/DataListToolbar/DataListToolbar.jsx:199
msgid "Sort"
msgstr ""
#: src/App.jsx:291
msgid "System"
msgstr ""
#: src/pages/SystemSettings.jsx:17
msgid "System Settings"
msgstr ""
#: src/App.jsx:247
#: src/pages/Organizations/components/OrganizationListItem.jsx:58
#: src/pages/Teams.jsx:17
msgid "Teams"
msgstr ""
#: src/App.jsx:213
#: src/pages/Templates.jsx:17
msgid "Templates"
msgstr ""
#: src/components/TowerLogo/TowerLogo.jsx:48
msgid "Tower Brand Image"
msgstr ""
#: src/App.jsx:295
msgid "User Interface"
msgstr ""
#: src/pages/UISettings.jsx:17
msgid "User Interface Settings"
msgstr ""
#: src/pages/Login.jsx:73
msgid "Username"
msgstr ""
#: src/App.jsx:243
#: src/pages/Organizations/components/OrganizationListItem.jsx:50
#: src/pages/Users.jsx:17
msgid "Users"
msgstr ""
#: src/App.jsx:187
msgid "Views"
msgstr ""
#: src/pages/Login.jsx:70
msgid "Welcome to Ansible Tower! Please Sign In."
msgstr ""
#: src/pages/Organizations/components/OrganizationDetail.jsx:87
msgid "add {currentTab}"
msgstr ""
#: src/pages/Organizations/components/OrganizationDetail.jsx:76
msgid "adding {currentTab}"
msgstr ""
#: src/pages/Organizations/components/OrganizationDetail.jsx:69
msgid "confirm removal of {currentTab}/cancel and go back to {currentTab} view."
msgstr ""
#: src/pages/Organizations/components/OrganizationDetail.jsx:91
msgid "delete {currentTab}"
msgstr ""
#: src/pages/Organizations/components/OrganizationDetail.jsx:67
msgid "deleting {currentTab} association with orgs"
msgstr ""
#: src/pages/Organizations/components/OrganizationEdit.jsx:20
msgid "edit view"
msgstr ""
#: src/pages/Organizations/components/OrganizationEdit.jsx:22
msgid "save/cancel and go back to view"
msgstr ""
#: src/pages/Organizations/components/OrganizationDetail.jsx:78
msgid "save/cancel and go back to {currentTab} view"
msgstr ""
#: src/pages/Organizations/components/OrganizationListItem.jsx:30
msgid "select organization {itemId}"
msgstr ""
#: src/pages/Organizations/components/OrganizationDetail.jsx:85
msgid "{currentTab} detail view"
msgstr ""
#: src/components/Pagination/Pagination.jsx:158
msgid "{itemMin} - {itemMax} of {count}"
msgstr ""

View File

@ -0,0 +1 @@
/* eslint-disable */module.exports={languageData:{"plurals":function(n,ord){if(ord)return"other";return"other"}},messages:{"> add":"> add","> edit":"> edit","About":"About","AboutModal Logo":"AboutModal Logo","Access":"Access","Add":"Add","Administration":"Administration","Admins":"Admins","Ansible Version":"Ansible Version","Applications":"Applications","Authentication":"Authentication","Authentication Settings":"Authentication Settings","Brand Image":"Brand Image","Collapse":"Collapse","Copyright 2018 Red Hat, Inc.":"Copyright 2018 Red Hat, Inc.","Created":"Created","Credential Types":"Credential Types","Credentials":"Credentials","Dashboard":"Dashboard","Delete":"Delete","Expand":"Expand","First":"First","Help":"Help","Instance Groups":"Instance Groups","Integrations":"Integrations","Invalid username or password. Please try again.":"Invalid username or password. Please try again.","Inventories":"Inventories","Inventory Scripts":"Inventory Scripts","Jobs":"Jobs","Jobs Settings":"Jobs Settings","Last":"Last","License":"License","Logout":"Logout","Management Jobs":"Management Jobs","Modified":"Modified","My View":"My View","Name":"Name","Next":"Next","Notification Templates":"Notification Templates","Notifications":"Notifications","Organization Add":"Organization Add","Organization detail tabs":"Organization detail tabs","Organizations":"Organizations","Organizations List":"Organizations List","Page <0/> of {pageCount}":function(a){return["Page <0/> of ",a("pageCount")]},"Page Number":"Page Number","Password":"Password","Per Page":"Per Page","Portal Mode":"Portal Mode","Previous":"Previous","Primary Navigation":"Primary Navigation","Projects":"Projects","Resources":"Resources","Schedules":"Schedules","Search":"Search","Search text input":"Search text input","Select all":"Select all","Settings":"Settings","Sort":"Sort","System":"System","System Settings":"System Settings","Teams":"Teams","Templates":"Templates","Tower Brand Image":"Tower Brand Image","User Interface":"User Interface","User Interface Settings":"User Interface Settings","Username":"Username","Users":"Users","Views":"Views","Welcome to Ansible Tower! Please Sign In.":"Welcome to Ansible Tower! Please Sign In.","add {currentTab}":function(a){return["add ",a("currentTab")]},"adding {currentTab}":function(a){return["adding ",a("currentTab")]},"confirm removal of {currentTab}/cancel and go back to {currentTab} view.":function(a){return["confirm removal of ",a("currentTab"),"/cancel and go back to ",a("currentTab")," view."]},"delete {currentTab}":function(a){return["delete ",a("currentTab")]},"deleting {currentTab} association with orgs":function(a){return["deleting ",a("currentTab")," association with orgs"]},"edit view":"edit view","save/cancel and go back to view":"save/cancel and go back to view","save/cancel and go back to {currentTab} view":function(a){return["save/cancel and go back to ",a("currentTab")," view"]},"select organization {itemId}":function(a){return["select organization ",a("itemId")]},"{currentTab} detail view":function(a){return[a("currentTab")," detail view"]},"{itemMin} - {itemMax} of {count}":function(a){return[a("itemMin")," - ",a("itemMax")," of ",a("count")]}}};

View File

@ -0,0 +1,360 @@
msgid ""
msgstr ""
"POT-Creation-Date: 2018-12-10 10:08-0500\n"
"Mime-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: @lingui/cli\n"
"Language: ja\n"
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: \n"
"Plural-Forms: \n"
#: src/pages/Organizations/components/OrganizationBreadcrumb.jsx:60
msgid "> add"
msgstr ""
#: src/pages/Organizations/components/OrganizationBreadcrumb.jsx:53
msgid "> edit"
msgstr ""
#: src/components/HelpDropdown.jsx:32
msgid "About"
msgstr ""
#: src/components/About.jsx:82
msgid "AboutModal Logo"
msgstr ""
#: src/App.jsx:235
msgid "Access"
msgstr ""
#: src/components/DataListToolbar/DataListToolbar.jsx:227
#: src/components/DataListToolbar/DataListToolbar.jsx:228
msgid "Add"
msgstr ""
#: src/App.jsx:253
msgid "Administration"
msgstr ""
#: src/pages/Organizations/components/OrganizationListItem.jsx:66
msgid "Admins"
msgstr ""
#: src/components/About.jsx:100
msgid "Ansible Version"
msgstr ""
#: src/pages/Applications.jsx:17
msgid "Applications"
msgstr ""
#: src/App.jsx:283
msgid "Authentication"
msgstr ""
#: src/pages/AuthSettings.jsx:17
msgid "Authentication Settings"
msgstr ""
#: src/components/About.jsx:80
msgid "Brand Image"
msgstr ""
#: src/components/DataListToolbar/DataListToolbar.jsx:212
msgid "Collapse"
msgstr ""
#: src/components/About.jsx:78
msgid "Copyright 2018 Red Hat, Inc."
msgstr ""
#: src/pages/Organizations/views/Organizations.list.jsx:32
msgid "Created"
msgstr ""
#: src/App.jsx:257
#: src/pages/CredentialTypes.jsx:17
msgid "Credential Types"
msgstr ""
#: src/App.jsx:217
#: src/pages/Credentials.jsx:17
msgid "Credentials"
msgstr ""
#: src/App.jsx:191
#: src/pages/Dashboard.jsx:17
msgid "Dashboard"
msgstr ""
#: src/components/DataListToolbar/DataListToolbar.jsx:220
#: src/components/DataListToolbar/DataListToolbar.jsx:221
msgid "Delete"
msgstr ""
#: src/components/DataListToolbar/DataListToolbar.jsx:207
msgid "Expand"
msgstr ""
#: src/components/Pagination/Pagination.jsx:164
msgid "First"
msgstr ""
#: src/components/HelpDropdown.jsx:26
msgid "Help"
msgstr ""
#: src/App.jsx:269
#: src/pages/InstanceGroups.jsx:17
msgid "Instance Groups"
msgstr ""
#: src/App.jsx:273
msgid "Integrations"
msgstr ""
#: src/pages/Login.jsx:80
msgid "Invalid username or password. Please try again."
msgstr ""
#: src/App.jsx:225
#: src/pages/Inventories.jsx:17
msgid "Inventories"
msgstr ""
#: src/App.jsx:229
#: src/pages/InventoryScripts.jsx:17
msgid "Inventory Scripts"
msgstr ""
#: src/App.jsx:195
#: src/App.jsx:287
#: src/pages/Jobs.jsx:17
msgid "Jobs"
msgstr ""
#: src/pages/JobsSettings.jsx:17
msgid "Jobs Settings"
msgstr ""
#: src/components/Pagination/Pagination.jsx:216
msgid "Last"
msgstr ""
#: src/App.jsx:299
#: src/pages/License.jsx:17
msgid "License"
msgstr ""
#: src/components/LogoutButton.jsx:17
msgid "Logout"
msgstr ""
#: src/App.jsx:265
#: src/pages/ManagementJobs.jsx:17
msgid "Management Jobs"
msgstr ""
#: src/pages/Organizations/views/Organizations.list.jsx:31
msgid "Modified"
msgstr ""
#: src/pages/Portal.jsx:17
msgid "My View"
msgstr ""
#: src/pages/Organizations/views/Organizations.list.jsx:30
msgid "Name"
msgstr ""
#: src/components/Pagination/Pagination.jsx:207
msgid "Next"
msgstr ""
#: src/pages/NotificationTemplates.jsx:17
msgid "Notification Templates"
msgstr ""
#: src/App.jsx:261
msgid "Notifications"
msgstr ""
#: src/pages/Organizations/views/Organization.add.jsx:15
msgid "Organization Add"
msgstr ""
#: src/pages/Organizations/components/OrganizationDetail.jsx:99
msgid "Organization detail tabs"
msgstr ""
#: src/App.jsx:239
#: src/pages/Organizations/views/Organization.view.jsx:62
#: src/pages/Organizations/views/Organizations.list.jsx:189
#: src/pages/Organizations/views/Organizations.list.jsx:195
msgid "Organizations"
msgstr ""
#: src/pages/Organizations/views/Organizations.list.jsx:211
msgid "Organizations List"
msgstr ""
#: src/components/Pagination/Pagination.jsx:184
msgid "Page <0/> of {pageCount}"
msgstr ""
#: src/components/Pagination/Pagination.jsx:187
msgid "Page Number"
msgstr ""
#: src/pages/Login.jsx:76
msgid "Password"
msgstr ""
#: src/components/Pagination/Pagination.jsx:153
msgid "Per Page"
msgstr ""
#: src/App.jsx:203
msgid "Portal Mode"
msgstr ""
#: src/components/Pagination/Pagination.jsx:173
msgid "Previous"
msgstr ""
#: src/App.jsx:180
msgid "Primary Navigation"
msgstr ""
#: src/App.jsx:221
#: src/pages/Projects.jsx:17
msgid "Projects"
msgstr ""
#: src/App.jsx:209
msgid "Resources"
msgstr ""
#: src/App.jsx:199
#: src/pages/Schedules.jsx:17
msgid "Schedules"
msgstr ""
#: src/components/DataListToolbar/DataListToolbar.jsx:163
msgid "Search"
msgstr ""
#: src/components/DataListToolbar/DataListToolbar.jsx:157
msgid "Search text input"
msgstr ""
#: src/components/DataListToolbar/DataListToolbar.jsx:128
msgid "Select all"
msgstr ""
#: src/App.jsx:279
msgid "Settings"
msgstr ""
#: src/components/DataListToolbar/DataListToolbar.jsx:199
msgid "Sort"
msgstr ""
#: src/App.jsx:291
msgid "System"
msgstr ""
#: src/pages/SystemSettings.jsx:17
msgid "System Settings"
msgstr ""
#: src/App.jsx:247
#: src/pages/Organizations/components/OrganizationListItem.jsx:58
#: src/pages/Teams.jsx:17
msgid "Teams"
msgstr ""
#: src/App.jsx:213
#: src/pages/Templates.jsx:17
msgid "Templates"
msgstr ""
#: src/components/TowerLogo/TowerLogo.jsx:48
msgid "Tower Brand Image"
msgstr ""
#: src/App.jsx:295
msgid "User Interface"
msgstr ""
#: src/pages/UISettings.jsx:17
msgid "User Interface Settings"
msgstr ""
#: src/pages/Login.jsx:73
msgid "Username"
msgstr ""
#: src/App.jsx:243
#: src/pages/Organizations/components/OrganizationListItem.jsx:50
#: src/pages/Users.jsx:17
msgid "Users"
msgstr ""
#: src/App.jsx:187
msgid "Views"
msgstr ""
#: src/pages/Login.jsx:70
msgid "Welcome to Ansible Tower! Please Sign In."
msgstr ""
#: src/pages/Organizations/components/OrganizationDetail.jsx:87
msgid "add {currentTab}"
msgstr ""
#: src/pages/Organizations/components/OrganizationDetail.jsx:76
msgid "adding {currentTab}"
msgstr ""
#: src/pages/Organizations/components/OrganizationDetail.jsx:69
msgid "confirm removal of {currentTab}/cancel and go back to {currentTab} view."
msgstr ""
#: src/pages/Organizations/components/OrganizationDetail.jsx:91
msgid "delete {currentTab}"
msgstr ""
#: src/pages/Organizations/components/OrganizationDetail.jsx:67
msgid "deleting {currentTab} association with orgs"
msgstr ""
#: src/pages/Organizations/components/OrganizationEdit.jsx:20
msgid "edit view"
msgstr ""
#: src/pages/Organizations/components/OrganizationEdit.jsx:22
msgid "save/cancel and go back to view"
msgstr ""
#: src/pages/Organizations/components/OrganizationDetail.jsx:78
msgid "save/cancel and go back to {currentTab} view"
msgstr ""
#: src/pages/Organizations/components/OrganizationListItem.jsx:30
msgid "select organization {itemId}"
msgstr ""
#: src/pages/Organizations/components/OrganizationDetail.jsx:85
msgid "{currentTab} detail view"
msgstr ""
#: src/components/Pagination/Pagination.jsx:158
msgid "{itemMin} - {itemMax} of {count}"
msgstr ""

View File

@ -2,6 +2,9 @@ module.exports = {
collectCoverageFrom: [
'src/**/*.{js,jsx}'
],
coveragePathIgnorePatterns: [
'<rootDir>/src/locales'
],
moduleNameMapper: {
'\\.(css|scss|less)$': '<rootDir>/__mocks__/styleMock.js'
},

769
package-lock.json generated
View File

@ -14,36 +14,110 @@
}
},
"@babel/core": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.1.2.tgz",
"integrity": "sha512-IFeSSnjXdhDaoysIlev//UzHZbdEmm7D0EIH2qtse9xK7mXEZQpYjs2P00XlP1qYsYvid79p+Zgg6tz1mp6iVw==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.2.0.tgz",
"integrity": "sha512-7pvAdC4B+iKjFFp9Ztj0QgBndJ++qaMeonT185wAqUnhipw8idm9Rv1UMyBuKtYjfl6ORNkgEgcsYLfHX/GpLw==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0",
"@babel/generator": "^7.1.2",
"@babel/helpers": "^7.1.2",
"@babel/parser": "^7.1.2",
"@babel/generator": "^7.2.0",
"@babel/helpers": "^7.2.0",
"@babel/parser": "^7.2.0",
"@babel/template": "^7.1.2",
"@babel/traverse": "^7.1.0",
"@babel/types": "^7.1.2",
"@babel/traverse": "^7.1.6",
"@babel/types": "^7.2.0",
"convert-source-map": "^1.1.0",
"debug": "^3.1.0",
"json5": "^0.5.0",
"debug": "^4.1.0",
"json5": "^2.1.0",
"lodash": "^4.17.10",
"resolve": "^1.3.2",
"semver": "^5.4.1",
"source-map": "^0.5.0"
},
"dependencies": {
"@babel/generator": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.2.0.tgz",
"integrity": "sha512-BA75MVfRlFQG2EZgFYIwyT1r6xSkwfP2bdkY/kLZusEYWiJs4xCowab/alaEaT0wSvmVuXGqiefeBlP+7V1yKg==",
"dev": true,
"requires": {
"@babel/types": "^7.2.0",
"jsesc": "^2.5.1",
"lodash": "^4.17.10",
"source-map": "^0.5.0",
"trim-right": "^1.0.1"
}
},
"@babel/parser": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.2.0.tgz",
"integrity": "sha512-M74+GvK4hn1eejD9lZ7967qAwvqTZayQa3g10ag4s9uewgR7TKjeaT0YMyoq+gVfKYABiWZ4MQD701/t5e1Jhg==",
"dev": true
},
"@babel/traverse": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.1.6.tgz",
"integrity": "sha512-CXedit6GpISz3sC2k2FsGCUpOhUqKdyL0lqNrImQojagnUMXf8hex4AxYFRuMkNGcvJX5QAFGzB5WJQmSv8SiQ==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0",
"@babel/generator": "^7.1.6",
"@babel/helper-function-name": "^7.1.0",
"@babel/helper-split-export-declaration": "^7.0.0",
"@babel/parser": "^7.1.6",
"@babel/types": "^7.1.6",
"debug": "^4.1.0",
"globals": "^11.1.0",
"lodash": "^4.17.10"
}
},
"@babel/types": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.0.tgz",
"integrity": "sha512-b4v7dyfApuKDvmPb+O488UlGuR1WbwMXFsO/cyqMrnfvRAChZKJAYeeglWTjUO1b9UghKKgepAQM5tsvBJca6A==",
"dev": true,
"requires": {
"esutils": "^2.0.2",
"lodash": "^4.17.10",
"to-fast-properties": "^2.0.0"
}
},
"debug": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz",
"integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==",
"dev": true,
"requires": {
"ms": "^2.1.1"
}
},
"globals": {
"version": "11.9.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.9.0.tgz",
"integrity": "sha512-5cJVtyXWH8PiJPVLZzzoIizXx944O4OmRro5MWKx5fT4MgcN7OfaMutPeaTdJCCURwbWdhhcCWcKIffPnmTzBg==",
"dev": true
},
"jsesc": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
"dev": true
},
"json5": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz",
"integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==",
"dev": true,
"requires": {
"minimist": "^1.2.0"
}
},
"minimist": {
"version": "1.2.0",
"resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true
},
"ms": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
@ -286,14 +360,96 @@
}
},
"@babel/helpers": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.1.2.tgz",
"integrity": "sha512-Myc3pUE8eswD73aWcartxB16K6CGmHDv9KxOmD2CeOs/FaEAQodr3VYGmlvOmog60vNQ2w8QbatuahepZwrHiA==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.2.0.tgz",
"integrity": "sha512-Fr07N+ea0dMcMN8nFpuK6dUIT7/ivt9yKQdEEnjVS83tG2pHwPi03gYmk/tyuwONnZ+sY+GFFPlWGgCtW1hF9A==",
"dev": true,
"requires": {
"@babel/template": "^7.1.2",
"@babel/traverse": "^7.1.0",
"@babel/types": "^7.1.2"
"@babel/traverse": "^7.1.5",
"@babel/types": "^7.2.0"
},
"dependencies": {
"@babel/generator": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.2.0.tgz",
"integrity": "sha512-BA75MVfRlFQG2EZgFYIwyT1r6xSkwfP2bdkY/kLZusEYWiJs4xCowab/alaEaT0wSvmVuXGqiefeBlP+7V1yKg==",
"dev": true,
"requires": {
"@babel/types": "^7.2.0",
"jsesc": "^2.5.1",
"lodash": "^4.17.10",
"source-map": "^0.5.0",
"trim-right": "^1.0.1"
}
},
"@babel/parser": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.2.0.tgz",
"integrity": "sha512-M74+GvK4hn1eejD9lZ7967qAwvqTZayQa3g10ag4s9uewgR7TKjeaT0YMyoq+gVfKYABiWZ4MQD701/t5e1Jhg==",
"dev": true
},
"@babel/traverse": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.1.6.tgz",
"integrity": "sha512-CXedit6GpISz3sC2k2FsGCUpOhUqKdyL0lqNrImQojagnUMXf8hex4AxYFRuMkNGcvJX5QAFGzB5WJQmSv8SiQ==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0",
"@babel/generator": "^7.1.6",
"@babel/helper-function-name": "^7.1.0",
"@babel/helper-split-export-declaration": "^7.0.0",
"@babel/parser": "^7.1.6",
"@babel/types": "^7.1.6",
"debug": "^4.1.0",
"globals": "^11.1.0",
"lodash": "^4.17.10"
}
},
"@babel/types": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.0.tgz",
"integrity": "sha512-b4v7dyfApuKDvmPb+O488UlGuR1WbwMXFsO/cyqMrnfvRAChZKJAYeeglWTjUO1b9UghKKgepAQM5tsvBJca6A==",
"dev": true,
"requires": {
"esutils": "^2.0.2",
"lodash": "^4.17.10",
"to-fast-properties": "^2.0.0"
}
},
"debug": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz",
"integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==",
"dev": true,
"requires": {
"ms": "^2.1.1"
}
},
"globals": {
"version": "11.9.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.9.0.tgz",
"integrity": "sha512-5cJVtyXWH8PiJPVLZzzoIizXx944O4OmRro5MWKx5fT4MgcN7OfaMutPeaTdJCCURwbWdhhcCWcKIffPnmTzBg==",
"dev": true
},
"jsesc": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
"dev": true
},
"ms": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
"dev": true
},
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
"dev": true
}
}
},
"@babel/highlight": {
@ -976,6 +1132,179 @@
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.8.2.tgz",
"integrity": "sha512-rLu3wcBWH4P5q1CGoSSH/i9hrXs7SlbRLkoq9IGuoPYNGQvDJ3pt/wmOM+XgYjIDRMVIdkUWt0RsfzF50JfnCw=="
},
"@lingui/babel-plugin-extract-messages": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/@lingui/babel-plugin-extract-messages/-/babel-plugin-extract-messages-2.7.2.tgz",
"integrity": "sha512-YKyB0avVZteskOeXsJCTO+346FfhTCmhF4W4rG4nwRbyIeTOYL5heBL54g1e+T5YXBJLi5HJuhRd1OGuGzbQzg==",
"dev": true,
"requires": {
"@lingui/conf": "2.7.2",
"babel-generator": "^6.26.1"
}
},
"@lingui/babel-plugin-transform-js": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/@lingui/babel-plugin-transform-js/-/babel-plugin-transform-js-2.7.2.tgz",
"integrity": "sha512-V2avCZOqiyejz4vM24X+Wz1+bvzdovLUdW9iBRocaH6hcRtA1ttVQ6JAQBw/tACY5sFw73LdrPiVMiD7dw1NtA==",
"dev": true
},
"@lingui/babel-plugin-transform-react": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/@lingui/babel-plugin-transform-react/-/babel-plugin-transform-react-2.7.2.tgz",
"integrity": "sha512-50+GM9LL7V4mB6ekY7hUGmyMLewDQ9bzVXTyRtbTl/78xJoQKSv8Wuz+jlvPcP0fKAFcmcHo3QJPPOkSAbgnOw==",
"dev": true
},
"@lingui/cli": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/@lingui/cli/-/cli-2.7.2.tgz",
"integrity": "sha512-PdSUALHUyHG7iwwoECWqUlvWjI5jg+9TUWJMgnLyelGi/th6EAdhs/DPxT1uYWQl438hYXLRN61QrPzKtKgZHQ==",
"dev": true,
"requires": {
"@lingui/babel-plugin-extract-messages": "2.7.2",
"@lingui/babel-plugin-transform-js": "2.7.2",
"@lingui/babel-plugin-transform-react": "2.7.2",
"@lingui/conf": "2.7.2",
"babel-generator": "^6.26.1",
"babel-plugin-syntax-jsx": "^6.18.0",
"babel-runtime": "^6.26.0",
"babel-types": "^6.26.0",
"babylon": "^6.18.0",
"bcp-47": "^1.0.4",
"chalk": "^2.3.0",
"cli-table": "^0.3.1",
"commander": "^2.17.1",
"date-fns": "^1.29.0",
"fuzzaldrin": "^2.1.0",
"glob": "^7.1.2",
"inquirer": "^6.2.0",
"make-plural": "^4.1.1",
"messageformat-parser": "^2.0.0",
"mkdirp": "^0.5.1",
"opencollective": "^1.0.3",
"ora": "^3.0.0",
"pofile": "^1.0.11",
"pseudolocale": "^1.1.0",
"ramda": "^0.25.0",
"typescript": "^2.9.2"
},
"dependencies": {
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "^1.9.0"
}
},
"chalk": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
"integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
}
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
}
}
},
"@lingui/conf": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/@lingui/conf/-/conf-2.7.2.tgz",
"integrity": "sha512-SMTY4GWaP611hEnWm4YiPXp6jufD+qzH6RUiDe+r9aX5j9kMXV2/aYQfNezbh46BpLFf29xxm+Wn5q89/fRbIw==",
"dev": true,
"requires": {
"chalk": "^2.3.0",
"cosmiconfig": "^5.0.6",
"jest-regex-util": "^23.3.0",
"jest-validate": "^23.5.0",
"pkg-conf": "^2.1.0"
},
"dependencies": {
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "^1.9.0"
}
},
"chalk": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
"integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
}
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
}
}
},
"@lingui/core": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/@lingui/core/-/core-2.7.2.tgz",
"integrity": "sha512-LLMNcxfXmjhxG18sQmIb4azNuwVyhMUhcVFgOQ682eVQEGaSIncPsnSQRGhgYbiqY1LGXlZEXZnYdyyDMSZR0A==",
"requires": {
"babel-runtime": "^6.26.0",
"make-plural": "^4.1.1",
"messageformat-parser": "^2.0.0"
}
},
"@lingui/macro": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/@lingui/macro/-/macro-2.7.2.tgz",
"integrity": "sha512-kU5fl/2MvHQLfVRju2QR28uKOHsUqlLZOJwMXNAm/eq42D6ORa5vZR2ovlBRF9IdcYGqkCCPjeTLN5NC7b5gyQ==",
"dev": true,
"requires": {
"@lingui/babel-plugin-transform-react": "2.7.2",
"babel-plugin-macros": "^2.2.0"
}
},
"@lingui/react": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/@lingui/react/-/react-2.7.2.tgz",
"integrity": "sha512-dYfhojpYLKyXp3V1i3twjipIXGK80rBBLNMZOXBBCqm5Lypef3d7Ip1jLXuhk4Ni++ijWJfHC40F3gWNBNFSjw==",
"requires": {
"@lingui/core": "2.7.2",
"babel-runtime": "^6.26.0",
"hash-sum": "^1.0.2",
"hoist-non-react-statics": "3.0.1",
"prop-types": "^15.6.2"
},
"dependencies": {
"hoist-non-react-statics": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.0.1.tgz",
"integrity": "sha512-1kXwPsOi0OGQIZNVMPvgWJ9tSnGMiMfJdihqEzrPEXlHOBh9AAHXX/QYmAJTXztnz/K+PQ8ryCb4eGaN6HlGbQ==",
"requires": {
"react-is": "^16.3.2"
}
}
}
},
"@patternfly/patternfly-next": {
"version": "1.0.84",
"resolved": "https://registry.npmjs.org/@patternfly/patternfly-next/-/patternfly-next-1.0.84.tgz",
@ -2461,6 +2790,25 @@
"babel-types": "^6.24.1"
}
},
"babel-polyfill": {
"version": "6.23.0",
"resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.23.0.tgz",
"integrity": "sha1-g2TKYt+Or7gwSZ9pkXdGbDsDSZ0=",
"dev": true,
"requires": {
"babel-runtime": "^6.22.0",
"core-js": "^2.4.0",
"regenerator-runtime": "^0.10.0"
},
"dependencies": {
"regenerator-runtime": {
"version": "0.10.5",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz",
"integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=",
"dev": true
}
}
},
"babel-preset-fbjs": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/babel-preset-fbjs/-/babel-preset-fbjs-2.3.0.tgz",
@ -2697,6 +3045,17 @@
"integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=",
"dev": true
},
"bcp-47": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-1.0.4.tgz",
"integrity": "sha512-KquGHKBVXDBnOOntjqkqINNyNX0eKhDXYbK+83pDJXWO7lV6D7Ey1IQNIDbVQOHxNv6rdynnfS/RfPLVz5X0WA==",
"dev": true,
"requires": {
"is-alphabetical": "^1.0.0",
"is-alphanumerical": "^1.0.0",
"is-decimal": "^1.0.0"
}
},
"bcrypt-pbkdf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
@ -3354,6 +3713,29 @@
"restore-cursor": "^2.0.0"
}
},
"cli-spinners": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-1.3.1.tgz",
"integrity": "sha512-1QL4544moEsDVH9T/l6Cemov/37iv1RtoKf7NJ04A60+4MREXNfx/QvavbH6QoGdsD4N4Mwy49cmaINR/o2mdg==",
"dev": true
},
"cli-table": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.1.tgz",
"integrity": "sha1-9TsFJmqLGguTSz0IIebi3FkUriM=",
"dev": true,
"requires": {
"colors": "1.0.3"
},
"dependencies": {
"colors": {
"version": "1.0.3",
"resolved": "http://registry.npmjs.org/colors/-/colors-1.0.3.tgz",
"integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=",
"dev": true
}
}
},
"cli-width": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
@ -3388,6 +3770,12 @@
}
}
},
"clone": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
"integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=",
"dev": true
},
"clone-deep": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-2.0.2.tgz",
@ -3933,6 +4321,12 @@
}
}
},
"date-fns": {
"version": "1.29.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.29.0.tgz",
"integrity": "sha512-lbTXWZ6M20cWH8N9S6afb0SBm6tMk+uUg6z3MqHPKE9atmsY3kJkTm8vKe93izJ2B2+q5MV990sM2CHgtAZaOw==",
"dev": true
},
"date-now": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz",
@ -4018,6 +4412,15 @@
"strip-bom": "^2.0.0"
}
},
"defaults": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz",
"integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=",
"dev": true,
"requires": {
"clone": "^1.0.2"
}
},
"define-properties": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
@ -4428,6 +4831,15 @@
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
"dev": true
},
"encoding": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
"integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
"dev": true,
"requires": {
"iconv-lite": "~0.4.13"
}
},
"end-of-stream": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
@ -6226,6 +6638,12 @@
"integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
"dev": true
},
"fuzzaldrin": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fuzzaldrin/-/fuzzaldrin-2.1.0.tgz",
"integrity": "sha1-kCBMPi/appQbso0WZF1BgGOpDps=",
"dev": true
},
"gauge": {
"version": "2.7.4",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
@ -6532,6 +6950,11 @@
"safe-buffer": "^5.0.1"
}
},
"hash-sum": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz",
"integrity": "sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ="
},
"hash.js": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.5.tgz",
@ -7310,6 +7733,22 @@
}
}
},
"is-alphabetical": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.2.tgz",
"integrity": "sha512-V0xN4BYezDHcBSKb1QHUFMlR4as/XEuCZBzMJUU4n7+Cbt33SmUnSol+pnXFvLxSHNq2CemUXNdaXV6Flg7+xg==",
"dev": true
},
"is-alphanumerical": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.2.tgz",
"integrity": "sha512-pyfU/0kHdISIgslFfZN9nfY1Gk3MquQgUm1mJTjdkEPpkAKNWuBTSqFwewOpR7N351VkErCiyV71zX7mlQQqsg==",
"dev": true,
"requires": {
"is-alphabetical": "^1.0.0",
"is-decimal": "^1.0.0"
}
},
"is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
@ -7385,6 +7824,12 @@
"integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=",
"dev": true
},
"is-decimal": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.2.tgz",
"integrity": "sha512-TRzl7mOCchnhchN+f3ICUCzYvL9ul7R+TYOsZ8xia++knyZAJfv/uA1FvQXsAnYIl1T3B2X5E/J7Wb1QXiIBXg==",
"dev": true
},
"is-descriptor": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
@ -8948,6 +9393,46 @@
"integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=",
"dev": true
},
"log-symbols": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz",
"integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==",
"dev": true,
"requires": {
"chalk": "^2.0.1"
},
"dependencies": {
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "^1.9.0"
}
},
"chalk": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
"integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
}
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
}
}
},
"loglevel": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz",
@ -9003,6 +9488,22 @@
}
}
},
"make-plural": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/make-plural/-/make-plural-4.3.0.tgz",
"integrity": "sha512-xTYd4JVHpSCW+aqDof6w/MebaMVNTVYBZhbB/vi513xXdiPT92JMVCo0Jq8W2UZnzYRFeVbQiQ+I25l13JuKvA==",
"requires": {
"minimist": "^1.2.0"
},
"dependencies": {
"minimist": {
"version": "1.2.0",
"resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"optional": true
}
}
},
"makeerror": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz",
@ -9195,6 +9696,11 @@
}
}
},
"messageformat-parser": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/messageformat-parser/-/messageformat-parser-2.0.0.tgz",
"integrity": "sha512-C2ZjB5GlLeikkeoMCTcwEeb68LrFl9osxQzXHIPh0Wcj+43wNsoKpRRKq9rm204sAIdknrdcoeQMUnzvDuMf6g=="
},
"methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
@ -9589,6 +10095,16 @@
"lower-case": "^1.1.1"
}
},
"node-fetch": {
"version": "1.6.3",
"resolved": "http://registry.npmjs.org/node-fetch/-/node-fetch-1.6.3.tgz",
"integrity": "sha1-3CNO3WSJmC1Y6PDbT2lQKavNjAQ=",
"dev": true,
"requires": {
"encoding": "^0.1.11",
"is-stream": "^1.0.1"
}
},
"node-forge": {
"version": "0.7.5",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.5.tgz",
@ -10047,6 +10563,82 @@
"mimic-fn": "^1.0.0"
}
},
"opencollective": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/opencollective/-/opencollective-1.0.3.tgz",
"integrity": "sha1-ruY3K8KBRFg2kMPKja7PwSDdDvE=",
"dev": true,
"requires": {
"babel-polyfill": "6.23.0",
"chalk": "1.1.3",
"inquirer": "3.0.6",
"minimist": "1.2.0",
"node-fetch": "1.6.3",
"opn": "4.0.2"
},
"dependencies": {
"ansi-escapes": {
"version": "1.4.0",
"resolved": "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz",
"integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=",
"dev": true
},
"chardet": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz",
"integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=",
"dev": true
},
"external-editor": {
"version": "2.2.0",
"resolved": "http://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz",
"integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==",
"dev": true,
"requires": {
"chardet": "^0.4.0",
"iconv-lite": "^0.4.17",
"tmp": "^0.0.33"
}
},
"inquirer": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.0.6.tgz",
"integrity": "sha1-4EqqnQW3o8ubD0B9BDdfBEcZA0c=",
"dev": true,
"requires": {
"ansi-escapes": "^1.1.0",
"chalk": "^1.0.0",
"cli-cursor": "^2.1.0",
"cli-width": "^2.0.0",
"external-editor": "^2.0.1",
"figures": "^2.0.0",
"lodash": "^4.3.0",
"mute-stream": "0.0.7",
"run-async": "^2.2.0",
"rx": "^4.1.0",
"string-width": "^2.0.0",
"strip-ansi": "^3.0.0",
"through": "^2.3.6"
}
},
"minimist": {
"version": "1.2.0",
"resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true
},
"opn": {
"version": "4.0.2",
"resolved": "http://registry.npmjs.org/opn/-/opn-4.0.2.tgz",
"integrity": "sha1-erwi5kTf9jsKltWrfyeQwPAavJU=",
"dev": true,
"requires": {
"object-assign": "^4.0.1",
"pinkie-promise": "^2.0.0"
}
}
}
},
"opn": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/opn/-/opn-5.4.0.tgz",
@ -10087,6 +10679,66 @@
"wordwrap": "~1.0.0"
}
},
"ora": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ora/-/ora-3.0.0.tgz",
"integrity": "sha512-LBS97LFe2RV6GJmXBi6OKcETKyklHNMV0xw7BtsVn2MlsgsydyZetSCbCANr+PFLmDyv4KV88nn0eCKza665Mg==",
"dev": true,
"requires": {
"chalk": "^2.3.1",
"cli-cursor": "^2.1.0",
"cli-spinners": "^1.1.0",
"log-symbols": "^2.2.0",
"strip-ansi": "^4.0.0",
"wcwidth": "^1.0.1"
},
"dependencies": {
"ansi-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
"dev": true
},
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "^1.9.0"
}
},
"chalk": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
"integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
}
},
"strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"dev": true,
"requires": {
"ansi-regex": "^3.0.0"
}
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
}
}
},
"original": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz",
@ -10379,6 +11031,42 @@
"pinkie": "^2.0.0"
}
},
"pkg-conf": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz",
"integrity": "sha1-ISZRTKbyq/69FoWW3xi6V4Z/AFg=",
"dev": true,
"requires": {
"find-up": "^2.0.0",
"load-json-file": "^4.0.0"
},
"dependencies": {
"load-json-file": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
"integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=",
"dev": true,
"requires": {
"graceful-fs": "^4.1.2",
"parse-json": "^4.0.0",
"pify": "^3.0.0",
"strip-bom": "^3.0.0"
}
},
"pify": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
"integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
"dev": true
},
"strip-bom": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
"integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
"dev": true
}
}
},
"pkg-dir": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz",
@ -10411,6 +11099,12 @@
"resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz",
"integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA=="
},
"pofile": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/pofile/-/pofile-1.0.11.tgz",
"integrity": "sha512-Vy9eH1dRD9wHjYt/QqXcTz+RnX/zg53xK+KljFSX30PvdDMb2z+c6uDUeblUGqqJgz3QFsdlA0IJvHziPmWtQg==",
"dev": true
},
"portfinder": {
"version": "1.0.19",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.19.tgz",
@ -10624,6 +11318,15 @@
"integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=",
"dev": true
},
"pseudolocale": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/pseudolocale/-/pseudolocale-1.1.0.tgz",
"integrity": "sha512-OZ8I/hwYEJ3beN3IEcNnt8EpcqblH0/x23hulKBXjs+WhTTEle+ijCHCkh2bd+cIIeCuCwSCbBe93IthGG6hLw==",
"dev": true,
"requires": {
"commander": "*"
}
},
"pseudomap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
@ -10712,6 +11415,12 @@
"integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=",
"dev": true
},
"ramda": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/ramda/-/ramda-0.25.0.tgz",
"integrity": "sha512-GXpfrYVPwx3K7RQ6aYT8KPS8XViSXUVJT1ONhoKPE9VAleW42YE+U+8VEyGWt41EnEQW7gwecYJriTI0pKoecQ==",
"dev": true
},
"randexp": {
"version": "0.4.6",
"resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz",
@ -10834,8 +11543,7 @@
"react-is": {
"version": "16.6.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.6.0.tgz",
"integrity": "sha512-q8U7k0Fi7oxF1HvQgyBjPwDXeMplEsArnKt2iYhuIF86+GBbgLHdAmokL3XUFjTd7Q363OSNG55FOGUdONVn1g==",
"dev": true
"integrity": "sha512-q8U7k0Fi7oxF1HvQgyBjPwDXeMplEsArnKt2iYhuIF86+GBbgLHdAmokL3XUFjTd7Q363OSNG55FOGUdONVn1g=="
},
"react-lifecycles-compat": {
"version": "3.0.4",
@ -11628,6 +12336,12 @@
"aproba": "^1.1.1"
}
},
"rx": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz",
"integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=",
"dev": true
},
"rxjs": {
"version": "6.3.3",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz",
@ -13387,6 +14101,12 @@
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
"dev": true
},
"typescript": {
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz",
"integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==",
"dev": true
},
"uglify-js": {
"version": "3.4.9",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz",
@ -13834,6 +14554,15 @@
"minimalistic-assert": "^1.0.0"
}
},
"wcwidth": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
"integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=",
"dev": true,
"requires": {
"defaults": "^1.0.3"
}
},
"webidl-conversions": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",

View File

@ -6,21 +6,27 @@
"scripts": {
"start": "webpack-dev-server --config ./webpack.config.js --mode development",
"test": "jest --watch --coverage",
"lint": "./node_modules/eslint/bin/eslint.js src/**/*.js src/**/*.jsx"
"lint": "./node_modules/eslint/bin/eslint.js src/**/*.js src/**/*.jsx",
"add-locale": "lingui add-locale",
"extract-strings": "lingui extract",
"compile-strings": "lingui compile"
},
"keywords": [],
"author": "",
"license": "Apache",
"devDependencies": {
"@babel/core": "^7.1.2",
"@babel/core": "^7.2.0",
"@babel/plugin-proposal-class-properties": "^7.1.0",
"@babel/polyfill": "^7.0.0",
"@babel/preset-env": "^7.1.0",
"@babel/preset-react": "^7.0.0",
"@lingui/cli": "^2.7.2",
"@lingui/macro": "^2.7.2",
"babel-core": "^7.0.0-bridge.0",
"babel-eslint": "^10.0.1",
"babel-jest": "^23.6.0",
"babel-loader": "^8.0.4",
"babel-plugin-macros": "^2.4.2",
"css-loader": "^1.0.0",
"enzyme": "^3.7.0",
"enzyme-adapter-react-16": "^1.6.0",
@ -40,6 +46,7 @@
"webpack-dev-server": "^3.1.4"
},
"dependencies": {
"@lingui/react": "^2.7.2",
"@patternfly/patternfly-next": "^1.0.84",
"@patternfly/react-core": "^1.37.2",
"@patternfly/react-icons": "^2.9.1",

View File

@ -1,4 +1,6 @@
import React, { Fragment } from 'react';
import { I18nProvider, I18n } from '@lingui/react';
import { t } from '@lingui/macro';
import {
Redirect,
Switch,
@ -53,6 +55,18 @@ 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 };
// This spits out the language and the region. Example: es-US
const language = (navigator.languages && navigator.languages[0])
|| navigator.language
|| navigator.userLanguage;
const languageWithoutRegionCode = language.toLowerCase().split(/[_-]+/)[0];
const SideNavItems = ({ items, history }) => {
const currentPath = history.location.pathname.split('/')[1];
let activeGroup;
@ -130,195 +144,201 @@ class App extends React.Component {
);
return (
<Fragment>
<BackgroundImage
src={{
[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'
}}
/>
<Switch>
<ConditionalRedirect shouldRedirect={() => api.isAuthenticated()} redirectPath="/" path="/login" component={() => <Login logo={logo} loginInfo={loginInfo} />} />
<Fragment>
<Page
header={(
<PageHeader
logo={<TowerLogo onClick={this.onLogoClick} />}
toolbar={PageToolbar}
showNavToggle
onNavToggle={this.onNavToggle}
/>
)}
sidebar={(
<PageSidebar
isNavOpen={isNavOpen}
nav={(
<Nav aria-label="Primary Navigation">
<NavList>
<SideNavItems
history={history}
items={[
{
groupName: 'views',
title: 'Views',
routes: [
{
path: 'home',
title: 'Dashboard'
},
{
path: 'jobs',
title: 'Jobs'
},
{
path: 'schedules',
title: 'Schedules'
},
{
path: 'portal',
title: 'Portal Mode'
},
]
},
{
groupName: 'resources',
title: 'Resources',
routes: [
{
path: 'templates',
title: 'Templates'
},
{
path: 'credentials',
title: 'Credentials'
},
{
path: 'projects',
title: 'Projects'
},
{
path: 'inventories',
title: 'Inventories'
},
{
path: 'inventory_scripts',
title: 'Inventory Scripts'
}
]
},
{
groupName: 'access',
title: 'Access',
routes: [
{
path: 'organizations',
title: 'Organizations'
},
{
path: 'users',
title: 'Users'
},
{
path: 'teams',
title: 'Teams'
}
]
},
{
groupName: 'administration',
title: 'Administration',
routes: [
{
path: 'credential_types',
title: 'Credential Types',
},
{
path: 'notification_templates',
title: 'Notifications'
},
{
path: 'management_jobs',
title: 'Management Jobs'
},
{
path: 'instance_groups',
title: 'Instance Groups'
},
{
path: 'applications',
title: 'Integrations'
}
]
},
{
groupName: 'settings',
title: 'Settings',
routes: [
{
path: 'auth_settings',
title: 'Authentication',
},
{
path: 'jobs_settings',
title: 'Jobs'
},
{
path: 'system_settings',
title: 'System'
},
{
path: 'ui_settings',
title: 'User Interface'
},
{
path: 'license',
title: 'License'
}
]
}
]}
/>
</NavList>
</Nav>
)}
/>
)}
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>
</Switch>
</Fragment>
<I18nProvider language={languageWithoutRegionCode} catalogs={catalogs}>
<Fragment>
<BackgroundImage
src={{
[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'
}}
/>
<Switch>
<ConditionalRedirect shouldRedirect={() => api.isAuthenticated()} redirectPath="/" path="/login" component={() => <Login logo={logo} loginInfo={loginInfo} />} />
<Fragment>
<Page
header={(
<PageHeader
logo={<TowerLogo onClick={this.onLogoClick} />}
toolbar={PageToolbar}
showNavToggle
onNavToggle={this.onNavToggle}
/>
)}
sidebar={(
<PageSidebar
isNavOpen={isNavOpen}
nav={(
<I18n>
{({ i18n }) => (
<Nav aria-label={i18n._(t`Primary Navigation`)}>
<NavList>
<SideNavItems
history={history}
items={[
{
groupName: 'views',
title: i18n._('Views'),
routes: [
{
path: 'home',
title: i18n._('Dashboard')
},
{
path: 'jobs',
title: i18n._('Jobs')
},
{
path: 'schedules',
title: i18n._('Schedules')
},
{
path: 'portal',
title: i18n._('Portal Mode')
},
]
},
{
groupName: 'resources',
title: i18n._('Resources'),
routes: [
{
path: 'templates',
title: i18n._('Templates')
},
{
path: 'credentials',
title: i18n._('Credentials')
},
{
path: 'projects',
title: i18n._('Projects')
},
{
path: 'inventories',
title: i18n._('Inventories')
},
{
path: 'inventory_scripts',
title: i18n._('Inventory Scripts')
}
]
},
{
groupName: 'access',
title: i18n._('Access'),
routes: [
{
path: 'organizations',
title: i18n._('Organizations')
},
{
path: 'users',
title: i18n._('Users')
},
{
path: 'teams',
title: i18n._('Teams')
}
]
},
{
groupName: 'administration',
title: i18n._('Administration'),
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')
},
{
path: 'applications',
title: i18n._('Integrations')
}
]
},
{
groupName: 'settings',
title: i18n._('Settings'),
routes: [
{
path: 'auth_settings',
title: i18n._('Authentication'),
},
{
path: 'jobs_settings',
title: i18n._('Jobs')
},
{
path: 'system_settings',
title: i18n._('System')
},
{
path: 'ui_settings',
title: i18n._('User Interface')
},
{
path: 'license',
title: i18n._('License')
}
]
}
]}
/>
</NavList>
</Nav>
)}
</I18n>
)}
/>
)}
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>
</Switch>
</Fragment>
</I18nProvider>
);
}
}

View File

@ -1,4 +1,6 @@
import React from 'react';
import { I18n } from '@lingui/react';
import { Trans, t } from '@lingui/macro';
import {
AboutModal,
TextContent,
@ -67,37 +69,43 @@ class About extends React.Component {
const { ansible_version = 'loading', version = 'loading' } = config;
return (
<AboutModal
isOpen={isOpen}
onClose={this.handleModalToggle}
productName="Ansible Tower"
trademark="Copyright 2018 Red Hat, Inc."
brandImageSrc={brandImg}
brandImageAlt="Brand Image"
logoImageSrc={logoImg}
logoImageAlt="AboutModal Logo"
heroImageSrc={heroImg}
>
<pre>
{ this.createSpeechBubble(version) }
{`
\\
\\ ^__^
(oo)\\_______
(__) A )\\
||----w |
|| ||
`}
</pre>
<I18n>
{({ i18n }) => (
<AboutModal
isOpen={isOpen}
onClose={this.handleModalToggle}
productName="Ansible Tower"
trademark={i18n._(t`Copyright 2018 Red Hat, Inc.`)}
brandImageSrc={brandImg}
brandImageAlt={i18n._(t`Brand Image`)}
logoImageSrc={logoImg}
logoImageAlt={i18n._(t`AboutModal Logo`)}
heroImageSrc={heroImg}
>
<pre>
{ this.createSpeechBubble(version) }
{`
\\
\\ ^__^
(oo)\\_______
(__) A )\\
||----w |
|| ||
`}
</pre>
<TextContent>
<TextList component="dl">
<TextListItem component="dt">Ansible Version</TextListItem>
<TextListItem component="dd">{ ansible_version }</TextListItem>
</TextList>
</TextContent>
{ error ? <div>error</div> : ''}
</AboutModal>
<TextContent>
<TextList component="dl">
<TextListItem component="dt">
<Trans>Ansible Version</Trans>
</TextListItem>
<TextListItem component="dd">{ ansible_version }</TextListItem>
</TextList>
</TextContent>
{ error ? <div>error</div> : ''}
</AboutModal>
)}
</I18n>
);
}
}

View File

@ -1,4 +1,6 @@
import React from 'react';
import { I18n } from '@lingui/react';
import { Trans, t } from '@lingui/macro';
import {
Button,
Checkbox,
@ -112,122 +114,126 @@ class DataListToolbar extends React.Component {
};
return (
<div className="awx-toolbar">
<Level>
<LevelItem>
<Toolbar style={{ marginLeft: '20px' }}>
<ToolbarGroup>
<ToolbarItem>
<Checkbox
checked={isAllSelected}
onChange={onSelectAll}
aria-label="Select all"
id="select-all"
/>
</ToolbarItem>
</ToolbarGroup>
<ToolbarGroup>
<ToolbarItem>
<div className="pf-c-input-group">
<Dropdown
onToggle={this.onSearchDropdownToggle}
onSelect={this.onSearchDropdownSelect}
direction={up}
isOpen={isSearchDropdownOpen}
toggle={(
<DropdownToggle
<I18n>
{({ i18n }) => (
<div className="awx-toolbar">
<Level>
<LevelItem>
<Toolbar style={{ marginLeft: '20px' }}>
<ToolbarGroup>
<ToolbarItem>
<Checkbox
checked={isAllSelected}
onChange={onSelectAll}
aria-label={i18n._(t`Select all`)}
id="select-all"
/>
</ToolbarItem>
</ToolbarGroup>
<ToolbarGroup>
<ToolbarItem>
<div className="pf-c-input-group">
<Dropdown
onToggle={this.onSearchDropdownToggle}
onSelect={this.onSearchDropdownSelect}
direction={up}
isOpen={isSearchDropdownOpen}
toggle={(
<DropdownToggle
onToggle={this.onSearchDropdownToggle}
>
{ searchColumnName }
</DropdownToggle>
)}
>
{ searchColumnName }
</DropdownToggle>
)}
>
{columns.filter(({ key }) => key !== searchKey).map(({ key, name }) => (
<DropdownItem key={key} component="button">
{ name }
</DropdownItem>
))}
</Dropdown>
<TextInput
type="search"
aria-label="search text input"
value={searchValue}
onChange={this.handleSearchInputChange}
/>
<Button
variant="tertiary"
aria-label="Search"
onClick={() => onSearch(searchValue)}
>
<i className="fas fa-search" aria-hidden="true" />
</Button>
</div>
</ToolbarItem>
</ToolbarGroup>
<ToolbarGroup>
<ToolbarItem>
<Dropdown
onToggle={this.onSortDropdownToggle}
onSelect={this.onSortDropdownSelect}
direction={up}
isOpen={isSortDropdownOpen}
toggle={(
<DropdownToggle
{columns.filter(({ key }) => key !== searchKey).map(({ key, name }) => (
<DropdownItem key={key} component="button">
{ name }
</DropdownItem>
))}
</Dropdown>
<TextInput
type="search"
aria-label={i18n._(t`Search text input`)}
value={searchValue}
onChange={this.handleSearchInputChange}
/>
<Button
variant="tertiary"
aria-label={i18n._(t`Search`)}
onClick={() => onSearch(searchValue)}
>
<i className="fas fa-search" aria-hidden="true" />
</Button>
</div>
</ToolbarItem>
</ToolbarGroup>
<ToolbarGroup>
<ToolbarItem>
<Dropdown
onToggle={this.onSortDropdownToggle}
onSelect={this.onSortDropdownSelect}
direction={up}
isOpen={isSortDropdownOpen}
toggle={(
<DropdownToggle
onToggle={this.onSortDropdownToggle}
>
{ sortedColumnName }
</DropdownToggle>
)}
>
{ sortedColumnName }
</DropdownToggle>
)}
>
{columns
.filter(({ key, isSortable }) => isSortable && key !== sortedColumnKey)
.map(({ key, name }) => (
<DropdownItem key={key} component="button">
{ name }
</DropdownItem>
))}
</Dropdown>
</ToolbarItem>
<ToolbarItem>
<Button
onClick={() => onSort(sortedColumnKey, sortOrder === 'ascending' ? 'descending' : 'ascending')}
variant="plain"
aria-label="Sort"
>
{displayedSortIcon()}
{columns
.filter(({ key, isSortable }) => isSortable && key !== sortedColumnKey)
.map(({ key, name }) => (
<DropdownItem key={key} component="button">
{ name }
</DropdownItem>
))}
</Dropdown>
</ToolbarItem>
<ToolbarItem>
<Button
onClick={() => onSort(sortedColumnKey, sortOrder === 'ascending' ? 'descending' : 'ascending')}
variant="plain"
aria-label={i18n._(t`Sort`)}
>
{displayedSortIcon()}
</Button>
</ToolbarItem>
</ToolbarGroup>
<ToolbarGroup>
<ToolbarItem>
<Button variant="plain" aria-label={i18n._(t`Expand`)}>
<BarsIcon />
</Button>
</ToolbarItem>
<ToolbarItem>
<Button variant="plain" aria-label={i18n._(t`Collapse`)}>
<EqualsIcon />
</Button>
</ToolbarItem>
</ToolbarGroup>
</Toolbar>
</LevelItem>
<LevelItem>
<Tooltip message={i18n._(t`Delete`)} position="top">
<Button variant="plain" aria-label={i18n._(t`Delete`)}>
<TrashAltIcon />
</Button>
</ToolbarItem>
</ToolbarGroup>
<ToolbarGroup>
<ToolbarItem>
<Button variant="plain" aria-label="Expand">
<BarsIcon />
</Button>
</ToolbarItem>
<ToolbarItem>
<Button variant="plain" aria-label="Collapse">
<EqualsIcon />
</Button>
</ToolbarItem>
</ToolbarGroup>
</Toolbar>
</LevelItem>
<LevelItem>
<Tooltip message="Delete" position="top">
<Button variant="plain" aria-label="Delete">
<TrashAltIcon />
</Button>
</Tooltip>
{addUrl && (
<Link to={addUrl}>
<Button variant="primary" aria-label="Add">
Add
</Button>
</Link>
)}
</LevelItem>
</Level>
</div>
</Tooltip>
{addUrl && (
<Link to={addUrl}>
<Button variant="primary" aria-label={i18n._(t`Add`)}>
<Trans>Add</Trans>
</Button>
</Link>
)}
</LevelItem>
</Level>
</div>
)}
</I18n>
);
}
}

View File

@ -1,4 +1,5 @@
import React, { Component, Fragment } from 'react';
import { Trans } from '@lingui/macro';
import {
Dropdown,
DropdownItem,
@ -22,14 +23,14 @@ class HelpDropdown extends Component {
target="_blank"
key="help"
>
Help
<Trans>Help</Trans>
</DropdownItem>,
<DropdownItem
onClick={() => this.setState({ showAboutModal: true })}
key="about"
>
About
</DropdownItem>,
<Trans>About</Trans>
</DropdownItem>
];
return (

View File

@ -1,4 +1,6 @@
import React from 'react';
import { I18n } from '@lingui/react';
import { t } from '@lingui/macro';
import {
Button,
@ -8,19 +10,23 @@ import {
import { UserIcon } from '@patternfly/react-icons';
const LogoutButton = ({ onDevLogout }) => (
<Button
id="button-logout"
aria-label="Logout"
variant={ButtonVariant.plain}
onClick={onDevLogout}
onKeyDown={event => {
if (event.keyCode === 13) {
onDevLogout();
}
}}
>
<UserIcon />
</Button>
<I18n>
{({ i18n }) => (
<Button
id="button-logout"
aria-label={i18n._(t`Logout`)}
variant={ButtonVariant.plain}
onClick={onDevLogout}
onKeyDown={event => {
if (event.keyCode === 13) {
onDevLogout();
}
}}
>
<UserIcon />
</Button>
)}
</I18n>
);
export default LogoutButton;

View File

@ -1,4 +1,6 @@
import React, { Component } from 'react';
import { I18n } from '@lingui/react';
import { Trans, t } from '@lingui/macro';
import {
Button,
Dropdown,
@ -126,93 +128,106 @@ class Pagination extends Component {
};
return (
<div className="awx-pagination">
<Level>
<LevelItem>
<Dropdown
onToggle={this.onTogglePageSize}
onSelect={this.onSelectPageSize}
direction={up}
isOpen={isOpen}
toggle={(
<DropdownToggle
onToggle={this.onTogglePageSize}>
{ page_size }
</DropdownToggle>
)}>
{opts.map(option => (
<DropdownItem key={option} component="button">
{ option }
</DropdownItem>
))}
</Dropdown> Per Page
</LevelItem>
<LevelItem>
<Split gutter="md" className="pf-u-display-flex pf-u-align-items-center">
<SplitItem>{itemMin} - {itemMax } of { count }</SplitItem>
<SplitItem>
<div className="pf-c-input-group">
<Button
variant="tertiary"
aria-label="first"
style={isOnFirst ? disabledStyle : {}}
isDisabled={isOnFirst}
onClick={this.onFirst}>
<i className="fas fa-angle-double-left"></i>
</Button>
<Button
variant="tertiary"
aria-label="previous"
style={isOnFirst ? disabledStyle : {}}
isDisabled={isOnFirst}
onClick={this.onPrevious}>
<i className="fas fa-angle-left"></i>
</Button>
</div>
</SplitItem>
<SplitItem isMain>
<form onSubmit={this.onSubmit}>
Page <TextInput
isDisabled={pageCount === 1}
aria-label="Page Number"
style={{
height: '30px',
width: '30px',
textAlign: 'center',
padding: '0',
margin: '0',
...(pageCount === 1 ? disabledStyle : {})
}}
value={value}
type="text"
onChange={this.onPageChange}
/> of { pageCount }
</form>
</SplitItem>
<SplitItem>
<div className="pf-c-input-group">
<Button
variant="tertiary"
aria-label="next"
style={isOnLast ? disabledStyle : {}}
isDisabled={isOnLast}
onClick={this.onNext}>
<i className="fas fa-angle-right"></i>
</Button>
<Button
variant="tertiary"
aria-label="last"
style={isOnLast ? disabledStyle : {}}
isDisabled={isOnLast}
onClick={this.onLast}>
<i className="fas fa-angle-double-right"></i>
</Button>
</div>
</SplitItem>
</Split>
</LevelItem>
</Level>
</div>
<I18n>
{({ i18n }) => (
<div className="awx-pagination">
<Level>
<LevelItem>
<Dropdown
onToggle={this.onTogglePageSize}
onSelect={this.onSelectPageSize}
direction={up}
isOpen={isOpen}
toggle={(
<DropdownToggle
onToggle={this.onTogglePageSize}>
{ page_size }
</DropdownToggle>
)}>
{opts.map(option => (
<DropdownItem key={option} component="button">
{ option }
</DropdownItem>
))}
</Dropdown>
<Trans>Per Page</Trans>
</LevelItem>
<LevelItem>
<Split gutter="md" className="pf-u-display-flex pf-u-align-items-center">
<SplitItem>
<Trans>{ itemMin } - { itemMax } of { count }</Trans>
</SplitItem>
<SplitItem>
<div className="pf-c-input-group">
<Button
variant="tertiary"
aria-label={i18n._(t`First`)}
style={isOnFirst ? disabledStyle : {}}
isDisabled={isOnFirst}
onClick={this.onFirst}
>
<i className="fas fa-angle-double-left" />
</Button>
<Button
variant="tertiary"
aria-label={i18n._(t`Previous`)}
style={isOnFirst ? disabledStyle : {}}
isDisabled={isOnFirst}
onClick={this.onPrevious}
>
<i className="fas fa-angle-left" />
</Button>
</div>
</SplitItem>
<SplitItem isMain>
<form onSubmit={this.onSubmit}>
<Trans>
Page <TextInput
isDisabled={pageCount === 1}
aria-label={i18n._(t`Page Number`)}
style={{
height: '30px',
width: '30px',
textAlign: 'center',
padding: '0',
margin: '0',
...(pageCount === 1 ? disabledStyle : {})
}}
value={value}
type="text"
onChange={this.onPageChange}
/> of { pageCount }
</Trans>
</form>
</SplitItem>
<SplitItem>
<div className="pf-c-input-group">
<Button
variant="tertiary"
aria-label={i18n._(t`Next`)}
style={isOnLast ? disabledStyle : {}}
isDisabled={isOnLast}
onClick={this.onNext}
>
<i className="fas fa-angle-right" />
</Button>
<Button
variant="tertiary"
aria-label={i18n._(t`Last`)}
style={isOnLast ? disabledStyle : {}}
isDisabled={isOnLast}
onClick={this.onLast}
>
<i className="fas fa-angle-double-right" />
</Button>
</div>
</SplitItem>
</Split>
</LevelItem>
</Level>
</div>
)}
</I18n>
);
}
}

View File

@ -1,5 +1,7 @@
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import { I18n } from '@lingui/react';
import { t } from '@lingui/macro';
import { Brand } from '@patternfly/react-core';
import TowerLogoHeader from '../../../images/tower-logo-header.svg';
@ -39,15 +41,19 @@ class TowerLogo extends Component {
}
return (
<Brand
src={src}
alt="Tower Brand Image"
onMouseOut={this.onHover}
onMouseOver={this.onHover}
onBlur={this.onHover}
onFocus={this.onHover}
onClick={this.onClick}
/>
<I18n>
{({ i18n }) => (
<Brand
src={src}
alt={i18n._(t`Tower Brand Image`)}
onMouseOut={this.onHover}
onMouseOver={this.onHover}
onBlur={this.onHover}
onFocus={this.onHover}
onClick={this.onClick}
/>
)}
</I18n>
);
}
}

View File

@ -1,4 +1,5 @@
import React, { Component, Fragment } from 'react';
import { Trans } from '@lingui/macro';
import {
PageSection,
PageSectionVariants,
@ -11,7 +12,11 @@ class Applications extends Component {
return (
<Fragment>
<PageSection variant={light} className="pf-m-condensed"><Title size="2xl">Applications</Title></PageSection>
<PageSection variant={light} className="pf-m-condensed">
<Title size="2xl">
<Trans>Applications</Trans>
</Title>
</PageSection>
<PageSection variant={medium} />
</Fragment>
);

View File

@ -1,4 +1,5 @@
import React, { Component, Fragment } from 'react';
import { Trans } from '@lingui/macro';
import {
PageSection,
PageSectionVariants,
@ -11,7 +12,11 @@ class AuthSettings extends Component {
return (
<Fragment>
<PageSection variant={light} className="pf-m-condensed"><Title size="2xl">Authentication Settings</Title></PageSection>
<PageSection variant={light} className="pf-m-condensed">
<Title size="2xl">
<Trans>Authentication Settings</Trans>
</Title>
</PageSection>
<PageSection variant={medium} />
</Fragment>
);

View File

@ -1,4 +1,5 @@
import React, { Component, Fragment } from 'react';
import { Trans } from '@lingui/macro';
import {
PageSection,
PageSectionVariants,
@ -11,7 +12,11 @@ class CredentialTypes extends Component {
return (
<Fragment>
<PageSection variant={light} className="pf-m-condensed"><Title size="2xl">Credential Types</Title></PageSection>
<PageSection variant={light} className="pf-m-condensed">
<Title size="2xl">
<Trans>Credential Types</Trans>
</Title>
</PageSection>
<PageSection variant={medium} />
</Fragment>
);

View File

@ -1,4 +1,5 @@
import React, { Component, Fragment } from 'react';
import { Trans } from '@lingui/macro';
import {
PageSection,
PageSectionVariants,
@ -11,7 +12,11 @@ class Credentials extends Component {
return (
<Fragment>
<PageSection variant={light} className="pf-m-condensed"><Title size="2xl">Credentials</Title></PageSection>
<PageSection variant={light} className="pf-m-condensed">
<Title size="2xl">
<Trans>Credentials</Trans>
</Title>
</PageSection>
<PageSection variant={medium} />
</Fragment>
);

View File

@ -1,4 +1,5 @@
import React, { Component, Fragment } from 'react';
import { Trans } from '@lingui/macro';
import {
PageSection,
PageSectionVariants,
@ -11,7 +12,11 @@ class Dashboard extends Component {
return (
<Fragment>
<PageSection variant={light} className="pf-m-condensed"><Title size="2xl">Dashboard</Title></PageSection>
<PageSection variant={light} className="pf-m-condensed">
<Title size="2xl">
<Trans>Dashboard</Trans>
</Title>
</PageSection>
<PageSection variant={medium} />
</Fragment>
);

View File

@ -1,4 +1,5 @@
import React, { Component, Fragment } from 'react';
import { Trans } from '@lingui/macro';
import {
PageSection,
PageSectionVariants,
@ -11,7 +12,11 @@ class InstanceGroups extends Component {
return (
<Fragment>
<PageSection variant={light} className="pf-m-condensed"><Title size="2xl">Instance Groups</Title></PageSection>
<PageSection variant={light} className="pf-m-condensed">
<Title size="2xl">
<Trans>Instance Groups</Trans>
</Title>
</PageSection>
<PageSection variant={medium} />
</Fragment>
);

View File

@ -1,4 +1,5 @@
import React, { Component, Fragment } from 'react';
import { Trans } from '@lingui/macro';
import {
PageSection,
PageSectionVariants,
@ -11,7 +12,11 @@ class Inventories extends Component {
return (
<Fragment>
<PageSection variant={light} className="pf-m-condensed"><Title size="2xl">Inventories</Title></PageSection>
<PageSection variant={light} className="pf-m-condensed">
<Title size="2xl">
<Trans>Inventories</Trans>
</Title>
</PageSection>
<PageSection variant={medium} />
</Fragment>
);

View File

@ -1,4 +1,5 @@
import React, { Component, Fragment } from 'react';
import { Trans } from '@lingui/macro';
import {
PageSection,
PageSectionVariants,
@ -11,7 +12,11 @@ class InventoryScripts extends Component {
return (
<Fragment>
<PageSection variant={light} className="pf-m-condensed"><Title size="2xl">Inventory Scripts</Title></PageSection>
<PageSection variant={light} className="pf-m-condensed">
<Title size="2xl">
<Trans>Inventory Scripts</Trans>
</Title>
</PageSection>
<PageSection variant={medium} />
</Fragment>
);

View File

@ -1,4 +1,5 @@
import React, { Component, Fragment } from 'react';
import { Trans } from '@lingui/macro';
import {
PageSection,
PageSectionVariants,
@ -11,7 +12,11 @@ class Jobs extends Component {
return (
<Fragment>
<PageSection variant={light} className="pf-m-condensed"><Title size="2xl">Jobz</Title></PageSection>
<PageSection variant={light} className="pf-m-condensed">
<Title size="2xl">
<Trans>Jobs</Trans>
</Title>
</PageSection>
<PageSection variant={medium} />
</Fragment>
);

View File

@ -1,4 +1,5 @@
import React, { Component, Fragment } from 'react';
import { Trans } from '@lingui/macro';
import {
PageSection,
PageSectionVariants,
@ -11,7 +12,11 @@ class JobsSettings extends Component {
return (
<Fragment>
<PageSection variant={light} className="pf-m-condensed"><Title size="2xl">Jobs Settings</Title></PageSection>
<PageSection variant={light} className="pf-m-condensed">
<Title size="2xl">
<Trans>Jobs Settings</Trans>
</Title>
</PageSection>
<PageSection variant={medium} />
</Fragment>
);

View File

@ -1,4 +1,5 @@
import React, { Component, Fragment } from 'react';
import { Trans } from '@lingui/macro';
import {
PageSection,
PageSectionVariants,
@ -11,7 +12,11 @@ class License extends Component {
return (
<Fragment>
<PageSection variant={light} className="pf-m-condensed"><Title size="2xl">License</Title></PageSection>
<PageSection variant={light} className="pf-m-condensed">
<Title size="2xl">
<Trans>License</Trans>
</Title>
</PageSection>
<PageSection variant={medium} />
</Fragment>
);

View File

@ -1,5 +1,7 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router-dom';
import { I18n } from '@lingui/react';
import { t } from '@lingui/macro';
import {
LoginForm,
LoginPage,
@ -54,34 +56,33 @@ class AtLogin extends Component {
const { username, password, isValidPassword } = this.state;
const { logo, alt } = this.props;
const logoSrc = logo ? `data:image/jpeg;${logo}` : towerLogo;
const logoAlt = alt || 'Ansible Tower';
const LOGIN_ERROR_MESSAGE = 'Invalid username or password. Please try again.';
const LOGIN_TITLE = 'Welcome to Ansible Tower! Please Sign In.';
const LOGIN_USERNAME = 'Username';
const LOGIN_PASSWORD = 'Password';
if (api.isAuthenticated()) {
return (<Redirect to="/" />);
}
return (
<LoginPage
mainBrandImgSrc={logoSrc}
mainBrandImgAlt={logoAlt}
loginTitle={LOGIN_TITLE}
>
<LoginForm
usernameLabel={LOGIN_USERNAME}
usernameValue={username}
onChangeUsername={this.handleUsernameChange}
passwordLabel={LOGIN_PASSWORD}
passwordValue={password}
onChangePassword={this.handlePasswordChange}
isValidPassword={isValidPassword}
passwordHelperTextInvalid={LOGIN_ERROR_MESSAGE}
onLoginButtonClick={this.handleSubmit}
/>
</LoginPage>
<I18n>
{({ i18n }) => (
<LoginPage
mainBrandImgSrc={logoSrc}
mainBrandImgAlt={alt || 'Ansible Tower'}
loginTitle={i18n._(t`Welcome to Ansible Tower! Please Sign In.`)}
>
<LoginForm
usernameLabel={i18n._(t`Username`)}
usernameValue={username}
onChangeUsername={this.handleUsernameChange}
passwordLabel={i18n._(t`Password`)}
passwordValue={password}
onChangePassword={this.handlePasswordChange}
isValidPassword={isValidPassword}
passwordHelperTextInvalid={i18n._(t`Invalid username or password. Please try again.`)}
onLoginButtonClick={this.handleSubmit}
/>
</LoginPage>
)}
</I18n>
);
}
}

View File

@ -1,4 +1,5 @@
import React, { Component, Fragment } from 'react';
import { Trans } from '@lingui/macro';
import {
PageSection,
PageSectionVariants,
@ -11,7 +12,11 @@ class ManagementJobs extends Component {
return (
<Fragment>
<PageSection variant={light} className="pf-m-condensed"><Title size="2xl">Management Jobs</Title></PageSection>
<PageSection variant={light} className="pf-m-condensed">
<Title size="2xl">
<Trans>Management Jobs</Trans>
</Title>
</PageSection>
<PageSection variant={medium} />
</Fragment>
);

View File

@ -1,4 +1,5 @@
import React, { Component, Fragment } from 'react';
import { Trans } from '@lingui/macro';
import {
PageSection,
PageSectionVariants,
@ -11,7 +12,11 @@ class NotificationTemplates extends Component {
return (
<Fragment>
<PageSection variant={light} className="pf-m-condensed"><Title size="2xl">Notification Templates</Title></PageSection>
<PageSection variant={light} className="pf-m-condensed">
<Title size="2xl">
<Trans>Notification Templates</Trans>
</Title>
</PageSection>
<PageSection variant={medium} />
</Fragment>
);

View File

@ -1,4 +1,5 @@
import React, { Fragment } from 'react';
import { Trans } from '@lingui/macro';
import {
PageSection,
PageSectionVariants,
@ -49,14 +50,14 @@ const OrganizationBreadcrumb = ({ parentObj, organization, currentTab, location
breadcrumb = (
<Fragment>
{generateCrumb()}
{' > edit'}
<Trans>{' > edit'}</Trans>
</Fragment>
);
} else if (location.pathname.indexOf('add') > -1) {
breadcrumb = (
<Fragment>
{generateCrumb()}
{' > add'}
<Trans>{' > add'}</Trans>
</Fragment>
);
} else {

View File

@ -1,4 +1,6 @@
import React, { Fragment } from 'react';
import { I18n } from '@lingui/react';
import { Trans, t } from '@lingui/macro';
import {
Card,
CardHeader,
@ -62,52 +64,56 @@ const OrganizationDetail = ({
const deleteResourceView = () => (
<Fragment>
{`deleting ${currentTab} association with orgs `}
<Trans>{`deleting ${currentTab} association with orgs `}</Trans>
<Link to={{ pathname: `${match.url}`, search: `?${params.toString()}`, state: { breadcrumb: parentBreadcrumbObj, organization } }}>
{`confirm removal of ${currentTab}/cancel and go back to ${currentTab} view.`}
<Trans>{`confirm removal of ${currentTab}/cancel and go back to ${currentTab} view.`}</Trans>
</Link>
</Fragment>
);
const addResourceView = () => (
<Fragment>
{`adding ${currentTab} `}
<Trans>{`adding ${currentTab} `}</Trans>
<Link to={{ pathname: `${match.url}`, search: `?${params.toString()}`, state: { breadcrumb: parentBreadcrumbObj, organization } }}>
{`save/cancel and go back to ${currentTab} view`}
<Trans>{`save/cancel and go back to ${currentTab} view`}</Trans>
</Link>
</Fragment>
);
const resourceView = () => (
<Fragment>
{`${currentTab} detail view `}
<Trans>{`${currentTab} detail view `}</Trans>
<Link to={{ pathname: `${match.url}/add-resource`, search: `?${params.toString()}`, state: { breadcrumb: parentBreadcrumbObj, organization } }}>
{`add ${currentTab}`}
<Trans>{`add ${currentTab}`}</Trans>
</Link>
{' '}
<Link to={{ pathname: `${match.url}/delete-resources`, search: `?${params.toString()}`, state: { breadcrumb: parentBreadcrumbObj, organization } }}>
{`delete ${currentTab}`}
<Trans>{`delete ${currentTab}`}</Trans>
</Link>
</Fragment>
);
const detailTabs = (tabs) => (
<ToolbarSection aria-label="Organization detail tabs">
<ToolbarGroup className="at-c-tabs">
{tabs.map(tab => (
<DetailTab
key={tab}
tab={tab}
location={location}
match={match}
currentTab={currentTab}
breadcrumb={parentBreadcrumbObj}
>
{getTabName(tab)}
</DetailTab>
))}
</ToolbarGroup>
</ToolbarSection>
<I18n>
{({ i18n }) => (
<ToolbarSection aria-label={i18n._(t`Organization detail tabs`)}>
<ToolbarGroup className="at-c-tabs">
{tabs.map(tab => (
<DetailTab
key={tab}
tab={tab}
location={location}
match={match}
currentTab={currentTab}
breadcrumb={parentBreadcrumbObj}
>
{getTabName(tab)}
</DetailTab>
))}
</ToolbarGroup>
</ToolbarSection>
)}
</I18n>
);
return (

View File

@ -1,4 +1,5 @@
import React from 'react';
import { Trans } from '@lingui/macro';
import {
Card,
CardBody,
@ -16,9 +17,9 @@ const OrganizationEdit = ({ match, parentBreadcrumbObj, organization }) => {
<PageSection variant={medium}>
<Card className="at-c-orgPane">
<CardBody>
{'edit view '}
<Trans>edit view </Trans>
<Link to={{ pathname: `/organizations/${match.params.id}`, state: { breadcrumb: parentBreadcrumbObj, organization } }}>
{'save/cancel and go back to view'}
<Trans>save/cancel and go back to view</Trans>
</Link>
</CardBody>
</Card>

View File

@ -1,4 +1,6 @@
import React from 'react';
import { I18n } from '@lingui/react';
import { Trans, t } from '@lingui/macro';
import {
Badge,
Checkbox,
@ -20,12 +22,16 @@ export default ({
}) => (
<li key={itemId} className="pf-c-data-list__item" aria-labelledby="check-action-item1">
<div className="pf-c-data-list__check">
<Checkbox
checked={isSelected}
onChange={onSelect}
aria-label={`select organization ${itemId}`}
id={`select-organization-${itemId}`}
/>
<I18n>
{({ i18n }) => (
<Checkbox
checked={isSelected}
onChange={onSelect}
aria-label={i18n._(t`select organization ${itemId}`)}
id={`select-organization-${itemId}`}
/>
)}
</I18n>
</div>
<div className="pf-c-data-list__cell">
<span id="check-action-item1">
@ -41,7 +47,7 @@ export default ({
</div>
<div className="pf-c-data-list__cell">
<Link to={`${detailUrl}?tab=users`}>
Users
<Trans>Users</Trans>
</Link>
<Badge isRead>
{' '}
@ -49,7 +55,7 @@ export default ({
{' '}
</Badge>
<Link to={`${detailUrl}?tab=teams`}>
Teams
<Trans>Teams</Trans>
</Link>
<Badge isRead>
{' '}
@ -57,7 +63,7 @@ export default ({
{' '}
</Badge>
<Link to={`${detailUrl}?tab=admins`}>
Admins
<Trans>Admins</Trans>
</Link>
<Badge isRead>
{' '}

View File

@ -1,4 +1,5 @@
import React, { Fragment } from 'react';
import { Trans } from '@lingui/macro';
import {
PageSection,
PageSectionVariants,
@ -10,7 +11,9 @@ const { light, medium } = PageSectionVariants;
const OrganizationView = () => (
<Fragment>
<PageSection variant={light} className="pf-m-condensed">
<Title size="2xl">Organization Add</Title>
<Title size="2xl">
<Trans>Organization Add</Trans>
</Title>
</PageSection>
<PageSection variant={medium}>
This is the add view

View File

@ -1,4 +1,5 @@
import React, { Component, Fragment } from 'react';
import { i18nMark } from '@lingui/react';
import {
Switch,
Route
@ -58,7 +59,7 @@ class OrganizationView extends Component {
}
const { name } = data;
if (parentBreadcrumbObj === 'loading') {
this.setState({ parentBreadcrumbObj: [{ name: 'Organizations', url: '/organizations' }, { name, url: match.url }] });
this.setState({ parentBreadcrumbObj: [{ name: i18nMark('Organizations'), url: '/organizations' }, { name, url: match.url }] });
}
} catch (err) {
this.setState({ error: true });

View File

@ -5,6 +5,8 @@ import React, {
import {
withRouter
} from 'react-router-dom';
import { I18n, i18nMark } from '@lingui/react';
import { Trans, t } from '@lingui/macro';
import {
PageSection,
PageSectionVariants,
@ -25,9 +27,9 @@ import {
class Organizations extends Component {
columns = [
{ name: 'Name', key: 'name', isSortable: true },
{ name: 'Modified', key: 'modified', isSortable: true, isNumeric: true },
{ name: 'Created', key: 'created', isSortable: true, isNumeric: true },
{ name: i18nMark('Name'), key: 'name', isSortable: true },
{ name: i18nMark('Modified'), key: 'modified', isSortable: true, isNumeric: true },
{ name: i18nMark('Created'), key: 'created', isSortable: true, isNumeric: true },
];
defaultParams = {
@ -184,12 +186,14 @@ class Organizations extends Component {
selected,
} = this.state;
const { match } = this.props;
const parentBreadcrumb = { name: 'Organizations', url: match.url };
const parentBreadcrumb = { name: i18nMark('Organizations'), url: match.url };
return (
<Fragment>
<PageSection variant={light} className="pf-m-condensed">
<Title size="2xl">Organizations</Title>
<Title size="2xl">
<Trans>Organizations</Trans>
</Title>
</PageSection>
<PageSection variant={medium}>
<DataListToolbar
@ -202,22 +206,26 @@ class Organizations extends Component {
onSort={this.onSort}
onSelectAll={this.onSelectAll}
/>
<ul className="pf-c-data-list" aria-label="Organizations List">
{ results.map(o => (
<OrganizationListItem
key={o.id}
itemId={o.id}
name={o.name}
detailUrl={`${match.url}/${o.id}`}
parentBreadcrumb={parentBreadcrumb}
userCount={o.summary_fields.related_field_counts.users}
teamCount={o.summary_fields.related_field_counts.teams}
adminCount={o.summary_fields.related_field_counts.admins}
isSelected={selected.includes(o.id)}
onSelect={() => this.onSelect(o.id)}
/>
))}
</ul>
<I18n>
{({ i18n }) => (
<ul className="pf-c-data-list" aria-label={i18n._(t`Organizations List`)}>
{ results.map(o => (
<OrganizationListItem
key={o.id}
itemId={o.id}
name={o.name}
detailUrl={`${match.url}/${o.id}`}
parentBreadcrumb={parentBreadcrumb}
userCount={o.summary_fields.related_field_counts.users}
teamCount={o.summary_fields.related_field_counts.teams}
adminCount={o.summary_fields.related_field_counts.admins}
isSelected={selected.includes(o.id)}
onSelect={() => this.onSelect(o.id)}
/>
))}
</ul>
)}
</I18n>
<Pagination
count={count}
page={page}

View File

@ -1,4 +1,5 @@
import React, { Component, Fragment } from 'react';
import { Trans } from '@lingui/macro';
import {
PageSection,
PageSectionVariants,
@ -11,7 +12,11 @@ class Portal extends Component {
return (
<Fragment>
<PageSection variant={light} className="pf-m-condensed"><Title size="2xl">My View</Title></PageSection>
<PageSection variant={light} className="pf-m-condensed">
<Title size="2xl">
<Trans>My View</Trans>
</Title>
</PageSection>
<PageSection variant={medium} />
</Fragment>
);

View File

@ -1,4 +1,5 @@
import React, { Component, Fragment } from 'react';
import { Trans } from '@lingui/macro';
import {
PageSection,
PageSectionVariants,
@ -11,7 +12,11 @@ class Projects extends Component {
return (
<Fragment>
<PageSection variant={light} className="pf-m-condensed"><Title size="2xl">Projects</Title></PageSection>
<PageSection variant={light} className="pf-m-condensed">
<Title size="2xl">
<Trans>Projects</Trans>
</Title>
</PageSection>
<PageSection variant={medium} />
</Fragment>
);

View File

@ -1,4 +1,5 @@
import React, { Component, Fragment } from 'react';
import { Trans } from '@lingui/macro';
import {
PageSection,
PageSectionVariants,
@ -11,7 +12,11 @@ class Schedules extends Component {
return (
<Fragment>
<PageSection variant={light} className="pf-m-condensed"><Title size="2xl">Schedules</Title></PageSection>
<PageSection variant={light} className="pf-m-condensed">
<Title size="2xl">
<Trans>Schedules</Trans>
</Title>
</PageSection>
<PageSection variant={medium} />
</Fragment>
);

View File

@ -1,4 +1,5 @@
import React, { Component, Fragment } from 'react';
import { Trans } from '@lingui/macro';
import {
PageSection,
PageSectionVariants,
@ -11,7 +12,11 @@ class SystemSettings extends Component {
return (
<Fragment>
<PageSection variant={light} className="pf-m-condensed"><Title size="2xl">System Settings</Title></PageSection>
<PageSection variant={light} className="pf-m-condensed">
<Title size="2xl">
<Trans>System Settings</Trans>
</Title>
</PageSection>
<PageSection variant={medium} />
</Fragment>
);

View File

@ -1,4 +1,5 @@
import React, { Component, Fragment } from 'react';
import { Trans } from '@lingui/macro';
import {
PageSection,
PageSectionVariants,
@ -11,7 +12,11 @@ class Teams extends Component {
return (
<Fragment>
<PageSection variant={light} className="pf-m-condensed"><Title size="2xl">Teams</Title></PageSection>
<PageSection variant={light} className="pf-m-condensed">
<Title size="2xl">
<Trans>Teams</Trans>
</Title>
</PageSection>
<PageSection variant={medium} />
</Fragment>
);

View File

@ -1,4 +1,5 @@
import React, { Component, Fragment } from 'react';
import { Trans } from '@lingui/macro';
import {
PageSection,
PageSectionVariants,
@ -11,7 +12,11 @@ class Templates extends Component {
return (
<Fragment>
<PageSection variant={light} className="pf-m-condensed"><Title size="2xl">Templates</Title></PageSection>
<PageSection variant={light} className="pf-m-condensed">
<Title size="2xl">
<Trans>Templates</Trans>
</Title>
</PageSection>
<PageSection variant={medium} />
</Fragment>
);

View File

@ -1,4 +1,5 @@
import React, { Component, Fragment } from 'react';
import { Trans } from '@lingui/macro';
import {
PageSection,
PageSectionVariants,
@ -11,7 +12,11 @@ class UISettings extends Component {
return (
<Fragment>
<PageSection variant={light} className="pf-m-condensed"><Title size="2xl">User Interface Settings</Title></PageSection>
<PageSection variant={light} className="pf-m-condensed">
<Title size="2xl">
<Trans>User Interface Settings</Trans>
</Title>
</PageSection>
<PageSection variant={medium} />
</Fragment>
);

View File

@ -1,4 +1,5 @@
import React, { Component, Fragment } from 'react';
import { Trans } from '@lingui/macro';
import {
PageSection,
PageSectionVariants,
@ -11,7 +12,11 @@ class Users extends Component {
return (
<Fragment>
<PageSection variant={light} className="pf-m-condensed"><Title size="2xl">Users</Title></PageSection>
<PageSection variant={light} className="pf-m-condensed">
<Title size="2xl">
<Trans>Users</Trans>
</Title>
</PageSection>
<PageSection variant={medium} />
</Fragment>
);

View File

@ -97,5 +97,9 @@ module.exports = {
ws: true
}
]
},
// https://github.com/lingui/js-lingui/issues/408
node: {
fs: 'empty'
}
};