Deployment Plan page updates
Updated deployment steps, added Roles listing to Register and Assign Nodes step, implemented plan reloading process when plan is switched, listing of assigned/available/introspected nodes Change-Id: I1cff92d7d95382013304751ebdee2a2734fb331c
This commit is contained in:
parent
a1d162faa6
commit
04b32c117e
|
@ -0,0 +1,36 @@
|
|||
import RolesActions from '../../js/actions/RolesActions';
|
||||
import RolesConstants from '../../js/constants/RolesConstants';
|
||||
|
||||
describe('Roles actions', () => {
|
||||
it('should create an action for pending Roles request', () => {
|
||||
const expectedAction = {
|
||||
type: RolesConstants.FETCH_ROLES_PENDING
|
||||
};
|
||||
expect(RolesActions.fetchRolesPending()).toEqual(expectedAction);
|
||||
});
|
||||
|
||||
it('should create an action for successful Roles retrieval', () => {
|
||||
const normalizedRolesResponse = {
|
||||
entities: {
|
||||
roles: {
|
||||
1: 'first role',
|
||||
2: 'second role'
|
||||
}
|
||||
},
|
||||
result: [1, 2]
|
||||
};
|
||||
const expectedAction = {
|
||||
type: RolesConstants.FETCH_ROLES_SUCCESS,
|
||||
payload: normalizedRolesResponse.entities.roles
|
||||
};
|
||||
expect(RolesActions.fetchRolesSuccess(normalizedRolesResponse.entities.roles))
|
||||
.toEqual(expectedAction);
|
||||
});
|
||||
|
||||
it('should create an action for failed Roles request', () => {
|
||||
const expectedAction = {
|
||||
type: RolesConstants.FETCH_ROLES_FAILED
|
||||
};
|
||||
expect(RolesActions.fetchRolesFailed()).toEqual(expectedAction);
|
||||
});
|
||||
});
|
|
@ -3,17 +3,9 @@ import TestUtils from 'react-addons-test-utils';
|
|||
|
||||
import Login from '../../js/components/Login';
|
||||
import LoginActions from '../../js/actions/LoginActions';
|
||||
import LoginStore from '../../js/stores/LoginStore';
|
||||
|
||||
let loginInstance;
|
||||
|
||||
let loggedInState = {
|
||||
token: '123123123',
|
||||
user: 'admin',
|
||||
serviceCatalog: 'some service catalog',
|
||||
metadata: 'some metadata'
|
||||
};
|
||||
|
||||
describe('Login component', () => {
|
||||
describe('When user is not logged in', () => {
|
||||
beforeEach(() => {
|
||||
|
@ -49,10 +41,6 @@ describe('Login component', () => {
|
|||
});
|
||||
|
||||
describe('When user is logged in', () => {
|
||||
beforeEach(() => {
|
||||
LoginStore.getState = LoginStore.getState.mockReturnValue(loggedInState);
|
||||
LoginStore.isLoggedIn = LoginStore.isLoggedIn.mockReturnValue(true);
|
||||
});
|
||||
xit('redirects to nextPath when user is logged in and visits login page', () => {
|
||||
loginInstance = new Login();
|
||||
loginInstance.context = {
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
import { List, Map } from 'immutable';
|
||||
|
||||
import { mapStateToProps } from '../../../js/components/deployment-plan/DeploymentPlan.js';
|
||||
|
||||
describe('DeploymentPlan mapStateToProps', () => {
|
||||
describe('hasPlans flag', () => {
|
||||
it('returns ``hasPlans`` as `false`', () => {
|
||||
let props = mapStateToProps({ plans: Map({
|
||||
all: List()
|
||||
})});
|
||||
expect(props.hasPlans).toBe(false);
|
||||
});
|
||||
it('returns ``hasPlans`` as `false`', () => {
|
||||
let props = mapStateToProps({ plans: Map({
|
||||
all: List(['foo', 'bar'])
|
||||
})});
|
||||
expect(props.hasPlans).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,42 @@
|
|||
import { List, Map } from 'immutable';
|
||||
|
||||
import { mapStateToProps } from '../../../js/components/deployment-plan/DeploymentPlan.js';
|
||||
|
||||
describe('DeploymentPlan mapStateToProps', () => {
|
||||
describe('hasPlans flag', () => {
|
||||
it('returns ``hasPlans`` as `false`', () => {
|
||||
let props = mapStateToProps(
|
||||
{
|
||||
plans: Map({ all: List() }),
|
||||
roles: Map({
|
||||
loaded: false,
|
||||
isFetching: false,
|
||||
roles: Map()
|
||||
}),
|
||||
nodes: Map({
|
||||
isFetching: false,
|
||||
all: Map()
|
||||
})
|
||||
}
|
||||
);
|
||||
expect(props.hasPlans).toBe(false);
|
||||
});
|
||||
it('returns ``hasPlans`` as `false`', () => {
|
||||
let props = mapStateToProps(
|
||||
{
|
||||
plans: Map({ all: List(['foo', 'bar']) }),
|
||||
roles: Map({
|
||||
loaded: false,
|
||||
isFetching: false,
|
||||
roles: Map()
|
||||
}),
|
||||
nodes: Map({
|
||||
isFetching: false,
|
||||
all: Map()
|
||||
})
|
||||
}
|
||||
);
|
||||
expect(props.hasPlans).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,76 @@
|
|||
import matchers from 'jasmine-immutable-matchers';
|
||||
import { Map } from 'immutable';
|
||||
|
||||
import { Role } from '../../js/immutableRecords/roles';
|
||||
import PlansConstants from '../../js/constants/PlansConstants';
|
||||
import RolesConstants from '../../js/constants/RolesConstants';
|
||||
import rolesReducer from '../../js/reducers/rolesReducer';
|
||||
|
||||
describe('rolesReducer', () => {
|
||||
beforeEach(() => {
|
||||
jasmine.addMatchers(matchers);
|
||||
});
|
||||
|
||||
const initialState = Map({
|
||||
loaded: false,
|
||||
isFetching: false,
|
||||
roles: Map()
|
||||
});
|
||||
|
||||
const updatedState = Map({
|
||||
loaded: true,
|
||||
isFetching: false,
|
||||
roles: Map({
|
||||
control: new Role({
|
||||
title: 'Controller',
|
||||
name: 'control'
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
it('should return initial state', () => {
|
||||
expect(rolesReducer(initialState, {})).toEqual(initialState);
|
||||
});
|
||||
|
||||
it('should handle FETCH_ROLES_PENDING', () => {
|
||||
const action = {
|
||||
type: RolesConstants.FETCH_ROLES_PENDING
|
||||
};
|
||||
const newState = rolesReducer(initialState, action);
|
||||
expect(newState.get('isFetching')).toEqual(true);
|
||||
});
|
||||
|
||||
it('should handle FETCH_ROLES_SUCCESS', () => {
|
||||
const action = {
|
||||
type: RolesConstants.FETCH_ROLES_SUCCESS,
|
||||
payload: {
|
||||
control: {
|
||||
name: 'control',
|
||||
title: 'Controller'
|
||||
}
|
||||
}
|
||||
};
|
||||
const newState = rolesReducer(initialState, action);
|
||||
expect(newState.get('roles')).toEqualImmutable(
|
||||
updatedState.get('roles')
|
||||
);
|
||||
expect(newState.get('loaded')).toEqual(true);
|
||||
});
|
||||
|
||||
it('should handle FETCH_ROLES_FAILED', () => {
|
||||
const action = {
|
||||
type: RolesConstants.FETCH_ROLES_FAILED
|
||||
};
|
||||
const newState = rolesReducer(initialState, action);
|
||||
expect(newState.get('loaded')).toEqual(true);
|
||||
expect(newState.get('roles')).toEqual(Map());
|
||||
});
|
||||
|
||||
it('should handle PLAN_CHOSEN', () => {
|
||||
const action = {
|
||||
type: PlansConstants.PLAN_CHOSEN
|
||||
};
|
||||
const newState = rolesReducer(updatedState, action);
|
||||
expect(newState.get('loaded')).toEqual(false);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,53 @@
|
|||
import { fromJS, Map } from 'immutable';
|
||||
import matchers from 'jasmine-immutable-matchers';
|
||||
|
||||
import * as selectors from '../../js/selectors/nodes';
|
||||
|
||||
describe('Nodes selectors', () => {
|
||||
beforeEach(() => {
|
||||
jasmine.addMatchers(matchers);
|
||||
});
|
||||
|
||||
const state = {
|
||||
nodes: Map({
|
||||
isFetching: false,
|
||||
dataOperationInProgress: false,
|
||||
allFilter: '',
|
||||
registeredFilter: '',
|
||||
introspectedFilter: '',
|
||||
provisionedFilter: '',
|
||||
maintenanceFilter: '',
|
||||
all: fromJS([
|
||||
{
|
||||
provision_state: 'available',
|
||||
provision_updated_at: '12-12-2016',
|
||||
properties: { capabilities: 'boot_option:local' }
|
||||
},
|
||||
{
|
||||
provision_state: 'available',
|
||||
provision_updated_at: '12-12-2016',
|
||||
properties: { capabilities: 'boot_option:local,profile:control' }
|
||||
},
|
||||
{
|
||||
provision_state: 'available',
|
||||
provision_updated_at: '12-12-2016',
|
||||
properties: { capabilities: 'profile:control,boot_option:local' }
|
||||
},
|
||||
{
|
||||
provision_state: 'available',
|
||||
provision_updated_at: '12-12-2016',
|
||||
properties: { capabilities: 'profile:compute,boot_option:local' }
|
||||
},
|
||||
{
|
||||
provision_state: 'available',
|
||||
provision_updated_at: '12-12-2016',
|
||||
properties: { capabilities: '' }
|
||||
}
|
||||
])
|
||||
})
|
||||
};
|
||||
|
||||
it('provides selector to list Introspected Nodes unassigned to a Role', () => {
|
||||
expect(selectors.getUnassignedIntrospectedNodes(state).size).toEqual(2);
|
||||
});
|
||||
});
|
|
@ -1,10 +1,51 @@
|
|||
import AppDispatcher from '../dispatchers/AppDispatcher.js';
|
||||
import { normalize, arrayOf } from 'normalizr';
|
||||
|
||||
// import NotificationActions from './NotificationActions';
|
||||
import RolesConstants from '../constants/RolesConstants';
|
||||
import roles from '../mockData/roles';
|
||||
import { roleSchema } from '../normalizrSchemas/roles';
|
||||
|
||||
export default {
|
||||
updateRole(role) {
|
||||
AppDispatcher.dispatch({
|
||||
actionType: 'UPDATE_FLAVOR_ROLE',
|
||||
role: role
|
||||
});
|
||||
fetchRoles() {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(this.fetchRolesPending());
|
||||
|
||||
// TODO(jtomasek): Replace this with an actual action which fetches Roles from HEAT
|
||||
// fake the reques/response delay
|
||||
const normalizedRoles = normalize(roles, arrayOf(roleSchema)).entities.roles;
|
||||
setTimeout(() => dispatch(this.fetchRolesSuccess(normalizedRoles)), 500);
|
||||
|
||||
// TODO(jtomasek): Use this when roles are fetched from Heat
|
||||
// HeatApiService.getRoles().then((response) => {
|
||||
// response = normalize(response, arrayOf(roleSchema));
|
||||
// dispatch(this.fetchRolesSuccess(response));
|
||||
// }).catch((error) => {
|
||||
// console.error('Error in RolesAction.fetchRoles', error.stack || error); //eslint-disable-line no-console
|
||||
// dispatch(this.fetchRolesFailed());
|
||||
// let errorHandler = new HeatApiErrorHandler(error);
|
||||
// errorHandler.errors.forEach((error) => {
|
||||
// dispatch(NotificationActions.notify(error));
|
||||
// });
|
||||
// });
|
||||
};
|
||||
},
|
||||
|
||||
fetchRolesPending() {
|
||||
return {
|
||||
type: RolesConstants.FETCH_ROLES_PENDING
|
||||
};
|
||||
},
|
||||
|
||||
fetchRolesSuccess(roles) {
|
||||
return {
|
||||
type: RolesConstants.FETCH_ROLES_SUCCESS,
|
||||
payload: roles
|
||||
};
|
||||
},
|
||||
|
||||
fetchRolesFailed() {
|
||||
return {
|
||||
type: RolesConstants.FETCH_ROLES_FAILED
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
@ -40,7 +40,6 @@ export default class NavBar extends React.Component {
|
|||
</ul>
|
||||
<ul className="nav navbar-nav navbar-primary">
|
||||
<NavTab to="/deployment-plan">Deployment Plan</NavTab>
|
||||
<NavTab to="/roles">Roles</NavTab>
|
||||
<NavTab to="/nodes">Nodes</NavTab>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -4,13 +4,16 @@ import { Link } from 'react-router';
|
|||
import React from 'react';
|
||||
|
||||
import { getAllPlansButCurrent } from '../../selectors/plans';
|
||||
import { getIntrospectedNodes, getUnassignedIntrospectedNodes } from '../../selectors/nodes';
|
||||
import DeploymentStep from './DeploymentStep';
|
||||
import PlansDropdown from './PlansDropdown';
|
||||
import FlavorStore from '../../stores/FlavorStore';
|
||||
import Loader from '../ui/Loader';
|
||||
import NodesActions from '../../actions/NodesActions';
|
||||
import NoPlans from './NoPlans';
|
||||
import NotificationActions from '../../actions/NotificationActions';
|
||||
import PlansActions from '../../actions/PlansActions';
|
||||
import Roles from './Roles';
|
||||
import RolesActions from '../../actions/RolesActions';
|
||||
import TripleOApiService from '../../services/TripleOApiService';
|
||||
import TripleOApiErrorHandler from '../../services/TripleOApiErrorHandler';
|
||||
|
||||
|
@ -18,15 +21,10 @@ class DeploymentPlan extends React.Component {
|
|||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
readyToDeploy: false,
|
||||
flavors: []
|
||||
readyToDeploy: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setState({flavors: FlavorStore.getState().flavors});
|
||||
}
|
||||
|
||||
handleDeploy() {
|
||||
TripleOApiService.deployPlan(this.props.currentPlanName).then((response) => {
|
||||
this.setState({ parameters: response.parameters });
|
||||
|
@ -44,16 +42,23 @@ class DeploymentPlan extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
let deploymentConfigLinks = [
|
||||
<Link className="btn btn-link" key="1" to={'/deployment-plan/configuration'}>
|
||||
const deploymentConfigLinks = [
|
||||
<Link className="btn btn-link"
|
||||
key="deploymentConfiguration"
|
||||
to={'/deployment-plan/configuration'}>
|
||||
Edit Configuration
|
||||
</Link>
|
||||
];
|
||||
|
||||
let roleConfigLinks = [
|
||||
<Link className="btn btn-link" key="2" to={'/deployment-plan/configuration/parameters'}>
|
||||
Edit Parameters
|
||||
</Link>
|
||||
const registerAndAssignLinks = [
|
||||
<Link className="btn btn-default" key="registerNodes" to={'/nodes/registered/register'}>
|
||||
<span className="fa fa-plus"/> Register Nodes
|
||||
</Link>,
|
||||
<span key="space"> </span>,
|
||||
<Loader key="rolesLoader"
|
||||
loaded={!(this.props.rolesLoaded && this.props.isFetchingRoles)}
|
||||
content="Loading Deployment Roles..."
|
||||
inline/>
|
||||
];
|
||||
|
||||
let children;
|
||||
|
@ -87,12 +92,16 @@ class DeploymentPlan extends React.Component {
|
|||
<DeploymentStep title="Specify Deployment Configuration"
|
||||
subTitle={deploymentConfigDescription}
|
||||
links={deploymentConfigLinks}/>
|
||||
<DeploymentStep title="Create Flavors and Register Nodes"
|
||||
subTitle={this.state.flavors.length > 0 ?
|
||||
'' : 'There are no flavors or nodes currently.'} />
|
||||
<DeploymentStep title="Configure and Assign Roles"
|
||||
subTitle="Parameters for all roles can be configured."
|
||||
links={roleConfigLinks}/>
|
||||
<DeploymentStep title="Register and Assign Nodes"
|
||||
links={registerAndAssignLinks}>
|
||||
<Roles roles={this.props.roles.toList().toJS()}
|
||||
introspectedNodes={this.props.introspectedNodes}
|
||||
unassignedIntrospectedNodes={this.props.unassignedIntrospectedNodes}
|
||||
fetchRoles={this.props.fetchRoles}
|
||||
fetchNodes={this.props.fetchNodes}
|
||||
isFetchingNodes={this.props.isFetchingNodes}
|
||||
loaded={this.props.rolesLoaded}/>
|
||||
</DeploymentStep>
|
||||
<DeploymentStep title="Deploy">
|
||||
<div className="actions pull-left">
|
||||
<a className={'link btn btn-primary btn-lg ' +
|
||||
|
@ -120,26 +129,40 @@ DeploymentPlan.propTypes = {
|
|||
children: React.PropTypes.node,
|
||||
choosePlan: React.PropTypes.func,
|
||||
currentPlanName: React.PropTypes.string,
|
||||
fetchNodes: React.PropTypes.func,
|
||||
fetchRoles: React.PropTypes.func,
|
||||
hasPlans: React.PropTypes.bool,
|
||||
inactivePlans: ImmutablePropTypes.map,
|
||||
introspectedNodes: ImmutablePropTypes.list,
|
||||
isFetchingNodes: React.PropTypes.bool,
|
||||
isFetchingPlans: React.PropTypes.bool,
|
||||
route: React.PropTypes.object
|
||||
isFetchingRoles: React.PropTypes.bool,
|
||||
roles: ImmutablePropTypes.map,
|
||||
rolesLoaded: React.PropTypes.bool,
|
||||
route: React.PropTypes.object,
|
||||
unassignedIntrospectedNodes: ImmutablePropTypes.list
|
||||
};
|
||||
|
||||
export function mapStateToProps(state) {
|
||||
return {
|
||||
currentPlanName: state.plans.get('currentPlanName'),
|
||||
isFetchingNodes: state.nodes.get('isFetching'),
|
||||
isFetchingPlans: state.plans.get('isFetchingPlans'),
|
||||
isFetchingRoles: state.roles.get('isFetching'),
|
||||
hasPlans: !state.plans.get('all').isEmpty(),
|
||||
inactivePlans: getAllPlansButCurrent(state)
|
||||
inactivePlans: getAllPlansButCurrent(state),
|
||||
introspectedNodes: getIntrospectedNodes(state),
|
||||
roles: state.roles.get('roles'),
|
||||
rolesLoaded: state.roles.get('loaded'),
|
||||
unassignedIntrospectedNodes: getUnassignedIntrospectedNodes(state)
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
choosePlan: planName => {
|
||||
dispatch(PlansActions.choosePlan(planName));
|
||||
}
|
||||
choosePlan: planName => dispatch(PlansActions.choosePlan(planName)),
|
||||
fetchRoles: () => dispatch(RolesActions.fetchRoles()),
|
||||
fetchNodes: () => dispatch(NodesActions.fetchNodes())
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -10,8 +10,10 @@ export default class DeploymentStep extends React.Component {
|
|||
</h3>
|
||||
<div className="row">
|
||||
<div className="col-sm-12">
|
||||
<span className="deployment-step-subtitle">{this.props.subTitle}</span>
|
||||
{this.props.links}
|
||||
<div className="deployment-step-subtitle">
|
||||
<span>{this.props.subTitle}</span>
|
||||
{this.props.links}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-sm-12">
|
||||
{this.props.children}
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
import React from 'react';
|
||||
|
||||
import Loader from '../ui/Loader';
|
||||
|
||||
export default class RoleCard extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className={`card-pf card-pf-accented role-card ${this.props.name}`}>
|
||||
<h2 className="card-pf-title">
|
||||
{this.props.title}
|
||||
</h2>
|
||||
<div className="card-pf-body">
|
||||
<Loader loaded={!this.props.isFetchingNodes}
|
||||
content="Loading Nodes..."
|
||||
inline>
|
||||
<p className="card-pf-utilization-details">
|
||||
<span className="card-pf-utilization-card-details-count">
|
||||
{this.props.assignedNodesCount}
|
||||
</span>
|
||||
<span className="card-pf-utilization-card-details-description">
|
||||
<span className="card-pf-utilization-card-details-line-1">Nodes assigned</span>
|
||||
<span className="card-pf-utilization-card-details-line-2">
|
||||
of {this.props.availableNodesCount} available
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
</Loader>
|
||||
</div>
|
||||
<div className="card-pf-footer">
|
||||
<p>
|
||||
<a href="#" className="card-pf-link-with-icon">
|
||||
<span className="pficon pficon-add-circle-o"></span>Assign Nodes
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
RoleCard.propTypes = {
|
||||
assignedNodesCount: React.PropTypes.number.isRequired,
|
||||
availableNodesCount: React.PropTypes.number.isRequired,
|
||||
isFetchingNodes: React.PropTypes.bool,
|
||||
name: React.PropTypes.string.isRequired,
|
||||
title: React.PropTypes.string.isRequired
|
||||
};
|
|
@ -0,0 +1,74 @@
|
|||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
|
||||
import Loader from '../ui/Loader';
|
||||
import RoleCard from './RoleCard';
|
||||
|
||||
export default class Roles extends React.Component {
|
||||
componentDidMount() {
|
||||
this.props.fetchRoles();
|
||||
this.props.fetchNodes();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if(!this.props.loaded) {
|
||||
this.props.fetchRoles();
|
||||
this.props.fetchNodes();
|
||||
}
|
||||
}
|
||||
|
||||
getAssignedNodes(roleName) {
|
||||
return this.props.introspectedNodes.filter(
|
||||
node => node.getIn(['properties', 'capabilities']).includes(`profile:${roleName}`)
|
||||
);
|
||||
}
|
||||
|
||||
renderRoleCards() {
|
||||
return this.props.roles.map(role => {
|
||||
return (
|
||||
<div className="col-xs-6 col-sm-4 col-md-3 col-lg-2" key={role.name}>
|
||||
<RoleCard name={role.name}
|
||||
title={role.title}
|
||||
isFetchingNodes={this.props.isFetchingNodes}
|
||||
assignedNodesCount={this.getAssignedNodes(role.name).size}
|
||||
availableNodesCount={this.props.introspectedNodes.size}/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Loader loaded={this.props.loaded}
|
||||
content="Loading Deployment Roles..."
|
||||
size="lg"
|
||||
inline>
|
||||
<Loader loaded={!this.props.isFetchingNodes}
|
||||
content="Loading Nodes..."
|
||||
inline>
|
||||
<p>
|
||||
There are <strong>{this.props.unassignedIntrospectedNodes.size}</strong> of <strong>
|
||||
{this.props.introspectedNodes.size}</strong> Introspected
|
||||
Nodes available to assign
|
||||
</p>
|
||||
</Loader>
|
||||
<div className="panel panel-default roles-panel">
|
||||
<div className="panel-body">
|
||||
<div className="row-cards-pf">
|
||||
{this.renderRoleCards()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Loader>
|
||||
);
|
||||
}
|
||||
}
|
||||
Roles.propTypes = {
|
||||
fetchNodes: React.PropTypes.func.isRequired,
|
||||
fetchRoles: React.PropTypes.func.isRequired,
|
||||
introspectedNodes: ImmutablePropTypes.list,
|
||||
isFetchingNodes: React.PropTypes.bool,
|
||||
loaded: React.PropTypes.bool.isRequired,
|
||||
roles: React.PropTypes.array.isRequired,
|
||||
unassignedIntrospectedNodes: ImmutablePropTypes.list
|
||||
};
|
|
@ -6,6 +6,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||
|
||||
import NavTab from '../ui/NavTab';
|
||||
import NodesActions from '../../actions/NodesActions';
|
||||
import { getRegisteredNodes,
|
||||
getIntrospectedNodes,
|
||||
getProvisionedNodes,
|
||||
getMaintenanceNodes } from '../../selectors/nodes';
|
||||
|
||||
class Nodes extends React.Component {
|
||||
componentDidMount() {
|
||||
|
@ -88,13 +92,10 @@ function mapStateToProps(state) {
|
|||
return {
|
||||
nodes: state.nodes.merge(
|
||||
Map({
|
||||
registered: state.nodes.get('all').filter( node => node.provision_state === 'available' &&
|
||||
!node.provision_updated_at ||
|
||||
node.provision_state === 'manageable' ),
|
||||
introspected: state.nodes.get('all').filter( node => node.provision_state === 'available' &&
|
||||
!!node.provision_updated_at ),
|
||||
provisioned: state.nodes.get('all').filter( node => node.instance_uuid ),
|
||||
maintenance: state.nodes.get('all').filter( node => node.maintenance )
|
||||
registered: getRegisteredNodes(state),
|
||||
introspected: getIntrospectedNodes(state),
|
||||
provisioned: getProvisionedNodes(state),
|
||||
maintenance: getMaintenanceNodes(state)
|
||||
})
|
||||
)
|
||||
};
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
import NodeStack from './NodeStack';
|
||||
|
||||
export default class NodePicker extends React.Component {
|
||||
/*
|
||||
Component that implements Node Count Picker expects onIncrement function
|
||||
(that expects increment parameter) passed through props from owner.
|
||||
*/
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="node-picker">
|
||||
<PickerArrow direction="left"
|
||||
increment={this.props.onIncrement.bind(this, -this.props.incrementValue)}/>
|
||||
<NodeStack count={this.props.nodeCount}/>
|
||||
<PickerArrow direction="right"
|
||||
increment={this.props.onIncrement.bind(this, this.props.incrementValue)}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
NodePicker.propTypes = {
|
||||
incrementValue: React.PropTypes.number,
|
||||
nodeCount: React.PropTypes.number.isRequired,
|
||||
onIncrement: React.PropTypes.func.isRequired
|
||||
};
|
||||
NodePicker.defaultProps = { incrementValue: 1 };
|
||||
|
||||
|
||||
export class PickerArrow extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<button className="picker-arrow" onClick={this.props.increment}>
|
||||
<span className={'fa fa-angle-' + this.props.direction}
|
||||
aria-hidden="true"></span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
}
|
||||
PickerArrow.propTypes = {
|
||||
direction: React.PropTypes.oneOf(['left', 'right']).isRequired,
|
||||
increment: React.PropTypes.func.isRequired
|
||||
};
|
|
@ -1,21 +0,0 @@
|
|||
import ClassNames from 'classnames';
|
||||
import React from 'react';
|
||||
|
||||
export default class NodeStack extends React.Component {
|
||||
render() {
|
||||
let classes = ClassNames({
|
||||
'stack': true,
|
||||
'single-stack': this.props.count == 2,
|
||||
'double-stack': this.props.count > 2
|
||||
});
|
||||
return (
|
||||
<div className="node-stack">
|
||||
<div className={classes}>{this.props.count}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
NodeStack.propTypes = {
|
||||
count: React.PropTypes.number.isRequired
|
||||
};
|
||||
|
|
@ -1,192 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
import RolesActions from '../../actions/RolesActions';
|
||||
import FlavorStore from '../../stores/FlavorStore';
|
||||
import NodePicker from './NodePicker';
|
||||
import NodeStack from './NodeStack';
|
||||
|
||||
export default class Roles extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
flavors: []
|
||||
};
|
||||
this.changeListener = this._onChange.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setState(FlavorStore.getState());
|
||||
FlavorStore.addChangeListener(this.changeListener);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
FlavorStore.removeChangeListener(this.changeListener);
|
||||
}
|
||||
|
||||
_onChange() {
|
||||
this.setState(FlavorStore.getState());
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-sm-12">
|
||||
<div className="page-header">
|
||||
<h1>Roles</h1>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-sm-12">
|
||||
<h3>Hardware</h3>
|
||||
<FlavorPanelList flavors={this.state.flavors}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// export class FreeRolesList extends React.Component {
|
||||
// render() {
|
||||
// let freeRoles = this.props.data.filter((role, index) => {
|
||||
// return role;
|
||||
// });
|
||||
// return (
|
||||
// <div className="row">
|
||||
// <RoleList roles={freeRoles}/>
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
export class FlavorPanelList extends React.Component {
|
||||
render() {
|
||||
let flavors = this.props.flavors.map((flavor, index) => {
|
||||
return (
|
||||
<FlavorPanel flavor={flavor} key={index}/>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<div>
|
||||
{flavors}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
FlavorPanelList.propTypes = {
|
||||
flavors: React.PropTypes.array.isRequired
|
||||
};
|
||||
|
||||
|
||||
export class FlavorPanel extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="panel panel-default flavor-panel">
|
||||
<div className="panel-heading">
|
||||
<h3 className="panel-title">
|
||||
<strong>{this.props.flavor.name}</strong>
|
||||
<small className='subheader'> {this.props.flavor.hwSpecs}</small>
|
||||
</h3>
|
||||
</div>
|
||||
<div className="panel-body">
|
||||
<div className="row">
|
||||
<div className="col-sm-4 col-md-3">
|
||||
<FreeNodesPanel nodeCount={this.props.flavor.freeNodeCount} />
|
||||
</div>
|
||||
<RoleList roles={this.props.flavor.roles}/>
|
||||
<div className="col-sm-4 col-md-3">
|
||||
<DropZonePanel />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
FlavorPanel.propTypes = {
|
||||
flavor: React.PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
|
||||
export class RoleList extends React.Component {
|
||||
render() {
|
||||
let roles = this.props.roles.map((role, index) => {
|
||||
return (
|
||||
<div className="col-sm-4 col-md-3" key={index}>
|
||||
<RolePanel role={role}/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<div>
|
||||
{roles}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
RoleList.propTypes = {
|
||||
roles: React.PropTypes.array.isRequired
|
||||
};
|
||||
|
||||
|
||||
export class FreeNodesPanel extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="panel panel-default role-panel free-nodes-panel">
|
||||
<div className="panel-heading">
|
||||
<h3 className="panel-title">Available Nodes</h3>
|
||||
</div>
|
||||
<div className="panel-body clearfix">
|
||||
<NodeStack count={this.props.nodeCount} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
FreeNodesPanel.propTypes = {
|
||||
nodeCount: React.PropTypes.number.isRequired
|
||||
};
|
||||
|
||||
|
||||
export class DropZonePanel extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="panel panel-default role-panel drop-zone-panel">
|
||||
<div className="panel-heading">
|
||||
<h3 className="panel-title">Add Role</h3>
|
||||
</div>
|
||||
<div className="panel-body clearfix">
|
||||
<span className="glyphicon glyphicon-plus"></span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class RolePanel extends React.Component {
|
||||
updateCount(increment) {
|
||||
let updatedRole = this.props.role;
|
||||
updatedRole.nodeCount = this.props.role.nodeCount + increment;
|
||||
RolesActions.updateRole(updatedRole);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={'panel panel-default role-panel ' + this.props.role.name.toLowerCase()}>
|
||||
<div className="panel-heading">
|
||||
<h3 className="panel-title">{this.props.role.name}</h3>
|
||||
</div>
|
||||
<div className="panel-body clearfix">
|
||||
<NodePicker nodeCount={this.props.role.nodeCount}
|
||||
onIncrement={this.updateCount.bind(this)}/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
RolePanel.propTypes = {
|
||||
role: React.PropTypes.object.isRequired
|
||||
};
|
|
@ -2,6 +2,44 @@ import ClassNames from 'classnames';
|
|||
import React from 'react';
|
||||
|
||||
export default class Loader extends React.Component {
|
||||
renderGlobalLoader(classes) {
|
||||
return (
|
||||
<div className={this.props.className}>
|
||||
<div className="modal modal-routed in" role="loading">
|
||||
<div className="modal-dialog modal-sm">
|
||||
<div className="modal-content">
|
||||
<div className="modal-body loader">
|
||||
<div className={classes}/>
|
||||
<div className="text-center">{this.props.content}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="modal-backdrop in"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderInlineLoader(classes) {
|
||||
return (
|
||||
<span className={this.props.className}>
|
||||
<span className={classes}></span>
|
||||
{this.props.content}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
renderDefaultLoader(classes) {
|
||||
return (
|
||||
<div style={{marginTop: `${this.props.height/2}px`,
|
||||
marginBottom: `${this.props.height/2}px`}}
|
||||
className={this.props.className}>
|
||||
<div className={classes}/>
|
||||
<div className="text-center">{this.props.content}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
let classes = ClassNames({
|
||||
'spinner': true,
|
||||
|
@ -14,36 +52,11 @@ export default class Loader extends React.Component {
|
|||
|
||||
if(!this.props.loaded) {
|
||||
if(this.props.global) {
|
||||
return (
|
||||
<div>
|
||||
<div className="modal modal-routed in" role="loading">
|
||||
<div className="modal-dialog modal-sm">
|
||||
<div className="modal-content">
|
||||
<div className="modal-body loader">
|
||||
<div className={classes}/>
|
||||
<div className="text-center">{this.props.content}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="modal-backdrop in"></div>
|
||||
</div>
|
||||
);
|
||||
return this.renderGlobalLoader(classes);
|
||||
} else if(this.props.inline) {
|
||||
return (
|
||||
<span>
|
||||
<span className={classes}></span>
|
||||
{this.props.content}
|
||||
</span>
|
||||
);
|
||||
return this.renderInlineLoader(classes);
|
||||
} else {
|
||||
return (
|
||||
<div style={{marginTop: `${this.props.height/2}px`,
|
||||
marginBottom: `${this.props.height/2}px`}}>
|
||||
<div className={classes}/>
|
||||
<div className="text-center">{this.props.content}</div>
|
||||
</div>
|
||||
);
|
||||
return this.renderDefaultLoader(classes);
|
||||
}
|
||||
}
|
||||
return React.createElement(this.props.component, {}, this.props.children);
|
||||
|
@ -54,6 +67,7 @@ Loader.propTypes = {
|
|||
React.PropTypes.arrayOf(React.PropTypes.node),
|
||||
React.PropTypes.node
|
||||
]),
|
||||
className: React.PropTypes.string,
|
||||
component: React.PropTypes.any, // Component to wrap children when loaded
|
||||
content: React.PropTypes.string,
|
||||
global: React.PropTypes.bool,
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
import keyMirror from 'keymirror';
|
||||
|
||||
export default keyMirror({
|
||||
FETCH_ROLES_PENDING: null,
|
||||
FETCH_ROLES_SUCCESS: null,
|
||||
FETCH_ROLES_FAILED: null
|
||||
});
|
|
@ -1,23 +0,0 @@
|
|||
export default [
|
||||
{
|
||||
name: 'Baremetal',
|
||||
hwSpecs: '1CPU, 40GB RAM, HDD 500GB',
|
||||
roles: [
|
||||
{
|
||||
name: 'Controller',
|
||||
nodeCount: 2
|
||||
},
|
||||
{
|
||||
name: 'Compute',
|
||||
nodeCount: 0
|
||||
}
|
||||
],
|
||||
nodeCount: 20
|
||||
},
|
||||
{
|
||||
name: 'Flavor2',
|
||||
hwSpecs: '1CPU, 20GB RAM, HDD 250GB',
|
||||
roles: [],
|
||||
nodeCount: 10
|
||||
}
|
||||
];
|
|
@ -1,22 +0,0 @@
|
|||
export default [
|
||||
{
|
||||
name: 'Controller',
|
||||
flavor: null
|
||||
},
|
||||
{
|
||||
name: 'Compute',
|
||||
flavor: null
|
||||
},
|
||||
{
|
||||
name: 'Ceph-Storage',
|
||||
flavor: null
|
||||
},
|
||||
{
|
||||
name: 'Cinder-Storage',
|
||||
flavor: null
|
||||
},
|
||||
{
|
||||
name: 'Swift-Storage',
|
||||
flavor: null
|
||||
}
|
||||
];
|
|
@ -0,0 +1,6 @@
|
|||
import { Record } from 'immutable';
|
||||
|
||||
export const Role = Record({
|
||||
name: '',
|
||||
title: ''
|
||||
});
|
|
@ -27,7 +27,6 @@ import Plans from './components/plan/Plans.js';
|
|||
import ProvisionedNodesTabPane from './components/nodes/ProvisionedNodesTabPane';
|
||||
import RegisterNodesDialog from './components/nodes/RegisterNodesDialog';
|
||||
import RegisteredNodesTabPane from './components/nodes/RegisteredNodesTabPane';
|
||||
import Roles from './components/roles/Roles.js';
|
||||
import TempStorage from './services/TempStorage.js';
|
||||
import store from './store';
|
||||
|
||||
|
@ -65,8 +64,6 @@ TempStorage.initialized.then(() => {
|
|||
</Route>
|
||||
</Route>
|
||||
|
||||
<Route path="roles" component={Roles}/>
|
||||
|
||||
<Redirect from="nodes" to="nodes/registered"/>
|
||||
<Route path="nodes" component={Nodes}>
|
||||
<Route path="registered" component={RegisteredNodesTabPane}>
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
export default [
|
||||
{
|
||||
name: 'control',
|
||||
title: 'Controller'
|
||||
},
|
||||
{
|
||||
name: 'compute',
|
||||
title: 'Compute'
|
||||
},
|
||||
{
|
||||
name: 'blockStorage',
|
||||
title: 'Block Storage'
|
||||
},
|
||||
{
|
||||
name: 'objectStorage',
|
||||
title: 'Object Storage'
|
||||
},
|
||||
{
|
||||
name: 'cephStorage',
|
||||
title: 'Ceph Storage'
|
||||
}
|
||||
];
|
|
@ -0,0 +1,3 @@
|
|||
import { Schema } from 'normalizr';
|
||||
|
||||
export const roleSchema = new Schema('roles', { idAttribute: 'name' });
|
|
@ -6,6 +6,7 @@ import notificationsReducer from './notificationsReducer';
|
|||
import parametersReducer from './parametersReducer';
|
||||
import plansReducer from './plansReducer';
|
||||
import registerNodesReducer from './registerNodesReducer';
|
||||
import rolesReducer from './rolesReducer';
|
||||
import validationsReducer from './validationsReducer';
|
||||
|
||||
const appReducer = combineReducers({
|
||||
|
@ -16,6 +17,7 @@ const appReducer = combineReducers({
|
|||
parameters: parametersReducer,
|
||||
plans: plansReducer,
|
||||
registerNodes: registerNodesReducer,
|
||||
roles: rolesReducer,
|
||||
validations: validationsReducer
|
||||
});
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { List, Map } from 'immutable';
|
||||
import { fromJS, List, Map } from 'immutable';
|
||||
|
||||
import NodesConstants from '../constants/NodesConstants';
|
||||
|
||||
|
@ -21,7 +21,7 @@ export default function nodesReducer(state = initialState, action) {
|
|||
|
||||
case NodesConstants.RECEIVE_NODES:
|
||||
return state
|
||||
.set('all', List(action.payload))
|
||||
.set('all', fromJS(action.payload))
|
||||
.set('isFetching', false);
|
||||
|
||||
case NodesConstants.START_NODES_OPERATION:
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
import { fromJS, Map } from 'immutable';
|
||||
|
||||
import PlansConstants from '../constants/PlansConstants';
|
||||
import RolesConstants from '../constants/RolesConstants';
|
||||
import { Role } from '../immutableRecords/roles';
|
||||
|
||||
const initialState = Map({
|
||||
loaded: false,
|
||||
isFetching: false,
|
||||
roles: Map()
|
||||
});
|
||||
|
||||
export default function rolesReducer(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
|
||||
case RolesConstants.FETCH_ROLES_PENDING:
|
||||
return state.set('isFetching', true);
|
||||
|
||||
case RolesConstants.FETCH_ROLES_SUCCESS:
|
||||
const roles = action.payload || {};
|
||||
return state.set('roles', fromJS(roles).map(role => new Role(role)))
|
||||
.set('isFetching', false)
|
||||
.set('loaded', true);
|
||||
|
||||
case RolesConstants.FETCH_ROLES_FAILED:
|
||||
return state.set('roles', Map())
|
||||
.set('isFetching', false)
|
||||
.set('loaded', true);
|
||||
|
||||
case PlansConstants.PLAN_CHOSEN:
|
||||
return state.set('loaded', false);
|
||||
|
||||
default:
|
||||
return state;
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
import { createSelector } from 'reselect';
|
||||
|
||||
const nodes = state => state.nodes.get('all');
|
||||
|
||||
export const getRegisteredNodes = createSelector(
|
||||
nodes, (nodes) => {
|
||||
return nodes.filter( node => node.get('provision_state') === 'available' &&
|
||||
!node.get('provision_updated_at') ||
|
||||
node.get('provision_state') === 'manageable' );
|
||||
}
|
||||
);
|
||||
|
||||
export const getIntrospectedNodes = createSelector(
|
||||
nodes, (nodes) => {
|
||||
return nodes.filter( node => node.get('provision_state') === 'available' &&
|
||||
!!node.get('provision_updated_at') );
|
||||
}
|
||||
);
|
||||
|
||||
export const getProvisionedNodes = createSelector(
|
||||
nodes, (nodes) => {
|
||||
return nodes.filter( node => node.get('instance_uuid') );
|
||||
}
|
||||
);
|
||||
|
||||
export const getMaintenanceNodes = createSelector(
|
||||
nodes, (nodes) => {
|
||||
return nodes.filter( node => node.get('maintenance') );
|
||||
}
|
||||
);
|
||||
|
||||
export const getUnassignedIntrospectedNodes = createSelector(
|
||||
getIntrospectedNodes, (introspectedNodes) => {
|
||||
return introspectedNodes.filterNot(
|
||||
node => node.getIn(['properties', 'capabilities']).match(/.*profile:.+(,|$)/)
|
||||
);
|
||||
}
|
||||
);
|
|
@ -1,28 +0,0 @@
|
|||
import { EventEmitter } from 'events';
|
||||
import AppDispatcher from '../dispatchers/AppDispatcher';
|
||||
|
||||
export default class BaseStore extends EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
subscribe(actionSubscribe) {
|
||||
this._dispatchToken = AppDispatcher.register(actionSubscribe());
|
||||
}
|
||||
|
||||
get dispatchToken() {
|
||||
return this._dispatchToken;
|
||||
}
|
||||
|
||||
emitChange() {
|
||||
this.emit('CHANGE');
|
||||
}
|
||||
|
||||
addChangeListener(cb) {
|
||||
this.on('CHANGE', cb);
|
||||
}
|
||||
|
||||
removeChangeListener(cb) {
|
||||
this.removeListener('CHANGE', cb);
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
import BaseStore from './BaseStore';
|
||||
import Flavors from '../data/Flavors';
|
||||
|
||||
class FlavorStore extends BaseStore {
|
||||
constructor() {
|
||||
super();
|
||||
this.subscribe(() => this._registerToActions.bind(this));
|
||||
this.state = {
|
||||
flavors: Flavors
|
||||
};
|
||||
}
|
||||
|
||||
_registerToActions(payload) {
|
||||
switch(payload.actionType) {
|
||||
case 'UPDATE_FLAVOR_ROLE':
|
||||
this.updateFlavorRole(payload.role);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
updateFlavorRole(role) {
|
||||
this.state.flavors[0].roles.filter((r) => { r.name == role.name; })[0] = role;
|
||||
this.state.flavors[0].freeNodeCount = this._calculateFreeNodes(this.state.flavors[0]);
|
||||
this.emitChange();
|
||||
}
|
||||
|
||||
_calculateFreeNodes(flavor) {
|
||||
let reserved = 0;
|
||||
flavor.roles.forEach((role) => { reserved += role.nodeCount; });
|
||||
return flavor.nodeCount - reserved;
|
||||
}
|
||||
|
||||
getState() {
|
||||
this.state.flavors.forEach((flavor) => {
|
||||
flavor.freeNodeCount = this._calculateFreeNodes(flavor);
|
||||
});
|
||||
return this.state;
|
||||
}
|
||||
}
|
||||
|
||||
export default new FlavorStore();
|
|
@ -1,60 +0,0 @@
|
|||
import * as _ from 'lodash';
|
||||
import BaseStore from './BaseStore';
|
||||
import LoginConstants from '../constants/LoginConstants';
|
||||
|
||||
class LoginStore extends BaseStore {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.subscribe(() => this._registerToActions.bind(this));
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
_registerToActions(payload) {
|
||||
switch(payload.actionType) {
|
||||
case LoginConstants.USER_AUTH_STARTED:
|
||||
break;
|
||||
case LoginConstants.LOGIN_USER:
|
||||
this.onLoginUser(payload.keystoneAccess);
|
||||
break;
|
||||
case LoginConstants.LOGOUT_USER:
|
||||
this.onLogoutUser();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
onLoginUser(keystoneAccess) {
|
||||
this.state = {
|
||||
token: keystoneAccess.token,
|
||||
user: keystoneAccess.user,
|
||||
serviceCatalog: keystoneAccess.serviceCatalog,
|
||||
metadata: keystoneAccess.metadata
|
||||
};
|
||||
this.emitChange();
|
||||
}
|
||||
|
||||
onLogoutUser() {
|
||||
this.state = {};
|
||||
this.emitChange();
|
||||
}
|
||||
|
||||
getState() {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
isLoggedIn() {
|
||||
return !!this.state.user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the public url of an openstack API,
|
||||
* determined by the service's name.
|
||||
*/
|
||||
getServiceUrl(name) {
|
||||
return _.result(_.find(this.state.serviceCatalog, 'name', name), 'endpoints[0].publicURL');
|
||||
}
|
||||
}
|
||||
|
||||
export default new LoginStore();
|
|
@ -1,34 +0,0 @@
|
|||
import BaseStore from './BaseStore';
|
||||
import ValidationsConstants from '../constants/ValidationsConstants';
|
||||
|
||||
class ValidationsStore extends BaseStore {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.subscribe(() => this._registerToActions.bind(this));
|
||||
this.state = {
|
||||
stages: []
|
||||
};
|
||||
}
|
||||
|
||||
_registerToActions(payload) {
|
||||
switch(payload.actionType) {
|
||||
case ValidationsConstants.LIST_STAGES:
|
||||
this.onListStages(payload.stages);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
onListStages(stages) {
|
||||
this.state.stages = stages;
|
||||
this.emitChange();
|
||||
}
|
||||
|
||||
getState() {
|
||||
return this.state;
|
||||
}
|
||||
}
|
||||
|
||||
export default new ValidationsStore();
|
|
@ -27,8 +27,36 @@ ol.deployment-step-list {
|
|||
}
|
||||
|
||||
.deployment-step-subtitle {
|
||||
margin-left: 10px;
|
||||
vertical-align: middle;
|
||||
margin-bottom: 10px;
|
||||
& > span {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.roles-panel .panel-body {
|
||||
padding-bottom: 0;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.role-color-mixin(@colour) {
|
||||
border-top-color: @colour;
|
||||
}
|
||||
|
||||
.card-pf.role-card {
|
||||
.card-pf-body {
|
||||
margin: 0;
|
||||
.card-pf-utilization-details {
|
||||
border-bottom: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
&.card-pf-accented {
|
||||
&.control{ .role-color-mixin(#f0ab00); }
|
||||
&.compute{ .role-color-mixin(#007a87); }
|
||||
&.blockStorage{ .role-color-mixin(#3b0083); }
|
||||
&.objectStorage{ .role-color-mixin(#0088ce); }
|
||||
&.cephStorage{ .role-color-mixin(#b35c00); }
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue