Merge "Add advanced configuration of container images"

This commit is contained in:
Zuul 2018-12-11 12:01:46 +00:00 committed by Gerrit Code Review
commit efd135b199
9 changed files with 384 additions and 51 deletions

View File

@ -0,0 +1,111 @@
/**
* Copyright 2018 Red Hat Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { injectIntl, defineMessages } from 'react-intl';
import { reduxForm, Field, SubmissionError } from 'redux-form';
import { Form, Wizard } from 'patternfly-react';
import yaml from 'js-yaml';
import { OverlayLoader } from '../ui/Loader';
import ModalFormErrorList from '../ui/forms/ModalFormErrorList';
import AceEditorInput from '../ui/reduxForm/AceEditorInput';
import { updateParameters } from '../../actions/ParametersActions';
const messages = defineMessages({
updatingConfiguration: {
id: 'ContainerImagePrepareParameterForm.updatingConfiguration',
defaultMessage: 'Updating configuration...'
},
yamlSyntaxError: {
id: 'ContainerImagePrepareParameterForm.yamlSyntaxError',
defaultMessage: 'Invalid Yaml Syntax:'
}
});
const ContainerImagePrepareParameterForm = ({
currentPlanName,
error,
handleSubmit,
intl: { formatMessage },
parameter,
submitting
}) => (
<Form onSubmit={handleSubmit} horizontal>
<OverlayLoader
loaded={!submitting}
content={formatMessage(messages.updatingConfiguration)}
>
<ModalFormErrorList errors={error ? [error] : []} />
<Wizard.Main style={{ padding: 0 }}>
<Wizard.Contents stepIndex={1} activeStepIndex={1}>
<Field
width="100%"
height="680px"
name={parameter.name}
component={AceEditorInput}
description={parameter.description}
/>
</Wizard.Contents>
</Wizard.Main>
</OverlayLoader>
</Form>
);
ContainerImagePrepareParameterForm.propTypes = {
currentPlanName: PropTypes.string.isRequired,
error: PropTypes.object,
handleSubmit: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
parameter: ImmutablePropTypes.record.isRequired,
submitting: PropTypes.bool.isRequired
};
const form = reduxForm({
form: 'parametersForm',
onSubmit: (
{ ContainerImagePrepare },
dispatch,
{ currentPlanName, intl: { formatMessage } }
) => {
try {
ContainerImagePrepare = yaml.safeLoad(ContainerImagePrepare, {
json: true
});
dispatch(updateParameters(currentPlanName, { ContainerImagePrepare }));
} catch (e) {
return Promise.reject(
new SubmissionError({
_error: {
title: formatMessage(messages.yamlSyntaxError),
message: e.message
}
})
);
}
},
validate: ({ ContainerImagePrepare }) => {
try {
yaml.safeLoad(ContainerImagePrepare, { json: true });
return {};
} catch (e) {
return { ContainerImagePrepare: e.message };
}
}
});
export default injectIntl(form(ContainerImagePrepareParameterForm));

View File

@ -0,0 +1,88 @@
/**
* Copyright 2018 Red Hat Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
import React, { Fragment } from 'react';
import { connect } from 'react-redux';
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
import PropTypes from 'prop-types';
import { Icon, Button } from 'patternfly-react';
import { submit, isSubmitting, isPristine, isInvalid } from 'redux-form';
import { CloseModalButton } from '../ui/Modals';
const messages = defineMessages({
back: {
id: 'ContainerImagePrepareParameterFormActions.back',
defaultMessage: 'Back'
},
save: {
id: 'ContainerImagePrepareParameterFormActions.save',
defaultMessage: 'Save Changes'
},
close: {
id: 'ContainerImagePrepareParameterFormActions.close',
defaultMessage: 'Close'
}
});
const ContainerImagePrepareParameterFormActions = ({
goBack,
isSubmitting,
isInvalid,
isPristine,
submitForm
}) => (
<Fragment>
<Button bsStyle="default" onClick={goBack} disabled={isSubmitting}>
<Icon type="fa" name="angle-left" />
<FormattedMessage {...messages.back} />
</Button>
<Button
bsStyle="primary"
onClick={submitForm}
disabled={isSubmitting || isPristine || isInvalid}
>
<FormattedMessage {...messages.save} />
</Button>
<CloseModalButton>
<FormattedMessage {...messages.close} />
</CloseModalButton>
</Fragment>
);
ContainerImagePrepareParameterFormActions.propTypes = {
goBack: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
isInvalid: PropTypes.bool.isRequired,
isPristine: PropTypes.bool.isRequired,
isSubmitting: PropTypes.bool.isRequired,
submitForm: PropTypes.func.isRequired
};
const mapStateToProps = state => ({
isSubmitting: isSubmitting('parametersForm')(state),
isPristine: isPristine('parametersForm')(state),
isInvalid: isInvalid('parametersForm')(state)
});
const mapDispatchToProps = dispatch => ({
submitForm: () => dispatch(submit('parametersForm'))
});
export default injectIntl(
connect(mapStateToProps, mapDispatchToProps)(
ContainerImagePrepareParameterFormActions
)
);

View File

@ -21,11 +21,20 @@ import PropTypes from 'prop-types';
import { Icon, Button } from 'patternfly-react';
import { submit, isSubmitting, isPristine, isInvalid } from 'redux-form';
import { CloseModalButton } from '../ui/Modals';
import { startContainerImagesPrepare } from '../../actions/ContainerImagesActions';
const messages = defineMessages({
cancel: {
id: 'ContainerImagesWizard.cancel',
defaultMessage: 'Cancel'
},
save: {
id: 'ContainerImagesPrepareFormActions.save',
defaultMessage: 'Save Changes'
},
next: {
id: 'ContainerImagesWizard.next',
id: 'ContainerImagesPrepareFormActions.next',
defaultMessage: 'Next'
},
reset: {
@ -35,38 +44,44 @@ const messages = defineMessages({
});
const ContainerImagesPrepareFormActions = ({
goForward,
isSubmitting,
isInvalid,
isPristine,
resetToDefaults,
submitImagesPrepareForm
submitForm
}) => (
<Fragment>
<CloseModalButton>
<FormattedMessage {...messages.cancel} />
</CloseModalButton>
<Button onClick={resetToDefaults} disabled={isSubmitting}>
<FormattedMessage {...messages.reset} />
</Button>
<Button
bsStyle="primary"
onClick={submitImagesPrepareForm}
onClick={submitForm}
disabled={isSubmitting || isPristine || isInvalid}
>
<FormattedMessage {...messages.save} />
</Button>
<Button bsStyle="default" onClick={goForward} disabled={isSubmitting}>
<FormattedMessage {...messages.next} />
<Icon type="fa" name="angle-right" />
</Button>
</Fragment>
);
ContainerImagesPrepareFormActions.propTypes = {
goForward: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
isFetchingParameters: PropTypes.bool.isRequired,
isInvalid: PropTypes.bool.isRequired,
isPristine: PropTypes.bool.isRequired,
isSubmitting: PropTypes.bool.isRequired,
resetToDefaults: PropTypes.func.isRequired,
submitImagesPrepareForm: PropTypes.func.isRequired
submitForm: PropTypes.func.isRequired
};
const mapStateToProps = state => ({
isFetchingParameters: state.parameters.isFetching,
isSubmitting: isSubmitting('containerImagesPrepareForm')(state),
isPristine: isPristine('containerImagesPrepareForm')(state),
isInvalid: isInvalid('containerImagesPrepareForm')(state)
@ -74,7 +89,7 @@ const mapStateToProps = state => ({
const mapDispatchToProps = dispatch => ({
resetToDefaults: () => dispatch(startContainerImagesPrepare({})),
submitImagesPrepareForm: () => dispatch(submit('containerImagesPrepareForm'))
submitForm: () => dispatch(submit('containerImagesPrepareForm'))
});
export default injectIntl(

View File

@ -14,43 +14,30 @@
* under the License.
*/
import React, { Component, Fragment } from 'react';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
import PropTypes from 'prop-types';
import { Wizard, Modal, Icon, Button } from 'patternfly-react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { Wizard, Modal } from 'patternfly-react';
import yaml from 'js-yaml';
import { checkRunningDeployment } from '../utils/checkRunningDeploymentHOC';
import { getCurrentPlanName } from '../../selectors/plans';
import { Loader } from '../ui/Loader';
import { fetchParameters } from '../../actions/ParametersActions';
import { startContainerImagesPrepare } from '../../actions/ContainerImagesActions';
import {
RoutedWizard,
CloseModalXButton,
CloseModalButton
} from '../ui/Modals';
import { RoutedWizard, CloseModalXButton } from '../ui/Modals';
import ContainerImagesPrepareForm from './ContainerImagesPrepareForm';
import { getContainerImagePrepareParameterSeed } from '../../selectors/parameters';
import {
getContainerImagePrepareParameterSeed,
getContainerImagePrepareParameter
} from '../../selectors/parameters';
import ContainerImagesPrepareFormActions from './ContainerImagesPrepareFormActions';
import ContainerImagePrepareParameterForm from './ContainerImagePrepareParameterForm';
import ContainerImagePrepareParameterFormActions from './ContainerImagePrepareParameterFormActions';
const messages = defineMessages({
close: {
id: 'ContainerImagesWizard.close',
defaultMessage: 'Close'
},
cancel: {
id: 'ContainerImagesWizard.cancel',
defaultMessage: 'Cancel'
},
back: {
id: 'ContainerImagesWizard.back',
defaultMessage: 'Back'
},
save: {
id: 'ContainerImagesWizard.save',
defaultMessage: 'Save Changes'
},
title: {
id: 'ContainerImagesWizard.title',
defaultMessage: 'Prepare Container Images'
@ -93,6 +80,7 @@ class ContainerImagesWizard extends Component {
intl: { formatMessage },
isFetchingParameters,
containerImagePrepareParameterSeed,
containerImagePrepareParameter,
resetToDefaults
} = this.props;
@ -152,31 +140,28 @@ class ContainerImagesWizard extends Component {
resetToDefaults={resetToDefaults}
/>
) : (
<p>parameter form here</p>
<ContainerImagePrepareParameterForm
parameter={containerImagePrepareParameter}
currentPlanName={currentPlanName}
initialValues={{
[containerImagePrepareParameter.name]: yaml.safeDump(
containerImagePrepareParameter.default
)
}}
/>
)}
</Loader>
</Wizard.Body>
<Wizard.Footer>
<CloseModalButton>
<FormattedMessage {...messages.cancel} />
</CloseModalButton>
{activeStepIndex === 0 && <ContainerImagesPrepareFormActions />}
{activeStepIndex === 0 && (
<ContainerImagesPrepareFormActions
goForward={() => this.setActiveStepIndex(1)}
/>
)}
{activeStepIndex === 1 && (
<Fragment>
<Button
bsStyle="default"
onClick={() => this.setActiveStepIndex(0)}
>
<Icon type="fa" name="angle-left" />
<FormattedMessage {...messages.back} />
</Button>
<Button bsStyle="primary" onClick={this.onNextButtonClick}>
<FormattedMessage {...messages.save} />
</Button>
<CloseModalButton>
<FormattedMessage {...messages.close} />
</CloseModalButton>
</Fragment>
<ContainerImagePrepareParameterFormActions
goBack={() => this.setActiveStepIndex(0)}
/>
)}
</Wizard.Footer>
</RoutedWizard>
@ -184,6 +169,7 @@ class ContainerImagesWizard extends Component {
}
}
ContainerImagesWizard.propTypes = {
containerImagePrepareParameter: ImmutablePropTypes.record.isRequired,
containerImagePrepareParameterSeed: PropTypes.object.isRequired,
currentPlanName: PropTypes.string.isRequired,
fetchParameters: PropTypes.func.isRequired,
@ -196,6 +182,7 @@ const mapStateToProps = state => ({
containerImagePrepareParameterSeed: getContainerImagePrepareParameterSeed(
state
),
containerImagePrepareParameter: getContainerImagePrepareParameter(state),
currentPlanName: getCurrentPlanName(state),
isFetchingParameters: state.parameters.isFetching
});

View File

@ -0,0 +1,51 @@
/**
* Copyright 2018 Red Hat Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import AceEditor from 'react-ace';
import 'brace/theme/textmate';
import 'brace/mode/yaml';
import AceEditorInputToolbar from './AceEditorInputToolbar';
const aceOnBlur = onBlur => (_event, editor) => {
const value = editor && editor.getValue();
onBlur(value);
};
const AceEditorInput = ({ input, meta, description, ...rest }) => (
<Fragment>
<AceEditor {...input} onBlur={aceOnBlur(input.onBlur)} {...rest} />
<AceEditorInputToolbar {...meta} description={description} />
</Fragment>
);
AceEditorInput.propTypes = {
description: PropTypes.string.isRequired,
input: PropTypes.object.isRequired,
meta: PropTypes.object.isRequired,
mode: PropTypes.string.isRequired,
theme: PropTypes.string.isRequired
};
AceEditorInput.defaultProps = {
theme: 'textmate',
mode: 'yaml',
tabSize: 2,
enableBasicAutocompletion: false,
editorProps: { $blockScrolling: true }
};
export default AceEditorInput;

View File

@ -0,0 +1,56 @@
/**
* Copyright 2018 Red Hat Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { Icon } from 'patternfly-react';
const AceEditorInputToolbar = ({
valid,
invalid,
warning,
error,
description
}) => (
<div className="ace-editor-toolbar">
{invalid && (
<Fragment>
<Icon type="pf" name="error-circle-o" /> {error}
</Fragment>
)}
{warning && (
<Fragment>
<Icon type="pf" name="warning-triangle-o" /> {warning}
</Fragment>
)}
{valid &&
!warning &&
description && (
<Fragment>
<Icon type="pf" name="info" /> {description}
</Fragment>
)}
</div>
);
AceEditorInputToolbar.propTypes = {
description: PropTypes.string,
error: PropTypes.string,
invalid: PropTypes.bool.isRequired,
valid: PropTypes.bool.isRequired,
warning: PropTypes.string
};
export default AceEditorInputToolbar;

View File

@ -163,6 +163,7 @@
@import "ui/Sidebar";
@import "ui/Toolbar";
@import "ui/Tooltips";
@import "ui/AceEditorToolbar";
@import "components/Breadcrumbs";
@import "components/MainContent";
@import "components/EnvironmentConfiguration";

View File

@ -25,6 +25,7 @@
display: flex;
flex-direction: column;
justify-content: center;
z-index: 100;
&.card-loader {
box-sizing: content-box;

View File

@ -0,0 +1,23 @@
/**
* Copyright 2018 Red Hat Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
.ace-editor-toolbar {
background: #f0f0f0;
color: #333;
border-top: 1px solid #e8e8e8;
padding: 3px 5px;
min-height: 24px;
}