Resolve CI issues

* update prettier dependency to version which works with npm5
  [MIT] https://github.com/prettier/prettier
* remove favicons-webpack-plugin

* autofix linting errors to make tests pass
* add simple favicon using html-webpack-plugin
* remove unused .gitignore lines

Change-Id: I6b7d1116f522a6a1dd5e8455e85f4db958424e7c
Partial-Bug: 1743722
Closes-Bug: 1743898
This commit is contained in:
Honza Pokorny 2018-01-16 09:23:27 -04:00 committed by Jiri Tomasek
parent 9941b07efb
commit 27dfe42f5e
111 changed files with 10114 additions and 1342 deletions

2
.gitignore vendored
View File

@ -1,8 +1,6 @@
node_modules node_modules
node_modules.tar.gz node_modules.tar.gz
dist dist
!dist/index.html
!dist/tripleo_ui_config.js.sample
tests_results.xml tests_results.xml
tripleo-ui-*.tgz tripleo-ui-*.tgz
tripleo-ui-*.tar.gz tripleo-ui-*.tar.gz

9761
npm-shrinkwrap.json generated

File diff suppressed because it is too large Load Diff

View File

@ -59,14 +59,13 @@
"eslint-config-prettier": "^1.7.0", "eslint-config-prettier": "^1.7.0",
"eslint-plugin-prettier": "2.1.2", "eslint-plugin-prettier": "2.1.2",
"eslint-plugin-react": "5.1.1", "eslint-plugin-react": "5.1.1",
"favicons-webpack-plugin": "0.0.7",
"file-loader": "~0.9.0", "file-loader": "~0.9.0",
"html-webpack-plugin": "^2.28.0", "html-webpack-plugin": "^2.28.0",
"jest": "19.0.2", "jest": "19.0.2",
"json-loader": "^0.5.4", "json-loader": "^0.5.4",
"less": "~2.7.1", "less": "~2.7.1",
"less-loader": "~2.2.3", "less-loader": "~2.2.3",
"prettier": "1.3.1", "prettier": "1.10.2",
"react-addons-test-utils": "^15.5.1", "react-addons-test-utils": "^15.5.1",
"react-intl-po": "^1.1.0", "react-intl-po": "^1.1.0",
"react-test-renderer": "^15.5.4", "react-test-renderer": "^15.5.4",

View File

@ -17,8 +17,7 @@
import configureMockStore from 'redux-mock-store'; import configureMockStore from 'redux-mock-store';
import thunkMiddleware from 'redux-thunk'; import thunkMiddleware from 'redux-thunk';
import EnvironmentConfigurationActions import EnvironmentConfigurationActions from '../../js/actions/EnvironmentConfigurationActions';
from '../../js/actions/EnvironmentConfigurationActions';
import NotificationActions from '../../js/actions/NotificationActions'; import NotificationActions from '../../js/actions/NotificationActions';
import MistralApiService from '../../js/services/MistralApiService'; import MistralApiService from '../../js/services/MistralApiService';
import { mockGetIntl } from './utils'; import { mockGetIntl } from './utils';
@ -35,7 +34,8 @@ describe('fetchEnvironmentConfiguration', () => {
'General Deployment Options': { 'General Deployment Options': {
environment_groups: [ environment_groups: [
{ {
description: 'Enable basic configuration required for OpenStack Deployment', description:
'Enable basic configuration required for OpenStack Deployment',
environments: [ environments: [
{ {
enabled: true, enabled: true,
@ -54,7 +54,8 @@ describe('fetchEnvironmentConfiguration', () => {
const normalizedResponse = { const normalizedResponse = {
environmentGroups: { environmentGroups: {
'Enable basic configuration required for OpenStack Deployment': { 'Enable basic configuration required for OpenStack Deployment': {
description: 'Enable basic configuration required for OpenStack Deployment', description:
'Enable basic configuration required for OpenStack Deployment',
environments: ['overcloud-resource-registry-puppet.yaml'], environments: ['overcloud-resource-registry-puppet.yaml'],
title: null title: null
} }

View File

@ -15,8 +15,7 @@
*/ */
import IronicApiService from '../../js/services/IronicApiService'; import IronicApiService from '../../js/services/IronicApiService';
import IronicInspectorApiService import IronicInspectorApiService from '../../js/services/IronicInspectorApiService';
from '../../js/services/IronicInspectorApiService';
import MistralApiService from '../../js/services/MistralApiService'; import MistralApiService from '../../js/services/MistralApiService';
import { mockStore } from './utils'; import { mockStore } from './utils';
import NodesActions from '../../js/actions/NodesActions'; import NodesActions from '../../js/actions/NodesActions';
@ -240,11 +239,12 @@ describe('Asynchronous Introspect Nodes Action', () => {
return store return store
.dispatch(NodesActions.startNodesIntrospection(nodeIds)) .dispatch(NodesActions.startNodesIntrospection(nodeIds))
.then(() => { .then(() => {
expect( expect(MistralApiService.runWorkflow).toHaveBeenCalledWith(
MistralApiService.runWorkflow MistralConstants.BAREMETAL_INTROSPECT,
).toHaveBeenCalledWith(MistralConstants.BAREMETAL_INTROSPECT, { {
node_uuids: nodeIds node_uuids: nodeIds
}); }
);
expect(NodesActions.pollNodeslistDuringProgress).toHaveBeenCalled(); expect(NodesActions.pollNodeslistDuringProgress).toHaveBeenCalled();
expect(store.getActions()).toEqual([ expect(store.getActions()).toEqual([
NodesActions.startOperation(nodeIds) NodesActions.startOperation(nodeIds)
@ -329,11 +329,12 @@ describe('startProvideNodes Action', () => {
it('dispatches actions', () => { it('dispatches actions', () => {
return store.dispatch(NodesActions.startProvideNodes(nodeIds)).then(() => { return store.dispatch(NodesActions.startProvideNodes(nodeIds)).then(() => {
expect( expect(MistralApiService.runWorkflow).toHaveBeenCalledWith(
MistralApiService.runWorkflow MistralConstants.BAREMETAL_PROVIDE,
).toHaveBeenCalledWith(MistralConstants.BAREMETAL_PROVIDE, { {
node_uuids: nodeIds node_uuids: nodeIds
}); }
);
expect(NodesActions.pollNodeslistDuringProgress).toHaveBeenCalled(); expect(NodesActions.pollNodeslistDuringProgress).toHaveBeenCalled();
expect(store.getActions()).toEqual([ expect(store.getActions()).toEqual([
NodesActions.startOperation(nodeIds) NodesActions.startOperation(nodeIds)

View File

@ -107,12 +107,13 @@ describe('ParametersActions', () => {
ParametersActions.updateParameters('overcloud', { foo: 'bar' }) ParametersActions.updateParameters('overcloud', { foo: 'bar' })
) )
.then(() => { .then(() => {
expect( expect(MistralApiService.runAction).toHaveBeenCalledWith(
MistralApiService.runAction MistralConstants.PARAMETERS_UPDATE,
).toHaveBeenCalledWith(MistralConstants.PARAMETERS_UPDATE, { {
container: 'overcloud', container: 'overcloud',
parameters: { foo: 'bar' } parameters: { foo: 'bar' }
}); }
);
expect(store.getActions()).toEqual([ expect(store.getActions()).toEqual([
ReduxFormActions.startSubmit('nodesAssignment'), ReduxFormActions.startSubmit('nodesAssignment'),
ParametersActions.updateParametersPending(), ParametersActions.updateParametersPending(),

View File

@ -19,11 +19,8 @@ import { Map } from 'immutable';
import MistralApiService from '../../js/services/MistralApiService'; import MistralApiService from '../../js/services/MistralApiService';
import ValidationsActions from '../../js/actions/ValidationsActions'; import ValidationsActions from '../../js/actions/ValidationsActions';
import ValidationsConstants from '../../js/constants/ValidationsConstants'; import ValidationsConstants from '../../js/constants/ValidationsConstants';
import WorkflowExecutionsActions import WorkflowExecutionsActions from '../../js/actions/WorkflowExecutionsActions';
from '../../js/actions/WorkflowExecutionsActions'; import { WorkflowExecution } from '../../js/immutableRecords/workflowExecutions';
import {
WorkflowExecution
} from '../../js/immutableRecords/workflowExecutions';
import MistralConstants from '../../js/constants/MistralConstants'; import MistralConstants from '../../js/constants/MistralConstants';
import { mockStore } from './utils'; import { mockStore } from './utils';
@ -125,12 +122,13 @@ describe('RunValidation action', () => {
return store return store
.dispatch(ValidationsActions.runValidation('512e', 'overcloud')) .dispatch(ValidationsActions.runValidation('512e', 'overcloud'))
.then(() => { .then(() => {
expect( expect(MistralApiService.runWorkflow).toHaveBeenCalledWith(
MistralApiService.runWorkflow MistralConstants.VALIDATIONS_RUN,
).toHaveBeenCalledWith(MistralConstants.VALIDATIONS_RUN, { {
validation_name: '512e', validation_name: '512e',
plan: 'overcloud' plan: 'overcloud'
}); }
);
expect(store.getActions()).toEqual([ expect(store.getActions()).toEqual([
WorkflowExecutionsActions.addWorkflowExecution( WorkflowExecutionsActions.addWorkflowExecution(
addWorkflowExecutionResponse addWorkflowExecutionResponse

View File

@ -15,8 +15,7 @@
*/ */
import MistralApiService from '../../js/services/MistralApiService'; import MistralApiService from '../../js/services/MistralApiService';
import WorkflowExecutionsActions import WorkflowExecutionsActions from '../../js/actions/WorkflowExecutionsActions';
from '../../js/actions/WorkflowExecutionsActions';
import { mockStore } from './utils'; import { mockStore } from './utils';
describe('fetchWorkflowExecutions action', () => { describe('fetchWorkflowExecutions action', () => {
@ -78,9 +77,10 @@ describe('updateWorkflowExecution action', () => {
}) })
) )
.then(() => { .then(() => {
expect( expect(MistralApiService.updateWorkflowExecution).toHaveBeenCalledWith(
MistralApiService.updateWorkflowExecution '512e',
).toHaveBeenCalledWith('512e', { state: 'PAUSED' }); { state: 'PAUSED' }
);
expect(store.getActions()).toEqual([ expect(store.getActions()).toEqual([
WorkflowExecutionsActions.updateWorkflowExecutionPending('512e', { WorkflowExecutionsActions.updateWorkflowExecutionPending('512e', {
state: 'PAUSED' state: 'PAUSED'

View File

@ -73,25 +73,20 @@ describe('Login component', () => {
}); });
describe('When user is logged in', () => { describe('When user is logged in', () => {
xit( xit('redirects to nextPath when user is logged in and visits login page', () => {
'redirects to nextPath when user is logged in and visits login page', loginInstance = new Login();
() => { loginInstance.context = {
loginInstance = new Login(); router: {
loginInstance.context = { getCurrentQuery() {
router: { return {
getCurrentQuery() { nextPath: 'nodes'
return { };
nextPath: 'nodes'
};
}
} }
}; }
// loginInstance.context.router.transitionTo = jest.genMockFunction(); };
loginInstance.componentWillMount(); // loginInstance.context.router.transitionTo = jest.genMockFunction();
expect(loginInstance.context.router.transitionTo).toBeCalledWith( loginInstance.componentWillMount();
'nodes' expect(loginInstance.context.router.transitionTo).toBeCalledWith('nodes');
); });
}
);
}); });
}); });

View File

@ -18,8 +18,7 @@ import { Map } from 'immutable';
import React from 'react'; import React from 'react';
import ReactShallowRenderer from 'react-test-renderer/shallow'; import ReactShallowRenderer from 'react-test-renderer/shallow';
import EnvironmentConfiguration import EnvironmentConfiguration from '../../../js/components/environment_configuration/EnvironmentConfiguration';
from '../../../js/components/environment_configuration/EnvironmentConfiguration';
describe('EnvironmentConfiguration component', () => { describe('EnvironmentConfiguration component', () => {
let EnvConfVdom; let EnvConfVdom;

View File

@ -18,8 +18,7 @@ import { fromJS } from 'immutable';
import React from 'react'; import React from 'react';
import ReactShallowRenderer from 'react-test-renderer/shallow'; import ReactShallowRenderer from 'react-test-renderer/shallow';
import EnvironmentConfigurationTopic import EnvironmentConfigurationTopic from '../../../js/components/environment_configuration/EnvironmentConfigurationTopic';
from '../../../js/components/environment_configuration/EnvironmentConfigurationTopic';
import MockPlan from '../../mocks/MockPlan'; import MockPlan from '../../mocks/MockPlan';
const topic = MockPlan.capabilities.topics[0]; const topic = MockPlan.capabilities.topics[0];

View File

@ -17,8 +17,7 @@
import React from 'react'; import React from 'react';
import ReactShallowRenderer from 'react-test-renderer/shallow'; import ReactShallowRenderer from 'react-test-renderer/shallow';
import EnvironmentGroup import EnvironmentGroup from '../../../js/components/environment_configuration/EnvironmentGroup';
from '../../../js/components/environment_configuration/EnvironmentGroup';
import MockPlan from '../../mocks/MockPlan'; import MockPlan from '../../mocks/MockPlan';
const envGroup = MockPlan.capabilities.topics[0].environment_groups[0]; const envGroup = MockPlan.capabilities.topics[0].environment_groups[0];

View File

@ -42,10 +42,7 @@ describe('Nodes Component', () => {
xit('should listen to NodesStore changes', () => {}); xit('should listen to NodesStore changes', () => {});
xit( xit('should get nodes from NodesStore and store them in state on change in NodesStore', () => {});
'should get nodes from NodesStore and store them in state on change in NodesStore',
() => {}
);
xit('should issue a request to list Nodes on when mounted', () => { xit('should issue a request to list Nodes on when mounted', () => {
spyOn(IronicApiService, 'handleGetNodes'); spyOn(IronicApiService, 'handleGetNodes');

View File

@ -23,8 +23,7 @@ import {
DataTableHeaderCell, DataTableHeaderCell,
DataTableDataFieldCell DataTableDataFieldCell
} from '../../../../js/components/ui/tables/DataTableCells'; } from '../../../../js/components/ui/tables/DataTableCells';
import DataTableColumn import DataTableColumn from '../../../../js/components/ui/tables/DataTableColumn';
from '../../../../js/components/ui/tables/DataTableColumn';
const data = [ const data = [
{ uuid: 1, provision_state: 'failed' }, { uuid: 1, provision_state: 'failed' },

View File

@ -26,7 +26,8 @@ export default {
environment_groups: [ environment_groups: [
{ {
title: null, title: null,
description: 'Enable basic configuration required for OpenStack Deployment', description:
'Enable basic configuration required for OpenStack Deployment',
environments: { environments: {
'overcloud-resource-registry-puppet.yaml': { 'overcloud-resource-registry-puppet.yaml': {
file: 'overcloud-resource-registry-puppet.yaml', file: 'overcloud-resource-registry-puppet.yaml',
@ -43,12 +44,14 @@ export default {
'environments/neutron-ml2-bigswitch.yaml': { 'environments/neutron-ml2-bigswitch.yaml': {
file: 'environments/neutron-ml2-bigswitch.yaml', file: 'environments/neutron-ml2-bigswitch.yaml',
title: 'BigSwitch extensions', title: 'BigSwitch extensions',
description: 'Enable Big Switch extensions, configured via puppet\n' description:
'Enable Big Switch extensions, configured via puppet\n'
}, },
'environments/neutron-ml2-cisco-n1kv.yaml': { 'environments/neutron-ml2-cisco-n1kv.yaml': {
file: 'environments/neutron-ml2-cisco-n1kv.yaml', file: 'environments/neutron-ml2-cisco-n1kv.yaml',
title: 'Cisco N1KV backend', title: 'Cisco N1KV backend',
description: 'Enable a Cisco N1KV backend, configured via puppet\n' description:
'Enable a Cisco N1KV backend, configured via puppet\n'
} }
} }
} }

View File

@ -16,15 +16,10 @@
import { Map, fromJS } from 'immutable'; import { Map, fromJS } from 'immutable';
import EnvironmentConfigurationActions import EnvironmentConfigurationActions from '../../js/actions/EnvironmentConfigurationActions';
from '../../js/actions/EnvironmentConfigurationActions'; import EnvironmentConfigurationConstants from '../../js/constants/EnvironmentConfigurationConstants';
import EnvironmentConfigurationConstants import environmentConfigurationReducer from '../../js/reducers/environmentConfigurationReducer';
from '../../js/constants/EnvironmentConfigurationConstants'; import { EnvironmentConfigurationState } from '../../js/immutableRecords/environmentConfiguration';
import environmentConfigurationReducer
from '../../js/reducers/environmentConfigurationReducer';
import {
EnvironmentConfigurationState
} from '../../js/immutableRecords/environmentConfiguration';
const initialState = new EnvironmentConfigurationState(); const initialState = new EnvironmentConfigurationState();
@ -55,7 +50,8 @@ describe('environmentConfigurationReducer', () => {
describe('fetchEnvironmentConfigurationPending', () => { describe('fetchEnvironmentConfigurationPending', () => {
let newState; let newState;
let action = { let action = {
type: EnvironmentConfigurationConstants.FETCH_ENVIRONMENT_CONFIGURATION_PENDING type:
EnvironmentConfigurationConstants.FETCH_ENVIRONMENT_CONFIGURATION_PENDING
}; };
beforeEach(() => { beforeEach(() => {

View File

@ -16,13 +16,9 @@
import { Map, OrderedMap } from 'immutable'; import { Map, OrderedMap } from 'immutable';
import { import { WorkflowExecution } from '../../js/immutableRecords/workflowExecutions';
WorkflowExecution import WorkflowExecutionsConstants from '../../js/constants/WorkflowExecutionsConstants';
} from '../../js/immutableRecords/workflowExecutions'; import workflowExecutionsReducer from '../../js/reducers/workflowExecutionsReducer';
import WorkflowExecutionsConstants
from '../../js/constants/WorkflowExecutionsConstants';
import workflowExecutionsReducer
from '../../js/reducers/workflowExecutionsReducer';
import MistralConstants from '../../js/constants/MistralConstants'; import MistralConstants from '../../js/constants/MistralConstants';
const updatedAt = '1970-01-01T00:00:01Z'; const updatedAt = '1970-01-01T00:00:01Z';

View File

@ -23,9 +23,7 @@ import {
FiltersInitialState FiltersInitialState
} from '../../js/immutableRecords/filters'; } from '../../js/immutableRecords/filters';
import { InitialPlanState, Plan } from '../../js/immutableRecords/plans'; import { InitialPlanState, Plan } from '../../js/immutableRecords/plans';
import { import { WorkflowExecution } from '../../js/immutableRecords/workflowExecutions';
WorkflowExecution
} from '../../js/immutableRecords/workflowExecutions';
import MistralConstants from '../../js/constants/MistralConstants'; import MistralConstants from '../../js/constants/MistralConstants';
describe(' validations selectors', () => { describe(' validations selectors', () => {

View File

@ -18,8 +18,7 @@ import { defineMessages } from 'react-intl';
import { normalize, arrayOf } from 'normalizr'; import { normalize, arrayOf } from 'normalizr';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import EnvironmentConfigurationConstants import EnvironmentConfigurationConstants from '../constants/EnvironmentConfigurationConstants';
from '../constants/EnvironmentConfigurationConstants';
import { handleErrors } from './ErrorActions'; import { handleErrors } from './ErrorActions';
import MistralApiService from '../services/MistralApiService'; import MistralApiService from '../services/MistralApiService';
import NotificationActions from '../actions/NotificationActions'; import NotificationActions from '../actions/NotificationActions';
@ -30,7 +29,8 @@ import SwiftApiService from '../services/SwiftApiService';
const messages = defineMessages({ const messages = defineMessages({
envConfigUpdatedNotificationMessage: { envConfigUpdatedNotificationMessage: {
id: 'EnvironmentConfigurationActions.envConfigUpdatedNotificationMessage', id: 'EnvironmentConfigurationActions.envConfigUpdatedNotificationMessage',
defaultMessage: 'The Environment Configuration has been successfully updated.' defaultMessage:
'The Environment Configuration has been successfully updated.'
}, },
envConfigUpdatedNotificationTitle: { envConfigUpdatedNotificationTitle: {
id: 'EnvironmentConfigurationActions.envConfigUpdatedNotificationTitle', id: 'EnvironmentConfigurationActions.envConfigUpdatedNotificationTitle',
@ -48,8 +48,8 @@ export default {
}) })
) )
.then(response => { .then(response => {
const entities = normalize(response, arrayOf(topicSchema)) const entities =
.entities || {}; normalize(response, arrayOf(topicSchema)).entities || {};
dispatch(this.fetchEnvironmentConfigurationSuccess(entities)); dispatch(this.fetchEnvironmentConfigurationSuccess(entities));
}) })
.catch(error => { .catch(error => {
@ -66,20 +66,23 @@ export default {
fetchEnvironmentConfigurationPending() { fetchEnvironmentConfigurationPending() {
return { return {
type: EnvironmentConfigurationConstants.FETCH_ENVIRONMENT_CONFIGURATION_PENDING type:
EnvironmentConfigurationConstants.FETCH_ENVIRONMENT_CONFIGURATION_PENDING
}; };
}, },
fetchEnvironmentConfigurationSuccess(entities) { fetchEnvironmentConfigurationSuccess(entities) {
return { return {
type: EnvironmentConfigurationConstants.FETCH_ENVIRONMENT_CONFIGURATION_SUCCESS, type:
EnvironmentConfigurationConstants.FETCH_ENVIRONMENT_CONFIGURATION_SUCCESS,
payload: entities payload: entities
}; };
}, },
fetchEnvironmentConfigurationFailed(environment) { fetchEnvironmentConfigurationFailed(environment) {
return { return {
type: EnvironmentConfigurationConstants.FETCH_ENVIRONMENT_CONFIGURATION_FAILED type:
EnvironmentConfigurationConstants.FETCH_ENVIRONMENT_CONFIGURATION_FAILED
}; };
}, },
@ -128,20 +131,23 @@ export default {
updateEnvironmentConfigurationPending() { updateEnvironmentConfigurationPending() {
return { return {
type: EnvironmentConfigurationConstants.UPDATE_ENVIRONMENT_CONFIGURATION_PENDING type:
EnvironmentConfigurationConstants.UPDATE_ENVIRONMENT_CONFIGURATION_PENDING
}; };
}, },
updateEnvironmentConfigurationSuccess(enabledEnvironments) { updateEnvironmentConfigurationSuccess(enabledEnvironments) {
return { return {
type: EnvironmentConfigurationConstants.UPDATE_ENVIRONMENT_CONFIGURATION_SUCCESS, type:
EnvironmentConfigurationConstants.UPDATE_ENVIRONMENT_CONFIGURATION_SUCCESS,
payload: enabledEnvironments payload: enabledEnvironments
}; };
}, },
updateEnvironmentConfigurationFailed(formErrors = [], formFieldErrors = {}) { updateEnvironmentConfigurationFailed(formErrors = [], formFieldErrors = {}) {
return { return {
type: EnvironmentConfigurationConstants.UPDATE_ENVIRONMENT_CONFIGURATION_FAILED, type:
EnvironmentConfigurationConstants.UPDATE_ENVIRONMENT_CONFIGURATION_FAILED,
payload: { payload: {
formErrors, formErrors,
formFieldErrors formFieldErrors
@ -154,13 +160,13 @@ export default {
dispatch(this.fetchEnvironmentPending(environmentPath)); dispatch(this.fetchEnvironmentPending(environmentPath));
dispatch(SwiftApiService.getObject(planName, environmentPath)) dispatch(SwiftApiService.getObject(planName, environmentPath))
.then(response => { .then(response => {
const { const { resource_registry, parameter_defaults } = yaml.safeLoad(
resource_registry, response,
parameter_defaults {
} = yaml.safeLoad(response, { filename: environmentPath,
filename: environmentPath, json: true
json: true }
}); );
dispatch( dispatch(
this.fetchEnvironmentSuccess({ this.fetchEnvironmentSuccess({
file: environmentPath, file: environmentPath,

View File

@ -34,9 +34,10 @@ export default {
navigator.userLanguage; navigator.userLanguage;
// If the locale contains the country but we can't find // If the locale contains the country but we can't find
// messages for it then we only use the country part: // messages for it then we only use the country part:
language = locale.match(/^[A-Za-z]+-[A-Za-z]+$/) && !MESSAGES[locale] language =
? locale.split('-')[0] locale.match(/^[A-Za-z]+-[A-Za-z]+$/) && !MESSAGES[locale]
: locale; ? locale.split('-')[0]
: locale;
} }
dispatch(this.chooseLanguage(language)); dispatch(this.chooseLanguage(language));
}; };

View File

@ -143,8 +143,9 @@ export default {
dispatch(this.requestPlan()); dispatch(this.requestPlan());
return dispatch(SwiftApiService.getContainer(planName)) return dispatch(SwiftApiService.getContainer(planName))
.then(response => { .then(response => {
const planFiles = normalize(response, arrayOf(planFileSchema)) const planFiles =
.entities.planFiles || {}; normalize(response, arrayOf(planFileSchema)).entities.planFiles ||
{};
dispatch(this.receivePlan(planName, planFiles)); dispatch(this.receivePlan(planName, planFiles));
}) })
.catch(error => { .catch(error => {

View File

@ -34,8 +34,8 @@ export default {
}) })
) )
.then(response => { .then(response => {
const roles = normalize(response, arrayOf(roleSchema)).entities const roles =
.roles || {}; normalize(response, arrayOf(roleSchema)).entities.roles || {};
dispatch(this.fetchRolesSuccess(roles)); dispatch(this.fetchRolesSuccess(roles));
}) })
.catch(error => { .catch(error => {

View File

@ -46,8 +46,9 @@ export default {
dispatch(this.fetchStacksPending()); dispatch(this.fetchStacksPending());
return dispatch(HeatApiService.getStacks()) return dispatch(HeatApiService.getStacks())
.then(response => { .then(response => {
const stacks = normalize(response.stacks, arrayOf(stackSchema)) const stacks =
.entities.stacks || {}; normalize(response.stacks, arrayOf(stackSchema)).entities.stacks ||
{};
dispatch(this.fetchStacksSuccess(stacks)); dispatch(this.fetchStacksSuccess(stacks));
}) })
.catch(error => { .catch(error => {
@ -81,8 +82,9 @@ export default {
dispatch(this.fetchResourcesPending()); dispatch(this.fetchResourcesPending());
dispatch(HeatApiService.getResources(stackName, stackId)) dispatch(HeatApiService.getResources(stackName, stackId))
.then(({ resources }) => { .then(({ resources }) => {
const res = normalize(resources, arrayOf(stackResourceSchema)) const res =
.entities.stackResources || {}; normalize(resources, arrayOf(stackResourceSchema)).entities
.stackResources || {};
dispatch(this.fetchResourcesSuccess(res)); dispatch(this.fetchResourcesSuccess(res));
}) })
.catch(error => { .catch(error => {

View File

@ -32,8 +32,9 @@ export default {
MistralApiService.runAction(MistralConstants.VALIDATIONS_LIST) MistralApiService.runAction(MistralConstants.VALIDATIONS_LIST)
) )
.then(response => { .then(response => {
const validations = normalize(response, arrayOf(validationSchema)) const validations =
.entities.validations || {}; normalize(response, arrayOf(validationSchema)).entities
.validations || {};
dispatch(this.fetchValidationsSuccess(validations)); dispatch(this.fetchValidationsSuccess(validations));
}) })
.catch(error => { .catch(error => {

View File

@ -18,11 +18,8 @@ import { normalize, arrayOf } from 'normalizr';
import { handleErrors } from './ErrorActions'; import { handleErrors } from './ErrorActions';
import MistralApiService from '../services/MistralApiService'; import MistralApiService from '../services/MistralApiService';
import WorkflowExecutionsConstants import WorkflowExecutionsConstants from '../constants/WorkflowExecutionsConstants';
from '../constants/WorkflowExecutionsConstants'; import { workflowExecutionSchema } from '../normalizrSchemas/workflowExecutions';
import {
workflowExecutionSchema
} from '../normalizrSchemas/workflowExecutions';
export default { export default {
fetchWorkflowExecutions() { fetchWorkflowExecutions() {
@ -30,10 +27,9 @@ export default {
dispatch(this.fetchWorkflowExecutionsPending()); dispatch(this.fetchWorkflowExecutionsPending());
return dispatch(MistralApiService.getWorkflowExecutions()) return dispatch(MistralApiService.getWorkflowExecutions())
.then(response => { .then(response => {
const executions = normalize( const executions =
response, normalize(response, arrayOf(workflowExecutionSchema)).entities
arrayOf(workflowExecutionSchema) .executions || {};
).entities.executions || {};
dispatch(this.fetchWorkflowExecutionsSuccess(executions)); dispatch(this.fetchWorkflowExecutionsSuccess(executions));
}) })
.catch(error => { .catch(error => {

View File

@ -60,9 +60,11 @@ class AuthenticatedContent extends React.Component {
<Route path="/nodes" component={Nodes} /> <Route path="/nodes" component={Nodes} />
<Route path="/plans/manage" component={Plans} /> <Route path="/plans/manage" component={Plans} />
<Route path="/plans/:planName" component={DeploymentPlan} /> <Route path="/plans/:planName" component={DeploymentPlan} />
{currentPlanName {currentPlanName ? (
? <Redirect from="/" to={`/plans/${currentPlanName}`} /> <Redirect from="/" to={`/plans/${currentPlanName}`} />
: <Redirect from="/" to="/plans/manage" />} ) : (
<Redirect from="/" to="/plans/manage" />
)}
</Switch> </Switch>
</MainContent> </MainContent>
</GlobalLoader> </GlobalLoader>

View File

@ -65,11 +65,11 @@ class NavBar extends React.Component {
_renderLanguageDropdown() { _renderLanguageDropdown() {
// Only include the I18nDropdown if there's more than one // Only include the I18nDropdown if there's more than one
// language to choose from. // language to choose from.
return this.props.languages.size > 1 return this.props.languages.size > 1 ? (
? <li> <li>
<I18nDropdown /> <I18nDropdown />
</li> </li>
: null; ) : null;
} }
_renderHelpDropdown() { _renderHelpDropdown() {

View File

@ -52,7 +52,8 @@ const messages = defineMessages({
}, },
downloadLogsMessage: { downloadLogsMessage: {
id: 'DebugScreen.downloadLogsMessage', id: 'DebugScreen.downloadLogsMessage',
defaultMessage: 'The file you requested is ready. Please click the button below to ' + defaultMessage:
'The file you requested is ready. Please click the button below to ' +
'download the export. You might need to right-click the button and choose ' + 'download the export. You might need to right-click the button and choose ' +
'"Save link as...".' '"Save link as...".'
}, },
@ -102,19 +103,21 @@ class DebugScreen extends React.Component {
return; return;
} }
return this.props.logsUrl return this.props.logsUrl ? (
? <div> <div>
<div> <div>
<FormattedMessage {...messages.downloadLogsMessage} /> <FormattedMessage {...messages.downloadLogsMessage} />
</div>
<br />
<a href={this.props.logsUrl} className="btn btn-success">
<FormattedMessage {...messages.downloadLogs} />
</a>
</div> </div>
: <div> <br />
<FormattedMessage {...messages.downloadError} /> <a href={this.props.logsUrl} className="btn btn-success">
</div>; <FormattedMessage {...messages.downloadLogs} />
</a>
</div>
) : (
<div>
<FormattedMessage {...messages.downloadError} />
</div>
);
} }
render() { render() {

View File

@ -50,7 +50,8 @@ const messages = defineMessages({
}, },
validationsWarningMessage: { validationsWarningMessage: {
id: 'DeploymentConfirmation.validationsWarningMessage', id: 'DeploymentConfirmation.validationsWarningMessage',
defaultMessage: 'It is highly recommended that you resolve all validation issues before ' + defaultMessage:
'It is highly recommended that you resolve all validation issues before ' +
'continuing.' 'continuing.'
} }
}); });
@ -78,8 +79,9 @@ class DeploymentConfirmation extends React.Component {
)} )}
> >
<p> <p>
<strong><FormattedMessage {...messages.summary} /></strong> <strong>
{' '} <FormattedMessage {...messages.summary} />
</strong>{' '}
{environmentSummary} {environmentSummary}
</p> </p>
<ValidationsWarning <ValidationsWarning
@ -144,8 +146,7 @@ export const DeployButton = injectIntl(
loaded={!isRequestingPlanDeploy} loaded={!isRequestingPlanDeploy}
content={intl.formatMessage(messages.requestingDeploymentLoader)} content={intl.formatMessage(messages.requestingDeploymentLoader)}
> >
<span className="fa fa-cloud-upload" /> <span className="fa fa-cloud-upload" />{' '}
{' '}
<FormattedMessage {...messages.deployButton} /> <FormattedMessage {...messages.deployButton} />
</InlineLoader> </InlineLoader>
</button> </button>

View File

@ -21,9 +21,7 @@ import { ModalHeader, ModalTitle, ModalFooter } from 'react-bootstrap';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import { import { allPreDeploymentValidationsSuccessful } from '../../selectors/validations';
allPreDeploymentValidationsSuccessful
} from '../../selectors/validations';
import DeploymentConfirmation from './DeploymentConfirmation'; import DeploymentConfirmation from './DeploymentConfirmation';
import DeploymentProgress from './DeploymentProgress'; import DeploymentProgress from './DeploymentProgress';
import DeploymentSuccess from './DeploymentSuccess'; import DeploymentSuccess from './DeploymentSuccess';
@ -33,9 +31,7 @@ import {
getCurrentStack, getCurrentStack,
getCurrentStackDeploymentProgress getCurrentStackDeploymentProgress
} from '../../selectors/stacks'; } from '../../selectors/stacks';
import { import { getEnvironmentConfigurationSummary } from '../../selectors/environmentConfiguration';
getEnvironmentConfigurationSummary
} from '../../selectors/environmentConfiguration';
import { Loader } from '../ui/Loader'; import { Loader } from '../ui/Loader';
import { import {
CloseModalButton, CloseModalButton,

View File

@ -46,7 +46,9 @@ class DeploymentFailure extends React.Component {
<InlineNotification type="error" title={status}> <InlineNotification type="error" title={status}>
<p>{this.props.stack.stack_status_reason}</p> <p>{this.props.stack.stack_status_reason}</p>
</InlineNotification> </InlineNotification>
<h2><FormattedMessage {...messages.resources} /></h2> <h2>
<FormattedMessage {...messages.resources} />
</h2>
<div className="flex-column"> <div className="flex-column">
<StackResourcesTable <StackResourcesTable
isFetchingResources={!this.props.stackResourcesLoaded} isFetchingResources={!this.props.stackResourcesLoaded}

View File

@ -41,13 +41,13 @@ export default class DeploymentProgress extends React.Component {
} }
renderProgressBar() { renderProgressBar() {
return this.props.stack.stack_status === stackStates.CREATE_IN_PROGRESS return this.props.stack.stack_status === stackStates.CREATE_IN_PROGRESS ? (
? <ProgressBar <ProgressBar
value={this.props.deploymentProgress} value={this.props.deploymentProgress}
label={this.props.deploymentProgress + '%'} label={this.props.deploymentProgress + '%'}
labelPosition="topRight" labelPosition="topRight"
/> />
: null; ) : null;
} }
render() { render() {
@ -65,7 +65,9 @@ export default class DeploymentProgress extends React.Component {
</div> </div>
{this.renderProgressBar()} {this.renderProgressBar()}
</div> </div>
<h2><FormattedMessage {...messages.resources} /></h2> <h2>
<FormattedMessage {...messages.resources} />
</h2>
<div className="flex-column"> <div className="flex-column">
<StackResourcesTable <StackResourcesTable
isFetchingResources={!this.props.stackResourcesLoaded} isFetchingResources={!this.props.stackResourcesLoaded}

View File

@ -52,15 +52,23 @@ const OvercloudInfo = ({ intl, overcloudInfo }) => {
return ( return (
<div> <div>
<h4><FormattedMessage {...messages.overcloudInformationHeader} /></h4> <h4>
<FormattedMessage {...messages.overcloudInformationHeader} />
</h4>
<Loader <Loader
loaded={!!(ip && password)} loaded={!!(ip && password)}
content={intl.formatMessage(messages.loadingOvercloudInformation)} content={intl.formatMessage(messages.loadingOvercloudInformation)}
> >
<ul className="list"> <ul className="list">
<li><FormattedMessage {...messages.overcloudIpAddress} /> {ip}</li> <li>
<li><FormattedMessage {...messages.username} /> admin</li> <FormattedMessage {...messages.overcloudIpAddress} /> {ip}
<li><FormattedMessage {...messages.password} /> {password}</li> </li>
<li>
<FormattedMessage {...messages.username} /> admin
</li>
<li>
<FormattedMessage {...messages.password} /> {password}
</li>
</ul> </ul>
</Loader> </Loader>
<br /> <br />

View File

@ -37,17 +37,14 @@ import {
getNodeCountParametersByRole, getNodeCountParametersByRole,
getTotalAssignedNodesCount getTotalAssignedNodesCount
} from '../../selectors/nodesAssignment'; } from '../../selectors/nodesAssignment';
import { import { getEnvironmentConfigurationSummary } from '../../selectors/environmentConfiguration';
getEnvironmentConfigurationSummary
} from '../../selectors/environmentConfiguration';
import { getCurrentPlan } from '../../selectors/plans'; import { getCurrentPlan } from '../../selectors/plans';
import { getRoles } from '../../selectors/roles'; import { getRoles } from '../../selectors/roles';
import ConfigurePlanStep from './ConfigurePlanStep'; import ConfigurePlanStep from './ConfigurePlanStep';
import CurrentPlanActions from '../../actions/CurrentPlanActions'; import CurrentPlanActions from '../../actions/CurrentPlanActions';
import { DeploymentPlanStep } from './DeploymentPlanStep'; import { DeploymentPlanStep } from './DeploymentPlanStep';
import DeployStep from './DeployStep'; import DeployStep from './DeployStep';
import EnvironmentConfigurationActions import EnvironmentConfigurationActions from '../../actions/EnvironmentConfigurationActions';
from '../../actions/EnvironmentConfigurationActions';
import HardwareStep from './HardwareStep'; import HardwareStep from './HardwareStep';
import NodesActions from '../../actions/NodesActions'; import NodesActions from '../../actions/NodesActions';
import NotificationActions from '../../actions/NotificationActions'; import NotificationActions from '../../actions/NotificationActions';
@ -83,7 +80,8 @@ const messages = defineMessages({
}, },
hardwareStepTooltip: { hardwareStepTooltip: {
id: 'CurrentPlan.hardwareStepTooltip', id: 'CurrentPlan.hardwareStepTooltip',
defaultMessage: 'This step registers and introspects your nodes. Registration involves ' + defaultMessage:
'This step registers and introspects your nodes. Registration involves ' +
'defining the power management details of each node so that you so that the director can ' + 'defining the power management details of each node so that you so that the director can ' +
'control them during the introspection and provisioning stages. After registration, you ' + 'control them during the introspection and provisioning stages. After registration, you ' +
'introspect the nodes, which identifies the hardware each node uses and builds a profile of ' + 'introspect the nodes, which identifies the hardware each node uses and builds a profile of ' +
@ -92,13 +90,15 @@ const messages = defineMessages({
}, },
configurePlanStepTooltip: { configurePlanStepTooltip: {
id: 'CurrentPlan.configurePlanStepTooltip', id: 'CurrentPlan.configurePlanStepTooltip',
defaultMessage: "This step allows you edit specific settings for the overcloud's network, " + defaultMessage:
"This step allows you edit specific settings for the overcloud's network, " +
'storage, and other certified plugins. Use this step to define your network isolation ' + 'storage, and other certified plugins. Use this step to define your network isolation ' +
'configuration and your backend storage settings.' 'configuration and your backend storage settings.'
}, },
configureRolesStepTooltip: { configureRolesStepTooltip: {
id: 'CurrentPlan.configureRolesStepTooltip', id: 'CurrentPlan.configureRolesStepTooltip',
defaultMessage: 'This step assigns and removes nodes from roles in your overcloud. On each ' + defaultMessage:
'This step assigns and removes nodes from roles in your overcloud. On each ' +
"role's selection dialog, you can tag available nodes into the role or untag nodes already " + "role's selection dialog, you can tag available nodes into the role or untag nodes already " +
'assigned to the role. Click "Assign Nodes" for a particular role to open the selection ' + 'assigned to the role. Click "Assign Nodes" for a particular role to open the selection ' +
'dialog and start assigning nodes. ' + 'dialog and start assigning nodes. ' +
@ -108,7 +108,8 @@ const messages = defineMessages({
}, },
deployStepTooltip: { deployStepTooltip: {
id: 'CurrentPlan.deploymentStepTooltip', id: 'CurrentPlan.deploymentStepTooltip',
defaultMessage: 'This step performs the deployment of the overcloud. Once the deployment ' + defaultMessage:
'This step performs the deployment of the overcloud. Once the deployment ' +
'begins, the director tracks the progress and provides a report of each completed, running, ' + 'begins, the director tracks the progress and provides a report of each completed, running, ' +
'or failed step. When the deployment completes, the director displays the current overcloud ' + 'or failed step. When the deployment completes, the director displays the current overcloud ' +
'status and login details, which you use to interact with your overcloud. Click "Deploy" to ' + 'status and login details, which you use to interact with your overcloud. Click "Deploy" to ' +
@ -329,8 +330,8 @@ export function mapStateToProps(state, props) {
currentStackDeploymentProgress: getCurrentStackDeploymentProgress(state), currentStackDeploymentProgress: getCurrentStackDeploymentProgress(state),
environmentConfigurationLoaded: state.environmentConfiguration.loaded, environmentConfigurationLoaded: state.environmentConfiguration.loaded,
environmentConfigurationSummary: getEnvironmentConfigurationSummary(state), environmentConfigurationSummary: getEnvironmentConfigurationSummary(state),
isFetchingEnvironmentConfiguration: state.environmentConfiguration isFetchingEnvironmentConfiguration:
.isFetching, state.environmentConfiguration.isFetching,
isFetchingNodes: state.nodes.get('isFetching'), isFetchingNodes: state.nodes.get('isFetching'),
isFetchingParameters: state.parameters.isFetching, isFetchingParameters: state.parameters.isFetching,
isFetchingRoles: state.roles.get('isFetching'), isFetchingRoles: state.roles.get('isFetching'),

View File

@ -66,8 +66,7 @@ export const DeployStep = ({
loaded={!currentPlan.isRequestingPlanDeploy} loaded={!currentPlan.isRequestingPlanDeploy}
content={intl.formatMessage(messages.requestingDeploy)} content={intl.formatMessage(messages.requestingDeploy)}
> >
<span className="fa fa-cloud-upload" /> <span className="fa fa-cloud-upload" />{' '}
{' '}
<FormattedMessage {...messages.validateAndDeploy} /> <FormattedMessage {...messages.validateAndDeploy} />
</InlineLoader> </InlineLoader>
</Link> </Link>

View File

@ -21,8 +21,7 @@ import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import { checkRunningDeployment } from '../utils/checkRunningDeploymentHOC'; import { checkRunningDeployment } from '../utils/checkRunningDeploymentHOC';
import EnvironmentConfiguration import EnvironmentConfiguration from '../environment_configuration/EnvironmentConfiguration';
from '../environment_configuration/EnvironmentConfiguration';
import NavTab from '../ui/NavTab'; import NavTab from '../ui/NavTab';
import { CloseModalXButton, RoutedModal } from '../ui/Modals'; import { CloseModalXButton, RoutedModal } from '../ui/Modals';
import Parameters from '../parameters/Parameters'; import Parameters from '../parameters/Parameters';

View File

@ -54,8 +54,7 @@ class DeploymentFailure extends React.Component {
<div> <div>
<InlineNotification type="error" title={status}> <InlineNotification type="error" title={status}>
<p> <p>
{stack.stack_status_reason} {stack.stack_status_reason}{' '}
{' '}
<Link to={`/plans/${currentPlanName}/deployment-detail`}> <Link to={`/plans/${currentPlanName}/deployment-detail`}>
<FormattedMessage {...messages.moreDetails} /> <FormattedMessage {...messages.moreDetails} />
</Link> </Link>

View File

@ -22,11 +22,11 @@ export const DeploymentPlanStep = ({ children, disabled, title, tooltip }) => {
<li className={disabled ? 'disabled' : null}> <li className={disabled ? 'disabled' : null}>
<h3> <h3>
<span>{title}</span> <span>{title}</span>
{tooltip {tooltip ? (
? <span data-tooltip={tooltip} className="tooltip-right"> <span data-tooltip={tooltip} className="tooltip-right">
<span className="pficon pficon-info" /> <span className="pficon pficon-info" />
</span> </span>
: null} ) : null}
</h3> </h3>
{children} {children}
</li> </li>

View File

@ -49,13 +49,13 @@ const messages = defineMessages({
class DeploymentProgress extends React.Component { class DeploymentProgress extends React.Component {
renderProgressBar() { renderProgressBar() {
return this.props.stack.stack_status === stackStates.CREATE_IN_PROGRESS return this.props.stack.stack_status === stackStates.CREATE_IN_PROGRESS ? (
? <ProgressBar <ProgressBar
value={this.props.deploymentProgress} value={this.props.deploymentProgress}
label={this.props.deploymentProgress + '%'} label={this.props.deploymentProgress + '%'}
labelPosition="topRight" labelPosition="topRight"
/> />
: null; ) : null;
} }
render() { render() {
@ -73,8 +73,9 @@ class DeploymentProgress extends React.Component {
</strong> </strong>
); );
const deleteButton = stack.stack_status !== stackStates.DELETE_IN_PROGRESS const deleteButton =
? <DeleteStackButton stack.stack_status !== stackStates.DELETE_IN_PROGRESS ? (
<DeleteStackButton
content={formatMessage(messages.cancelDeployment)} content={formatMessage(messages.cancelDeployment)}
buttonIconClass="fa fa-ban" buttonIconClass="fa fa-ban"
deleteStack={deleteStack} deleteStack={deleteStack}
@ -83,12 +84,14 @@ class DeploymentProgress extends React.Component {
loaderContent={formatMessage(messages.requestingDeletion)} loaderContent={formatMessage(messages.requestingDeletion)}
stack={stack} stack={stack}
/> />
: null; ) : null;
return ( return (
<div> <div>
<p> <p>
<span><FormattedMessage {...messages.deploymentInProgress} /> </span> <span>
<FormattedMessage {...messages.deploymentInProgress} />{' '}
</span>
<Link to={`/plans/${currentPlanName}/deployment-detail`}> <Link to={`/plans/${currentPlanName}/deployment-detail`}>
<FormattedMessage {...messages.viewInformation} /> <FormattedMessage {...messages.viewInformation} />
</Link> </Link>

View File

@ -41,7 +41,8 @@ const messages = defineMessages({
availableNodesCount: { availableNodesCount: {
id: 'RoleCard.availableNodesCount', id: 'RoleCard.availableNodesCount',
defaultMessage: 'of {count, number}', defaultMessage: 'of {count, number}',
description: 'Used to display available nodes to assign, e.g. 1 of 2 Nodes Assigned' description:
'Used to display available nodes to assign, e.g. 1 of 2 Nodes Assigned'
} }
}); });
@ -80,21 +81,23 @@ const RoleCard = ({
<div className="card-pf-body"> <div className="card-pf-body">
<div className="card-pf-utilization-details"> <div className="card-pf-utilization-details">
<div className="node-picker-cell"> <div className="node-picker-cell">
{assignedNodesCountParameter {assignedNodesCountParameter ? (
? <Field <Field
component={NodePickerInput} component={NodePickerInput}
increment={1} increment={1}
validate={validations} validate={validations}
name={assignedNodesCountParameter.name} name={assignedNodesCountParameter.name}
max={availableNodesCount} max={availableNodesCount}
/> />
: <NodePickerInput ) : (
increment={1} <NodePickerInput
input={{ value: '-' }} increment={1}
meta={{ submitting: true }} input={{ value: '-' }}
max={availableNodesCount} meta={{ submitting: true }}
min={0} max={availableNodesCount}
/>} min={0}
/>
)}
</div> </div>
<span className="card-pf-utilization-card-details-description"> <span className="card-pf-utilization-card-details-description">
<span className="card-pf-utilization-card-details-line-1"> <span className="card-pf-utilization-card-details-line-1">

View File

@ -23,8 +23,7 @@ import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import { CloseModalButton } from '../ui/Modals'; import { CloseModalButton } from '../ui/Modals';
import EnvironmentConfigurationActions import EnvironmentConfigurationActions from '../../actions/EnvironmentConfigurationActions';
from '../../actions/EnvironmentConfigurationActions';
import EnvironmentConfigurationTopic from './EnvironmentConfigurationTopic'; import EnvironmentConfigurationTopic from './EnvironmentConfigurationTopic';
import { getCurrentPlanName } from '../../selectors/plans'; import { getCurrentPlanName } from '../../selectors/plans';
import ModalFormErrorList from '../ui/forms/ModalFormErrorList'; import ModalFormErrorList from '../ui/forms/ModalFormErrorList';
@ -208,9 +207,7 @@ class EnvironmentConfiguration extends React.Component {
</ul> </ul>
</div> </div>
<div className="col-sm-8"> <div className="col-sm-8">
<div className="tab-content"> <div className="tab-content">{topics}</div>
{topics}
</div>
</div> </div>
</div> </div>
</div> </div>
@ -293,10 +290,10 @@ export default injectIntl(
); );
/** /**
* requiresEnvironments validation * requiresEnvironments validation
* Invalidates input if it is selected and environment it requires is not. * Invalidates input if it is selected and environment it requires is not.
* example: validations="requiredEnvironments:['some_environment.yaml']" * example: validations="requiredEnvironments:['some_environment.yaml']"
*/ */
Formsy.addValidationRule('requiredEnvironments', function( Formsy.addValidationRule('requiredEnvironments', function(
values, values,
value, value,

View File

@ -40,7 +40,11 @@ export default class EnvironmentConfigurationTopic extends React.Component {
const { description } = this.props; const { description } = this.props;
return ( return (
<fieldset className="environment-topic"> <fieldset className="environment-topic">
{description && <p><i>{description}</i></p>} {description && (
<p>
<i>{description}</i>
</p>
)}
{environmentGroups} {environmentGroups}
</fieldset> </fieldset>
); );

View File

@ -139,7 +139,8 @@ class EnvironmentGroupHeading extends React.Component {
if (this.props.title) { if (this.props.title) {
return ( return (
<h4> <h4>
{this.props.title}<br /> {this.props.title}
<br />
<small>{this.props.description}</small> <small>{this.props.description}</small>
</h4> </h4>
); );

View File

@ -40,15 +40,15 @@ class I18nDropdown extends React.Component {
return languages return languages
.map((langName, langKey) => { .map((langName, langKey) => {
const active = currentLanguage === langKey; const active = currentLanguage === langKey;
return MESSAGES[langKey] || langKey === 'en' return MESSAGES[langKey] || langKey === 'en' ? (
? <DropdownItem <DropdownItem
key={`lang-${langKey}`} key={`lang-${langKey}`}
active={active} active={active}
onClick={this.props.chooseLanguage.bind(this, langKey)} onClick={this.props.chooseLanguage.bind(this, langKey)}
> >
{langName} {langName}
</DropdownItem> </DropdownItem>
: null; ) : null;
}) })
.toList(); .toList();
} }

View File

@ -65,7 +65,8 @@ const messages = defineMessages({
}, },
description: { description: {
id: 'Login.description', id: 'Login.description',
defaultMessage: 'This tool will walk you through the process of configuring and ' + defaultMessage:
'This tool will walk you through the process of configuring and ' +
'deploying an OpenStack environment.' 'deploying an OpenStack environment.'
} }
}); });
@ -183,7 +184,9 @@ class Login extends React.Component {
</div> </div>
<div className="col-sm-5 col-md-6 col-lg-7 details"> <div className="col-sm-5 col-md-6 col-lg-7 details">
<p> <p>
<strong><FormattedMessage {...messages.welcome} /></strong> <strong>
<FormattedMessage {...messages.welcome} />
</strong>
<br /> <br />
<FormattedMessage {...messages.description} /> <FormattedMessage {...messages.description} />
</p> </p>

View File

@ -89,23 +89,37 @@ const NodeDrive = ({ drive }) => {
<Row> <Row>
<Col sm={11}> <Col sm={11}>
<dl className="dl-horizontal"> <dl className="dl-horizontal">
<dt><FormattedMessage {...messages.model} /></dt> <dt>
<FormattedMessage {...messages.model} />
</dt>
<dd>{drive.model || <FormattedMessage {...messages.na} />}</dd> <dd>{drive.model || <FormattedMessage {...messages.na} />}</dd>
<dt><FormattedMessage {...messages.serial} /></dt> <dt>
<FormattedMessage {...messages.serial} />
</dt>
<dd>{drive.serial || <FormattedMessage {...messages.na} />}</dd> <dd>{drive.serial || <FormattedMessage {...messages.na} />}</dd>
<dt><FormattedMessage {...messages.vendor} /></dt> <dt>
<FormattedMessage {...messages.vendor} />
</dt>
<dd>{drive.vendor || <FormattedMessage {...messages.na} />}</dd> <dd>{drive.vendor || <FormattedMessage {...messages.na} />}</dd>
<dt><FormattedMessage {...messages.wwn} /></dt> <dt>
<FormattedMessage {...messages.wwn} />
</dt>
<dd>{drive.wwn || <FormattedMessage {...messages.na} />}</dd> <dd>{drive.wwn || <FormattedMessage {...messages.na} />}</dd>
<dt><FormattedMessage {...messages.wwnVendorExtension} /></dt> <dt>
<FormattedMessage {...messages.wwnVendorExtension} />
</dt>
<dd> <dd>
{drive.wwn_vendor_extension || {drive.wwn_vendor_extension || (
<FormattedMessage {...messages.na} />} <FormattedMessage {...messages.na} />
)}
</dd> </dd>
<dt><FormattedMessage {...messages.wwnWithExtension} /></dt> <dt>
<FormattedMessage {...messages.wwnWithExtension} />
</dt>
<dd> <dd>
{drive.wwn_with_extension || {drive.wwn_with_extension || (
<FormattedMessage {...messages.na} />} <FormattedMessage {...messages.na} />
)}
</dd> </dd>
</dl> </dl>
</Col> </Col>

View File

@ -67,15 +67,17 @@ class Nodes extends React.Component {
} }
renderContentView() { renderContentView() {
return this.props.contentView === 'table' return this.props.contentView === 'table' ? (
? <NodesTableView /> <NodesTableView />
: <NodesListForm> ) : (
<NodesListView <NodesListForm>
fetchNodeIntrospectionData={this.props.fetchNodeIntrospectionData} <NodesListView
nodes={this.props.nodes} fetchNodeIntrospectionData={this.props.fetchNodeIntrospectionData}
nodesInProgress={this.props.nodesInProgress} nodes={this.props.nodes}
/> nodesInProgress={this.props.nodesInProgress}
</NodesListForm>; />
</NodesListForm>
);
} }
render() { render() {
@ -107,7 +109,9 @@ class Nodes extends React.Component {
<FormattedMessage {...messages.registerNodes} /> <FormattedMessage {...messages.registerNodes} />
</Link> </Link>
</div> </div>
<h1><FormattedMessage {...messages.nodes} /></h1> <h1>
<FormattedMessage {...messages.nodes} />
</h1>
</div> </div>
<Loader <Loader
loaded={this.props.nodesLoaded} loaded={this.props.nodesLoaded}

View File

@ -103,26 +103,29 @@ class NodeExtendedInfo extends React.Component {
if (node.getIn(['introspectionData', 'interfaces']).isEmpty()) { if (node.getIn(['introspectionData', 'interfaces']).isEmpty()) {
return ( return (
<dl> <dl>
<dt><FormattedMessage {...messages.macAddresses} /></dt> <dt>
<FormattedMessage {...messages.macAddresses} />
</dt>
{node.get('macs').map(mac => <dd key={mac}>{mac}</dd>)} {node.get('macs').map(mac => <dd key={mac}>{mac}</dd>)}
</dl> </dl>
); );
} else { } else {
return ( return (
<dl> <dl>
<dt><FormattedMessage {...messages.interfaces} /></dt> <dt>
<FormattedMessage {...messages.interfaces} />
</dt>
<dd> <dd>
{node {node
.getIn(['introspectionData', 'interfaces']) .getIn(['introspectionData', 'interfaces'])
.map((ifc, k) => { .map((ifc, k) => {
return ( return (
<div key={k}> <div key={k}>
{k} {k} -{' '}
{' '} - {' '}
<span title={intl.formatMessage(messages.macAddress)}> <span title={intl.formatMessage(messages.macAddress)}>
{ifc.get('mac')} {ifc.get('mac')}
</span> </span>{' '}
{' '} | {' '} |{' '}
<span title={intl.formatMessage(messages.ipAddress)}> <span title={intl.formatMessage(messages.ipAddress)}>
{ifc.get('ip')} {ifc.get('ip')}
</span> </span>
@ -140,58 +143,71 @@ class NodeExtendedInfo extends React.Component {
renderBios() { renderBios() {
const bios = this.props.node.getIn(['introspectionData', 'bios']); const bios = this.props.node.getIn(['introspectionData', 'bios']);
return ( return (
!bios.isEmpty() && !bios.isEmpty() && (
<div> <div>
<dt><FormattedMessage {...messages.bios} /></dt> <dt>
<dd className="NodeExtendedInfo__bios"> <FormattedMessage {...messages.bios} />
{bios </dt>
.map((i, k) => <span key={k} title={startCase(k)}>{i} </span>) <dd className="NodeExtendedInfo__bios">
.toList()} {bios
</dd> .map((i, k) => (
</div> <span key={k} title={startCase(k)}>
{i}{' '}
</span>
))
.toList()}
</dd>
</div>
)
); );
} }
renderRootDisk() { renderRootDisk() {
const rootDisk = this.props.node.getIn(['introspectionData', 'rootDisk']); const rootDisk = this.props.node.getIn(['introspectionData', 'rootDisk']);
return ( return (
rootDisk && rootDisk && (
<div> <div>
<dt><FormattedMessage {...messages.rootDisk} /></dt> <dt>
<dd className="NodeExtendedInfo__rootDisk">{rootDisk}</dd> <FormattedMessage {...messages.rootDisk} />
</div> </dt>
<dd className="NodeExtendedInfo__rootDisk">{rootDisk}</dd>
</div>
)
); );
} }
renderProduct() { renderProduct() {
const product = this.props.node.getIn(['introspectionData', 'product']); const product = this.props.node.getIn(['introspectionData', 'product']);
return ( return (
!product.isEmpty() && !product.isEmpty() && (
<div> <div>
<dt><FormattedMessage {...messages.product} /></dt> <dt>
<dd> <FormattedMessage {...messages.product} />
<span </dt>
className="NodeExtendedInfo__productName" <dd>
title={this.props.intl.formatMessage(messages.productName)} <span
> className="NodeExtendedInfo__productName"
{product.get('name')} title={this.props.intl.formatMessage(messages.productName)}
</span> >
{' '} - {' '} {product.get('name')}
<span </span>{' '}
className="NodeExtendedInfo__productVendor" -{' '}
title={this.props.intl.formatMessage(messages.productVendor)} <span
> className="NodeExtendedInfo__productVendor"
{product.get('vendor')} title={this.props.intl.formatMessage(messages.productVendor)}
</span> >
{' '} | {' '} {product.get('vendor')}
<span </span>{' '}
className="NodeExtendedInfo__productVersion" |{' '}
title={this.props.intl.formatMessage(messages.productVersion)} <span
> className="NodeExtendedInfo__productVersion"
{product.get('version')} title={this.props.intl.formatMessage(messages.productVersion)}
</span> >
</dd> {product.get('version')}
</div> </span>
</dd>
</div>
)
); );
} }
@ -201,13 +217,14 @@ class NodeExtendedInfo extends React.Component {
'kernelVersion' 'kernelVersion'
]); ]);
return ( return (
kernelVersion && kernelVersion && (
<div> <div>
<dt><FormattedMessage {...messages.kernel} /></dt> <dt>
<dd className="NodeExtendedInfo__kernelVersion"> <FormattedMessage {...messages.kernel} />
{kernelVersion} </dt>
</dd> <dd className="NodeExtendedInfo__kernelVersion">{kernelVersion}</dd>
</div> </div>
)
); );
} }
@ -217,15 +234,21 @@ class NodeExtendedInfo extends React.Component {
<Row className="NodeExtendedInfo__extendedInfoRow"> <Row className="NodeExtendedInfo__extendedInfoRow">
<Col lg={4} md={6}> <Col lg={4} md={6}>
<dl className="dl-horizontal dl-horizontal-condensed"> <dl className="dl-horizontal dl-horizontal-condensed">
<dt><FormattedMessage {...messages.uuid} /></dt> <dt>
<FormattedMessage {...messages.uuid} />
</dt>
<dd className="NodeExtendedInfo__uuid">{node.get('uuid')}</dd> <dd className="NodeExtendedInfo__uuid">{node.get('uuid')}</dd>
<dt><FormattedMessage {...messages.registered} /></dt> <dt>
<FormattedMessage {...messages.registered} />
</dt>
<dd className="NodeExtendedInfo__registered"> <dd className="NodeExtendedInfo__registered">
<FormattedDate value={node.get('created_at')} /> <FormattedDate value={node.get('created_at')} />
&nbsp; &nbsp;
<FormattedTime value={node.get('created_at')} /> <FormattedTime value={node.get('created_at')} />
</dd> </dd>
<dt><FormattedMessage {...messages.architecture} /></dt> <dt>
<FormattedMessage {...messages.architecture} />
</dt>
<dd className="NodeExtendedInfo__architecture"> <dd className="NodeExtendedInfo__architecture">
{node.getIn(['properties', 'cpu_arch'])} {node.getIn(['properties', 'cpu_arch'])}
</dd> </dd>
@ -237,10 +260,10 @@ class NodeExtendedInfo extends React.Component {
</Col> </Col>
<Col lg={4} md={6}> <Col lg={4} md={6}>
<dl className="dl-horizontal dl-horizontal-condensed"> <dl className="dl-horizontal dl-horizontal-condensed">
<dt><FormattedMessage {...messages.driver} /></dt> <dt>
<dd className="NodeExtendedInfo__driver"> <FormattedMessage {...messages.driver} />
{node.get('driver')} </dt>
</dd> <dd className="NodeExtendedInfo__driver">{node.get('driver')}</dd>
{node {node
.get('driver_info') .get('driver_info')
.map((dInfo, key) => ( .map((dInfo, key) => (

View File

@ -75,12 +75,13 @@ export const NodeListItem = ({
/> />
} }
actions={ actions={
node.getIn(['introspectionStatus', 'state']) === 'finished' && node.getIn(['introspectionStatus', 'state']) === 'finished' && (
<DropdownKebab id={`${node.get('uuid')}Actions`} pullRight> <DropdownKebab id={`${node.get('uuid')}Actions`} pullRight>
<MenuItemLink to={`/nodes/${node.get('uuid')}/drives`}> <MenuItemLink to={`/nodes/${node.get('uuid')}/drives`}>
<FormattedMessage {...messages.manageDrives} /> <FormattedMessage {...messages.manageDrives} />
</MenuItemLink> </MenuItemLink>
</DropdownKebab> </DropdownKebab>
)
} }
leftContent={ leftContent={
<ListViewIcon <ListViewIcon
@ -137,9 +138,7 @@ export const NodeListItem = ({
</ListViewInfoItem>, </ListViewInfoItem>,
<ListViewInfoItem key="memory" className="NodeListItem__memorySize"> <ListViewInfoItem key="memory" className="NodeListItem__memorySize">
<span className="pficon pficon-memory" /> <span className="pficon pficon-memory" />
<strong> <strong>{node.getIn(['properties', 'memory_mb'], '-')}</strong>
{node.getIn(['properties', 'memory_mb'], '-')}
</strong>
&nbsp; &nbsp;
<FormattedMessage {...messages.ram} /> <FormattedMessage {...messages.ram} />
</ListViewInfoItem>, </ListViewInfoItem>,

View File

@ -85,16 +85,17 @@ export const NodeProvisionState = ({
...rest ...rest
}) => ( }) => (
<span {...rest}> <span {...rest}>
<strong><FormattedMessage {...messages.provisionState} /></strong>&nbsp; <strong>
{targetProvisionState <FormattedMessage {...messages.provisionState} />
? <span> </strong>&nbsp;
{provisionState} {targetProvisionState ? (
{' '} <span>
<span className="fa fa-long-arrow-right" /> {provisionState} <span className="fa fa-long-arrow-right" />{' '}
{' '} {targetProvisionState}
{targetProvisionState} </span>
</span> ) : (
: provisionState} provisionState
)}
</span> </span>
); );
NodeProvisionState.propTypes = { NodeProvisionState.propTypes = {
@ -107,7 +108,9 @@ export const NodeIntrospectionStatus = ({
...rest ...rest
}) => ( }) => (
<span title={error} {...rest}> <span title={error} {...rest}>
<strong><FormattedMessage {...messages.introspectionState} /></strong>&nbsp; <strong>
<FormattedMessage {...messages.introspectionState} />
</strong>&nbsp;
{state} {state}
</span> </span>
); );

View File

@ -32,7 +32,8 @@ const messages = defineMessages({
}, },
operationInProgress: { operationInProgress: {
id: 'NodesListForm.operationInProgressValidationMessage', id: 'NodesListForm.operationInProgressValidationMessage',
defaultMessage: 'There is an operation in progress on some of the selected Nodes' defaultMessage:
'There is an operation in progress on some of the selected Nodes'
} }
}); });

View File

@ -303,11 +303,7 @@ export default injectIntl(NodesTable);
export const NodesTableMaintenanceCell = props => { export const NodesTableMaintenanceCell = props => {
const value = _.result(props.data[props.rowIndex], props.field).toString(); const value = _.result(props.data[props.rowIndex], props.field).toString();
return ( return <DataTableCell {...props}>{value}</DataTableCell>;
<DataTableCell {...props}>
{value}
</DataTableCell>
);
}; };
NodesTableMaintenanceCell.propTypes = { NodesTableMaintenanceCell.propTypes = {
data: PropTypes.array.isRequired, data: PropTypes.array.isRequired,
@ -317,11 +313,7 @@ NodesTableMaintenanceCell.propTypes = {
export const NodesTableMacsCell = props => { export const NodesTableMacsCell = props => {
const value = _.result(props.data[props.rowIndex], props.field).join(', '); const value = _.result(props.data[props.rowIndex], props.field).join(', ');
return ( return <DataTableCell {...props}>{value}</DataTableCell>;
<DataTableCell {...props}>
{value}
</DataTableCell>
);
}; };
NodesTableMacsCell.propTypes = { NodesTableMacsCell.propTypes = {
data: PropTypes.array.isRequired, data: PropTypes.array.isRequired,

View File

@ -47,7 +47,8 @@ const messages = defineMessages({
provideNodes: { provideNodes: {
id: 'NodesTableView.provideNodes', id: 'NodesTableView.provideNodes',
defaultMessage: 'Provide Nodes', defaultMessage: 'Provide Nodes',
description: '"Providing" the nodes changes the provisioning state to "available" so that ' + description:
'"Providing" the nodes changes the provisioning state to "available" so that ' +
'they can be used in a deployment.' 'they can be used in a deployment.'
}, },
deleteNodes: { deleteNodes: {
@ -238,7 +239,8 @@ class NodesTableView extends React.Component {
availableProfiles={this.props.availableProfiles.toArray()} availableProfiles={this.props.availableProfiles.toArray()}
onProfileSelected={this.onTagNodesSubmit.bind(this)} onProfileSelected={this.onTagNodesSubmit.bind(this)}
onCancel={() => onCancel={() =>
this.setState({ showTagNodesModal: false, submitParameters: {} })} this.setState({ showTagNodesModal: false, submitParameters: {} })
}
show={this.state.showTagNodesModal} show={this.state.showTagNodesModal}
/> />
</Formsy.Form> </Formsy.Form>

View File

@ -60,7 +60,8 @@ const messages = defineMessages({
}, },
nonFilteredToolbarResults: { nonFilteredToolbarResults: {
id: 'NodesToolbar.nonFilteredToolbarResults', id: 'NodesToolbar.nonFilteredToolbarResults',
defaultMessage: '{totalCount, number} {totalCount, plural, one {Node} other {Nodes}}' defaultMessage:
'{totalCount, number} {totalCount, plural, one {Node} other {Nodes}}'
} }
}); });
@ -89,7 +90,8 @@ class NodesToolbar extends React.Component {
id="NodesToolbar_toolbarFiltersForm" id="NodesToolbar_toolbarFiltersForm"
form="nodesToolbarFilter" form="nodesToolbarFilter"
formatSelectValue={value => formatSelectValue={value =>
intl.formatMessage(nodeColumnMessages[value])} intl.formatMessage(nodeColumnMessages[value])
}
initialValues={{ filterBy: 'name' }} initialValues={{ filterBy: 'name' }}
onSubmit={addActiveFilter} onSubmit={addActiveFilter}
options={{ options={{

View File

@ -36,7 +36,8 @@ const messages = defineMessages({
}, },
disabledButtonsWarning: { disabledButtonsWarning: {
id: 'NodesToolbarActions.disabledButtonsWarning', id: 'NodesToolbarActions.disabledButtonsWarning',
defaultMessage: 'You need to select Nodes first, or there is an operation already in ' + defaultMessage:
'You need to select Nodes first, or there is an operation already in ' +
'progress on some of the selected Nodes.' 'progress on some of the selected Nodes.'
}, },
introspectNodes: { introspectNodes: {
@ -50,13 +51,15 @@ const messages = defineMessages({
provideNodes: { provideNodes: {
id: 'NodesToolbarActions.provideNodes', id: 'NodesToolbarActions.provideNodes',
defaultMessage: 'Provide Nodes', defaultMessage: 'Provide Nodes',
description: '"Providing" the nodes changes the provisioning state to "available" so that ' + description:
'"Providing" the nodes changes the provisioning state to "available" so that ' +
'they can be used in a deployment.' 'they can be used in a deployment.'
}, },
manageNodes: { manageNodes: {
id: 'NodesToolbarActions.manageNodes', id: 'NodesToolbarActions.manageNodes',
defaultMessage: 'Manage Nodes', defaultMessage: 'Manage Nodes',
description: '"Managing" the nodes changes the provisioning state to "manageable" so that ' + description:
'"Managing" the nodes changes the provisioning state to "manageable" so that ' +
'they can be introspected.' 'they can be introspected.'
}, },
deleteNodes: { deleteNodes: {
@ -98,68 +101,64 @@ class NodesToolbarActions extends React.Component {
// TODO(jtomasek): include proper error message from the form accessed via getFormSyncErrors // TODO(jtomasek): include proper error message from the form accessed via getFormSyncErrors
// selector once the 'error' is available via selector // selector once the 'error' is available via selector
// https://github.com/erikras/redux-form/issues/2872 // https://github.com/erikras/redux-form/issues/2872
( <FormGroup
<FormGroup title={
title={ disabled ? intl.formatMessage(messages.disabledButtonsWarning) : ''
disabled ? intl.formatMessage(messages.disabledButtonsWarning) : '' }
} >
<Button
id="NodesToolbarActions__introspectNodesAction"
disabled={this.props.disabled}
onClick={this.submitForm.bind(this, 'introspect')}
> >
<Button <FormattedMessage {...messages.introspectNodes} />
id="NodesToolbarActions__introspectNodesAction" </Button>
<Button
id="NodesToolbarActions__provideNodesAction"
disabled={this.props.disabled}
onClick={this.submitForm.bind(this, 'provide')}
>
<FormattedMessage {...messages.provideNodes} />
</Button>
<DropdownKebab id="NodesToolbarActions__nodesActionsKebab" pullRight>
<MenuItem
id="NodesToolbarActions__manageNodesAction"
disabled={this.props.disabled} disabled={this.props.disabled}
onClick={this.submitForm.bind(this, 'introspect')} onSelect={this.submitForm.bind(this, 'manage')}
> >
<FormattedMessage {...messages.introspectNodes} /> <FormattedMessage {...messages.manageNodes} />
</Button> </MenuItem>
<Button <MenuItem
id="NodesToolbarActions__provideNodesAction" id="NodesToolbarActions__tagNodesAction"
disabled={this.props.disabled} disabled={this.props.disabled}
onClick={this.submitForm.bind(this, 'provide')} onSelect={() => this.setState({ showTagNodesModal: true })}
> >
<FormattedMessage {...messages.provideNodes} /> <FormattedMessage {...messages.tagNodes} />
</Button> </MenuItem>
<DropdownKebab id="NodesToolbarActions__nodesActionsKebab" pullRight> <MenuItem
<MenuItem id="NodesToolbarActions__deleteNodesAction"
id="NodesToolbarActions__manageNodesAction" className="bg-danger"
disabled={this.props.disabled} disabled={this.props.disabled}
onSelect={this.submitForm.bind(this, 'manage')} onSelect={() => this.setState({ showDeleteModal: true })}
> >
<FormattedMessage {...messages.manageNodes} /> <FormattedMessage {...messages.deleteNodes} />
</MenuItem> </MenuItem>
<MenuItem </DropdownKebab>
id="NodesToolbarActions__tagNodesAction" <ConfirmationModal
disabled={this.props.disabled} show={this.state.showDeleteModal}
onSelect={() => this.setState({ showTagNodesModal: true })} title={this.props.intl.formatMessage(messages.deleteNodesModalTitle)}
> question={this.props.intl.formatMessage(
<FormattedMessage {...messages.tagNodes} /> messages.deleteNodesModalMessage
</MenuItem> )}
<MenuItem onConfirm={() => this.deleteNodes('delete')}
id="NodesToolbarActions__deleteNodesAction" onCancel={() => this.setState({ showDeleteModal: false })}
className="bg-danger" />
disabled={this.props.disabled} <TagNodesModal
onSelect={() => this.setState({ showDeleteModal: true })} onProfileSelected={this.tagNodes.bind(this)}
> onCancel={() => this.setState({ showTagNodesModal: false })}
<FormattedMessage {...messages.deleteNodes} /> show={this.state.showTagNodesModal}
</MenuItem> />
</DropdownKebab> </FormGroup>
<ConfirmationModal
show={this.state.showDeleteModal}
title={this.props.intl.formatMessage(
messages.deleteNodesModalTitle
)}
question={this.props.intl.formatMessage(
messages.deleteNodesModalMessage
)}
onConfirm={() => this.deleteNodes('delete')}
onCancel={() => this.setState({ showDeleteModal: false })}
/>
<TagNodesModal
onProfileSelected={this.tagNodes.bind(this)}
onCancel={() => this.setState({ showTagNodesModal: false })}
show={this.state.showTagNodesModal}
/>
</FormGroup>
)
); );
} }
} }

View File

@ -38,8 +38,7 @@ const NodeTab = ({
}) => ( }) => (
<Tab isActive={isActive}> <Tab isActive={isActive}>
<a className="link" onClick={selectNode}> <a className="link" onClick={selectNode}>
<span className={cx('pficon', { 'pficon-error-circle-o': invalid })} /> <span className={cx('pficon', { 'pficon-error-circle-o': invalid })} />{' '}
{' '}
{node.name || {node.name ||
(node.pm_addr && (node.pm_addr &&
node.pm_addr + (node.pm_port ? `:${node.pm_port}` : '')) || node.pm_addr + (node.pm_port ? `:${node.pm_port}` : '')) ||

View File

@ -35,7 +35,8 @@ const messages = defineMessages({
}, },
nodeNameRegexp: { nodeNameRegexp: {
id: 'RegisterNodeForm.nodeNameRegexp', id: 'RegisterNodeForm.nodeNameRegexp',
defaultMessage: 'Name may only consist of RFC3986 unreserved characters: alphanumeric, hyphen (-),' + defaultMessage:
'Name may only consist of RFC3986 unreserved characters: alphanumeric, hyphen (-),' +
' period (.), underscore (_) and tilde (~) characters.' ' period (.), underscore (_) and tilde (~) characters.'
}, },
nodeNameMaxLength: { nodeNameMaxLength: {
@ -92,7 +93,8 @@ const messages = defineMessages({
}, },
macAddressesDescription: { macAddressesDescription: {
id: 'RegisterNodeForm.macAddressesDescription', id: 'RegisterNodeForm.macAddressesDescription',
defaultMessage: 'If you are specifying multiple MAC Addresses, please enter a comma separated list. (e.g. aa:bb:cc:dd:ee:ff,12:34:56:78:90:xx,do:re:mi:fa:so:ra)' defaultMessage:
'If you are specifying multiple MAC Addresses, please enter a comma separated list. (e.g. aa:bb:cc:dd:ee:ff,12:34:56:78:90:xx,do:re:mi:fa:so:ra)'
} }
}); });
@ -114,7 +116,9 @@ const RegisterNodeFields = ({
}) => { }) => {
return ( return (
<div> <div>
<h4><FormattedMessage {...messages.nodeDetail} /></h4> <h4>
<FormattedMessage {...messages.nodeDetail} />
</h4>
<Fieldset legend={formatMessage(messages.general)}> <Fieldset legend={formatMessage(messages.general)}>
<Field <Field
name={`${node}.name`} name={`${node}.name`}

View File

@ -203,8 +203,7 @@ class RegisterNodesDialog extends React.Component {
<div className="col-sm-4 col-lg-3 sidebar-pf sidebar-pf-left"> <div className="col-sm-4 col-lg-3 sidebar-pf sidebar-pf-left">
<div className="nav-stacked-actions"> <div className="nav-stacked-actions">
<Button onClick={e => this.addNode()}> <Button onClick={e => this.addNode()}>
<span className="fa fa-plus" /> <span className="fa fa-plus" />{' '}
{' '}
<FormattedMessage {...messages.addNew} /> <FormattedMessage {...messages.addNew} />
</Button> </Button>
&nbsp; <FormattedMessage {...messages.or} /> &nbsp; &nbsp; <FormattedMessage {...messages.or} /> &nbsp;
@ -268,9 +267,11 @@ function mapStateToProps(state) {
syncErrors: getFormSyncErrors('registerNodesForm')(state), syncErrors: getFormSyncErrors('registerNodesForm')(state),
invalid: isInvalid('registerNodesForm')(state), invalid: isInvalid('registerNodesForm')(state),
pristine: isPristine('registerNodesForm')(state), pristine: isPristine('registerNodesForm')(state),
nodesToRegister: (getFormValues('registerNodesForm')(state) || { nodesToRegister: (
nodes: [] getFormValues('registerNodesForm')(state) || {
}).nodes, nodes: []
}
).nodes,
submitting: isSubmitting('registerNodesForm')(state) submitting: isSubmitting('registerNodesForm')(state)
}; };
} }

View File

@ -66,22 +66,23 @@ const RegisterNodesForm = ({
selectedNodeIndex selectedNodeIndex
}) => ( }) => (
<Form onSubmit={handleSubmit} horizontal> <Form onSubmit={handleSubmit} horizontal>
{error && {error && <InlineNotification>{error}</InlineNotification>}
<InlineNotification> {selectedNodeIndex !== -1 ? (
{error} <FieldArray
</InlineNotification>} name="nodes"
{selectedNodeIndex !== -1 component={RegisterNodesTabPanes}
? <FieldArray selectedNodeIndex={selectedNodeIndex}
name="nodes" />
component={RegisterNodesTabPanes} ) : (
selectedNodeIndex={selectedNodeIndex} <BlankSlate
/> iconClass="fa fa-cubes"
: <BlankSlate title={formatMessage(messages.noNodesToRegister)}
iconClass="fa fa-cubes" >
title={formatMessage(messages.noNodesToRegister)} <p>
> <FormattedMessage {...messages.addANodeManually} />
<p><FormattedMessage {...messages.addANodeManually} /></p> </p>
</BlankSlate>} </BlankSlate>
)}
</Form> </Form>
); );
RegisterNodesForm.propTypes = { RegisterNodesForm.propTypes = {

View File

@ -34,7 +34,8 @@ const messages = defineMessages({
noRolesInfo: { noRolesInfo: {
id: 'TagNodesForm.noRolesInfo', id: 'TagNodesForm.noRolesInfo',
defaultMessage: '{link} to select profiles which match available Roles', defaultMessage: '{link} to select profiles which match available Roles',
description: 'A second part of noRolesInfo message - rest of the text after link' description:
'A second part of noRolesInfo message - rest of the text after link'
}, },
confirm: { confirm: {
id: 'TagNodesForm.confirm', id: 'TagNodesForm.confirm',
@ -104,7 +105,9 @@ class TagNodesForm extends React.Component {
return this.props.profiles return this.props.profiles
.map((profile, index) => <option key={index}>{profile}</option>) .map((profile, index) => <option key={index}>{profile}</option>)
.concat([ .concat([
<option key="spacer1" value="spacer" disabled></option>, <option key="spacer1" value="spacer" disabled>
</option>,
<option key="noProfile" value=""> <option key="noProfile" value="">
{this.props.intl.formatMessage(messages.noProfileOption)} {this.props.intl.formatMessage(messages.noProfileOption)}
</option>, </option>,
@ -126,7 +129,7 @@ class TagNodesForm extends React.Component {
onInvalid={this.disableButton.bind(this)} onInvalid={this.disableButton.bind(this)}
> >
<div className="modal-body"> <div className="modal-body">
{roles.isEmpty() && {roles.isEmpty() && (
<InlineNotification type="info"> <InlineNotification type="info">
<FormattedMessage <FormattedMessage
{...messages.noRolesInfo} {...messages.noRolesInfo}
@ -138,7 +141,8 @@ class TagNodesForm extends React.Component {
) )
}} }}
/> />
</InlineNotification>} </InlineNotification>
)}
<fieldset> <fieldset>
<HorizontalSelect <HorizontalSelect
name="profile" name="profile"
@ -149,22 +153,22 @@ class TagNodesForm extends React.Component {
> >
{this.renderOptions()} {this.renderOptions()}
</HorizontalSelect> </HorizontalSelect>
{this.state.showCustomInput {this.state.showCustomInput ? (
? <HorizontalInput <HorizontalInput
name="customProfile" name="customProfile"
title={formatMessage(messages.customProfileLabel)} title={formatMessage(messages.customProfileLabel)}
type="text" type="text"
inputColumnClasses="col-sm-7" inputColumnClasses="col-sm-7"
labelColumnClasses="col-sm-3" labelColumnClasses="col-sm-3"
value="" value=""
validations={{ matchRegexp: /^[0-9a-z]+(-[0-9a-z]+)*$/ }} validations={{ matchRegexp: /^[0-9a-z]+(-[0-9a-z]+)*$/ }}
validationError={formatMessage( validationError={formatMessage(
messages.customProfileErrorMessage messages.customProfileErrorMessage
)} )}
description={formatMessage(messages.customProfileDescription)} description={formatMessage(messages.customProfileDescription)}
required required
/> />
: null} ) : null}
</fieldset> </fieldset>
</div> </div>
<div className="modal-footer"> <div className="modal-footer">

View File

@ -54,8 +54,7 @@ class TagNodesModal extends React.Component {
<ModalHeader> <ModalHeader>
<CloseModalXButton /> <CloseModalXButton />
<ModalTitle> <ModalTitle>
<span className="fa fa-tag" /> <span className="fa fa-tag" />{' '}
{' '}
<FormattedMessage {...messages.title} /> <FormattedMessage {...messages.title} />
</ModalTitle> </ModalTitle>
</ModalHeader> </ModalHeader>

View File

@ -58,11 +58,7 @@ export default class Notification extends React.Component {
renderMessage(message) { renderMessage(message) {
if (typeof message === 'object') { if (typeof message === 'object') {
return ( return <ul>{message.map((msg, i) => <li key={i}>{msg}</li>)}</ul>;
<ul>
{message.map((msg, i) => <li key={i}>{msg}</li>)}
</ul>
);
} else { } else {
return <p>{message}</p>; return <p>{message}</p>;
} }
@ -90,16 +86,18 @@ export default class Notification extends React.Component {
<div className="clearfix"> <div className="clearfix">
<div className={classes} role="alert"> <div className={classes} role="alert">
<span className={iconClass} aria-hidden="true" /> <span className={iconClass} aria-hidden="true" />
{this.props.dismissable {this.props.dismissable ? (
? <button <button
type="button" type="button"
className="close" className="close"
aria-label="Close" aria-label="Close"
onClick={this.props.removeNotification} onClick={this.props.removeNotification}
> >
<span className="pficon pficon-close" aria-hidden="true" /> <span className="pficon pficon-close" aria-hidden="true" />
</button> </button>
: false} ) : (
false
)}
<strong>{this.props.title}</strong> <strong>{this.props.title}</strong>
{this.renderMessage(this.props.message)} {this.renderMessage(this.props.message)}
</div> </div>

View File

@ -52,7 +52,8 @@ class NotificationsToaster extends React.Component {
timeoutable={notification.type !== 'error'} timeoutable={notification.type !== 'error'}
timerPaused={this.state.isHovered} timerPaused={this.state.isHovered}
removeNotification={() => removeNotification={() =>
this.props.removeNotification(notification.id)} this.props.removeNotification(notification.id)
}
/> />
); );
}); });

View File

@ -19,8 +19,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import EnvironmentConfigurationActions import EnvironmentConfigurationActions from '../../actions/EnvironmentConfigurationActions';
from '../../actions/EnvironmentConfigurationActions';
import { getCurrentPlanName } from '../../selectors/plans'; import { getCurrentPlanName } from '../../selectors/plans';
import { getEnvironmentParameters } from '../../selectors/parameters'; import { getEnvironmentParameters } from '../../selectors/parameters';
import { getEnvironment } from '../../selectors/environmentConfiguration'; import { getEnvironment } from '../../selectors/environmentConfiguration';
@ -44,13 +43,15 @@ class EnvironmentParameters extends React.Component {
loaded={!isFetchingEnvironment} loaded={!isFetchingEnvironment}
content="Fetching Parameters..." content="Fetching Parameters..."
> >
{environmentError {environmentError ? (
? <fieldset> <fieldset>
<InlineNotification title={environmentError.title} type="error"> <InlineNotification title={environmentError.title} type="error">
{environmentError.message} {environmentError.message}
</InlineNotification> </InlineNotification>
</fieldset> </fieldset>
: <ParameterInputList parameters={parameters.toList()} />} ) : (
<ParameterInputList parameters={parameters.toList()} />
)}
</Loader> </Loader>
); );
} }

View File

@ -35,8 +35,8 @@ const messages = defineMessages({
class ParameterInput extends React.Component { class ParameterInput extends React.Component {
/** /**
* Process the parameter, generate relevant input * Process the parameter, generate relevant input
*/ */
render() { render() {
const { name, label, description, defaultValue, value, type } = this.props; const { name, label, description, defaultValue, value, type } = this.props;
if (value) { if (value) {

View File

@ -25,7 +25,8 @@ import ParameterInput from './ParameterInput';
const messages = defineMessages({ const messages = defineMessages({
noParameters: { noParameters: {
id: 'ParameterInputList.noParameters', id: 'ParameterInputList.noParameters',
defaultMessage: 'There are currently no parameters to configure in this section.' defaultMessage:
'There are currently no parameters to configure in this section.'
} }
}); });
@ -58,11 +59,7 @@ class ParameterInputList extends React.Component {
</fieldset> </fieldset>
); );
} else { } else {
return ( return <fieldset>{parameters}</fieldset>;
<fieldset>
{parameters}
</fieldset>
);
} }
} }
} }

View File

@ -24,14 +24,11 @@ import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import { CloseModalButton } from '../ui/Modals'; import { CloseModalButton } from '../ui/Modals';
import EnvironmentConfigurationActions import EnvironmentConfigurationActions from '../../actions/EnvironmentConfigurationActions';
from '../../actions/EnvironmentConfigurationActions';
import EnvironmentParameters from './EnvironmentParameters'; import EnvironmentParameters from './EnvironmentParameters';
import { getCurrentPlanName } from '../../selectors/plans'; import { getCurrentPlanName } from '../../selectors/plans';
import { getRootParameters } from '../../selectors/parameters'; import { getRootParameters } from '../../selectors/parameters';
import { import { getEnabledEnvironments } from '../../selectors/environmentConfiguration';
getEnabledEnvironments
} from '../../selectors/environmentConfiguration';
import { Loader } from '../ui/Loader'; import { Loader } from '../ui/Loader';
import ModalFormErrorList from '../ui/forms/ModalFormErrorList'; import ModalFormErrorList from '../ui/forms/ModalFormErrorList';
import ParametersActions from '../../actions/ParametersActions'; import ParametersActions from '../../actions/ParametersActions';
@ -92,9 +89,9 @@ class Parameters extends React.Component {
} }
/** /**
* Filter out non updated parameters, so only parameters which have been actually changed * Filter out non updated parameters, so only parameters which have been actually changed
* get sent to updateparameters * get sent to updateparameters
*/ */
_filterFormData(formData) { _filterFormData(formData) {
return fromJS(formData) return fromJS(formData)
.filterNot((value, key) => { .filterNot((value, key) => {
@ -104,9 +101,9 @@ class Parameters extends React.Component {
} }
/** /**
* Json parameter values are sent as string, this function parses them and checks if they're object * Json parameter values are sent as string, this function parses them and checks if they're object
* or array. Also, parameters with undefined value are set to null * or array. Also, parameters with undefined value are set to null
*/ */
_jsonParseFormData(formData) { _jsonParseFormData(formData) {
return mapValues(formData, value => { return mapValues(formData, value => {
try { try {
@ -212,7 +209,6 @@ class Parameters extends React.Component {
onValid={this.enableButton.bind(this)} onValid={this.enableButton.bind(this)}
onInvalid={this.disableButton.bind(this)} onInvalid={this.disableButton.bind(this)}
> >
<div className="container-fluid"> <div className="container-fluid">
<div className="row row-eq-height"> <div className="row row-eq-height">
<div className="col-sm-4 sidebar-pf sidebar-pf-left"> <div className="col-sm-4 sidebar-pf sidebar-pf-left">
@ -240,9 +236,7 @@ class Parameters extends React.Component {
loaded={this.props.parametersLoaded} loaded={this.props.parametersLoaded}
> >
<ModalFormErrorList errors={this.props.formErrors.toJS()} /> <ModalFormErrorList errors={this.props.formErrors.toJS()} />
<div className="tab-content"> <div className="tab-content">{this.renderTabPanes()}</div>
{this.renderTabPanes()}
</div>
</Loader> </Loader>
</div> </div>
</div> </div>

View File

@ -100,60 +100,60 @@ class EditPlan extends React.Component {
render() { render() {
const { plan } = this.props; const { plan } = this.props;
return plan return plan ? (
? <RoutedModal bsSize="lg" redirectPath="/plans/manage"> <RoutedModal bsSize="lg" redirectPath="/plans/manage">
<Formsy.Form <Formsy.Form
ref="EditPlanForm" ref="EditPlanForm"
role="form" role="form"
className="form-horizontal" className="form-horizontal"
onChange={this.onPlanFilesChange.bind(this)} onChange={this.onPlanFilesChange.bind(this)}
onValidSubmit={this.onFormSubmit.bind(this)} onValidSubmit={this.onFormSubmit.bind(this)}
onValid={this.onFormValid.bind(this)} onValid={this.onFormValid.bind(this)}
onInvalid={this.onFormInvalid.bind(this)} onInvalid={this.onFormInvalid.bind(this)}
>
<ModalHeader>
<CloseModalXButton />
<ModalTitle>
<FormattedMessage
{...messages.updatePlanNameFiles}
values={{ planName: plan.name }}
/>
</ModalTitle>
</ModalHeader>
<Loader
loaded={!this.props.isTransitioningPlan}
size="lg"
height={60}
content={this.props.intl.formatMessage(messages.updatingPlanLoader)}
> >
<ModalHeader> <ModalFormErrorList errors={this.props.planFormErrors.toJS()} />
<CloseModalXButton /> <div className="modal-body">
<ModalTitle> <PlanEditFormTabs
<FormattedMessage selectedFiles={this.state.selectedFiles}
{...messages.updatePlanNameFiles} planName={plan.name}
values={{ planName: plan.name }} planFiles={plan.files}
/> setUploadType={this.setUploadType.bind(this)}
</ModalTitle> uploadType={this.state.uploadType}
</ModalHeader> />
<Loader </div>
loaded={!this.props.isTransitioningPlan} </Loader>
size="lg" <ModalFooter>
height={60} <button
content={this.props.intl.formatMessage( disabled={!this.state.canSubmit}
messages.updatingPlanLoader className="btn btn-primary"
)} type="submit"
> >
<ModalFormErrorList errors={this.props.planFormErrors.toJS()} /> <FormattedMessage {...messages.uploadAndUpdate} />
<div className="modal-body"> </button>
<PlanEditFormTabs <CloseModalButton>
selectedFiles={this.state.selectedFiles} <FormattedMessage {...messages.cancel} />
planName={plan.name} </CloseModalButton>
planFiles={plan.files} </ModalFooter>
setUploadType={this.setUploadType.bind(this)} </Formsy.Form>
uploadType={this.state.uploadType} </RoutedModal>
/> ) : (
</div> <Redirect to="/plans" />
</Loader> );
<ModalFooter>
<button
disabled={!this.state.canSubmit}
className="btn btn-primary"
type="submit"
>
<FormattedMessage {...messages.uploadAndUpdate} />
</button>
<CloseModalButton>
<FormattedMessage {...messages.cancel} />
</CloseModalButton>
</ModalFooter>
</Formsy.Form>
</RoutedModal>
: <Redirect to="/plans" />;
} }
} }

View File

@ -44,7 +44,8 @@ const messages = defineMessages({
}, },
downloadPlanExportMessage: { downloadPlanExportMessage: {
id: 'ExportPlan.downloadMessage', id: 'ExportPlan.downloadMessage',
defaultMessage: 'The plan export you requested is ready. Please click the button below to ' + defaultMessage:
'The plan export you requested is ready. Please click the button below to ' +
'download the export. You might need to right-click the button and choose ' + 'download the export. You might need to right-click the button and choose ' +
'"Save link as...".' '"Save link as...".'
}, },
@ -94,21 +95,20 @@ class ExportPlan extends React.Component {
messages.exportingPlanLoader messages.exportingPlanLoader
)} )}
> >
{this.props.planExportUrl {this.props.planExportUrl ? (
? <div> <div>
<p> <p>
<FormattedMessage {...messages.downloadPlanExportMessage} /> <FormattedMessage {...messages.downloadPlanExportMessage} />
</p> </p>
<a <a href={this.props.planExportUrl} className="btn btn-success">
href={this.props.planExportUrl} <FormattedMessage {...messages.downloadPlanExport} />
className="btn btn-success" </a>
> </div>
<FormattedMessage {...messages.downloadPlanExport} /> ) : (
</a> <div>
</div> <FormattedMessage {...messages.exportError} />
: <div> </div>
<FormattedMessage {...messages.exportError} /> )}
</div>}
</Loader> </Loader>
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>

View File

@ -91,9 +91,7 @@ export default class FileList extends React.Component {
</h4> </h4>
</div> </div>
<table className="table upload-files"> <table className="table upload-files">
<tbody> <tbody>{files}</tbody>
{files}
</tbody>
</table> </table>
</div> </div>
); );

View File

@ -25,7 +25,8 @@ const messages = defineMessages({
}, },
noPlansAvailableMessage: { noPlansAvailableMessage: {
id: 'NoPlans.noPlansAvailableMessage', id: 'NoPlans.noPlansAvailableMessage',
defaultMessage: 'There are no Deployment Plans available. Please create one first.' defaultMessage:
'There are no Deployment Plans available. Please create one first.'
}, },
importPlan: { importPlan: {
id: 'NoPlans.importPlan', id: 'NoPlans.importPlan',
@ -40,12 +41,15 @@ export default class NoPlans extends React.Component {
<div className="blank-slate-pf-icon"> <div className="blank-slate-pf-icon">
<span className="fa fa-ban" /> <span className="fa fa-ban" />
</div> </div>
<h1><FormattedMessage {...messages.noPlansAvailable} /></h1> <h1>
<p><FormattedMessage {...messages.noPlansAvailableMessage} /></p> <FormattedMessage {...messages.noPlansAvailable} />
</h1>
<p>
<FormattedMessage {...messages.noPlansAvailableMessage} />
</p>
<div className="blank-slate-pf-main-action"> <div className="blank-slate-pf-main-action">
<Link to="/plans/manage/new" className="btn btn-lg btn-primary"> <Link to="/plans/manage/new" className="btn btn-lg btn-primary">
<span className="fa fa-plus" /> <span className="fa fa-plus" />{' '}
{' '}
<FormattedMessage {...messages.importPlan} /> <FormattedMessage {...messages.importPlan} />
</Link> </Link>
</div> </div>

View File

@ -50,7 +50,8 @@ const messages = defineMessages({
}, },
badExtension: { badExtension: {
id: 'PlanEditFormTabs.badExtension', id: 'PlanEditFormTabs.badExtension',
defaultMessage: 'Invalid type: plan file must be a tar archive (.tar.gz or .tgz)' defaultMessage:
'Invalid type: plan file must be a tar archive (.tar.gz or .tgz)'
} }
}); });
@ -89,9 +90,8 @@ export default class PlanEditFormTabs extends React.Component {
</Tab> </Tab>
<Tab isActive={this.isActiveTab('planFiles')}> <Tab isActive={this.isActiveTab('planFiles')}>
<a className="link" onClick={() => this.setActiveTab('planFiles')}> <a className="link" onClick={() => this.setActiveTab('planFiles')}>
<FormattedMessage {...messages.files} /> <span className="badge"> <FormattedMessage {...messages.files} />{' '}
{this.getFileCount.bind(this)()} <span className="badge">{this.getFileCount.bind(this)()}</span>
</span>
</a> </a>
</Tab> </Tab>
</ul> </ul>

View File

@ -104,27 +104,33 @@ class PlanFileInput extends React.Component {
if (!errorMessage && this.state.unreadableFile) { if (!errorMessage && this.state.unreadableFile) {
errorMessage = `${this.state.unreadableFile} could not be read.`; errorMessage = `${this.state.unreadableFile} could not be read.`;
} }
return errorMessage return errorMessage ? (
? <span className="help-block">{errorMessage}</span> <span className="help-block">{errorMessage}</span>
: false; ) : (
false
);
} }
renderDescription() { renderDescription() {
let description = this.props.description; let description = this.props.description;
return description return description ? (
? <small className="help-block">{description}</small> <small className="help-block">{description}</small>
: false; ) : (
false
);
} }
renderProgress() { renderProgress() {
return this.state.progress > 0 return this.state.progress > 0 ? (
? <div className="progress active help-block"> <div className="progress active help-block">
<div <div
className="progress-bar" className="progress-bar"
style={{ width: `${this.state.progress}%` }} style={{ width: `${this.state.progress}%` }}
/> />
</div> </div>
: false; ) : (
false
);
} }
render() { render() {

View File

@ -58,7 +58,8 @@ const messages = defineMessages({
}, },
badExtension: { badExtension: {
id: 'PlanFormTabs.badExtension', id: 'PlanFormTabs.badExtension',
defaultMessage: 'Invalid type: plan file must be a tar archive (.tar.gz or .tgz)' defaultMessage:
'Invalid type: plan file must be a tar archive (.tar.gz or .tgz)'
} }
}); });
@ -89,9 +90,8 @@ export default class PlanFormTabs extends React.Component {
</Tab> </Tab>
<Tab isActive={this.isActiveTab('planFiles')}> <Tab isActive={this.isActiveTab('planFiles')}>
<a className="link" onClick={() => this.setActiveTab('planFiles')}> <a className="link" onClick={() => this.setActiveTab('planFiles')}>
<FormattedMessage {...messages.files} /> <span className="badge"> <FormattedMessage {...messages.files} />{' '}
{this.props.selectedFiles.length} <span className="badge">{this.props.selectedFiles.length}</span>
</span>
</a> </a>
</Tab> </Tab>
</ul> </ul>

View File

@ -46,8 +46,7 @@ export default class PlanUploadTypeRadios extends React.Component {
value="tarball" value="tarball"
onChange={this.props.setUploadType} onChange={this.props.setUploadType}
defaultChecked defaultChecked
/> />{' '}
{' '}
<FormattedMessage {...messages.tarArchive} /> <FormattedMessage {...messages.tarArchive} />
</label> </label>
<label className="radio-inline" htmlFor="checkbox-folder"> <label className="radio-inline" htmlFor="checkbox-folder">
@ -58,8 +57,7 @@ export default class PlanUploadTypeRadios extends React.Component {
name="uploadType" name="uploadType"
onChange={this.props.setUploadType} onChange={this.props.setUploadType}
value="folder" value="folder"
/> />{' '}
{' '}
<FormattedMessage {...messages.localFolder} /> <FormattedMessage {...messages.localFolder} />
</label> </label>
</div> </div>

View File

@ -79,16 +79,18 @@ class PlansList extends React.Component {
</Link> </Link>
</div> </div>
</PageHeader> </PageHeader>
{this.props.plans.isEmpty() {this.props.plans.isEmpty() ? (
? <NoPlans /> <NoPlans />
: <div className="panel panel-default"> ) : (
<div className="cards-pf"> <div className="panel panel-default">
<div className="row row-cards-pf"> <div className="cards-pf">
<ImportPlanCard /> <div className="row row-cards-pf">
{this.renderCards()} <ImportPlanCard />
</div> {this.renderCards()}
</div> </div>
</div>} </div>
</div>
)}
</div> </div>
); );
} }

View File

@ -63,10 +63,11 @@ const PlanActions = ({ planName, stack }) => {
<MenuItemLink to={`/plans/manage/${planName}/export`}> <MenuItemLink to={`/plans/manage/${planName}/export`}>
<FormattedMessage {...messages.export} /> <FormattedMessage {...messages.export} />
</MenuItemLink> </MenuItemLink>
{!stack && {!stack && (
<MenuItemLink to={`/plans/manage/${planName}/delete`}> <MenuItemLink to={`/plans/manage/${planName}/delete`}>
<FormattedMessage {...messages.delete} /> <FormattedMessage {...messages.delete} />
</MenuItemLink>} </MenuItemLink>
)}
</DropdownKebab> </DropdownKebab>
</div> </div>
); );

View File

@ -96,15 +96,10 @@ class PlanCard extends React.Component {
return ( return (
<ul className="list-unstyled"> <ul className="list-unstyled">
<li> <li>
<strong>Status:</strong> <strong>Status:</strong> {this.renderStackIcon(stack)}{' '}
{' '}
{this.renderStackIcon(stack)}
{' '}
{this.getDeploymentStatus(stack)} {this.getDeploymentStatus(stack)}
</li> </li>
<li> <li>{modified}</li>
{modified}
</li>
</ul> </ul>
); );
} }
@ -143,13 +138,10 @@ class PlanCard extends React.Component {
{plan.name} {plan.name}
<PlanActions planName={plan.name} stack={stack} /> <PlanActions planName={plan.name} stack={stack} />
</h2> </h2>
{plan.description && {plan.description && (
<div className="card-pf-body"> <div className="card-pf-body">{plan.description}</div>
{plan.description} )}
</div>} <div className="card-pf-footer">{this.renderStackInfo()}</div>
<div className="card-pf-footer">
{this.renderStackInfo()}
</div>
</OverlayLoader> </OverlayLoader>
</div> </div>
</div> </div>

View File

@ -100,9 +100,9 @@ class RoleDetail extends React.Component {
} }
/** /**
* Filter out non updated parameters, so only parameters which have been actually changed * Filter out non updated parameters, so only parameters which have been actually changed
* get sent to updateparameters * get sent to updateparameters
*/ */
_filterFormData(formData) { _filterFormData(formData) {
return fromJS(formData) return fromJS(formData)
.filterNot((value, key) => { .filterNot((value, key) => {
@ -112,9 +112,9 @@ class RoleDetail extends React.Component {
} }
/** /**
* Json parameter values are sent as string, this function parses them and checks if they're object * Json parameter values are sent as string, this function parses them and checks if they're object
* or array. Also, parameters with undefined value are set to null * or array. Also, parameters with undefined value are set to null
*/ */
_jsonParseFormData(formData) { _jsonParseFormData(formData) {
return mapValues(formData, value => { return mapValues(formData, value => {
try { try {
@ -154,17 +154,23 @@ class RoleDetail extends React.Component {
return ( return (
<ul className="nav nav-tabs"> <ul className="nav nav-tabs">
<NavTab <NavTab
to={`/plans/${currentPlanName}/roles/${urlParams.roleName}/parameters`} to={`/plans/${currentPlanName}/roles/${
urlParams.roleName
}/parameters`}
> >
<FormattedMessage {...messages.parameters} /> <FormattedMessage {...messages.parameters} />
</NavTab> </NavTab>
<NavTab <NavTab
to={`/plans/${currentPlanName}/roles/${urlParams.roleName}/services`} to={`/plans/${currentPlanName}/roles/${
urlParams.roleName
}/services`}
> >
<FormattedMessage {...messages.services} /> <FormattedMessage {...messages.services} />
</NavTab> </NavTab>
<NavTab <NavTab
to={`/plans/${currentPlanName}/roles/${urlParams.roleName}/network-configuration`} to={`/plans/${currentPlanName}/roles/${
urlParams.roleName
}/network-configuration`}
> >
<FormattedMessage {...messages.networkConfiguration} /> <FormattedMessage {...messages.networkConfiguration} />
</NavTab> </NavTab>
@ -226,24 +232,26 @@ class RoleDetail extends React.Component {
/> />
<Redirect <Redirect
from="/plans/:planName/roles/:roleName" from="/plans/:planName/roles/:roleName"
to={`/plans/${currentPlanName}/roles/${urlParams.roleName}/parameters`} to={`/plans/${currentPlanName}/roles/${
urlParams.roleName
}/parameters`}
/> />
</Switch> </Switch>
</Loader> </Loader>
{dataLoaded {dataLoaded ? (
? <ModalFooter> <ModalFooter>
<button <button
type="submit" type="submit"
disabled={!this.state.canSubmit} disabled={!this.state.canSubmit}
className="btn btn-primary" className="btn btn-primary"
> >
<FormattedMessage {...messages.saveChanges} /> <FormattedMessage {...messages.saveChanges} />
</button> </button>
<CloseModalButton> <CloseModalButton>
<FormattedMessage {...messages.cancel} /> <FormattedMessage {...messages.cancel} />
</CloseModalButton> </CloseModalButton>
</ModalFooter> </ModalFooter>
: null} ) : null}
</Formsy.Form> </Formsy.Form>
</RoutedModalPanel> </RoutedModalPanel>
); );

View File

@ -29,7 +29,8 @@ import Tab from '../ui/Tab';
const messages = defineMessages({ const messages = defineMessages({
noParameters: { noParameters: {
id: 'RoleServices.noParameters', id: 'RoleServices.noParameters',
defaultMessage: 'There are currently no parameters to configure in this section.' defaultMessage:
'There are currently no parameters to configure in this section.'
}, },
selectService: { selectService: {
id: 'RoleServices.selectService', id: 'RoleServices.selectService',

View File

@ -31,7 +31,11 @@ export default class ConfirmationModal extends React.Component {
renderTitle() { renderTitle() {
const iconClass = this.props.iconClass; const iconClass = this.props.iconClass;
if (iconClass) { if (iconClass) {
return <span><span className={iconClass} /> {this.props.title}</span>; return (
<span>
<span className={iconClass} /> {this.props.title}
</span>
);
} else { } else {
return <span>{this.props.title}</span>; return <span>{this.props.title}</span>;
} }
@ -51,9 +55,7 @@ export default class ConfirmationModal extends React.Component {
<Modal bsSize="sm" show={show} onHide={onCancel}> <Modal bsSize="sm" show={show} onHide={onCancel}>
<ModalHeader> <ModalHeader>
<CloseModalXButton /> <CloseModalXButton />
<ModalTitle> <ModalTitle>{this.renderTitle()}</ModalTitle>
{this.renderTitle()}
</ModalTitle>
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
<p>{question}</p> <p>{question}</p>

View File

@ -81,9 +81,7 @@ class ModalPanel extends React.Component {
className={cx('modal-panel', modalPanelClassName)} className={cx('modal-panel', modalPanelClassName)}
style={style} style={style}
> >
<div className="row flex-container"> <div className="row flex-container">{children}</div>
{children}
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -32,11 +32,7 @@ export default class TabPane extends React.Component {
active: this.props.isActive active: this.props.isActive
}); });
return ( return <div className={classes}>{this.renderChildren()}</div>;
<div className={classes}>
{this.renderChildren()}
</div>
);
} }
} }
TabPane.propTypes = { TabPane.propTypes = {

View File

@ -27,9 +27,7 @@ export const ActiveFiltersList = ({
return ( return (
<span className="toolbar-pf-active-filters"> <span className="toolbar-pf-active-filters">
<p>{label}&nbsp;</p> <p>{label}&nbsp;</p>
<ul className="list-inline"> <ul className="list-inline">{children}</ul>
{children}
</ul>
<p> <p>
<a className="link" onClick={() => handleClearAll()}> <a className="link" onClick={() => handleClearAll()}>
{clearAllLabel} {clearAllLabel}

View File

@ -94,9 +94,11 @@ export const SelectAllButton = selectAll(
onClick={() => toggleSelectAll()} onClick={() => toggleSelectAll()}
disabled={disabled} disabled={disabled}
> >
{shouldSelectAll {shouldSelectAll ? (
? <FormattedMessage {...messages.selectAll} /> <FormattedMessage {...messages.selectAll} />
: <FormattedMessage {...messages.deselectAll} />} ) : (
<FormattedMessage {...messages.deselectAll} />
)}
</Button> </Button>
) )
); );

View File

@ -30,7 +30,9 @@ class ToolbarFiltersForm extends React.Component {
renderFilterByOptions() { renderFilterByOptions() {
const { options } = this.props; const { options } = this.props;
return Object.keys(options).map(k => ( return Object.keys(options).map(k => (
<MenuItem key={k} eventKey={k}>{options[k]}</MenuItem> <MenuItem key={k} eventKey={k}>
{options[k]}
</MenuItem>
)); ));
} }
@ -52,20 +54,22 @@ class ToolbarFiltersForm extends React.Component {
return ( return (
<form id={id} onSubmit={handleSubmit(this.submit.bind(this))}> <form id={id} onSubmit={handleSubmit(this.submit.bind(this))}>
<FormGroup className="toolbar-pf-filter"> <FormGroup className="toolbar-pf-filter">
{options {options ? (
? <InputGroup> <InputGroup>
<InputGroup.Button> <InputGroup.Button>
<Field <Field
name="filterBy" name="filterBy"
component={DropdownSelect} component={DropdownSelect}
format={formatSelectValue} format={formatSelectValue}
> >
{this.renderFilterByOptions()} {this.renderFilterByOptions()}
</Field> </Field>
</InputGroup.Button> </InputGroup.Button>
{this.renderFilterStringField()} {this.renderFilterStringField()}
</InputGroup> </InputGroup>
: this.renderFilterStringField()} ) : (
this.renderFilterStringField()
)}
</FormGroup> </FormGroup>
</form> </form>
); );

View File

@ -21,18 +21,14 @@ export const Toolbar = ({ children, tableView }) => {
if (tableView) { if (tableView) {
return ( return (
<div className="toolbar-pf row table-view-pf-toolbar"> <div className="toolbar-pf row table-view-pf-toolbar">
<div className="col-sm-12"> <div className="col-sm-12">{children}</div>
{children}
</div>
</div> </div>
); );
} else { } else {
return ( return (
<div className="container-fluid"> <div className="container-fluid">
<div className="toolbar-pf row"> <div className="toolbar-pf row">
<div className="col-sm-12"> <div className="col-sm-12">{children}</div>
{children}
</div>
</div> </div>
</div> </div>
); );
@ -47,9 +43,7 @@ Toolbar.defaultProps = {
}; };
export const ToolbarActions = ({ children }) => ( export const ToolbarActions = ({ children }) => (
<div className="toolbar-pf-actions"> <div className="toolbar-pf-actions">{children}</div>
{children}
</div>
); );
ToolbarActions.propTypes = { ToolbarActions.propTypes = {
children: PropTypes.node children: PropTypes.node
@ -57,9 +51,7 @@ ToolbarActions.propTypes = {
export const ToolbarResults = ({ children }) => ( export const ToolbarResults = ({ children }) => (
<div className="toolbar-pf-results row"> <div className="toolbar-pf-results row">
<div className="col-sm-12"> <div className="col-sm-12">{children}</div>
{children}
</div>
</div> </div>
); );
ToolbarResults.propTypes = { ToolbarResults.propTypes = {

View File

@ -29,9 +29,7 @@ export const ActionCard = ({ children, className, onClick, ...rest }) => (
{...rest} {...rest}
> >
<div className="card-pf-body"> <div className="card-pf-body">
<p className="text-center"> <p className="text-center">{children}</p>
{children}
</p>
</div> </div>
</div> </div>
); );

View File

@ -76,12 +76,12 @@ export default class Dropdown extends React.Component {
{items} {items}
</ul> </ul>
</div> </div>
{this.state.isOpen {this.state.isOpen ? (
? <div <div
onClick={this.toggleDropdown.bind(this)} onClick={this.toggleDropdown.bind(this)}
className="modal-backdrop fade" className="modal-backdrop fade"
/> />
: null} ) : null}
</span> </span>
); );
} }

View File

@ -28,9 +28,7 @@ const DropdownKebab = ({ children, id, pullRight }) => {
> >
<span className="fa fa-ellipsis-v" /> <span className="fa fa-ellipsis-v" />
</Dropdown.Toggle> </Dropdown.Toggle>
<Dropdown.Menu> <Dropdown.Menu>{children}</Dropdown.Menu>
{children}
</Dropdown.Menu>
</Dropdown> </Dropdown>
); );
}; };

View File

@ -32,9 +32,7 @@ export default class FormErrorList extends React.Component {
return ( return (
<div> <div>
<strong>{`${errors.length} Errors Found:`}</strong> <strong>{`${errors.length} Errors Found:`}</strong>
<ul> <ul>{errorList}</ul>
{errorList}
</ul>
</div> </div>
); );
} else { } else {

View File

@ -19,9 +19,9 @@ import React from 'react';
export default class InputDescription extends React.Component { export default class InputDescription extends React.Component {
render() { render() {
return this.props.description return this.props.description ? (
? <small className="help-block">{this.props.description}</small> <small className="help-block">{this.props.description}</small>
: null; ) : null;
} }
} }
InputDescription.propTypes = { InputDescription.propTypes = {

View File

@ -20,9 +20,9 @@ import React from 'react';
export default class InputDescription extends React.Component { export default class InputDescription extends React.Component {
render() { render() {
const errorMessage = this.props.getErrorMessage(); const errorMessage = this.props.getErrorMessage();
return errorMessage return errorMessage ? (
? <span className="help-block">{errorMessage}</span> <span className="help-block">{errorMessage}</span>
: null; ) : null;
} }
} }
InputDescription.propTypes = { InputDescription.propTypes = {

View File

@ -38,7 +38,7 @@ InputDescription.propTypes = {
export const InputMessage = ({ fieldMeta: { touched, error, warning } }) => export const InputMessage = ({ fieldMeta: { touched, error, warning } }) =>
touched touched
? (error ? <HelpBlock>{error}</HelpBlock> : null) || ? (error ? <HelpBlock>{error}</HelpBlock> : null) ||
(warning ? <HelpBlock>{warning}</HelpBlock> : null) (warning ? <HelpBlock>{warning}</HelpBlock> : null)
: null; : null;
InputMessage.propTypes = { InputMessage.propTypes = {

View File

@ -120,9 +120,7 @@ class DataTable extends React.Component {
role="grid" role="grid"
> >
<thead> <thead>
<tr> <tr>{headers}</tr>
{headers}
</tr>
</thead> </thead>
<tbody> <tbody>
{rows.length > 0 ? rows : this.props.noRowsRenderer()} {rows.length > 0 ? rows : this.props.noRowsRenderer()}

View File

@ -22,15 +22,11 @@ import TableCheckBox from '../forms/TableCheckBox';
import { InlineLoader } from '../Loader'; import { InlineLoader } from '../Loader';
/** /**
* Default table header cell class * Default table header cell class
*/ */
export class DataTableHeaderCell extends React.Component { export class DataTableHeaderCell extends React.Component {
render() { render() {
return ( return <th {...this.props}>{this.props.children}</th>;
<th {...this.props}>
{this.props.children}
</th>
);
} }
} }
DataTableHeaderCell.propTypes = { DataTableHeaderCell.propTypes = {
@ -38,15 +34,11 @@ DataTableHeaderCell.propTypes = {
}; };
/** /**
* Default table cell class * Default table cell class
*/ */
export class DataTableCell extends React.Component { export class DataTableCell extends React.Component {
render() { render() {
return ( return <td>{this.props.children}</td>;
<td>
{this.props.children}
</td>
);
} }
} }
DataTableCell.propTypes = { DataTableCell.propTypes = {
@ -54,19 +46,15 @@ DataTableCell.propTypes = {
}; };
/** /**
* Table cell class able to render value from data set passed to columns * Table cell class able to render value from data set passed to columns
*/ */
export class DataTableDataFieldCell extends React.Component { export class DataTableDataFieldCell extends React.Component {
render() { render() {
let value = _.result( let value = _.result(
this.props.data[this.props.rowIndex], this.props.data[this.props.rowIndex],
this.props.field this.props.field
); );
return ( return <DataTableCell {...this.props}>{value}</DataTableCell>;
<DataTableCell {...this.props}>
{value}
</DataTableCell>
);
} }
} }
DataTableDataFieldCell.propTypes = { DataTableDataFieldCell.propTypes = {
@ -79,11 +67,7 @@ export const DataTableDateFieldCell = props => {
//TODO(jtomasek): Update this component to parse date and format it using React Intl's //TODO(jtomasek): Update this component to parse date and format it using React Intl's
// FormatedDate // FormatedDate
const value = _.result(props.data[props.rowIndex], props.field); const value = _.result(props.data[props.rowIndex], props.field);
return ( return <DataTableCell {...props}>{value}</DataTableCell>;
<DataTableCell {...props}>
{value}
</DataTableCell>
);
}; };
DataTableDateFieldCell.propTypes = { DataTableDateFieldCell.propTypes = {
data: PropTypes.array.isRequired, data: PropTypes.array.isRequired,

View File

@ -29,16 +29,20 @@ export const checkRunningDeployment = WrappedComponent => {
if (this.props.currentStackDeploymentInProgress) { if (this.props.currentStackDeploymentInProgress) {
this.props.notify({ this.props.notify({
title: 'Not allowed', title: 'Not allowed',
message: `A deployment for the plan ${this.props.currentPlanName} is already in progress.`, message: `A deployment for the plan ${
this.props.currentPlanName
} is already in progress.`,
type: 'warning' type: 'warning'
}); });
} }
} }
render() { render() {
return this.props.currentStackDeploymentInProgress return this.props.currentStackDeploymentInProgress ? (
? <Redirect to={`/plans/${this.props.currentPlanName}`} /> <Redirect to={`/plans/${this.props.currentPlanName}`} />
: <WrappedComponent {...this.props} />; ) : (
<WrappedComponent {...this.props} />
);
} }
} }
CheckRunningDeploymentHOC.propTypes = { CheckRunningDeploymentHOC.propTypes = {

View File

@ -59,11 +59,10 @@ export default class Validation extends React.Component {
this.props.addActiveFilter({ this.props.addActiveFilter({
filterBy: 'group', filterBy: 'group',
filterString: group filterString: group
})} })
}
> >
<small> <small>{group}</small>
{group}
</small>
</span> </span>
</div> </div>
); );

View File

@ -92,7 +92,11 @@ class ValidationDetail extends React.Component {
if (lastResult && !includes(['running', 'paused'], this.props.status)) { if (lastResult && !includes(['running', 'paused'], this.props.status)) {
return ( return (
<div> <div>
<p><strong><FormattedMessage {...messages.output} /></strong></p> <p>
<strong>
<FormattedMessage {...messages.output} />
</strong>
</p>
<pre> <pre>
{lastResult.output.get('stdout', lastResult.output.get('result'))} {lastResult.output.get('stdout', lastResult.output.get('result'))}
</pre> </pre>
@ -128,19 +132,22 @@ class ValidationDetail extends React.Component {
<h3>{this.props.name}</h3> <h3>{this.props.name}</h3>
</div> </div>
<p> <p>
<strong><FormattedMessage {...messages.description} /></strong> <strong>
{' '} <FormattedMessage {...messages.description} />
</strong>{' '}
<br /> <br />
{this.props.description} {this.props.description}
</p> </p>
<p> <p>
<strong><FormattedMessage {...messages.groups} /></strong> <strong>
{' '} <FormattedMessage {...messages.groups} />
</strong>{' '}
{this.renderValidationGroups()} {this.renderValidationGroups()}
</p> </p>
<p> <p>
<strong><FormattedMessage {...messages.status} /></strong> <strong>
{' '} <FormattedMessage {...messages.status} />
</strong>{' '}
{this.props.status} {this.props.status}
</p> </p>
{this.renderValidationOutput()} {this.renderValidationOutput()}

View File

@ -111,7 +111,9 @@ class ValidationsList extends React.Component {
iconClass="pficon pficon-flag" iconClass="pficon pficon-flag"
title={this.props.intl.formatMessage(messages.noValidations)} title={this.props.intl.formatMessage(messages.noValidations)}
> >
<p><FormattedMessage {...messages.noValidationsMessage} /></p> <p>
<FormattedMessage {...messages.noValidationsMessage} />
</p>
</BlankSlate> </BlankSlate>
); );
} else { } else {
@ -170,8 +172,10 @@ class ValidationsList extends React.Component {
<div className="actions pull-right"> <div className="actions pull-right">
<InlineLoader <InlineLoader
loaded={ loaded={
!(this.props.validationsLoaded && !(
this.props.isFetchingValidations) this.props.validationsLoaded &&
this.props.isFetchingValidations
)
} }
component="small" component="small"
content={formatMessage(messages.loadingValidations)} content={formatMessage(messages.loadingValidations)}
@ -180,8 +184,7 @@ class ValidationsList extends React.Component {
className="link refresh" className="link refresh"
onClick={this.refreshValidations.bind(this)} onClick={this.refreshValidations.bind(this)}
> >
<span className="pficon pficon-refresh" /> <span className="pficon pficon-refresh" />{' '}
{' '}
<FormattedMessage {...messages.refresh} /> <FormattedMessage {...messages.refresh} />
</a> </a>
</InlineLoader> </InlineLoader>

Some files were not shown because too many files have changed in this diff Show More