Deployment Detail
* Adds DeploymentProgress, DeploymentSuccess and DeploymentFailure to deployment detail * Adds StackResourcesTable component * DeploymentProgress and DeploymentFailure components display StackResourcesTable Closes-Bug: 1623977 Change-Id: I70bfaa8c0f1994226b826cdd725967b575d2165f
This commit is contained in:
parent
32da7d2381
commit
e529993fc6
@ -1,4 +1,4 @@
|
|||||||
import { Map } from 'immutable';
|
import { Map, OrderedMap } from 'immutable';
|
||||||
import matchers from 'jasmine-immutable-matchers';
|
import matchers from 'jasmine-immutable-matchers';
|
||||||
|
|
||||||
import { StacksState, Stack } from '../../js/immutableRecords/stacks';
|
import { StacksState, Stack } from '../../js/immutableRecords/stacks';
|
||||||
@ -68,7 +68,7 @@ describe('stacksReducer state', () => {
|
|||||||
description: undefined,
|
description: undefined,
|
||||||
id: undefined,
|
id: undefined,
|
||||||
parent: undefined,
|
parent: undefined,
|
||||||
resources: Map(),
|
resources: OrderedMap(),
|
||||||
stack_name: 'overcloud',
|
stack_name: 'overcloud',
|
||||||
stack_owner: undefined,
|
stack_owner: undefined,
|
||||||
stack_status: 'CREATE_COMPLETE',
|
stack_status: 'CREATE_COMPLETE',
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Map } from 'immutable';
|
import { Map } from 'immutable';
|
||||||
import matchers from 'jasmine-immutable-matchers';
|
import matchers from 'jasmine-immutable-matchers';
|
||||||
|
|
||||||
import { getCurrentStackDeploymentProgress,
|
import { getCurrentStackDeploymentInProgress,
|
||||||
getCurrentStack } from '../../js/selectors/stacks';
|
getCurrentStack } from '../../js/selectors/stacks';
|
||||||
import { CurrentPlanState } from '../../js/immutableRecords/currentPlan';
|
import { CurrentPlanState } from '../../js/immutableRecords/currentPlan';
|
||||||
import { Stack, StacksState } from '../../js/immutableRecords/stacks';
|
import { Stack, StacksState } from '../../js/immutableRecords/stacks';
|
||||||
@ -31,7 +31,7 @@ describe('stacks selectors', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getCurrentStackDeploymentProgress', () => {
|
describe('getCurrentStackDeploymentInProgress', () => {
|
||||||
it('returns true if the current plan\'s deployment is in progress', () => {
|
it('returns true if the current plan\'s deployment is in progress', () => {
|
||||||
const state = {
|
const state = {
|
||||||
stacks: new StacksState({
|
stacks: new StacksState({
|
||||||
@ -44,7 +44,7 @@ describe('stacks selectors', () => {
|
|||||||
currentPlanName: 'overcloud'
|
currentPlanName: 'overcloud'
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
expect(getCurrentStackDeploymentProgress(state)).toBe(true);
|
expect(getCurrentStackDeploymentInProgress(state)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns false if the current plan\'s deployment is not in progress', () => {
|
it('returns false if the current plan\'s deployment is not in progress', () => {
|
||||||
@ -59,7 +59,7 @@ describe('stacks selectors', () => {
|
|||||||
currentplanname: 'overcloud'
|
currentplanname: 'overcloud'
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
expect(getCurrentStackDeploymentProgress(state)).toBe(false);
|
expect(getCurrentStackDeploymentInProgress(state)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns false if the current plan does not have an associated stack', () => {
|
it('returns false if the current plan does not have an associated stack', () => {
|
||||||
@ -73,7 +73,7 @@ describe('stacks selectors', () => {
|
|||||||
currentplanname: 'overcloud'
|
currentplanname: 'overcloud'
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
expect(getCurrentStackDeploymentProgress(state)).toBe(false);
|
expect(getCurrentStackDeploymentInProgress(state)).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -43,88 +43,75 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchStackPending() {
|
fetchResourcesPending() {
|
||||||
return {
|
return {
|
||||||
type: StacksConstants.FETCH_STACK_PENDING
|
type: StacksConstants.FETCH_RESOURCES_PENDING
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchStackSuccess(stack) {
|
fetchResourcesSuccess(resources) {
|
||||||
return {
|
return {
|
||||||
type: StacksConstants.FETCH_STACK_SUCCESS,
|
type: StacksConstants.FETCH_RESOURCES_SUCCESS,
|
||||||
payload: stack
|
payload: resources
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchStackFailed() {
|
fetchResourcesFailed() {
|
||||||
return {
|
return {
|
||||||
type: StacksConstants.FETCH_STACK_FAILED
|
type: StacksConstants.FETCH_RESOURCES_FAILED
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchStack(stackName, stackId) {
|
fetchResources(stackName, stackId) {
|
||||||
return (dispatch) => {
|
return (dispatch) => {
|
||||||
dispatch(this.fetchStackPending());
|
dispatch(this.fetchResourcesPending());
|
||||||
HeatApiService.getStack(stackName, stackId).then(({ stack }) => {
|
HeatApiService.getResources(stackName, stackId).then(({ resources }) => {
|
||||||
return HeatApiService.getResources(stack).then(({ resources }) => {
|
const res = normalize(resources,
|
||||||
stack.resources = normalize(resources,
|
|
||||||
arrayOf(stackResourceSchema)).entities.stackResources || {};
|
arrayOf(stackResourceSchema)).entities.stackResources || {};
|
||||||
dispatch(this.fetchStackSuccess(stack));
|
dispatch(this.fetchResourcesSuccess(res));
|
||||||
});
|
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
console.error('Error retrieving resources StackActions.fetchResources', error); //eslint-disable-line no-console
|
console.error('Error retrieving resources StackActions.fetchResources', error); //eslint-disable-line no-console
|
||||||
let errorHandler = new HeatApiErrorHandler(error);
|
let errorHandler = new HeatApiErrorHandler(error);
|
||||||
errorHandler.errors.forEach((error) => {
|
errorHandler.errors.forEach((error) => {
|
||||||
dispatch(NotificationActions.notify(error));
|
dispatch(NotificationActions.notify(error));
|
||||||
});
|
});
|
||||||
dispatch(this.fetchStackFailed(error));
|
dispatch(this.fetchResourcesFailed(error));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchResourceSuccess(stack, resourceName, resource) {
|
fetchResourceSuccess(resource) {
|
||||||
return {
|
return {
|
||||||
type: StacksConstants.FETCH_RESOURCE_SUCCESS,
|
type: StacksConstants.FETCH_RESOURCE_SUCCESS,
|
||||||
payload: {
|
payload: resource
|
||||||
stack,
|
|
||||||
resourceName,
|
|
||||||
resource
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchResourceFailed(stack, resourceName) {
|
fetchResourceFailed(resourceName) {
|
||||||
return {
|
return {
|
||||||
type: StacksConstants.FETCH_RESOURCE_FAILED,
|
type: StacksConstants.FETCH_RESOURCE_FAILED,
|
||||||
payload: {
|
payload: resourceName
|
||||||
stack,
|
|
||||||
resourceName
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchResourcePending(stack, resourceName) {
|
fetchResourcePending() {
|
||||||
return {
|
return {
|
||||||
type: StacksConstants.FETCH_RESOURCE_PENDING,
|
type: StacksConstants.FETCH_RESOURCE_PENDING
|
||||||
payload: {
|
|
||||||
stack,
|
|
||||||
resourceName
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchResource(stack, resourceName) {
|
fetchResource(stack, resourceName) {
|
||||||
return (dispatch) => {
|
return (dispatch) => {
|
||||||
dispatch(this.fetchResourcePending(stack));
|
dispatch(this.fetchResourcePending());
|
||||||
HeatApiService.getResource(stack, resourceName).then((response) => {
|
HeatApiService.getResource(stack, resourceName).then(({ resource }) => {
|
||||||
dispatch(this.fetchResourceSuccess(stack, resourceName, response));
|
dispatch(this.fetchResourceSuccess(resource));
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
console.error('Error retrieving resource StackActions.fetchResource', error); //eslint-disable-line no-console
|
console.error('Error retrieving resource StackActions.fetchResource', error); //eslint-disable-line no-console
|
||||||
let errorHandler = new HeatApiErrorHandler(error);
|
let errorHandler = new HeatApiErrorHandler(error);
|
||||||
errorHandler.errors.forEach((error) => {
|
errorHandler.errors.forEach((error) => {
|
||||||
dispatch(NotificationActions.notify(error));
|
dispatch(NotificationActions.notify(error));
|
||||||
});
|
});
|
||||||
dispatch(this.fetchResourceFailed(error));
|
dispatch(this.fetchResourceFailed(resourceName));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -1,35 +1,15 @@
|
|||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import { Link } from 'react-router';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import BlankSlate from '../ui/BlankSlate';
|
import BlankSlate from '../ui/BlankSlate';
|
||||||
import InlineNotification from '../ui/InlineNotification';
|
import InlineNotification from '../ui/InlineNotification';
|
||||||
import Loader from '../ui/Loader';
|
import Loader from '../ui/Loader';
|
||||||
import { ModalPanelBackdrop,
|
|
||||||
ModalPanel,
|
|
||||||
ModalPanelHeader,
|
|
||||||
ModalPanelBody,
|
|
||||||
ModalPanelFooter } from '../ui/ModalPanel';
|
|
||||||
|
|
||||||
const DeploymentConfirmation = ({ allValidationsSuccessful,
|
const DeploymentConfirmation = ({ allValidationsSuccessful,
|
||||||
currentPlan,
|
currentPlan,
|
||||||
deployPlan,
|
deployPlan,
|
||||||
environmentSummary }) => {
|
environmentSummary }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
|
||||||
<ModalPanelBackdrop />
|
|
||||||
<ModalPanel>
|
|
||||||
<ModalPanelHeader>
|
|
||||||
<Link to="/deployment-plan"
|
|
||||||
type="button"
|
|
||||||
className="close">
|
|
||||||
<span aria-hidden="true" className="pficon pficon-close"/>
|
|
||||||
</Link>
|
|
||||||
<h2 className="modal-title">
|
|
||||||
Deploy Plan {currentPlan.name}
|
|
||||||
</h2>
|
|
||||||
</ModalPanelHeader>
|
|
||||||
<ModalPanelBody>
|
|
||||||
<div className="col-sm-12 deployment-summary">
|
<div className="col-sm-12 deployment-summary">
|
||||||
<BlankSlate iconClass="fa fa-cloud-upload"
|
<BlankSlate iconClass="fa fa-cloud-upload"
|
||||||
title={`Deploy Plan ${currentPlan.name}`}>
|
title={`Deploy Plan ${currentPlan.name}`}>
|
||||||
@ -44,16 +24,6 @@ const DeploymentConfirmation = ({ allValidationsSuccessful,
|
|||||||
isRequestingPlanDeploy={currentPlan.isRequestingPlanDeploy}/>
|
isRequestingPlanDeploy={currentPlan.isRequestingPlanDeploy}/>
|
||||||
</BlankSlate>
|
</BlankSlate>
|
||||||
</div>
|
</div>
|
||||||
</ModalPanelBody>
|
|
||||||
<ModalPanelFooter>
|
|
||||||
<Link to="/deployment-plan"
|
|
||||||
type="button"
|
|
||||||
className="btn btn-default">
|
|
||||||
Cancel
|
|
||||||
</Link>
|
|
||||||
</ModalPanelFooter>
|
|
||||||
</ModalPanel>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
DeploymentConfirmation.propTypes = {
|
DeploymentConfirmation.propTypes = {
|
||||||
|
@ -1,52 +1,119 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import { Link } from 'react-router';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import DeploymentConfirmation from './DeploymentConfirmation';
|
|
||||||
import { getCurrentPlan } from '../../selectors/plans';
|
|
||||||
import { getCurrentStack } from '../../selectors/stacks';
|
|
||||||
import { getEnvironmentConfigurationSummary } from '../../selectors/environmentConfiguration';
|
|
||||||
import { allPreDeploymentValidationsSuccessful } from '../../selectors/validations';
|
import { allPreDeploymentValidationsSuccessful } from '../../selectors/validations';
|
||||||
|
import DeploymentConfirmation from './DeploymentConfirmation';
|
||||||
|
import DeploymentProgress from './DeploymentProgress';
|
||||||
|
import DeploymentSuccess from './DeploymentSuccess';
|
||||||
|
import DeploymentFailure from './DeploymentFailure';
|
||||||
|
import { getCurrentPlan } from '../../selectors/plans';
|
||||||
|
import { getCurrentStack,
|
||||||
|
getCurrentStackDeploymentProgress } from '../../selectors/stacks';
|
||||||
|
import { getEnvironmentConfigurationSummary } from '../../selectors/environmentConfiguration';
|
||||||
|
import Loader from '../ui/Loader';
|
||||||
|
import { ModalPanelBackdrop,
|
||||||
|
ModalPanel,
|
||||||
|
ModalPanelHeader,
|
||||||
|
ModalPanelBody,
|
||||||
|
ModalPanelFooter } from '../ui/ModalPanel';
|
||||||
import PlanActions from '../../actions/PlansActions';
|
import PlanActions from '../../actions/PlansActions';
|
||||||
import { stackStates } from '../../constants/StacksConstants';
|
import { stackStates } from '../../constants/StacksConstants';
|
||||||
|
import StacksActions from '../../actions/StacksActions';
|
||||||
|
|
||||||
const DeploymentDetail = ({ currentPlan,
|
class DeploymentDetail extends React.Component {
|
||||||
|
renderStatus() {
|
||||||
|
const { allPreDeploymentValidationsSuccessful,
|
||||||
|
currentPlan,
|
||||||
currentStack,
|
currentStack,
|
||||||
|
currentStackDeploymentProgress,
|
||||||
|
currentStackResources,
|
||||||
|
currentStackResourcesLoaded,
|
||||||
deployPlan,
|
deployPlan,
|
||||||
environmentConfigurationSummary,
|
environmentConfigurationSummary,
|
||||||
allPreDeploymentValidationsSuccessful }) => {
|
fetchStackResources,
|
||||||
|
stacksLoaded } = this.props;
|
||||||
|
|
||||||
if (!currentStack || currentStack.stack_status === stackStates.DELETE_COMPLETE) {
|
if (!currentStack || currentStack.stack_status === stackStates.DELETE_COMPLETE) {
|
||||||
return (
|
return (
|
||||||
|
<Loader loaded={stacksLoaded}
|
||||||
|
content="Loading Stacks..."
|
||||||
|
height={40}>
|
||||||
<DeploymentConfirmation
|
<DeploymentConfirmation
|
||||||
allValidationsSuccessful={allPreDeploymentValidationsSuccessful}
|
allValidationsSuccessful={allPreDeploymentValidationsSuccessful}
|
||||||
currentPlan={currentPlan}
|
currentPlan={currentPlan}
|
||||||
deployPlan={deployPlan}
|
deployPlan={deployPlan}
|
||||||
environmentSummary={environmentConfigurationSummary}/>
|
environmentSummary={environmentConfigurationSummary}/>
|
||||||
|
</Loader>
|
||||||
);
|
);
|
||||||
} else if (currentStack.stack_status.match(/PROGRESS/)) {
|
} else if (currentStack.stack_status.match(/PROGRESS/)) {
|
||||||
return (
|
return (
|
||||||
// TODO(jtomasek): render component DeploymentProgress
|
<DeploymentProgress stack={currentStack}
|
||||||
null
|
stackResources={currentStackResources}
|
||||||
|
deploymentProgress={currentStackDeploymentProgress}
|
||||||
|
stackResourcesLoaded={currentStackResourcesLoaded}
|
||||||
|
fetchStackResources={fetchStackResources} />
|
||||||
);
|
);
|
||||||
} else if (currentStack.stack_status.match(/COMPLETE/)) {
|
} else if (currentStack.stack_status.match(/COMPLETE/)) {
|
||||||
return (
|
return (
|
||||||
// TODO(jtomasek): render component DeploymentSuccess
|
<DeploymentSuccess stack={currentStack}
|
||||||
null
|
stackResources={currentStackResources}
|
||||||
|
stackResourcesLoaded={currentStackResourcesLoaded}/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
// TODO(jtomasek): render component DeploymentFailure
|
<DeploymentFailure fetchStackResources={fetchStackResources}
|
||||||
null
|
stack={currentStack}
|
||||||
|
stackResources={currentStackResources}
|
||||||
|
stackResourcesLoaded={currentStackResourcesLoaded}
|
||||||
|
planName={currentPlan.name}/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<ModalPanelBackdrop />
|
||||||
|
<ModalPanel>
|
||||||
|
<ModalPanelHeader>
|
||||||
|
<Link to="/deployment-plan"
|
||||||
|
type="button"
|
||||||
|
className="close">
|
||||||
|
<span aria-hidden="true" className="pficon pficon-close"/>
|
||||||
|
</Link>
|
||||||
|
<h2 className="modal-title">
|
||||||
|
Plan {this.props.currentPlan.name} deployment
|
||||||
|
</h2>
|
||||||
|
</ModalPanelHeader>
|
||||||
|
<ModalPanelBody>
|
||||||
|
{this.renderStatus()}
|
||||||
|
</ModalPanelBody>
|
||||||
|
<ModalPanelFooter>
|
||||||
|
<Link to="/deployment-plan"
|
||||||
|
type="button"
|
||||||
|
className="btn btn-default">
|
||||||
|
Close
|
||||||
|
</Link>
|
||||||
|
</ModalPanelFooter>
|
||||||
|
</ModalPanel>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DeploymentDetail.propTypes = {
|
DeploymentDetail.propTypes = {
|
||||||
allPreDeploymentValidationsSuccessful: React.PropTypes.bool.isRequired,
|
allPreDeploymentValidationsSuccessful: React.PropTypes.bool.isRequired,
|
||||||
currentPlan: ImmutablePropTypes.record.isRequired,
|
currentPlan: ImmutablePropTypes.record.isRequired,
|
||||||
currentStack: ImmutablePropTypes.record,
|
currentStack: ImmutablePropTypes.record,
|
||||||
|
currentStackDeploymentProgress: React.PropTypes.number.isRequired,
|
||||||
|
currentStackResources: ImmutablePropTypes.map,
|
||||||
|
currentStackResourcesLoaded: React.PropTypes.bool.isRequired,
|
||||||
deployPlan: React.PropTypes.func.isRequired,
|
deployPlan: React.PropTypes.func.isRequired,
|
||||||
environmentConfigurationSummary: React.PropTypes.string
|
environmentConfigurationSummary: React.PropTypes.string,
|
||||||
|
fetchStackResources: React.PropTypes.func.isRequired,
|
||||||
|
stacksLoaded: React.PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state) => {
|
const mapStateToProps = (state) => {
|
||||||
@ -54,13 +121,19 @@ const mapStateToProps = (state) => {
|
|||||||
allPreDeploymentValidationsSuccessful: allPreDeploymentValidationsSuccessful(state),
|
allPreDeploymentValidationsSuccessful: allPreDeploymentValidationsSuccessful(state),
|
||||||
currentPlan: getCurrentPlan(state),
|
currentPlan: getCurrentPlan(state),
|
||||||
currentStack: getCurrentStack(state),
|
currentStack: getCurrentStack(state),
|
||||||
environmentConfigurationSummary: getEnvironmentConfigurationSummary(state)
|
currentStackDeploymentProgress: getCurrentStackDeploymentProgress(state),
|
||||||
|
currentStackResources: state.stacks.resources,
|
||||||
|
currentStackResourcesLoaded: state.stacks.resourcesLoaded,
|
||||||
|
environmentConfigurationSummary: getEnvironmentConfigurationSummary(state),
|
||||||
|
stacksLoaded: state.stacks.isLoaded
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => {
|
const mapDispatchToProps = (dispatch) => {
|
||||||
return {
|
return {
|
||||||
deployPlan: planName => dispatch(PlanActions.deployPlan(planName))
|
deployPlan: planName => dispatch(PlanActions.deployPlan(planName)),
|
||||||
|
fetchStackResources: (stack) =>
|
||||||
|
dispatch(StacksActions.fetchResources(stack.stack_name, stack.id))
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
34
src/js/components/deployment/DeploymentFailure.js
Normal file
34
src/js/components/deployment/DeploymentFailure.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { deploymentStatusMessages } from '../../constants/StacksConstants';
|
||||||
|
import InlineNotification from '../ui/InlineNotification';
|
||||||
|
import StackResourcesTable from './StackResourcesTable';
|
||||||
|
|
||||||
|
export default class DeploymentSuccess extends React.Component {
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.fetchStackResources(this.props.stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="col-sm-12">
|
||||||
|
<InlineNotification type="error"
|
||||||
|
title={deploymentStatusMessages[this.props.stack.stack_status]}>
|
||||||
|
<p>{this.props.stack.stack_status_reason}</p>
|
||||||
|
</InlineNotification>
|
||||||
|
<h2>Resources</h2>
|
||||||
|
<StackResourcesTable isFetchingResources={!this.props.stackResourcesLoaded}
|
||||||
|
resources={this.props.stackResources.reverse()}/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DeploymentSuccess.propTypes = {
|
||||||
|
fetchStackResources: React.PropTypes.func.isRequired,
|
||||||
|
planName: React.PropTypes.string.isRequired,
|
||||||
|
stack: ImmutablePropTypes.record.isRequired,
|
||||||
|
stackResources: ImmutablePropTypes.map.isRequired,
|
||||||
|
stackResourcesLoaded: React.PropTypes.bool.isRequired
|
||||||
|
};
|
50
src/js/components/deployment/DeploymentProgress.js
Normal file
50
src/js/components/deployment/DeploymentProgress.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { deploymentStatusMessages as statusMessages,
|
||||||
|
stackStates } from '../../constants/StacksConstants';
|
||||||
|
import Loader from '../ui/Loader';
|
||||||
|
import ProgressBar from '../ui/ProgressBar';
|
||||||
|
import StackResourcesTable from './StackResourcesTable';
|
||||||
|
|
||||||
|
export default class DeploymentProgress extends React.Component {
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.fetchStackResources(this.props.stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderProgressBar() {
|
||||||
|
return (
|
||||||
|
this.props.stack.stack_status === stackStates.CREATE_IN_PROGRESS ? (
|
||||||
|
<ProgressBar value={this.props.deploymentProgress}
|
||||||
|
label={this.props.deploymentProgress + '%'}
|
||||||
|
labelPosition="topRight"/>
|
||||||
|
) : null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const statusMessage = (
|
||||||
|
<strong>{statusMessages[this.props.stack.stack_status]}</strong>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="col-sm-12">
|
||||||
|
<div className="progress-description">
|
||||||
|
<Loader loaded={false} content={statusMessage} inline/>
|
||||||
|
</div>
|
||||||
|
{this.renderProgressBar()}
|
||||||
|
<h2>Resources</h2>
|
||||||
|
<StackResourcesTable isFetchingResources={!this.props.stackResourcesLoaded}
|
||||||
|
resources={this.props.stackResources.reverse()}/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DeploymentProgress.propTypes = {
|
||||||
|
deploymentProgress: React.PropTypes.number.isRequired,
|
||||||
|
fetchStackResources: React.PropTypes.func.isRequired,
|
||||||
|
stack: ImmutablePropTypes.record.isRequired,
|
||||||
|
stackResources: ImmutablePropTypes.map.isRequired,
|
||||||
|
stackResourcesLoaded: React.PropTypes.bool.isRequired
|
||||||
|
};
|
41
src/js/components/deployment/DeploymentSuccess.js
Normal file
41
src/js/components/deployment/DeploymentSuccess.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import Loader from '../ui/Loader';
|
||||||
|
import { deploymentStatusMessages } from '../../constants/StacksConstants';
|
||||||
|
import InlineNotification from '../ui/InlineNotification';
|
||||||
|
|
||||||
|
export default class DeploymentSuccess extends React.Component {
|
||||||
|
render() {
|
||||||
|
const ip = this.props.stackResources.getIn([
|
||||||
|
'PublicVirtualIP', 'attributes', 'ip_address'
|
||||||
|
]);
|
||||||
|
|
||||||
|
const password = this.props.stack.getIn([
|
||||||
|
'environment', 'parameter_defaults', 'AdminPassword'
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="col-sm-12">
|
||||||
|
<InlineNotification type="success"
|
||||||
|
title={deploymentStatusMessages[this.props.stack.stack_status]}>
|
||||||
|
<p>{this.props.stack.stack_status_reason}</p>
|
||||||
|
</InlineNotification>
|
||||||
|
<h4>Overcloud information:</h4>
|
||||||
|
<Loader loaded={this.props.stackResourcesLoaded}
|
||||||
|
content="Loading overcloud information...">
|
||||||
|
<ul>
|
||||||
|
<li>Overcloud IP address: <a href={`http://${ip}`}>http://{ip}</a></li>
|
||||||
|
<li>Password: {password}</li>
|
||||||
|
</ul>
|
||||||
|
</Loader>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DeploymentSuccess.propTypes = {
|
||||||
|
stack: ImmutablePropTypes.record.isRequired,
|
||||||
|
stackResources: ImmutablePropTypes.map.isRequired,
|
||||||
|
stackResourcesLoaded: React.PropTypes.bool.isRequired
|
||||||
|
};
|
78
src/js/components/deployment/StackResourcesTable.js
Normal file
78
src/js/components/deployment/StackResourcesTable.js
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
|
||||||
|
import DataTable from '../ui/tables/DataTable';
|
||||||
|
import { DataTableDateFieldCell,
|
||||||
|
DataTableDataFieldCell,
|
||||||
|
DataTableHeaderCell } from '../ui/tables/DataTableCells';
|
||||||
|
import DataTableColumn from '../ui/tables/DataTableColumn';
|
||||||
|
import Loader from '../ui/Loader';
|
||||||
|
|
||||||
|
export default class StackResourcesTable extends React.Component {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.state = {
|
||||||
|
filterString: ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
renderNoResourcesFound() {
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<td className="no-results" colSpan="10">
|
||||||
|
<Loader loaded={!this.props.isFetchingResources}
|
||||||
|
height={40}
|
||||||
|
content="Loading Resources...">
|
||||||
|
<p className="text-center">There are no Resources available</p>
|
||||||
|
</Loader>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onFilter(filterString) {
|
||||||
|
this.setState({
|
||||||
|
filterString: filterString
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_filterData(filterString, data) {
|
||||||
|
let dataKeys = ['resource_name', 'resource_status'];
|
||||||
|
return filterString ? data.filter((row) => {
|
||||||
|
let result = dataKeys.filter((dataKey) => {
|
||||||
|
return row[dataKey].toLowerCase().includes(filterString.toLowerCase());
|
||||||
|
});
|
||||||
|
return result.length > 0;
|
||||||
|
}) : data;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let filteredData = this._filterData(this.state.filterString,
|
||||||
|
this.props.resources.toList().toJS());
|
||||||
|
return (
|
||||||
|
<DataTable {...this.props}
|
||||||
|
data={this.props.resources.toList().toJS()}
|
||||||
|
rowsCount={filteredData.length}
|
||||||
|
noRowsRenderer={this.renderNoResourcesFound.bind(this)}
|
||||||
|
onFilter={this.onFilter.bind(this)}
|
||||||
|
filterString={this.state.filterString}>
|
||||||
|
<DataTableColumn
|
||||||
|
key="resource_name"
|
||||||
|
header={<DataTableHeaderCell key="resource_name">Name</DataTableHeaderCell>}
|
||||||
|
cell={<DataTableDataFieldCell data={filteredData} field="resource_name"/>}/>
|
||||||
|
<DataTableColumn
|
||||||
|
key="resource_status"
|
||||||
|
header={<DataTableHeaderCell key="resource_status">Status</DataTableHeaderCell>}
|
||||||
|
cell={<DataTableDataFieldCell data={filteredData} field="resource_status"/>}/>
|
||||||
|
<DataTableColumn
|
||||||
|
key="updated_time"
|
||||||
|
header={<DataTableHeaderCell key="updated_time">Updated Time</DataTableHeaderCell>}
|
||||||
|
cell={<DataTableDateFieldCell data={filteredData} field="updated_time"/>}/>
|
||||||
|
</DataTable>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StackResourcesTable.propTypes = {
|
||||||
|
isFetchingResources: React.PropTypes.bool.isRequired,
|
||||||
|
resources: ImmutablePropTypes.map.isRequired
|
||||||
|
};
|
@ -8,9 +8,10 @@ import Link from '../ui/Link';
|
|||||||
import Loader from '../ui/Loader';
|
import Loader from '../ui/Loader';
|
||||||
import { stackStates } from '../../constants/StacksConstants';
|
import { stackStates } from '../../constants/StacksConstants';
|
||||||
|
|
||||||
export const DeployStep = ({ currentPlan, currentStack, deployPlan, fetchStack, fetchStackResource,
|
export const DeployStep = ({ currentPlan, currentStack, currentStackResources,
|
||||||
fetchStackEnvironment, runPostDeploymentValidations,
|
currentStackResourcesLoaded, currentStackDeploymentProgress,
|
||||||
stacksLoaded }) => {
|
deployPlan, fetchStackResource, fetchStackEnvironment,
|
||||||
|
runPostDeploymentValidations, stacksLoaded }) => {
|
||||||
if (!currentStack || currentStack.stack_status === stackStates.DELETE_COMPLETE) {
|
if (!currentStack || currentStack.stack_status === stackStates.DELETE_COMPLETE) {
|
||||||
return (
|
return (
|
||||||
<Loader loaded={stacksLoaded}>
|
<Loader loaded={stacksLoaded}>
|
||||||
@ -29,11 +30,13 @@ export const DeployStep = ({ currentPlan, currentStack, deployPlan, fetchStack,
|
|||||||
} else if (currentStack.stack_status.match(/PROGRESS/)) {
|
} else if (currentStack.stack_status.match(/PROGRESS/)) {
|
||||||
return (
|
return (
|
||||||
<DeploymentProgress stack={currentStack}
|
<DeploymentProgress stack={currentStack}
|
||||||
fetchStack={fetchStack} />
|
deploymentProgress={currentStackDeploymentProgress}/>
|
||||||
);
|
);
|
||||||
} else if (currentStack.stack_status.match(/COMPLETE/)) {
|
} else if (currentStack.stack_status.match(/COMPLETE/)) {
|
||||||
return (
|
return (
|
||||||
<DeploymentSuccess stack={currentStack}
|
<DeploymentSuccess stack={currentStack}
|
||||||
|
stackResources={currentStackResources}
|
||||||
|
stackResourcesLoaded={currentStackResourcesLoaded}
|
||||||
fetchStackResource={fetchStackResource}
|
fetchStackResource={fetchStackResource}
|
||||||
fetchStackEnvironment={fetchStackEnvironment}
|
fetchStackEnvironment={fetchStackEnvironment}
|
||||||
runPostDeploymentValidations={runPostDeploymentValidations}/>
|
runPostDeploymentValidations={runPostDeploymentValidations}/>
|
||||||
@ -48,8 +51,10 @@ export const DeployStep = ({ currentPlan, currentStack, deployPlan, fetchStack,
|
|||||||
DeployStep.propTypes = {
|
DeployStep.propTypes = {
|
||||||
currentPlan: ImmutablePropTypes.record.isRequired,
|
currentPlan: ImmutablePropTypes.record.isRequired,
|
||||||
currentStack: ImmutablePropTypes.record,
|
currentStack: ImmutablePropTypes.record,
|
||||||
|
currentStackDeploymentProgress: React.PropTypes.number.isRequired,
|
||||||
|
currentStackResources: ImmutablePropTypes.map,
|
||||||
|
currentStackResourcesLoaded: React.PropTypes.bool.isRequired,
|
||||||
deployPlan: React.PropTypes.func.isRequired,
|
deployPlan: React.PropTypes.func.isRequired,
|
||||||
fetchStack: React.PropTypes.func.isRequired,
|
|
||||||
fetchStackEnvironment: React.PropTypes.func.isRequired,
|
fetchStackEnvironment: React.PropTypes.func.isRequired,
|
||||||
fetchStackResource: React.PropTypes.func.isRequired,
|
fetchStackResource: React.PropTypes.func.isRequired,
|
||||||
runPostDeploymentValidations: React.PropTypes.func.isRequired,
|
runPostDeploymentValidations: React.PropTypes.func.isRequired,
|
||||||
|
@ -4,7 +4,8 @@ import React from 'react';
|
|||||||
|
|
||||||
import { getAllPlansButCurrent } from '../../selectors/plans';
|
import { getAllPlansButCurrent } from '../../selectors/plans';
|
||||||
import { getCurrentStack,
|
import { getCurrentStack,
|
||||||
getCurrentStackDeploymentProgress } from '../../selectors/stacks';
|
getCurrentStackDeploymentProgress,
|
||||||
|
getCurrentStackDeploymentInProgress } from '../../selectors/stacks';
|
||||||
import { getAvailableNodes, getUnassignedAvailableNodes } from '../../selectors/nodes';
|
import { getAvailableNodes, getUnassignedAvailableNodes } from '../../selectors/nodes';
|
||||||
import { getEnvironmentConfigurationSummary } from '../../selectors/environmentConfiguration';
|
import { getEnvironmentConfigurationSummary } from '../../selectors/environmentConfiguration';
|
||||||
import { getCurrentPlan } from '../../selectors/plans';
|
import { getCurrentPlan } from '../../selectors/plans';
|
||||||
@ -41,7 +42,10 @@ class DeploymentPlan extends React.Component {
|
|||||||
if (currentStack) {
|
if (currentStack) {
|
||||||
if (currentStack.stack_status.match(/PROGRESS/)) {
|
if (currentStack.stack_status.match(/PROGRESS/)) {
|
||||||
clearTimeout(this.stackProgressTimeout);
|
clearTimeout(this.stackProgressTimeout);
|
||||||
this.stackProgressTimeout = setTimeout(() => this.props.fetchStack(currentStack), 20000);
|
this.stackProgressTimeout = setTimeout(() => {
|
||||||
|
this.props.fetchStacks();
|
||||||
|
this.props.fetchStackResources();
|
||||||
|
}, 20000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -69,11 +73,11 @@ class DeploymentPlan extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
<ol className="deployment-step-list">
|
<ol className="deployment-step-list">
|
||||||
<DeploymentPlanStep title="Prepare Hardware"
|
<DeploymentPlanStep title="Prepare Hardware"
|
||||||
disabled={this.props.currentStackDeploymentProgress}>
|
disabled={this.props.currentStackDeploymentInProgress}>
|
||||||
<HardwareStep />
|
<HardwareStep />
|
||||||
</DeploymentPlanStep>
|
</DeploymentPlanStep>
|
||||||
<DeploymentPlanStep title="Specify Deployment Configuration"
|
<DeploymentPlanStep title="Specify Deployment Configuration"
|
||||||
disabled={this.props.currentStackDeploymentProgress}>
|
disabled={this.props.currentStackDeploymentInProgress}>
|
||||||
<ConfigurePlanStep
|
<ConfigurePlanStep
|
||||||
fetchEnvironmentConfiguration={this.props.fetchEnvironmentConfiguration}
|
fetchEnvironmentConfiguration={this.props.fetchEnvironmentConfiguration}
|
||||||
summary={this.props.environmentConfigurationSummary}
|
summary={this.props.environmentConfigurationSummary}
|
||||||
@ -82,7 +86,7 @@ class DeploymentPlan extends React.Component {
|
|||||||
loaded={this.props.environmentConfigurationLoaded}/>
|
loaded={this.props.environmentConfigurationLoaded}/>
|
||||||
</DeploymentPlanStep>
|
</DeploymentPlanStep>
|
||||||
<DeploymentPlanStep title="Configure Roles and Assign Nodes"
|
<DeploymentPlanStep title="Configure Roles and Assign Nodes"
|
||||||
disabled={this.props.currentStackDeploymentProgress}>
|
disabled={this.props.currentStackDeploymentInProgress}>
|
||||||
<RolesStep availableNodes={this.props.availableNodes}
|
<RolesStep availableNodes={this.props.availableNodes}
|
||||||
fetchNodes={this.props.fetchNodes}
|
fetchNodes={this.props.fetchNodes}
|
||||||
fetchRoles={this.props.fetchRoles}
|
fetchRoles={this.props.fetchRoles}
|
||||||
@ -96,8 +100,10 @@ class DeploymentPlan extends React.Component {
|
|||||||
<DeployStep
|
<DeployStep
|
||||||
currentPlan={this.props.currentPlan}
|
currentPlan={this.props.currentPlan}
|
||||||
currentStack={this.props.currentStack}
|
currentStack={this.props.currentStack}
|
||||||
|
currentStackResources={this.props.currentStackResources}
|
||||||
|
currentStackResourcesLoaded={this.props.currentStackResourcesLoaded}
|
||||||
|
currentStackDeploymentProgress={this.props.currentStackDeploymentProgress}
|
||||||
deployPlan={this.props.deployPlan}
|
deployPlan={this.props.deployPlan}
|
||||||
fetchStack={this.props.fetchStack}
|
|
||||||
fetchStackEnvironment={this.props.fetchStackEnvironment}
|
fetchStackEnvironment={this.props.fetchStackEnvironment}
|
||||||
fetchStackResource={this.props.fetchStackResource}
|
fetchStackResource={this.props.fetchStackResource}
|
||||||
runPostDeploymentValidations={
|
runPostDeploymentValidations={
|
||||||
@ -123,16 +129,19 @@ DeploymentPlan.propTypes = {
|
|||||||
choosePlan: React.PropTypes.func,
|
choosePlan: React.PropTypes.func,
|
||||||
currentPlan: ImmutablePropTypes.record,
|
currentPlan: ImmutablePropTypes.record,
|
||||||
currentStack: ImmutablePropTypes.record,
|
currentStack: ImmutablePropTypes.record,
|
||||||
currentStackDeploymentProgress: React.PropTypes.bool,
|
currentStackDeploymentInProgress: React.PropTypes.bool,
|
||||||
|
currentStackDeploymentProgress: React.PropTypes.number.isRequired,
|
||||||
|
currentStackResources: ImmutablePropTypes.map,
|
||||||
|
currentStackResourcesLoaded: React.PropTypes.bool.isRequired,
|
||||||
deployPlan: React.PropTypes.func,
|
deployPlan: React.PropTypes.func,
|
||||||
environmentConfigurationLoaded: React.PropTypes.bool,
|
environmentConfigurationLoaded: React.PropTypes.bool,
|
||||||
environmentConfigurationSummary: React.PropTypes.string,
|
environmentConfigurationSummary: React.PropTypes.string,
|
||||||
fetchEnvironmentConfiguration: React.PropTypes.func,
|
fetchEnvironmentConfiguration: React.PropTypes.func,
|
||||||
fetchNodes: React.PropTypes.func,
|
fetchNodes: React.PropTypes.func,
|
||||||
fetchRoles: React.PropTypes.func,
|
fetchRoles: React.PropTypes.func,
|
||||||
fetchStack: React.PropTypes.func.isRequired,
|
|
||||||
fetchStackEnvironment: React.PropTypes.func,
|
fetchStackEnvironment: React.PropTypes.func,
|
||||||
fetchStackResource: React.PropTypes.func,
|
fetchStackResource: React.PropTypes.func,
|
||||||
|
fetchStackResources: React.PropTypes.func.isRequired,
|
||||||
fetchStacks: React.PropTypes.func,
|
fetchStacks: React.PropTypes.func,
|
||||||
hasPlans: React.PropTypes.bool,
|
hasPlans: React.PropTypes.bool,
|
||||||
inactivePlans: ImmutablePropTypes.map,
|
inactivePlans: ImmutablePropTypes.map,
|
||||||
@ -152,6 +161,9 @@ export function mapStateToProps(state) {
|
|||||||
return {
|
return {
|
||||||
currentPlan: getCurrentPlan(state),
|
currentPlan: getCurrentPlan(state),
|
||||||
currentStack: getCurrentStack(state),
|
currentStack: getCurrentStack(state),
|
||||||
|
currentStackResources: state.stacks.resources,
|
||||||
|
currentStackResourcesLoaded: state.stacks.resourcesLoaded,
|
||||||
|
currentStackDeploymentInProgress: getCurrentStackDeploymentInProgress(state),
|
||||||
currentStackDeploymentProgress: getCurrentStackDeploymentProgress(state),
|
currentStackDeploymentProgress: getCurrentStackDeploymentProgress(state),
|
||||||
environmentConfigurationLoaded: state.environmentConfiguration.loaded,
|
environmentConfigurationLoaded: state.environmentConfiguration.loaded,
|
||||||
environmentConfigurationSummary: getEnvironmentConfigurationSummary(state),
|
environmentConfigurationSummary: getEnvironmentConfigurationSummary(state),
|
||||||
@ -178,7 +190,8 @@ function mapDispatchToProps(dispatch) {
|
|||||||
},
|
},
|
||||||
fetchNodes: () => dispatch(NodesActions.fetchNodes()),
|
fetchNodes: () => dispatch(NodesActions.fetchNodes()),
|
||||||
fetchRoles: () => dispatch(RolesActions.fetchRoles()),
|
fetchRoles: () => dispatch(RolesActions.fetchRoles()),
|
||||||
fetchStack: (stack) => dispatch(StacksActions.fetchStack(stack.stack_name, stack.id)),
|
fetchStackResources: (stack) =>
|
||||||
|
dispatch(StacksActions.fetchResources(stack.stack_name, stack.id)),
|
||||||
fetchStackResource: (stack, resourceName) =>
|
fetchStackResource: (stack, resourceName) =>
|
||||||
dispatch(StacksActions.fetchResource(stack, resourceName)),
|
dispatch(StacksActions.fetchResource(stack, resourceName)),
|
||||||
fetchStacks: () => dispatch(StacksActions.fetchStacks()),
|
fetchStacks: () => dispatch(StacksActions.fetchStacks()),
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Link } from 'react-router';
|
||||||
|
|
||||||
import { deploymentStatusMessages as statusMessages,
|
import { deploymentStatusMessages as statusMessages,
|
||||||
stackStates } from '../../constants/StacksConstants';
|
stackStates } from '../../constants/StacksConstants';
|
||||||
@ -7,23 +8,11 @@ import Loader from '../ui/Loader';
|
|||||||
import ProgressBar from '../ui/ProgressBar';
|
import ProgressBar from '../ui/ProgressBar';
|
||||||
|
|
||||||
export default class DeploymentProgress extends React.Component {
|
export default class DeploymentProgress extends React.Component {
|
||||||
calculateProgress() {
|
|
||||||
let allResources = this.props.stack.resources.size;
|
|
||||||
if(allResources > 0) {
|
|
||||||
let completeResources = this.props.stack.resources.filter(r => {
|
|
||||||
return r.resource_status === 'CREATE_COMPLETE';
|
|
||||||
}).size;
|
|
||||||
return Math.ceil(completeResources / allResources * 100);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderProgressBar() {
|
renderProgressBar() {
|
||||||
const progress = this.calculateProgress();
|
|
||||||
return (
|
return (
|
||||||
this.props.stack.stack_status === stackStates.CREATE_IN_PROGRESS ? (
|
this.props.stack.stack_status === stackStates.CREATE_IN_PROGRESS ? (
|
||||||
<ProgressBar value={progress}
|
<ProgressBar value={this.props.deploymentProgress}
|
||||||
label={progress + '%'}
|
label={this.props.deploymentProgress + '%'}
|
||||||
labelPosition="topRight"/>
|
labelPosition="topRight"/>
|
||||||
) : null
|
) : null
|
||||||
);
|
);
|
||||||
@ -36,6 +25,11 @@ export default class DeploymentProgress extends React.Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
<p>
|
||||||
|
Deployment is currently in progress. <Link to="/deployment-plan/deployment-detail">
|
||||||
|
View detailed information
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
<div className="progress-description">
|
<div className="progress-description">
|
||||||
<Loader loaded={false} content={statusMessage} inline/>
|
<Loader loaded={false} content={statusMessage} inline/>
|
||||||
</div>
|
</div>
|
||||||
@ -46,6 +40,6 @@ export default class DeploymentProgress extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DeploymentProgress.propTypes = {
|
DeploymentProgress.propTypes = {
|
||||||
fetchStack: React.PropTypes.func.isRequired,
|
deploymentProgress: React.PropTypes.number.isRequired,
|
||||||
stack: ImmutablePropTypes.record.isRequired
|
stack: ImmutablePropTypes.record.isRequired
|
||||||
};
|
};
|
||||||
|
@ -13,8 +13,7 @@ export default class DeploymentSuccess extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const loaded = !this.props.stack.resources.isEmpty();
|
const ip = this.props.stackResources.getIn([
|
||||||
const ip = this.props.stack.resources.getIn([
|
|
||||||
'PublicVirtualIP', 'attributes', 'ip_address'
|
'PublicVirtualIP', 'attributes', 'ip_address'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -31,7 +30,8 @@ export default class DeploymentSuccess extends React.Component {
|
|||||||
<p>{this.props.stack.stack_status_reason}</p>
|
<p>{this.props.stack.stack_status_reason}</p>
|
||||||
</InlineNotification>
|
</InlineNotification>
|
||||||
<h4>Overcloud information:</h4>
|
<h4>Overcloud information:</h4>
|
||||||
<Loader loaded={loaded} content="Loading overcloud information...">
|
<Loader loaded={this.props.stackResourcesLoaded}
|
||||||
|
content="Loading overcloud information...">
|
||||||
<ul>
|
<ul>
|
||||||
<li>Overcloud IP address: <a href={`http://${ip}`}>http://{ip}</a></li>
|
<li>Overcloud IP address: <a href={`http://${ip}`}>http://{ip}</a></li>
|
||||||
<li>Password: {password}</li>
|
<li>Password: {password}</li>
|
||||||
@ -46,5 +46,7 @@ DeploymentSuccess.propTypes = {
|
|||||||
fetchStackEnvironment: React.PropTypes.func.isRequired,
|
fetchStackEnvironment: React.PropTypes.func.isRequired,
|
||||||
fetchStackResource: React.PropTypes.func.isRequired,
|
fetchStackResource: React.PropTypes.func.isRequired,
|
||||||
runPostDeploymentValidations: React.PropTypes.func.isRequired,
|
runPostDeploymentValidations: React.PropTypes.func.isRequired,
|
||||||
stack: ImmutablePropTypes.record.isRequired
|
stack: ImmutablePropTypes.record.isRequired,
|
||||||
|
stackResources: ImmutablePropTypes.map.isRequired,
|
||||||
|
stackResourcesLoaded: React.PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
@ -55,6 +55,22 @@ DataTableDataFieldCell.propTypes = {
|
|||||||
rowIndex: React.PropTypes.number
|
rowIndex: React.PropTypes.number
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const DataTableDateFieldCell = (props) => {
|
||||||
|
//TODO(jtomasek): Update this component to parse date and format it using React Intl's
|
||||||
|
// FormatedDate
|
||||||
|
const value = _.result(props.data[props.rowIndex], props.field);
|
||||||
|
return (
|
||||||
|
<DataTableCell {...props}>
|
||||||
|
{value}
|
||||||
|
</DataTableCell>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
DataTableDateFieldCell.propTypes = {
|
||||||
|
data: React.PropTypes.array.isRequired,
|
||||||
|
field: React.PropTypes.string.isRequired,
|
||||||
|
rowIndex: React.PropTypes.number
|
||||||
|
};
|
||||||
|
|
||||||
export class DataTableCheckBoxCell extends React.Component {
|
export class DataTableCheckBoxCell extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
let value = _.result(this.props.data[this.props.rowIndex], this.props.field);
|
let value = _.result(this.props.data[this.props.rowIndex], this.props.field);
|
||||||
|
@ -7,9 +7,9 @@ export default keyMirror({
|
|||||||
FETCH_RESOURCE_SUCCESS: null,
|
FETCH_RESOURCE_SUCCESS: null,
|
||||||
FETCH_RESOURCE_PENDING: null,
|
FETCH_RESOURCE_PENDING: null,
|
||||||
FETCH_RESOURCE_FAILED: null,
|
FETCH_RESOURCE_FAILED: null,
|
||||||
FETCH_STACK_PENDING: null,
|
FETCH_RESOURCES_PENDING: null,
|
||||||
FETCH_STACK_SUCCESS: null,
|
FETCH_RESOURCES_SUCCESS: null,
|
||||||
FETCH_STACK_FAILED: null,
|
FETCH_RESOURCES_FAILED: null,
|
||||||
FETCH_STACKS_PENDING: null,
|
FETCH_STACKS_PENDING: null,
|
||||||
FETCH_STACKS_SUCCESS: null,
|
FETCH_STACKS_SUCCESS: null,
|
||||||
FETCH_STACKS_FAILED: null
|
FETCH_STACKS_FAILED: null
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import { List, Map, Record } from 'immutable';
|
import { List, Map, OrderedMap, Record } from 'immutable';
|
||||||
|
|
||||||
export const StacksState = Record({
|
export const StacksState = Record({
|
||||||
isLoaded: false,
|
isLoaded: false,
|
||||||
isFetching: false,
|
isFetching: false,
|
||||||
|
isFetchingResources: false,
|
||||||
|
resourcesLoaded: false,
|
||||||
|
resources: OrderedMap(),
|
||||||
stacks: Map()
|
stacks: Map()
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -13,7 +16,6 @@ export const Stack = Record({
|
|||||||
environment: Map(),
|
environment: Map(),
|
||||||
id: undefined,
|
id: undefined,
|
||||||
parent: undefined,
|
parent: undefined,
|
||||||
resources: Map(),
|
|
||||||
stack_name: undefined,
|
stack_name: undefined,
|
||||||
stack_owner: undefined,
|
stack_owner: undefined,
|
||||||
stack_status: undefined,
|
stack_status: undefined,
|
||||||
|
@ -2,6 +2,7 @@ import { fromJS, Map } from 'immutable';
|
|||||||
|
|
||||||
import { Stack, StackResource, StacksState } from '../immutableRecords/stacks';
|
import { Stack, StackResource, StacksState } from '../immutableRecords/stacks';
|
||||||
import StacksConstants from '../constants/StacksConstants';
|
import StacksConstants from '../constants/StacksConstants';
|
||||||
|
import PlansConstants from '../constants/PlansConstants';
|
||||||
|
|
||||||
const initialState = new StacksState;
|
const initialState = new StacksState;
|
||||||
|
|
||||||
@ -23,19 +24,19 @@ export default function stacksReducer(state = initialState, action) {
|
|||||||
.set('isFetching', false)
|
.set('isFetching', false)
|
||||||
.set('stacks', Map());
|
.set('stacks', Map());
|
||||||
|
|
||||||
case StacksConstants.FETCH_STACK_PENDING:
|
case StacksConstants.FETCH_RESOURCES_PENDING:
|
||||||
return state.set('isFetching', true);
|
return state.set('isFetchingResources', true);
|
||||||
|
|
||||||
case StacksConstants.FETCH_STACK_SUCCESS: {
|
case StacksConstants.FETCH_RESOURCES_SUCCESS: {
|
||||||
const stack = new Stack(fromJS(action.payload))
|
return state.set('isFetchingResources', false)
|
||||||
.update('resources', resources => resources
|
.set('resourcesLoaded', true)
|
||||||
.map(resource => new StackResource(resource)));
|
.set('resources',
|
||||||
return state.set('isFetching', false)
|
fromJS(action.payload).map(resource => new StackResource(resource))
|
||||||
.mergeDeepIn(['stacks', action.payload.stack_name], stack);
|
.sortBy(resource => resource.updated_time));
|
||||||
}
|
}
|
||||||
|
|
||||||
case StacksConstants.FETCH_STACK_FAILED:
|
case StacksConstants.FETCH_RESOURCES_FAILED:
|
||||||
return state.set('isFetching', false);
|
return state.set('isFetchingResources', false);
|
||||||
|
|
||||||
case StacksConstants.FETCH_ENVIRONMENT_SUCCESS:
|
case StacksConstants.FETCH_ENVIRONMENT_SUCCESS:
|
||||||
return state.setIn(
|
return state.setIn(
|
||||||
@ -43,13 +44,12 @@ export default function stacksReducer(state = initialState, action) {
|
|||||||
fromJS(action.payload.environment));
|
fromJS(action.payload.environment));
|
||||||
|
|
||||||
case StacksConstants.FETCH_RESOURCE_SUCCESS:
|
case StacksConstants.FETCH_RESOURCE_SUCCESS:
|
||||||
if (state.stacks.get(action.payload.stack.stack_name)) {
|
return state.set('resourcesLoaded', true)
|
||||||
return state.setIn(
|
.setIn(['resources', action.payload.resource_name],
|
||||||
['stacks', action.payload.stack.stack_name, 'resources', action.payload.resourceName],
|
new StackResource(fromJS(action.payload)));
|
||||||
new StackResource(fromJS(action.payload.resource.resource))
|
|
||||||
);
|
case PlansConstants.PLAN_CHOSEN:
|
||||||
}
|
return initialState;
|
||||||
return state;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
|
@ -3,7 +3,8 @@ import { createSelector } from 'reselect';
|
|||||||
import { Stack } from '../immutableRecords/stacks';
|
import { Stack } from '../immutableRecords/stacks';
|
||||||
import { currentPlanNameSelector } from './plans';
|
import { currentPlanNameSelector } from './plans';
|
||||||
|
|
||||||
const stacksSelector = state => state.stacks.get('stacks');
|
const stacksSelector = state => state.stacks.stacks;
|
||||||
|
const stackResourcesSelector = state => state.stacks.resources;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the stack associated with currentPlanName
|
* Returns the stack associated with currentPlanName
|
||||||
@ -17,9 +18,25 @@ export const getCurrentStack = createSelector(
|
|||||||
* Returns a flag for the deployment progress of the current plan
|
* Returns a flag for the deployment progress of the current plan
|
||||||
* (true if the plan is currently being deployed, false it not).
|
* (true if the plan is currently being deployed, false it not).
|
||||||
*/
|
*/
|
||||||
export const getCurrentStackDeploymentProgress = createSelector(
|
export const getCurrentStackDeploymentInProgress = createSelector(
|
||||||
[stacksSelector, currentPlanNameSelector],
|
[stacksSelector, currentPlanNameSelector],
|
||||||
(stacks, currentPlanName) => {
|
(stacks, currentPlanName) => {
|
||||||
return stacks.get(currentPlanName, new Stack()).stack_status === 'CREATE_IN_PROGRESS';
|
return stacks.get(currentPlanName, new Stack()).stack_status === 'CREATE_IN_PROGRESS';
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns calculated percentage of deployment progress
|
||||||
|
*/
|
||||||
|
export const getCurrentStackDeploymentProgress = createSelector(
|
||||||
|
[stackResourcesSelector], (resources) => {
|
||||||
|
let allResources = resources.size;
|
||||||
|
if(allResources > 0) {
|
||||||
|
let completeResources = resources.filter(r => {
|
||||||
|
return r.resource_status === 'CREATE_COMPLETE';
|
||||||
|
}).size;
|
||||||
|
return Math.ceil(completeResources / allResources * 100);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
@ -32,8 +32,8 @@ class HeatApiService {
|
|||||||
return this.defaultRequest(`/stacks/${stackName}/${stackId}`);
|
return this.defaultRequest(`/stacks/${stackName}/${stackId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
getResources(stack) {
|
getResources(stackName, stackId) {
|
||||||
return this.defaultRequest(`/stacks/${stack.stack_name}/${stack.id}/resources`);
|
return this.defaultRequest(`/stacks/${stackName}/${stackId}/resources`);
|
||||||
}
|
}
|
||||||
|
|
||||||
getResource(stack, resourceName) {
|
getResource(stack, resourceName) {
|
||||||
|
Loading…
Reference in New Issue
Block a user