[React] Edit node disks screen

Related to blueprint backbone-to-react

Change-Id: I5b19884a3f1a2ed253b67bcf3583e99739c716e1
This commit is contained in:
Kate Pimenova 2015-06-09 16:58:06 +03:00
parent 2d18ec2cfc
commit 4244eb13e3
16 changed files with 447 additions and 739 deletions

View File

@ -82,6 +82,30 @@ define(['jquery', 'underscore', 'backbone', 'dispatcher', 'react', 'react.backbo
$('html').off(this.state.clickEventName);
Backbone.history.off('route', null, this);
}
},
nodeConfigurationScreenMixin: {
goToNodeList: function(cluster) {
if (!cluster) cluster = this.props.cluster;
app.navigate('#cluster/' + cluster.id + '/nodes', {trigger: true, replace: true});
},
isLockedScreen: function() {
var nodesAvailableForChanges = this.props.nodes.any(function(node) {
return node.get('pending_addition') || node.get('status') == 'error';
});
return !nodesAvailableForChanges ||
this.props.cluster && !!this.props.cluster.tasks({group: 'deployment', status: 'running'}).length;
},
showDiscardChangesDialog: function() {
var dialogs = require('jsx!views/dialogs');
dialogs.DiscardSettingsChangesDialog.show({cb: this.goToNodeList});
},
returnToNodeList: function() {
if (this.hasChanges()) {
this.showDiscardChangesDialog();
} else {
this.goToNodeList();
}
}
}
};
});

View File

@ -613,7 +613,7 @@ define([
var error;
var unallocatedSpace = this.getUnallocatedSpace({volumes: attrs.volumes});
if (unallocatedSpace < 0) {
error = 'Volume groups total size exceeds available space of ' + utils.formatNumber(unallocatedSpace * -1) + ' MB';
error = i18n('cluster_page.nodes_tab.configure_disks.validation_error', {size: utils.formatNumber(unallocatedSpace * -1)});
}
return error;
}
@ -634,15 +634,10 @@ define([
var groupAllocatedSpace = currentDisk.collection.reduce(function(sum, disk) {return disk.id == currentDisk.id ? sum : sum + disk.get('volumes').findWhere({name: this.get('name')}).get('size');}, 0, this);
return minimum - groupAllocatedSpace;
},
validate: function(attrs, options) {
var error;
var min = this.getMinimalSize(options.minimum);
if (_.isNaN(attrs.size)) {
error = 'Invalid size';
} else if (attrs.size < min) {
error = 'The value is too low. You must allocate at least ' + utils.formatNumber(min) + ' MB';
}
return error;
getMaxSize: function() {
var volumes = this.collection.disk.get('volumes'),
diskAllocatedSpace = volumes.reduce(function(total, volume) {return this.get('name') == volume.get('name') ? total : total + volume.get('size');}, 0, this);
return this.collection.disk.get('size') - diskAllocatedSpace ;
}
});

View File

@ -1987,9 +1987,18 @@ button, .btn:not(.btn-link) {.font-semibold;}
// Disks styles
.disk-box {
label {
.font-semibold;
@colors: green, blue, orange, gray, pure-red, black, red;
.generate-colors(length(@colors));
.generate-colors(@n, @i: 1) when (@i =< @n) {
.volume-type-@{i} {
@color: extract(@colors, @i);
background: @@color;
}
.generate-colors(@n, (@i + 1));
}
label {.font-semibold;}
.disk-visual {
@disk-visual-block-height: 50px;
@disk-visual-block-padding: 4px;
@ -2010,6 +2019,7 @@ button, .btn:not(.btn-link) {.font-semibold;}
}
.close-btn {
position: absolute;
z-index: 1000;
top: 0;
right: @disk-visual-block-padding;
cursor: pointer;
@ -2027,45 +2037,59 @@ button, .btn:not(.btn-link) {.font-semibold;}
.disk-info-box {
.form-group {
margin-bottom: 0;
label {
text-transform: capitalize;
}
p {
padding: 0;
}
label {text-transform: capitalize;}
p {padding: 0;}
}
}
.disk-utility-box {
@disk-form-margin: 5px;
.form-group {
margin-bottom: @disk-form-margin;
margin: 0;
.volume-group-flag {
display: inline-block;
width: 10px;
}
.volume-group-use-all-allowed-btn button {
font-size: @base-font-size - 4px;
padding: 0;
text-transform: uppercase;
}
.volume-group-size-label {
padding: 0;
}
.volume-group-input {
padding-right: @disk-form-margin;
}
label, .volume-group-use-all-allowed-btn, .volume-group-size-label {
padding-top: 6px;
.volume-group-range, .volume-group-input {
label, .help-block {display: none;}
}
.volume-group-label {
margin: 0;
padding-top: @disk-form-margin;
}
.volume-group-size-label {
padding: @disk-form-margin;
text-align: right;
}
input[type=range] {
/*fix for FF unable to apply focus style bug */
border: 1px solid transparent;
margin-top: @disk-form-margin;
&:focus {outline: none;}
&:focus::-webkit-slider-runnable-track {background: transparent;}
&:disabled {opacity: 0.4;}
}
input[type=number] {
position: relative;
top: -2px;
}
}
.volume-group-error {
.volume-group {margin-bottom: @disk-form-margin;}
.volume-group-notice {
font-size: @base-font-size - 2px;
margin: 0 @disk-form-margin * 2 @disk-form-margin * 2;
color: @blue;
margin: 0 @disk-form-margin @disk-form-margin * 2 0;
}
}
}
}
.node-disks + .page-buttons {
margin-top: 30px;
}
// Interfaces styles
.ifc-list {

View File

@ -1,23 +0,0 @@
<div class="row">
<div class="title">
<%- i18n('cluster_page.nodes_tab.configure_disks.title', {count: nodes.length, name: nodes.length && nodes.at(0).get('name')}) %>
</div>
<div class="col-xs-12 node-disks">
<div class="progress">
<div class="progress-bar progress-bar-striped active" style="width: 100%;"></div>
</div>
</div>
<div class="col-xs-12 page-buttons">
<div class="well clearfix">
<div class="btn-group">
<button class="btn btn-default btn-return" data-i18n="cluster_page.nodes_tab.back_to_nodes_button"></button>
</div>
<div class="btn-group pull-right">
<button data-i18n="common.load_defaults_button" class="btn btn-default btn-defaults"></button>
<button data-i18n="common.cancel_changes_button" class="btn btn-default btn-revert-changes"></button>
<button data-i18n="common.apply_button" class="btn btn-success btn-apply"></button>
</div>
</div>
</div>

View File

@ -1,89 +0,0 @@
<% var diskName = disk.get('name') %>
<div class="col-xs-12 disk-box" data-disk="<%- disk.id %>">
<div class="row">
<h4 class="col-xs-6">
<%- diskName %> (<%- disk.id %>)
</h4>
<h4 class="col-xs-6 text-right">
<%- i18n('cluster_page.nodes_tab.configure_disks.total_space') %>: <%= showDiskSize(disk.get('size'), 2) %>
</h4>
</div>
<div class="row disk-visual clearfix" data-toggle="collapse" data-target="#<%- diskName %>">
<% volumes.each(function(volume) { %>
<div class="volume-group pull-left" data-volume="<%- volume.get('name') %>" style="width:0;">
<div class="text-center">
<div><%- volume.get('label') %></div>
<div class="volume-group-size"><%= showDiskSize(0) %></div>
</div>
<div class="close-btn hide">&times;</div>
</div>
<% }) %>
<div class="volume-group pull-left" data-volume="unallocated" style="width: 100%">
<div class="text-center">
<div data-i18n="cluster_page.nodes_tab.configure_disks.unallocated"></div>
<div class="volume-group-size"><%= showDiskSize(disk.get('size'), 2) %></div>
</div>
</div>
</div>
<div class="row collapse disk-details" id="<%- diskName %>">
<div class="col-xs-6">
<% if (diskMetaData) { %>
<h5 data-i18n="cluster_page.nodes_tab.configure_disks.disk_information"></h5>
<div class="form-horizontal disk-info-box">
<% _.each(sortEntryProperties(diskMetaData, ['name', 'model', 'size']), function(propertyName) { %>
<div class="form-group">
<label class="col-xs-2">
<%- propertyName.replace(/_/g, ' ') %>
</label>
<div class="col-xs-10">
<p class="form-control-static">
<%- propertyName == 'size' ? showDiskSize(diskMetaData[propertyName]) : diskMetaData[propertyName] %>
</p>
</div>
</div>
<% }) %>
</div>
<% } %>
</div>
<div class="col-xs-6">
<h5 data-i18n="cluster_page.nodes_tab.configure_disks.volume_groups"></h5>
<div class="form-horizontal disk-utility-box">
<% volumes.each(function(volume) { %>
<% var volumeName = volume.get('name') %>
<div class="form-group volume-group" data-volume="<%- volumeName %>">
<label class="col-xs-4">
<span class="volume-group-flag <%- volumeName %>">&nbsp;</span>
<%- volume.get('label') %>
</label>
<div class="col-xs-3 volume-group-use-all-allowed-btn">
<% if (!locked) { %>
<button class="btn btn-link" data-i18n="cluster_page.nodes_tab.configure_disks.use_all_allowed_space"></button>
<% } %>
</div>
<div class="col-xs-4 volume-group-input">
<input
id="<%- disk.id + '-' + volumeName %>"
type="text"
class="form-control"
<%= locked ? 'disabled' : '' %>
name="<%- volumeName %>"
value="<%- disk.get('volumes').findWhere({name: volumeName}).get('size') || 0 %>"
/>
</div>
<div class="col-xs-1 volume-group-size-label" data-i18n="common.size.mb"></div>
</div>
<div class="volume-group-error text-danger text-right"></div>
<% }) %>
<div class="volume-group-error common text-danger text-right"></div>
</div>
</div>
</div>
</div>

View File

@ -1,92 +0,0 @@
<div class="clearfix"></div>
<% var slaveInterfaces = ifc.getSlaveInterfaces() %>
<div class="physical-network-box" data-name="<%- ifc.get('name') %>">
<div class="network-box-item">
<% if (ifc.isBond()) { %>
<div class="network-box-name">
<% if (bondingAvailable) { %>
<label>
<div class="pull-left">
<div class="custom-tumbler network-bond-name-checkbox">
<input type="checkbox">
<!-- [if !IE |(gte IE 9)]> --><span>&nbsp;</span><!-- <![endif] -->
</div>
</div>
<div class="network-bond-name pull-left"><%- ifc.get('name') %></div>
</label>
<% } else { %>
<div class="network-bond-name pull-left disabled"><%- ifc.get('name') %></div>
<% } %>
<div class="network-bond-mode pull-right">
<b><%- i18n('cluster_page.nodes_tab.configure_interfaces.bonding_mode') %>:</b>
<span>
<select name="mode" <%= bondingAvailable ? '' : 'disabled' %>></select>
</span>
</div>
<div class="clearfix"></div>
</div>
<div class="physical-network-checkbox"></div>
<% } else { %>
<div class="physical-network-checkbox">
<% var bondable = bondingAvailable && !ifc.get('assigned_networks').find(function(interfaceNetwork) {return interfaceNetwork.getFullNetwork().get('meta').unmovable}) %>
<% if (bondable) { %>
<label>
<div class="custom-tumbler">
<input type="checkbox">
<!-- [if !IE |(gte IE 9)]> --><span>&nbsp;</span><!-- <![endif] -->
</div>
</label>
<% } %>
</div>
<% } %>
<div class="network-connections-block">
<% _.each(slaveInterfaces, function(slaveInterface) { %>
<div class="network-interfaces-status">
<div class="interface-<%= slaveInterface.get('state') != 'down' ? 'online' : 'offline'%>"></div>
<div class="network-interfaces-name"><%- slaveInterface.get('name') %></div>
</div>
<% }) %>
</div>
<div class="network-connections-info-block">
<% _.each(slaveInterfaces, function(slaveInterface) { %>
<div class="network-connections-info-block-item">
<div class="network-connections-info-position"></div>
<div class="network-connections-info-description">
<div>MAC: <%- slaveInterface.get('mac') %></div>
<div><%- i18n('cluster_page.nodes_tab.configure_interfaces.speed') %>: <%- showBandwidth(slaveInterface.get('current_speed')) %></div>
<% if (bondingAvailable && slaveInterfaces.length >= 3) { %>
<button class="btn btn-link btn-remove-interface" type="button" data-interface-id="<%= slaveInterface.id %>">Remove</button>
<% } %>
</div>
</div>
<% }) %>
</div>
<div class="logical-network-box">
<div class="logical-network-group">
<% ifc.get('assigned_networks').each(function(interfaceNetwork) { %>
<% var networkName = interfaceNetwork.get('name') %>
<% var network = interfaceNetwork.getFullNetwork() %>
<% if (networkName != 'floating') { %>
</div><div class="logical-network-group <%= locked || network.get('meta').unmovable ? 'disabled' : '' %>">
<% } %>
<div class="logical-network-item" data-name="<%- networkName %>">
<div class="name"><%- i18n('network.' + networkName, {defaultValue: networkName}) %></div>
<% var vlanRange = network.getVlanRange() %>
<% if (!_.isNull(vlanRange)) { %>
<div class="id">
<%- i18n('cluster_page.nodes_tab.configure_interfaces.vlan_id', {count: _.uniq(vlanRange).length}) + ': ' + _.uniq(vlanRange).join('-') %>
</div>
<% } %>
</div>
<% }) %>
</div>
<div class="network-help-message hide" data-i18n="cluster_page.nodes_tab.configure_interfaces.drag_and_drop_description"></div>
</div>
</div>
</div>
<div class="network-box-error-message common enable-selection">&nbsp;</div>

View File

@ -1,8 +0,0 @@
background: <%= startColor %>;
background: -moz-linear-gradient(top, <%= startColor %> 0%, <%= endColor %> 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,<%= startColor %>), color-stop(100%,<%= endColor %>));
background: -webkit-linear-gradient(top, <%= startColor %> 0%, <%= endColor %> 100%);
background: -o-linear-gradient(top, <%= startColor %> 0%, <%= endColor %> 100%);
background: -ms-linear-gradient(top, <%= startColor %> 0%, <%= endColor %> 100%);
background: linear-gradient(to bottom, <%= startColor %> 0%, <%= endColor %> 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="<%= startColor %>", endColorstr="<%= endColor %>", GradientType=0);

View File

@ -262,6 +262,10 @@
"disks": "Configure Disks",
"interfaces": "Configure Interfaces"
},
"node_loading_error": {
"title": "Error loading nodes",
"load_error": "Some nodes failed to load"
},
"configure_interfaces": {
"title": "Configure interfaces on __name__",
"title_plural": "Configure interfaces on __count__ nodes",
@ -303,10 +307,6 @@
"saving_warning": "Unable to apply changes",
"load_defaults_warning": "Unable to load default configuration"
},
"node_loading_error": {
"title": "Error loading nodes",
"load_error": "Some nodes failed to load"
},
"validation": {
"too_many_untagged_networks": "Untagged networks can not be assigned to the same interface",
"invalid_mtu": "Invalid MTU value"
@ -322,6 +322,8 @@
"total_space": "Total Space",
"unallocated": "Unallocated",
"use_all_allowed_space": "Use All Allowed Space",
"validation_error": "Volume groups total size exceeds available space of __size__ MB",
"minimum_reached": "Minimum allowed size reached",
"configuration_error": {
"title": "Disks Configuration Error",
"saving_warning": "Unable to apply changes",

View File

@ -18,14 +18,13 @@ define(
'jquery',
'underscore',
'react',
'jsx!backbone_view_wrapper',
'jsx!views/cluster_page_tabs/nodes_tab_screens/cluster_nodes_screen',
'jsx!views/cluster_page_tabs/nodes_tab_screens/add_nodes_screen',
'jsx!views/cluster_page_tabs/nodes_tab_screens/edit_nodes_screen',
'views/cluster_page_tabs/nodes_tab_screens/edit_node_disks_screen',
'jsx!views/cluster_page_tabs/nodes_tab_screens/edit_node_disks_screen',
'jsx!views/cluster_page_tabs/nodes_tab_screens/edit_node_interfaces_screen'
],
function($, _, React, BackboneViewWrapper, ClusterNodesScreen, AddNodesScreen, EditNodesScreen, EditNodeDisksScreen, EditNodeInterfacesScreen) {
function($, _, React, ClusterNodesScreen, AddNodesScreen, EditNodesScreen, EditNodeDisksScreen, EditNodeInterfacesScreen) {
'use strict';
var ReactTransitionGroup = React.addons.TransitionGroup;
@ -48,7 +47,7 @@ function($, _, React, BackboneViewWrapper, ClusterNodesScreen, AddNodesScreen, E
list: ClusterNodesScreen,
add: AddNodesScreen,
edit: EditNodesScreen,
disks: BackboneViewWrapper(EditNodeDisksScreen),
disks: EditNodeDisksScreen,
interfaces: EditNodeInterfacesScreen
};
},

View File

@ -1,314 +0,0 @@
/*
* Copyright 2013 Mirantis, 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.
**/
define(
[
'jquery',
'underscore',
'i18n',
'backbone',
'utils',
'models',
'views/cluster_page_tabs/nodes_tab_screens/edit_node_screen',
'text!templates/cluster/edit_node_disks.html',
'text!templates/cluster/node_disk.html',
'text!templates/cluster/volume_style.html',
'jquery-autoNumeric'
],
function($, _, i18n, Backbone, utils, models, EditNodeScreen, editNodeDisksScreenTemplate, nodeDisksTemplate, volumeStylesTemplate) {
'use strict';
var EditNodeDisksScreen, NodeDisk;
EditNodeDisksScreen = EditNodeScreen.extend({
className: 'edit-node-disks-screen',
constructorName: 'EditNodeDisksScreen',
template: _.template(editNodeDisksScreenTemplate),
events: {
'click .btn-defaults': 'loadDefaults',
'click .btn-revert-changes': 'revertChanges',
'click .btn-apply:not(:disabled)': 'applyChanges',
'click .btn-return:not(:disabled)': 'returnToNodeList'
},
hasChanges: function() {
var volumes = _.pluck(this.disks.toJSON(), 'volumes');
return !this.nodes.reduce(function(result, node) {
return result && _.isEqual(volumes, _.pluck(node.disks.toJSON(), 'volumes'));
}, true);
},
hasValidationErrors: function() {
var result = false;
this.disks.each(function(disk) {result = result || disk.validationError || _.some(disk.get('volumes').models, 'validationError');}, this);
return result;
},
isLocked: function() {
var nodesAvailableForChanges = this.nodes.filter(function(node) {
return node.get('pending_addition') || (node.get('status') == 'error' && node.get('error_type') == 'provision');
});
return !nodesAvailableForChanges.length || this.constructor.__super__.isLocked.apply(this);
},
checkForChanges: function() {
this.updateButtonsState(this.isLocked());
this.applyChangesButton.set('disabled', this.isLocked() || !this.hasChanges() || this.hasValidationErrors());
this.cancelChangesButton.set('disabled', this.isLocked() || (!this.hasChanges() && !this.hasValidationErrors()));
},
loadDefaults: function() {
this.disableControls(true);
this.disks.fetch({url: _.result(this.nodes.at(0), 'url') + '/disks/defaults/'})
.fail(_.bind(function(response) {
utils.showErrorDialog({
title: i18n('cluster_page.nodes_tab.configure_disks.configuration_error.title'),
message: i18n('cluster_page.nodes_tab.configure_disks.configuration_error.load_defaults_warning'),
response: response
});
}, this));
},
revertChanges: function() {
this.disks.reset(_.cloneDeep(this.nodes.at(0).disks.toJSON()), {parse: true});
},
applyChanges: function() {
if (this.hasValidationErrors()) {
return (new $.Deferred()).reject();
}
this.disableControls(true);
return $.when.apply($, this.nodes.map(function(node) {
node.disks.each(function(disk, index) {
disk.set({volumes: new models.Volumes(this.disks.at(index).get('volumes').toJSON())});
}, this);
return Backbone.sync('update', node.disks, {url: _.result(node, 'url') + '/disks'});
}, this))
.done(_.bind(function() {
this.cluster.fetch();
this.render();
}, this))
.fail(_.bind(function(response) {
this.checkForChanges();
utils.showErrorDialog({
title: i18n('cluster_page.nodes_tab.configure_disks.configuration_error.title'),
message: utils.getResponseText(response) || i18n('cluster_page.nodes_tab.configure_disks.configuration_error.saving_warning')
});
}, this));
},
mapVolumesColors: function() {
this.volumesColors = {};
var colors = [
['#23a85e', '#1d8a4d'],
['#3582ce', '#2b6ba9'],
['#eea616', '#c38812'],
['#1cbbb4', '#189f99'],
['#9e0b0f', '#870a0d'],
['#8f50ca', '#7a44ac'],
['#1fa0e3', '#1b88c1'],
['#85c329', '#71a623'],
['#7d4900', '#6b3e00']
];
this.volumes.each(function(volume, index) {
this.volumesColors[volume.get('name')] = colors[index];
}, this);
},
initialize: function() {
this.constructor.__super__.initialize.apply(this, arguments);
if (this.nodes.length) {
this.cluster.on('change:status', this.revertChanges, this);
this.volumes = new models.Volumes();
this.volumes.url = _.result(this.nodes.at(0), 'url') + '/volumes';
this.loading = $.when.apply($, this.nodes.map(function(node) {
node.disks = new models.Disks();
return node.disks.fetch({url: _.result(node, 'url') + '/disks'});
}, this).concat(this.volumes.fetch()))
.done(_.bind(function() {
this.disks = new models.Disks(_.cloneDeep(this.nodes.at(0).disks.toJSON()), {parse: true});
this.disks.on('sync', this.render, this);
this.disks.on('reset', this.render, this);
this.disks.on('error', this.checkForChanges, this);
this.mapVolumesColors();
this.render();
}, this))
.fail(_.bind(this.goToNodeList, this));
} else {
this.goToNodeList();
}
this.initButtons();
},
getDiskMetaData: function(disk) {
var result;
var disksMetaData = this.nodes.at(0).get('meta').disks;
// try to find disk metadata by matching "extra" field
// if at least one entry presents both in disk and metadata entry,
// this metadata entry is for our disk
var extra = disk.get('extra') || [];
result = _.find(disksMetaData, function(diskMetaData) {
if (_.isArray(diskMetaData.extra)) {
return _.intersection(diskMetaData.extra, extra).length;
}
return false;
}, this);
// if matching "extra" fields doesn't work, try to search by disk id
if (!result) {
result = _.find(disksMetaData, {disk: disk.id});
}
return result;
},
renderDisks: function() {
this.tearDownRegisteredSubViews();
this.$('.node-disks').html('');
this.disks.each(function(disk) {
var nodeDisk = new NodeDisk({
disk: disk,
diskMetaData: this.getDiskMetaData(disk),
screen: this
});
this.registerSubView(nodeDisk);
this.$('.node-disks').append(nodeDisk.render().el);
}, this);
},
render: function() {
this.$el.html(this.template({
nodes: this.nodes,
locked: this.isLocked()
})).i18n();
if (this.loading && this.loading.state() != 'pending') {
this.renderDisks();
this.checkForChanges();
}
this.setupButtonsBindings();
return this;
}
});
NodeDisk = Backbone.View.extend({
template: _.template(nodeDisksTemplate),
volumeStylesTemplate: _.template(volumeStylesTemplate),
templateHelpers: {
sortEntryProperties: utils.sortEntryProperties,
showDiskSize: utils.showDiskSize
},
events: {
'click .disk-visual': 'toggleDiskFormVisibleAttribute',
'click .close-btn': 'deleteVolume',
'click .volume-group-use-all-allowed-btn button': 'useAllAllowedSpace'
},
toggleDiskFormVisibleAttribute: function() {
this.diskForm.set({visible: !this.diskForm.get('visible')});
},
getVolumeMinimum: function(name) {
return this.screen.volumes.findWhere({name: name}).get('min_size');
},
checkForGroupsDeletionAvailability: function() {
this.disk.get('volumes').each(function(volume) {
var name = volume.get('name'),
showRemoveButton = !this.screen.isLocked() && this.diskForm.get('visible') && volume.getMinimalSize(this.getVolumeMinimum(name)) <= 0;
this.$('.disk-visual [data-volume=' + name + '] .close-btn').toggleClass('hide', !showRemoveButton);
}, this);
},
updateDisk: function() {
this.$('.disk-utility-box .form-group').removeClass('has-error').next('.volume-group-error').text('');
this.$('.volume-group-error.common').text('');
this.disk.get('volumes').each(function(volume) {
volume.set({size: volume.get('size')}, {validate: true, minimum: this.getVolumeMinimum(volume.get('name'))});
}, this); // volumes validation (minimum)
this.disk.set({volumes: this.disk.get('volumes')}, {validate: true}); // disk validation (maximum)
this.renderVisualGraph();
},
updateDisks: function() {
this.updateDisk();
_.invoke(_.omit(this.screen.subViews, this.cid), 'updateDisk', this);
this.screen.checkForChanges();
},
deleteVolume: function(e) {
e.stopPropagation();
var volumeName = this.$(e.currentTarget).parents('.volume-group').data('volume');
var volume = this.disk.get('volumes').findWhere({name: volumeName});
volume.set({size: 0});
},
useAllAllowedSpace: function(e) {
var volumeName = this.$(e.currentTarget).parents('.volume-group').data('volume');
var volume = this.disk.get('volumes').findWhere({name: volumeName});
volume.set({size: _.max([0, this.disk.getUnallocatedSpace({skip: volumeName})])});
},
initialize: function(options) {
_.defaults(this, options);
this.diskForm = new Backbone.Model({visible: false});
this.diskForm.on('change:visible', this.checkForGroupsDeletionAvailability, this);
this.disk.on('invalid', function(cluster, error) {
this.$('.disk-utility-box .form-group').addClass('has-error');
this.$('.volume-group-error.common').text(error);
}, this);
this.disk.get('volumes').each(function(volume) {
volume.on('change:size', this.updateDisks, this);
volume.on('change:size', function() {_.invoke(this.screen.subViews, 'checkForGroupsDeletionAvailability', this);}, this);
volume.on('invalid', function(cluster, error) {
this.$('.form-group[data-volume=' + volume.get('name') + ']').addClass('has-error').next('.volume-group-error').text(error);
}, this);
}, this);
},
renderVolume: function(name, width, size) {
this.$('.disk-visual [data-volume=' + name + ']')
.css('width', width + '%')
.find('.volume-group-size').text(utils.showDiskSize(size, 2));
},
renderVisualGraph: function() {
if (!this.disk.get('volumes').some('validationError') && this.disk.isValid()) {
var unallocatedWidth = 100;
this.disk.get('volumes').each(function(volume) {
var width = this.disk.get('size') ? utils.floor(volume.get('size') / this.disk.get('size') * 100, 2) : 0;
unallocatedWidth -= width;
this.renderVolume(volume.get('name'), width, volume.get('size'));
}, this);
this.renderVolume('unallocated', unallocatedWidth, this.disk.getUnallocatedSpace());
}
},
applyColors: function() {
this.disk.get('volumes').each(function(volume) {
var name = volume.get('name');
var colors = this.screen.volumesColors[name];
this.$('.disk-visual [data-volume=' + name + '], .volume-group-flag.' + name)
.attr('style', this.volumeStylesTemplate({startColor: _.first(colors), endColor: _.last(colors)}));
}, this);
},
setupVolumesBindings: function() {
this.disk.get('volumes').each(function(volume) {
var bindings = {};
bindings['input[name=' + volume.get('name') + ']'] = {
events: ['keyup'],
observe: 'size',
getVal: function($el) {
return Number($el.autoNumeric('get'));
},
update: function($el, value) {
$el.autoNumeric('set', value);
}
};
this.stickit(volume, bindings);
}, this);
},
render: function() {
this.$el.html(this.template(_.extend({
diskMetaData: this.diskMetaData,
disk: this.disk,
volumes: this.screen.volumes,
locked: this.screen.isLocked()
}, this.templateHelpers))).i18n();
this.applyColors();
this.renderVisualGraph();
this.$('input').autoNumeric('init', {mDec: 0});
this.setupVolumesBindings();
return this;
}
});
return EditNodeDisksScreen;
});

View File

@ -0,0 +1,331 @@
/*
* Copyright 2015 Mirantis, 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.
**/
define(
[
'jquery',
'underscore',
'i18n',
'backbone',
'react',
'utils',
'models',
'jsx!component_mixins',
'jsx!views/controls'
],
function($, _, i18n, Backbone, React, utils, models, ComponentMixins, controls) {
'use strict';
var EditNodeDisksScreen = React.createClass({
mixins: [
ComponentMixins.nodeConfigurationScreenMixin,
ComponentMixins.backboneMixin('cluster', 'change:status change:nodes sync'),
ComponentMixins.backboneMixin('nodes', 'change sync'),
ComponentMixins.backboneMixin('disks', 'reset change')
],
statics: {
fetchData: function(options) {
var cluster = options.cluster,
nodeIds = utils.deserializeTabOptions(options.screenOptions[0]).nodes.split(',').map(function(id) {return parseInt(id, 10);}),
nodes = new models.Nodes(cluster.get('nodes').getByIds(nodeIds));
if (nodes.length != nodeIds.length) {
utils.showErrorDialog({
title: i18n('cluster_page.nodes_tab.node_loading_error.title'),
message: i18n('cluster_page.nodes_tab.node_loading_error.load_error')
});
ComponentMixins.nodeConfigurationScreenMixin.goToNodeList(cluster);
return;
}
var volumes = new models.Volumes();
volumes.url = _.result(nodes.at(0), 'url') + '/volumes';
return $.when.apply($, nodes.map(function(node) {
node.disks = new models.Disks();
return node.disks.fetch({url: _.result(node, 'url') + '/disks'});
}, this).concat(volumes.fetch()))
.then(function() {
var disks = new models.Disks(_.cloneDeep(nodes.at(0).disks.toJSON()), {parse: true});
return {
disks: disks,
nodes: nodes,
volumes: volumes
};
});
}
},
getInitialState: function() {
return {actionInProgress: false};
},
hasChanges: function() {
var volumes = _.pluck(this.props.disks.toJSON(), 'volumes');
return this.props.nodes.any(function(node) {
return !_.isEqual(volumes, _.pluck(node.disks.toJSON(), 'volumes'));
});
},
loadDefaults: function() {
this.setState({actionInProgress: true});
this.props.disks.fetch({url: _.result(this.props.nodes.at(0), 'url') + '/disks/defaults/'})
.fail(_.bind(function(response) {
var ns = 'cluster_page.nodes_tab.configure_disks.configuration_error.';
utils.showErrorDialog({
title: i18n(ns + 'title'),
message: utils.getResponseText(response) || i18n(ns + 'load_defaults_warning')
});
}, this))
.always(_.bind(function() {
this.setState({actionInProgress: false});
}, this));
},
revertChanges: function() {
this.props.disks.reset(_.cloneDeep(this.props.nodes.at(0).disks.toJSON()), {parse: true});
},
applyChanges: function() {
this.setState({actionInProgress: true});
return $.when.apply($, this.props.nodes.map(function(node) {
node.disks.each(function(disk, index) {
disk.set({volumes: new models.Volumes(this.props.disks.at(index).get('volumes').toJSON())});
}, this);
return Backbone.sync('update', node.disks, {url: _.result(node, 'url') + '/disks'});
}, this))
.fail(_.bind(function(response) {
var ns = 'cluster_page.nodes_tab.configure_disks.configuration_error.';
utils.showErrorDialog({
title: i18n(ns + 'title'),
message: utils.getResponseText(response) || i18n(ns + 'saving_warning')
});
}, this))
.always(_.bind(function() {
this.setState({actionInProgress: false});
}, this));
},
getDiskMetaData: function(disk) {
var result,
disksMetaData = this.props.nodes.at(0).get('meta').disks;
// try to find disk metadata by matching "extra" field
// if at least one entry presents both in disk and metadata entry,
// this metadata entry is for our disk
var extra = disk.get('extra') || [];
result = _.find(disksMetaData, function(diskMetaData) {
return _.isArray(diskMetaData.extra) && _.intersection(diskMetaData.extra, extra).length;
}, this);
// if matching "extra" fields doesn't work, try to search by disk id
if (!result) {
result = _.find(disksMetaData, {disk: disk.id});
}
return result;
},
getVolumesInfo: function(disk) {
var volumes = {},
unallocatedWidth = 100;
disk.get('volumes').each(function(volume) {
var size = volume.get('size') || 0,
width = this.getVolumeWidth(disk, size),
name = volume.get('name');
unallocatedWidth -= width;
volumes[name] = {
size: size,
width: width,
max: volume.getMaxSize(),
min: volume.getMinimalSize(this.props.volumes.findWhere({name: name}).get('min_size'))
};
}, this);
volumes.unallocated = {
size: disk.getUnallocatedSpace(),
width: unallocatedWidth
};
return volumes;
},
getVolumeWidth: function(disk, size) {
return disk.get('size') ? utils.floor(size / disk.get('size') * 100, 2) : 0;
},
render: function() {
var hasChanges = this.hasChanges(),
loadDefaultsDisabled = !!this.state.actionInProgress || this.isLockedScreen(),
revertChangesDisabled = !!this.state.actionInProgress || !hasChanges,
applyDisabled = !!this.state.actionInProgress || !hasChanges;
return (
<div className='edit-node-disks-screen'>
<div className='row'>
<div className='title'>
{i18n('cluster_page.nodes_tab.configure_disks.title', {count: this.props.nodes.length, name: this.props.nodes.length && this.props.nodes.at(0).get('name')})}
</div>
<div className='col-xs-12 node-disks'>
{this.props.disks.map(function(disk, index) {
return (<NodeDisk
disk={disk}
key={index}
disabled={this.state.actionInProgress}
volumes={this.props.volumes}
volumesInfo={this.getVolumesInfo(disk)}
diskMetaData={this.getDiskMetaData(disk)}
/>);
}, this)}
</div>
<div className='col-xs-12 page-buttons'>
<div className='well clearfix'>
<div className='btn-group'>
<button onClick={this.returnToNodeList} className='btn btn-default btn-return'>{i18n('cluster_page.nodes_tab.back_to_nodes_button')}</button>
</div>
<div className='btn-group pull-right'>
<button className='btn btn-default btn-defaults' onClick={this.loadDefaults} disabled={loadDefaultsDisabled}>{i18n('common.load_defaults_button')}</button>
<button className='btn btn-default btn-revert-changes' onClick={this.revertChanges} disabled={revertChangesDisabled}>{i18n('common.cancel_changes_button')}</button>
<button className='btn btn-success btn-apply' onClick={this.applyChanges} disabled={applyDisabled}>{i18n('common.apply_button')}</button>
</div>
</div>
</div>
</div>
</div>
);
}
});
var NodeDisk = React.createClass({
getInitialState: function() {
return {key: _.now()};
},
componentDidMount: function() {
$('.disk-details', this.getDOMNode())
.on('show.bs.collapse', this.setState.bind(this, {collapsed: true}, null))
.on('hide.bs.collapse', this.setState.bind(this, {collapsed: false}, null));
},
updateDisk: function(name, value, force) {
var size = parseInt(value) || 0,
volumeInfo = this.props.volumesInfo[name];
if (size > volumeInfo.max) {
size = volumeInfo.max;
} else if (size < volumeInfo.min) {
size = volumeInfo.min;
}
this.props.disk.get('volumes').findWhere({name: name}).set({size: size});
this.props.disk.trigger('change', this.props.disk);
if (force) {
this.setState({key: _.now()});
}
},
render: function() {
var disk = this.props.disk,
volumesInfo = this.props.volumesInfo,
diskMetaData = this.props.diskMetaData,
sortOrder = ['name', 'model', 'size'],
ns = 'cluster_page.nodes_tab.configure_disks.';
return (
<div className='col-xs-12 disk-box' data-disk={disk.id} key={this.props.key}>
<div className='row'>
<h4 className='col-xs-6'>
{disk.get('name')} ({disk.id})
</h4>
<h4 className='col-xs-6 text-right'>
{i18n(ns + 'total_space')} : {utils.showDiskSize(disk.get('size'), 2)}
</h4>
</div>
<div className='row disk-visual clearfix'>
{this.props.volumes.map(function(volume, index) {
var volumeName = volume.get('name');
return (
<div className={'volume-group pull-left volume-type-' + (index + 1)} data-volume={volumeName} key={'volume_' + volumeName} style={{width: volumesInfo[volumeName].width + '%'}}>
<div className='text-center toggle' data-toggle='collapse' data-target={'#' + disk.get('name')}>
<div>{volume.get('label')}</div>
<div className='volume-group-size'>{utils.showDiskSize(volumesInfo[volumeName].size, 2)}</div>
</div>
{volumesInfo[volumeName].min <= 0 && this.state.collapsed &&
<div className='close-btn' onClick={_.partial(this.updateDisk, volumeName, 0, true)}>&times;</div>
}
</div>
);
}, this)}
<div className={'volume-group pull-left'} data-volume='unallocated' style={{width: volumesInfo.unallocated.width + '%'}}>
<div className='text-center toggle' data-toggle='collapse' data-target={'#' + disk.get('name')}>
<div className='volume-group-name'>{i18n(ns + 'unallocated')}</div>
<div className='volume-group-size'>{utils.showDiskSize(volumesInfo.unallocated.size, 2)}</div>
</div>
</div>
</div>
<div className='row collapse disk-details' id={disk.get('name')} key='diskDetails' ref={disk.get('name')}>
<div className='col-xs-5'>
{diskMetaData &&
<div>
<h5>{i18n(ns + 'disk_information')}</h5>
<div className='form-horizontal disk-info-box'>
{_.map(utils.sortEntryProperties(diskMetaData, sortOrder), function(propertyName) {
return (
<div className='form-group' key={'property_' + propertyName}>
<label className='col-xs-2'>{propertyName.replace(/_/g, ' ')}</label>
<div className='col-xs-10'>
<p className='form-control-static'>
{propertyName == 'size' ? utils.showDiskSize(diskMetaData[propertyName]) : diskMetaData[propertyName]}
</p>
</div>
</div>
);
})}
</div>
</div>
}
</div>
<div className='col-xs-7'>
<h5>{i18n(ns + 'volume_groups')}</h5>
<div className='form-horizontal disk-utility-box'>
{this.props.volumes.map(function(volume, index) {
var volumeName = volume.get('name'),
value = volumesInfo[volumeName].size,
currentMaxSize = volumesInfo[volumeName].max,
currentMinSize = _.max([volumesInfo[volumeName].min, 0]),
disabled = this.props.disabled || currentMaxSize == currentMinSize;
var props = {
name: volumeName,
min: currentMinSize,
max: currentMaxSize,
disabled: disabled
};
return (
<div key={'edit_' + volumeName}>
<div className='form-group volume-group row' data-volume={volumeName}>
<label className='col-xs-4 volume-group-label'>
<span ref={'volume-group-flag ' + volumeName} className={'volume-type-' + (index + 1)}> &nbsp; </span>
{volume.get('label')}
</label>
<div className='col-xs-4 volume-group-range'>
<controls.Input {...props}
key={currentMinSize + currentMaxSize + this.state.key}
type='range'
onInput={this.updateDisk}
defaultValue={value}
/>
</div>
<controls.Input {...props}
type='number'
wrapperClassName='col-xs-3 volume-group-input'
onChange={_.partialRight(this.updateDisk, true)}
value={value}
/>
<div className='col-xs-1 volume-group-size-label'>{i18n('common.size.mb')}</div>
</div>
{!!value && value == currentMinSize &&
<div className='volume-group-notice text-right'>{i18n(ns + 'minimum_reached')}</div>
}
</div>
);
}, this)}
</div>
</div>
</div>
</div>
);
}
});
return EditNodeDisksScreen;
});

View File

@ -23,39 +23,18 @@ define(
'utils',
'models',
'dispatcher',
'jsx!views/dialogs',
'jsx!views/controls',
'jsx!component_mixins',
'jquery-ui/sortable'
],
function($, _, Backbone, React, i18n, utils, models, dispatcher, dialogs, controls, ComponentMixins) {
function($, _, Backbone, React, i18n, utils, models, dispatcher, controls, ComponentMixins) {
'use strict';
var ScreenMixin, EditNodeInterfacesScreen, NodeInterface;
ScreenMixin = {
goToNodeList: function() {
app.navigate('#cluster/' + this.props.cluster.get('id') + '/nodes', {trigger: true});
},
isLockedScreen: function() {
var nodesAvailableForChanges = this.props.nodes.filter(function(node) {
return node.get('pending_addition') || node.get('status') == 'error';
});
return !nodesAvailableForChanges.length ||
this.props.cluster && !!this.props.cluster.tasks({group: 'deployment', status: 'running'}).length;
},
returnToNodeList: function() {
if (this.hasChanges()) {
dialogs.DiscardSettingsChangesDialog.show({cb: _.bind(this.goToNodeList, this)});
} else {
this.goToNodeList();
}
}
};
var EditNodeInterfacesScreen, NodeInterface;
EditNodeInterfacesScreen = React.createClass({
mixins: [
ScreenMixin,
ComponentMixins.nodeConfigurationScreenMixin,
ComponentMixins.backboneMixin('interfaces', 'change:checked change:slaves change:bond_properties change:interface_properties reset sync'),
ComponentMixins.backboneMixin('cluster', 'change:status change:networkConfiguration change:nodes sync'),
ComponentMixins.backboneMixin('nodes', 'change sync')
@ -64,7 +43,7 @@ function($, _, Backbone, React, i18n, utils, models, dispatcher, dialogs, contro
fetchData: function(options) {
var cluster = options.cluster,
nodeIds = utils.deserializeTabOptions(options.screenOptions[0]).nodes.split(',').map(function(id) {return parseInt(id, 10);}),
nodeLoadingErrorNS = 'cluster_page.nodes_tab.configure_interfaces.node_loading_error.',
nodeLoadingErrorNS = 'cluster_page.nodes_tab.node_loading_error.',
nodes,
networkConfiguration,
networksMetadata = new models.ReleaseNetworkProperties();
@ -76,7 +55,7 @@ function($, _, Backbone, React, i18n, utils, models, dispatcher, dialogs, contro
title: i18n(nodeLoadingErrorNS + 'title'),
message: i18n(nodeLoadingErrorNS + 'load_error')
});
ScreenMixin.goToNodeList();
ComponentMixins.nodeConfigurationScreenMixin.goToNodeList(cluster);
return;
}

View File

@ -1,49 +0,0 @@
/*
* Copyright 2013 Mirantis, 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.
**/
define(
[
'underscore',
'utils',
'models',
'jsx!views/dialogs',
'views/cluster_page_tabs/nodes_tab_screens/screen'
],
function(_, utils, models, dialogs, Screen) {
'use strict';
var EditNodeScreen;
EditNodeScreen = Screen.extend({
constructorName: 'EditNodeScreen',
keepScrollPosition: true,
disableControls: function(disable) {
this.updateButtonsState(disable || this.isLocked());
},
returnToNodeList: function() {
if (this.hasChanges()) {
dialogs.DiscardSettingsChangesDialog.show({cb: _.bind(this.goToNodeList, this)});
} else {
this.goToNodeList();
}
},
initialize: function(options) {
_.defaults(this, options);
var nodeIds = utils.deserializeTabOptions(this.screenOptions[0]).nodes.split(',').map(function(id) {return parseInt(id, 10);});
this.nodes = new models.Nodes(this.cluster.get('nodes').getByIds(nodeIds));
}
});
return EditNodeScreen;
});

View File

@ -1,49 +0,0 @@
/*
* Copyright 2013 Mirantis, 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.
**/
define(['backbone'],
function(Backbone) {
'use strict';
var Screen;
Screen = Backbone.View.extend({
constructorName: 'Screen',
keepScrollPosition: false,
goToNodeList: function() {
app.navigate('#cluster/' + this.cluster.id + '/nodes', {trigger: true});
},
isLocked: function() {
return !!this.cluster.tasks({group: 'deployment', status: 'running'}).length;
},
initButtons: function() {
this.loadDefaultsButton = new Backbone.Model({disabled: false});
this.cancelChangesButton = new Backbone.Model({disabled: true});
this.applyChangesButton = new Backbone.Model({disabled: true});
},
setupButtonsBindings: function() {
var bindings = {attributes: [{name: 'disabled', observe: 'disabled'}]};
this.stickit(this.loadDefaultsButton, {'.btn-defaults': bindings});
this.stickit(this.cancelChangesButton, {'.btn-revert-changes': bindings});
this.stickit(this.applyChangesButton, {'.btn-apply': bindings});
},
updateButtonsState: function(state) {
this.applyChangesButton.set('disabled', state);
this.cancelChangesButton.set('disabled', state);
this.loadDefaultsButton.set('disabled', state);
}
});
return Screen;
});

View File

@ -52,7 +52,7 @@ define(['jquery', 'underscore', 'react', 'utils', 'jsx!component_mixins'], funct
controls.Input = React.createClass({
mixins: [tooltipMixin],
propTypes: {
type: React.PropTypes.oneOf(['text', 'password', 'textarea', 'checkbox', 'radio', 'select', 'hidden', 'number']).isRequired,
type: React.PropTypes.oneOf(['text', 'password', 'textarea', 'checkbox', 'radio', 'select', 'hidden', 'number', 'range']).isRequired,
name: React.PropTypes.node,
label: React.PropTypes.node,
description: React.PropTypes.node,
@ -62,6 +62,7 @@ define(['jquery', 'underscore', 'react', 'utils', 'jsx!component_mixins'], funct
tooltipText: React.PropTypes.node,
toggleable: React.PropTypes.bool,
onChange: React.PropTypes.func,
onInput: React.PropTypes.func,
extraContent: React.PropTypes.node
},
getInitialState: function() {
@ -79,24 +80,37 @@ define(['jquery', 'underscore', 'react', 'utils', 'jsx!component_mixins'], funct
debouncedChange: _.debounce(function() {
return this.onChange();
}, 200, {leading: true}),
debouncedInput: _.debounce(function() {
return this.onInput();
}, 10, {leading: true}),
onChange: function() {
if (this.props.onChange) {
var input = this.getInputDOMNode();
return this.props.onChange(this.props.name, this.props.type == 'checkbox' ? input.checked : input.value);
}
},
onInput: function() {
if (this.props.onInput) {
var input = this.getInputDOMNode();
return this.props.onInput(this.props.name, input.value);
}
},
renderInput: function() {
var classes = {'form-control': true};
var classes = {'form-control': this.props.type != 'range'};
classes[this.props.inputClassName] = this.props.inputClassName;
var props = {
ref: 'input',
key: 'input',
type: (this.props.toggleable && this.state.visible) ? 'text' : this.props.type,
className: utils.classNames(classes),
// debounced onChange callback is supported for uncontrolled inputs
onChange: this.props.value ? this.onChange : this.debouncedChange
},
Tag = _.contains(['select', 'textarea'], this.props.type) ? this.props.type : 'input',
className: utils.classNames(classes)
};
if (this.props.type == 'range') {
props.onInput = this.debouncedInput;
} else {
// debounced onChange callback is supported for uncontrolled inputs
props.onChange = this.props.value ? this.onChange : this.debouncedChange;
}
var Tag = _.contains(['select', 'textarea'], this.props.type) ? this.props.type : 'input',
input = <Tag {...this.props} {...props}>{this.props.children}</Tag>,
isCheckboxOrRadio = this.isCheckboxOrRadio(),
inputWrapperClasses = {

View File

@ -57,13 +57,13 @@ casper.then(function() {
this.then(function() {
this.test.comment('Testing nodes disk block');
this.click(sdaDisk + ' .disk-visual');
this.click(sdaDisk + ' .disk-visual [data-volume=os] .toggle');
vmSDA = this.getElementAttribute(sdaDiskVM + ' input', 'value');
osSDA = this.getElementAttribute(sdaDiskOS + ' input', 'value');
this.test.assertExists(sdaDiskOS, 'Base system group form is presented');
this.test.assertExists(sdaDiskVM, 'Virtual Storage group form is presented');
this.test.assertExists(sdaDisk + ' .disk-visual [data-volume=os] .close-btn.hide', 'Button Close for Base system group is not presented');
this.test.assertDoesntExist(sdaDisk + ' .disk-visual [data-volume=vm] .close-btn:visible', 'Button Close for Virtual Storage group is presented');
this.test.assertDoesntExist(sdaDisk + ' .disk-visual [data-volume=os] .close-btn', 'Button Close for Base system group is not presented');
this.test.assertExists(sdaDisk + ' .disk-visual [data-volume=vm] .close-btn', 'Button Close for Virtual Storage group is presented');
});
this.then(function() {
@ -73,10 +73,6 @@ casper.then(function() {
this.test.assertExists('.btn-defaults:not(:disabled)', 'Load Defaults button is enabled');
this.test.assertExists('.btn-revert-changes:not(:disabled)', 'Cancel button is enabled');
this.test.assertExists('.btn-apply:not(:disabled)', 'Apply button is enabled');
this.click(sdaDiskVM + ' .volume-group-use-all-allowed-btn button');
this.test.assertExists('.btn-defaults:not(:disabled)', 'Load Defaults button is enabled');
this.test.assertExists('.btn-revert-changes:disabled', 'Cancel button is disabled');
this.test.assertExists('.btn-apply:disabled', 'Apply button is disabled');
});
this.then(function() {
@ -85,8 +81,8 @@ casper.then(function() {
this.click('.btn-defaults');
this.test.assertSelectorAppears('.btn-defaults:not(:disabled)', 'Defaults were loaded');
this.then(function() {
this.test.assertEvalEquals(function(sdaDiskVM) {return $(sdaDiskVM + ' input').attr('value')}, vmSDA, 'Volume group input control VM contains default value', {sdaDiskVM:sdaDiskVM});
this.test.assertEvalEquals(function(sdaDiskOS) {return $(sdaDiskOS + ' input').attr('value')}, osSDA, 'Volume group input control OS contains default value', {sdaDiskOS:sdaDiskOS});
this.test.assertEvalEquals(function(sdaDiskVM) {return $(sdaDiskVM + ' input[type=number]').attr('value')}, vmSDA, 'Volume group input control VM contains default value', {sdaDiskVM:sdaDiskVM});
this.test.assertEvalEquals(function(sdaDiskOS) {return $(sdaDiskOS + ' input[type=number]').attr('value')}, osSDA, 'Volume group input control OS contains default value', {sdaDiskOS:sdaDiskOS});
});
});
@ -95,42 +91,10 @@ casper.then(function() {
this.click(sdaDisk + ' .disk-visual [data-volume=vm] .close-btn');
this.test.assertEquals(this.getElementBounds(sdaDisk + ' .disk-visual [data-volume=vm]').width, 0, 'VM group was removed successfully');
this.click('.btn-revert-changes');
this.test.assertEvalEquals(function(sdaDiskVM) {return $(sdaDiskVM + ' input').attr('value')}, vmSDA, 'Volume group input control VM contains default value', {sdaDiskVM:sdaDiskVM});
this.test.assertEvalEquals(function(sdaDiskVM) {return $(sdaDiskVM + ' input[type=number]').attr('value')}, vmSDA, 'Volume group input control VM contains default value', {sdaDiskVM:sdaDiskVM});
this.click(sdaDisk + ' .disk-visual [data-volume=vm] .close-btn');
this.test.assertEval(function(sdaDisk) {return $(sdaDisk + ' .disk-visual [data-volume=unallocated]').width() > 0}, 'There is unallocated space after Virtual Storage VG removal',{sdaDisk:sdaDisk});
this.test.assertEvalEquals(function(sdaDiskVM) {return $(sdaDiskVM + ' input').val()}, '0', 'Volume group input control contains correct value',{sdaDiskVM:sdaDiskVM});
this.click(sdaDiskVM + ' .volume-group-use-all-allowed-btn button');
this.test.assertEquals(this.getElementBounds(sdaDisk + ' .disk-visual [data-volume=unallocated]').width, 0, 'Use all unallocated area for VM');
this.fill(sdaDiskVM, {'vm': '0'});
this.evaluate(function(sdaDiskVM) {$(sdaDiskVM + ' input').keyup();},{sdaDiskVM: sdaDiskVM});
this.test.assertEquals(this.getElementBounds(sdaDisk + ' .disk-visual [data-volume=vm]').width, 0, 'VM group was removed successfully');
this.test.assertEval(function(sdaDisk) {return $(sdaDisk + ' .disk-visual [data-volume=unallocated]').width() > 0}, 'There is unallocated space after Virtual Storage VG removal', {sdaDisk:sdaDisk});
this.test.assertEvalEquals(function(sdaDiskVM) {return $(sdaDiskVM + ' input').val()},'0', 'Volume group input control contains correct value',{sdaDiskVM:sdaDiskVM});
});
this.then(function() {
this.test.comment('Testing use all allowed link');
this.click(sdaDiskOS + ' .volume-group-use-all-allowed-btn button');
this.test.assertEquals(this.getElementBounds(sdaDisk + ' .disk-visual [data-volume=unallocated]').width, 0, 'Use all allowed link works correctly');
});
this.then(function() {
this.test.comment('Testing validation of VG size');
this.fill(sdaDiskOS, {'os': '0'});
this.evaluate(function(sdaDiskOS) {$(sdaDiskOS + ' input').keyup();}, {sdaDiskOS: sdaDiskOS});
this.test.assertExists(sdaDiskOS + '.has-error', 'Field validation has worked');
this.test.assertEval(function(sdaDisk) {return $(sdaDisk + ' .disk-visual [data-volume=os]').width() > 0}, 'VG size was not changed',{sdaDisk:sdaDisk});
this.click(vdaDisk + ' .disk-visual');
this.test.assertExists(vdaDiskVM, 'Virtual Storage group form is presented');
this.fill(vdaDiskVM, {'vm': '10000'});
this.evaluate(function(vdaDiskVM) {$(vdaDiskVM + ' input').keyup();}, {vdaDiskVM: vdaDiskVM});
this.fill(vdaDiskOS, {'os': '50000'});
this.evaluate(function(vdaDiskOS) {$(vdaDiskOS + ' input').keyup();}, {vdaDiskOS: vdaDiskOS});
this.test.assertDoesntExist(vdaDiskOS + '.has-error', 'Field validation has worked');
this.fill(vdaDiskVM, {'vm': '200000'});
this.evaluate(function(vdaDiskVM) {$(vdaDiskVM + ' input').keyup();}, {vdaDiskVM: vdaDiskVM});
this.test.assertExists(vdaDiskOS + '.has-error', 'Field validation has worked in case of number that bigger than available space on disk');
this.test.assertEvalEquals(function(sdaDiskVM) {return $(sdaDiskVM + ' input[type=number]').val()}, '0', 'Volume group input control contains correct value',{sdaDiskVM:sdaDiskVM});
});
});