Plans routing refactor
* Plans are now top level navigation item * No plan needs to be active by default - fixes the problem when no plans exist * Plan is activated by visiting /plans/<planName> url * On application initialization (page refresh) currentPlanName in in application state is populated from local storage * Plans selectors have been updated to find plan and plan name based on comparing list of a plans to currentPlanName - this ensures that active plan always exist * When a plan is active, user can use breadcrumb navigation to get to plan management page and activate/manage plans * detectPlan action has been removed as it is no longer needed * Routing has been updated to match new structure Change-Id: Ia5d4b3444c27edafa5b9209314dead1625dde5ef
This commit is contained in:
parent
938119cc84
commit
a2d103b83c
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
features:
|
||||
- |
|
||||
'Plans' are now top level navigation item instead of deployment plan. No
|
||||
deployment plan needs to be active by default. Activating deployment plan
|
||||
is done by visiting plan via url.
|
|
@ -17,6 +17,7 @@
|
|||
import when from 'when';
|
||||
import { Map } from 'immutable';
|
||||
|
||||
import { InitialPlanState, Plan } from '../../js/immutableRecords/plans';
|
||||
import MistralApiService from '../../js/services/MistralApiService';
|
||||
import mockHistory from '../mocks/history';
|
||||
import { mockGetIntl } from './utils';
|
||||
|
@ -112,6 +113,14 @@ describe('nodesRegistrationFinished', () => {
|
|||
() => {},
|
||||
() => {
|
||||
return {
|
||||
plans: new InitialPlanState({
|
||||
plansLoaded: true,
|
||||
all: Map({
|
||||
testplan: new Plan({
|
||||
name: 'testplan'
|
||||
})
|
||||
})
|
||||
}),
|
||||
currentPlan: new CurrentPlanState({
|
||||
currentPlanName: 'testplan'
|
||||
})
|
||||
|
@ -163,6 +172,14 @@ describe('nodesRegistrationFinished', () => {
|
|||
() => {},
|
||||
() => {
|
||||
return {
|
||||
plans: new InitialPlanState({
|
||||
plansLoaded: true,
|
||||
all: Map({
|
||||
testplan: new Plan({
|
||||
name: 'testplan'
|
||||
})
|
||||
})
|
||||
}),
|
||||
currentPlan: new CurrentPlanState({
|
||||
currentPlanName: 'testplan'
|
||||
})
|
||||
|
|
|
@ -1,76 +0,0 @@
|
|||
/**
|
||||
* Copyright 2017 Red Hat Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License. You may obtain
|
||||
* a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { List, Map } from 'immutable';
|
||||
|
||||
import { InitialPlanState } from '../../../js/immutableRecords/plans';
|
||||
import { CurrentPlanState } from '../../../js/immutableRecords/currentPlan';
|
||||
import {
|
||||
ParametersDefaultState
|
||||
} from '../../../js/immutableRecords/parameters';
|
||||
import { RolesState } from '../../../js/immutableRecords/roles';
|
||||
import { StacksState } from '../../../js/immutableRecords/stacks';
|
||||
import {
|
||||
EnvironmentConfigurationState
|
||||
} from '../../../js/immutableRecords/environmentConfiguration';
|
||||
import {
|
||||
mapStateToProps
|
||||
} from '../../../js/components/deployment_plan/DeploymentPlan.js';
|
||||
|
||||
describe('DeploymentPlan mapStateToProps', () => {
|
||||
describe('hasPlans flag', () => {
|
||||
it('returns ``hasPlans`` as `false`', () => {
|
||||
let props = mapStateToProps({
|
||||
currentPlan: new CurrentPlanState(),
|
||||
parameters: new ParametersDefaultState(),
|
||||
plans: new InitialPlanState({ all: List() }),
|
||||
stacks: new StacksState(),
|
||||
roles: new RolesState({
|
||||
loaded: false,
|
||||
isFetching: false,
|
||||
roles: Map()
|
||||
}),
|
||||
nodes: Map({
|
||||
isFetching: false,
|
||||
all: Map()
|
||||
}),
|
||||
environmentConfiguration: new EnvironmentConfigurationState(),
|
||||
validations: Map()
|
||||
});
|
||||
expect(props.hasPlans).toBe(false);
|
||||
});
|
||||
it('returns ``hasPlans`` as `true`', () => {
|
||||
let props = mapStateToProps({
|
||||
currentPlan: new CurrentPlanState(),
|
||||
parameters: new ParametersDefaultState(),
|
||||
plans: new InitialPlanState({ all: List(['foo', 'bar']) }),
|
||||
stacks: new StacksState(),
|
||||
roles: new RolesState({
|
||||
loaded: false,
|
||||
isFetching: false,
|
||||
roles: Map()
|
||||
}),
|
||||
nodes: Map({
|
||||
isFetching: false,
|
||||
all: Map()
|
||||
}),
|
||||
environmentConfiguration: new EnvironmentConfigurationState(),
|
||||
validations: Map()
|
||||
});
|
||||
expect(props.hasPlans).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -26,10 +26,6 @@ describe('plansReducer state', () => {
|
|||
state = currentPlanReducer(undefined, { type: 'undefined-action' });
|
||||
});
|
||||
|
||||
it('`conflict` is undefined', () => {
|
||||
expect(state.get('conflict')).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('`currentPlanName` is undefined', () => {
|
||||
expect(state.get('currentPlanName')).not.toBeDefined();
|
||||
});
|
||||
|
|
|
@ -40,7 +40,6 @@ describe('plans selectors', () => {
|
|||
})
|
||||
}),
|
||||
currentPlan: new CurrentPlanState({
|
||||
conflict: undefined,
|
||||
currentPlanName: 'plan1'
|
||||
})
|
||||
};
|
||||
|
|
|
@ -22,6 +22,7 @@ import {
|
|||
getOvercloudInfo
|
||||
} from '../../js/selectors/stacks';
|
||||
import { CurrentPlanState } from '../../js/immutableRecords/currentPlan';
|
||||
import { InitialPlanState, Plan } from '../../js/immutableRecords/plans';
|
||||
import { Stack, StacksState } from '../../js/immutableRecords/stacks';
|
||||
|
||||
describe('stacks selectors', () => {
|
||||
|
@ -41,6 +42,14 @@ describe('stacks selectors', () => {
|
|||
})
|
||||
}),
|
||||
currentStackEnvironment: Map(),
|
||||
plans: new InitialPlanState({
|
||||
plansLoaded: true,
|
||||
all: Map({
|
||||
overcloud: new Plan({
|
||||
name: 'overcloud'
|
||||
})
|
||||
})
|
||||
}),
|
||||
currentPlan: new CurrentPlanState({
|
||||
currentPlanName: 'overcloud'
|
||||
})
|
||||
|
@ -118,6 +127,14 @@ describe('stacks selectors', () => {
|
|||
})
|
||||
})
|
||||
}),
|
||||
plans: new InitialPlanState({
|
||||
plansLoaded: true,
|
||||
all: Map({
|
||||
overcloud: new Plan({
|
||||
name: 'overcloud'
|
||||
})
|
||||
})
|
||||
}),
|
||||
currentPlan: new CurrentPlanState({
|
||||
currentPlanName: 'overcloud'
|
||||
})
|
||||
|
@ -145,6 +162,14 @@ describe('stacks selectors', () => {
|
|||
})
|
||||
})
|
||||
}),
|
||||
plans: new InitialPlanState({
|
||||
plansLoaded: true,
|
||||
all: Map({
|
||||
overcloud: new Plan({
|
||||
name: 'overcloud'
|
||||
})
|
||||
})
|
||||
}),
|
||||
currentPlan: new CurrentPlanState({
|
||||
currentPlanName: 'overcloud'
|
||||
})
|
||||
|
@ -166,6 +191,14 @@ describe('stacks selectors', () => {
|
|||
})
|
||||
})
|
||||
}),
|
||||
plans: new InitialPlanState({
|
||||
plansLoaded: true,
|
||||
all: Map({
|
||||
overcloud: new Plan({
|
||||
name: 'overcloud'
|
||||
})
|
||||
})
|
||||
}),
|
||||
currentPlan: new CurrentPlanState({
|
||||
currentplanname: 'overcloud'
|
||||
})
|
||||
|
@ -183,6 +216,14 @@ describe('stacks selectors', () => {
|
|||
})
|
||||
})
|
||||
}),
|
||||
plans: new InitialPlanState({
|
||||
plansLoaded: true,
|
||||
all: Map({
|
||||
overcloud: new Plan({
|
||||
name: 'overcloud'
|
||||
})
|
||||
})
|
||||
}),
|
||||
currentPlan: new CurrentPlanState({
|
||||
currentplanname: 'overcloud'
|
||||
})
|
||||
|
|
|
@ -23,6 +23,7 @@ import {
|
|||
FiltersInitialState
|
||||
} from '../../js/immutableRecords/filters';
|
||||
import { CurrentPlanState } from '../../js/immutableRecords/currentPlan';
|
||||
import { InitialPlanState, Plan } from '../../js/immutableRecords/plans';
|
||||
import {
|
||||
WorkflowExecution
|
||||
} from '../../js/immutableRecords/workflowExecutions';
|
||||
|
@ -32,8 +33,15 @@ describe(' validations selectors', () => {
|
|||
let state;
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
plans: new InitialPlanState({
|
||||
plansLoaded: true,
|
||||
all: Map({
|
||||
overcloud: new Plan({
|
||||
name: 'overcloud'
|
||||
})
|
||||
})
|
||||
}),
|
||||
currentPlan: new CurrentPlanState({
|
||||
conflict: undefined,
|
||||
currentPlanName: 'overcloud'
|
||||
}),
|
||||
filters: FiltersInitialState(),
|
||||
|
|
|
@ -14,83 +14,31 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { defineMessages } from 'react-intl';
|
||||
|
||||
import NotificationActions from '../actions/NotificationActions';
|
||||
import PlansConstants from '../constants/PlansConstants';
|
||||
import ValidationsActions from '../actions/ValidationsActions';
|
||||
|
||||
const messages = defineMessages({
|
||||
planActivatedNotificationTitle: {
|
||||
id: 'CurrentPlanActions.planActivatedNotificationTitle',
|
||||
defaultMessage: 'Plan Activated'
|
||||
},
|
||||
planActivatedNotificationMessage: {
|
||||
id: 'CurrentPlanActions.planActivatedNotificationMessage',
|
||||
defaultMessage: 'The plan {planName} was activated.'
|
||||
}
|
||||
});
|
||||
import { getPlans, getCurrentPlanName } from '../selectors/plans';
|
||||
|
||||
export default {
|
||||
detectPlan() {
|
||||
choosePlan(newPlanName) {
|
||||
return (dispatch, getState) => {
|
||||
let state = getState();
|
||||
let plans = state.plans.all.map(plan => plan.get('name'));
|
||||
let conflict;
|
||||
let currentPlanName = state.currentPlan.get('currentPlanName');
|
||||
let previousPlan = currentPlanName || getStoredPlan();
|
||||
// No plans present.
|
||||
if (plans.size < 1) {
|
||||
if (!previousPlan) {
|
||||
currentPlanName = undefined;
|
||||
const currentPlanName = getCurrentPlanName(getState());
|
||||
const plans = getPlans(getState());
|
||||
|
||||
if (plans.get(newPlanName)) {
|
||||
if (newPlanName !== currentPlanName) {
|
||||
storePlan(newPlanName);
|
||||
dispatch(this.planChosen(newPlanName));
|
||||
dispatch(
|
||||
ValidationsActions.runValidationGroups(
|
||||
['prep', 'pre-deployment'],
|
||||
newPlanName
|
||||
)
|
||||
);
|
||||
}
|
||||
} else if (!previousPlan) {
|
||||
// Plans present.
|
||||
// No previously chosen plan.
|
||||
currentPlanName = plans.first();
|
||||
} else if (!plans.includes(previousPlan)) {
|
||||
// Previously chosen plan doesn't exist any more.
|
||||
conflict = previousPlan;
|
||||
currentPlanName = plans.first();
|
||||
} else if (!currentPlanName && previousPlan) {
|
||||
// No plan in state, but in localStorage
|
||||
currentPlanName = previousPlan;
|
||||
} else {
|
||||
storePlan();
|
||||
dispatch(this.planChosen());
|
||||
}
|
||||
storePlan(currentPlanName);
|
||||
dispatch(this.planDetected(currentPlanName, conflict));
|
||||
};
|
||||
},
|
||||
|
||||
planDetected(currentPlanName, conflict) {
|
||||
return {
|
||||
type: PlansConstants.PLAN_DETECTED,
|
||||
payload: {
|
||||
currentPlanName: currentPlanName,
|
||||
conflict: conflict
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
choosePlan(planName) {
|
||||
return (dispatch, getState, { getIntl }) => {
|
||||
const { formatMessage } = getIntl(getState());
|
||||
dispatch(
|
||||
NotificationActions.notify({
|
||||
title: formatMessage(messages.planActivatedNotificationTitle),
|
||||
message: formatMessage(messages.planActivatedNotificationMessage, {
|
||||
planName: planName
|
||||
}),
|
||||
type: 'success'
|
||||
})
|
||||
);
|
||||
storePlan(planName);
|
||||
dispatch(this.planChosen(planName));
|
||||
dispatch(
|
||||
ValidationsActions.runValidationGroups(
|
||||
['prep', 'pre-deployment'],
|
||||
planName
|
||||
)
|
||||
);
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -104,17 +52,10 @@ export default {
|
|||
|
||||
function storePlan(name) {
|
||||
if (window && window.localStorage) {
|
||||
if (!name) {
|
||||
window.localStorage.removeItem('currentPlanName');
|
||||
} else {
|
||||
if (name) {
|
||||
window.localStorage.setItem('currentPlanName', name);
|
||||
} else {
|
||||
window.localStorage.removeItem('currentPlanName');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getStoredPlan() {
|
||||
if (window && window.localStorage) {
|
||||
return window.localStorage.getItem('currentPlanName');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ import { fromJS } from 'immutable';
|
|||
import { normalize, arrayOf } from 'normalizr';
|
||||
import when from 'when';
|
||||
|
||||
import CurrentPlanActions from '../actions/CurrentPlanActions';
|
||||
import logger from '../services/logger';
|
||||
import MistralApiService from '../services/MistralApiService';
|
||||
import MistralApiErrorHandler from '../services/MistralApiErrorHandler';
|
||||
|
@ -88,7 +87,6 @@ export default {
|
|||
.then(response => {
|
||||
let plans = JSON.parse(response.output).result || [];
|
||||
dispatch(this.receivePlans(plans));
|
||||
dispatch(CurrentPlanActions.detectPlan());
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error(
|
||||
|
@ -172,7 +170,7 @@ export default {
|
|||
this._uploadFilesToContainer(planName, fromJS(planFiles), dispatch)
|
||||
.then(() => {
|
||||
dispatch(this.updatePlanSuccess(planName));
|
||||
history.push('/plans');
|
||||
history.push('/plans/manage');
|
||||
dispatch(
|
||||
NotificationActions.notify({
|
||||
title: formatMessage(messages.planUpdatedNotificationTitle),
|
||||
|
@ -201,7 +199,7 @@ export default {
|
|||
SwiftApiService.uploadTarball(planName, file)
|
||||
.then(response => {
|
||||
dispatch(this.updatePlanSuccess(planName));
|
||||
history.push('/plans');
|
||||
history.push('/plans/manage');
|
||||
dispatch(
|
||||
NotificationActions.notify({
|
||||
title: formatMessage(messages.planUpdatedNotificationTitle),
|
||||
|
@ -350,7 +348,7 @@ export default {
|
|||
})
|
||||
);
|
||||
dispatch(this.fetchPlans());
|
||||
history.push('/plans');
|
||||
history.push('/plans/manage');
|
||||
} else {
|
||||
dispatch(
|
||||
this.createPlanFailed([{ title: 'Error', message: payload.message }])
|
||||
|
@ -439,7 +437,7 @@ export default {
|
|||
return (dispatch, getState, { getIntl }) => {
|
||||
const { formatMessage } = getIntl(getState());
|
||||
dispatch(this.deletePlanPending(planName));
|
||||
history.push('/plans');
|
||||
history.push('/plans/manage');
|
||||
MistralApiService.runAction(MistralConstants.PLAN_DELETE, {
|
||||
container: planName
|
||||
})
|
||||
|
@ -454,7 +452,6 @@ export default {
|
|||
type: 'success'
|
||||
})
|
||||
);
|
||||
dispatch(CurrentPlanActions.detectPlan());
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error(
|
||||
|
|
|
@ -18,6 +18,7 @@ import { defineMessages } from 'react-intl';
|
|||
import { normalize, arrayOf } from 'normalizr';
|
||||
import { Map } from 'immutable';
|
||||
|
||||
import { getCurrentPlanName } from '../selectors/plans';
|
||||
import RegisterNodesConstants from '../constants/RegisterNodesConstants';
|
||||
import MistralApiErrorHandler from '../services/MistralApiErrorHandler';
|
||||
import MistralApiService from '../services/MistralApiService';
|
||||
|
@ -137,7 +138,7 @@ export default {
|
|||
dispatch(
|
||||
ValidationsActions.runValidationGroups(
|
||||
['pre-introspection'],
|
||||
getState().currentPlan.currentPlanName
|
||||
getCurrentPlanName(getState())
|
||||
)
|
||||
);
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import React from 'react';
|
|||
import { Redirect, Route, Switch, withRouter } from 'react-router-dom';
|
||||
|
||||
import DeploymentPlan from './deployment_plan/DeploymentPlan';
|
||||
import { getCurrentPlanName } from '../selectors/plans';
|
||||
import Loader from './ui/Loader';
|
||||
import LoginActions from '../actions/LoginActions';
|
||||
import NavBar from './NavBar';
|
||||
|
@ -47,17 +48,10 @@ class AuthenticatedContent extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
currentPlanName,
|
||||
intl,
|
||||
logoutUser,
|
||||
noPlans,
|
||||
plansLoaded,
|
||||
user
|
||||
} = this.props;
|
||||
const { currentPlanName, intl, logoutUser, plansLoaded, user } = this.props;
|
||||
return (
|
||||
<Loader
|
||||
loaded={plansLoaded && (!!currentPlanName || noPlans)}
|
||||
loaded={plansLoaded}
|
||||
content={intl.formatMessage(messages.loadingDeployments)}
|
||||
global
|
||||
>
|
||||
|
@ -69,9 +63,11 @@ class AuthenticatedContent extends React.Component {
|
|||
<div className="col-sm-12 col-lg-9">
|
||||
<Switch>
|
||||
<Route path="/nodes" component={Nodes} />
|
||||
<Route path="/deployment-plan" component={DeploymentPlan} />
|
||||
<Route path="/plans" component={Plans} />
|
||||
<Redirect from="/" to="/deployment-plan" />
|
||||
<Route path="/plans/manage" component={Plans} />
|
||||
<Route path="/plans/:planName" component={DeploymentPlan} />
|
||||
{currentPlanName
|
||||
? <Redirect from="/" to={`/plans/${currentPlanName}`} />
|
||||
: <Redirect from="/" to="/plans/manage" />}
|
||||
</Switch>
|
||||
</div>
|
||||
<ValidationsList />
|
||||
|
@ -90,7 +86,6 @@ AuthenticatedContent.propTypes = {
|
|||
initializeZaqarConnection: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object,
|
||||
logoutUser: PropTypes.func.isRequired,
|
||||
noPlans: PropTypes.bool,
|
||||
plansLoaded: PropTypes.bool,
|
||||
user: ImmutablePropTypes.map
|
||||
};
|
||||
|
@ -105,8 +100,7 @@ const mapDispatchToProps = (dispatch, ownProps) => ({
|
|||
});
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
currentPlanName: state.currentPlan.currentPlanName,
|
||||
noPlans: state.plans.get('all').isEmpty(),
|
||||
currentPlanName: getCurrentPlanName(state),
|
||||
plansLoaded: state.plans.get('plansLoaded'),
|
||||
user: state.login.getIn(['token', 'user'])
|
||||
});
|
||||
|
|
|
@ -35,9 +35,9 @@ const messages = defineMessages({
|
|||
id: 'NavBar.logoutLink',
|
||||
defaultMessage: 'Logout'
|
||||
},
|
||||
deploymentPlanTab: {
|
||||
id: 'NavBar.deploymentPlanTab',
|
||||
defaultMessage: 'Deployment Plan'
|
||||
plansTab: {
|
||||
id: 'NavBar.plansTab',
|
||||
defaultMessage: 'Plans'
|
||||
},
|
||||
nodesTab: {
|
||||
id: 'Navbar.nodesTab',
|
||||
|
@ -108,8 +108,8 @@ export default class NavBar extends React.Component {
|
|||
</li>
|
||||
</ul>
|
||||
<ul className="nav navbar-nav navbar-primary">
|
||||
<NavTab to="/deployment-plan" id="NavBar__deploymentPlanTab">
|
||||
<FormattedMessage {...messages.deploymentPlanTab} />
|
||||
<NavTab to="/plans" id="NavBar__PlansTab">
|
||||
<FormattedMessage {...messages.plansTab} />
|
||||
</NavTab>
|
||||
<NavTab to="/nodes" id="NavBar__nodesTab">
|
||||
<FormattedMessage {...messages.nodesTab} />
|
||||
|
|
|
@ -28,7 +28,7 @@ import DeploymentConfirmation from './DeploymentConfirmation';
|
|||
import DeploymentProgress from './DeploymentProgress';
|
||||
import DeploymentSuccess from './DeploymentSuccess';
|
||||
import DeploymentFailure from './DeploymentFailure';
|
||||
import { getCurrentPlan } from '../../selectors/plans';
|
||||
import { getCurrentPlan, getCurrentPlanName } from '../../selectors/plans';
|
||||
import {
|
||||
getCurrentStack,
|
||||
getCurrentStackDeploymentProgress
|
||||
|
@ -69,6 +69,7 @@ class DeploymentDetail extends React.Component {
|
|||
const {
|
||||
allPreDeploymentValidationsSuccessful,
|
||||
currentPlan,
|
||||
currentPlanName,
|
||||
currentStack,
|
||||
currentStackDeploymentProgress,
|
||||
currentStackResources,
|
||||
|
@ -124,25 +125,30 @@ class DeploymentDetail extends React.Component {
|
|||
stack={currentStack}
|
||||
stackResources={currentStackResources}
|
||||
stackResourcesLoaded={currentStackResourcesLoaded}
|
||||
planName={currentPlan.name}
|
||||
planName={currentPlanName}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { currentPlanName } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<ModalPanelBackdrop />
|
||||
<ModalPanel>
|
||||
<ModalPanelHeader>
|
||||
<Link to="/deployment-plan" type="button" className="close">
|
||||
<Link
|
||||
to={`/plans/${currentPlanName}`}
|
||||
type="button"
|
||||
className="close"
|
||||
>
|
||||
<span aria-hidden="true" className="pficon pficon-close" />
|
||||
</Link>
|
||||
<h2 className="modal-title">
|
||||
<FormattedMessage
|
||||
{...messages.modalTitle}
|
||||
values={{ planName: this.props.currentPlan.name }}
|
||||
values={{ planName: currentPlanName }}
|
||||
/>
|
||||
</h2>
|
||||
</ModalPanelHeader>
|
||||
|
@ -151,7 +157,7 @@ class DeploymentDetail extends React.Component {
|
|||
</ModalPanelBody>
|
||||
<ModalPanelFooter>
|
||||
<Link
|
||||
to="/deployment-plan"
|
||||
to={`/plans/${currentPlanName}`}
|
||||
type="button"
|
||||
className="btn btn-default"
|
||||
>
|
||||
|
@ -167,6 +173,7 @@ class DeploymentDetail extends React.Component {
|
|||
DeploymentDetail.propTypes = {
|
||||
allPreDeploymentValidationsSuccessful: PropTypes.bool.isRequired,
|
||||
currentPlan: ImmutablePropTypes.record.isRequired,
|
||||
currentPlanName: PropTypes.string.isRequired,
|
||||
currentStack: ImmutablePropTypes.record,
|
||||
currentStackDeploymentProgress: PropTypes.number.isRequired,
|
||||
currentStackResources: ImmutablePropTypes.map,
|
||||
|
@ -185,6 +192,7 @@ const mapStateToProps = state => {
|
|||
state
|
||||
),
|
||||
currentPlan: getCurrentPlan(state),
|
||||
currentPlanName: getCurrentPlanName(state),
|
||||
currentStack: getCurrentStack(state),
|
||||
currentStackDeploymentProgress: getCurrentStackDeploymentProgress(state),
|
||||
currentStackResources: state.stacks.resources,
|
||||
|
|
|
@ -33,7 +33,7 @@ const ConfigurePlanStep = props => {
|
|||
<div>
|
||||
<DeploymentConfigurationSummary {...props} />
|
||||
|
||||
<Link to="/deployment-plan/configuration">
|
||||
<Link to={`/plans/${props.planName}/configuration`}>
|
||||
<FormattedMessage {...messages.editConfigurationLink} />
|
||||
</Link>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,385 @@
|
|||
/**
|
||||
* Copyright 2017 Red Hat Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License. You may obtain
|
||||
* a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { Link, Route, withRouter } from 'react-router-dom';
|
||||
|
||||
import DeploymentConfiguration from './DeploymentConfiguration';
|
||||
import DeploymentDetail from '../deployment/DeploymentDetail';
|
||||
import { getAllPlansButCurrent } from '../../selectors/plans';
|
||||
import {
|
||||
getCurrentStack,
|
||||
getCurrentStackDeploymentProgress,
|
||||
getCurrentStackDeploymentInProgress,
|
||||
getOvercloudInfo
|
||||
} from '../../selectors/stacks';
|
||||
import {
|
||||
getAvailableNodes,
|
||||
getAvailableNodesCountsByRole,
|
||||
getNodeCountParametersByRole,
|
||||
getTotalAssignedNodesCount
|
||||
} from '../../selectors/nodesAssignment';
|
||||
import {
|
||||
getEnvironmentConfigurationSummary
|
||||
} from '../../selectors/environmentConfiguration';
|
||||
import { getCurrentPlan } from '../../selectors/plans';
|
||||
import { getRoles } from '../../selectors/roles';
|
||||
import ConfigurePlanStep from './ConfigurePlanStep';
|
||||
import CurrentPlanActions from '../../actions/CurrentPlanActions';
|
||||
import { DeploymentPlanStep } from './DeploymentPlanStep';
|
||||
import DeployStep from './DeployStep';
|
||||
import EnvironmentConfigurationActions
|
||||
from '../../actions/EnvironmentConfigurationActions';
|
||||
import HardwareStep from './HardwareStep';
|
||||
import NodesActions from '../../actions/NodesActions';
|
||||
import NotificationActions from '../../actions/NotificationActions';
|
||||
import ParametersActions from '../../actions/ParametersActions';
|
||||
import PlansActions from '../../actions/PlansActions';
|
||||
import RoleDetail from '../roles/RoleDetail';
|
||||
import RolesStep from './RolesStep';
|
||||
import RolesActions from '../../actions/RolesActions';
|
||||
import StacksActions from '../../actions/StacksActions';
|
||||
import stackStates from '../../constants/StacksConstants';
|
||||
import ValidationsActions from '../../actions/ValidationsActions';
|
||||
|
||||
const messages = defineMessages({
|
||||
backToAllPlans: {
|
||||
id: 'CurrentPlan.backToAllPlans',
|
||||
defaultMessage: 'All Plans'
|
||||
},
|
||||
hardwareStepHeader: {
|
||||
id: 'CurrentPlan.hardwareStepHeader',
|
||||
defaultMessage: 'Prepare Hardware'
|
||||
},
|
||||
configureRolesStepHeader: {
|
||||
id: 'CurrentPlan.configureRolesStepHeader',
|
||||
defaultMessage: 'Configure Roles and Assign Nodes'
|
||||
},
|
||||
deploymentConfigurationStepHeader: {
|
||||
id: 'CurrentPlan.deploymentConfigurationStepHeader',
|
||||
defaultMessage: 'Specify Deployment Configuration'
|
||||
},
|
||||
deployStepHeader: {
|
||||
id: 'CurrentPlan.deployStepHeader',
|
||||
defaultMessage: 'Deploy'
|
||||
},
|
||||
hardwareStepTooltip: {
|
||||
id: 'CurrentPlan.hardwareStepTooltip',
|
||||
defaultMessage: 'This step registers and introspects your nodes. Registration involves ' +
|
||||
'defining the power management details of each node so that you so that the director can ' +
|
||||
'control them during the introspection and provisioning stages. After registration, you ' +
|
||||
'introspect the nodes, which identifies the hardware each node uses and builds a profile of ' +
|
||||
'each node. After registration and introspection, you can assign these nodes into specific ' +
|
||||
'roles in your overcloud.'
|
||||
},
|
||||
configurePlanStepTooltip: {
|
||||
id: 'CurrentPlan.configurePlanStepTooltip',
|
||||
defaultMessage: "This step allows you edit specific settings for the overcloud's network, " +
|
||||
'storage, and other certified plugins. Use this step to define your network isolation ' +
|
||||
'configuration and your backend storage settings.'
|
||||
},
|
||||
configureRolesStepTooltip: {
|
||||
id: 'CurrentPlan.configureRolesStepTooltip',
|
||||
defaultMessage: 'This step assigns and removes nodes from roles in your overcloud. On each ' +
|
||||
"role's selection dialog, you can tag available nodes into the role or untag nodes already " +
|
||||
'assigned to the role. Click "Assign Nodes" for a particular role to open the selection ' +
|
||||
'dialog and start assigning nodes. ' +
|
||||
'You can also customize role-specific settings in this step. For example, you can compose ' +
|
||||
'services on each role and customize specific parameters related to each role. Click the ' +
|
||||
'pencil icon in the top-right corner of each role to see these role-specific settings'
|
||||
},
|
||||
deployStepTooltip: {
|
||||
id: 'CurrentPlan.deploymentStepTooltip',
|
||||
defaultMessage: 'This step performs the deployment of the overcloud. Once the deployment ' +
|
||||
'begins, the director tracks the progress and provides a report of each completed, running, ' +
|
||||
'or failed step. When the deployment completes, the director displays the current overcloud ' +
|
||||
'status and login details, which you use to interact with your overcloud. Click "Deploy" to ' +
|
||||
'start the deployment.'
|
||||
}
|
||||
});
|
||||
|
||||
class CurrentPlan extends React.Component {
|
||||
componentDidMount() {
|
||||
this.props.fetchStacks();
|
||||
this.fetchParameters();
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (!nextProps.stacksLoaded) {
|
||||
this.props.fetchStacks();
|
||||
}
|
||||
if (nextProps.currentPlan !== this.props.currentPlan) {
|
||||
this.fetchParameters();
|
||||
}
|
||||
this.postDeploymentValidationsCheck(nextProps.currentStack);
|
||||
this.pollCurrentStack(nextProps.currentStack);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearTimeout(this.stackProgressTimeout);
|
||||
}
|
||||
|
||||
fetchParameters() {
|
||||
!this.props.isFetchingParameters &&
|
||||
this.props.fetchParameters(this.props.currentPlan.name);
|
||||
}
|
||||
|
||||
pollCurrentStack(currentStack) {
|
||||
if (currentStack) {
|
||||
if (currentStack.stack_status.match(/PROGRESS/)) {
|
||||
clearTimeout(this.stackProgressTimeout);
|
||||
this.stackProgressTimeout = setTimeout(() => {
|
||||
this.props.fetchStacks();
|
||||
this.props.fetchStackResources(currentStack);
|
||||
}, 20000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
postDeploymentValidationsCheck(nextStack) {
|
||||
const { currentStack, currentPlan } = this.props;
|
||||
const progressStates = [
|
||||
stackStates.UPDATE_IN_PROGRESS,
|
||||
stackStates.CREATE_IN_PROGRESS
|
||||
];
|
||||
const successStates = [
|
||||
stackStates.UPDATE_COMPLETE,
|
||||
stackStates.CREATE_COMPLETE
|
||||
];
|
||||
if (
|
||||
currentStack &&
|
||||
nextStack &&
|
||||
progressStates.includes(currentStack.stack_status) &&
|
||||
successStates.includes(nextStack.stack_status)
|
||||
) {
|
||||
this.props.runPostDeploymentValidations(currentPlan.name);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { intl: { formatMessage }, currentPlan } = this.props;
|
||||
|
||||
const currentPlanName = currentPlan.name;
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-sm-12">
|
||||
<ol className="breadcrumb">
|
||||
<li>
|
||||
<Link to="/plans/manage">
|
||||
<FormattedMessage {...messages.backToAllPlans} />
|
||||
</Link>
|
||||
</li>
|
||||
<li className="active">
|
||||
{currentPlanName}
|
||||
</li>
|
||||
</ol>
|
||||
<div className="page-header page-header-bleed-right">
|
||||
<h1>{currentPlanName}</h1>
|
||||
</div>
|
||||
<ol className="deployment-step-list">
|
||||
<DeploymentPlanStep
|
||||
title={formatMessage(messages.hardwareStepHeader)}
|
||||
disabled={this.props.currentStackDeploymentInProgress}
|
||||
tooltip={formatMessage(messages.hardwareStepTooltip)}
|
||||
>
|
||||
<HardwareStep />
|
||||
</DeploymentPlanStep>
|
||||
<DeploymentPlanStep
|
||||
title={formatMessage(messages.deploymentConfigurationStepHeader)}
|
||||
disabled={this.props.currentStackDeploymentInProgress}
|
||||
tooltip={formatMessage(messages.configurePlanStepTooltip)}
|
||||
>
|
||||
<ConfigurePlanStep
|
||||
fetchEnvironmentConfiguration={
|
||||
this.props.fetchEnvironmentConfiguration
|
||||
}
|
||||
summary={this.props.environmentConfigurationSummary}
|
||||
planName={currentPlanName}
|
||||
isFetching={this.props.isFetchingEnvironmentConfiguration}
|
||||
loaded={this.props.environmentConfigurationLoaded}
|
||||
/>
|
||||
</DeploymentPlanStep>
|
||||
<DeploymentPlanStep
|
||||
title={formatMessage(messages.configureRolesStepHeader)}
|
||||
disabled={this.props.currentStackDeploymentInProgress}
|
||||
tooltip={formatMessage(messages.configureRolesStepTooltip)}
|
||||
>
|
||||
<RolesStep
|
||||
availableNodesCount={this.props.availableNodes.size}
|
||||
availableNodesCountsByRole={
|
||||
this.props.availableNodesCountsByRole
|
||||
}
|
||||
currentPlanName={currentPlanName}
|
||||
nodeCountParametersByRole={this.props.nodeCountParametersByRole}
|
||||
fetchNodes={this.props.fetchNodes}
|
||||
fetchRoles={this.props.fetchRoles.bind(this, currentPlanName)}
|
||||
isFetchingNodes={this.props.isFetchingNodes}
|
||||
isFetchingRoles={this.props.isFetchingRoles}
|
||||
isFetchingParameters={this.props.isFetchingParameters}
|
||||
roles={this.props.roles}
|
||||
rolesLoaded={this.props.rolesLoaded}
|
||||
totalAssignedNodesCount={this.props.totalAssignedNodesCount}
|
||||
/>
|
||||
</DeploymentPlanStep>
|
||||
<DeploymentPlanStep
|
||||
title={formatMessage(messages.deployStepHeader)}
|
||||
tooltip={formatMessage(messages.deployStepTooltip)}
|
||||
>
|
||||
<DeployStep
|
||||
currentPlan={currentPlan}
|
||||
currentStack={this.props.currentStack}
|
||||
currentStackResources={this.props.currentStackResources}
|
||||
currentStackDeploymentProgress={
|
||||
this.props.currentStackDeploymentProgress
|
||||
}
|
||||
deleteStack={this.props.deleteStack}
|
||||
deployPlan={this.props.deployPlan}
|
||||
fetchStackEnvironment={this.props.fetchStackEnvironment}
|
||||
fetchStackResource={this.props.fetchStackResource}
|
||||
overcloudInfo={this.props.overcloudInfo}
|
||||
isRequestingStackDelete={this.props.isRequestingStackDelete}
|
||||
stacksLoaded={this.props.stacksLoaded}
|
||||
/>
|
||||
</DeploymentPlanStep>
|
||||
</ol>
|
||||
</div>
|
||||
<Route
|
||||
path="/plans/:planName/configuration"
|
||||
component={DeploymentConfiguration}
|
||||
/>
|
||||
<Route
|
||||
path="/plans/:planName/roles/:roleIdentifier"
|
||||
component={RoleDetail}
|
||||
/>
|
||||
<Route
|
||||
path="/plans/:planName/deployment-detail"
|
||||
component={DeploymentDetail}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
CurrentPlan.propTypes = {
|
||||
availableNodes: ImmutablePropTypes.map,
|
||||
availableNodesCountsByRole: ImmutablePropTypes.map.isRequired,
|
||||
choosePlan: PropTypes.func,
|
||||
currentPlan: ImmutablePropTypes.record,
|
||||
currentStack: ImmutablePropTypes.record,
|
||||
currentStackDeploymentInProgress: PropTypes.bool,
|
||||
currentStackDeploymentProgress: PropTypes.number.isRequired,
|
||||
currentStackResources: ImmutablePropTypes.map,
|
||||
deleteStack: PropTypes.func,
|
||||
deployPlan: PropTypes.func,
|
||||
environmentConfigurationLoaded: PropTypes.bool,
|
||||
environmentConfigurationSummary: PropTypes.string,
|
||||
fetchEnvironmentConfiguration: PropTypes.func,
|
||||
fetchNodes: PropTypes.func,
|
||||
fetchParameters: PropTypes.func,
|
||||
fetchRoles: PropTypes.func,
|
||||
fetchStackEnvironment: PropTypes.func,
|
||||
fetchStackResource: PropTypes.func,
|
||||
fetchStackResources: PropTypes.func.isRequired,
|
||||
fetchStacks: PropTypes.func,
|
||||
inactivePlans: ImmutablePropTypes.map,
|
||||
intl: PropTypes.object,
|
||||
isFetchingEnvironmentConfiguration: PropTypes.bool,
|
||||
isFetchingNodes: PropTypes.bool,
|
||||
isFetchingParameters: PropTypes.bool,
|
||||
isFetchingRoles: PropTypes.bool,
|
||||
isRequestingStackDelete: PropTypes.bool,
|
||||
nodeCountParametersByRole: ImmutablePropTypes.map,
|
||||
notify: PropTypes.func,
|
||||
overcloudInfo: ImmutablePropTypes.map.isRequired,
|
||||
roles: ImmutablePropTypes.map,
|
||||
rolesLoaded: PropTypes.bool,
|
||||
route: PropTypes.object,
|
||||
runPostDeploymentValidations: PropTypes.func.isRequired,
|
||||
stacksLoaded: PropTypes.bool.isRequired,
|
||||
totalAssignedNodesCount: PropTypes.number.isRequired
|
||||
};
|
||||
|
||||
export function mapStateToProps(state, props) {
|
||||
return {
|
||||
availableNodesCountsByRole: getAvailableNodesCountsByRole(state),
|
||||
nodeCountParametersByRole: getNodeCountParametersByRole(state),
|
||||
availableNodes: getAvailableNodes(state),
|
||||
currentPlan: getCurrentPlan(state),
|
||||
currentStack: getCurrentStack(state),
|
||||
currentStackResources: state.stacks.resources,
|
||||
currentStackDeploymentInProgress: getCurrentStackDeploymentInProgress(
|
||||
state
|
||||
),
|
||||
currentStackDeploymentProgress: getCurrentStackDeploymentProgress(state),
|
||||
environmentConfigurationLoaded: state.environmentConfiguration.loaded,
|
||||
environmentConfigurationSummary: getEnvironmentConfigurationSummary(state),
|
||||
isFetchingEnvironmentConfiguration: state.environmentConfiguration
|
||||
.isFetching,
|
||||
isFetchingNodes: state.nodes.get('isFetching'),
|
||||
isFetchingParameters: state.parameters.isFetching,
|
||||
isFetchingRoles: state.roles.get('isFetching'),
|
||||
overcloudInfo: getOvercloudInfo(state),
|
||||
isRequestingStackDelete: state.stacks.get('isRequestingStackDelete'),
|
||||
inactivePlans: getAllPlansButCurrent(state),
|
||||
roles: getRoles(state),
|
||||
rolesLoaded: state.roles.get('loaded'),
|
||||
stacksLoaded: state.stacks.get('isLoaded'),
|
||||
totalAssignedNodesCount: getTotalAssignedNodesCount(state)
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
choosePlan: planName => dispatch(CurrentPlanActions.choosePlan(planName)),
|
||||
deleteStack: (stackName, stackId) => {
|
||||
dispatch(StacksActions.deleteStack(stackName, stackId));
|
||||
},
|
||||
deployPlan: planName => dispatch(PlansActions.deployPlan(planName)),
|
||||
fetchStackEnvironment: stack =>
|
||||
dispatch(StacksActions.fetchEnvironment(stack)),
|
||||
fetchEnvironmentConfiguration: (planName, parentPath) => {
|
||||
dispatch(
|
||||
EnvironmentConfigurationActions.fetchEnvironmentConfiguration(
|
||||
planName,
|
||||
parentPath
|
||||
)
|
||||
);
|
||||
},
|
||||
fetchNodes: () => dispatch(NodesActions.fetchNodes()),
|
||||
fetchParameters: planName =>
|
||||
dispatch(ParametersActions.fetchParameters(planName)),
|
||||
fetchRoles: planName => dispatch(RolesActions.fetchRoles(planName)),
|
||||
fetchStackResources: stack =>
|
||||
dispatch(StacksActions.fetchResources(stack.stack_name, stack.id)),
|
||||
fetchStackResource: (stack, resourceName) =>
|
||||
dispatch(StacksActions.fetchResource(stack, resourceName)),
|
||||
fetchStacks: () => dispatch(StacksActions.fetchStacks()),
|
||||
notify: notification => dispatch(NotificationActions.notify(notification)),
|
||||
runPostDeploymentValidations: planName => {
|
||||
dispatch(
|
||||
ValidationsActions.runValidationGroups(['post-deployment'], planName)
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default injectIntl(
|
||||
withRouter(connect(mapStateToProps, mapDispatchToProps)(CurrentPlan))
|
||||
);
|
|
@ -60,7 +60,7 @@ export const DeployStep = ({
|
|||
<Link
|
||||
className="link btn btn-primary btn-lg"
|
||||
disabled={currentPlan.isRequestingPlanDeploy}
|
||||
to="/deployment-plan/deployment-detail"
|
||||
to={`/plans/${currentPlan.name}/deployment-detail`}
|
||||
>
|
||||
<Loader
|
||||
loaded={!currentPlan.isRequestingPlanDeploy}
|
||||
|
@ -78,6 +78,7 @@ export const DeployStep = ({
|
|||
} else if (currentStack.stack_status.match(/PROGRESS/)) {
|
||||
return (
|
||||
<DeploymentProgress
|
||||
currentPlanName={currentPlan.name}
|
||||
stack={currentStack}
|
||||
isRequestingStackDelete={isRequestingStackDelete}
|
||||
deleteStack={deleteStack}
|
||||
|
@ -99,6 +100,7 @@ export const DeployStep = ({
|
|||
} else {
|
||||
return (
|
||||
<DeploymentFailure
|
||||
currentPlanName={currentPlan.name}
|
||||
deleteStack={deleteStack}
|
||||
isRequestingStackDelete={isRequestingStackDelete}
|
||||
stack={currentStack}
|
||||
|
|
|
@ -43,11 +43,15 @@ const messages = defineMessages({
|
|||
|
||||
class DeploymentConfiguration extends React.Component {
|
||||
render() {
|
||||
const { location } = this.props;
|
||||
const { location, match } = this.props;
|
||||
return (
|
||||
<Modal dialogClasses="modal-xl">
|
||||
<div className="modal-header">
|
||||
<Link to="/deployment-plan" type="button" className="close">
|
||||
<Link
|
||||
to={`/plans/${match.params.planName}`}
|
||||
type="button"
|
||||
className="close"
|
||||
>
|
||||
<span aria-hidden="true" className="pficon pficon-close" />
|
||||
</Link>
|
||||
<h4 className="modal-title">
|
||||
|
@ -56,34 +60,26 @@ class DeploymentConfiguration extends React.Component {
|
|||
</div>
|
||||
|
||||
<ul className="nav nav-tabs">
|
||||
<NavTab
|
||||
location={location}
|
||||
to="/deployment-plan/configuration/environment"
|
||||
>
|
||||
<NavTab to={`${match.url}/environment`}>
|
||||
<FormattedMessage {...messages.overallSettings} />
|
||||
</NavTab>
|
||||
<NavTab
|
||||
location={location}
|
||||
to="/deployment-plan/configuration/parameters"
|
||||
>
|
||||
<NavTab to={`${match.url}/parameters`}>
|
||||
<FormattedMessage {...messages.parameters} />
|
||||
</NavTab>
|
||||
</ul>
|
||||
|
||||
<Switch location={location}>
|
||||
<Route
|
||||
location={location}
|
||||
path="/deployment-plan/configuration/environment"
|
||||
path="/plans/:planName/configuration/environment"
|
||||
component={EnvironmentConfiguration}
|
||||
/>
|
||||
<Route
|
||||
location={location}
|
||||
path="/deployment-plan/configuration/parameters"
|
||||
path="/plans/:planName/configuration/parameters"
|
||||
component={Parameters}
|
||||
/>
|
||||
<Redirect
|
||||
from="/deployment-plan/configuration"
|
||||
to="/deployment-plan/configuration/environment"
|
||||
from="/plans/:planName/configuration"
|
||||
to={`${match.url}/environment`}
|
||||
/>
|
||||
</Switch>
|
||||
</Modal>
|
||||
|
@ -91,7 +87,8 @@ class DeploymentConfiguration extends React.Component {
|
|||
}
|
||||
}
|
||||
DeploymentConfiguration.propTypes = {
|
||||
location: PropTypes.object.isRequired
|
||||
location: PropTypes.object.isRequired,
|
||||
match: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default checkRunningDeployment(DeploymentConfiguration);
|
||||
|
|
|
@ -41,29 +41,33 @@ const messages = defineMessages({
|
|||
|
||||
class DeploymentFailure extends React.Component {
|
||||
render() {
|
||||
const { formatMessage } = this.props.intl;
|
||||
const status = formatMessage(
|
||||
deploymentStatusMessages[this.props.stack.stack_status]
|
||||
);
|
||||
const {
|
||||
currentPlanName,
|
||||
deleteStack,
|
||||
intl: { formatMessage },
|
||||
isRequestingStackDelete,
|
||||
stack
|
||||
} = this.props;
|
||||
const status = formatMessage(deploymentStatusMessages[stack.stack_status]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<InlineNotification type="error" title={status}>
|
||||
<p>
|
||||
{this.props.stack.stack_status_reason}
|
||||
{stack.stack_status_reason}
|
||||
{' '}
|
||||
<Link to="/deployment-plan/deployment-detail">
|
||||
<Link to={`/plans/${currentPlanName}/deployment-detail`}>
|
||||
<FormattedMessage {...messages.moreDetails} />
|
||||
</Link>
|
||||
</p>
|
||||
</InlineNotification>
|
||||
<DeleteStackButton
|
||||
content={formatMessage(messages.deleteDeployment)}
|
||||
deleteStack={this.props.deleteStack}
|
||||
disabled={this.props.isRequestingStackDelete}
|
||||
loaded={!this.props.isRequestingStackDelete}
|
||||
deleteStack={deleteStack}
|
||||
disabled={isRequestingStackDelete}
|
||||
loaded={!isRequestingStackDelete}
|
||||
loaderContent={formatMessage(messages.requestingDeletion)}
|
||||
stack={this.props.stack}
|
||||
stack={stack}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -71,6 +75,7 @@ class DeploymentFailure extends React.Component {
|
|||
}
|
||||
|
||||
DeploymentFailure.propTypes = {
|
||||
currentPlanName: PropTypes.string.isRequired,
|
||||
deleteStack: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object,
|
||||
isRequestingStackDelete: PropTypes.bool,
|
||||
|
|
|
@ -15,380 +15,36 @@
|
|||
*/
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { Route } from 'react-router-dom';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import DeploymentConfiguration from './DeploymentConfiguration';
|
||||
import DeploymentDetail from '../deployment/DeploymentDetail';
|
||||
import { getAllPlansButCurrent } from '../../selectors/plans';
|
||||
import {
|
||||
getCurrentStack,
|
||||
getCurrentStackDeploymentProgress,
|
||||
getCurrentStackDeploymentInProgress,
|
||||
getOvercloudInfo
|
||||
} from '../../selectors/stacks';
|
||||
import {
|
||||
getAvailableNodes,
|
||||
getAvailableNodesCountsByRole,
|
||||
getNodeCountParametersByRole,
|
||||
getTotalAssignedNodesCount
|
||||
} from '../../selectors/nodesAssignment';
|
||||
import {
|
||||
getEnvironmentConfigurationSummary
|
||||
} from '../../selectors/environmentConfiguration';
|
||||
import { getCurrentPlan } from '../../selectors/plans';
|
||||
import { getRoles } from '../../selectors/roles';
|
||||
import ConfigurePlanStep from './ConfigurePlanStep';
|
||||
import CurrentPlanActions from '../../actions/CurrentPlanActions';
|
||||
import { DeploymentPlanStep } from './DeploymentPlanStep';
|
||||
import DeployStep from './DeployStep';
|
||||
import EnvironmentConfigurationActions
|
||||
from '../../actions/EnvironmentConfigurationActions';
|
||||
import HardwareStep from './HardwareStep';
|
||||
import PlansDropdown from './PlansDropdown';
|
||||
import NodesActions from '../../actions/NodesActions';
|
||||
import NoPlans from './NoPlans';
|
||||
import NotificationActions from '../../actions/NotificationActions';
|
||||
import ParametersActions from '../../actions/ParametersActions';
|
||||
import PlansActions from '../../actions/PlansActions';
|
||||
import RoleDetail from '../roles/RoleDetail';
|
||||
import RolesStep from './RolesStep';
|
||||
import RolesActions from '../../actions/RolesActions';
|
||||
import StacksActions from '../../actions/StacksActions';
|
||||
import stackStates from '../../constants/StacksConstants';
|
||||
import ValidationsActions from '../../actions/ValidationsActions';
|
||||
import CurrentPlan from './CurrentPlan';
|
||||
import { getPlans } from '../../selectors/plans';
|
||||
|
||||
const messages = defineMessages({
|
||||
hardwareStepHeader: {
|
||||
id: 'DeploymentPlan.hardwareStepHeader',
|
||||
defaultMessage: 'Prepare Hardware'
|
||||
},
|
||||
configureRolesStepHeader: {
|
||||
id: 'DeploymentPlan.configureRolesStepHeader',
|
||||
defaultMessage: 'Configure Roles and Assign Nodes'
|
||||
},
|
||||
deploymentConfigurationStepHeader: {
|
||||
id: 'DeploymentPlan.deploymentConfigurationStepHeader',
|
||||
defaultMessage: 'Specify Deployment Configuration'
|
||||
},
|
||||
deployStepHeader: {
|
||||
id: 'DeploymentPlan.deployStepHeader',
|
||||
defaultMessage: 'Deploy'
|
||||
},
|
||||
hardwareStepTooltip: {
|
||||
id: 'DeploymentPlan.hardwareStepTooltip',
|
||||
defaultMessage: 'This step registers and introspects your nodes. Registration involves ' +
|
||||
'defining the power management details of each node so that you so that the director can ' +
|
||||
'control them during the introspection and provisioning stages. After registration, you ' +
|
||||
'introspect the nodes, which identifies the hardware each node uses and builds a profile of ' +
|
||||
'each node. After registration and introspection, you can assign these nodes into specific ' +
|
||||
'roles in your overcloud.'
|
||||
},
|
||||
configurePlanStepTooltip: {
|
||||
id: 'DeploymentPlan.configurePlanStepTooltip',
|
||||
defaultMessage: "This step allows you edit specific settings for the overcloud's network, " +
|
||||
'storage, and other certified plugins. Use this step to define your network isolation ' +
|
||||
'configuration and your backend storage settings.'
|
||||
},
|
||||
configureRolesStepTooltip: {
|
||||
id: 'DeploymentPlan.configureRolesStepTooltip',
|
||||
defaultMessage: 'This step assigns and removes nodes from roles in your overcloud. On each ' +
|
||||
"role's selection dialog, you can tag available nodes into the role or untag nodes already " +
|
||||
'assigned to the role. Click "Assign Nodes" for a particular role to open the selection ' +
|
||||
'dialog and start assigning nodes. ' +
|
||||
'You can also customize role-specific settings in this step. For example, you can compose ' +
|
||||
'services on each role and customize specific parameters related to each role. Click the ' +
|
||||
'pencil icon in the top-right corner of each role to see these role-specific settings'
|
||||
},
|
||||
deployStepTooltip: {
|
||||
id: 'DeploymentPlan.deploymentStepTooltip',
|
||||
defaultMessage: 'This step performs the deployment of the overcloud. Once the deployment ' +
|
||||
'begins, the director tracks the progress and provides a report of each completed, running, ' +
|
||||
'or failed step. When the deployment completes, the director displays the current overcloud ' +
|
||||
'status and login details, which you use to interact with your overcloud. Click "Deploy" to ' +
|
||||
'start the deployment.'
|
||||
}
|
||||
});
|
||||
|
||||
class DeploymentPlan extends React.Component {
|
||||
componentDidMount() {
|
||||
this.props.fetchStacks();
|
||||
this.fetchParameters();
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (!nextProps.stacksLoaded) {
|
||||
this.props.fetchStacks();
|
||||
}
|
||||
if (nextProps.currentPlan !== this.props.currentPlan) {
|
||||
this.fetchParameters();
|
||||
}
|
||||
this.postDeploymentValidationsCheck(nextProps.currentStack);
|
||||
this.pollCurrentStack(nextProps.currentStack);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearTimeout(this.stackProgressTimeout);
|
||||
}
|
||||
|
||||
fetchParameters() {
|
||||
!this.props.isFetchingParameters &&
|
||||
this.props.fetchParameters(this.props.currentPlan.name);
|
||||
}
|
||||
|
||||
pollCurrentStack(currentStack) {
|
||||
if (currentStack) {
|
||||
if (currentStack.stack_status.match(/PROGRESS/)) {
|
||||
clearTimeout(this.stackProgressTimeout);
|
||||
this.stackProgressTimeout = setTimeout(() => {
|
||||
this.props.fetchStacks();
|
||||
this.props.fetchStackResources(currentStack);
|
||||
}, 20000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
postDeploymentValidationsCheck(nextStack) {
|
||||
const { currentStack, currentPlan } = this.props;
|
||||
const progressStates = [
|
||||
stackStates.UPDATE_IN_PROGRESS,
|
||||
stackStates.CREATE_IN_PROGRESS
|
||||
];
|
||||
const successStates = [
|
||||
stackStates.UPDATE_COMPLETE,
|
||||
stackStates.CREATE_COMPLETE
|
||||
];
|
||||
if (
|
||||
currentStack &&
|
||||
nextStack &&
|
||||
progressStates.includes(currentStack.stack_status) &&
|
||||
successStates.includes(nextStack.stack_status)
|
||||
) {
|
||||
this.props.runPostDeploymentValidations(currentPlan.name);
|
||||
}
|
||||
class DeploymentPlan extends Component {
|
||||
componentWillMount() {
|
||||
this.props.choosePlan(this.props.match.params.planName);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { formatMessage } = this.props.intl;
|
||||
const currentPlanName = this.props.hasPlans
|
||||
? this.props.currentPlan.name
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<div className="row">
|
||||
{this.props.hasPlans
|
||||
? <div className="col-sm-12">
|
||||
<div className="page-header page-header-bleed-right">
|
||||
<h1>
|
||||
{currentPlanName}
|
||||
<PlansDropdown
|
||||
currentPlanName={currentPlanName}
|
||||
plans={this.props.inactivePlans}
|
||||
choosePlan={this.props.choosePlan}
|
||||
/>
|
||||
</h1>
|
||||
</div>
|
||||
<ol className="deployment-step-list">
|
||||
<DeploymentPlanStep
|
||||
title={formatMessage(messages.hardwareStepHeader)}
|
||||
disabled={this.props.currentStackDeploymentInProgress}
|
||||
tooltip={formatMessage(messages.hardwareStepTooltip)}
|
||||
>
|
||||
<HardwareStep />
|
||||
</DeploymentPlanStep>
|
||||
<DeploymentPlanStep
|
||||
title={formatMessage(
|
||||
messages.deploymentConfigurationStepHeader
|
||||
)}
|
||||
disabled={this.props.currentStackDeploymentInProgress}
|
||||
tooltip={formatMessage(messages.configurePlanStepTooltip)}
|
||||
>
|
||||
<ConfigurePlanStep
|
||||
fetchEnvironmentConfiguration={
|
||||
this.props.fetchEnvironmentConfiguration
|
||||
}
|
||||
summary={this.props.environmentConfigurationSummary}
|
||||
planName={currentPlanName}
|
||||
isFetching={this.props.isFetchingEnvironmentConfiguration}
|
||||
loaded={this.props.environmentConfigurationLoaded}
|
||||
/>
|
||||
</DeploymentPlanStep>
|
||||
<DeploymentPlanStep
|
||||
title={formatMessage(messages.configureRolesStepHeader)}
|
||||
disabled={this.props.currentStackDeploymentInProgress}
|
||||
tooltip={formatMessage(messages.configureRolesStepTooltip)}
|
||||
>
|
||||
<RolesStep
|
||||
nodeCountParametersByRole={
|
||||
this.props.nodeCountParametersByRole
|
||||
}
|
||||
availableNodesCount={this.props.availableNodes.size}
|
||||
availableNodesCountsByRole={
|
||||
this.props.availableNodesCountsByRole
|
||||
}
|
||||
fetchNodes={this.props.fetchNodes}
|
||||
fetchRoles={this.props.fetchRoles.bind(
|
||||
this,
|
||||
currentPlanName
|
||||
)}
|
||||
isFetchingNodes={this.props.isFetchingNodes}
|
||||
isFetchingRoles={this.props.isFetchingRoles}
|
||||
isFetchingParameters={this.props.isFetchingParameters}
|
||||
roles={this.props.roles}
|
||||
rolesLoaded={this.props.rolesLoaded}
|
||||
totalAssignedNodesCount={this.props.totalAssignedNodesCount}
|
||||
/>
|
||||
</DeploymentPlanStep>
|
||||
<DeploymentPlanStep
|
||||
title={formatMessage(messages.deployStepHeader)}
|
||||
tooltip={formatMessage(messages.deployStepTooltip)}
|
||||
>
|
||||
<DeployStep
|
||||
currentPlan={this.props.currentPlan}
|
||||
currentStack={this.props.currentStack}
|
||||
currentStackResources={this.props.currentStackResources}
|
||||
currentStackDeploymentProgress={
|
||||
this.props.currentStackDeploymentProgress
|
||||
}
|
||||
deleteStack={this.props.deleteStack}
|
||||
deployPlan={this.props.deployPlan}
|
||||
fetchStackEnvironment={this.props.fetchStackEnvironment}
|
||||
fetchStackResource={this.props.fetchStackResource}
|
||||
overcloudInfo={this.props.overcloudInfo}
|
||||
isRequestingStackDelete={this.props.isRequestingStackDelete}
|
||||
stacksLoaded={this.props.stacksLoaded}
|
||||
/>
|
||||
</DeploymentPlanStep>
|
||||
</ol>
|
||||
</div>
|
||||
: <div className="col-sm-12">
|
||||
<NoPlans />
|
||||
</div>}
|
||||
<Route
|
||||
path="/deployment-plan/configuration"
|
||||
component={DeploymentConfiguration}
|
||||
/>
|
||||
<Route
|
||||
path="/deployment-plan/roles/:roleIdentifier"
|
||||
component={RoleDetail}
|
||||
/>
|
||||
<Route
|
||||
path="/deployment-plan/deployment-detail"
|
||||
component={DeploymentDetail}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
return this.props.currentPlan ? <CurrentPlan /> : <Redirect to="/plans" />;
|
||||
}
|
||||
}
|
||||
|
||||
DeploymentPlan.propTypes = {
|
||||
availableNodes: ImmutablePropTypes.map,
|
||||
availableNodesCountsByRole: ImmutablePropTypes.map.isRequired,
|
||||
choosePlan: PropTypes.func,
|
||||
choosePlan: PropTypes.func.isRequired,
|
||||
currentPlan: ImmutablePropTypes.record,
|
||||
currentStack: ImmutablePropTypes.record,
|
||||
currentStackDeploymentInProgress: PropTypes.bool,
|
||||
currentStackDeploymentProgress: PropTypes.number.isRequired,
|
||||
currentStackResources: ImmutablePropTypes.map,
|
||||
deleteStack: PropTypes.func,
|
||||
deployPlan: PropTypes.func,
|
||||
environmentConfigurationLoaded: PropTypes.bool,
|
||||
environmentConfigurationSummary: PropTypes.string,
|
||||
fetchEnvironmentConfiguration: PropTypes.func,
|
||||
fetchNodes: PropTypes.func,
|
||||
fetchParameters: PropTypes.func,
|
||||
fetchRoles: PropTypes.func,
|
||||
fetchStackEnvironment: PropTypes.func,
|
||||
fetchStackResource: PropTypes.func,
|
||||
fetchStackResources: PropTypes.func.isRequired,
|
||||
fetchStacks: PropTypes.func,
|
||||
hasPlans: PropTypes.bool,
|
||||
inactivePlans: ImmutablePropTypes.map,
|
||||
intl: PropTypes.object,
|
||||
isFetchingEnvironmentConfiguration: PropTypes.bool,
|
||||
isFetchingNodes: PropTypes.bool,
|
||||
isFetchingParameters: PropTypes.bool,
|
||||
isFetchingRoles: PropTypes.bool,
|
||||
isRequestingStackDelete: PropTypes.bool,
|
||||
nodeCountParametersByRole: ImmutablePropTypes.map,
|
||||
notify: PropTypes.func,
|
||||
overcloudInfo: ImmutablePropTypes.map.isRequired,
|
||||
roles: ImmutablePropTypes.map,
|
||||
rolesLoaded: PropTypes.bool,
|
||||
route: PropTypes.object,
|
||||
runPostDeploymentValidations: PropTypes.func.isRequired,
|
||||
stacksLoaded: PropTypes.bool.isRequired,
|
||||
totalAssignedNodesCount: PropTypes.number.isRequired
|
||||
match: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export function mapStateToProps(state) {
|
||||
return {
|
||||
availableNodesCountsByRole: getAvailableNodesCountsByRole(state),
|
||||
nodeCountParametersByRole: getNodeCountParametersByRole(state),
|
||||
availableNodes: getAvailableNodes(state),
|
||||
currentPlan: getCurrentPlan(state),
|
||||
currentStack: getCurrentStack(state),
|
||||
currentStackResources: state.stacks.resources,
|
||||
currentStackDeploymentInProgress: getCurrentStackDeploymentInProgress(
|
||||
state
|
||||
),
|
||||
currentStackDeploymentProgress: getCurrentStackDeploymentProgress(state),
|
||||
environmentConfigurationLoaded: state.environmentConfiguration.loaded,
|
||||
environmentConfigurationSummary: getEnvironmentConfigurationSummary(state),
|
||||
isFetchingEnvironmentConfiguration: state.environmentConfiguration
|
||||
.isFetching,
|
||||
isFetchingNodes: state.nodes.get('isFetching'),
|
||||
isFetchingParameters: state.parameters.isFetching,
|
||||
isFetchingRoles: state.roles.get('isFetching'),
|
||||
overcloudInfo: getOvercloudInfo(state),
|
||||
isRequestingStackDelete: state.stacks.get('isRequestingStackDelete'),
|
||||
hasPlans: !state.plans.get('all').isEmpty(),
|
||||
inactivePlans: getAllPlansButCurrent(state),
|
||||
roles: getRoles(state),
|
||||
rolesLoaded: state.roles.get('loaded'),
|
||||
stacksLoaded: state.stacks.get('isLoaded'),
|
||||
totalAssignedNodesCount: getTotalAssignedNodesCount(state)
|
||||
};
|
||||
}
|
||||
const mapStateToProps = (state, props) => ({
|
||||
currentPlan: getPlans(state).get(props.match.params.planName)
|
||||
});
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
choosePlan: planName => dispatch(CurrentPlanActions.choosePlan(planName)),
|
||||
deleteStack: (stackName, stackId) => {
|
||||
dispatch(StacksActions.deleteStack(stackName, stackId));
|
||||
},
|
||||
deployPlan: planName => dispatch(PlansActions.deployPlan(planName)),
|
||||
fetchStackEnvironment: stack =>
|
||||
dispatch(StacksActions.fetchEnvironment(stack)),
|
||||
fetchEnvironmentConfiguration: (planName, parentPath) => {
|
||||
dispatch(
|
||||
EnvironmentConfigurationActions.fetchEnvironmentConfiguration(
|
||||
planName,
|
||||
parentPath
|
||||
)
|
||||
);
|
||||
},
|
||||
fetchNodes: () => dispatch(NodesActions.fetchNodes()),
|
||||
fetchParameters: planName =>
|
||||
dispatch(ParametersActions.fetchParameters(planName)),
|
||||
fetchRoles: planName => dispatch(RolesActions.fetchRoles(planName)),
|
||||
fetchStackResources: stack =>
|
||||
dispatch(StacksActions.fetchResources(stack.stack_name, stack.id)),
|
||||
fetchStackResource: (stack, resourceName) =>
|
||||
dispatch(StacksActions.fetchResource(stack, resourceName)),
|
||||
fetchStacks: () => dispatch(StacksActions.fetchStacks()),
|
||||
notify: notification => dispatch(NotificationActions.notify(notification)),
|
||||
runPostDeploymentValidations: planName => {
|
||||
dispatch(
|
||||
ValidationsActions.runValidationGroups(['post-deployment'], planName)
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
const mapDispatchToProps = (dispatch, props) => ({
|
||||
choosePlan: planName => dispatch(CurrentPlanActions.choosePlan(planName))
|
||||
});
|
||||
|
||||
export default injectIntl(
|
||||
connect(mapStateToProps, mapDispatchToProps)(DeploymentPlan)
|
||||
);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(DeploymentPlan);
|
||||
|
|
|
@ -59,24 +59,29 @@ class DeploymentProgress extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { formatMessage } = this.props.intl;
|
||||
const {
|
||||
currentPlanName,
|
||||
deleteStack,
|
||||
intl: { formatMessage },
|
||||
isRequestingStackDelete,
|
||||
stack
|
||||
} = this.props;
|
||||
|
||||
const statusMessage = (
|
||||
<strong>
|
||||
<FormattedMessage {...statusMessages[this.props.stack.stack_status]} />
|
||||
<FormattedMessage {...statusMessages[stack.stack_status]} />
|
||||
</strong>
|
||||
);
|
||||
|
||||
const deleteButton = this.props.stack.stack_status !==
|
||||
stackStates.DELETE_IN_PROGRESS
|
||||
const deleteButton = stack.stack_status !== stackStates.DELETE_IN_PROGRESS
|
||||
? <DeleteStackButton
|
||||
content={formatMessage(messages.cancelDeployment)}
|
||||
buttonIconClass="fa fa-ban"
|
||||
deleteStack={this.props.deleteStack}
|
||||
disabled={this.props.isRequestingStackDelete}
|
||||
loaded={!this.props.isRequestingStackDelete}
|
||||
deleteStack={deleteStack}
|
||||
disabled={isRequestingStackDelete}
|
||||
loaded={!isRequestingStackDelete}
|
||||
loaderContent={formatMessage(messages.requestingDeletion)}
|
||||
stack={this.props.stack}
|
||||
stack={stack}
|
||||
/>
|
||||
: null;
|
||||
|
||||
|
@ -84,7 +89,7 @@ class DeploymentProgress extends React.Component {
|
|||
<div>
|
||||
<p>
|
||||
<span><FormattedMessage {...messages.deploymentInProgress} /> </span>
|
||||
<Link to="/deployment-plan/deployment-detail">
|
||||
<Link to={`/plans/${currentPlanName}/deployment-detail`}>
|
||||
<FormattedMessage {...messages.viewInformation} />
|
||||
</Link>
|
||||
</p>
|
||||
|
@ -99,6 +104,7 @@ class DeploymentProgress extends React.Component {
|
|||
}
|
||||
|
||||
DeploymentProgress.propTypes = {
|
||||
currentPlanName: PropTypes.string.isRequired,
|
||||
deleteStack: PropTypes.func.isRequired,
|
||||
deploymentProgress: PropTypes.number.isRequired,
|
||||
intl: PropTypes.object,
|
||||
|
|
|
@ -43,7 +43,7 @@ export default class NoPlans extends React.Component {
|
|||
<h1><FormattedMessage {...messages.noPlansAvailable} /></h1>
|
||||
<p><FormattedMessage {...messages.noPlansAvailableMessage} /></p>
|
||||
<div className="blank-slate-pf-main-action">
|
||||
<Link to="/plans/new" className="btn btn-lg btn-primary">
|
||||
<Link to="/plans/manage/new" className="btn btn-lg btn-primary">
|
||||
<span className="fa fa-plus" />
|
||||
{' '}
|
||||
<FormattedMessage {...messages.createNewPlan} />
|
||||
|
|
|
@ -1,78 +0,0 @@
|
|||
/**
|
||||
* Copyright 2017 Red Hat Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License. You may obtain
|
||||
* a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { Link } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import DropdownItem from '../ui/dropdown/DropdownItem';
|
||||
import DropdownButton from '../ui/dropdown/DropdownButton';
|
||||
import Dropdown from '../ui/dropdown/Dropdown';
|
||||
|
||||
const messages = defineMessages({
|
||||
manageDeployments: {
|
||||
id: 'PlansDropdown.manageDeployments',
|
||||
defaultMessage: 'Manage Deployments'
|
||||
},
|
||||
selectDeployment: {
|
||||
id: 'PlansDropdown.selectDeployment',
|
||||
defaultMessage: 'Select Deployment'
|
||||
}
|
||||
});
|
||||
|
||||
export default class PlansDropdown extends React.Component {
|
||||
renderRecentPlans() {
|
||||
return this.props.plans.toList().map(plan => {
|
||||
return (
|
||||
<DropdownItem
|
||||
key={plan.name}
|
||||
onClick={this.props.choosePlan.bind(this, plan.name)}
|
||||
>
|
||||
{plan.name}
|
||||
</DropdownItem>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.props.plans.isEmpty()) {
|
||||
return (
|
||||
<Link className="btn btn-link" to="/plans">
|
||||
<FormattedMessage {...messages.manageDeployments} />
|
||||
</Link>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Dropdown>
|
||||
<DropdownButton className="btn-link">
|
||||
<FormattedMessage {...messages.selectDeployment} />
|
||||
</DropdownButton>
|
||||
{this.renderRecentPlans()}
|
||||
<DropdownItem key="divider" divider />
|
||||
<DropdownItem key="plansLink" to="/plans">
|
||||
<FormattedMessage {...messages.manageDeployments} />
|
||||
</DropdownItem>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
PlansDropdown.propTypes = {
|
||||
choosePlan: PropTypes.func,
|
||||
plans: ImmutablePropTypes.map
|
||||
};
|
|
@ -48,6 +48,7 @@ const messages = defineMessages({
|
|||
const RoleCard = ({
|
||||
assignedNodesCountParameter,
|
||||
availableNodesCount,
|
||||
currentPlanName,
|
||||
identifier,
|
||||
intl,
|
||||
name,
|
||||
|
@ -69,7 +70,7 @@ const RoleCard = ({
|
|||
<h2 className="card-pf-title">
|
||||
{title}
|
||||
<Link
|
||||
to={`deployment-plan/roles/${identifier}`}
|
||||
to={`/plans/${currentPlanName}/roles/${identifier}`}
|
||||
className="link pull-right"
|
||||
title="Edit Role parameters"
|
||||
>
|
||||
|
@ -114,6 +115,7 @@ const RoleCard = ({
|
|||
RoleCard.propTypes = {
|
||||
assignedNodesCountParameter: ImmutablePropTypes.record,
|
||||
availableNodesCount: PropTypes.number.isRequired,
|
||||
currentPlanName: PropTypes.string.isRequired,
|
||||
identifier: PropTypes.string.isRequired,
|
||||
intl: PropTypes.object,
|
||||
name: PropTypes.string.isRequired,
|
||||
|
|
|
@ -48,6 +48,7 @@ class Roles extends React.Component {
|
|||
return (
|
||||
<div className="col-xs-6 col-sm-4 col-md-3 col-lg-2" key={role.name}>
|
||||
<RoleCard
|
||||
currentPlanName={this.props.currentPlanName}
|
||||
name={role.name}
|
||||
title={role.title}
|
||||
identifier={role.identifier}
|
||||
|
@ -88,6 +89,7 @@ class Roles extends React.Component {
|
|||
}
|
||||
Roles.propTypes = {
|
||||
availableNodesCountsByRole: ImmutablePropTypes.map.isRequired,
|
||||
currentPlanName: PropTypes.string.isRequired,
|
||||
fetchNodes: PropTypes.func.isRequired,
|
||||
fetchRoles: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object,
|
||||
|
|
|
@ -40,6 +40,7 @@ const messages = defineMessages({
|
|||
const RolesStep = ({
|
||||
availableNodesCount,
|
||||
availableNodesCountsByRole,
|
||||
currentPlanName,
|
||||
roles,
|
||||
fetchRoles,
|
||||
fetchNodes,
|
||||
|
@ -79,6 +80,7 @@ const RolesStep = ({
|
|||
</Loader>
|
||||
</p>
|
||||
<Roles
|
||||
currentPlanName={currentPlanName}
|
||||
roles={roles.toList().toJS()}
|
||||
availableNodesCountsByRole={availableNodesCountsByRole}
|
||||
nodeCountParametersByRole={nodeCountParametersByRole}
|
||||
|
@ -94,6 +96,7 @@ const RolesStep = ({
|
|||
RolesStep.propTypes = {
|
||||
availableNodesCount: PropTypes.number.isRequired,
|
||||
availableNodesCountsByRole: ImmutablePropTypes.map.isRequired,
|
||||
currentPlanName: PropTypes.string.isRequired,
|
||||
fetchNodes: PropTypes.func.isRequired,
|
||||
fetchRoles: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object,
|
||||
|
|
|
@ -26,6 +26,7 @@ import React from 'react';
|
|||
import EnvironmentConfigurationActions
|
||||
from '../../actions/EnvironmentConfigurationActions';
|
||||
import EnvironmentConfigurationTopic from './EnvironmentConfigurationTopic';
|
||||
import { getCurrentPlanName } from '../../selectors/plans';
|
||||
import ModalFormErrorList from '../ui/forms/ModalFormErrorList';
|
||||
import { getTopicsTree } from '../../selectors/environmentConfiguration';
|
||||
import Loader from '../ui/Loader';
|
||||
|
@ -58,8 +59,13 @@ class EnvironmentConfiguration extends React.Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.fetchEnvironmentConfiguration(this.props.currentPlanName, () =>
|
||||
this.props.history.push('/deployment-plan')
|
||||
const {
|
||||
currentPlanName,
|
||||
fetchEnvironmentConfiguration,
|
||||
history
|
||||
} = this.props;
|
||||
fetchEnvironmentConfiguration(currentPlanName, () =>
|
||||
history.push(`/plans/${currentPlanName}`)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -110,7 +116,7 @@ class EnvironmentConfiguration extends React.Component {
|
|||
closeOnSubmit: false
|
||||
});
|
||||
|
||||
this.props.history.push('/deployment-plan');
|
||||
this.props.history.push(`/plans/${this.props.currentPlanName}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -209,7 +215,11 @@ class EnvironmentConfiguration extends React.Component {
|
|||
>
|
||||
<FormattedMessage {...messages.saveAndClose} />
|
||||
</button>
|
||||
<Link to="/deployment-plan" type="button" className="btn btn-default">
|
||||
<Link
|
||||
to={`/plans/${this.props.currentPlanName}`}
|
||||
type="button"
|
||||
className="btn btn-default"
|
||||
>
|
||||
<FormattedMessage {...messages.cancel} />
|
||||
</Link>
|
||||
</div>
|
||||
|
@ -232,7 +242,7 @@ EnvironmentConfiguration.propTypes = {
|
|||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
currentPlanName: state.currentPlan.currentPlanName,
|
||||
currentPlanName: getCurrentPlanName(state),
|
||||
environmentConfigurationTopics: getTopicsTree(state),
|
||||
formErrors: state.environmentConfiguration.getIn(['form', 'formErrors']),
|
||||
formFieldErrors: state.environmentConfiguration.getIn([
|
||||
|
|
|
@ -32,7 +32,6 @@ import NodesListView from './NodesListView/NodesListView';
|
|||
import NodesToolbar from './NodesToolbar/NodesToolbar';
|
||||
import NodesTableView from './NodesTableView';
|
||||
import RegisterNodesDialog from './RegisterNodesDialog';
|
||||
import RolesActions from '../../actions/RolesActions';
|
||||
|
||||
const messages = defineMessages({
|
||||
loadingNodes: {
|
||||
|
@ -56,13 +55,11 @@ const messages = defineMessages({
|
|||
class Nodes extends React.Component {
|
||||
componentDidMount() {
|
||||
this.props.fetchNodes();
|
||||
this.props.fetchRoles(this.props.currentPlanName);
|
||||
}
|
||||
|
||||
refreshResults(e) {
|
||||
e.preventDefault();
|
||||
this.props.fetchNodes();
|
||||
this.props.fetchRoles(this.props.currentPlanName);
|
||||
}
|
||||
|
||||
renderContentView() {
|
||||
|
@ -120,10 +117,8 @@ class Nodes extends React.Component {
|
|||
}
|
||||
Nodes.propTypes = {
|
||||
contentView: PropTypes.string.isRequired,
|
||||
currentPlanName: PropTypes.string.isRequired,
|
||||
fetchNodeIntrospectionData: PropTypes.func.isRequired,
|
||||
fetchNodes: PropTypes.func.isRequired,
|
||||
fetchRoles: PropTypes.func.isRequired,
|
||||
fetchingNodes: PropTypes.bool.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
nodes: ImmutablePropTypes.map.isRequired,
|
||||
|
@ -136,7 +131,6 @@ const mapStateToProps = state => ({
|
|||
'contentView',
|
||||
'list'
|
||||
),
|
||||
currentPlanName: state.currentPlan.currentPlanName,
|
||||
fetchingNodes: state.nodes.get('isFetching'),
|
||||
nodes: getFilteredNodes(state),
|
||||
nodesInProgress: nodesInProgress(state),
|
||||
|
@ -146,9 +140,7 @@ const mapStateToProps = state => ({
|
|||
const mapDispatchToProps = dispatch => ({
|
||||
fetchNodes: () => dispatch(NodesActions.fetchNodes()),
|
||||
fetchNodeIntrospectionData: nodeId =>
|
||||
dispatch(NodesActions.fetchNodeIntrospectionData(nodeId)),
|
||||
fetchRoles: currentPlanName =>
|
||||
dispatch(RolesActions.fetchRoles(currentPlanName))
|
||||
dispatch(NodesActions.fetchNodeIntrospectionData(nodeId))
|
||||
});
|
||||
|
||||
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(Nodes));
|
||||
|
|
|
@ -21,6 +21,7 @@ import React from 'react';
|
|||
|
||||
import EnvironmentConfigurationActions
|
||||
from '../../actions/EnvironmentConfigurationActions';
|
||||
import { getCurrentPlanName } from '../../selectors/plans';
|
||||
import { getEnvironmentParameters } from '../../selectors/parameters';
|
||||
import { getEnvironment } from '../../selectors/environmentConfiguration';
|
||||
import InlineNotification from '../ui/InlineNotification';
|
||||
|
@ -65,7 +66,7 @@ EnvironmentParameters.propTypes = {
|
|||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
currentPlanName: state.currentPlan.currentPlanName,
|
||||
currentPlanName: getCurrentPlanName(state),
|
||||
environmentError: getEnvironment(state, ownProps.environment).error,
|
||||
parameters: getEnvironmentParameters(state, ownProps.environment),
|
||||
parametersLoaded: state.parameters.loaded,
|
||||
|
|
|
@ -27,6 +27,7 @@ import React from 'react';
|
|||
import EnvironmentConfigurationActions
|
||||
from '../../actions/EnvironmentConfigurationActions';
|
||||
import EnvironmentParameters from './EnvironmentParameters';
|
||||
import { getCurrentPlanName } from '../../selectors/plans';
|
||||
import { getRootParameters } from '../../selectors/parameters';
|
||||
import {
|
||||
getEnabledEnvironments
|
||||
|
@ -134,7 +135,7 @@ class Parameters extends React.Component {
|
|||
closeOnSubmit: false
|
||||
});
|
||||
|
||||
this.props.history.push('/deployment-plan');
|
||||
this.props.history.push(`/plans/${this.props.currentPlanName}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -259,7 +260,11 @@ class Parameters extends React.Component {
|
|||
>
|
||||
<FormattedMessage {...messages.saveAndClose} />
|
||||
</button>
|
||||
<Link to="/deployment-plan" type="button" className="btn btn-default">
|
||||
<Link
|
||||
to={`/plans/${this.props.currentPlanName}`}
|
||||
type="button"
|
||||
className="btn btn-default"
|
||||
>
|
||||
<FormattedMessage {...messages.cancel} />
|
||||
</Link>
|
||||
</div>
|
||||
|
@ -290,6 +295,7 @@ function mapStateToProps(state, ownProps) {
|
|||
form: state.parameters.form,
|
||||
formErrors: state.parameters.form.get('formErrors'),
|
||||
formFieldErrors: state.parameters.form.get('formFieldErrors'),
|
||||
currentPlanName: getCurrentPlanName(state),
|
||||
isFetchingParameters: state.parameters.isFetching,
|
||||
mistralParameters: state.parameters.mistralParameters,
|
||||
parameters: getRootParameters(state),
|
||||
|
@ -303,14 +309,14 @@ function mapDispatchToProps(dispatch, ownProps) {
|
|||
dispatch(
|
||||
EnvironmentConfigurationActions.fetchEnvironmentConfiguration(
|
||||
currentPlanName,
|
||||
() => ownProps.history.push('/deployment-plan')
|
||||
() => ownProps.history.push(`/plans/${currentPlanName}`)
|
||||
)
|
||||
);
|
||||
},
|
||||
fetchParameters: currentPlanName => {
|
||||
dispatch(
|
||||
ParametersActions.fetchParameters(currentPlanName, () =>
|
||||
ownProps.history.push('/deployment-plan')
|
||||
ownProps.history.push(`/plans/${currentPlanName}`)
|
||||
)
|
||||
);
|
||||
},
|
||||
|
|
|
@ -59,7 +59,7 @@ class DeletePlan extends React.Component {
|
|||
return (
|
||||
<Modal dialogClasses="modal-sm" id="DeletePlan__deletePlanModal">
|
||||
<div className="modal-header">
|
||||
<Link to="/plans" type="button" className="close">
|
||||
<Link to="/plans/manage" type="button" className="close">
|
||||
<span aria-hidden="true" className="pficon pficon-close" />
|
||||
</Link>
|
||||
<h4 className="modal-title">
|
||||
|
@ -97,7 +97,7 @@ class DeletePlan extends React.Component {
|
|||
<FormattedMessage {...messages.deletePlan} />
|
||||
</button>
|
||||
<Link
|
||||
to="/plans"
|
||||
to="/plans/manage"
|
||||
type="button"
|
||||
className="btn btn-default"
|
||||
id="DeletePlan__cancelDeletePlanModalButton"
|
||||
|
|
|
@ -116,7 +116,7 @@ class EditPlan extends React.Component {
|
|||
onInvalid={this.onFormInvalid.bind(this)}
|
||||
>
|
||||
<div className="modal-header">
|
||||
<Link to="/plans" type="button" className="close">
|
||||
<Link to="/plans/manage" type="button" className="close">
|
||||
<span aria-hidden="true" className="pficon pficon-close" />
|
||||
</Link>
|
||||
<h4>
|
||||
|
@ -151,7 +151,7 @@ class EditPlan extends React.Component {
|
|||
>
|
||||
<FormattedMessage {...messages.uploadAndUpdate} />
|
||||
</button>
|
||||
<Link to="/plans" type="button" className="btn btn-default">
|
||||
<Link to="/plans/manage" type="button" className="btn btn-default">
|
||||
<FormattedMessage {...messages.cancel} />
|
||||
</Link>
|
||||
</div>
|
||||
|
|
|
@ -68,7 +68,7 @@ class ExportPlan extends React.Component {
|
|||
return (
|
||||
<Modal dialogClasses="modal-sm" id="ExportPlan__exportPlanModal">
|
||||
<div className="modal-header">
|
||||
<Link to="/plans" type="button" className="close">
|
||||
<Link to="/plans/manage" type="button" className="close">
|
||||
<span aria-hidden="true" className="pficon pficon-close" />
|
||||
</Link>
|
||||
<h4 className="modal-title">
|
||||
|
@ -107,7 +107,7 @@ class ExportPlan extends React.Component {
|
|||
</div>
|
||||
<div className="modal-footer">
|
||||
<Link
|
||||
to="/plans"
|
||||
to="/plans/manage"
|
||||
type="button"
|
||||
className="btn btn-default"
|
||||
id="ExportPlan__cancelExportPlanModalButton"
|
||||
|
|
|
@ -20,7 +20,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import CurrentPlanActions from '../../actions/CurrentPlanActions';
|
||||
import { getCurrentPlanName, getPlans } from '../../selectors/plans';
|
||||
import { PageHeader } from '../ui/PageHeader';
|
||||
import PlansActions from '../../actions/PlansActions';
|
||||
import StacksActions from '../../actions/StacksActions';
|
||||
|
@ -50,7 +50,6 @@ class ListPlans extends React.Component {
|
|||
<PlanCard
|
||||
key={plan.name}
|
||||
plan={plan}
|
||||
choosePlan={this.props.choosePlan}
|
||||
currentPlanName={this.props.currentPlanName}
|
||||
stack={stack}
|
||||
/>
|
||||
|
@ -83,8 +82,6 @@ class ListPlans extends React.Component {
|
|||
|
||||
ListPlans.propTypes = {
|
||||
children: PropTypes.node,
|
||||
choosePlan: PropTypes.func,
|
||||
conflict: PropTypes.string,
|
||||
currentPlanName: PropTypes.string,
|
||||
fetchPlans: PropTypes.func,
|
||||
fetchStacks: PropTypes.func,
|
||||
|
@ -94,8 +91,8 @@ ListPlans.propTypes = {
|
|||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
currentPlanName: state.currentPlan.currentPlanName,
|
||||
plans: state.plans.get('all'),
|
||||
currentPlanName: getCurrentPlanName(state),
|
||||
plans: getPlans(state),
|
||||
stacks: state.stacks.get('stacks')
|
||||
};
|
||||
}
|
||||
|
@ -103,8 +100,7 @@ function mapStateToProps(state) {
|
|||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
fetchPlans: () => dispatch(PlansActions.fetchPlans()),
|
||||
fetchStacks: () => dispatch(StacksActions.fetchStacks()),
|
||||
choosePlan: planName => dispatch(CurrentPlanActions.choosePlan(planName))
|
||||
fetchStacks: () => dispatch(StacksActions.fetchStacks())
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -107,7 +107,7 @@ class NewPlan extends React.Component {
|
|||
>
|
||||
<div className="modal-header">
|
||||
<Link
|
||||
to="/plans"
|
||||
to="/plans/manage"
|
||||
type="button"
|
||||
onClick={() => this.props.cancelCreatePlan()}
|
||||
className="close"
|
||||
|
@ -142,7 +142,7 @@ class NewPlan extends React.Component {
|
|||
<FormattedMessage {...messages.uploadAndCreate} />
|
||||
</button>
|
||||
<Link
|
||||
to="/plans"
|
||||
to="/plans/manage"
|
||||
type="button"
|
||||
onClick={() => this.props.cancelCreatePlan()}
|
||||
className="btn btn-default"
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Route } from 'react-router-dom';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
|
||||
import ListPlans from './ListPlans';
|
||||
import DeletePlan from './DeletePlan';
|
||||
|
@ -28,11 +28,19 @@ export default class Plans extends React.Component {
|
|||
return (
|
||||
<div className="row">
|
||||
<div className="col-sm-12">
|
||||
<ListPlans />
|
||||
<Route path="/plans/new" component={NewPlan} />
|
||||
<Route path="/plans/:planName/delete" component={DeletePlan} />
|
||||
<Route path="/plans/:planName/edit" component={EditPlan} />
|
||||
<Route path="/plans/:planName/export" component={ExportPlan} />
|
||||
<Route path="/plans/manage" component={ListPlans} />
|
||||
<Switch>
|
||||
<Route path="/plans/manage/new" component={NewPlan} />
|
||||
<Route
|
||||
path="/plans/manage/:planName/delete"
|
||||
component={DeletePlan}
|
||||
/>
|
||||
<Route path="/plans/manage/:planName/edit" component={EditPlan} />
|
||||
<Route
|
||||
path="/plans/manage/:planName/export"
|
||||
component={ExportPlan}
|
||||
/>
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -30,7 +30,7 @@ const CreatePlanCard = () => (
|
|||
<div className="card-pf">
|
||||
<div className="card-pf-body">
|
||||
<span className="pficon pficon-add-circle-o" />
|
||||
<Link to="/plans/new" id="ListPlans__newPlanLink">
|
||||
<Link to="/plans/manage/new" id="ListPlans__newPlanLink">
|
||||
<FormattedMessage {...messages.createNewPlan} />
|
||||
</Link>
|
||||
</div>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { Link } from 'react-router-dom';
|
||||
import {
|
||||
defineMessages,
|
||||
FormattedMessage,
|
||||
|
@ -56,11 +56,6 @@ const messages = defineMessages({
|
|||
});
|
||||
|
||||
class PlanCard extends React.Component {
|
||||
onPlanClick(e) {
|
||||
e.preventDefault();
|
||||
this.props.choosePlan(e.target.textContent);
|
||||
}
|
||||
|
||||
getActiveIcon() {
|
||||
if (this.props.plan.name === this.props.currentPlanName) {
|
||||
return <span className="pficon pficon-flag" />;
|
||||
|
@ -78,9 +73,9 @@ class PlanCard extends React.Component {
|
|||
);
|
||||
} else {
|
||||
return (
|
||||
<a href="" onClick={this.onPlanClick.bind(this)}>
|
||||
<Link to={`/plans/${this.props.plan.name}`}>
|
||||
{this.props.plan.name}
|
||||
</a>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -158,14 +153,14 @@ class PlanCard extends React.Component {
|
|||
|
||||
{this.getActiveIcon()}
|
||||
<div className="pull-right">
|
||||
<DropdownKebab id={`card-actions-${planName}`}>
|
||||
<MenuItemLink to={`/plans/${planName}/edit`}>
|
||||
<DropdownKebab id={`card-actions-${planName}`} pullRight>
|
||||
<MenuItemLink to={`/plans/manage/${planName}/edit`}>
|
||||
<FormattedMessage {...messages.edit} />
|
||||
</MenuItemLink>
|
||||
<MenuItemLink to={`/plans/${planName}/export`}>
|
||||
<MenuItemLink to={`/plans/manage/${planName}/export`}>
|
||||
<FormattedMessage {...messages.export} />
|
||||
</MenuItemLink>
|
||||
<MenuItemLink to={`/plans/${planName}/delete`}>
|
||||
<MenuItemLink to={`/plans/manage/${planName}/delete`}>
|
||||
<FormattedMessage {...messages.delete} />
|
||||
</MenuItemLink>
|
||||
</DropdownKebab>
|
||||
|
@ -182,12 +177,10 @@ class PlanCard extends React.Component {
|
|||
}
|
||||
|
||||
PlanCard.propTypes = {
|
||||
choosePlan: PropTypes.func,
|
||||
currentPlanName: PropTypes.string,
|
||||
history: PropTypes.object,
|
||||
intl: PropTypes.object,
|
||||
plan: PropTypes.object,
|
||||
stack: PropTypes.object
|
||||
};
|
||||
|
||||
export default withRouter(injectIntl(PlanCard));
|
||||
export default injectIntl(PlanCard);
|
||||
|
|
|
@ -25,6 +25,7 @@ import PropTypes from 'prop-types';
|
|||
import React from 'react';
|
||||
|
||||
import { checkRunningDeployment } from '../utils/checkRunningDeploymentHOC';
|
||||
import { getCurrentPlanName } from '../../selectors/plans';
|
||||
import { getRole } from '../../selectors/roles';
|
||||
import { getRoleServices } from '../../selectors/parameters';
|
||||
import Loader from '../ui/Loader';
|
||||
|
@ -78,7 +79,8 @@ class RoleDetail extends React.Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.fetchParameters(this.props.currentPlanName, '/deployment-plan');
|
||||
const { currentPlanName } = this.props;
|
||||
this.props.fetchParameters(currentPlanName, `/plans/${currentPlanName}`);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
|
@ -139,8 +141,8 @@ class RoleDetail extends React.Component {
|
|||
|
||||
renderRoleTabs() {
|
||||
const {
|
||||
currentPlanName,
|
||||
formErrors,
|
||||
location,
|
||||
match: { params: urlParams },
|
||||
parametersLoaded,
|
||||
rolesLoaded
|
||||
|
@ -150,23 +152,17 @@ class RoleDetail extends React.Component {
|
|||
<div className="row">
|
||||
<ul className="nav nav-tabs">
|
||||
<NavTab
|
||||
location={location}
|
||||
to={`/deployment-plan/roles/${urlParams.roleIdentifier}/parameters`}
|
||||
to={`/plans/${currentPlanName}/roles/${urlParams.roleIdentifier}/parameters`}
|
||||
>
|
||||
<FormattedMessage {...messages.parameters} />
|
||||
</NavTab>
|
||||
<NavTab
|
||||
location={location}
|
||||
to={`/deployment-plan/roles/${urlParams.roleIdentifier}/services`}
|
||||
to={`/plans/${currentPlanName}/roles/${urlParams.roleIdentifier}/services`}
|
||||
>
|
||||
<FormattedMessage {...messages.services} />
|
||||
</NavTab>
|
||||
<NavTab
|
||||
location={location}
|
||||
to={
|
||||
`/deployment-plan/roles/${urlParams.roleIdentifier}` +
|
||||
'/network-configuration'
|
||||
}
|
||||
to={`/plans/${currentPlanName}/roles/${urlParams.roleIdentifier}/network-configuration`}
|
||||
>
|
||||
<FormattedMessage {...messages.networkConfiguration} />
|
||||
</NavTab>
|
||||
|
@ -180,7 +176,12 @@ class RoleDetail extends React.Component {
|
|||
render() {
|
||||
const dataLoaded = this.props.rolesLoaded && this.props.parametersLoaded;
|
||||
const roleName = this.props.role ? this.props.role.name : null;
|
||||
const { match: { params: urlParams } } = this.props;
|
||||
const {
|
||||
currentPlanName,
|
||||
intl,
|
||||
location,
|
||||
match: { params: urlParams }
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Formsy.Form
|
||||
|
@ -194,7 +195,11 @@ class RoleDetail extends React.Component {
|
|||
<ModalPanelBackdrop />
|
||||
<ModalPanel>
|
||||
<ModalPanelHeader>
|
||||
<Link to="/deployment-plan" type="button" className="close">
|
||||
<Link
|
||||
to={`/plans/${currentPlanName}`}
|
||||
type="button"
|
||||
className="close"
|
||||
>
|
||||
<span aria-hidden="true" className="pficon pficon-close" />
|
||||
</Link>
|
||||
<h2 className="modal-title">
|
||||
|
@ -208,27 +213,25 @@ class RoleDetail extends React.Component {
|
|||
<ModalPanelBody>
|
||||
<Loader
|
||||
height={60}
|
||||
content={this.props.intl.formatMessage(
|
||||
messages.loadingParameters
|
||||
)}
|
||||
content={intl.formatMessage(messages.loadingParameters)}
|
||||
loaded={dataLoaded}
|
||||
>
|
||||
<Switch>
|
||||
<Switch location={location}>
|
||||
<Route
|
||||
path="/deployment-plan/roles/:roleIdentifier/parameters"
|
||||
path="/plans/:planName/roles/:roleIdentifier/parameters"
|
||||
component={RoleParameters}
|
||||
/>
|
||||
<Route
|
||||
path="/deployment-plan/roles/:roleIdentifier/services"
|
||||
path="/plans/:planName/roles/:roleIdentifier/services"
|
||||
component={RoleServices}
|
||||
/>
|
||||
<Route
|
||||
path="/deployment-plan/roles/:roleIdentifier/network-configuration"
|
||||
path="/plans/:planName/roles/:roleIdentifier/network-configuration"
|
||||
component={RoleNetworkConfig}
|
||||
/>
|
||||
<Redirect
|
||||
from="/deployment-plan/roles/:roleIdentifier"
|
||||
to={`/deployment-plan/roles/${urlParams.roleIdentifier}/parameters`}
|
||||
from="/plans/:planName/roles/:roleIdentifier"
|
||||
to={`/plans/${currentPlanName}/roles/${urlParams.roleIdentifier}/parameters`}
|
||||
/>
|
||||
</Switch>
|
||||
</Loader>
|
||||
|
@ -266,7 +269,7 @@ RoleDetail.propTypes = {
|
|||
|
||||
function mapStateToProps(state, props) {
|
||||
return {
|
||||
currentPlanName: state.currentPlan.currentPlanName,
|
||||
currentPlanName: getCurrentPlanName(state),
|
||||
formErrors: state.parameters.form.get('formErrors'),
|
||||
formFieldErrors: state.parameters.form.get('formFieldErrors'),
|
||||
allParameters: state.parameters.parameters,
|
||||
|
|
|
@ -19,9 +19,7 @@ import PropTypes from 'prop-types';
|
|||
import { Redirect } from 'react-router-dom';
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
currentPlanNameSelector as getCurrentPlanName
|
||||
} from '../../selectors/plans';
|
||||
import { getCurrentPlanName } from '../../selectors/plans';
|
||||
import { getCurrentStackDeploymentInProgress } from '../../selectors/stacks';
|
||||
import NotificationActions from '../../actions/NotificationActions';
|
||||
|
||||
|
@ -39,7 +37,7 @@ export const checkRunningDeployment = WrappedComponent => {
|
|||
|
||||
render() {
|
||||
return this.props.currentStackDeploymentInProgress
|
||||
? <Redirect to="/deployment-plan" />
|
||||
? <Redirect to={`/plans/${this.props.currentPlanName}`} />
|
||||
: <WrappedComponent {...this.props} />;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import PropTypes from 'prop-types';
|
|||
import React from 'react';
|
||||
|
||||
import BlankSlate from '../ui/BlankSlate';
|
||||
import { getCurrentPlanName } from '../../selectors/plans';
|
||||
import Loader from '../ui/Loader';
|
||||
import ValidationsActions from '../../actions/ValidationsActions';
|
||||
import ValidationsToolbar from './ValidationsToolbar';
|
||||
|
@ -222,7 +223,7 @@ const mapStateToProps = state => {
|
|||
isFetchingValidations: state.validations.get('isFetching'),
|
||||
validations: getFilteredValidations(state),
|
||||
validationsLoaded: state.validations.get('validationsLoaded'),
|
||||
currentPlanName: state.currentPlan.currentPlanName
|
||||
currentPlanName: getCurrentPlanName(state)
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@ export default keyMirror({
|
|||
REQUEST_PLAN_DELETE: null,
|
||||
RECEIVE_PLAN_DELETE: null,
|
||||
PLAN_CHOSEN: null,
|
||||
PLAN_DETECTED: null,
|
||||
DELETE_PLAN_PENDING: null,
|
||||
DELETE_PLAN_SUCCESS: null,
|
||||
DELETE_PLAN_FAILED: null,
|
||||
|
|
|
@ -17,6 +17,5 @@
|
|||
import { Record } from 'immutable';
|
||||
|
||||
export const CurrentPlanState = Record({
|
||||
conflict: undefined,
|
||||
currentPlanName: undefined
|
||||
});
|
||||
|
|
|
@ -24,11 +24,6 @@ export default function currentPlanReducer(state = initialState, action) {
|
|||
case PlansConstants.PLAN_CHOSEN:
|
||||
return state.set('currentPlanName', action.payload);
|
||||
|
||||
case PlansConstants.PLAN_DETECTED:
|
||||
return state
|
||||
.set('currentPlanName', action.payload.currentPlanName)
|
||||
.set('conflict', action.payload.conflict);
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -16,22 +16,28 @@
|
|||
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
const plansSelector = state => state.plans.get('all').sortBy(plan => plan.name);
|
||||
export const plans = state => state.plans.get('all').sortBy(plan => plan.name);
|
||||
export const currentPlanName = state => state.currentPlan.currentPlanName;
|
||||
|
||||
export const currentPlanNameSelector = state =>
|
||||
state.currentPlan.get('currentPlanName');
|
||||
export const getCurrentPlan = createSelector(
|
||||
plansSelector,
|
||||
currentPlanNameSelector,
|
||||
plans,
|
||||
currentPlanName,
|
||||
(plans, currentPlanName) => plans.get(currentPlanName)
|
||||
);
|
||||
|
||||
export const getCurrentPlanName = createSelector(
|
||||
getCurrentPlan,
|
||||
currentPlan => currentPlan && currentPlan.name
|
||||
);
|
||||
|
||||
export const getPlans = createSelector(plans, plans => plans);
|
||||
|
||||
/**
|
||||
* Returns a Map o all plans except for the selected one
|
||||
*/
|
||||
// TODO(jtomasek): update this to list 3 last used plans
|
||||
export const getAllPlansButCurrent = createSelector(
|
||||
[plansSelector, currentPlanNameSelector],
|
||||
[plans, getCurrentPlanName],
|
||||
(plans, currentPlanName) => {
|
||||
return plans
|
||||
.filter(plan => plan.name != currentPlanName)
|
||||
|
|
|
@ -18,7 +18,7 @@ import { createSelector } from 'reselect';
|
|||
import { Map } from 'immutable';
|
||||
|
||||
import { Stack } from '../immutableRecords/stacks';
|
||||
import { currentPlanNameSelector } from './plans';
|
||||
import { getCurrentPlanName } from './plans';
|
||||
|
||||
const stacksSelector = state => state.stacks.stacks;
|
||||
const currentStackEnvironmentSelector = state =>
|
||||
|
@ -30,7 +30,7 @@ const stackResourceDetailsSelector = state => state.stacks.resourceDetails;
|
|||
* Returns the stack associated with currentPlanName
|
||||
*/
|
||||
export const getCurrentStack = createSelector(
|
||||
[stacksSelector, currentPlanNameSelector],
|
||||
[stacksSelector, getCurrentPlanName],
|
||||
(stacks, currentPlanName) => stacks.get(currentPlanName)
|
||||
);
|
||||
|
||||
|
@ -39,7 +39,7 @@ export const getCurrentStack = createSelector(
|
|||
* (true if the plan is currently being deployed, false it not).
|
||||
*/
|
||||
export const getCurrentStackDeploymentInProgress = createSelector(
|
||||
[stacksSelector, currentPlanNameSelector],
|
||||
[stacksSelector, getCurrentPlanName],
|
||||
(stacks, currentPlanName) => {
|
||||
return (
|
||||
stacks.get(currentPlanName, new Stack()).stack_status ===
|
||||
|
@ -71,7 +71,7 @@ export const getCurrentStackDeploymentProgress = createSelector(
|
|||
export const getOvercloudInfo = createSelector(
|
||||
[
|
||||
currentStackEnvironmentSelector,
|
||||
currentPlanNameSelector,
|
||||
getCurrentPlanName,
|
||||
stackResourceDetailsSelector
|
||||
],
|
||||
(currentStackEnvironment, currentPlanName, stackResourceDetails) => {
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import { currentPlanNameSelector } from './plans';
|
||||
import { getCurrentPlanName } from './plans';
|
||||
import { getFilterByName } from './filters';
|
||||
import MistralConstants from '../constants/MistralConstants';
|
||||
|
||||
|
@ -29,7 +29,7 @@ const validationsToolbarFilter = state =>
|
|||
* Filter workflow executions only to validations and current plan
|
||||
*/
|
||||
export const getValidationExecutionsForCurrentPlan = createSelector(
|
||||
[executions, currentPlanNameSelector],
|
||||
[executions, getCurrentPlanName],
|
||||
(executions, currentPlanName) => {
|
||||
return executions.filter(
|
||||
execution =>
|
||||
|
|
|
@ -20,8 +20,23 @@ import createLogger from 'redux-logger';
|
|||
import logger from './services/logger';
|
||||
|
||||
import appReducer from './reducers/appReducer';
|
||||
import { CurrentPlanState } from './immutableRecords/currentPlan';
|
||||
import { getIntl } from './selectors/i18n';
|
||||
|
||||
const hydrateStore = () => {
|
||||
return {
|
||||
currentPlan: new CurrentPlanState({
|
||||
currentPlanName: getStoredPlanName()
|
||||
})
|
||||
};
|
||||
};
|
||||
|
||||
function getStoredPlanName() {
|
||||
if (window && window.localStorage) {
|
||||
return window.localStorage.getItem('currentPlanName');
|
||||
}
|
||||
}
|
||||
|
||||
const loggerMiddleware = createLogger({
|
||||
collapsed: true,
|
||||
logger: logger
|
||||
|
@ -29,7 +44,7 @@ const loggerMiddleware = createLogger({
|
|||
|
||||
const store = createStore(
|
||||
appReducer,
|
||||
{},
|
||||
hydrateStore(),
|
||||
applyMiddleware(
|
||||
thunkMiddleware.withExtraArgument({ getIntl }),
|
||||
loggerMiddleware
|
||||
|
|
|
@ -166,3 +166,9 @@
|
|||
// text-overflow: clip;
|
||||
// white-space: normal;
|
||||
// }
|
||||
|
||||
|
||||
// Remove top margin from page-header if there are breadcrumbs above it
|
||||
.breadcrumb + .page-header {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue