Forbid interfaces configuration for nodes with different networks

If selected nodes have different networks then UI should provide
links to configuration of nodes with the same set of networks.

Closes-Bug: #1588865

Change-Id: I0233ec8aaf9221ac84137ecb254010e7863e4122
This commit is contained in:
Julia Aranovich 2016-06-07 14:08:05 +03:00
parent f5954e6d40
commit 90de7ef447
4 changed files with 89 additions and 8 deletions

View File

@ -3661,6 +3661,15 @@ input[type=range] {
// Interfaces styles
.ifc-management-panel {
@ifc-panel-indent: 5px;
.different-networks-alert a {
display: block;
margin-top: @ifc-panel-indent;
}
}
.ifc-list {
.ifc-container {
@base-ifc-indent: 5px;

View File

@ -458,7 +458,10 @@
"dpdk_description": "The Data Plane Development Kit (DPDK) provides high-performance packet processing libraries and user space drivers.",
"locked_dpdk_bond": "DPDK cannot be enabled because not all slave interfaces support it",
"different_availability": "<Different Availability>",
"availability_tooltip": "Some network interfaces do not support this feature, therefore, these properties will not change after saving."
"availability_tooltip": "Some network interfaces do not support this feature, therefore, these properties will not change after saving.",
"nodes_have_different_networks": "Interfaces of the selected nodes can not be configured in bulk. Configure them separately. The following nodes have different sets of networks:",
"node_networks": "__count__ node has __networks__ networks",
"node_networks_plural": "__count__ nodes have __networks__ networks"
},
"configure_disks": {
"no_disks": "No unassigned disks available.",

View File

@ -109,13 +109,12 @@ var NodesTab = React.createClass({
},
componentWillReceiveProps(newProps) {
var screen = this.getScreen(newProps);
if (this.state.screen !== screen && this.checkScreenExists(screen)) {
var screenOptions = this.getScreenOptions(newProps);
var newState = {
screen: screen,
screenOptions: screenOptions,
screenData: {}
};
var screenOptions = this.getScreenOptions(newProps);
if (
this.state.screen !== screen && this.checkScreenExists(screen) ||
!_.isEqual(this.state.screenOptions, screenOptions)
) {
var newState = {screen, screenOptions, screenData: {}};
if (this.shouldScreenDataBeLoaded(screen)) {
this.setState(_.extend(newState, {loading: true}));
this.loadScreenData(screen, screenOptions);

View File

@ -599,6 +599,23 @@ var EditNodeInterfacesScreen = React.createClass({
return _.intersection(... _.map(interfaces, this.getAvailableBondingTypes));
},
render() {
var nodesByNetworksMap = {};
this.props.nodes.each((node) => {
var networkNames = _.flatten(
node.interfaces.map((ifc) => ifc.get('assigned_networks').pluck('name'))
).sort();
nodesByNetworksMap[networkNames] =
_.union((nodesByNetworksMap[networkNames] || []), [node.id]);
});
if (_.size(nodesByNetworksMap) > 1) {
return (
<ErrorScreen
{... _.pick(this.props, 'nodes', 'cluster')}
nodesByNetworksMap={nodesByNetworksMap}
/>
);
}
var {nodes, interfaces} = this.props;
var {interfacesByIndex, indexByInterface} = this.state;
var nodeNames = nodes.pluck('name');
@ -780,6 +797,59 @@ var EditNodeInterfacesScreen = React.createClass({
}
});
var ErrorScreen = React.createClass({
render() {
var {nodes, cluster, nodesByNetworksMap} = this.props;
return (
<div className='ifc-management-panel row'>
<div className='title'>
{i18n(
ns + 'read_only_title',
{count: nodes.length, name: nodes.pluck('name').join(', ')}
)}
</div>
{_.size(nodesByNetworksMap) > 1 &&
<div className='col-xs-12'>
<div className='alert alert-danger different-networks-alert'>
{i18n(ns + 'nodes_have_different_networks')}
{_.map(nodesByNetworksMap, (nodeIds, networkNames) => {
return (
<a
key={networkNames}
className='no-leave-check'
href={
'#cluster/' + cluster.id + '/nodes/interfaces/' +
utils.serializeTabOptions({nodes: nodeIds})
}
>
{i18n(ns + 'node_networks', {
count: nodeIds.length,
networks: _.map(networkNames.split(','), (name) => i18n('network.' + name))
.join(', ')
})}
</a>
);
})}
</div>
</div>
}
<div className='col-xs-12 page-buttons content-elements'>
<div className='well clearfix'>
<div className='btn-group'>
<a
className='btn btn-default'
href={'#cluster/' + cluster.id + '/nodes'}
>
{i18n('cluster_page.nodes_tab.back_to_nodes_button')}
</a>
</div>
</div>
</div>
</div>
);
}
});
var NodeInterface = React.createClass({
statics: {
target: {