Unassigned networks on interfaces configuration screen

Closes-Bug: #1606177

Change-Id: I211fa81474d5f511584924e3ab2dae956930f1f1
(cherry picked from commit cff512b7ad)
This commit is contained in:
Julia Aranovich 2016-11-15 10:58:37 +03:00
parent b66841a966
commit 5f54c950d7
6 changed files with 210 additions and 36 deletions

View File

@ -3912,7 +3912,7 @@ input[type=range] {
}
}
.ifc-list {
.ifc-list, .unassigned-networks {
.ifc-container {
@base-ifc-indent: 5px;
margin-bottom: @base-ifc-indent * 2;
@ -4115,15 +4115,15 @@ input[type=range] {
.physnet {
label {width: 250px;}
}
.toggle-configuration-control {
.glyphicon {
margin-top: 9px;
left: 50px;
cursor: pointer;
transition: transform .35s ease;
&.rotate {
transform: rotate(-180deg);
}
}
.toggle-configuration-control {
.glyphicon {
margin-top: 9px;
left: 50px;
cursor: pointer;
transition: transform .35s ease;
&.rotate {
transform: rotate(-180deg);
}
}
}
@ -4176,16 +4176,16 @@ input[type=range] {
}
.networks-block {
padding: @base-ifc-indent * 2;
padding: 0;
> div {
padding: 0;
padding: @base-ifc-indent * 2;
}
.ifc-networks {
@network-block-width: 120px;
@network-block-height: @network-block-width / 2;
text-align: center;
font-weight: 100;
min-height: @network-block-height + @base-ifc-indent;
min-height: @network-block-height;
margin-right: 5px;
.network-block {
@ -4195,7 +4195,6 @@ input[type=range] {
border-box: sizing;
background-color: @ifc-container-dark-color;
margin-left: @base-ifc-indent;
margin-bottom: @base-ifc-indent;
padding: @base-ifc-indent * 2 @base-ifc-indent @base-ifc-indent;
cursor: move;
.network-name {
@ -4282,6 +4281,43 @@ input[type=range] {
}
}
.unassigned-networks {
.ifc-container .ifc-inner-container {
background: #f5f5f5;
border-color: #e3e3e3;
.ifc-header {
width: 100%;
.common-ifc-name.no-checkbox {
padding: 0 5px;
}
.toggle-configuration-control {
.glyphicon {
margin-top: 0;
}
}
}
.networks-block .ifc-networks {
min-height: 80px;
.no-networks {
padding-left: 5px;
}
}
&.compact {
.networks-block .ifc-networks {
min-height: 70px;
}
}
&.collapsed {
.ifc-header {
border-bottom: none;
.common-ifc-name.no-checkbox {
padding-top: 0px;
}
}
}
}
}
// Healthcheck tab
.healthcheck-tab {
@padding-top-controls: 10px;

View File

@ -86,6 +86,24 @@ class InterfacesPage {
.then((ifcElement) => this.remote.dragTo(ifcElement));
}
removeNetwork(networkName) {
return this.remote
.findAllByCssSelector('div.network-block')
.then(
(networkElements) => networkElements.reduce(
(result, networkElement) => networkElement
.getVisibleText()
.then((currentNetworkName) => {
return currentNetworkName === networkName ? networkElement : result;
}),
null
)
)
.then((networkElement) => this.remote.dragFrom(networkElement))
.then(() => this.remote.findByCssSelector('.unassigned-networks .ifc-inner-container'))
.then((ifcElement) => this.remote.dragTo(ifcElement));
}
selectInterface(ifcName) {
return this.remote
.then(() => this.findInterfaceElement(ifcName))

View File

@ -83,6 +83,22 @@ registerSuite(() => {
'MTU control is hidden after clicking MTU link again'
);
},
'Unassigned networks'() {
return this.remote
.assertElementExists('.unassigned-networks .collapsed', 'Unassigned networks block exists')
.assertElementNotExists(
'.unassigned-networks .networks-block.collapse.in',
'Unassigned networks block is collapsed by default'
)
.clickByCssSelector('.unassigned-networks .toggle-configuration-control .glyphicon')
.then(() => interfacesPage.removeNetwork('Public'))
.assertElementTextEquals(
'.unassigned-networks .network-block .network-name',
'Public',
'Public network was successfully removed'
)
.assertElementEnabled('.btn-apply', 'Network removal can be saved');
},
'Untagged networks error'() {
return this.remote
.then(() => interfacesPage.assignNetworkToInterface('Public', 'eth0'))

View File

@ -507,6 +507,8 @@
"fast": "Fast"
},
"bonding_policy": "Xmit Hash Policy",
"unassigned_networks": "Unassigned Networks (__count__)",
"no_unassigned_networks": "There are no unassigned networks",
"drag_and_drop_description": "You can drag and drop logical networks between the interfaces",
"configuration_error": {
"title": "Interfaces Configuration Error",

View File

@ -1271,10 +1271,12 @@ var Network = React.createClass({
return (
<div className={'forms-box ' + networkName}>
<h3 className='networks'>
{i18n('network.' + networkName)}
{i18n('network.' + networkName, {defaultValue: networkName})}
<NetworkRequirementsHelp sectionName={networkName} />
</h3>
<div className='network-description'>{i18n('network.descriptions.' + networkName)}</div>
<div className='network-description'>
{i18n('network.descriptions.' + networkName, {defaultValue: networkName})}
</div>
<CidrControl
{... this.composeProps('cidr')}
{... _.pick(this.props, 'cluster', 'network')}

View File

@ -781,6 +781,17 @@ var EditNodeInterfacesScreen = React.createClass({
</div>}
</div>
]}
<UnassignedNetworksDropTarget
networks={
cluster.get('networkConfiguration').get('networks').reject(
(network) => interfaces.some(
(ifc) => ifc.get('assigned_networks').some({name: network.get('name')})
)
)
}
networkingParameters={cluster.get('networkConfiguration').get('networking_parameters')}
{...{viewMode, locked, interfaces}}
/>
<div className='ifc-list col-xs-12'>
{interfaces.map((ifc, index) => {
var ifcName = ifc.get('name');
@ -859,6 +870,98 @@ var EditNodeInterfacesScreen = React.createClass({
}
});
var UnassignedNetworks = React.createClass({
statics: {
target: {
drop({interfaces}, monitor) {
var {networkName, interfaceName} = monitor.getItem();
var sourceInterface = interfaces.find({name: interfaceName});
var network = sourceInterface.get('assigned_networks').find({name: networkName});
sourceInterface.get('assigned_networks').remove(network);
// trigger 'change' event to update screen buttons state
sourceInterface.trigger('change', sourceInterface);
},
canDrop(props, monitor) {
return !!monitor.getItem().interfaceName;
}
},
collect(connect, monitor) {
return {
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver(),
canDrop: monitor.canDrop()
};
}
},
getInitialState() {
return {collapsed: true};
},
toggle() {
$(ReactDOM.findDOMNode(this.refs['networks-panel'])).collapse('toggle');
},
assignNetworksPanelEvents() {
$(ReactDOM.findDOMNode(this.refs['networks-panel']))
.on('show.bs.collapse', () => this.setState({collapsed: false}))
.on('hide.bs.collapse', () => this.setState({collapsed: true}));
},
componentDidMount() {
this.assignNetworksPanelEvents();
},
render() {
var {networks, viewMode, connectDropTarget} = this.props;
var {collapsed} = this.state;
return connectDropTarget(
<div className='unassigned-networks col-xs-12'>
<div className='ifc-container'>
<div className={
utils.classNames({
'ifc-inner-container': true,
compact: viewMode === 'compact',
collapsed
})
}>
<div className='ifc-header row'>
<div className='common-ifc-name no-checkbox col-xs-11'>
{i18n(ns + 'unassigned_networks', {count: networks.length})}
</div>
<div className='col-xs-1 toggle-configuration-control'>
<i
className={utils.classNames({
'glyphicon glyphicon-menu-down': true,
rotate: !collapsed
})}
onClick={this.toggle}
/>
</div>
</div>
<div className='networks-block collapse' ref='networks-panel'>
<div className='ifc-networks'>
{networks.map((network) =>
<DraggableNetwork
key={'network-' + network.id}
{... _.pick(this.props, ['networkingParameters', 'viewMode', 'locked'])}
label={network.get('name')}
network={network}
/>
)}
{!networks.length &&
<div className='no-networks'>{i18n(ns + 'no_unassigned_networks')}</div>
}
</div>
</div>
</div>
</div>
</div>
);
}
});
var UnassignedNetworksDropTarget = DropTarget(
'network',
UnassignedNetworks.target,
UnassignedNetworks.collect
)(UnassignedNetworks);
var ErrorScreen = React.createClass({
render() {
var {nodes, cluster, nodesByNetworksMap} = this.props;
@ -915,12 +1018,15 @@ var ErrorScreen = React.createClass({
var NodeInterface = React.createClass({
statics: {
target: {
drop(props, monitor) {
var targetInterface = props.interface;
var sourceInterface = props.interfaces.find({name: monitor.getItem().interfaceName});
var network = sourceInterface.get('assigned_networks')
.find({name: monitor.getItem().networkName});
sourceInterface.get('assigned_networks').remove(network);
drop({interface: targetInterface, interfaces, cluster}, monitor) {
var {networkName, interfaceName} = monitor.getItem();
var sourceInterface = interfaces.find({name: interfaceName});
var network = sourceInterface ?
sourceInterface.get('assigned_networks').find({name: networkName})
:
cluster.get('networkConfiguration').get('networks')
.find({name: networkName}).pick('id', 'name');
if (sourceInterface) sourceInterface.get('assigned_networks').remove(network);
targetInterface.get('assigned_networks').add(network);
// trigger 'change' event to update screen buttons state
targetInterface.trigger('change', targetInterface);
@ -1065,7 +1171,6 @@ var NodeInterface = React.createClass({
<div
className={utils.classNames({
'ifc-inner-container': true,
nodrag: !!networkErrors,
over: this.props.isOver && this.props.canDrop,
'has-changes': this.props.hasChanges,
[ifc.get('name')]: true,
@ -1204,7 +1309,7 @@ var NodeInterface = React.createClass({
key={'network-' + network.id}
{... _.pick(this.props, ['locked', 'interface'])}
networkingParameters={networkingParameters}
interfaceNetwork={interfaceNetwork}
label={interfaceNetwork.get('name')}
network={network}
/>
);
@ -1234,8 +1339,8 @@ var NodeInterface = React.createClass({
isMassConfiguration={!!this.props.nodes.length}
bondingModeChanged={this.bondingModeChanged}
/>
:
<div className='clearfix'></div>
:
<div className='clearfix' />
}
</div>
</div>
@ -1255,7 +1360,7 @@ var Network = React.createClass({
beginDrag(props) {
return {
networkName: props.network.get('name'),
interfaceName: props.interface.get('name')
interfaceName: props.interface && props.interface.get('name')
};
},
canDrag(props) {
@ -1270,9 +1375,7 @@ var Network = React.createClass({
}
},
render() {
var network = this.props.network;
var interfaceNetwork = this.props.interfaceNetwork;
var networkingParameters = this.props.networkingParameters;
var {network, label, networkingParameters} = this.props;
var classes = {
'network-block pull-left': true,
disabled: !this.constructor.source.canDrag(this.props),
@ -1283,10 +1386,7 @@ var Network = React.createClass({
return this.props.connectDragSource(
<div className={utils.classNames(classes)}>
<div className='network-name'>
{i18n(
'network.' + interfaceNetwork.get('name'),
{defaultValue: interfaceNetwork.get('name')}
)}
{i18n('network.' + label, {defaultValue: label})}
</div>
{vlanRange &&
<div className='vlan-id'>
@ -1304,8 +1404,8 @@ var DraggableNetwork = DragSource('network', Network.source, Network.collect)(Ne
var NodeInterfaceAttributes = React.createClass({
assignConfigurationPanelEvents() {
$(ReactDOM.findDOMNode(this.refs['configuration-panel']))
.on('show.bs.collapse', () => this.setState({pendingToggle: false, collapsed: false}))
.on('hide.bs.collapse', () => this.setState({pendingToggle: false, collapsed: true}));
.on('show.bs.collapse', () => this.setState({pendingToggle: false, collapsed: false}))
.on('hide.bs.collapse', () => this.setState({pendingToggle: false, collapsed: true}));
},
componentDidMount() {
this.assignConfigurationPanelEvents();