Merge "Sort and filter environment nodes by node network group"

This commit is contained in:
Jenkins 2015-11-03 11:36:05 +00:00 committed by Gerrit Code Review
commit 2e3f38de81
11 changed files with 160 additions and 90 deletions

View File

@ -64,7 +64,7 @@ class NodeGroupCollectionHandler(CollectionHandler):
if user_data.cluster_id is not None:
return self.collection.to_json(
query=self.collection.get_by_cluster_id(
self.collection.get_by_cluster_id(
user_data.cluster_id
)
)

View File

@ -148,7 +148,8 @@ NODE_LIST_FILTERS = Enum(
'hdd',
'disks_amount',
'ram',
'interfaces'
'interfaces',
'group_id'
)
NETWORK_INTERFACE_TYPES = Enum(

View File

@ -162,39 +162,47 @@ function($, _, i18n, Backbone, React, utils, layoutComponents, Coccyx, models, K
this.user = new models.User();
this.statistics = new models.NodesStatistics();
this.notifications = new models.Notifications();
this.nodeNetworkGroups = new models.NodeNetworkGroups();
this.fetchData();
}
_.extend(App.prototype, {
fetchData: function() {
this.version.fetch().then(_.bind(function() {
this.user.set({authenticated: !this.version.get('auth_required')});
this.patchBackboneSync();
if (this.version.get('auth_required')) {
_.extend(this.keystoneClient, this.user.pick('token'));
return this.keystoneClient.authenticate()
.done(_.bind(function() {
this.user.set({authenticated: true});
}, this));
}
return $.Deferred().resolve();
}, this)).then(_.bind(function() {
return this.settings.fetch();
}, this)).then(null, _.bind(function() {
if (this.version.get('auth_required') && !this.user.get('authenticated')) {
this.version.fetch()
.then(_.bind(function() {
this.user.set({authenticated: !this.version.get('auth_required')});
this.patchBackboneSync();
if (this.version.get('auth_required')) {
_.extend(this.keystoneClient, this.user.pick('token'));
return this.keystoneClient.authenticate()
.done(_.bind(function() {
this.user.set({authenticated: true});
}, this));
}
return $.Deferred().resolve();
} else {
utils.showErrorDialog({
message: i18n('common.loading_error'),
keyboard: false,
backdrop: false
});
this.mountNode.remove();
}
}, this)).done(function() {
Backbone.history.start();
});
}, this))
.then(_.bind(function() {
return $.when(
this.settings.fetch(),
this.nodeNetworkGroups.fetch()
);
}, this))
.then(null, _.bind(function() {
if (this.version.get('auth_required') && !this.user.get('authenticated')) {
return $.Deferred().resolve();
} else {
utils.showErrorDialog({
message: i18n('common.loading_error'),
keyboard: false,
backdrop: false
});
this.mountNode.remove();
}
}, this))
.done(function() {
Backbone.history.start();
});
},
renderLayout: function() {
var wrappedRootComponent = utils.universalMount(RootComponent, _.pick(this, 'version', 'user', 'statistics', 'notifications'), this.mountNode);

View File

@ -1141,5 +1141,18 @@ define([
nailgunUrl: 'api/tracking/restore_password'
});
models.NodeNetworkGroup = BaseModel.extend({
constructorName: 'NodeNetworkGroup',
urlRoot: '/api/nodegroups'
});
models.NodeNetworkGroups = BaseCollection.extend(cacheMixin).extend({
constructorName: 'NodeNetworkGroups',
cacheFor: 60 * 1000,
model: models.NodeNetworkGroup,
url: '/api/nodegroups',
comparator: 'id'
});
return models;
});

View File

@ -126,7 +126,7 @@ define([
return self.remote.assertElementTextEquals('.node-list .node-name .name p', firstNodeName, 'Order of sorting by roles was changed to asc (default)');
})
.clickByCssSelector(moreControlSelector + ' button')
.assertElementsExist(moreControlSelector + ' .popover .checkbox-group', 11, 'Standard node sorters are presented')
.assertElementsExist(moreControlSelector + ' .popover .checkbox-group', 12, 'Standard node sorters are presented')
// add sorting by CPU (real)
.clickByCssSelector(moreControlSelector + ' .popover [name=cores]')
// add sorting by manufacturer
@ -155,7 +155,7 @@ define([
.assertElementNotExists('.filters .filter-control .btn-remove-filter', 'Default filters can not be deleted from filters panel')
.assertElementNotExists('.filters .btn-reset-filters', 'No filters to be reset')
.clickByCssSelector(moreControlSelector + ' button')
.assertElementsExist(moreControlSelector + ' .popover .checkbox-group', 7, 'Standard node filters are presented')
.assertElementsExist(moreControlSelector + ' .popover .checkbox-group', 8, 'Standard node filters are presented')
.clickByCssSelector(moreControlSelector + ' [name=cores]')
.assertElementsExist('.filters .filter-control', 3, 'New Cores (real) filter was added')
.assertElementExists('.filter-by-cores .popover-content', 'New filter is open')

View File

@ -318,7 +318,8 @@
"hdd": "HDD total size",
"disks": "Number and size of disks",
"ram": "RAM total size",
"interfaces": "Number of interfaces"
"interfaces": "Number of interfaces",
"group_id": "Node network group"
},
"filters": {
"roles": "Roles",
@ -330,6 +331,7 @@
"disks_amount": "Number of disks",
"ram": "RAM total size (GB)",
"interfaces": "Number of interfaces",
"group_id": "Node network group",
"prefixes": {
"cores": "CPU (real)",
"ht_cores": "CPU (total)",
@ -492,7 +494,8 @@
"discard_deletion": "Discard Deletion",
"view_logs": "View Logs",
"edit_name": "Edit Name",
"more_info": "More Info"
"more_info": "More Info",
"node_network_group": "Node network group \"__group__\""
},
"back_to_nodes_button": "Back To Node List",
"unallocated_nodes": "Unallocated Nodes",
@ -899,7 +902,8 @@
"total": "total",
"bytes": "bytes",
"drive": "drive",
"drive_plural": "drives"
"drive_plural": "drives",
"node_network_group": "Node network group"
},
"delete_nodes": {
"title": "Delete Nodes",

View File

@ -109,7 +109,14 @@ function($, _, i18n, Backbone, React, utils, models, dispatcher, componentMixins
cluster.get('nodes').fetch = function(options) {
return this.constructor.__super__.fetch.call(this, _.extend({data: {cluster_id: id}}, options));
};
promise = $.when(cluster.fetch(), cluster.get('settings').fetch(), cluster.get('roles').fetch(), cluster.fetchRelated('nodes'), cluster.fetchRelated('tasks'))
promise = $.when(
cluster.fetch(),
cluster.get('settings').fetch(),
cluster.get('roles').fetch(),
cluster.fetchRelated('nodes'),
cluster.fetchRelated('tasks'),
app.nodeNetworkGroups.fetch({cache: true})
)
.then(function() {
var networkConfiguration = new models.NetworkConfiguration();
networkConfiguration.url = _.result(cluster, 'url') + '/network_configuration/' + cluster.get('net_provider');

View File

@ -40,7 +40,8 @@ function(_, React, NodeListScreen) {
'hdd',
'disks',
'ram',
'interfaces'
'interfaces',
'group_id'
]}
defaultSorting={[{roles: 'asc'}]}
filters={[
@ -52,7 +53,8 @@ function(_, React, NodeListScreen) {
'hdd',
'disks_amount',
'ram',
'interfaces'
'interfaces',
'group_id'
]}
statusesToFilter={[
'ready',

View File

@ -58,7 +58,7 @@ function($, _, i18n, Backbone, React, utils, models, dispatcher, controls, dialo
this.values = values;
this.title = isLabel ? this.name : i18n('cluster_page.nodes_tab.filters.' + this.name, {defaultValue: this.name});
this.isLabel = isLabel;
this.isNumberRange = !isLabel && !_.contains(['roles', 'status', 'manufacturer'], this.name)
this.isNumberRange = !isLabel && !_.contains(['roles', 'status', 'manufacturer', 'group_id'], this.name);
return this;
}
_.extend(Filter, {
@ -325,7 +325,23 @@ function($, _, i18n, Backbone, React, utils, models, dispatcher, controls, dialo
case 'roles':
options = this.props.cluster.get('roles').invoke('pick', 'name', 'label');
break;
case 'group_id':
options = _.uniq(this.props.nodes.pluck('group_id')).map(function(groupId) {
return {
name: groupId,
label: app.nodeNetworkGroups.get(groupId).get('name')
};
});
break;
}
// sort option list
options.sort(_.bind(function(option1, option2) {
// sort Node Network Group filter options by node network group id
if (this.props.name == 'group_id') return option1.name - option2.name;
return utils.natsort(option1.label, option2.label, {insensitive: true});
}, this));
return options;
},
addFilter: function(filter) {
@ -410,22 +426,26 @@ function($, _, i18n, Backbone, React, utils, models, dispatcher, controls, dialo
if (!filter.values.length) return true;
if (filter.name == 'roles') {
return _.any(filter.values, function(role) {return node.hasRole(role);});
var result;
switch (filter.name) {
case 'roles':
result = _.any(filter.values, function(role) {return node.hasRole(role);});
break;
case 'status':
result = _.contains(filter.values, node.getStatusSummary());
break;
case 'manufacturer':
case 'group_id':
result = _.contains(filter.values, node.get(filter.name));
break;
default:
// handle number ranges
var currentValue = node.resource(filter.name);
if (filter.name == 'hdd' || filter.name == 'ram') currentValue = currentValue / Math.pow(1024, 3);
result = currentValue >= filter.values[0] && (_.isUndefined(filter.values[1]) || currentValue <= filter.values[1]);
break;
}
if (filter.name == 'status') {
return _.contains(filter.values, node.getStatusSummary());
}
if (filter.name == 'manufacturer') {
return _.contains(filter.values, node.get('manufacturer'));
}
// handle number ranges
var currentValue = node.resource(filter.name);
if (filter.name == 'hdd' || filter.name == 'ram') {
currentValue = currentValue / Math.pow(1024, 3);
}
return currentValue >= filter.values[0] && (_.isUndefined(filter.values[1]) || currentValue <= filter.values[1]);
return result;
}, this);
}, this);
@ -526,16 +546,9 @@ function($, _, i18n, Backbone, React, utils, models, dispatcher, controls, dialo
var attributes, labels;
if (this.props.dynamicValues) {
this.props.options.sort(function(option1, option2) {
return utils.natsort(option1.title, option2.title, {insensitive: true});
});
var groupedOptions = _.groupBy(this.props.options, 'isLabel');
attributes = groupedOptions.false || [];
labels = groupedOptions.true || [];
} else {
this.props.options.sort(function(option1, option2) {
return utils.natsort(option1.label, option2.label, {insensitive: true});
});
}
var optionProps = _.bind(function(option) {
@ -895,13 +908,19 @@ function($, _, i18n, Backbone, React, utils, models, dispatcher, controls, dialo
var checkSorter = _.bind(function(sorter, isLabel) {
return !_.any(this.props.activeSorters, {name: sorter.name, isLabel: isLabel});
}, this);
inactiveSorters = _.union(_.filter(this.props.availableSorters, _.partial(checkSorter, _, false)), _.filter(this.props.labelSorters, _.partial(checkSorter, _, true)));
inactiveSorters = _.union(_.filter(this.props.availableSorters, _.partial(checkSorter, _, false)), _.filter(this.props.labelSorters, _.partial(checkSorter, _, true)))
.sort(function(sorter1, sorter2) {
return utils.natsort(sorter1.title, sorter2.title, {insensitive: true});
});
canResetSorters = _.any(this.props.activeSorters, {isLabel: true}) || !_(this.props.activeSorters).where({isLabel: false}).map(Sorter.toObject).isEqual(this.props.defaultSorting);
var checkFilter = _.bind(function(filter, isLabel) {
return !_.any(this.props.activeFilters, {name: filter.name, isLabel: isLabel});
}, this);
inactiveFilters = _.union(_.filter(this.props.availableFilters, _.partial(checkFilter, _, false)), _.filter(this.props.labelFilters, _.partial(checkFilter, _, true)));
inactiveFilters = _.union(_.filter(this.props.availableFilters, _.partial(checkFilter, _, false)), _.filter(this.props.labelFilters, _.partial(checkFilter, _, true)))
.sort(function(filter1, filter2) {
return utils.natsort(filter1.title, filter2.title, {insensitive: true});
});
appliedFilters = _.reject(this.props.activeFilters, function(filter) {
return !filter.isLabel && !filter.values.length;
});
@ -1605,36 +1624,42 @@ function($, _, i18n, Backbone, React, utils, models, dispatcher, controls, dialo
return _.compact(_.map(this.props.activeSorters, function(sorter) {
if (_.contains(uniqValueSorters, sorter.name)) return;
if (sorter.isLabel) {
return getLabelValue(node, sorter.name);
}
if (sorter.isLabel) return getLabelValue(node, sorter.name);
if (sorter.name == 'roles') {
return node.getRolesSummary(roles);
var result;
switch (sorter.name) {
case 'roles':
result = node.getRolesSummary(roles);
break;
case 'status':
result = i18n('cluster_page.nodes_tab.node.status.' + node.getStatusSummary(), {
os: this.props.cluster.get('release').get('operating_system') || 'OS'
});
break;
case 'manufacturer':
result = node.get('manufacturer') || i18n('common.not_specified');
break;
case 'group_id':
result = i18n('cluster_page.nodes_tab.node.node_network_group', {
group: app.nodeNetworkGroups.get(node.get('group_id')).get('name')
});
break;
case 'hdd':
result = i18n('node_details.total_hdd', {total: utils.showDiskSize(node.resource('hdd'))});
break;
case 'disks':
result = composeNodeDiskSizesLabel(node);
break;
case 'ram':
result = i18n('node_details.total_ram', {total: utils.showMemorySize(node.resource('ram'))});
break;
case 'interfaces':
result = i18n('node_details.interfaces_amount', {count: node.resource('interfaces')});
break;
default:
result = i18n('node_details.' + sorter.name, {count: node.resource(sorter.name)});
}
if (sorter.name == 'status') {
return i18n('cluster_page.nodes_tab.node.status.' + node.getStatusSummary(), {
os: this.props.cluster.get('release').get('operating_system') || 'OS'
});
}
if (sorter.name == 'manufacturer') {
return node.get('manufacturer') || i18n('common.not_specified');
}
if (sorter.name == 'hdd') {
return i18n('node_details.total_hdd', {
total: utils.showDiskSize(node.resource('hdd'))
});
}
if (sorter.name == 'disks') {
return composeNodeDiskSizesLabel(node);
}
if (sorter.name == 'ram') {
return i18n('node_details.total_ram', {
total: utils.showMemorySize(node.resource('ram'))
});
}
return i18n('node_details.' + (sorter.name == 'interfaces' ? 'interfaces_amount' : sorter.name), {count: node.resource(sorter.name)});
return result;
}, this)).join('; ');
}, this);
var groups = _.pairs(_.groupBy(this.props.nodes, groupingMethod));
@ -1683,7 +1708,8 @@ function($, _, i18n, Backbone, React, utils, models, dispatcher, controls, dialo
result = _.indexOf(this.props.statusesToFilter, node1.getStatusSummary()) - _.indexOf(this.props.statusesToFilter, node2.getStatusSummary());
break;
case 'manufacturer':
result = utils.compare(node1, node2, {attr: 'manufacturer'});
case 'group_id':
result = utils.compare(node1, node2, {attr: sorter.name});
break;
case 'disks':
result = utils.natsort(composeNodeDiskSizesLabel(node1), composeNodeDiskSizesLabel(node2));

View File

@ -630,12 +630,20 @@ function($, _, i18n, Backbone, React, utils, models, dispatcher, controls, compo
};
if (this.state.VMsConf) groups.push('config');
var nodeNetworkGroup = app.nodeNetworkGroups.get(node.get('group_id'));
return (
<div className='node-details-popup'>
<div className='row'>
<div className='col-xs-5'><div className='node-image-outline' /></div>
<div className='col-xs-7'>
<div><strong>{i18n('dialog.show_node.manufacturer_label')}: </strong>{node.get('manufacturer') || i18n('common.not_available')}</div>
{nodeNetworkGroup &&
<div>
<strong>{i18n('dialog.show_node.node_network_group')}: </strong>
{nodeNetworkGroup.get('name')}
</div>
}
<div><strong>{i18n('dialog.show_node.mac_address_label')}: </strong>{node.get('mac') || i18n('common.not_available')}</div>
<div><strong>{i18n('dialog.show_node.fqdn_label')}: </strong>{(node.get('meta').system || {}).fqdn || node.get('fqdn') || i18n('common.not_available')}</div>
<div className='change-hostname'>

View File

@ -337,6 +337,7 @@ function(require, $, _, i18n, Backbone, utils, models, createClusterWizardTempla
}, this))
.done(_.bind(function() {
this.close();
app.nodeNetworkGroups.fetch();
app.navigate('#cluster/' + this.cluster.id, {trigger: true});
}, this))
.fail(_.bind(function() {