diff --git a/app/javascript/react/screens/App/Settings/Settings.scss b/app/javascript/react/screens/App/Settings/Settings.scss index 89c2afa851..cc0cebcdf0 100644 --- a/app/javascript/react/screens/App/Settings/Settings.scss +++ b/app/javascript/react/screens/App/Settings/Settings.scss @@ -14,3 +14,8 @@ min-width: 150px; text-align: right; } + +.postfix-label { + background-color: #fafafa; + border-left-width: 0px; +} diff --git a/app/javascript/react/screens/App/Settings/helpers.js b/app/javascript/react/screens/App/Settings/helpers.js index 7fe0c26f19..5eae83b17d 100644 --- a/app/javascript/react/screens/App/Settings/helpers.js +++ b/app/javascript/react/screens/App/Settings/helpers.js @@ -1,13 +1,23 @@ import { FINISHED, ERROR } from './screens/ConversionHostsSettings/ConversionHostsSettingsConstants'; export const getFormValuesFromApiSettings = payload => ({ - max_concurrent_tasks_per_host: payload.transformation.limits.max_concurrent_tasks_per_host + max_concurrent_tasks_per_host: payload.transformation.limits.max_concurrent_tasks_per_host, + max_concurrent_tasks_per_ems: payload.transformation.limits.max_concurrent_tasks_per_ems + /* FIXME: remove the comment once backend is ready + cpu_limit_per_host: payload.transformation.limits.cpu_limit_per_host, + network_limit_per_host: payload.transformation.limits.network_limit_per_host + */ }); export const getApiSettingsFromFormValues = values => ({ transformation: { limits: { - max_concurrent_tasks_per_host: values.max_concurrent_tasks_per_host + max_concurrent_tasks_per_host: values.max_concurrent_tasks_per_host, + max_concurrent_tasks_per_ems: values.max_concurrent_tasks_per_ems + /* FIXME: remove the comment once backend is ready + cpu_limit_per_host: values.cpu_limit_per_host, + network_limit_per_host: values.network_limit_per_host + */ } } }); diff --git a/app/javascript/react/screens/App/Settings/screens/GeneralSettings/GeneralSettings.js b/app/javascript/react/screens/App/Settings/screens/GeneralSettings/GeneralSettings.js index ffb7f70a6c..d057dea05e 100644 --- a/app/javascript/react/screens/App/Settings/screens/GeneralSettings/GeneralSettings.js +++ b/app/javascript/react/screens/App/Settings/screens/GeneralSettings/GeneralSettings.js @@ -1,8 +1,11 @@ import React from 'react'; import PropTypes from 'prop-types'; import { reduxForm, Field } from 'redux-form'; -import { Form, Button, Spinner } from 'patternfly-react'; +import { Form, Button, Icon, OverlayTrigger, Popover, Spinner } from 'patternfly-react'; import NumberInput from '../../../common/forms/NumberInput'; +// import TextInputWithCheckbox from '../../../common/forms/TextInputWithCheckbox'; // FIXME: uncomment once backend is ready + +const FORM_NAME = 'settings'; export class GeneralSettings extends React.Component { componentDidMount() { @@ -16,6 +19,20 @@ export class GeneralSettings extends React.Component { patchSettingsAction(servers, settingsForm.values); }; + enforceConstraintsOnChange = (event, newValue, prevValue, fieldChanging) => { + const { + settingsForm: { + values: { max_concurrent_tasks_per_host, max_concurrent_tasks_per_ems } + }, + formChangeAction + } = this.props; + if (fieldChanging === 'max_concurrent_tasks_per_host' && newValue > max_concurrent_tasks_per_ems) { + formChangeAction(FORM_NAME, 'max_concurrent_tasks_per_ems', newValue); + } else if (fieldChanging === 'max_concurrent_tasks_per_ems' && newValue < max_concurrent_tasks_per_host) { + formChangeAction(FORM_NAME, 'max_concurrent_tasks_per_host', newValue); + } + }; + render() { const { isFetchingServers, isFetchingSettings, isSavingSettings, savedSettings, settingsForm } = this.props; @@ -24,27 +41,99 @@ export class GeneralSettings extends React.Component { settingsForm.values && Object.keys(savedSettings).some(key => savedSettings[key] !== settingsForm.values[key]); + // const inputEnabledFunction = value => value !== 'unlimited'; // FIXME: uncomment once backend is ready + return (
-
+ +
+

{__('Concurrent Migrations')}

+
- {__('Maximum concurrent migrations per conversion host')} -
+ + + {__('Maximum concurrent migrations per conversion host')} + + {__( + 'For VDDK transformations the maximum concurrent migrations per conversion host is limited to 20. See the product documentation for more information.' + )} + + } + placement="top" + trigger={['hover']} + delay={500} + rootClose={false} + > + + + + +
+ +
{__('Maximum concurrent migrations per provider')}
+
+
+ +
+
+ {/* FIXME: uncomment once backend is ready + +
+

{__('Resource Utilization Limits for Migrations')}

+
+ + +*/} + -
{isSavingSettings && (
@@ -70,7 +159,8 @@ GeneralSettings.propTypes = { savedSettings: PropTypes.object, settingsForm: PropTypes.object, fetchServersUrl: PropTypes.string, - fetchSettingsUrl: PropTypes.string + fetchSettingsUrl: PropTypes.string, + formChangeAction: PropTypes.func }; GeneralSettings.defaultProps = { @@ -79,5 +169,5 @@ GeneralSettings.defaultProps = { }; export default reduxForm({ - form: 'settings' + form: FORM_NAME })(GeneralSettings); diff --git a/app/javascript/react/screens/App/Settings/screens/GeneralSettings/index.js b/app/javascript/react/screens/App/Settings/screens/GeneralSettings/index.js index b4534df1b2..b664059942 100644 --- a/app/javascript/react/screens/App/Settings/screens/GeneralSettings/index.js +++ b/app/javascript/react/screens/App/Settings/screens/GeneralSettings/index.js @@ -1,8 +1,8 @@ import { connect } from 'react-redux'; +import { change } from 'redux-form'; import GeneralSettings from './GeneralSettings'; -import * as SettingsActions from '../../SettingsActions'; -import * as RouterActions from '../../../../../../redux/actions/routerActions'; +import { fetchServersAction, fetchSettingsAction, patchSettingsAction } from '../../SettingsActions'; const mapStateToProps = ({ settings, form }, ownProps) => ({ ...settings, @@ -17,6 +17,6 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => Object.assign(stateP export default connect( mapStateToProps, - Object.assign(SettingsActions, RouterActions), + { fetchServersAction, fetchSettingsAction, patchSettingsAction, formChangeAction: change }, mergeProps )(GeneralSettings); diff --git a/app/javascript/react/screens/App/common/forms/TextInputWithCheckbox.js b/app/javascript/react/screens/App/common/forms/TextInputWithCheckbox.js new file mode 100644 index 0000000000..e5c492d0f3 --- /dev/null +++ b/app/javascript/react/screens/App/common/forms/TextInputWithCheckbox.js @@ -0,0 +1,71 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Form } from 'patternfly-react'; + +class TextInputWithCheckbox extends React.Component { + constructor(props) { + super(props); + this.state = { + inputEnabled: props.inputEnabledFunction(props.input.value) + }; + } + + render() { + const { + id, + label, + input: { value, onChange }, + postfix + } = this.props; + + const onCheckboxChange = event => this.setState({ inputEnabled: event.target.checked }); + + return ( + + +
+ +
+
+
+
+ onChange(event.target.value)} + /> +
{postfix}
+
+
+
+ ); + } +} + +TextInputWithCheckbox.propTypes = { + id: PropTypes.string.isRequired, + input: PropTypes.shape({ + value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + onChange: PropTypes.func + }), + label: PropTypes.string.isRequired, + postfix: PropTypes.string, + inputEnabledFunction: PropTypes.func.isRequired +}; + +TextInputWithCheckbox.normalizeStringToInt = str => (str && parseInt(str.replace(/\D/g, ''), 10)) || 0; + +export default TextInputWithCheckbox;