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 { StacksState, Stack } from '../../js/immutableRecords/stacks';
|
||||
@ -68,7 +68,7 @@ describe('stacksReducer state', () => {
|
||||
description: undefined,
|
||||
id: undefined,
|
||||
parent: undefined,
|
||||
resources: Map(),
|
||||
resources: OrderedMap(),
|
||||
stack_name: 'overcloud',
|
||||
stack_owner: undefined,
|
||||
stack_status: 'CREATE_COMPLETE',
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Map } from 'immutable';
|
||||
import matchers from 'jasmine-immutable-matchers';
|
||||
|
||||
import { getCurrentStackDeploymentProgress,
|
||||
import { getCurrentStackDeploymentInProgress,
|
||||
getCurrentStack } from '../../js/selectors/stacks';
|
||||
import { CurrentPlanState } from '../../js/immutableRecords/currentPlan';
|
||||
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', () => {
|
||||
const state = {
|
||||
stacks: new StacksState({
|
||||
@ -44,7 +44,7 @@ describe('stacks selectors', () => {
|
||||
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', () => {
|
||||
@ -59,7 +59,7 @@ describe('stacks selectors', () => {
|
||||
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', () => {
|
||||
@ -73,7 +73,7 @@ describe('stacks selectors', () => {
|
||||
currentplanname: 'overcloud'
|
||||
})
|
||||
};
|
||||
expect(getCurrentStackDeploymentProgress(state)).toBe(false);
|
||||
expect(getCurrentStackDeploymentInProgress(state)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -43,88 +43,75 @@ export default {
|
||||
};
|
||||
},
|
||||
|
||||
fetchStackPending() {
|
||||
fetchResourcesPending() {
|
||||
return {
|
||||
type: StacksConstants.FETCH_STACK_PENDING
|
||||
type: StacksConstants.FETCH_RESOURCES_PENDING
|
||||
};
|
||||
},
|
||||
|
||||
fetchStackSuccess(stack) {
|
||||
fetchResourcesSuccess(resources) {
|
||||
return {
|
||||
type: StacksConstants.FETCH_STACK_SUCCESS,
|
||||
payload: stack
|
||||
type: StacksConstants.FETCH_RESOURCES_SUCCESS,
|
||||
payload: resources
|
||||
};
|
||||
},
|
||||
|
||||
fetchStackFailed() {
|
||||
fetchResourcesFailed() {
|
||||
return {
|
||||
type: StacksConstants.FETCH_STACK_FAILED
|
||||
type: StacksConstants.FETCH_RESOURCES_FAILED
|
||||
};
|
||||
},
|
||||
|
||||
fetchStack(stackName, stackId) {
|
||||
fetchResources(stackName, stackId) {
|
||||
return (dispatch) => {
|
||||
dispatch(this.fetchStackPending());
|
||||
HeatApiService.getStack(stackName, stackId).then(({ stack }) => {
|
||||
return HeatApiService.getResources(stack).then(({ resources }) => {
|
||||
stack.resources = normalize(resources,
|
||||
arrayOf(stackResourceSchema)).entities.stackResources || {};
|
||||
dispatch(this.fetchStackSuccess(stack));
|
||||
});
|
||||
dispatch(this.fetchResourcesPending());
|
||||
HeatApiService.getResources(stackName, stackId).then(({ resources }) => {
|
||||
const res = normalize(resources,
|
||||
arrayOf(stackResourceSchema)).entities.stackResources || {};
|
||||
dispatch(this.fetchResourcesSuccess(res));
|
||||
}).catch((error) => {
|
||||
console.error('Error retrieving resources StackActions.fetchResources', error); //eslint-disable-line no-console
|
||||
let errorHandler = new HeatApiErrorHandler(error);
|
||||
errorHandler.errors.forEach((error) => {
|
||||
dispatch(NotificationActions.notify(error));
|
||||
});
|
||||
dispatch(this.fetchStackFailed(error));
|
||||
dispatch(this.fetchResourcesFailed(error));
|
||||
});
|
||||
};
|
||||
},
|
||||
|
||||
fetchResourceSuccess(stack, resourceName, resource) {
|
||||
fetchResourceSuccess(resource) {
|
||||
return {
|
||||
type: StacksConstants.FETCH_RESOURCE_SUCCESS,
|
||||
payload: {
|
||||
stack,
|
||||
resourceName,
|
||||
resource
|
||||
}
|
||||
payload: resource
|
||||
};
|
||||
},
|
||||
|
||||
fetchResourceFailed(stack, resourceName) {
|
||||
fetchResourceFailed(resourceName) {
|
||||
return {
|
||||
type: StacksConstants.FETCH_RESOURCE_FAILED,
|
||||
payload: {
|
||||
stack,
|
||||
resourceName
|
||||
}
|
||||
payload: resourceName
|
||||
};
|
||||
},
|
||||
|
||||
fetchResourcePending(stack, resourceName) {
|
||||
fetchResourcePending() {
|
||||
return {
|
||||
type: StacksConstants.FETCH_RESOURCE_PENDING,
|
||||
payload: {
|
||||
stack,
|
||||
resourceName
|
||||
}
|
||||
type: StacksConstants.FETCH_RESOURCE_PENDING
|
||||
};
|
||||
},
|
||||
|
||||
fetchResource(stack, resourceName) {
|
||||
return (dispatch) => {
|
||||
dispatch(this.fetchResourcePending(stack));
|
||||
HeatApiService.getResource(stack, resourceName).then((response) => {
|
||||
dispatch(this.fetchResourceSuccess(stack, resourceName, response));
|
||||
dispatch(this.fetchResourcePending());
|
||||
HeatApiService.getResource(stack, resourceName).then(({ resource }) => {
|
||||
dispatch(this.fetchResourceSuccess(resource));
|
||||
}).catch((error) => {
|
||||
console.error('Error retrieving resource StackActions.fetchResource', error); //eslint-disable-line no-console
|
||||
let errorHandler = new HeatApiErrorHandler(error);
|
||||
errorHandler.errors.forEach((error) => {
|
||||
dispatch(NotificationActions.notify(error));
|
||||
});
|
||||
dispatch(this.fetchResourceFailed(error));
|
||||
dispatch(this.fetchResourceFailed(resourceName));
|
||||
});
|
||||
};
|
||||
},
|
||||
|
@ -1,58 +1,28 @@
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { Link } from 'react-router';
|
||||
import React from 'react';
|
||||
|
||||
import BlankSlate from '../ui/BlankSlate';
|
||||
import InlineNotification from '../ui/InlineNotification';
|
||||
import Loader from '../ui/Loader';
|
||||
import { ModalPanelBackdrop,
|
||||
ModalPanel,
|
||||
ModalPanelHeader,
|
||||
ModalPanelBody,
|
||||
ModalPanelFooter } from '../ui/ModalPanel';
|
||||
|
||||
const DeploymentConfirmation = ({ allValidationsSuccessful,
|
||||
currentPlan,
|
||||
deployPlan,
|
||||
environmentSummary }) => {
|
||||
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">
|
||||
<BlankSlate iconClass="fa fa-cloud-upload"
|
||||
title={`Deploy Plan ${currentPlan.name}`}>
|
||||
<p><strong>Summary:</strong> {environmentSummary}</p>
|
||||
<ValidationsWarning allValidationsSuccessful={allValidationsSuccessful}/>
|
||||
<p>
|
||||
Are you sure you want to deploy this plan?
|
||||
</p>
|
||||
<DeployButton
|
||||
disabled={currentPlan.isRequestingPlanDeploy}
|
||||
deploy={deployPlan.bind(this, currentPlan.name)}
|
||||
isRequestingPlanDeploy={currentPlan.isRequestingPlanDeploy}/>
|
||||
</BlankSlate>
|
||||
</div>
|
||||
</ModalPanelBody>
|
||||
<ModalPanelFooter>
|
||||
<Link to="/deployment-plan"
|
||||
type="button"
|
||||
className="btn btn-default">
|
||||
Cancel
|
||||
</Link>
|
||||
</ModalPanelFooter>
|
||||
</ModalPanel>
|
||||
<div className="col-sm-12 deployment-summary">
|
||||
<BlankSlate iconClass="fa fa-cloud-upload"
|
||||
title={`Deploy Plan ${currentPlan.name}`}>
|
||||
<p><strong>Summary:</strong> {environmentSummary}</p>
|
||||
<ValidationsWarning allValidationsSuccessful={allValidationsSuccessful}/>
|
||||
<p>
|
||||
Are you sure you want to deploy this plan?
|
||||
</p>
|
||||
<DeployButton
|
||||
disabled={currentPlan.isRequestingPlanDeploy}
|
||||
deploy={deployPlan.bind(this, currentPlan.name)}
|
||||
isRequestingPlanDeploy={currentPlan.isRequestingPlanDeploy}/>
|
||||
</BlankSlate>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,52 +1,119 @@
|
||||
import { connect } from 'react-redux';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { Link } from 'react-router';
|
||||
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 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 { stackStates } from '../../constants/StacksConstants';
|
||||
import StacksActions from '../../actions/StacksActions';
|
||||
|
||||
const DeploymentDetail = ({ currentPlan,
|
||||
currentStack,
|
||||
deployPlan,
|
||||
environmentConfigurationSummary,
|
||||
allPreDeploymentValidationsSuccessful }) => {
|
||||
if (!currentStack || currentStack.stack_status === stackStates.DELETE_COMPLETE) {
|
||||
class DeploymentDetail extends React.Component {
|
||||
renderStatus() {
|
||||
const { allPreDeploymentValidationsSuccessful,
|
||||
currentPlan,
|
||||
currentStack,
|
||||
currentStackDeploymentProgress,
|
||||
currentStackResources,
|
||||
currentStackResourcesLoaded,
|
||||
deployPlan,
|
||||
environmentConfigurationSummary,
|
||||
fetchStackResources,
|
||||
stacksLoaded } = this.props;
|
||||
|
||||
if (!currentStack || currentStack.stack_status === stackStates.DELETE_COMPLETE) {
|
||||
return (
|
||||
<Loader loaded={stacksLoaded}
|
||||
content="Loading Stacks..."
|
||||
height={40}>
|
||||
<DeploymentConfirmation
|
||||
allValidationsSuccessful={allPreDeploymentValidationsSuccessful}
|
||||
currentPlan={currentPlan}
|
||||
deployPlan={deployPlan}
|
||||
environmentSummary={environmentConfigurationSummary}/>
|
||||
</Loader>
|
||||
);
|
||||
} else if (currentStack.stack_status.match(/PROGRESS/)) {
|
||||
return (
|
||||
<DeploymentProgress stack={currentStack}
|
||||
stackResources={currentStackResources}
|
||||
deploymentProgress={currentStackDeploymentProgress}
|
||||
stackResourcesLoaded={currentStackResourcesLoaded}
|
||||
fetchStackResources={fetchStackResources} />
|
||||
);
|
||||
} else if (currentStack.stack_status.match(/COMPLETE/)) {
|
||||
return (
|
||||
<DeploymentSuccess stack={currentStack}
|
||||
stackResources={currentStackResources}
|
||||
stackResourcesLoaded={currentStackResourcesLoaded}/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<DeploymentFailure fetchStackResources={fetchStackResources}
|
||||
stack={currentStack}
|
||||
stackResources={currentStackResources}
|
||||
stackResourcesLoaded={currentStackResourcesLoaded}
|
||||
planName={currentPlan.name}/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<DeploymentConfirmation
|
||||
allValidationsSuccessful={allPreDeploymentValidationsSuccessful}
|
||||
currentPlan={currentPlan}
|
||||
deployPlan={deployPlan}
|
||||
environmentSummary={environmentConfigurationSummary}/>
|
||||
);
|
||||
} else if (currentStack.stack_status.match(/PROGRESS/)) {
|
||||
return (
|
||||
// TODO(jtomasek): render component DeploymentProgress
|
||||
null
|
||||
);
|
||||
} else if (currentStack.stack_status.match(/COMPLETE/)) {
|
||||
return (
|
||||
// TODO(jtomasek): render component DeploymentSuccess
|
||||
null
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
// TODO(jtomasek): render component DeploymentFailure
|
||||
null
|
||||
<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 = {
|
||||
allPreDeploymentValidationsSuccessful: React.PropTypes.bool.isRequired,
|
||||
currentPlan: ImmutablePropTypes.record.isRequired,
|
||||
currentStack: ImmutablePropTypes.record,
|
||||
currentStackDeploymentProgress: React.PropTypes.number.isRequired,
|
||||
currentStackResources: ImmutablePropTypes.map,
|
||||
currentStackResourcesLoaded: React.PropTypes.bool.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) => {
|
||||
@ -54,13 +121,19 @@ const mapStateToProps = (state) => {
|
||||
allPreDeploymentValidationsSuccessful: allPreDeploymentValidationsSuccessful(state),
|
||||
currentPlan: getCurrentPlan(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) => {
|
||||
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 { stackStates } from '../../constants/StacksConstants';
|
||||
|
||||
export const DeployStep = ({ currentPlan, currentStack, deployPlan, fetchStack, fetchStackResource,
|
||||
fetchStackEnvironment, runPostDeploymentValidations,
|
||||
stacksLoaded }) => {
|
||||
export const DeployStep = ({ currentPlan, currentStack, currentStackResources,
|
||||
currentStackResourcesLoaded, currentStackDeploymentProgress,
|
||||
deployPlan, fetchStackResource, fetchStackEnvironment,
|
||||
runPostDeploymentValidations, stacksLoaded }) => {
|
||||
if (!currentStack || currentStack.stack_status === stackStates.DELETE_COMPLETE) {
|
||||
return (
|
||||
<Loader loaded={stacksLoaded}>
|
||||
@ -29,11 +30,13 @@ export const DeployStep = ({ currentPlan, currentStack, deployPlan, fetchStack,
|
||||
} else if (currentStack.stack_status.match(/PROGRESS/)) {
|
||||
return (
|
||||
<DeploymentProgress stack={currentStack}
|
||||
fetchStack={fetchStack} />
|
||||
deploymentProgress={currentStackDeploymentProgress}/>
|
||||
);
|
||||
} else if (currentStack.stack_status.match(/COMPLETE/)) {
|
||||
return (
|
||||
<DeploymentSuccess stack={currentStack}
|
||||
stackResources={currentStackResources}
|
||||
stackResourcesLoaded={currentStackResourcesLoaded}
|
||||
fetchStackResource={fetchStackResource}
|
||||
fetchStackEnvironment={fetchStackEnvironment}
|
||||
runPostDeploymentValidations={runPostDeploymentValidations}/>
|
||||
@ -48,8 +51,10 @@ export const DeployStep = ({ currentPlan, currentStack, deployPlan, fetchStack,
|
||||
DeployStep.propTypes = {
|
||||
currentPlan: ImmutablePropTypes.record.isRequired,
|
||||
currentStack: ImmutablePropTypes.record,
|
||||
currentStackDeploymentProgress: React.PropTypes.number.isRequired,
|
||||
currentStackResources: ImmutablePropTypes.map,
|
||||
currentStackResourcesLoaded: React.PropTypes.bool.isRequired,
|
||||
deployPlan: React.PropTypes.func.isRequired,
|
||||
fetchStack: React.PropTypes.func.isRequired,
|
||||
fetchStackEnvironment: React.PropTypes.func.isRequired,
|
||||
fetchStackResource: React.PropTypes.func.isRequired,
|
||||
runPostDeploymentValidations: React.PropTypes.func.isRequired,
|
||||
|
@ -4,7 +4,8 @@ import React from 'react';
|
||||
|
||||
import { getAllPlansButCurrent } from '../../selectors/plans';
|
||||
import { getCurrentStack,
|
||||
getCurrentStackDeploymentProgress } from '../../selectors/stacks';
|
||||
getCurrentStackDeploymentProgress,
|
||||
getCurrentStackDeploymentInProgress } from '../../selectors/stacks';
|
||||
import { getAvailableNodes, getUnassignedAvailableNodes } from '../../selectors/nodes';
|
||||
import { getEnvironmentConfigurationSummary } from '../../selectors/environmentConfiguration';
|
||||
import { getCurrentPlan } from '../../selectors/plans';
|
||||
@ -41,7 +42,10 @@ class DeploymentPlan extends React.Component {
|
||||
if (currentStack) {
|
||||
if (currentStack.stack_status.match(/PROGRESS/)) {
|
||||
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>
|
||||
<ol className="deployment-step-list">
|
||||
<DeploymentPlanStep title="Prepare Hardware"
|
||||
disabled={this.props.currentStackDeploymentProgress}>
|
||||
disabled={this.props.currentStackDeploymentInProgress}>
|
||||
<HardwareStep />
|
||||
</DeploymentPlanStep>
|
||||
<DeploymentPlanStep title="Specify Deployment Configuration"
|
||||
disabled={this.props.currentStackDeploymentProgress}>
|
||||
disabled={this.props.currentStackDeploymentInProgress}>
|
||||
<ConfigurePlanStep
|
||||
fetchEnvironmentConfiguration={this.props.fetchEnvironmentConfiguration}
|
||||
summary={this.props.environmentConfigurationSummary}
|
||||
@ -82,7 +86,7 @@ class DeploymentPlan extends React.Component {
|
||||
loaded={this.props.environmentConfigurationLoaded}/>
|
||||
</DeploymentPlanStep>
|
||||
<DeploymentPlanStep title="Configure Roles and Assign Nodes"
|
||||
disabled={this.props.currentStackDeploymentProgress}>
|
||||
disabled={this.props.currentStackDeploymentInProgress}>
|
||||
<RolesStep availableNodes={this.props.availableNodes}
|
||||
fetchNodes={this.props.fetchNodes}
|
||||
fetchRoles={this.props.fetchRoles}
|
||||
@ -96,8 +100,10 @@ class DeploymentPlan extends React.Component {
|
||||
<DeployStep
|
||||
currentPlan={this.props.currentPlan}
|
||||
currentStack={this.props.currentStack}
|
||||
currentStackResources={this.props.currentStackResources}
|
||||
currentStackResourcesLoaded={this.props.currentStackResourcesLoaded}
|
||||
currentStackDeploymentProgress={this.props.currentStackDeploymentProgress}
|
||||
deployPlan={this.props.deployPlan}
|
||||
fetchStack={this.props.fetchStack}
|
||||
fetchStackEnvironment={this.props.fetchStackEnvironment}
|
||||
fetchStackResource={this.props.fetchStackResource}
|
||||
runPostDeploymentValidations={
|
||||
@ -123,16 +129,19 @@ DeploymentPlan.propTypes = {
|
||||
choosePlan: React.PropTypes.func,
|
||||
currentPlan: 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,
|
||||
environmentConfigurationLoaded: React.PropTypes.bool,
|
||||
environmentConfigurationSummary: React.PropTypes.string,
|
||||
fetchEnvironmentConfiguration: React.PropTypes.func,
|
||||
fetchNodes: React.PropTypes.func,
|
||||
fetchRoles: React.PropTypes.func,
|
||||
fetchStack: React.PropTypes.func.isRequired,
|
||||
fetchStackEnvironment: React.PropTypes.func,
|
||||
fetchStackResource: React.PropTypes.func,
|
||||
fetchStackResources: React.PropTypes.func.isRequired,
|
||||
fetchStacks: React.PropTypes.func,
|
||||
hasPlans: React.PropTypes.bool,
|
||||
inactivePlans: ImmutablePropTypes.map,
|
||||
@ -152,6 +161,9 @@ export function mapStateToProps(state) {
|
||||
return {
|
||||
currentPlan: getCurrentPlan(state),
|
||||
currentStack: getCurrentStack(state),
|
||||
currentStackResources: state.stacks.resources,
|
||||
currentStackResourcesLoaded: state.stacks.resourcesLoaded,
|
||||
currentStackDeploymentInProgress: getCurrentStackDeploymentInProgress(state),
|
||||
currentStackDeploymentProgress: getCurrentStackDeploymentProgress(state),
|
||||
environmentConfigurationLoaded: state.environmentConfiguration.loaded,
|
||||
environmentConfigurationSummary: getEnvironmentConfigurationSummary(state),
|
||||
@ -178,7 +190,8 @@ function mapDispatchToProps(dispatch) {
|
||||
},
|
||||
fetchNodes: () => dispatch(NodesActions.fetchNodes()),
|
||||
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) =>
|
||||
dispatch(StacksActions.fetchResource(stack, resourceName)),
|
||||
fetchStacks: () => dispatch(StacksActions.fetchStacks()),
|
||||
|
@ -1,5 +1,6 @@
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router';
|
||||
|
||||
import { deploymentStatusMessages as statusMessages,
|
||||
stackStates } from '../../constants/StacksConstants';
|
||||
@ -7,23 +8,11 @@ import Loader from '../ui/Loader';
|
||||
import ProgressBar from '../ui/ProgressBar';
|
||||
|
||||
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() {
|
||||
const progress = this.calculateProgress();
|
||||
return (
|
||||
this.props.stack.stack_status === stackStates.CREATE_IN_PROGRESS ? (
|
||||
<ProgressBar value={progress}
|
||||
label={progress + '%'}
|
||||
<ProgressBar value={this.props.deploymentProgress}
|
||||
label={this.props.deploymentProgress + '%'}
|
||||
labelPosition="topRight"/>
|
||||
) : null
|
||||
);
|
||||
@ -36,6 +25,11 @@ export default class DeploymentProgress extends React.Component {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>
|
||||
Deployment is currently in progress. <Link to="/deployment-plan/deployment-detail">
|
||||
View detailed information
|
||||
</Link>
|
||||
</p>
|
||||
<div className="progress-description">
|
||||
<Loader loaded={false} content={statusMessage} inline/>
|
||||
</div>
|
||||
@ -46,6 +40,6 @@ export default class DeploymentProgress extends React.Component {
|
||||
}
|
||||
|
||||
DeploymentProgress.propTypes = {
|
||||
fetchStack: React.PropTypes.func.isRequired,
|
||||
deploymentProgress: React.PropTypes.number.isRequired,
|
||||
stack: ImmutablePropTypes.record.isRequired
|
||||
};
|
||||
|
@ -13,8 +13,7 @@ export default class DeploymentSuccess extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const loaded = !this.props.stack.resources.isEmpty();
|
||||
const ip = this.props.stack.resources.getIn([
|
||||
const ip = this.props.stackResources.getIn([
|
||||
'PublicVirtualIP', 'attributes', 'ip_address'
|
||||
]);
|
||||
|
||||
@ -31,7 +30,8 @@ export default class DeploymentSuccess extends React.Component {
|
||||
<p>{this.props.stack.stack_status_reason}</p>
|
||||
</InlineNotification>
|
||||
<h4>Overcloud information:</h4>
|
||||
<Loader loaded={loaded} content="Loading overcloud information...">
|
||||
<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>
|
||||
@ -46,5 +46,7 @@ DeploymentSuccess.propTypes = {
|
||||
fetchStackEnvironment: React.PropTypes.func.isRequired,
|
||||
fetchStackResource: 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
|
||||
};
|
||||
|
||||
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 {
|
||||
render() {
|
||||
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_PENDING: null,
|
||||
FETCH_RESOURCE_FAILED: null,
|
||||
FETCH_STACK_PENDING: null,
|
||||
FETCH_STACK_SUCCESS: null,
|
||||
FETCH_STACK_FAILED: null,
|
||||
FETCH_RESOURCES_PENDING: null,
|
||||
FETCH_RESOURCES_SUCCESS: null,
|
||||
FETCH_RESOURCES_FAILED: null,
|
||||
FETCH_STACKS_PENDING: null,
|
||||
FETCH_STACKS_SUCCESS: 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({
|
||||
isLoaded: false,
|
||||
isFetching: false,
|
||||
isFetchingResources: false,
|
||||
resourcesLoaded: false,
|
||||
resources: OrderedMap(),
|
||||
stacks: Map()
|
||||
});
|
||||
|
||||
@ -13,7 +16,6 @@ export const Stack = Record({
|
||||
environment: Map(),
|
||||
id: undefined,
|
||||
parent: undefined,
|
||||
resources: Map(),
|
||||
stack_name: undefined,
|
||||
stack_owner: undefined,
|
||||
stack_status: undefined,
|
||||
|
@ -2,6 +2,7 @@ import { fromJS, Map } from 'immutable';
|
||||
|
||||
import { Stack, StackResource, StacksState } from '../immutableRecords/stacks';
|
||||
import StacksConstants from '../constants/StacksConstants';
|
||||
import PlansConstants from '../constants/PlansConstants';
|
||||
|
||||
const initialState = new StacksState;
|
||||
|
||||
@ -23,19 +24,19 @@ export default function stacksReducer(state = initialState, action) {
|
||||
.set('isFetching', false)
|
||||
.set('stacks', Map());
|
||||
|
||||
case StacksConstants.FETCH_STACK_PENDING:
|
||||
return state.set('isFetching', true);
|
||||
case StacksConstants.FETCH_RESOURCES_PENDING:
|
||||
return state.set('isFetchingResources', true);
|
||||
|
||||
case StacksConstants.FETCH_STACK_SUCCESS: {
|
||||
const stack = new Stack(fromJS(action.payload))
|
||||
.update('resources', resources => resources
|
||||
.map(resource => new StackResource(resource)));
|
||||
return state.set('isFetching', false)
|
||||
.mergeDeepIn(['stacks', action.payload.stack_name], stack);
|
||||
case StacksConstants.FETCH_RESOURCES_SUCCESS: {
|
||||
return state.set('isFetchingResources', false)
|
||||
.set('resourcesLoaded', true)
|
||||
.set('resources',
|
||||
fromJS(action.payload).map(resource => new StackResource(resource))
|
||||
.sortBy(resource => resource.updated_time));
|
||||
}
|
||||
|
||||
case StacksConstants.FETCH_STACK_FAILED:
|
||||
return state.set('isFetching', false);
|
||||
case StacksConstants.FETCH_RESOURCES_FAILED:
|
||||
return state.set('isFetchingResources', false);
|
||||
|
||||
case StacksConstants.FETCH_ENVIRONMENT_SUCCESS:
|
||||
return state.setIn(
|
||||
@ -43,13 +44,12 @@ export default function stacksReducer(state = initialState, action) {
|
||||
fromJS(action.payload.environment));
|
||||
|
||||
case StacksConstants.FETCH_RESOURCE_SUCCESS:
|
||||
if (state.stacks.get(action.payload.stack.stack_name)) {
|
||||
return state.setIn(
|
||||
['stacks', action.payload.stack.stack_name, 'resources', action.payload.resourceName],
|
||||
new StackResource(fromJS(action.payload.resource.resource))
|
||||
);
|
||||
}
|
||||
return state;
|
||||
return state.set('resourcesLoaded', true)
|
||||
.setIn(['resources', action.payload.resource_name],
|
||||
new StackResource(fromJS(action.payload)));
|
||||
|
||||
case PlansConstants.PLAN_CHOSEN:
|
||||
return initialState;
|
||||
|
||||
default:
|
||||
return state;
|
||||
|
@ -3,7 +3,8 @@ import { createSelector } from 'reselect';
|
||||
import { Stack } from '../immutableRecords/stacks';
|
||||
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
|
||||
@ -17,9 +18,25 @@ export const getCurrentStack = createSelector(
|
||||
* Returns a flag for the deployment progress of the current plan
|
||||
* (true if the plan is currently being deployed, false it not).
|
||||
*/
|
||||
export const getCurrentStackDeploymentProgress = createSelector(
|
||||
export const getCurrentStackDeploymentInProgress = createSelector(
|
||||
[stacksSelector, currentPlanNameSelector],
|
||||
(stacks, currentPlanName) => {
|
||||
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}`);
|
||||
}
|
||||
|
||||
getResources(stack) {
|
||||
return this.defaultRequest(`/stacks/${stack.stack_name}/${stack.id}/resources`);
|
||||
getResources(stackName, stackId) {
|
||||
return this.defaultRequest(`/stacks/${stackName}/${stackId}/resources`);
|
||||
}
|
||||
|
||||
getResource(stack, resourceName) {
|
||||
|
Loading…
Reference in New Issue
Block a user