Update code to React Router v4 API
* Move from route config to Route components in container components * Reorganize Authentication functionality to use <Redirect> components * Move Zaqar connection init from login actions to AuthenticatedContent component so history object can be passed to ZaqarActions * Update Deployment Plan, Plans, Roles and Deployment Configuration routes * Add checkRunningDeployment HOC to handle redirecting when Plan deployment is in progress Change-Id: Iaa87a710e3ed57f7bbf41d948583fc5c0b777cec
This commit is contained in:
parent
169b7171b8
commit
8302f53ba7
|
@ -19,7 +19,6 @@ import when from 'when';
|
|||
import * as utils from '../../js/services/utils';
|
||||
import EnvironmentConfigurationActions
|
||||
from '../../js/actions/EnvironmentConfigurationActions';
|
||||
import { browserHistory } from 'react-router';
|
||||
import MistralApiService from '../../js/services/MistralApiService';
|
||||
import { mockGetIntl } from './utils';
|
||||
|
||||
|
@ -79,7 +78,6 @@ describe('EnvironmentConfigurationActions', () => {
|
|||
EnvironmentConfigurationActions,
|
||||
'updateEnvironmentConfigurationSuccess'
|
||||
);
|
||||
spyOn(browserHistory, 'push');
|
||||
// Mock the service call.
|
||||
spyOn(MistralApiService, 'runAction').and.callFake(
|
||||
createResolvingPromise({
|
||||
|
@ -92,8 +90,7 @@ describe('EnvironmentConfigurationActions', () => {
|
|||
EnvironmentConfigurationActions.updateEnvironmentConfiguration(
|
||||
'overcloud',
|
||||
{},
|
||||
{},
|
||||
'/redirect/url'
|
||||
{}
|
||||
)(() => {}, () => {}, mockGetIntl);
|
||||
// Call done with a minimal timeout.
|
||||
setTimeout(() => {
|
||||
|
@ -112,9 +109,5 @@ describe('EnvironmentConfigurationActions', () => {
|
|||
EnvironmentConfigurationActions.updateEnvironmentConfigurationSuccess
|
||||
).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('redirects the page', () => {
|
||||
expect(browserHistory.push).toHaveBeenCalledWith('/redirect/url');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -18,6 +18,7 @@ import when from 'when';
|
|||
|
||||
import * as utils from '../../js/services/utils';
|
||||
import MistralApiService from '../../js/services/MistralApiService';
|
||||
import mockHistory from '../mocks/history';
|
||||
import { mockGetIntl } from './utils';
|
||||
import PlansActions from '../../js/actions/PlansActions';
|
||||
import SwiftApiService from '../../js/services/SwiftApiService';
|
||||
|
@ -49,7 +50,11 @@ describe('PlansActions', () => {
|
|||
);
|
||||
// Call the action creator and the resulting action.
|
||||
// In this case, dispatch and getState are just empty placeHolders.
|
||||
PlansActions.updatePlan('somecloud', {})(() => {}, () => {}, mockGetIntl);
|
||||
PlansActions.updatePlan('somecloud', {}, mockHistory)(
|
||||
() => {},
|
||||
() => {},
|
||||
mockGetIntl
|
||||
);
|
||||
// Call done with a minimal timeout.
|
||||
setTimeout(() => {
|
||||
done();
|
||||
|
@ -108,7 +113,11 @@ describe('PlansActions', () => {
|
|||
);
|
||||
// Call the action creator and the resulting action.
|
||||
// In this case, dispatch and getState are just empty placeHolders.
|
||||
PlansActions.deletePlan('somecloud')(() => {}, () => {}, mockGetIntl);
|
||||
PlansActions.deletePlan('somecloud', mockHistory)(
|
||||
() => {},
|
||||
() => {},
|
||||
mockGetIntl
|
||||
);
|
||||
// Call done with a minimal timeout.
|
||||
setTimeout(() => {
|
||||
done();
|
||||
|
|
|
@ -14,11 +14,11 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { browserHistory } from 'react-router';
|
||||
import when from 'when';
|
||||
import { Map } from 'immutable';
|
||||
|
||||
import MistralApiService from '../../js/services/MistralApiService';
|
||||
import mockHistory from '../mocks/history';
|
||||
import { mockGetIntl } from './utils';
|
||||
import RegisterNodesActions from '../../js/actions/RegisterNodesActions';
|
||||
import NodesActions from '../../js/actions/NodesActions';
|
||||
|
@ -107,9 +107,8 @@ describe('nodesRegistrationFinished', () => {
|
|||
|
||||
spyOn(NotificationActions, 'notify');
|
||||
spyOn(RegisterNodesActions, 'nodesRegistrationSuccess');
|
||||
spyOn(browserHistory, 'push');
|
||||
|
||||
RegisterNodesActions.nodesRegistrationFinished(messagePayload)(
|
||||
RegisterNodesActions.nodesRegistrationFinished(messagePayload, mockHistory)(
|
||||
() => {},
|
||||
() => {
|
||||
return {
|
||||
|
@ -129,7 +128,7 @@ describe('nodesRegistrationFinished', () => {
|
|||
successNotification
|
||||
);
|
||||
expect(RegisterNodesActions.nodesRegistrationSuccess).toHaveBeenCalled();
|
||||
expect(browserHistory.push).toHaveBeenCalledWith('/nodes');
|
||||
expect(mockHistory.push).toHaveBeenCalledWith('/nodes');
|
||||
});
|
||||
|
||||
it('handles failed nodes registration', () => {
|
||||
|
@ -159,9 +158,8 @@ describe('nodesRegistrationFinished', () => {
|
|||
];
|
||||
|
||||
spyOn(RegisterNodesActions, 'nodesRegistrationFailed');
|
||||
spyOn(browserHistory, 'push');
|
||||
|
||||
RegisterNodesActions.nodesRegistrationFinished(messagePayload)(
|
||||
RegisterNodesActions.nodesRegistrationFinished(messagePayload, mockHistory)(
|
||||
() => {},
|
||||
() => {
|
||||
return {
|
||||
|
@ -173,7 +171,7 @@ describe('nodesRegistrationFinished', () => {
|
|||
mockGetIntl
|
||||
);
|
||||
|
||||
expect(browserHistory.push).toHaveBeenCalledWith('/nodes/register');
|
||||
expect(mockHistory.push).toHaveBeenCalledWith('/nodes/register');
|
||||
expect(NodesActions.addNodes).toHaveBeenCalledWith(
|
||||
normalizedRegisteredNodes
|
||||
);
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export default {
|
||||
push: jest.fn()
|
||||
};
|
|
@ -20,7 +20,6 @@ import yaml from 'js-yaml';
|
|||
|
||||
import EnvironmentConfigurationConstants
|
||||
from '../constants/EnvironmentConfigurationConstants';
|
||||
import { browserHistory } from 'react-router';
|
||||
import MistralApiService from '../services/MistralApiService';
|
||||
import NotificationActions from '../actions/NotificationActions';
|
||||
import MistralApiErrorHandler from '../services/MistralApiErrorHandler';
|
||||
|
@ -42,7 +41,7 @@ const messages = defineMessages({
|
|||
});
|
||||
|
||||
export default {
|
||||
fetchEnvironmentConfiguration(planName, redirectPath) {
|
||||
fetchEnvironmentConfiguration(planName, redirect) {
|
||||
return dispatch => {
|
||||
dispatch(this.fetchEnvironmentConfigurationPending());
|
||||
MistralApiService.runAction(MistralConstants.CAPABILITIES_GET, {
|
||||
|
@ -60,8 +59,8 @@ export default {
|
|||
'Error retrieving EnvironmentConfigurationActions.fetchEnvironment',
|
||||
error.stack || error
|
||||
);
|
||||
if (redirectPath) {
|
||||
browserHistory.push(redirectPath);
|
||||
if (redirect) {
|
||||
redirect();
|
||||
}
|
||||
dispatch(this.fetchEnvironmentConfigurationFailed());
|
||||
let errorHandler = new MistralApiErrorHandler(error);
|
||||
|
@ -91,7 +90,7 @@ export default {
|
|||
};
|
||||
},
|
||||
|
||||
updateEnvironmentConfiguration(planName, data, formFields, redirectPath) {
|
||||
updateEnvironmentConfiguration(planName, data, formFields) {
|
||||
return (dispatch, getState, { getIntl }) => {
|
||||
const { formatMessage } = getIntl(getState());
|
||||
dispatch(this.updateEnvironmentConfigurationPending());
|
||||
|
@ -104,9 +103,6 @@ export default {
|
|||
response.output
|
||||
).result.environments.map(env => env.path);
|
||||
dispatch(this.updateEnvironmentConfigurationSuccess(enabledEnvs));
|
||||
if (redirectPath) {
|
||||
browserHistory.push(redirectPath);
|
||||
}
|
||||
dispatch(
|
||||
NotificationActions.notify({
|
||||
title: formatMessage(messages.envConfigUpdatedNotificationTitle),
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { browserHistory } from 'react-router';
|
||||
import { Map, fromJS } from 'immutable';
|
||||
|
||||
import KeystoneApiErrorHandler from '../services/KeystoneApiErrorHandler';
|
||||
|
@ -35,8 +34,6 @@ export default {
|
|||
response.token.id = tokenId;
|
||||
cookie.save('keystoneAuthTokenId', tokenId, { path: '/' });
|
||||
dispatch(this.userAuthSuccess(response.token));
|
||||
ZaqarWebSocketService.init(getState, dispatch);
|
||||
browserHistory.push(nextPath);
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error(
|
||||
|
@ -45,10 +42,6 @@ export default {
|
|||
);
|
||||
let errorHandler = new KeystoneApiErrorHandler(error);
|
||||
cookie.remove('keystoneAuthTokenId');
|
||||
browserHistory.push({
|
||||
pathname: '/login',
|
||||
query: { nextPath: nextPath }
|
||||
});
|
||||
dispatch(this.userAuthFailure(errorHandler.errors));
|
||||
});
|
||||
};
|
||||
|
@ -64,8 +57,6 @@ export default {
|
|||
response.token.id = tokenId;
|
||||
cookie.save('keystoneAuthTokenId', tokenId, { path: '/' });
|
||||
dispatch(this.userAuthSuccess(response.token));
|
||||
ZaqarWebSocketService.init(getState, dispatch);
|
||||
browserHistory.push(nextPath);
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error(
|
||||
|
@ -109,7 +100,6 @@ export default {
|
|||
|
||||
logoutUser() {
|
||||
return dispatch => {
|
||||
browserHistory.push('/login');
|
||||
cookie.remove('keystoneAuthTokenId');
|
||||
ZaqarWebSocketService.close();
|
||||
dispatch(this.logoutUserSuccess());
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
*/
|
||||
|
||||
import { defineMessages } from 'react-intl';
|
||||
import { browserHistory } from 'react-router';
|
||||
import { startSubmit, stopSubmit } from 'redux-form';
|
||||
|
||||
import NotificationActions from '../actions/NotificationActions';
|
||||
|
@ -56,7 +55,7 @@ export default {
|
|||
};
|
||||
},
|
||||
|
||||
fetchParameters(planName, parentPath) {
|
||||
fetchParameters(planName, redirect) {
|
||||
return dispatch => {
|
||||
dispatch(this.fetchParametersPending());
|
||||
MistralApiService.runAction(MistralConstants.PARAMETERS_GET, {
|
||||
|
@ -78,8 +77,8 @@ export default {
|
|||
})
|
||||
.catch(error => {
|
||||
dispatch(this.fetchParametersFailed());
|
||||
if (parentPath) {
|
||||
browserHistory.push(parentPath);
|
||||
if (redirect) {
|
||||
redirect();
|
||||
}
|
||||
logger.error(
|
||||
'Error in ParametersActions.fetchParameters',
|
||||
|
@ -116,7 +115,7 @@ export default {
|
|||
};
|
||||
},
|
||||
|
||||
updateParameters(planName, data, inputFieldNames, url) {
|
||||
updateParameters(planName, data, inputFieldNames, redirect) {
|
||||
return (dispatch, getState, { getIntl }) => {
|
||||
const { formatMessage } = getIntl(getState());
|
||||
dispatch(startSubmit('nodesAssignment'));
|
||||
|
@ -137,8 +136,8 @@ export default {
|
|||
type: 'success'
|
||||
})
|
||||
);
|
||||
if (url) {
|
||||
browserHistory.push(url);
|
||||
if (redirect) {
|
||||
redirect();
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
|
|
|
@ -20,7 +20,6 @@ import { normalize, arrayOf } from 'normalizr';
|
|||
import when from 'when';
|
||||
|
||||
import CurrentPlanActions from '../actions/CurrentPlanActions';
|
||||
import { browserHistory } from 'react-router';
|
||||
import logger from '../services/logger';
|
||||
import MistralApiService from '../services/MistralApiService';
|
||||
import MistralApiErrorHandler from '../services/MistralApiErrorHandler';
|
||||
|
@ -166,14 +165,14 @@ export default {
|
|||
};
|
||||
},
|
||||
|
||||
updatePlan(planName, planFiles) {
|
||||
updatePlan(planName, planFiles, history) {
|
||||
return (dispatch, getState, { getIntl }) => {
|
||||
const { formatMessage } = getIntl(getState());
|
||||
dispatch(this.updatePlanPending(planName));
|
||||
this._uploadFilesToContainer(planName, fromJS(planFiles), dispatch)
|
||||
.then(() => {
|
||||
dispatch(this.updatePlanSuccess(planName));
|
||||
browserHistory.push('/plans/list');
|
||||
history.push('/plans');
|
||||
dispatch(
|
||||
NotificationActions.notify({
|
||||
title: formatMessage(messages.planUpdatedNotificationTitle),
|
||||
|
@ -195,14 +194,14 @@ export default {
|
|||
};
|
||||
},
|
||||
|
||||
updatePlanFromTarball(planName, file) {
|
||||
updatePlanFromTarball(planName, file, history) {
|
||||
return (dispatch, getState, { getIntl }) => {
|
||||
const { formatMessage } = getIntl(getState());
|
||||
dispatch(this.updatePlanPending(planName));
|
||||
SwiftApiService.uploadTarball(planName, file)
|
||||
.then(response => {
|
||||
dispatch(this.updatePlanSuccess(planName));
|
||||
browserHistory.push('/plans/list');
|
||||
history.push('/plans');
|
||||
dispatch(
|
||||
NotificationActions.notify({
|
||||
title: formatMessage(messages.planUpdatedNotificationTitle),
|
||||
|
@ -335,7 +334,7 @@ export default {
|
|||
};
|
||||
},
|
||||
|
||||
createPlanFinished(payload) {
|
||||
createPlanFinished(payload, history) {
|
||||
return (dispatch, getState, { getIntl }) => {
|
||||
const { formatMessage } = getIntl(getState());
|
||||
if (payload.status === 'SUCCESS') {
|
||||
|
@ -351,7 +350,7 @@ export default {
|
|||
})
|
||||
);
|
||||
dispatch(this.fetchPlans());
|
||||
browserHistory.push('/plans/list');
|
||||
history.push('/plans');
|
||||
} else {
|
||||
dispatch(
|
||||
this.createPlanFailed([{ title: 'Error', message: payload.message }])
|
||||
|
@ -436,11 +435,11 @@ export default {
|
|||
};
|
||||
},
|
||||
|
||||
deletePlan(planName) {
|
||||
deletePlan(planName, history) {
|
||||
return (dispatch, getState, { getIntl }) => {
|
||||
const { formatMessage } = getIntl(getState());
|
||||
dispatch(this.deletePlanPending(planName));
|
||||
browserHistory.push('/plans/list');
|
||||
history.push('/plans');
|
||||
MistralApiService.runAction(MistralConstants.PLAN_DELETE, {
|
||||
container: planName
|
||||
})
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { browserHistory } from 'react-router';
|
||||
import { defineMessages } from 'react-intl';
|
||||
import { normalize, arrayOf } from 'normalizr';
|
||||
import { Map } from 'immutable';
|
||||
|
@ -69,7 +68,7 @@ export default {
|
|||
};
|
||||
},
|
||||
|
||||
startNodesRegistration(nodes, redirectPath) {
|
||||
startNodesRegistration(nodes) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(this.startNodesRegistrationPending(nodes));
|
||||
MistralApiService.runWorkflow(
|
||||
|
@ -124,7 +123,7 @@ export default {
|
|||
};
|
||||
},
|
||||
|
||||
nodesRegistrationFinished(messagePayload) {
|
||||
nodesRegistrationFinished(messagePayload, history) {
|
||||
return (dispatch, getState, { getIntl }) => {
|
||||
const { formatMessage } = getIntl(getState());
|
||||
const registeredNodes =
|
||||
|
@ -152,7 +151,7 @@ export default {
|
|||
})
|
||||
);
|
||||
dispatch(this.nodesRegistrationSuccess());
|
||||
browserHistory.push('/nodes');
|
||||
history.push('/nodes');
|
||||
break;
|
||||
}
|
||||
case 'FAILED': {
|
||||
|
@ -162,7 +161,7 @@ export default {
|
|||
message: JSON.stringify(messagePayload.message)
|
||||
}
|
||||
];
|
||||
browserHistory.push('/nodes/register');
|
||||
history.push('/nodes/register');
|
||||
// TODO(jtomasek): repopulate nodes registration form with failed nodes provided by message
|
||||
dispatch(this.nodesRegistrationFailed(errors));
|
||||
break;
|
||||
|
|
|
@ -19,14 +19,23 @@ import PlansActions from './PlansActions';
|
|||
import RegisterNodesActions from './RegisterNodesActions';
|
||||
import ValidationsActions from './ValidationsActions';
|
||||
import MistralConstants from '../constants/MistralConstants';
|
||||
import ZaqarWebSocketService from '../services/ZaqarWebSocketService';
|
||||
|
||||
export default {
|
||||
messageReceived(message) {
|
||||
initializeConnection(history) {
|
||||
return (dispatch, getState) => {
|
||||
ZaqarWebSocketService.init(getState, dispatch, history);
|
||||
};
|
||||
},
|
||||
|
||||
messageReceived(message, history) {
|
||||
return (dispatch, getState) => {
|
||||
const { type, payload } = message.body;
|
||||
switch (type) {
|
||||
case MistralConstants.BAREMETAL_REGISTER_OR_UPDATE:
|
||||
dispatch(RegisterNodesActions.nodesRegistrationFinished(payload));
|
||||
dispatch(
|
||||
RegisterNodesActions.nodesRegistrationFinished(payload, history)
|
||||
);
|
||||
break;
|
||||
|
||||
case MistralConstants.BAREMETAL_INTROSPECT:
|
||||
|
@ -43,7 +52,7 @@ export default {
|
|||
}
|
||||
|
||||
case MistralConstants.PLAN_CREATE: {
|
||||
dispatch(PlansActions.createPlanFinished(payload));
|
||||
dispatch(PlansActions.createPlanFinished(payload, history));
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -14,18 +14,19 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
|
||||
import Login from './Login';
|
||||
import UserAuthenticator from './UserAuthenticator';
|
||||
|
||||
export default class App extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{this.props.children}
|
||||
</div>
|
||||
<Switch>
|
||||
<Route path="/login" component={Login} />
|
||||
<Route path="/" component={UserAuthenticator} />
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
}
|
||||
App.propTypes = {
|
||||
children: PropTypes.element
|
||||
};
|
||||
|
|
|
@ -19,13 +19,18 @@ import { defineMessages, injectIntl } from 'react-intl';
|
|||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { Redirect, Route, Switch, withRouter } from 'react-router-dom';
|
||||
|
||||
import DeploymentPlan from './deployment_plan/DeploymentPlan';
|
||||
import Loader from './ui/Loader';
|
||||
import LoginActions from '../actions/LoginActions';
|
||||
import PlansActions from '../actions/PlansActions';
|
||||
import NavBar from './NavBar';
|
||||
import Nodes from './nodes/Nodes';
|
||||
import Plans from './plan/Plans.js';
|
||||
import PlansActions from '../actions/PlansActions';
|
||||
import ValidationsList from './validations/ValidationsList';
|
||||
import WorkflowExecutionsActions from '../actions/WorkflowExecutionsActions';
|
||||
import ZaqarActions from '../actions/ZaqarActions';
|
||||
|
||||
const messages = defineMessages({
|
||||
loadingDeployments: {
|
||||
|
@ -38,27 +43,37 @@ class AuthenticatedContent extends React.Component {
|
|||
componentDidMount() {
|
||||
this.props.fetchPlans();
|
||||
this.props.fetchWorkflowExecutions();
|
||||
this.props.initializeZaqarConnection();
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
currentPlanName,
|
||||
intl,
|
||||
logoutUser,
|
||||
noPlans,
|
||||
plansLoaded,
|
||||
user
|
||||
} = this.props;
|
||||
return (
|
||||
<Loader
|
||||
loaded={
|
||||
this.props.plansLoaded &&
|
||||
(!!this.props.currentPlanName || this.props.noPlans)
|
||||
}
|
||||
content={this.props.intl.formatMessage(messages.loadingDeployments)}
|
||||
loaded={plansLoaded && (!!currentPlanName || noPlans)}
|
||||
content={intl.formatMessage(messages.loadingDeployments)}
|
||||
global
|
||||
>
|
||||
<header>
|
||||
<NavBar
|
||||
user={this.props.user}
|
||||
onLogout={this.props.logoutUser.bind(this)}
|
||||
/>
|
||||
<NavBar user={user} onLogout={logoutUser.bind(this)} />
|
||||
</header>
|
||||
<div className="wrapper-fixed-body container-fluid">
|
||||
<div className="row">
|
||||
<div className="col-sm-12 col-lg-9">{this.props.children}</div>
|
||||
<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" />
|
||||
</Switch>
|
||||
</div>
|
||||
<ValidationsList />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -72,6 +87,7 @@ AuthenticatedContent.propTypes = {
|
|||
dispatch: PropTypes.func,
|
||||
fetchPlans: PropTypes.func,
|
||||
fetchWorkflowExecutions: PropTypes.func,
|
||||
initializeZaqarConnection: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object,
|
||||
logoutUser: PropTypes.func.isRequired,
|
||||
noPlans: PropTypes.bool,
|
||||
|
@ -79,24 +95,22 @@ AuthenticatedContent.propTypes = {
|
|||
user: ImmutablePropTypes.map
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
logoutUser: () => dispatch(LoginActions.logoutUser()),
|
||||
fetchPlans: () => dispatch(PlansActions.fetchPlans()),
|
||||
fetchWorkflowExecutions: () =>
|
||||
dispatch(WorkflowExecutionsActions.fetchWorkflowExecutions())
|
||||
};
|
||||
};
|
||||
const mapDispatchToProps = (dispatch, ownProps) => ({
|
||||
logoutUser: () => dispatch(LoginActions.logoutUser()),
|
||||
fetchPlans: () => dispatch(PlansActions.fetchPlans()),
|
||||
fetchWorkflowExecutions: () =>
|
||||
dispatch(WorkflowExecutionsActions.fetchWorkflowExecutions()),
|
||||
initializeZaqarConnection: () =>
|
||||
dispatch(ZaqarActions.initializeConnection(ownProps.history))
|
||||
});
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
currentPlanName: state.currentPlan.currentPlanName,
|
||||
noPlans: state.plans.get('all').isEmpty(),
|
||||
plansLoaded: state.plans.get('plansLoaded'),
|
||||
user: state.login.getIn(['token', 'user'])
|
||||
};
|
||||
};
|
||||
const mapStateToProps = state => ({
|
||||
currentPlanName: state.currentPlan.currentPlanName,
|
||||
noPlans: state.plans.get('all').isEmpty(),
|
||||
plansLoaded: state.plans.get('plansLoaded'),
|
||||
user: state.login.getIn(['token', 'user'])
|
||||
});
|
||||
|
||||
export default injectIntl(
|
||||
connect(mapStateToProps, mapDispatchToProps)(AuthenticatedContent)
|
||||
withRouter(connect(mapStateToProps, mapDispatchToProps)(AuthenticatedContent))
|
||||
);
|
||||
|
|
|
@ -21,8 +21,10 @@ import Formsy from 'formsy-react';
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
|
||||
import FormErrorList from './ui/forms/FormErrorList';
|
||||
import Loader from './ui/Loader';
|
||||
import LoginInput from './ui/forms/LoginInput';
|
||||
import LoginActions from '../actions/LoginActions';
|
||||
import NotificationsToaster from './notifications/NotificationsToaster';
|
||||
|
@ -31,6 +33,10 @@ import LogoSvg from '../../img/logo.svg';
|
|||
import TripleoOwlSvg from '../../img/tripleo-owl.svg';
|
||||
|
||||
const messages = defineMessages({
|
||||
authenticating: {
|
||||
id: 'UserAuthenticator.authenticating',
|
||||
defaultMessage: 'Authenticating...'
|
||||
},
|
||||
username: {
|
||||
id: 'Login.username',
|
||||
defaultMessage: 'Username'
|
||||
|
@ -74,7 +80,9 @@ class Login extends React.Component {
|
|||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.invalidateLoginForm(this.props.formFieldErrors.toJS());
|
||||
if (!this.props.isAuthenticated || this.props.isAuthenticating) {
|
||||
this.invalidateLoginForm(this.props.formFieldErrors.toJS());
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -94,83 +102,93 @@ class Login extends React.Component {
|
|||
}
|
||||
|
||||
handleLogin(formData, resetForm, invalidateForm) {
|
||||
const nextPath = this.props.location.query.nextPath || '/';
|
||||
const formFields = Object.keys(this.refs.form.inputs);
|
||||
this.props.dispatch(
|
||||
LoginActions.authenticateUser(formData, formFields, nextPath)
|
||||
);
|
||||
this.props.dispatch(LoginActions.authenticateUser(formData, formFields));
|
||||
}
|
||||
|
||||
render() {
|
||||
const { formatMessage } = this.props.intl;
|
||||
const { from } = this.props.location.state || { from: { pathname: '/' } };
|
||||
const {
|
||||
formErrors,
|
||||
isAuthenticating,
|
||||
isAuthenticated,
|
||||
intl: { formatMessage }
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<span id="badge">
|
||||
<img src={LogoSvg} alt="TripleO" />
|
||||
</span>
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<div className="col-sm-12">
|
||||
<div id="brand">
|
||||
<img src={TripleoOwlSvg} alt="TripleO" />
|
||||
if (!isAuthenticated) {
|
||||
return (
|
||||
<div>
|
||||
<span id="badge">
|
||||
<img src={LogoSvg} alt="TripleO" />
|
||||
</span>
|
||||
<Loader
|
||||
content={formatMessage(messages.authenticating)}
|
||||
loaded={!isAuthenticating}
|
||||
global
|
||||
/>
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<div className="col-sm-12">
|
||||
<div id="brand">
|
||||
<img src={TripleoOwlSvg} alt="TripleO" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-sm-7 col-md-6 col-lg-5 login">
|
||||
<FormErrorList errors={formErrors.toJS()} />
|
||||
<Formsy.Form
|
||||
ref="form"
|
||||
role="form"
|
||||
className="form-horizontal"
|
||||
onSubmit={this.handleLogin.bind(this)}
|
||||
onValid={this._enableButton.bind(this)}
|
||||
onInvalid={this._disableButton.bind(this)}
|
||||
>
|
||||
<LoginInput
|
||||
name="username"
|
||||
placeholder={formatMessage(messages.username)}
|
||||
title={formatMessage(messages.username)}
|
||||
validationError={formatMessage(messages.usernameRequired)}
|
||||
required
|
||||
autoFocus
|
||||
/>
|
||||
<LoginInput
|
||||
type="password"
|
||||
name="password"
|
||||
placeholder={formatMessage(messages.password)}
|
||||
title={formatMessage(messages.password)}
|
||||
validationError={formatMessage(messages.passwordRequired)}
|
||||
required
|
||||
/>
|
||||
<div className="form-group">
|
||||
<div className="col-xs-offset-8 col-xs-4 col-sm-4 col-md-4 submit">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!this.state.canSubmit || isAuthenticating}
|
||||
className="btn btn-primary btn-lg"
|
||||
tabIndex="4"
|
||||
id="Login__loginButton"
|
||||
>
|
||||
<FormattedMessage {...messages.login} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Formsy.Form>
|
||||
</div>
|
||||
<div className="col-sm-5 col-md-6 col-lg-7 details">
|
||||
<p>
|
||||
<strong><FormattedMessage {...messages.welcome} /></strong>
|
||||
<br />
|
||||
<FormattedMessage {...messages.description} />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-sm-7 col-md-6 col-lg-5 login">
|
||||
<FormErrorList errors={this.props.formErrors.toJS()} />
|
||||
<Formsy.Form
|
||||
ref="form"
|
||||
role="form"
|
||||
className="form-horizontal"
|
||||
onSubmit={this.handleLogin.bind(this)}
|
||||
onValid={this._enableButton.bind(this)}
|
||||
onInvalid={this._disableButton.bind(this)}
|
||||
>
|
||||
<LoginInput
|
||||
name="username"
|
||||
placeholder={formatMessage(messages.username)}
|
||||
title={formatMessage(messages.username)}
|
||||
validationError={formatMessage(messages.usernameRequired)}
|
||||
required
|
||||
autoFocus
|
||||
/>
|
||||
<LoginInput
|
||||
type="password"
|
||||
name="password"
|
||||
placeholder={formatMessage(messages.password)}
|
||||
title={formatMessage(messages.password)}
|
||||
validationError={formatMessage(messages.passwordRequired)}
|
||||
required
|
||||
/>
|
||||
<div className="form-group">
|
||||
<div className="col-xs-offset-8 col-xs-4 col-sm-4 col-md-4 submit">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={
|
||||
!this.state.canSubmit || this.props.isAuthenticating
|
||||
}
|
||||
className="btn btn-primary btn-lg"
|
||||
tabIndex="4"
|
||||
id="Login__loginButton"
|
||||
>
|
||||
<FormattedMessage {...messages.login} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Formsy.Form>
|
||||
</div>
|
||||
<div className="col-sm-5 col-md-6 col-lg-7 details">
|
||||
<p>
|
||||
<strong><FormattedMessage {...messages.welcome} /></strong>
|
||||
<br />
|
||||
<FormattedMessage {...messages.description} />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<NotificationsToaster />
|
||||
</div>
|
||||
<NotificationsToaster />
|
||||
</div>
|
||||
);
|
||||
);
|
||||
} else {
|
||||
return <Redirect to={from} />;
|
||||
}
|
||||
}
|
||||
}
|
||||
Login.propTypes = {
|
||||
|
@ -178,17 +196,17 @@ Login.propTypes = {
|
|||
formErrors: ImmutablePropTypes.list.isRequired,
|
||||
formFieldErrors: ImmutablePropTypes.map.isRequired,
|
||||
intl: PropTypes.object,
|
||||
isAuthenticated: PropTypes.bool.isRequired,
|
||||
isAuthenticating: PropTypes.bool.isRequired,
|
||||
location: PropTypes.object,
|
||||
userLoggedIn: PropTypes.bool.isRequired
|
||||
location: PropTypes.object
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
formErrors: state.login.getIn(['loginForm', 'formErrors']),
|
||||
formFieldErrors: state.login.getIn(['loginForm', 'formFieldErrors']),
|
||||
userLoggedIn: state.login.hasIn(['keystoneAccess', 'user']),
|
||||
isAuthenticating: state.login.get('isAuthenticating')
|
||||
isAuthenticated: state.login.isAuthenticated,
|
||||
isAuthenticating: state.login.isAuthenticating
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
|
||||
import { getAppConfig } from '../services/utils';
|
||||
|
|
|
@ -15,11 +15,15 @@
|
|||
*/
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import cookie from 'react-cookie';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
|
||||
import AuthenticatedContent from './AuthenticatedContent';
|
||||
import Loader from './ui/Loader';
|
||||
import LoginActions from '../actions/LoginActions';
|
||||
import NotificationsToaster from './notifications/NotificationsToaster';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -30,37 +34,71 @@ const messages = defineMessages({
|
|||
});
|
||||
|
||||
/**
|
||||
* Takes care of authenticating user. User Authentication is triggered in routes
|
||||
* 'onEnter' to this component. After authentication is resolved, component children
|
||||
* are rendered. No Actions calling API services can be dispatched from this component
|
||||
* Takes care of authenticating user. After authentication is resolved, AuthenticatedContent
|
||||
* is rendered. No Actions calling API services except Keystone can be dispatched from this
|
||||
* component
|
||||
*/
|
||||
class UserAuthenticator extends React.Component {
|
||||
componentWillMount() {
|
||||
this.checkAuth(this.props);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.checkAuth(nextProps);
|
||||
}
|
||||
|
||||
checkAuth(props) {
|
||||
const { isAuthenticated, isAuthenticating } = props;
|
||||
const keystoneAuthTokenId = cookie.load('keystoneAuthTokenId');
|
||||
if (!isAuthenticated && !isAuthenticating && keystoneAuthTokenId) {
|
||||
this.props.authenticateUserViaToken(keystoneAuthTokenId);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Loader
|
||||
loaded={this.props.isAuthenticated}
|
||||
content={this.props.intl.formatMessage(messages.authenticating)}
|
||||
global
|
||||
>
|
||||
{this.props.children}
|
||||
</Loader>
|
||||
<NotificationsToaster />
|
||||
</div>
|
||||
);
|
||||
const { isAuthenticating, isAuthenticated, location } = this.props;
|
||||
const keystoneAuthTokenId = cookie.load('keystoneAuthTokenId');
|
||||
|
||||
if (isAuthenticated || isAuthenticating || keystoneAuthTokenId) {
|
||||
return (
|
||||
<div>
|
||||
<Loader
|
||||
loaded={this.props.isAuthenticated}
|
||||
content={this.props.intl.formatMessage(messages.authenticating)}
|
||||
global
|
||||
>
|
||||
<AuthenticatedContent />
|
||||
</Loader>
|
||||
<NotificationsToaster />
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Redirect to={{ pathname: '/login', state: { from: location } }} />
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
UserAuthenticator.propTypes = {
|
||||
children: PropTypes.node,
|
||||
dispatch: PropTypes.func,
|
||||
authenticateUserViaToken: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object,
|
||||
isAuthenticated: PropTypes.bool.isRequired
|
||||
isAuthenticated: PropTypes.bool.isRequired,
|
||||
isAuthenticating: PropTypes.bool.isRequired,
|
||||
location: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
isAuthenticated: state.login.isAuthenticated
|
||||
isAuthenticated: state.login.isAuthenticated,
|
||||
isAuthenticating: state.login.isAuthenticating
|
||||
};
|
||||
};
|
||||
|
||||
export default injectIntl(connect(mapStateToProps)(UserAuthenticator));
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
authenticateUserViaToken: tokenId =>
|
||||
dispatch(LoginActions.authenticateUserViaToken(tokenId))
|
||||
});
|
||||
|
||||
export default injectIntl(
|
||||
connect(mapStateToProps, mapDispatchToProps)(UserAuthenticator)
|
||||
);
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { Link } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
import { Link } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
|
|
|
@ -15,12 +15,16 @@
|
|||
*/
|
||||
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
import { Link } from 'react-router';
|
||||
import { Link, Redirect, Route, Switch } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import { checkRunningDeployment } from '../utils/checkRunningDeploymentHOC';
|
||||
import EnvironmentConfiguration
|
||||
from '../environment_configuration/EnvironmentConfiguration';
|
||||
import NavTab from '../ui/NavTab';
|
||||
import Modal from '../ui/Modal';
|
||||
import Parameters from '../parameters/Parameters';
|
||||
|
||||
const messages = defineMessages({
|
||||
deploymentConfiguration: {
|
||||
|
@ -37,12 +41,13 @@ const messages = defineMessages({
|
|||
}
|
||||
});
|
||||
|
||||
export default class DeploymentConfiguration extends React.Component {
|
||||
class DeploymentConfiguration extends React.Component {
|
||||
render() {
|
||||
const { location } = this.props;
|
||||
return (
|
||||
<Modal dialogClasses="modal-xl">
|
||||
<div className="modal-header">
|
||||
<Link to={this.props.parentPath} type="button" className="close">
|
||||
<Link to="/deployment-plan" type="button" className="close">
|
||||
<span aria-hidden="true" className="pficon pficon-close" />
|
||||
</Link>
|
||||
<h4 className="modal-title">
|
||||
|
@ -51,24 +56,42 @@ export default class DeploymentConfiguration extends React.Component {
|
|||
</div>
|
||||
|
||||
<ul className="nav nav-tabs">
|
||||
<NavTab to="/deployment-plan/configuration/environment">
|
||||
<NavTab
|
||||
location={location}
|
||||
to="/deployment-plan/configuration/environment"
|
||||
>
|
||||
<FormattedMessage {...messages.overallSettings} />
|
||||
</NavTab>
|
||||
<NavTab to="/deployment-plan/configuration/parameters">
|
||||
<NavTab
|
||||
location={location}
|
||||
to="/deployment-plan/configuration/parameters"
|
||||
>
|
||||
<FormattedMessage {...messages.parameters} />
|
||||
</NavTab>
|
||||
</ul>
|
||||
|
||||
{React.cloneElement(this.props.children, {
|
||||
currentPlanName: this.props.currentPlanName,
|
||||
parentPath: this.props.parentPath
|
||||
})}
|
||||
<Switch location={location}>
|
||||
<Route
|
||||
location={location}
|
||||
path="/deployment-plan/configuration/environment"
|
||||
component={EnvironmentConfiguration}
|
||||
/>
|
||||
<Route
|
||||
location={location}
|
||||
path="/deployment-plan/configuration/parameters"
|
||||
component={Parameters}
|
||||
/>
|
||||
<Redirect
|
||||
from="/deployment-plan/configuration"
|
||||
to="/deployment-plan/configuration/environment"
|
||||
/>
|
||||
</Switch>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
DeploymentConfiguration.propTypes = {
|
||||
children: PropTypes.node,
|
||||
currentPlanName: PropTypes.string,
|
||||
parentPath: PropTypes.string
|
||||
location: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default checkRunningDeployment(DeploymentConfiguration);
|
||||
|
|
|
@ -18,7 +18,7 @@ import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { Link } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import DeleteStackButton from './DeleteStackButton';
|
||||
import InlineNotification from '../ui/InlineNotification';
|
||||
|
|
|
@ -19,7 +19,10 @@ 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 DeploymentConfiguration from './DeploymentConfiguration';
|
||||
import DeploymentDetail from '../deployment/DeploymentDetail';
|
||||
import { getAllPlansButCurrent } from '../../selectors/plans';
|
||||
import {
|
||||
getCurrentStack,
|
||||
|
@ -51,10 +54,11 @@ import NoPlans from './NoPlans';
|
|||
import NotificationActions from '../../actions/NotificationActions';
|
||||
import ParametersActions from '../../actions/ParametersActions';
|
||||
import PlansActions from '../../actions/PlansActions';
|
||||
import StacksActions from '../../actions/StacksActions';
|
||||
import stackStates from '../../constants/StacksConstants';
|
||||
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({
|
||||
|
@ -169,19 +173,10 @@ class DeploymentPlan extends React.Component {
|
|||
|
||||
render() {
|
||||
const { formatMessage } = this.props.intl;
|
||||
let children;
|
||||
const currentPlanName = this.props.hasPlans
|
||||
? this.props.currentPlan.name
|
||||
: undefined;
|
||||
|
||||
// Render children only when current plan is already selected
|
||||
if (this.props.children && currentPlanName) {
|
||||
children = React.cloneElement(this.props.children, {
|
||||
currentPlanName: currentPlanName,
|
||||
parentPath: '/' + this.props.route.path
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="row">
|
||||
{this.props.hasPlans
|
||||
|
@ -272,7 +267,18 @@ class DeploymentPlan extends React.Component {
|
|||
: <div className="col-sm-12">
|
||||
<NoPlans />
|
||||
</div>}
|
||||
{children}
|
||||
<Route
|
||||
path="/deployment-plan/configuration"
|
||||
component={DeploymentConfiguration}
|
||||
/>
|
||||
<Route
|
||||
path="/deployment-plan/roles/:roleIdentifier"
|
||||
component={RoleDetail}
|
||||
/>
|
||||
<Route
|
||||
path="/deployment-plan/deployment-detail"
|
||||
component={DeploymentDetail}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -281,7 +287,6 @@ class DeploymentPlan extends React.Component {
|
|||
DeploymentPlan.propTypes = {
|
||||
availableNodes: ImmutablePropTypes.map,
|
||||
availableNodesCountsByRole: ImmutablePropTypes.map.isRequired,
|
||||
children: PropTypes.node,
|
||||
choosePlan: PropTypes.func,
|
||||
currentPlan: ImmutablePropTypes.record,
|
||||
currentStack: ImmutablePropTypes.record,
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { Link } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
import { Link } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
import React from 'react';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
import { Link } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
import React from 'react';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -43,11 +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"
|
||||
query={{ tab: 'newPlan' }}
|
||||
className="btn btn-lg btn-primary"
|
||||
>
|
||||
<Link to="/plans/new" className="btn btn-lg btn-primary">
|
||||
<span className="fa fa-plus" />
|
||||
{' '}
|
||||
<FormattedMessage {...messages.createNewPlan} />
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { Link } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
|
@ -52,7 +52,7 @@ export default class PlansDropdown extends React.Component {
|
|||
render() {
|
||||
if (this.props.plans.isEmpty()) {
|
||||
return (
|
||||
<Link className="btn btn-link" to="/plans/list">
|
||||
<Link className="btn btn-link" to="/plans">
|
||||
<FormattedMessage {...messages.manageDeployments} />
|
||||
</Link>
|
||||
);
|
||||
|
@ -64,7 +64,7 @@ export default class PlansDropdown extends React.Component {
|
|||
</DropdownButton>
|
||||
{this.renderRecentPlans()}
|
||||
<DropdownItem key="divider" divider />
|
||||
<DropdownItem key="plansLink" to="/plans/list">
|
||||
<DropdownItem key="plansLink" to="/plans">
|
||||
<FormattedMessage {...messages.manageDeployments} />
|
||||
</DropdownItem>
|
||||
</Dropdown>
|
||||
|
|
|
@ -19,7 +19,7 @@ import { connect } from 'react-redux';
|
|||
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
||||
import Formsy from 'formsy-react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { Link, browserHistory } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
|
@ -58,9 +58,8 @@ class EnvironmentConfiguration extends React.Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.fetchEnvironmentConfiguration(
|
||||
this.props.currentPlanName,
|
||||
this.props.parentPath
|
||||
this.props.fetchEnvironmentConfiguration(this.props.currentPlanName, () =>
|
||||
this.props.history.push('/deployment-plan')
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -111,7 +110,7 @@ class EnvironmentConfiguration extends React.Component {
|
|||
closeOnSubmit: false
|
||||
});
|
||||
|
||||
browserHistory.push(this.props.parentPath);
|
||||
this.props.history.push('/deployment-plan');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -210,11 +209,7 @@ class EnvironmentConfiguration extends React.Component {
|
|||
>
|
||||
<FormattedMessage {...messages.saveAndClose} />
|
||||
</button>
|
||||
<Link
|
||||
to={this.props.parentPath}
|
||||
type="button"
|
||||
className="btn btn-default"
|
||||
>
|
||||
<Link to="/deployment-plan" type="button" className="btn btn-default">
|
||||
<FormattedMessage {...messages.cancel} />
|
||||
</Link>
|
||||
</div>
|
||||
|
@ -229,16 +224,12 @@ EnvironmentConfiguration.propTypes = {
|
|||
fetchEnvironmentConfiguration: PropTypes.func,
|
||||
formErrors: ImmutablePropTypes.list.isRequired,
|
||||
formFieldErrors: ImmutablePropTypes.map.isRequired,
|
||||
history: PropTypes.object.isRequired,
|
||||
isFetching: PropTypes.bool,
|
||||
location: PropTypes.object,
|
||||
parentPath: PropTypes.string.isRequired,
|
||||
updateEnvironmentConfiguration: PropTypes.func
|
||||
};
|
||||
|
||||
EnvironmentConfiguration.defaultProps = {
|
||||
parentPath: '/deployment-plan'
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
currentPlanName: state.currentPlan.currentPlanName,
|
||||
|
@ -259,18 +250,12 @@ function mapDispatchToProps(dispatch) {
|
|||
EnvironmentConfigurationActions.fetchEnvironmentConfiguration(planName)
|
||||
);
|
||||
},
|
||||
updateEnvironmentConfiguration: (
|
||||
planName,
|
||||
data,
|
||||
inputFields,
|
||||
parentPath
|
||||
) => {
|
||||
updateEnvironmentConfiguration: (planName, data, inputFields) => {
|
||||
dispatch(
|
||||
EnvironmentConfigurationActions.updateEnvironmentConfiguration(
|
||||
planName,
|
||||
data,
|
||||
inputFields,
|
||||
parentPath
|
||||
inputFields
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -17,9 +17,10 @@
|
|||
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { Link } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { Route } from 'react-router-dom';
|
||||
|
||||
import { getFilterByName } from '../../selectors/filters';
|
||||
import { getFilteredNodes, nodesInProgress } from '../../selectors/nodes';
|
||||
|
@ -29,6 +30,7 @@ import NodesListForm from './NodesListView/NodesListForm';
|
|||
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({
|
||||
|
@ -108,13 +110,12 @@ class Nodes extends React.Component {
|
|||
<NodesToolbar />
|
||||
{this.renderContentView()}
|
||||
</Loader>
|
||||
{this.props.children}
|
||||
<Route path="/nodes/register" component={RegisterNodesDialog} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
Nodes.propTypes = {
|
||||
children: PropTypes.node,
|
||||
contentView: PropTypes.string.isRequired,
|
||||
currentPlanName: PropTypes.string.isRequired,
|
||||
fetchNodes: PropTypes.func.isRequired,
|
||||
|
|
|
@ -18,7 +18,7 @@ import { connect } from 'react-redux';
|
|||
import { fromJS } from 'immutable';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ClassNames from 'classnames';
|
||||
import { Link } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
|
|
@ -19,7 +19,7 @@ import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
|||
import Formsy from 'formsy-react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { isObjectLike, mapValues } from 'lodash';
|
||||
import { Link, browserHistory } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { fromJS, is } from 'immutable';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
@ -68,11 +68,10 @@ class Parameters extends React.Component {
|
|||
currentPlanName,
|
||||
fetchEnvironmentConfiguration,
|
||||
fetchParameters,
|
||||
isFetchingParameters,
|
||||
parentPath
|
||||
isFetchingParameters
|
||||
} = this.props;
|
||||
fetchEnvironmentConfiguration(currentPlanName, parentPath);
|
||||
!isFetchingParameters && fetchParameters(currentPlanName, parentPath);
|
||||
fetchEnvironmentConfiguration(currentPlanName);
|
||||
!isFetchingParameters && fetchParameters(currentPlanName);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
|
@ -135,7 +134,7 @@ class Parameters extends React.Component {
|
|||
closeOnSubmit: false
|
||||
});
|
||||
|
||||
browserHistory.push(this.props.parentPath);
|
||||
this.props.history.push('/deployment-plan');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -281,14 +280,9 @@ Parameters.propTypes = {
|
|||
mistralParameters: ImmutablePropTypes.map.isRequired,
|
||||
parameters: ImmutablePropTypes.map.isRequired,
|
||||
parametersLoaded: PropTypes.bool,
|
||||
parentPath: PropTypes.string.isRequired,
|
||||
updateParameters: PropTypes.func
|
||||
};
|
||||
|
||||
Parameters.defaultProps = {
|
||||
parentPath: '/deployment-plan'
|
||||
};
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
allParameters: state.parameters.parameters,
|
||||
|
@ -303,28 +297,30 @@ function mapStateToProps(state, ownProps) {
|
|||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
function mapDispatchToProps(dispatch, ownProps) {
|
||||
return {
|
||||
fetchEnvironmentConfiguration: (currentPlanName, redirectPath) => {
|
||||
fetchEnvironmentConfiguration: currentPlanName => {
|
||||
dispatch(
|
||||
EnvironmentConfigurationActions.fetchEnvironmentConfiguration(
|
||||
currentPlanName,
|
||||
redirectPath
|
||||
() => ownProps.history.push('/deployment-plan')
|
||||
)
|
||||
);
|
||||
},
|
||||
fetchParameters: (currentPlanName, redirectPath) => {
|
||||
fetchParameters: currentPlanName => {
|
||||
dispatch(
|
||||
ParametersActions.fetchParameters(currentPlanName, redirectPath)
|
||||
ParametersActions.fetchParameters(currentPlanName, () =>
|
||||
ownProps.history.push('/deployment-plan')
|
||||
)
|
||||
);
|
||||
},
|
||||
updateParameters: (currentPlanName, data, inputFields, redirectPath) => {
|
||||
updateParameters: (currentPlanName, data, inputFields, redirect) => {
|
||||
dispatch(
|
||||
ParametersActions.updateParameters(
|
||||
currentPlanName,
|
||||
data,
|
||||
inputFields,
|
||||
redirectPath
|
||||
redirect
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ import { connect } from 'react-redux';
|
|||
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import PlansActions from '../../actions/PlansActions';
|
||||
import Modal from '../ui/Modal';
|
||||
|
@ -44,7 +44,7 @@ const messages = defineMessages({
|
|||
|
||||
class DeletePlan extends React.Component {
|
||||
getNameFromUrl() {
|
||||
let planName = this.props.params.planName || '';
|
||||
let planName = this.props.match.params.planName || '';
|
||||
return planName.replace(/[^A-Za-z0-9_-]*/g, '');
|
||||
}
|
||||
|
||||
|
@ -59,7 +59,7 @@ class DeletePlan extends React.Component {
|
|||
return (
|
||||
<Modal dialogClasses="modal-sm" id="DeletePlan__deletePlanModal">
|
||||
<div className="modal-header">
|
||||
<Link to="/plans/list" type="button" className="close">
|
||||
<Link to="/plans" 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/list"
|
||||
to="/plans"
|
||||
type="button"
|
||||
className="btn btn-default"
|
||||
id="DeletePlan__cancelDeletePlanModalButton"
|
||||
|
@ -112,13 +112,14 @@ class DeletePlan extends React.Component {
|
|||
|
||||
DeletePlan.propTypes = {
|
||||
deletePlan: PropTypes.func,
|
||||
match: PropTypes.object,
|
||||
params: PropTypes.object
|
||||
};
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
function mapDispatchToProps(dispatch, ownProps) {
|
||||
return {
|
||||
deletePlan: planName => {
|
||||
dispatch(PlansActions.deletePlan(planName));
|
||||
dispatch(PlansActions.deletePlan(planName, ownProps.history));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ import { connect } from 'react-redux';
|
|||
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
||||
import Formsy from 'formsy-react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { Link } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
|
@ -96,7 +96,7 @@ class EditPlan extends React.Component {
|
|||
}
|
||||
|
||||
getNameFromUrl() {
|
||||
let planName = this.props.params.planName || '';
|
||||
let planName = this.props.match.params.planName || '';
|
||||
return planName.replace(/[^A-Za-z0-9_-]*/g, '');
|
||||
}
|
||||
|
||||
|
@ -116,7 +116,7 @@ class EditPlan extends React.Component {
|
|||
onInvalid={this.onFormInvalid.bind(this)}
|
||||
>
|
||||
<div className="modal-header">
|
||||
<Link to="/plans/list" type="button" className="close">
|
||||
<Link to="/plans" type="button" className="close">
|
||||
<span aria-hidden="true" className="pficon pficon-close" />
|
||||
</Link>
|
||||
<h4>
|
||||
|
@ -135,7 +135,6 @@ class EditPlan extends React.Component {
|
|||
<ModalFormErrorList errors={this.props.planFormErrors.toJS()} />
|
||||
<div className="modal-body">
|
||||
<PlanEditFormTabs
|
||||
currentTab={this.props.location.query.tab || 'editPlan'}
|
||||
selectedFiles={this.state.selectedFiles}
|
||||
planName={this.getNameFromUrl()}
|
||||
planFiles={planFiles}
|
||||
|
@ -152,7 +151,7 @@ class EditPlan extends React.Component {
|
|||
>
|
||||
<FormattedMessage {...messages.uploadAndUpdate} />
|
||||
</button>
|
||||
<Link to="/plans/list" type="button" className="btn btn-default">
|
||||
<Link to="/plans" type="button" className="btn btn-default">
|
||||
<FormattedMessage {...messages.cancel} />
|
||||
</Link>
|
||||
</div>
|
||||
|
@ -167,7 +166,7 @@ EditPlan.propTypes = {
|
|||
history: PropTypes.object,
|
||||
intl: PropTypes.object,
|
||||
isTransitioningPlan: PropTypes.bool,
|
||||
location: PropTypes.object,
|
||||
match: PropTypes.object,
|
||||
params: PropTypes.object,
|
||||
planFormErrors: ImmutablePropTypes.list,
|
||||
plans: ImmutablePropTypes.map,
|
||||
|
@ -183,16 +182,18 @@ function mapStateToProps(state) {
|
|||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
function mapDispatchToProps(dispatch, ownProps) {
|
||||
return {
|
||||
fetchPlan: planName => {
|
||||
dispatch(PlansActions.fetchPlan(planName));
|
||||
},
|
||||
updatePlan: (planName, files) => {
|
||||
dispatch(PlansActions.updatePlan(planName, files));
|
||||
dispatch(PlansActions.updatePlan(planName, files, ownProps.history));
|
||||
},
|
||||
updatePlanFromTarball: (planName, files) => {
|
||||
dispatch(PlansActions.updatePlanFromTarball(planName, files));
|
||||
dispatch(
|
||||
PlansActions.updatePlanFromTarball(planName, files, ownProps.history)
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ import { connect } from 'react-redux';
|
|||
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import PlansActions from '../../actions/PlansActions';
|
||||
import Loader from '../ui/Loader';
|
||||
|
@ -60,7 +60,7 @@ class ExportPlan extends React.Component {
|
|||
}
|
||||
|
||||
getNameFromUrl() {
|
||||
let planName = this.props.params.planName || '';
|
||||
let planName = this.props.match.params.planName || '';
|
||||
return planName.replace(/[^A-Za-z0-9_-]*/g, '');
|
||||
}
|
||||
|
||||
|
@ -68,7 +68,7 @@ class ExportPlan extends React.Component {
|
|||
return (
|
||||
<Modal dialogClasses="modal-sm" id="ExportPlan__exportPlanModal">
|
||||
<div className="modal-header">
|
||||
<Link to="/plans/list" type="button" className="close">
|
||||
<Link to="/plans" 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/list"
|
||||
to="/plans"
|
||||
type="button"
|
||||
className="btn btn-default"
|
||||
id="ExportPlan__cancelExportPlanModalButton"
|
||||
|
@ -124,6 +124,7 @@ ExportPlan.propTypes = {
|
|||
exportPlan: PropTypes.func,
|
||||
intl: PropTypes.object,
|
||||
isExportingPlan: PropTypes.bool,
|
||||
match: PropTypes.object,
|
||||
params: PropTypes.object,
|
||||
planExportUrl: PropTypes.string
|
||||
};
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
import { connect } from 'react-redux';
|
||||
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
||||
import { Link } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
@ -88,11 +88,7 @@ class ListPlans extends React.Component {
|
|||
<FormattedMessage {...messages.noPlans} />
|
||||
</p>
|
||||
<p className="text-center">
|
||||
<Link
|
||||
to="/plans/new"
|
||||
query={{ tab: 'newPlan' }}
|
||||
className="btn btn-success"
|
||||
>
|
||||
<Link to="/plans/new" className="btn btn-success">
|
||||
<FormattedMessage {...messages.createNewPlan} />
|
||||
</Link>
|
||||
</p>
|
||||
|
@ -105,7 +101,6 @@ class ListPlans extends React.Component {
|
|||
return (
|
||||
<Link
|
||||
to="/plans/new"
|
||||
query={{ tab: 'newPlan' }}
|
||||
className="btn btn-primary"
|
||||
id="ListPlans__newPlanLink"
|
||||
>
|
||||
|
@ -206,7 +201,6 @@ class RowActionsCell extends React.Component {
|
|||
<Link
|
||||
key="edit"
|
||||
to={`/plans/${plan.name}/edit`}
|
||||
query={{ tab: 'editPlan' }}
|
||||
className="btn btn-xs btn-default"
|
||||
>
|
||||
<FormattedMessage {...messages.edit} />
|
||||
|
|
|
@ -18,7 +18,7 @@ import { connect } from 'react-redux';
|
|||
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
||||
import Formsy from 'formsy-react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { Link } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
|
@ -107,7 +107,7 @@ class NewPlan extends React.Component {
|
|||
>
|
||||
<div className="modal-header">
|
||||
<Link
|
||||
to="/plans/list"
|
||||
to="/plans"
|
||||
type="button"
|
||||
onClick={() => this.props.cancelCreatePlan()}
|
||||
className="close"
|
||||
|
@ -126,7 +126,6 @@ class NewPlan extends React.Component {
|
|||
<ModalFormErrorList errors={this.props.planFormErrors.toJS()} />
|
||||
<div className="modal-body">
|
||||
<PlanFormTabs
|
||||
currentTab={this.props.location.query.tab || 'newPlan'}
|
||||
selectedFiles={this.state.selectedFiles}
|
||||
setUploadType={this.setUploadType.bind(this)}
|
||||
uploadType={this.state.uploadType}
|
||||
|
@ -143,7 +142,7 @@ class NewPlan extends React.Component {
|
|||
<FormattedMessage {...messages.uploadAndCreate} />
|
||||
</button>
|
||||
<Link
|
||||
to="/plans/list"
|
||||
to="/plans"
|
||||
type="button"
|
||||
onClick={() => this.props.cancelCreatePlan()}
|
||||
className="btn btn-default"
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import ClassNames from 'classnames';
|
||||
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { List } from 'immutable';
|
||||
|
@ -21,7 +22,7 @@ import PropTypes from 'prop-types';
|
|||
import React from 'react';
|
||||
|
||||
import HorizontalStaticText from '../ui/forms/HorizontalStaticText';
|
||||
import NavTab from '../ui/NavTab';
|
||||
import Tab from '../ui/Tab';
|
||||
import PlanFileInput from './PlanFileInput';
|
||||
import PlanFilesTab from './PlanFilesTab';
|
||||
import PlanUploadTypeRadios from './PlanUploadTypeRadios';
|
||||
|
@ -50,8 +51,19 @@ const messages = defineMessages({
|
|||
});
|
||||
|
||||
export default class PlanEditFormTabs extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
activeTab: 'editPlan'
|
||||
};
|
||||
}
|
||||
|
||||
setActiveTab(tabName) {
|
||||
return this.props.currentTab === tabName ? 'active' : '';
|
||||
this.setState({ activeTab: tabName });
|
||||
}
|
||||
|
||||
isActiveTab(tabName) {
|
||||
return this.state.activeTab === tabName;
|
||||
}
|
||||
|
||||
getFileCount() {
|
||||
|
@ -66,30 +78,28 @@ export default class PlanEditFormTabs extends React.Component {
|
|||
return (
|
||||
<div>
|
||||
<ul className="nav nav-tabs">
|
||||
<NavTab
|
||||
to={`/plans/${this.props.planName}/edit`}
|
||||
query={{ tab: 'editPlan' }}
|
||||
>
|
||||
<FormattedMessage {...messages.updatePlan} />
|
||||
</NavTab>
|
||||
<NavTab
|
||||
to={`/plans/${this.props.planName}/edit`}
|
||||
query={{ tab: 'planFiles' }}
|
||||
>
|
||||
<FormattedMessage {...messages.files} /> <span className="badge">
|
||||
{this.getFileCount.bind(this)()}
|
||||
</span>
|
||||
</NavTab>
|
||||
<Tab isActive={this.isActiveTab('editPlan')}>
|
||||
<a className="link" onClick={() => this.setActiveTab('editPlan')}>
|
||||
<FormattedMessage {...messages.updatePlan} />
|
||||
</a>
|
||||
</Tab>
|
||||
<Tab isActive={this.isActiveTab('planFiles')}>
|
||||
<a className="link" onClick={() => this.setActiveTab('planFiles')}>
|
||||
<FormattedMessage {...messages.files} /> <span className="badge">
|
||||
{this.getFileCount.bind(this)()}
|
||||
</span>
|
||||
</a>
|
||||
</Tab>
|
||||
</ul>
|
||||
<div className="tab-content">
|
||||
<PlanFormTab
|
||||
active={this.setActiveTab('editPlan')}
|
||||
active={this.isActiveTab('editPlan')}
|
||||
planName={this.props.planName}
|
||||
uploadType={this.props.uploadType}
|
||||
setUploadType={this.props.setUploadType}
|
||||
/>
|
||||
<PlanFilesTab
|
||||
active={this.setActiveTab('planFiles')}
|
||||
active={this.isActiveTab('planFiles')}
|
||||
planFiles={this.props.planFiles}
|
||||
selectedFiles={this.props.selectedFiles}
|
||||
/>
|
||||
|
@ -99,7 +109,6 @@ export default class PlanEditFormTabs extends React.Component {
|
|||
}
|
||||
}
|
||||
PlanEditFormTabs.propTypes = {
|
||||
currentTab: PropTypes.string,
|
||||
planFiles: ImmutablePropTypes.map,
|
||||
planName: PropTypes.string,
|
||||
selectedFiles: PropTypes.array,
|
||||
|
@ -114,7 +123,9 @@ class _PlanFormTab extends React.Component {
|
|||
render() {
|
||||
const { formatMessage } = this.props.intl;
|
||||
return (
|
||||
<div className={`tab-pane ${this.props.active}`}>
|
||||
<div
|
||||
className={ClassNames({ 'tab-pane': true, active: this.props.active })}
|
||||
>
|
||||
<HorizontalStaticText
|
||||
title={formatMessage(messages.planName)}
|
||||
text={this.props.planName}
|
||||
|
@ -142,10 +153,12 @@ class _PlanFormTab extends React.Component {
|
|||
}
|
||||
}
|
||||
_PlanFormTab.propTypes = {
|
||||
active: PropTypes.string,
|
||||
active: PropTypes.bool.isRequired,
|
||||
intl: PropTypes.object,
|
||||
planName: PropTypes.string,
|
||||
setUploadType: PropTypes.func.isRequired,
|
||||
uploadType: PropTypes.string.isRequired
|
||||
};
|
||||
_PlanFormTab.defaultProps = { active: false };
|
||||
|
||||
const PlanFormTab = injectIntl(_PlanFormTab);
|
||||
|
|
|
@ -14,26 +14,25 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import ClassNames from 'classnames';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import FileList from './FileList';
|
||||
|
||||
export default class PlanFilesTab extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className={`tab-pane ${this.props.active}`}>
|
||||
<FileList
|
||||
planFiles={this.props.planFiles}
|
||||
selectedFiles={this.props.selectedFiles}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
const PlanFilesTab = ({ active, ...rest }) => (
|
||||
<div className={ClassNames({ 'tab-pane': true, active: active })}>
|
||||
<FileList {...rest} />
|
||||
</div>
|
||||
);
|
||||
PlanFilesTab.propTypes = {
|
||||
active: PropTypes.string,
|
||||
active: PropTypes.bool.isRequired,
|
||||
planFiles: ImmutablePropTypes.map,
|
||||
selectedFiles: PropTypes.array
|
||||
};
|
||||
PlanFilesTab.defaultProps = {
|
||||
active: false
|
||||
};
|
||||
|
||||
export default PlanFilesTab;
|
||||
|
|
|
@ -14,12 +14,13 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import ClassNames from 'classnames';
|
||||
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import HorizontalInput from '../ui/forms/HorizontalInput';
|
||||
import NavTab from '../ui/NavTab';
|
||||
import Tab from '../ui/Tab';
|
||||
import PlanFileInput from './PlanFileInput';
|
||||
import PlanFilesTab from './PlanFilesTab';
|
||||
import PlanUploadTypeRadios from './PlanUploadTypeRadios';
|
||||
|
@ -57,31 +58,46 @@ const messages = defineMessages({
|
|||
});
|
||||
|
||||
export default class PlanFormTabs extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
activeTab: 'newPlan'
|
||||
};
|
||||
}
|
||||
|
||||
setActiveTab(tabName) {
|
||||
return this.props.currentTab === tabName ? 'active' : '';
|
||||
this.setState({ activeTab: tabName });
|
||||
}
|
||||
|
||||
isActiveTab(tabName) {
|
||||
return this.state.activeTab === tabName;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<ul className="nav nav-tabs">
|
||||
<NavTab to="/plans/new" query={{ tab: 'newPlan' }}>
|
||||
<FormattedMessage {...messages.newPlan} />
|
||||
</NavTab>
|
||||
<NavTab to="/plans/new" query={{ tab: 'planFiles' }}>
|
||||
<FormattedMessage {...messages.files} /> <span className="badge">
|
||||
{this.props.selectedFiles.length}
|
||||
</span>
|
||||
</NavTab>
|
||||
<Tab isActive={this.isActiveTab('newPlan')}>
|
||||
<a className="link" onClick={() => this.setActiveTab('newPlan')}>
|
||||
<FormattedMessage {...messages.newPlan} />
|
||||
</a>
|
||||
</Tab>
|
||||
<Tab isActive={this.isActiveTab('planFiles')}>
|
||||
<a className="link" onClick={() => this.setActiveTab('planFiles')}>
|
||||
<FormattedMessage {...messages.files} /> <span className="badge">
|
||||
{this.props.selectedFiles.length}
|
||||
</span>
|
||||
</a>
|
||||
</Tab>
|
||||
</ul>
|
||||
<div className="tab-content">
|
||||
<PlanFormTab
|
||||
active={this.setActiveTab('newPlan')}
|
||||
active={this.isActiveTab('newPlan')}
|
||||
uploadType={this.props.uploadType}
|
||||
setUploadType={this.props.setUploadType}
|
||||
/>
|
||||
<PlanFilesTab
|
||||
active={this.setActiveTab('planFiles')}
|
||||
active={this.isActiveTab('planFiles')}
|
||||
selectedFiles={this.props.selectedFiles}
|
||||
/>
|
||||
</div>
|
||||
|
@ -90,7 +106,6 @@ export default class PlanFormTabs extends React.Component {
|
|||
}
|
||||
}
|
||||
PlanFormTabs.propTypes = {
|
||||
currentTab: PropTypes.string,
|
||||
selectedFiles: PropTypes.array,
|
||||
setUploadType: PropTypes.func.isRequired,
|
||||
uploadType: PropTypes.string.isRequired
|
||||
|
@ -104,7 +119,9 @@ class _PlanFormTab extends React.Component {
|
|||
render() {
|
||||
const { formatMessage } = this.props.intl;
|
||||
return (
|
||||
<div className={`tab-pane ${this.props.active}`}>
|
||||
<div
|
||||
className={ClassNames({ 'tab-pane': true, active: this.props.active })}
|
||||
>
|
||||
<HorizontalInput
|
||||
name="planName"
|
||||
title={formatMessage(messages.planName)}
|
||||
|
@ -136,10 +153,11 @@ class _PlanFormTab extends React.Component {
|
|||
}
|
||||
}
|
||||
_PlanFormTab.propTypes = {
|
||||
active: PropTypes.string,
|
||||
active: PropTypes.bool.isRequired,
|
||||
intl: PropTypes.object,
|
||||
setUploadType: PropTypes.func.isRequired,
|
||||
uploadType: PropTypes.string.isRequired
|
||||
};
|
||||
_PlanFormTab.defaultProps = { active: false };
|
||||
|
||||
const PlanFormTab = injectIntl(_PlanFormTab);
|
||||
|
|
|
@ -14,21 +14,27 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { Route } from 'react-router-dom';
|
||||
|
||||
import ListPlans from './ListPlans';
|
||||
import DeletePlan from './DeletePlan';
|
||||
import EditPlan from './EditPlan';
|
||||
import ExportPlan from './ExportPlan';
|
||||
import NewPlan from './NewPlan';
|
||||
|
||||
export default class Plans extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-sm-12">
|
||||
{this.props.children}
|
||||
<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} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Plans.propTypes = {
|
||||
children: PropTypes.node
|
||||
};
|
||||
|
|
|
@ -20,10 +20,11 @@ import Formsy from 'formsy-react';
|
|||
import { fromJS, is } from 'immutable';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { isObjectLike, mapValues } from 'lodash';
|
||||
import { Link, Redirect, Route, Switch } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router';
|
||||
|
||||
import { checkRunningDeployment } from '../utils/checkRunningDeploymentHOC';
|
||||
import { getRole } from '../../selectors/roles';
|
||||
import { getRoleServices } from '../../selectors/parameters';
|
||||
import Loader from '../ui/Loader';
|
||||
|
@ -37,6 +38,9 @@ import {
|
|||
} from '../ui/ModalPanel';
|
||||
import NavTab from '../ui/NavTab';
|
||||
import ParametersActions from '../../actions/ParametersActions';
|
||||
import RoleNetworkConfig from './RoleNetworkConfig';
|
||||
import RoleParameters from './RoleParameters';
|
||||
import RoleServices from './RoleServices';
|
||||
|
||||
const messages = defineMessages({
|
||||
networkConfiguration: {
|
||||
|
@ -134,30 +138,40 @@ class RoleDetail extends React.Component {
|
|||
}
|
||||
|
||||
renderRoleTabs() {
|
||||
if (this.props.rolesLoaded && this.props.parametersLoaded) {
|
||||
const {
|
||||
formErrors,
|
||||
location,
|
||||
match: { params: urlParams },
|
||||
parametersLoaded,
|
||||
rolesLoaded
|
||||
} = this.props;
|
||||
if (rolesLoaded && parametersLoaded) {
|
||||
return (
|
||||
<div className="row">
|
||||
<ul className="nav nav-tabs">
|
||||
<NavTab
|
||||
to={`/deployment-plan/roles/${this.props.params.roleIdentifier}/parameters`}
|
||||
location={location}
|
||||
to={`/deployment-plan/roles/${urlParams.roleIdentifier}/parameters`}
|
||||
>
|
||||
<FormattedMessage {...messages.parameters} />
|
||||
</NavTab>
|
||||
<NavTab
|
||||
to={`/deployment-plan/roles/${this.props.params.roleIdentifier}/services`}
|
||||
location={location}
|
||||
to={`/deployment-plan/roles/${urlParams.roleIdentifier}/services`}
|
||||
>
|
||||
<FormattedMessage {...messages.services} />
|
||||
</NavTab>
|
||||
<NavTab
|
||||
location={location}
|
||||
to={
|
||||
`/deployment-plan/roles/${this.props.params.roleIdentifier}` +
|
||||
`/deployment-plan/roles/${urlParams.roleIdentifier}` +
|
||||
'/network-configuration'
|
||||
}
|
||||
>
|
||||
<FormattedMessage {...messages.networkConfiguration} />
|
||||
</NavTab>
|
||||
</ul>
|
||||
<ModalFormErrorList errors={this.props.formErrors.toJS()} />
|
||||
<ModalFormErrorList errors={formErrors.toJS()} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -166,6 +180,7 @@ 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;
|
||||
|
||||
return (
|
||||
<Formsy.Form
|
||||
|
@ -198,7 +213,24 @@ class RoleDetail extends React.Component {
|
|||
)}
|
||||
loaded={dataLoaded}
|
||||
>
|
||||
{this.props.children}
|
||||
<Switch>
|
||||
<Route
|
||||
path="/deployment-plan/roles/:roleIdentifier/parameters"
|
||||
component={RoleParameters}
|
||||
/>
|
||||
<Route
|
||||
path="/deployment-plan/roles/:roleIdentifier/services"
|
||||
component={RoleServices}
|
||||
/>
|
||||
<Route
|
||||
path="/deployment-plan/roles/:roleIdentifier/network-configuration"
|
||||
component={RoleNetworkConfig}
|
||||
/>
|
||||
<Redirect
|
||||
from="/deployment-plan/roles/:roleIdentifier"
|
||||
to={`/deployment-plan/roles/${urlParams.roleIdentifier}/parameters`}
|
||||
/>
|
||||
</Switch>
|
||||
</Loader>
|
||||
</ModalPanelBody>
|
||||
{dataLoaded
|
||||
|
@ -219,14 +251,14 @@ class RoleDetail extends React.Component {
|
|||
}
|
||||
RoleDetail.propTypes = {
|
||||
allParameters: ImmutablePropTypes.map.isRequired,
|
||||
children: PropTypes.node,
|
||||
currentPlanName: PropTypes.string,
|
||||
fetchParameters: PropTypes.func,
|
||||
formErrors: ImmutablePropTypes.list,
|
||||
formFieldErrors: ImmutablePropTypes.map,
|
||||
intl: PropTypes.object,
|
||||
location: PropTypes.object.isRequired,
|
||||
match: PropTypes.object.isRequired,
|
||||
parametersLoaded: PropTypes.bool.isRequired,
|
||||
params: PropTypes.object.isRequired,
|
||||
role: ImmutablePropTypes.record,
|
||||
rolesLoaded: PropTypes.bool.isRequired,
|
||||
updateParameters: PropTypes.func
|
||||
|
@ -239,8 +271,8 @@ function mapStateToProps(state, props) {
|
|||
formFieldErrors: state.parameters.form.get('formFieldErrors'),
|
||||
allParameters: state.parameters.parameters,
|
||||
parametersLoaded: state.parameters.loaded,
|
||||
role: getRole(state, props.params.roleIdentifier),
|
||||
roleServices: getRoleServices(state, props.params.roleIdentifier),
|
||||
role: getRole(state, props.match.params.roleIdentifier),
|
||||
roleServices: getRoleServices(state, props.match.params.roleIdentifier),
|
||||
rolesLoaded: state.roles.get('loaded')
|
||||
};
|
||||
}
|
||||
|
@ -265,6 +297,6 @@ function mapDispatchToProps(dispatch) {
|
|||
};
|
||||
}
|
||||
|
||||
export default injectIntl(
|
||||
connect(mapStateToProps, mapDispatchToProps)(RoleDetail)
|
||||
export default checkRunningDeployment(
|
||||
injectIntl(connect(mapStateToProps, mapDispatchToProps)(RoleDetail))
|
||||
);
|
||||
|
|
|
@ -39,18 +39,17 @@ class RoleNetworkConfig extends React.Component {
|
|||
RoleNetworkConfig.propTypes = {
|
||||
description: PropTypes.string,
|
||||
mistralParameters: ImmutablePropTypes.map.isRequired,
|
||||
parameters: ImmutablePropTypes.map.isRequired,
|
||||
params: PropTypes.object.isRequired
|
||||
parameters: ImmutablePropTypes.map.isRequired
|
||||
};
|
||||
|
||||
function mapStateToProps(state, props) {
|
||||
return {
|
||||
description: getRoleNetworkConfig(state, props.params.roleIdentifier)
|
||||
description: getRoleNetworkConfig(state, props.match.params.roleIdentifier)
|
||||
.description,
|
||||
mistralParameters: state.parameters.mistralParameters,
|
||||
parameters: getRoleNetworkConfig(state, props.params.roleIdentifier)
|
||||
parameters: getRoleNetworkConfig(state, props.match.params.roleIdentifier)
|
||||
.parameters,
|
||||
role: getRole(state, props.params.roleIdentifier)
|
||||
role: getRole(state, props.match.params.roleIdentifier)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -36,16 +36,16 @@ class RoleParameters extends React.Component {
|
|||
}
|
||||
}
|
||||
RoleParameters.propTypes = {
|
||||
match: PropTypes.object.isRequired,
|
||||
mistralParameters: ImmutablePropTypes.map.isRequired,
|
||||
parameters: ImmutablePropTypes.map.isRequired,
|
||||
params: PropTypes.object.isRequired
|
||||
parameters: ImmutablePropTypes.map.isRequired
|
||||
};
|
||||
|
||||
function mapStateToProps(state, props) {
|
||||
return {
|
||||
mistralParameters: state.parameters.mistralParameters,
|
||||
parameters: getRoleParameters(state, props.params.roleIdentifier),
|
||||
role: getRole(state, props.params.roleIdentifier)
|
||||
parameters: getRoleParameters(state, props.match.params.roleIdentifier),
|
||||
role: getRole(state, props.match.params.roleIdentifier)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -99,7 +99,6 @@ class RoleServices extends React.Component {
|
|||
RoleServices.propTypes = {
|
||||
intl: PropTypes.object,
|
||||
mistralParameters: ImmutablePropTypes.map.isRequired,
|
||||
params: PropTypes.object.isRequired,
|
||||
role: ImmutablePropTypes.record.isRequired,
|
||||
services: ImmutablePropTypes.map.isRequired
|
||||
};
|
||||
|
@ -107,8 +106,8 @@ RoleServices.propTypes = {
|
|||
function mapStateToProps(state, props) {
|
||||
return {
|
||||
mistralParameters: state.parameters.mistralParameters,
|
||||
role: getRole(state, props.params.roleIdentifier),
|
||||
services: getRoleServices(state, props.params.roleIdentifier)
|
||||
role: getRole(state, props.match.params.roleIdentifier),
|
||||
services: getRoleServices(state, props.match.params.roleIdentifier)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { Link as ReactRouterLink } from 'react-router';
|
||||
import { Link as ReactRouterLink } from 'react-router-dom';
|
||||
|
||||
export default class Link extends React.Component {
|
||||
render() {
|
||||
|
|
|
@ -16,28 +16,32 @@
|
|||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router';
|
||||
import { Link, Route } from 'react-router-dom';
|
||||
|
||||
export default class NavTab extends Link {
|
||||
render() {
|
||||
let router = this.context.router;
|
||||
let isActive = router.isActive(
|
||||
{ pathname: this.props.to, query: this.props.query },
|
||||
this.props.onlyActiveOnIndex
|
||||
);
|
||||
let className = isActive ? 'active' : '';
|
||||
let link = <Link {...this.props} />;
|
||||
return <li className={className}>{link}</li>;
|
||||
}
|
||||
}
|
||||
const NavTab = ({ activeClassName, children, to, exact, location }) => {
|
||||
return (
|
||||
<Route
|
||||
location={location}
|
||||
path={typeof to === 'object' ? to.pathname : to}
|
||||
exact={exact}
|
||||
children={({ match, location }) => (
|
||||
<li className={match ? activeClassName : ''}>
|
||||
<Link to={to}>{children}</Link>
|
||||
</li>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
NavTab.propTypes = {
|
||||
onlyActiveOnIndex: PropTypes.bool.isRequired,
|
||||
query: PropTypes.object,
|
||||
to: PropTypes.oneOfType([PropTypes.string, PropTypes.route]).isRequired
|
||||
activeClassName: PropTypes.string.isRequired,
|
||||
children: PropTypes.node,
|
||||
exact: PropTypes.bool.isRequired,
|
||||
location: PropTypes.object,
|
||||
to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired
|
||||
};
|
||||
NavTab.defaultProps = {
|
||||
onlyActiveOnIndex: false
|
||||
};
|
||||
NavTab.contextTypes = {
|
||||
router: PropTypes.object
|
||||
activeClassName: 'active',
|
||||
exact: false
|
||||
};
|
||||
|
||||
export default NavTab;
|
||||
|
|
|
@ -20,7 +20,7 @@ import React from 'react';
|
|||
export default class Tab extends React.Component {
|
||||
render() {
|
||||
let className = this.props.isActive ? 'active' : '';
|
||||
return <li {...this.props} className={className}>{this.props.children}</li>;
|
||||
return <li className={className}>{this.props.children}</li>;
|
||||
}
|
||||
}
|
||||
Tab.propTypes = {
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
|
||||
import ClassNames from 'classnames';
|
||||
import { Link } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
/**
|
||||
* 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 PropTypes from 'prop-types';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
currentPlanNameSelector as getCurrentPlanName
|
||||
} from '../../selectors/plans';
|
||||
import { getCurrentStackDeploymentInProgress } from '../../selectors/stacks';
|
||||
import NotificationActions from '../../actions/NotificationActions';
|
||||
|
||||
export const checkRunningDeployment = WrappedComponent => {
|
||||
class CheckRunningDeploymentHOC extends React.Component {
|
||||
componentDidMount() {
|
||||
if (this.props.currentStackDeploymentInProgress) {
|
||||
this.props.notify({
|
||||
title: 'Not allowed',
|
||||
message: `A deployment for the plan ${this.props.currentPlanName} is already in progress.`,
|
||||
type: 'warning'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return this.props.currentStackDeploymentInProgress
|
||||
? <Redirect to="/deployment-plan" />
|
||||
: <WrappedComponent {...this.props} />;
|
||||
}
|
||||
}
|
||||
CheckRunningDeploymentHOC.propTypes = {
|
||||
currentPlanName: PropTypes.string,
|
||||
currentStackDeploymentInProgress: PropTypes.bool.isRequired,
|
||||
notify: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
currentPlanName: getCurrentPlanName(state),
|
||||
currentStackDeploymentInProgress: getCurrentStackDeploymentInProgress(state)
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
notify: notification => dispatch(NotificationActions.notify(notification))
|
||||
});
|
||||
|
||||
return connect(mapStateToProps, mapDispatchToProps)(
|
||||
CheckRunningDeploymentHOC
|
||||
);
|
||||
};
|
131
src/js/index.js
131
src/js/index.js
|
@ -19,145 +19,22 @@ import 'babel-polyfill';
|
|||
import { Provider } from 'react-redux';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Router, Route, Redirect } from 'react-router';
|
||||
import { browserHistory } from 'react-router';
|
||||
import cookie from 'react-cookie';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
|
||||
import App from './components/App';
|
||||
import AuthenticatedContent from './components/AuthenticatedContent';
|
||||
import UserAuthenticator from './components/UserAuthenticator';
|
||||
import DeletePlan from './components/plan/DeletePlan';
|
||||
import DeploymentConfiguration
|
||||
from './components/deployment_plan/DeploymentConfiguration';
|
||||
import DeploymentDetail from './components/deployment/DeploymentDetail';
|
||||
import DeploymentPlan from './components/deployment_plan/DeploymentPlan';
|
||||
import EditPlan from './components/plan/EditPlan';
|
||||
import ExportPlan from './components/plan/ExportPlan';
|
||||
import EnvironmentConfiguration
|
||||
from './components/environment_configuration/EnvironmentConfiguration.js';
|
||||
import { getCurrentStackDeploymentInProgress } from './selectors/stacks';
|
||||
import I18nProvider from './components/i18n/I18nProvider';
|
||||
import initFormsy from './components/utils/Formsy';
|
||||
import ListPlans from './components/plan/ListPlans';
|
||||
import Login from './components/Login';
|
||||
import LoginActions from './actions/LoginActions';
|
||||
import NewPlan from './components/plan/NewPlan';
|
||||
import Nodes from './components/nodes/Nodes';
|
||||
import NotificationActions from './actions/NotificationActions';
|
||||
import Parameters from './components/parameters/Parameters.js';
|
||||
import Plans from './components/plan/Plans.js';
|
||||
import RegisterNodesDialog from './components/nodes/RegisterNodesDialog';
|
||||
import RoleDetail from './components/roles/RoleDetail';
|
||||
import RoleNetworkConfig from './components/roles/RoleNetworkConfig';
|
||||
import RoleParameters from './components/roles/RoleParameters';
|
||||
import RoleServices from './components/roles/RoleServices';
|
||||
import store from './store';
|
||||
import '../less/base.less';
|
||||
|
||||
/**
|
||||
* @function checkAuth
|
||||
* If user is not logged in, check if there is an auth token in a cookie
|
||||
* If there is, try to login with this token, else redirect to Login
|
||||
*/
|
||||
function checkAuth(nextState, replace) {
|
||||
if (!store.getState().login.hasIn(['keystoneAccess', 'user'])) {
|
||||
const keystoneAuthTokenId = cookie.load('keystoneAuthTokenId');
|
||||
if (keystoneAuthTokenId) {
|
||||
const nextPath =
|
||||
nextState.location.pathname + nextState.location.search || '/';
|
||||
store.dispatch(
|
||||
LoginActions.authenticateUserViaToken(keystoneAuthTokenId, nextPath)
|
||||
);
|
||||
} else {
|
||||
replace({
|
||||
pathname: '/login',
|
||||
query: {
|
||||
nextPath: nextState.location.pathname + nextState.location.search
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkRunningDeployment(nextState, replace) {
|
||||
const state = store.getState();
|
||||
let currentPlanName = state.currentPlan.currentPlanName;
|
||||
if (getCurrentStackDeploymentInProgress(state)) {
|
||||
store.dispatch(
|
||||
NotificationActions.notify({
|
||||
title: 'Not allowed',
|
||||
message: `A deployment for the plan ${currentPlanName} is already in progress.`,
|
||||
type: 'warning'
|
||||
})
|
||||
);
|
||||
// TODO(flfuchs): Redirect to deployment status modal instead of DeploymentPlan
|
||||
// page (in separate patch).
|
||||
replace('/deployment-plan/');
|
||||
}
|
||||
}
|
||||
|
||||
let routes = (
|
||||
<Route>
|
||||
<Redirect from="/" to="/deployment-plan" />
|
||||
<Route path="/" component={App}>
|
||||
<Route component={UserAuthenticator} onEnter={checkAuth}>
|
||||
<Route component={AuthenticatedContent}>
|
||||
|
||||
<Route path="deployment-plan" component={DeploymentPlan}>
|
||||
<Redirect from="configuration" to="configuration/environment" />
|
||||
<Route
|
||||
path="configuration"
|
||||
component={DeploymentConfiguration}
|
||||
onEnter={checkRunningDeployment}
|
||||
>
|
||||
<Route path="environment" component={EnvironmentConfiguration} />
|
||||
<Route path="parameters" component={Parameters} />
|
||||
</Route>
|
||||
<Redirect
|
||||
from="roles/:roleIdentifier"
|
||||
to="roles/:roleIdentifier/parameters"
|
||||
/>
|
||||
<Route
|
||||
path="roles/:roleIdentifier"
|
||||
component={RoleDetail}
|
||||
onEnter={checkRunningDeployment}
|
||||
>
|
||||
<Route path="parameters" component={RoleParameters} />
|
||||
<Route path="services" component={RoleServices} />
|
||||
<Route
|
||||
path="network-configuration"
|
||||
component={RoleNetworkConfig}
|
||||
/>
|
||||
</Route>
|
||||
<Route path="deployment-detail" component={DeploymentDetail} />
|
||||
</Route>
|
||||
|
||||
<Route path="nodes" component={Nodes}>
|
||||
<Route path="register" component={RegisterNodesDialog} />
|
||||
</Route>
|
||||
|
||||
<Redirect from="plans" to="plans/list" />
|
||||
<Route path="plans" component={Plans}>
|
||||
<Route path="list" component={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>
|
||||
</Route>
|
||||
</Route>
|
||||
</Route>
|
||||
<Route path="login" component={Login} />
|
||||
</Route>
|
||||
</Route>
|
||||
);
|
||||
|
||||
initFormsy();
|
||||
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
<I18nProvider>
|
||||
<Router history={browserHistory}>{routes}</Router>
|
||||
<Router>
|
||||
<App />
|
||||
</Router>
|
||||
</I18nProvider>
|
||||
</Provider>,
|
||||
document.getElementById('react-app-index')
|
||||
|
|
|
@ -27,7 +27,7 @@ export default {
|
|||
socket: null,
|
||||
clientID: null,
|
||||
|
||||
init(getState, dispatch) {
|
||||
init(getState, dispatch, history) {
|
||||
when.try(getServiceUrl, 'zaqar-websocket').then(serviceUrl => {
|
||||
this.socket = new WebSocket(serviceUrl);
|
||||
this.clientID = uuid.v4();
|
||||
|
@ -55,7 +55,7 @@ export default {
|
|||
};
|
||||
|
||||
this.socket.onmessage = evt => {
|
||||
dispatch(ZaqarActions.messageReceived(JSON.parse(evt.data)));
|
||||
dispatch(ZaqarActions.messageReceived(JSON.parse(evt.data), history));
|
||||
};
|
||||
});
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue