Merge "Add container images configuration wizard"
This commit is contained in:
commit
6e16cd4034
|
@ -0,0 +1,92 @@
|
|||
/**
|
||||
* 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 { startSubmit, stopSubmit, reset } from 'redux-form';
|
||||
import { defineMessages } from 'react-intl';
|
||||
|
||||
import { CONTAINER_IMAGES_PREPARE_SUCCESS } from '../constants/ContainerImagesConstants';
|
||||
import MistralConstants from '../constants/MistralConstants';
|
||||
import { getCurrentPlanName } from '../selectors/plans';
|
||||
import { startWorkflow } from './WorkflowActions';
|
||||
import { handleErrors } from './ErrorActions';
|
||||
|
||||
const messages = defineMessages({
|
||||
configureImagesFailed: {
|
||||
id: 'ContainerImagesActions.configureImagesFailed',
|
||||
defaultMessage: 'Generating container images configuration failed'
|
||||
}
|
||||
});
|
||||
|
||||
export const startContainerImagesPrepare = values => (
|
||||
dispatch,
|
||||
getState,
|
||||
{ getIntl }
|
||||
) => {
|
||||
const currentPlanName = getCurrentPlanName(getState());
|
||||
const { formatMessage } = getIntl(getState());
|
||||
dispatch(startSubmit('containerImagesPrepareForm'));
|
||||
return dispatch(
|
||||
startWorkflow(
|
||||
MistralConstants.CONTAINER_IMAGES_PREPARE_DEFAULT,
|
||||
{
|
||||
container: currentPlanName,
|
||||
container_image_values: { ...values }
|
||||
},
|
||||
containerImagesPrepareFinished,
|
||||
60 * 1000
|
||||
)
|
||||
).catch(error => {
|
||||
dispatch(
|
||||
stopSubmit('containerImagesPrepareForm', {
|
||||
_error: {
|
||||
title: formatMessage(messages.configureImagesFailed),
|
||||
message: error.message
|
||||
}
|
||||
})
|
||||
);
|
||||
dispatch(
|
||||
handleErrors(error, formatMessage(messages.configureImagesFailed), false)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export const containerImagesPrepareFinished = execution => (
|
||||
dispatch,
|
||||
getState,
|
||||
{ getIntl }
|
||||
) => {
|
||||
const { formatMessage } = getIntl(getState());
|
||||
const { output: { message, status, params } } = execution;
|
||||
if (status === 'SUCCESS') {
|
||||
dispatch(containerImagesPrepareSuccess(params.ContainerImagePrepare));
|
||||
dispatch(reset('containerImagesPrepareForm'));
|
||||
dispatch(stopSubmit('containerImagesPrepareForm'));
|
||||
} else {
|
||||
dispatch(
|
||||
stopSubmit('containerImagesPrepareForm', {
|
||||
_error: {
|
||||
title: formatMessage(messages.configureImagesFailed),
|
||||
message: sanitizeMessage(message)
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const containerImagesPrepareSuccess = value => ({
|
||||
type: CONTAINER_IMAGES_PREPARE_SUCCESS,
|
||||
payload: value
|
||||
});
|
|
@ -55,6 +55,7 @@ import {
|
|||
getDeploymentFailuresFinished
|
||||
} from './DeploymentActions';
|
||||
import { fetchNetworksFinished } from './NetworksActions';
|
||||
import { containerImagesPrepareFinished } from './ContainerImagesActions';
|
||||
|
||||
export const handleAuthenticationSuccess = (message, dispatch) => {
|
||||
message = get(message, ['body', 'message']);
|
||||
|
@ -244,6 +245,16 @@ export const messageReceived = message => (dispatch, getState) => {
|
|||
break;
|
||||
}
|
||||
|
||||
case MistralConstants.CONTAINER_IMAGES_PREPARE_DEFAULT: {
|
||||
dispatch(
|
||||
handleWorkflowMessage(
|
||||
payload.execution.id,
|
||||
containerImagesPrepareFinished
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,313 @@
|
|||
/**
|
||||
* 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, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
||||
import { Form, FieldLevelHelp, Wizard } from 'patternfly-react';
|
||||
import { reduxForm, Field } from 'redux-form';
|
||||
import { format } from 'redux-form-validators';
|
||||
|
||||
import { OverlayLoader } from '../ui/Loader';
|
||||
import FormErrorList from '../ui/forms/FormErrorList';
|
||||
import HorizontalInput from '../ui/reduxForm/HorizontalInput';
|
||||
import { startContainerImagesPrepare } from '../../actions/ContainerImagesActions';
|
||||
import PushDestinationInput from './PushDestinationInput';
|
||||
import {
|
||||
IPV4_WITH_PORT_REGEX,
|
||||
DOCKER_TAG_REGEX,
|
||||
DOCKER_REGISTRY_NAMESPACE_REGEX
|
||||
} from '../../utils/regex';
|
||||
|
||||
const messages = defineMessages({
|
||||
generatingConfiguration: {
|
||||
id: 'ContainerImagesPrepareForm.generatingConfiguration',
|
||||
defaultMessage: 'Generating container images configuration...'
|
||||
},
|
||||
namespaceLabel: {
|
||||
id: 'ContainerImagesPrepareForm.namespaceLabel',
|
||||
defaultMessage: 'Registry Namespace'
|
||||
},
|
||||
namespaceInvalid: {
|
||||
id: 'ContainerImagesPrepareForm.namespaceInvalid',
|
||||
defaultMessage: 'Provide a valid registry address and images namespace'
|
||||
},
|
||||
namePrefixLabel: {
|
||||
id: 'ContainerImagesPrepareForm.namePrefixLabel',
|
||||
defaultMessage: 'Name Prefix'
|
||||
},
|
||||
nameSuffixLabel: {
|
||||
id: 'ContainerImagesPrepareForm.nameSuffixLabel',
|
||||
defaultMessage: 'Name Suffix'
|
||||
},
|
||||
tagLabel: {
|
||||
id: 'ContainerImagesPrepareForm.tagLabel',
|
||||
defaultMessage: 'Tag'
|
||||
},
|
||||
tagFromLabelLabel: {
|
||||
id: 'ContainerImagesPrepareForm.tagFromLabelLabel',
|
||||
defaultMessage: 'Tag from Label'
|
||||
},
|
||||
pushDestinationLabel: {
|
||||
id: 'ContainerImagesPrepareForm.pushDestinationLabel',
|
||||
defaultMessage: 'Push Destination'
|
||||
},
|
||||
pushDestinationUndercloud: {
|
||||
id: 'ContainerImagesPrepareForm.pushDestinationUndercloud',
|
||||
defaultMessage: 'Undercloud image registry'
|
||||
},
|
||||
namespaceDescription: {
|
||||
id: 'ContainerImagesPrepareForm.namespaceDescription',
|
||||
defaultMessage:
|
||||
'Namespace of the remote registry from which the container images will be pulled during deployment.'
|
||||
},
|
||||
namePrefixDescription: {
|
||||
id: 'ContainerImagesPrepareForm.namePrefixDescription',
|
||||
defaultMessage: 'Container image name prefix'
|
||||
},
|
||||
nameSuffixDescription: {
|
||||
id: 'ContainerImagesPrepareForm.nameSuffixDescription',
|
||||
defaultMessage: 'Container image name suffix'
|
||||
},
|
||||
tagDescription: {
|
||||
id: 'ContainerImagesPrepareForm.tagDescription',
|
||||
defaultMessage: 'Tag representing the latest image version.'
|
||||
},
|
||||
pushDestinationDescription: {
|
||||
id: 'ContainerImagesPrepareForm.pushDestinationDescription',
|
||||
defaultMessage:
|
||||
'By specifying a Push Destination, the required images will be copied \
|
||||
from provided namespace to this registry. As part of the undercloud \
|
||||
install, an image registry is configured on port 8787. This can be used \
|
||||
to increase reliability of image pulls, and minimise overall network \
|
||||
transfers. Alternatively it is possible to explicitly specify the \
|
||||
registry to push the images to.'
|
||||
},
|
||||
pushDestinationValidationMessage: {
|
||||
id: 'ContainerImagesPrepareForm.pushDestinationValidationMessage',
|
||||
defaultMessage: 'Please enter a valid IPv4 address and port'
|
||||
},
|
||||
tagFromLabelDescription: {
|
||||
id: 'ContainerImagesPrepareForm.tagFromLabelDescription',
|
||||
defaultMessage:
|
||||
'Provide a label to discover the versioned tag for images. Some build \
|
||||
pipelines have a versioned tag which can only be discovered via a \
|
||||
combination of labels. For this case, a template format can be specified \
|
||||
instead, e.g. {labelExample}. If you want these parameters to have \
|
||||
the actual tag instead of the discovered tag, this entry can be omitted.'
|
||||
}
|
||||
});
|
||||
|
||||
class ContainerImagesPrepareForm extends Component {
|
||||
componentDidMount() {
|
||||
const { initialValues: { namespace }, resetToDefaults } = this.props;
|
||||
if (!namespace) {
|
||||
resetToDefaults();
|
||||
}
|
||||
}
|
||||
|
||||
validatePushDestination(value) {
|
||||
if (typeof value === 'boolean') {
|
||||
return undefined;
|
||||
} else if (new RegExp(IPV4_WITH_PORT_REGEX).test(value)) {
|
||||
return undefined;
|
||||
} else {
|
||||
return (
|
||||
<FormattedMessage {...messages.pushDestinationValidationMessage} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
error,
|
||||
intl: { formatMessage },
|
||||
submitting,
|
||||
handleSubmit
|
||||
} = this.props;
|
||||
return (
|
||||
<Form onSubmit={handleSubmit} horizontal>
|
||||
<OverlayLoader
|
||||
loaded={!submitting}
|
||||
content={formatMessage(messages.generatingConfiguration)}
|
||||
>
|
||||
<Wizard.Row>
|
||||
<Wizard.Main>
|
||||
<Wizard.Contents stepIndex={0} activeStepIndex={0}>
|
||||
<FormErrorList errors={error ? [error] : []} />
|
||||
<fieldset>
|
||||
<Field
|
||||
name="namespace"
|
||||
component={HorizontalInput}
|
||||
id="namespace"
|
||||
label={
|
||||
<Fragment>
|
||||
<FormattedMessage {...messages.namespaceLabel} />
|
||||
<FieldLevelHelp
|
||||
placement="right"
|
||||
style={{ maxWidth: 400 }}
|
||||
content={formatMessage(messages.namespaceDescription)}
|
||||
/>
|
||||
</Fragment>
|
||||
}
|
||||
labelColumns={4}
|
||||
validate={format({
|
||||
with: DOCKER_REGISTRY_NAMESPACE_REGEX,
|
||||
message: formatMessage(messages.namespaceInvalid),
|
||||
allowBlank: true
|
||||
})}
|
||||
/>
|
||||
<Field
|
||||
name="name_prefix"
|
||||
component={HorizontalInput}
|
||||
id="name_prefix"
|
||||
label={
|
||||
<Fragment>
|
||||
<FormattedMessage {...messages.namePrefixLabel} />
|
||||
<FieldLevelHelp
|
||||
placement="right"
|
||||
style={{ maxWidth: 400 }}
|
||||
content={formatMessage(
|
||||
messages.namePrefixDescription
|
||||
)}
|
||||
/>
|
||||
</Fragment>
|
||||
}
|
||||
labelColumns={4}
|
||||
inputColumns={4}
|
||||
/>
|
||||
<Field
|
||||
name="name_suffix"
|
||||
component={HorizontalInput}
|
||||
id="name_suffix"
|
||||
label={
|
||||
<Fragment>
|
||||
<FormattedMessage {...messages.nameSuffixLabel} />
|
||||
<FieldLevelHelp
|
||||
placement="right"
|
||||
style={{ maxWidth: 400 }}
|
||||
content={formatMessage(
|
||||
messages.nameSuffixDescription
|
||||
)}
|
||||
/>
|
||||
</Fragment>
|
||||
}
|
||||
labelColumns={4}
|
||||
inputColumns={4}
|
||||
/>
|
||||
<Field
|
||||
name="tag"
|
||||
component={HorizontalInput}
|
||||
id="tag"
|
||||
label={
|
||||
<Fragment>
|
||||
<FormattedMessage {...messages.tagLabel} />
|
||||
<FieldLevelHelp
|
||||
placement="right"
|
||||
style={{ maxWidth: 400 }}
|
||||
content={formatMessage(messages.tagDescription)}
|
||||
/>
|
||||
</Fragment>
|
||||
}
|
||||
validate={format({
|
||||
with: DOCKER_TAG_REGEX,
|
||||
allowBlank: true
|
||||
})}
|
||||
labelColumns={4}
|
||||
inputColumns={4}
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<Field
|
||||
name="push_destination"
|
||||
component={PushDestinationInput}
|
||||
id="push_destination"
|
||||
label={
|
||||
<Fragment>
|
||||
<FormattedMessage {...messages.pushDestinationLabel} />
|
||||
<FieldLevelHelp
|
||||
placement="right"
|
||||
style={{ maxWidth: 400 }}
|
||||
content={formatMessage(
|
||||
messages.pushDestinationDescription
|
||||
)}
|
||||
/>
|
||||
</Fragment>
|
||||
}
|
||||
labelColumns={4}
|
||||
validate={this.validatePushDestination}
|
||||
/>
|
||||
<Field
|
||||
name="tag_from_label"
|
||||
component={HorizontalInput}
|
||||
id="tag_from_label"
|
||||
label={
|
||||
<Fragment>
|
||||
<FormattedMessage {...messages.tagFromLabelLabel} />
|
||||
<FieldLevelHelp
|
||||
placement="right"
|
||||
style={{ maxWidth: 400 }}
|
||||
content={formatMessage(
|
||||
messages.tagFromLabelDescription,
|
||||
{
|
||||
labelExample: '{version}-{release}'
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</Fragment>
|
||||
}
|
||||
labelColumns={4}
|
||||
inputColumns={4}
|
||||
/>
|
||||
</fieldset>
|
||||
</Wizard.Contents>
|
||||
</Wizard.Main>
|
||||
</Wizard.Row>
|
||||
</OverlayLoader>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
}
|
||||
ContainerImagesPrepareForm.propTypes = {
|
||||
error: PropTypes.object,
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
initialValues: PropTypes.object.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
resetToDefaults: PropTypes.func.isRequired,
|
||||
submitting: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
const normalizeValues = values => {
|
||||
const generateIfEmpty = ['namespace', 'tag', 'push_destination'];
|
||||
Object.keys(values).map(key => {
|
||||
if (generateIfEmpty.includes(key)) {
|
||||
!values[key] && delete values[key];
|
||||
}
|
||||
});
|
||||
return values;
|
||||
};
|
||||
|
||||
const form = reduxForm({
|
||||
form: 'containerImagesPrepareForm',
|
||||
onSubmit: (values, dispatch, props) => {
|
||||
normalizeValues(values);
|
||||
dispatch(startContainerImagesPrepare(values));
|
||||
},
|
||||
touchOnChange: true,
|
||||
enableReinitialize: true
|
||||
});
|
||||
|
||||
export default injectIntl(form(ContainerImagesPrepareForm));
|
|
@ -0,0 +1,84 @@
|
|||
/**
|
||||
* 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 { startContainerImagesPrepare } from '../../actions/ContainerImagesActions';
|
||||
|
||||
const messages = defineMessages({
|
||||
next: {
|
||||
id: 'ContainerImagesWizard.next',
|
||||
defaultMessage: 'Next'
|
||||
},
|
||||
reset: {
|
||||
id: 'ContainerImagesPrepareFormActions.reset',
|
||||
defaultMessage: 'Reset to Defaults'
|
||||
}
|
||||
});
|
||||
|
||||
const ContainerImagesPrepareFormActions = ({
|
||||
isSubmitting,
|
||||
isInvalid,
|
||||
isPristine,
|
||||
resetToDefaults,
|
||||
submitImagesPrepareForm
|
||||
}) => (
|
||||
<Fragment>
|
||||
<Button onClick={resetToDefaults} disabled={isSubmitting}>
|
||||
<FormattedMessage {...messages.reset} />
|
||||
</Button>
|
||||
<Button
|
||||
bsStyle="primary"
|
||||
onClick={submitImagesPrepareForm}
|
||||
disabled={isSubmitting || isPristine || isInvalid}
|
||||
>
|
||||
<FormattedMessage {...messages.next} />
|
||||
<Icon type="fa" name="angle-right" />
|
||||
</Button>
|
||||
</Fragment>
|
||||
);
|
||||
ContainerImagesPrepareFormActions.propTypes = {
|
||||
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
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
isFetchingParameters: state.parameters.isFetching,
|
||||
isSubmitting: isSubmitting('containerImagesPrepareForm')(state),
|
||||
isPristine: isPristine('containerImagesPrepareForm')(state),
|
||||
isInvalid: isInvalid('containerImagesPrepareForm')(state)
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
resetToDefaults: () => dispatch(startContainerImagesPrepare({})),
|
||||
submitImagesPrepareForm: () => dispatch(submit('containerImagesPrepareForm'))
|
||||
});
|
||||
|
||||
export default injectIntl(
|
||||
connect(mapStateToProps, mapDispatchToProps)(
|
||||
ContainerImagesPrepareFormActions
|
||||
)
|
||||
);
|
|
@ -0,0 +1,213 @@
|
|||
/**
|
||||
* 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, { Component, Fragment } 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 { 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 ContainerImagesPrepareForm from './ContainerImagesPrepareForm';
|
||||
import { getContainerImagePrepareParameterSeed } from '../../selectors/parameters';
|
||||
import ContainerImagesPrepareFormActions from './ContainerImagesPrepareFormActions';
|
||||
|
||||
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'
|
||||
},
|
||||
loadingData: {
|
||||
id: 'ContainerImagesWizard.loadingData',
|
||||
defaultMessage: 'Loading configuration...'
|
||||
},
|
||||
configureImages: {
|
||||
id: 'ContainerImagesWizard.configureImages',
|
||||
defaultMessage: 'Configure Images'
|
||||
},
|
||||
review: {
|
||||
id: 'ContainerImagesWizard.review',
|
||||
defaultMessage: 'Review Configuration'
|
||||
}
|
||||
});
|
||||
|
||||
class ContainerImagesWizard extends Component {
|
||||
state = {
|
||||
activeStepIndex: 0
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
const {
|
||||
currentPlanName,
|
||||
fetchParameters,
|
||||
isFetchingParameters
|
||||
} = this.props;
|
||||
!isFetchingParameters && fetchParameters(currentPlanName);
|
||||
}
|
||||
|
||||
setActiveStepIndex(index) {
|
||||
this.setState({ activeStepIndex: index });
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
currentPlanName,
|
||||
intl: { formatMessage },
|
||||
isFetchingParameters,
|
||||
containerImagePrepareParameterSeed,
|
||||
resetToDefaults
|
||||
} = this.props;
|
||||
|
||||
const { activeStepIndex } = this.state;
|
||||
|
||||
const steps = [
|
||||
{
|
||||
step: 1,
|
||||
label: '1',
|
||||
title: formatMessage(messages.configureImages)
|
||||
},
|
||||
{
|
||||
step: 2,
|
||||
label: '2',
|
||||
title: formatMessage(messages.review)
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<RoutedWizard
|
||||
id="ContainerImagesWizard__Wizard"
|
||||
redirectPath={`/plans/${currentPlanName}`}
|
||||
>
|
||||
<Modal.Header>
|
||||
<CloseModalXButton />
|
||||
<Modal.Title>
|
||||
<FormattedMessage {...messages.title} />
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Wizard.Body>
|
||||
<Wizard.Steps
|
||||
steps={steps.map(({ step, label, title }, index) => (
|
||||
<Wizard.Step
|
||||
key={step}
|
||||
stepIndex={index}
|
||||
step={step}
|
||||
label={label}
|
||||
title={title}
|
||||
activeStep={steps[activeStepIndex].step}
|
||||
onClick={() => this.setActiveStepIndex(index)}
|
||||
/>
|
||||
))}
|
||||
/>
|
||||
<Loader
|
||||
height={60}
|
||||
loaded={!isFetchingParameters}
|
||||
content={formatMessage(messages.loadingData)}
|
||||
>
|
||||
{activeStepIndex === 0 ? (
|
||||
<ContainerImagesPrepareForm
|
||||
onSubmit={this.handleContainerImagesPrepareFormSubmit}
|
||||
initialValues={{
|
||||
...containerImagePrepareParameterSeed,
|
||||
push_destination:
|
||||
containerImagePrepareParameterSeed.push_destination || false
|
||||
}}
|
||||
resetToDefaults={resetToDefaults}
|
||||
/>
|
||||
) : (
|
||||
<p>parameter form here</p>
|
||||
)}
|
||||
</Loader>
|
||||
</Wizard.Body>
|
||||
<Wizard.Footer>
|
||||
<CloseModalButton>
|
||||
<FormattedMessage {...messages.cancel} />
|
||||
</CloseModalButton>
|
||||
{activeStepIndex === 0 && <ContainerImagesPrepareFormActions />}
|
||||
{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>
|
||||
)}
|
||||
</Wizard.Footer>
|
||||
</RoutedWizard>
|
||||
);
|
||||
}
|
||||
}
|
||||
ContainerImagesWizard.propTypes = {
|
||||
containerImagePrepareParameterSeed: PropTypes.object.isRequired,
|
||||
currentPlanName: PropTypes.string.isRequired,
|
||||
fetchParameters: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
isFetchingParameters: PropTypes.bool.isRequired,
|
||||
resetToDefaults: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
containerImagePrepareParameterSeed: getContainerImagePrepareParameterSeed(
|
||||
state
|
||||
),
|
||||
currentPlanName: getCurrentPlanName(state),
|
||||
isFetchingParameters: state.parameters.isFetching
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
fetchParameters: currentPlanName =>
|
||||
dispatch(fetchParameters(currentPlanName)),
|
||||
resetToDefaults: () => dispatch(startContainerImagesPrepare({}))
|
||||
});
|
||||
|
||||
export default checkRunningDeployment(
|
||||
injectIntl(
|
||||
connect(mapStateToProps, mapDispatchToProps)(ContainerImagesWizard)
|
||||
)
|
||||
);
|
|
@ -0,0 +1,129 @@
|
|||
/**
|
||||
* Copyright 2017 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, { createRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
import {
|
||||
FormControl,
|
||||
Radio,
|
||||
FormGroup,
|
||||
ControlLabel,
|
||||
Col
|
||||
} from 'patternfly-react';
|
||||
import cx from 'classnames';
|
||||
|
||||
import {
|
||||
getValidationState,
|
||||
InputDescription,
|
||||
InputMessage
|
||||
} from '../ui/reduxForm/utils';
|
||||
|
||||
const messages = defineMessages({
|
||||
pushDestinationOptionFalse: {
|
||||
id: 'PushDestinationInput.pushDestinationOptionFalse',
|
||||
defaultMessage: `Don't push images`
|
||||
},
|
||||
pushDestinationOptionTrue: {
|
||||
id: 'PushDestinationInput.pushDestinationOptionTrue',
|
||||
defaultMessage: 'Push to Undercloud registry'
|
||||
},
|
||||
pushDestinationOptionCustom: {
|
||||
id: 'PushDestinationInput.pushDestinationOptionCustom',
|
||||
defaultMessage: 'Specify custom registry'
|
||||
}
|
||||
});
|
||||
|
||||
export default class PushDestinationInput extends React.Component {
|
||||
customRegistryInputRef = createRef();
|
||||
|
||||
render() {
|
||||
const {
|
||||
id,
|
||||
inputColumns,
|
||||
labelColumns,
|
||||
meta,
|
||||
label,
|
||||
input: { value, onChange, onBlur, ...inputRest },
|
||||
description,
|
||||
required
|
||||
} = this.props;
|
||||
return (
|
||||
<FormGroup controlId={id} validationState={getValidationState(meta)}>
|
||||
<Col
|
||||
componentClass={ControlLabel}
|
||||
sm={labelColumns}
|
||||
className={cx({ 'required-pf': required })}
|
||||
>
|
||||
{label}
|
||||
</Col>
|
||||
<Col sm={inputColumns}>
|
||||
<Radio
|
||||
{...inputRest}
|
||||
checked={value === false}
|
||||
onChange={e => onChange(false)}
|
||||
onBlur={e => onBlur(false)}
|
||||
>
|
||||
<FormattedMessage {...messages.pushDestinationOptionFalse} />
|
||||
</Radio>
|
||||
<Radio
|
||||
{...inputRest}
|
||||
checked={value === true}
|
||||
onChange={e => onChange(true)}
|
||||
onBlur={e => onBlur(true)}
|
||||
>
|
||||
<FormattedMessage {...messages.pushDestinationOptionTrue} />
|
||||
</Radio>
|
||||
<Radio
|
||||
{...inputRest}
|
||||
checked={value === this.customRegistryInputRef.value}
|
||||
onChange={e => onChange(this.customRegistryInputRef.value)}
|
||||
onBlur={e => onBlur(this.customRegistryInputRef.value)}
|
||||
>
|
||||
<FormattedMessage {...messages.pushDestinationOptionCustom} />
|
||||
</Radio>
|
||||
<FormControl
|
||||
{...inputRest}
|
||||
defaultValue="192.168.24.1:8787"
|
||||
inputRef={ref => (this.customRegistryInputRef = ref)}
|
||||
style={{ marginTop: 4 }}
|
||||
type="text"
|
||||
onChange={e => onChange(e.target.value)}
|
||||
onBlur={e => onBlur(e.target.value)}
|
||||
disabled={value !== this.customRegistryInputRef.value}
|
||||
/>
|
||||
<InputMessage {...meta} />
|
||||
<InputDescription description={description} />
|
||||
</Col>
|
||||
</FormGroup>
|
||||
);
|
||||
}
|
||||
}
|
||||
PushDestinationInput.propTypes = {
|
||||
description: PropTypes.node,
|
||||
id: PropTypes.string.isRequired,
|
||||
input: PropTypes.object.isRequired,
|
||||
inputColumns: PropTypes.number.isRequired,
|
||||
label: PropTypes.node,
|
||||
labelColumns: PropTypes.number.isRequired,
|
||||
meta: PropTypes.object.isRequired,
|
||||
required: PropTypes.bool.isRequired
|
||||
};
|
||||
PushDestinationInput.defaultProps = {
|
||||
labelColumns: 4,
|
||||
inputColumns: 7,
|
||||
required: false
|
||||
};
|
|
@ -43,6 +43,7 @@ import { fetchEnvironmentConfiguration } from '../../actions/EnvironmentConfigur
|
|||
import HardwareStep from './HardwareStep';
|
||||
import { Loader } from '../ui/Loader';
|
||||
import NetworkConfiguration from '../networkConfiguration/NetworkConfiguration';
|
||||
import ContainerImagesWizard from '../containerImages/ContainerImagesWizard';
|
||||
import { fetchParameters } from '../../actions/ParametersActions';
|
||||
import RoleDetail from '../roles/RoleDetail';
|
||||
import RolesStep from './RolesStep';
|
||||
|
@ -245,6 +246,10 @@ class CurrentPlan extends React.Component {
|
|||
path="/plans/:planName/network-configuration"
|
||||
component={NetworkConfiguration}
|
||||
/>
|
||||
<Route
|
||||
path="/plans/:planName/container-images"
|
||||
component={ContainerImagesWizard}
|
||||
/>
|
||||
<Route
|
||||
path="/plans/:planName/deployment-confirmation"
|
||||
render={() => (
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* 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 RoutedModal from './RoutedModal';
|
||||
|
||||
const RoutedWizard = props => (
|
||||
<RoutedModal {...props} dialogClassName="modal-lg wizard-pf" />
|
||||
);
|
||||
|
||||
export default RoutedWizard;
|
|
@ -19,4 +19,5 @@ export ConfirmationModal from './ConfirmationModal';
|
|||
export Modal from './Modal';
|
||||
export ModalPanel from './ModalPanel';
|
||||
export RoutedModal from './RoutedModal';
|
||||
export RoutedWizard from './RoutedWizard';
|
||||
export RoutedModalPanel from './RoutedModalPanel';
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const CONTAINER_IMAGES_PREPARE_SUCCESS =
|
||||
'CONTAINER_IMAGES_PREPARE_SUCCESS';
|
|
@ -27,6 +27,8 @@ export default {
|
|||
CAPABILITIES_UPDATE: 'tripleo.heat_capabilities.update',
|
||||
CREATE_CONTAINER: 'tripleo.plan.create_container',
|
||||
CONFIG_DOWNLOAD_DEPLOY: 'tripleo.deployment.v1.config_download_deploy',
|
||||
CONTAINER_IMAGES_PREPARE_DEFAULT:
|
||||
'tripleo.container_images.v1.container_image_prepare_default',
|
||||
DEPLOYMENT_DEPLOY_PLAN: 'tripleo.deployment.v1.deploy_plan',
|
||||
UNDEPLOY_PLAN: 'tripleo.deployment.v1.undeploy_plan',
|
||||
RECOVER_DEPLOYMENT_STATUS: 'tripleo.deployment.v1.recover_deployment_status',
|
||||
|
|
|
@ -24,6 +24,7 @@ import {
|
|||
Resource,
|
||||
Parameter
|
||||
} from '../immutableRecords/parameters';
|
||||
import { CONTAINER_IMAGES_PREPARE_SUCCESS } from '../constants/ContainerImagesConstants';
|
||||
|
||||
const initialState = new ParametersDefaultState();
|
||||
|
||||
|
@ -55,6 +56,12 @@ export default function parametersReducer(state = initialState, action) {
|
|||
case EnvironmentConfigurationConstants.UPDATE_ENVIRONMENT_CONFIGURATION_SUCCESS:
|
||||
return state.set('loaded', false);
|
||||
|
||||
case CONTAINER_IMAGES_PREPARE_SUCCESS:
|
||||
return state.updateIn(
|
||||
['parameters', 'ContainerImagePrepare'],
|
||||
p => p && p.set('default', action.payload)
|
||||
);
|
||||
|
||||
case PlansConstants.PLAN_CHOSEN:
|
||||
return initialState;
|
||||
|
||||
|
|
|
@ -33,3 +33,15 @@ export const FQDN_REGEX = new RegExp(
|
|||
export const PORT_REGEX = new RegExp(
|
||||
/^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/
|
||||
);
|
||||
|
||||
export const DOCKER_TAG_REGEX = new RegExp(/^[\w][\w.-]{0,127}$/);
|
||||
|
||||
// FQDN or IPV4:PORT / DOCKER_TAG (Docker tag format is the same as image name format)
|
||||
export const DOCKER_REGISTRY_NAMESPACE_REGEX = new RegExp(
|
||||
/^(((?=[a-z0-9-]{1,63}\.)[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?):([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]))\/[\w][\w.-]{0,127}$/i
|
||||
);
|
||||
|
||||
// IPV4:PORT
|
||||
export const IPV4_WITH_PORT_REGEX = new RegExp(
|
||||
/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?):([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue