Forcing eslint max-len rule

Implements: blueprint converge-to-eslint-config-openstack

Change-Id: I35e6abb96c4912608222cd85617764172858d191
This commit is contained in:
Alexandra Morozova 2016-01-20 10:21:03 +01:00 committed by Vitaly Kramskikh
parent d62ec107cd
commit fbdc1ccf1f
67 changed files with 2632 additions and 996 deletions

View File

@ -13,7 +13,6 @@
# to be fixed and enabled
eqeqeq: 0
max-len: [0, 120]
# extra rules
no-unexpected-multiline: 2

View File

@ -26,12 +26,15 @@ function validate(translations, locales) {
});
function compareLocales(locale1, locale2) {
return _.without.apply(null, [processedTranslations[locale1]].concat(processedTranslations[locale2]));
return _.without.apply(null, [processedTranslations[locale1]]
.concat(processedTranslations[locale2]));
}
_.each(_.without(locales, baseLocale), function(locale) {
gutil.log(gutil.colors.red('The list of keys present in', baseLocale, 'but absent in', locale, ':\n') + compareLocales(baseLocale, locale).join('\n'));
gutil.log(gutil.colors.red('The list of keys missing in', baseLocale, ':\n') + compareLocales(locale, baseLocale).join('\n'));
gutil.log(gutil.colors.red('The list of keys present in',
baseLocale, 'but absent in', locale, ':\n') + compareLocales(baseLocale, locale).join('\n'));
gutil.log(gutil.colors.red('The list of keys missing in', baseLocale, ':\n') +
compareLocales(locale, baseLocale).join('\n'));
});
}

View File

@ -80,7 +80,8 @@ gulp.task('selenium', ['selenium:fetch'], function(cb) {
if (err) throw err;
child.on('exit', function() {
if (seleniumProcess) {
gutil.log(gutil.colors.yellow('Selenium process died unexpectedly. Probably port', port, 'is already in use.'));
gutil.log(gutil.colors.yellow('Selenium process died unexpectedly. Probably port',
port, 'is already in use.'));
}
});
['exit', 'uncaughtException', 'SIGTERM', 'SIGINT'].forEach(function(event) {
@ -127,7 +128,9 @@ function runIntern(params) {
};
}
gulp.task('intern:functional', runIntern({functionalSuites: argv.suites || 'static/tests/functional/**/test_*.js'}));
gulp.task('intern:functional', runIntern({
functionalSuites: argv.suites || 'static/tests/functional/**/test_*.js'
}));
gulp.task('unit-tests', function(cb) {
runSequence('selenium', 'karma', function(err) {
@ -161,7 +164,8 @@ gulp.task('license', function(cb) {
_.each(data, function(moduleInfo) {
var name = moduleInfo.name;
var version = moduleInfo.version;
var license = _.pluck(moduleInfo.licenseSources.package.sources, 'license').join(', ') || 'unknown';
var license = _.pluck(moduleInfo.licenseSources.package.sources, 'license').join(', ') ||
'unknown';
var licenseOk = license.match(licenseRegexp);
if (!licenseOk) errors.push({libraryName: name, license: license});
gutil.log(
@ -260,10 +264,11 @@ gulp.task('dev-server', function() {
]
};
_.extend(options, config.output);
new WebpackDevServer(webpack(config), options).listen(devServerPort, devServerHost, function(err) {
if (err) throw err;
gutil.log('Development server started at ' + devServerUrl);
});
new WebpackDevServer(webpack(config), options).listen(devServerPort, devServerHost,
function(err) {
if (err) throw err;
gutil.log('Development server started at ' + devServerUrl);
});
});
gulp.task('build', function(cb) {
@ -301,7 +306,8 @@ gulp.task('build', function(cb) {
rimraf.sync(config.output.path);
var compiler = webpack(config);
var run = config.watch ? compiler.watch.bind(compiler, config.watchOptions) : compiler.run.bind(compiler);
var run = config.watch ? compiler.watch.bind(compiler, config.watchOptions) :
compiler.run.bind(compiler);
run(function(err, stats) {
if (err) return cb(err);

View File

@ -70,11 +70,14 @@ class Router extends Backbone.Router {
var specialRoutes = [
{name: 'login', condition: () => {
var result = app.version.get('auth_required') && !app.user.get('authenticated');
if (result && currentUrl != 'login' && currentUrl != 'logout') this.returnUrl = currentUrl;
if (result && currentUrl != 'login' && currentUrl != 'logout') {
this.returnUrl = currentUrl;
}
return result;
}},
{name: 'welcome', condition: (previousUrl) => {
return previousUrl != 'logout' && !app.fuelSettings.get('statistics.user_choice_saved.value');
return previousUrl != 'logout' &&
!app.fuelSettings.get('statistics.user_choice_saved.value');
}}
];
_.each(specialRoutes, (route) => {
@ -153,7 +156,8 @@ class App {
constructor() {
this.initialized = false;
// this is needed for IE, which caches requests resulting in wrong results (e.g /ostf/testruns/last/1)
// this is needed for IE,
// which caches requests resulting in wrong results (e.g /ostf/testruns/last/1)
$.ajaxSetup({cache: false});
this.router = new Router();
@ -217,10 +221,11 @@ class App {
}
loadPage(Page, options = []) {
return (Page.fetchData ? Page.fetchData(...options) : $.Deferred().resolve()).done((pageOptions) => {
if (!this.rootComponent) this.renderLayout();
this.setPage(Page, pageOptions);
});
return (Page.fetchData ? Page.fetchData(...options) : $.Deferred().resolve())
.done((pageOptions) => {
if (!this.rootComponent) this.renderLayout();
this.setPage(Page, pageOptions);
});
}
setPage(Page, options) {

View File

@ -38,7 +38,8 @@ export function dispatcherMixin(events, callback) {
export var unsavedChangesMixin = {
onBeforeunloadEvent() {
if (this.hasChanges()) return _.result(this, 'getStayMessage') || i18n('dialog.dismiss_settings.default_message');
if (this.hasChanges()) return _.result(this, 'getStayMessage') ||
i18n('dialog.dismiss_settings.default_message');
},
componentWillMount() {
this.eventName = _.uniqueId('unsavedchanges');
@ -71,13 +72,15 @@ export function pollingMixin(updateInterval, delayedStart) {
updateInterval = updateInterval * 1000;
return {
scheduleDataFetch() {
var shouldDataBeFetched = !_.isFunction(this.shouldDataBeFetched) || this.shouldDataBeFetched();
var shouldDataBeFetched = !_.isFunction(this.shouldDataBeFetched) ||
this.shouldDataBeFetched();
if (this.isMounted() && !this.activeTimeout && shouldDataBeFetched) {
this.activeTimeout = _.delay(this.startPolling, updateInterval);
}
},
startPolling(force) {
var shouldDataBeFetched = force || !_.isFunction(this.shouldDataBeFetched) || this.shouldDataBeFetched();
var shouldDataBeFetched = force || !_.isFunction(this.shouldDataBeFetched) ||
this.shouldDataBeFetched();
if (shouldDataBeFetched) {
this.stopPolling();
return this.fetchData().always(this.scheduleDataFetch);

View File

@ -43,7 +43,8 @@ class Expression {
getCompiledExpression() {
var cacheEntry = expressionCache[this.expressionText];
if (!cacheEntry) {
cacheEntry = expressionCache[this.expressionText] = ExpressionParser.parse(this.expressionText);
cacheEntry = expressionCache[this.expressionText] =
ExpressionParser.parse(this.expressionText);
}
return cacheEntry;
}

View File

@ -90,7 +90,10 @@ export class ModelPathWrapper {
var result = this.modelPath.get();
if (_.isUndefined(result)) {
if (expression.strict) {
throw new TypeError('Value of ' + this.modelPathText + ' is undefined. Set options.strict to false to allow undefined values.');
throw new TypeError(
'Value of ' + this.modelPathText +
' is undefined. Set options.strict to false to allow undefined values.'
);
}
result = null;
}

View File

@ -29,7 +29,11 @@ class KeystoneClient {
authenticate(username, password, options = {}) {
if (this.tokenUpdateRequest) return this.tokenUpdateRequest;
if (!options.force && this.tokenUpdateTime && (this.cacheTokenFor > (new Date() - this.tokenUpdateTime))) {
if (
!options.force &&
this.tokenUpdateTime &&
(this.cacheTokenFor > (new Date() - this.tokenUpdateTime))
) {
return $.Deferred().resolve();
}
var data = {auth: {}};

View File

@ -72,11 +72,13 @@ _.each(collectionMethods, (method) => {
});
var BaseModel = models.BaseModel = Backbone.Model.extend(superMixin);
var BaseCollection = models.BaseCollection = Backbone.Collection.extend(collectionMixin).extend(superMixin);
var BaseCollection = models.BaseCollection =
Backbone.Collection.extend(collectionMixin).extend(superMixin);
var cacheMixin = {
fetch(options) {
if (this.cacheFor && options && options.cache && this.lastSyncTime && (this.cacheFor > (new Date() - this.lastSyncTime))) {
if (this.cacheFor && options && options.cache && this.lastSyncTime &&
(this.cacheFor > (new Date() - this.lastSyncTime))) {
return $.Deferred().resolve();
}
return this._super('fetch', arguments);
@ -98,7 +100,8 @@ models.cacheMixin = cacheMixin;
var restrictionMixin = models.restrictionMixin = {
checkRestrictions(models, action, setting) {
var restrictions = _.map(setting ? setting.restrictions : this.get('restrictions'), utils.expandRestriction);
var restrictions = _.map(setting ? setting.restrictions : this.get('restrictions'),
utils.expandRestriction);
if (action) {
restrictions = _.where(restrictions, {action: action});
}
@ -125,7 +128,8 @@ var restrictionMixin = models.restrictionMixin = {
* and validate current model checking the possibility of adding/removing node
* So if max = 1 and we have 1 node then:
* - the model is valid as is (return true) -- case for checkLimitIsReached = true
* - there can be no more nodes added (return false) -- case for checkLimitIsReached = false
* - there can be no more nodes added (return false) -- case for
* checkLimitIsReached = false
* limitType -- array of limit types to check. Possible choices are 'min', 'max', 'recommended'
**/
@ -177,14 +181,16 @@ var restrictionMixin = models.restrictionMixin = {
comparator = (a, b) => a < b;
}
limitValue = parseInt(evaluateExpressionHelper(obj[limitType], models).value, 10);
// Update limitValue with overrides, this way at the end we have a flattened limitValues with overrides having priority
// Update limitValue with overrides, this way at the end we have a flattened
// limitValues with overrides having priority
limitValues[limitType] = limitValue;
checkedLimitTypes[limitType] = true;
if (comparator(count, limitValue)) {
return {
type: limitType,
value: limitValue,
message: obj.message || i18n('common.role_limits.' + limitType, {limitValue: limitValue, count: count, roleName: label})
message: obj.message || i18n('common.role_limits.' + limitType,
{limitValue: limitValue, count: count, roleName: label})
};
}
};
@ -291,7 +297,9 @@ models.Roles = BaseCollection.extend(restrictionMixin).extend({
_.each(role.conflicts, (conflictRoleName) => {
var conflictingRole = this.findWhere({name: conflictRoleName});
if (conflictingRole) conflictingRole.conflicts = _.uniq(_.union(conflictingRole.conflicts || [], [roleName]));
if (conflictingRole) {
conflictingRole.conflicts = _.uniq(_.union(conflictingRole.conflicts || [], [roleName]));
}
});
});
}
@ -343,7 +351,8 @@ models.Cluster = BaseModel.extend({
return this.get('tasks') && this.get('tasks').filterTasks(filters);
},
needsRedeployment() {
return this.get('nodes').any({pending_addition: false, status: 'error'}) && this.get('status') != 'update_error';
return this.get('nodes').any({pending_addition: false, status: 'error'}) &&
this.get('status') != 'update_error';
},
fetchRelated(related, options) {
return this.get(related).fetch(_.extend({data: {cluster_id: this.id}}, options));
@ -354,7 +363,8 @@ models.Cluster = BaseModel.extend({
isDeploymentPossible() {
var nodes = this.get('nodes');
return this.get('release').get('state') != 'unavailable' && !!nodes.length &&
(nodes.hasChanges() || this.needsRedeployment()) && !this.task({group: 'deployment', active: true});
(nodes.hasChanges() || this.needsRedeployment()) &&
!this.task({group: 'deployment', active: true});
}
});
@ -388,7 +398,9 @@ models.Node = BaseModel.extend({
} else if (resourceName == 'ht_cores') {
resource = this.get('meta').cpu.total;
} else if (resourceName == 'hdd') {
resource = _.reduce(this.get('meta').disks, (hdd, disk) => _.isNumber(disk.size) ? hdd + disk.size : hdd, 0);
resource = _.reduce(this.get('meta').disks, (hdd, disk) => {
return _.isNumber(disk.size) ? hdd + disk.size : hdd;
}, 0);
} else if (resourceName == 'ram') {
resource = this.get('meta').memory.total;
} else if (resourceName == 'disks') {
@ -412,7 +424,8 @@ models.Node = BaseModel.extend({
return this.get('status') != 'removing';
},
hasRole(role, onlyDeployedRoles) {
var roles = onlyDeployedRoles ? this.get('roles') : _.union(this.get('roles'), this.get('pending_roles'));
var roles = onlyDeployedRoles ? this.get('roles') :
_.union(this.get('roles'), this.get('pending_roles'));
return _.contains(roles, role);
},
hasChanges() {
@ -505,7 +518,8 @@ models.Nodes = BaseCollection.extend({
var roles = _.union(this.at(0).get('roles'), this.at(0).get('pending_roles'));
var disks = this.at(0).resource('disks');
return !this.any((node) => {
var roleConflict = _.difference(roles, _.union(node.get('roles'), node.get('pending_roles'))).length;
var roleConflict = _.difference(roles, _.union(node.get('roles'),
node.get('pending_roles'))).length;
return roleConflict || !_.isEqual(disks, node.resource('disks'));
});
},
@ -556,10 +570,12 @@ models.Task = BaseModel.extend({
match(filters) {
filters = filters || {};
if (!_.isEmpty(filters)) {
if ((filters.group || filters.name) && !_.contains(this.extendGroups(filters), this.get('name'))) {
if ((filters.group || filters.name) &&
!_.contains(this.extendGroups(filters), this.get('name'))) {
return false;
}
if ((filters.status || _.isBoolean(filters.active)) && !_.contains(this.extendStatuses(filters), this.get('status'))) {
if ((filters.status || _.isBoolean(filters.active)) &&
!_.contains(this.extendStatuses(filters), this.get('status'))) {
return false;
}
}
@ -607,121 +623,134 @@ models.Notifications = BaseCollection.extend({
}
});
models.Settings = Backbone.DeepModel.extend(superMixin).extend(cacheMixin).extend(restrictionMixin).extend({
constructorName: 'Settings',
urlRoot: '/api/clusters/',
root: 'editable',
cacheFor: 60 * 1000,
groupList: ['general', 'security', 'compute', 'network', 'storage', 'logging', 'openstack_services', 'other'],
isNew() {
return false;
},
isPlugin(section) {
return (section.metadata || {}).class == 'plugin';
},
parse(response) {
return response[this.root];
},
mergePluginSettings() {
_.each(this.attributes, (section, sectionName) => {
if (this.isPlugin(section)) {
var chosenVersionData = section.metadata.versions.find(
(version) => version.metadata.plugin_id == section.metadata.chosen_id
);
// merge metadata of a chosen plugin version
_.extend(section.metadata, _.omit(chosenVersionData.metadata, 'plugin_id', 'plugin_version'));
// merge settings of a chosen plugin version
this.attributes[sectionName] = _.extend(_.pick(section, 'metadata'), _.omit(chosenVersionData, 'metadata'));
}
}, this);
},
toJSON() {
var settings = this._super('toJSON', arguments);
if (!this.root) return settings;
models.Settings = Backbone.DeepModel
.extend(superMixin)
.extend(cacheMixin)
.extend(restrictionMixin)
.extend({
constructorName: 'Settings',
urlRoot: '/api/clusters/',
root: 'editable',
cacheFor: 60 * 1000,
groupList: ['general', 'security', 'compute', 'network', 'storage',
'logging', 'openstack_services', 'other'],
isNew() {
return false;
},
isPlugin(section) {
return (section.metadata || {}).class == 'plugin';
},
parse(response) {
return response[this.root];
},
mergePluginSettings() {
_.each(this.attributes, (section, sectionName) => {
if (this.isPlugin(section)) {
var chosenVersionData = section.metadata.versions.find(
(version) => version.metadata.plugin_id == section.metadata.chosen_id
);
// merge metadata of a chosen plugin version
_.extend(section.metadata,
_.omit(chosenVersionData.metadata, 'plugin_id', 'plugin_version'));
// merge settings of a chosen plugin version
this.attributes[sectionName] = _.extend(_.pick(section, 'metadata'),
_.omit(chosenVersionData, 'metadata'));
}
}, this);
},
toJSON() {
var settings = this._super('toJSON', arguments);
if (!this.root) return settings;
// update plugin settings
_.each(settings, (section, sectionName) => {
if (this.isPlugin(section)) {
var chosenVersionData = section.metadata.versions.find(
(version) => version.metadata.plugin_id == section.metadata.chosen_id
);
section.metadata = _.omit(section.metadata, _.without(_.keys(chosenVersionData.metadata), 'plugin_id', 'plugin_version'));
_.each(section, (setting, settingName) => {
if (settingName != 'metadata') chosenVersionData[settingName].value = setting.value;
});
settings[sectionName] = _.pick(section, 'metadata');
}
});
return {[this.root]: settings};
},
initialize() {
this.once('change', this.mergePluginSettings, this);
},
validate(attrs, options) {
var errors = {};
var models = options ? options.models : {};
var checkRestrictions = (setting) => this.checkRestrictions(models, null, setting);
_.each(attrs, (group, groupName) => {
if ((group.metadata || {}).enabled === false || checkRestrictions(group.metadata).result) return;
_.each(group, (setting, settingName) => {
if (checkRestrictions(setting).result) return;
var path = this.makePath(groupName, settingName);
// support of custom controls
var CustomControl = customControls[setting.type];
if (CustomControl) {
var error = CustomControl.validate(setting, models);
if (error) errors[path] = error;
return;
// update plugin settings
_.each(settings, (section, sectionName) => {
if (this.isPlugin(section)) {
var chosenVersionData = section.metadata.versions.find(
(version) => version.metadata.plugin_id == section.metadata.chosen_id
);
section.metadata = _.omit(section.metadata,
_.without(_.keys(chosenVersionData.metadata), 'plugin_id', 'plugin_version'));
_.each(section, (setting, settingName) => {
if (settingName != 'metadata') chosenVersionData[settingName].value = setting.value;
});
settings[sectionName] = _.pick(section, 'metadata');
}
});
return {[this.root]: settings};
},
initialize() {
this.once('change', this.mergePluginSettings, this);
},
validate(attrs, options) {
var errors = {};
var models = options ? options.models : {};
var checkRestrictions = (setting) => this.checkRestrictions(models, null, setting);
_.each(attrs, (group, groupName) => {
if ((group.metadata || {}).enabled === false ||
checkRestrictions(group.metadata).result) return;
_.each(group, (setting, settingName) => {
if (checkRestrictions(setting).result) return;
var path = this.makePath(groupName, settingName);
// support of custom controls
var CustomControl = customControls[setting.type];
if (CustomControl) {
var error = CustomControl.validate(setting, models);
if (error) errors[path] = error;
return;
}
if (!(setting.regex || {}).source) return;
if (!setting.value.match(new RegExp(setting.regex.source))) errors[path] = setting.regex.error;
});
});
return _.isEmpty(errors) ? null : errors;
},
makePath(...args) {
return args.join('.');
},
getValueAttribute(settingName) {
return settingName == 'metadata' ? 'enabled' : 'value';
},
hasChanges(initialAttributes, models) {
return _.any(this.attributes, (section, sectionName) => {
var metadata = section.metadata;
var result = false;
if (metadata) {
if (this.checkRestrictions(models, null, metadata).result) return result;
if (!_.isUndefined(metadata.enabled)) {
result = metadata.enabled != initialAttributes[sectionName].metadata.enabled;
}
if (!result && this.isPlugin(section)) {
result = metadata.chosen_id != initialAttributes[sectionName].metadata.chosen_id;
}
}
return result || (metadata || {}).enabled !== false && _.any(section, (setting, settingName) => {
if (this.checkRestrictions(models, null, setting).result) return false;
return !_.isEqual(setting.value, (initialAttributes[sectionName][settingName] || {}).value);
});
});
},
sanitizeGroup(group) {
return _.contains(this.groupList, group) ? group : 'other';
},
getGroupList() {
var groups = [];
_.each(this.attributes, (section) => {
if (section.metadata.group) {
groups.push(this.sanitizeGroup(section.metadata.group));
} else {
_.each(section, (setting, settingName) => {
if (settingName != 'metadata') groups.push(this.sanitizeGroup(setting.group));
if (!(setting.regex || {}).source) return;
if (!setting.value.match(new RegExp(setting.regex.source))) {
errors[path] = setting.regex.error;
}
});
}
});
return _.intersection(this.groupList, groups);
}
});
});
return _.isEmpty(errors) ? null : errors;
},
makePath(...args) {
return args.join('.');
},
getValueAttribute(settingName) {
return settingName == 'metadata' ? 'enabled' : 'value';
},
hasChanges(initialAttributes, models) {
return _.any(this.attributes, (section, sectionName) => {
var metadata = section.metadata;
var result = false;
if (metadata) {
if (this.checkRestrictions(models, null, metadata).result) return result;
if (!_.isUndefined(metadata.enabled)) {
result = metadata.enabled != initialAttributes[sectionName].metadata.enabled;
}
if (!result && this.isPlugin(section)) {
result = metadata.chosen_id != initialAttributes[sectionName].metadata.chosen_id;
}
}
return result || (metadata || {}).enabled !== false &&
_.any(section, (setting, settingName) => {
if (this.checkRestrictions(models, null, setting).result) return false;
return !_.isEqual(setting.value,
(initialAttributes[sectionName][settingName] || {}).value);
});
});
},
sanitizeGroup(group) {
return _.contains(this.groupList, group) ? group : 'other';
},
getGroupList() {
var groups = [];
_.each(this.attributes, (section) => {
if (section.metadata.group) {
groups.push(this.sanitizeGroup(section.metadata.group));
} else {
_.each(section, (setting, settingName) => {
if (settingName != 'metadata') groups.push(this.sanitizeGroup(setting.group));
});
}
});
return _.intersection(this.groupList, groups);
}
});
models.FuelSettings = models.Settings.extend({
constructorName: 'FuelSettings',
@ -741,7 +770,8 @@ models.Disk = BaseModel.extend({
return response;
},
toJSON(options) {
return _.extend(this.constructor.__super__.toJSON.call(this, options), {volumes: this.get('volumes').toJSON()});
return _.extend(this.constructor.__super__.toJSON.call(this, options),
{volumes: this.get('volumes').toJSON()});
},
getUnallocatedSpace(options) {
options = options || {};
@ -755,7 +785,8 @@ models.Disk = BaseModel.extend({
var error;
var unallocatedSpace = this.getUnallocatedSpace({volumes: attrs.volumes});
if (unallocatedSpace < 0) {
error = i18n('cluster_page.nodes_tab.configure_disks.validation_error', {size: utils.formatNumber(unallocatedSpace * -1)});
error = i18n('cluster_page.nodes_tab.configure_disks.validation_error',
{size: utils.formatNumber(unallocatedSpace * -1)});
}
return error;
}
@ -776,7 +807,8 @@ models.Volume = BaseModel.extend({
var groupAllocatedSpace = 0;
if (currentDisk && currentDisk.collection)
groupAllocatedSpace = currentDisk.collection.reduce((sum, disk) => {
return disk.id == currentDisk.id ? sum : sum + disk.get('volumes').findWhere({name: this.get('name')}).get('size');
return disk.id == currentDisk.id ? sum : sum +
disk.get('volumes').findWhere({name: this.get('name')}).get('size');
}, 0);
return minimum - groupAllocatedSpace;
},
@ -790,7 +822,8 @@ models.Volume = BaseModel.extend({
validate(attrs, options) {
var min = this.getMinimalSize(options.minimum);
if (attrs.size < min) {
return i18n('cluster_page.nodes_tab.configure_disks.volume_error', {size: utils.formatNumber(min)});
return i18n('cluster_page.nodes_tab.configure_disks.volume_error',
{size: utils.formatNumber(min)});
}
return null;
}
@ -826,11 +859,15 @@ models.Interface = BaseModel.extend({
},
validate(attrs) {
var errors = [];
var networks = new models.Networks(this.get('assigned_networks').invoke('getFullNetwork', attrs.networks));
var untaggedNetworks = networks.filter((network) => _.isNull(network.getVlanRange(attrs.networkingParameters)));
var networks = new models.Networks(this.get('assigned_networks')
.invoke('getFullNetwork', attrs.networks));
var untaggedNetworks = networks.filter((network) => {
return _.isNull(network.getVlanRange(attrs.networkingParameters));
});
var ns = 'cluster_page.nodes_tab.configure_interfaces.validation.';
// public and floating networks are allowed to be assigned to the same interface
var maxUntaggedNetworksCount = networks.any({name: 'public'}) && networks.any({name: 'floating'}) ? 2 : 1;
var maxUntaggedNetworksCount = networks.any({name: 'public'}) &&
networks.any({name: 'floating'}) ? 2 : 1;
if (untaggedNetworks.length > maxUntaggedNetworksCount) {
errors.push(i18n(ns + 'too_many_untagged_networks'));
}
@ -853,7 +890,8 @@ models.Interface = BaseModel.extend({
if (
_.any(vlanRanges,
(currentRange) => _.any(vlanRanges,
(range) => !_.isEqual(currentRange, range) && range[1] >= currentRange[0] && range[0] <= currentRange[1]
(range) => !_.isEqual(currentRange, range) &&
range[1] >= currentRange[0] && range[0] <= currentRange[1]
)
)
) errors.push(i18n(ns + 'vlan_range_intersection'));
@ -877,7 +915,8 @@ models.Interfaces = BaseCollection.extend({
}
});
var networkPreferredOrder = ['public', 'floating', 'storage', 'management', 'private', 'fixed', 'baremetal'];
var networkPreferredOrder = ['public', 'floating', 'storage', 'management',
'private', 'fixed', 'baremetal'];
models.InterfaceNetwork = BaseModel.extend({
constructorName: 'InterfaceNetwork',
@ -899,8 +938,11 @@ models.Network = BaseModel.extend({
getVlanRange(networkingParameters) {
if (!this.get('meta').neutron_vlan_range) {
var externalNetworkData = this.get('meta').ext_net_data;
var vlanStart = externalNetworkData ? networkingParameters.get(externalNetworkData[0]) : this.get('vlan_start');
return _.isNull(vlanStart) ? vlanStart : [vlanStart, externalNetworkData ? vlanStart + networkingParameters.get(externalNetworkData[1]) - 1 : vlanStart];
var vlanStart = externalNetworkData ?
networkingParameters.get(externalNetworkData[0]) : this.get('vlan_start');
return _.isNull(vlanStart) ? vlanStart :
[vlanStart, externalNetworkData ?
vlanStart + networkingParameters.get(externalNetworkData[1]) - 1 : vlanStart];
}
return networkingParameters.get('vlan_range');
}
@ -923,7 +965,9 @@ models.NetworkConfiguration = BaseModel.extend(cacheMixin).extend({
cacheFor: 60 * 1000,
parse(response) {
response.networks = new models.Networks(response.networks);
response.networking_parameters = new models.NetworkingParameters(response.networking_parameters);
response.networking_parameters = new models.NetworkingParameters(
response.networking_parameters
);
return response;
},
toJSON() {
@ -982,7 +1026,8 @@ models.NetworkConfiguration = BaseModel.extend(cacheMixin).extend({
var forbiddenVlans = novaNetManager ? networksToCheck.map((net) => {
return net.id != network.id ? net.get('vlan_start') : null;
}) : [];
_.extend(networkErrors, utils.validateVlan(network.get('vlan_start'), forbiddenVlans, 'vlan_start'));
_.extend(networkErrors,
utils.validateVlan(network.get('vlan_start'), forbiddenVlans, 'vlan_start'));
if (!_.isEmpty(networkErrors)) {
nodeNetworkGroupErrors[network.id] = networkErrors;
@ -995,7 +1040,9 @@ models.NetworkConfiguration = BaseModel.extend(cacheMixin).extend({
} else if (!cidrError && !utils.validateIpCorrespondsToCIDR(cidr, baremetalGateway)) {
networkingParametersErrors.baremetal_gateway = i18n(ns + 'gateway_does_not_match_cidr');
}
var baremetalRangeErrors = utils.validateIPRanges([networkParameters.get('baremetal_range')], cidrError ? null : cidr);
var baremetalRangeErrors =
utils.validateIPRanges([networkParameters.get('baremetal_range')], cidrError ?
null : cidr);
if (baremetalRangeErrors.length) {
var [{start, end}] = baremetalRangeErrors;
networkingParametersErrors.baremetal_range = [start, end];
@ -1014,13 +1061,15 @@ models.NetworkConfiguration = BaseModel.extend(cacheMixin).extend({
// validate networking parameters
if (novaNetManager) {
networkingParametersErrors = _.extend(networkingParametersErrors, utils.validateCidr(networkParameters.get('fixed_networks_cidr'), 'fixed_networks_cidr'));
networkingParametersErrors = _.extend(networkingParametersErrors,
utils.validateCidr(networkParameters.get('fixed_networks_cidr'), 'fixed_networks_cidr'));
var fixedAmount = networkParameters.get('fixed_networks_amount');
var fixedVlan = networkParameters.get('fixed_networks_vlan_start');
if (!utils.isNaturalNumber(parseInt(fixedAmount, 10))) {
networkingParametersErrors.fixed_networks_amount = i18n(ns + 'invalid_amount');
}
var vlanErrors = utils.validateVlan(fixedVlan, networks.pluck('vlan_start'), 'fixed_networks_vlan_start', novaNetManager == 'VlanManager');
var vlanErrors = utils.validateVlan(fixedVlan, networks.pluck('vlan_start'),
'fixed_networks_vlan_start', novaNetManager == 'VlanManager');
_.extend(networkingParametersErrors, vlanErrors);
if (_.isEmpty(vlanErrors)) {
if (!networkingParametersErrors.fixed_networks_amount && fixedAmount > 4095 - fixedVlan) {
@ -1071,7 +1120,8 @@ models.NetworkConfiguration = BaseModel.extend(cacheMixin).extend({
networkingParametersErrors.base_mac = i18n(ns + 'invalid_mac');
}
var cidr = networkParameters.get('internal_cidr');
networkingParametersErrors = _.extend(networkingParametersErrors, utils.validateCidr(cidr, 'internal_cidr'));
networkingParametersErrors = _.extend(networkingParametersErrors,
utils.validateCidr(cidr, 'internal_cidr'));
var gateway = networkParameters.get('internal_gateway');
if (!utils.validateIP(gateway)) {
networkingParametersErrors.internal_gateway = i18n(ns + 'invalid_gateway');
@ -1100,22 +1150,29 @@ models.NetworkConfiguration = BaseModel.extend(cacheMixin).extend({
var networkToCheckFloatingRangeData = networkToCheckFloatingRange ? {
cidr: networkToCheckFloatingRange.get('cidr'),
network: _.capitalize(networkToCheckFloatingRange.get('name')),
nodeNetworkGroup: nodeNetworkGroups.get(networkToCheckFloatingRange.get('group_id')).get('name')
nodeNetworkGroup: nodeNetworkGroups
.get(networkToCheckFloatingRange.get('group_id'))
.get('name')
} : {};
var networkToCheckFloatingRangeIPRanges = networkToCheckFloatingRange ? _.filter(networkToCheckFloatingRange.get('ip_ranges'), (range, index) => {
var ipRangeError = false;
try {
ipRangeError = !_.all(range) || !!_.find(errors.networks[networkToCheckFloatingRange.get('group_id')][networkToCheckFloatingRange.id].ip_ranges, {index: index});
} catch (error) {}
return !ipRangeError;
}) : [];
var networkToCheckFloatingRangeIPRanges = networkToCheckFloatingRange ?
_.filter(networkToCheckFloatingRange.get('ip_ranges'), (range, index) => {
var ipRangeError = false;
try {
ipRangeError = !_.all(range) ||
!!_.find(errors
.networks[networkToCheckFloatingRange
.get('group_id')][networkToCheckFloatingRange.id].ip_ranges, {index: index});
} catch (error) {}
return !ipRangeError;
}) : [];
floatingRangesErrors = utils.validateIPRanges(
floatingRanges,
networkToCheckFloatingRangeData.cidr,
networkToCheckFloatingRangeIPRanges,
{
IP_RANGES_INTERSECTION: i18n(ns + 'floating_and_public_ip_ranges_intersection', networkToCheckFloatingRangeData),
IP_RANGES_INTERSECTION: i18n(ns + 'floating_and_public_ip_ranges_intersection',
networkToCheckFloatingRangeData),
IP_RANGE_IS_NOT_IN_PUBLIC_CIDR: i18n(ns + 'floating_range_is_not_in_public_cidr')
}
);
@ -1268,7 +1325,8 @@ models.WizardModel = Backbone.DeepModel.extend({
var errors = [];
_.each(options.config, (attributeConfig, attribute) => {
if (!(attributeConfig.regex && attributeConfig.regex.source)) return;
var hasNoSatisfiedRestrictions = _.every(_.reject(attributeConfig.restrictions, {action: 'none'}), (restriction) => {
var hasNoSatisfiedRestrictions = _.every(_.reject(attributeConfig.restrictions,
{action: 'none'}), (restriction) => {
// this probably will be changed when other controls need validation
return !utils.evaluateExpression(restriction.condition, {default: this}).value;
});
@ -1291,14 +1349,17 @@ models.WizardModel = Backbone.DeepModel.extend({
models.MirantisCredentials = Backbone.DeepModel.extend(superMixin).extend({
constructorName: 'MirantisCredentials',
baseUrl: 'https://software.mirantis.com/wp-content/themes/mirantis_responsive_v_1_0/scripts/fuel_forms_api/',
baseUrl: 'https://software.mirantis.com/wp-content/themes/' +
'mirantis_responsive_v_1_0/scripts/fuel_forms_api/',
validate(attrs) {
var errors = {};
_.each(attrs, (group, groupName) => {
_.each(group, (setting, settingName) => {
var path = this.makePath(groupName, settingName);
if (!setting.regex || !setting.regex.source) return;
if (!setting.value.match(new RegExp(setting.regex.source))) errors[path] = setting.regex.error;
if (!setting.value.match(new RegExp(setting.regex.source))) {
errors[path] = setting.regex.error;
}
});
});
return _.isEmpty(errors) ? null : errors;
@ -1414,7 +1475,8 @@ models.ComponentModel = BaseModel.extend({
var expandProperty = (propertyName, components) => {
var expandedComponents = [];
_.each(this.get(propertyName), (patternDescription) => {
var patternName = _.isString(patternDescription) ? patternDescription : patternDescription.name;
var patternName = _.isString(patternDescription) ? patternDescription :
patternDescription.name;
var pattern = new ComponentPattern(patternName);
components.each((component) => {
if (pattern.match(component.id)) {

View File

@ -36,67 +36,74 @@ function testRegex(regexText, value) {
return regexCache[regexText].test(value);
}
var BaseModel = Backbone.Model.extend(models.superMixin).extend(models.cacheMixin).extend(models.restrictionMixin).extend({
constructorName: 'BaseModel',
cacheFor: 60 * 1000,
toJSON() {
return _.omit(this.attributes, 'metadata');
},
validate() {
var result = {};
_.each(this.attributes.metadata, (field) => {
if (!VmWareModels.isRegularField(field) || field.type == 'checkbox') {
return;
}
var isDisabled = this.checkRestrictions(restrictionModels, undefined, field);
if (isDisabled.result) {
return;
}
var value = this.get(field.name);
if (field.regex) {
if (!testRegex(field.regex.source, value)) {
result[field.name] = field.regex.error;
var BaseModel = Backbone.Model
.extend(models.superMixin)
.extend(models.cacheMixin)
.extend(models.restrictionMixin)
.extend({
constructorName: 'BaseModel',
cacheFor: 60 * 1000,
toJSON() {
return _.omit(this.attributes, 'metadata');
},
validate() {
var result = {};
_.each(this.attributes.metadata, (field) => {
if (!VmWareModels.isRegularField(field) || field.type == 'checkbox') {
return;
}
}
});
return _.isEmpty(result) ? null : result;
},
testRestrictions() {
var results = {
hide: {},
disable: {}
};
var metadata = this.get('metadata');
_.each(metadata, (field) => {
var disableResult = this.checkRestrictions(restrictionModels, undefined, field);
results.disable[field.name] = disableResult;
var isDisabled = this.checkRestrictions(restrictionModels, undefined, field);
if (isDisabled.result) {
return;
}
var value = this.get(field.name);
if (field.regex) {
if (!testRegex(field.regex.source, value)) {
result[field.name] = field.regex.error;
}
}
});
return _.isEmpty(result) ? null : result;
},
testRestrictions() {
var results = {
hide: {},
disable: {}
};
var metadata = this.get('metadata');
_.each(metadata, (field) => {
var disableResult = this.checkRestrictions(restrictionModels, undefined, field);
results.disable[field.name] = disableResult;
var hideResult = this.checkRestrictions(restrictionModels, 'hide', field);
results.hide[field.name] = hideResult;
});
return results;
}
});
var hideResult = this.checkRestrictions(restrictionModels, 'hide', field);
results.hide[field.name] = hideResult;
});
return results;
}
});
var BaseCollection = Backbone.Collection.extend(models.superMixin).extend(models.cacheMixin).extend({
constructorName: 'BaseCollection',
model: BaseModel,
cacheFor: 60 * 1000,
isValid() {
this.validationError = this.validate();
return this.validationError;
},
validate() {
var errors = _.compact(this.models.map((model) => {
model.isValid();
return model.validationError;
}));
return _.isEmpty(errors) ? null : errors;
},
testRestrictions() {
_.invoke(this.models, 'testRestrictions', restrictionModels);
}
});
var BaseCollection = Backbone.Collection
.extend(models.superMixin)
.extend(models.cacheMixin)
.extend({
constructorName: 'BaseCollection',
model: BaseModel,
cacheFor: 60 * 1000,
isValid() {
this.validationError = this.validate();
return this.validationError;
},
validate() {
var errors = _.compact(this.models.map((model) => {
model.isValid();
return model.validationError;
}));
return _.isEmpty(errors) ? null : errors;
},
testRestrictions() {
_.invoke(this.models, 'testRestrictions', restrictionModels);
}
});
VmWareModels.NovaCompute = BaseModel.extend({
constructorName: 'NovaCompute',
@ -209,7 +216,8 @@ VmWareModels.Glance = BaseModel.extend({constructorName: 'Glance'});
VmWareModels.VCenter = BaseModel.extend({
constructorName: 'VCenter',
url() {
return '/api/v1/clusters/' + this.id + '/vmware_attributes' + (this.loadDefaults ? '/defaults' : '');
return '/api/v1/clusters/' + this.id + '/vmware_attributes' +
(this.loadDefaults ? '/defaults' : '');
},
parse(response) {
if (!response.editable || !response.editable.metadata || !response.editable.value) {
@ -289,7 +297,8 @@ VmWareModels.VCenter = BaseModel.extend({
});
});
var unassignedNodes = restrictionModels.cluster.get('nodes').filter((node) => {
return _.contains(node.get('pending_roles'), 'compute-vmware') && !assignedNodes[node.get('hostname')];
return _.contains(node.get('pending_roles'), 'compute-vmware') &&
!assignedNodes[node.get('hostname')];
});
if (unassignedNodes.length > 0) {
errors.unassigned_nodes = unassignedNodes;

View File

@ -236,7 +236,13 @@ var AvailabilityZones = React.createClass({
}
</h3>
{this.props.collection.map((model) => {
return <AvailabilityZone key={model.cid} model={model} disabled={this.props.disabled} cluster={this.props.cluster} isLocked={this.props.isLocked}/>;
return <AvailabilityZone
key={model.cid}
model={model}
disabled={this.props.disabled}
cluster={this.props.cluster}
isLocked={this.props.isLocked}
/>;
})}
</div>
);
@ -310,7 +316,8 @@ var VmWareTab = React.createClass({
this.model.setModels({
cluster: this.props.cluster,
settings: this.props.cluster.get('settings'),
networking_parameters: this.props.cluster.get('networkConfiguration').get('networking_parameters')
networking_parameters: this.props.cluster.get('networkConfiguration')
.get('networking_parameters')
});
this.onModelSync(); // eslint-disable-line no-sync
@ -419,13 +426,25 @@ var VmWareTab = React.createClass({
<div className='col-xs-12 page-buttons content-elements'>
<div className='well clearfix'>
<div className='btn-group pull-right'>
<button className='btn btn-default btn-load-defaults' onClick={this.onLoadDefaults} disabled={!editable || defaultsDisabled}>
<button
className='btn btn-default btn-load-defaults'
onClick={this.onLoadDefaults}
disabled={!editable || defaultsDisabled}
>
{i18n('vmware.reset_to_defaults')}
</button>
<button className='btn btn-default btn-revert-changes' onClick={this.revertChanges} disabled={!hasChanges}>
<button
className='btn btn-default btn-revert-changes'
onClick={this.revertChanges}
disabled={!hasChanges}
>
{i18n('vmware.cancel')}
</button>
<button className='btn btn-success btn-apply-changes' onClick={this.applyChanges} disabled={saveDisabled}>
<button
className='btn btn-success btn-apply-changes'
onClick={this.applyChanges}
disabled={saveDisabled}
>
{i18n('vmware.apply')}
</button>
</div>

View File

@ -125,7 +125,8 @@ define([
var dragAndDrop = (function() {
var dispatchEvent, createEvent;
// Setup methods to call the proper event creation and dispatch functions for the current platform.
// Setup methods to call the proper event creation and
// dispatch functions for the current platform.
if (document.createEvent) {
dispatchEvent = function(element, eventName, event) {
element.dispatchEvent(event);
@ -215,8 +216,8 @@ define([
try {
event = createEvent('MouseEvent');
event.initMouseEvent(eventName, true, true, window, 0, screenX, screenY, clientX, clientY,
false, false, false, false, 0, null);
event.initMouseEvent(eventName, true, true, window, 0, screenX, screenY, clientX,
clientY, false, false, false, false, 0, null);
} catch (error) {
event = createCustomEvent(eventName, screenX, screenY, clientX, clientY);
}
@ -230,7 +231,8 @@ define([
function simulateEvent(element, eventName, dragStartEvent, options) {
var dataTransfer = dragStartEvent ? dragStartEvent.dataTransfer : null;
var createEvent = eventName.indexOf('mouse') !== -1 ? createMouseEvent : createDragEvent;
var createEvent = eventName.indexOf('mouse') !== -1 ? createMouseEvent :
createDragEvent;
var event = createEvent(eventName, options, dataTransfer);
return dispatchEvent(element, eventName, event);
}

View File

@ -86,7 +86,9 @@ function(_, assert, Helpers, pollUntil, LoginPage, WelcomePage, ClusterPage, Clu
return self.clusterPage.removeCluster(clusterName);
})
.catch(function(e) {
if (!suppressErrors) throw new Error('Unable to delete cluster ' + clusterName + ': ' + e);
if (!suppressErrors) {
throw new Error('Unable to delete cluster ' + clusterName + ': ' + e);
}
});
},
doesClusterExist: function(clusterName) {
@ -115,7 +117,8 @@ function(_, assert, Helpers, pollUntil, LoginPage, WelcomePage, ClusterPage, Clu
.clickByCssSelector('button.btn-add-nodes')
.waitForCssSelector('.node', 3000)
.then(pollUntil(function() {
return window.$('.node-list-management-buttons').is(':visible') && window.$('.role-panel').is(':visible') || null;
return window.$('.node-list-management-buttons').is(':visible') &&
window.$('.role-panel').is(':visible') || null;
}, 3000))
.then(function() {
if (nodeNameFilter) return self.clusterPage.searchForNode(nodeNameFilter);

View File

@ -51,7 +51,8 @@ define([
duration: '20s',
message: null,
id: 'fuel_health.tests.check_disk',
description: 'Target component: Nova Scenario: 1. Check outage on controller and compute nodes'
description: 'Target component: Nova ' +
'Scenario: 1. Check outage on controller and compute nodes'
},
{
status: null,
@ -73,7 +74,8 @@ define([
duration: '30s.',
message: null,
id: 'fuel_health.tests.general',
description: 'Target component: Logging Scenario: 1. Check logrotate cron job on all controller and compute nodes'
description: 'Target component: Logging ' +
'Scenario: 1. Check logrotate cron job on all controller and compute nodes'
},
{
status: null,
@ -84,7 +86,8 @@ define([
duration: '1sec',
message: null,
id: 'fuel_health.tests.credentials',
description: 'Target component: Configuration Scenario: 1. Check user can not ssh on master node with default credentials. '
description: 'Target component: Configuration ' +
'Scenario: 1. Check user can not ssh on master node with default credentials.'
},
{
status: null,
@ -95,7 +98,8 @@ define([
duration: '',
message: null,
id: 'fuel_health.tests.credentials_change',
description: 'Target component: Configuration Scenario: 1. Check if default credentials for OpenStack cluster have changed. '
description: 'Target component: Configuration ' +
'Scenario: 1. Check if default credentials for OpenStack cluster have changed.'
},
{
status: null,
@ -117,7 +121,8 @@ define([
duration: '',
message: null,
id: 'fuel_health.tests.credentials_erros',
description: 'Target component: Configuration Scenario: 1. Check if default credentials for OpenStack cluster have changed. '
description: 'Target component: Configuration ' +
'Scenario: 1. Check if default credentials for OpenStack cluster have changed. '
}]
)
]);
@ -154,7 +159,8 @@ define([
duration: '20s',
message: null,
id: 'fuel_health.tests.check_disk',
description: 'Target component: Nova Scenario: 1. Check outage on controller and compute nodes'
description: 'Target component: Nova ' +
'Scenario: 1. Check outage on controller and compute nodes'
},
{
status: 'stopped',
@ -176,7 +182,8 @@ define([
duration: '30s.',
message: 'Fast fail with message',
id: 'fuel_health.tests.general',
description: 'Target component: Logging Scenario: 1. Check logrotate cron job on all controller and compute nodes'
description: 'Target component: Logging ' +
'Scenario: 1. Check logrotate cron job on all controller and compute nodes'
},
{
status: 'wait_running',
@ -187,7 +194,8 @@ define([
duration: '1sec',
message: 'failure text message',
id: 'fuel_health.tests.credentials',
description: 'Target component: Configuration Scenario: 1. Check user can not ssh on master node with default credentials. '
description: 'Target component: Configuration ' +
'Scenario: 1. Check user can not ssh on master node with default credentials.'
},
{
status: 'running',
@ -198,7 +206,8 @@ define([
duration: '',
message: 'Error message',
id: 'fuel_health.tests.credentials_change',
description: ' Target component: Configuration Scenario: 1. Check if default credentials for OpenStack cluster have changed. '
description: ' Target component: Configuration ' +
'Scenario: 1. Check if default credentials for OpenStack cluster have changed.'
},
{
status: 'wait_running',
@ -220,7 +229,8 @@ define([
duration: '',
message: null,
id: 'fuel_health.tests.credentials_erros',
description: 'Target component: Configuration Scenario: 1. Check if error credentials for OpenStack cluster not suit.'
description: 'Target component: Configuration ' +
'Scenario: 1. Check if error credentials for OpenStack cluster not suit.'
}]
)
]);
@ -244,7 +254,8 @@ define([
duration: '20s',
message: null,
id: 'fuel_health.tests.check_disk',
description: 'Target component: Nova Scenario: 1. Check outage on controller and compute nodes'
description: 'Target component: Nova ' +
'Scenario: 1. Check outage on controller and compute nodes'
},
{
status: 'stopped',
@ -266,7 +277,8 @@ define([
duration: '30s.',
message: 'Fast fail with message',
id: 'fuel_health.tests.general',
description: 'Target component: Logging Scenario: 1. Check logrotate cron job on all controller and compute nodes'
description: 'Target component: Logging ' +
'Scenario: 1. Check logrotate cron job on all controller and compute nodes'
},
{
status: 'wait_running',
@ -277,7 +289,8 @@ define([
duration: '1sec',
message: 'failure text message',
id: 'fuel_health.tests.credentials',
description: 'Target component: Configuration Scenario: 1. Check user can not ssh on master node with default credentials. '
description: 'Target component: Configuration ' +
'Scenario: 1. Check user can not ssh on master node with default credentials.'
},
{
status: 'running',
@ -288,7 +301,9 @@ define([
duration: '',
message: 'Error message',
id: 'fuel_health.tests.credentials_change',
description: ' Target component: Configuration Scenario: 1. Check if default credentials for OpenStack cluster have changed. '
description: ' Target component: Configuration ' +
'Scenario: ' +
'1. Check if default credentials for OpenStack cluster have changed.'
},
{
status: 'wait_running',
@ -310,7 +325,8 @@ define([
duration: '',
message: null,
id: 'fuel_health.tests.credentials_erros',
description: 'Target component: Configuration Scenario: 1. Check if error credentials for OpenStack cluster not suit.'
description: 'Target component: Configuration ' +
'Scenario: 1. Check if error credentials for OpenStack cluster not suit.'
}
]
}]
@ -345,7 +361,8 @@ define([
duration: '20s',
message: null,
id: 'fuel_health.tests.check_disk',
description: 'Target component: Nova Scenario: 1. Check outage on controller and compute nodes'
description: 'Target component: Nova ' +
'Scenario: 1. Check outage on controller and compute nodes'
},
{
status: 'stopped',
@ -367,7 +384,9 @@ define([
duration: '30s.',
message: 'Fast fail with message',
id: 'fuel_health.tests.general',
description: 'Target component: Logging Scenario: 1. Check logrotate cron job on all controller and compute nodes'
description: 'Target component: Logging ' +
'Scenario: ' +
'1. Check logrotate cron job on all controller and compute nodes'
},
{
status: 'skipped',
@ -378,7 +397,8 @@ define([
duration: '1sec',
message: 'failure text message',
id: 'fuel_health.tests.credentials',
description: 'Target component: Configuration Scenario: 1. Check user can not ssh on master node with default credentials. '
description: 'Target component: Configuration ' +
'Scenario: 1. Check user can not ssh on master node with default credentials.'
},
{
status: 'success',
@ -389,7 +409,9 @@ define([
duration: '',
message: 'Error message',
id: 'fuel_health.tests.credentials_change',
description: ' Target component: Configuration Scenario: 1. Check if default credentials for OpenStack cluster have changed. '
description: ' Target component: Configuration ' +
'Scenario: ' +
'1. Check if default credentials for OpenStack cluster have changed. '
},
{
status: 'failure',
@ -411,7 +433,8 @@ define([
duration: '',
message: null,
id: 'fuel_health.tests.credentials_erros',
description: 'Target component: Configuration Scenario: 1. Check if error credentials for OpenStack cluster not suit.'
description: 'Target component: Configuration ' +
'Scenario: 1. Check if error credentials for OpenStack cluster not suit.'
}
])
]);
@ -435,7 +458,8 @@ define([
duration: '20s',
message: null,
id: 'fuel_health.tests.check_disk',
description: 'Target component: Nova Scenario: 1. Check outage on controller and compute nodes'
description: 'Target component: Nova ' +
'Scenario: 1. Check outage on controller and compute nodes'
},
{
status: 'stopped',
@ -457,7 +481,9 @@ define([
duration: '30s.',
message: 'Fast fail with message',
id: 'fuel_health.tests.general',
description: 'Target component: Logging Scenario: 1. Check logrotate cron job on all controller and compute nodes'
description: 'Target component: Logging ' +
'Scenario: ' +
'1. Check logrotate cron job on all controller and compute nodes'
},
{
status: 'skipped',
@ -468,7 +494,9 @@ define([
duration: '1sec',
message: 'failure text message',
id: 'fuel_health.tests.credentials',
description: 'Target component: Configuration Scenario: 1. Check user can not ssh on master node with default credentials. '
description: 'Target component: Configuration ' +
'Scenario: ' +
'1. Check user can not ssh on master node with default credentials. '
},
{
status: 'success',
@ -479,7 +507,9 @@ define([
duration: '',
message: 'Error message',
id: 'fuel_health.tests.credentials_change',
description: ' Target component: Configuration Scenario: 1. Check if default credentials for OpenStack cluster have changed. '
description: ' Target component: Configuration ' +
'Scenario: ' +
'1. Check if default credentials for OpenStack cluster have changed. '
},
{
status: 'failure',
@ -501,7 +531,8 @@ define([
duration: '',
message: null,
id: 'fuel_health.tests.credentials_erros',
description: 'Target component: Configuration Scenario: 1. Check if error credentials for OpenStack cluster not suit.'
description: 'Target component: Configuration ' +
'Scenario: 1. Check if error credentials for OpenStack cluster not suit.'
}
]
}]

View File

@ -133,7 +133,8 @@ define([
return bondElement
.findAllByCssSelector('.ifc-name')
.then(function(ifcNamesElements) {
assert.equal(ifcNamesElements.length, ifcsNames.length, 'Unexpected number of interfaces in bond');
assert.equal(ifcNamesElements.length, ifcsNames.length,
'Unexpected number of interfaces in bond');
return ifcNamesElements.forEach(
function(ifcNameElement) {

View File

@ -35,7 +35,8 @@ define([
},
checkTitle: function(expectedTitle) {
return this.remote
.assertElementContainsText(this.modalSelector + ' h4.modal-title', expectedTitle, 'Unexpected modal window title');
.assertElementContainsText(this.modalSelector + ' h4.modal-title', expectedTitle,
'Unexpected modal window title');
},
close: function() {
var self = this;

View File

@ -86,8 +86,10 @@ define([
.then(function() {
return dashboardPage.setClusterName(initialName);
})
.assertElementAppears('.rename-block.has-error', 1000, 'Error style for duplicate name is applied')
.assertElementTextEquals('.rename-block .text-danger', 'Environment with this name already exists',
.assertElementAppears('.rename-block.has-error', 1000,
'Error style for duplicate name is applied')
.assertElementTextEquals('.rename-block .text-danger',
'Environment with this name already exists',
'Duplicate name error text appears'
)
.findByCssSelector(renameInputSelector)
@ -129,7 +131,8 @@ define([
return clusterPage.goToTab('Dashboard');
})
.assertElementContainsText('.warnings-block',
'Please verify your network settings before deployment', 'Network verification warning is shown')
'Please verify your network settings before deployment',
'Network verification warning is shown')
.then(function() {
return dashboardPage.discardChanges();
});
@ -143,7 +146,8 @@ define([
.then(function() {
return clusterPage.goToTab('Dashboard');
})
.assertElementDisabled(dashboardPage.deployButtonSelector, 'No deployment should be possible without controller nodes added')
.assertElementDisabled(dashboardPage.deployButtonSelector,
'No deployment should be possible without controller nodes added')
.assertElementExists('div.instruction.invalid', 'Invalid configuration message is shown')
.assertElementContainsText('.environment-alerts ul.text-danger li',
'At least 1 Controller nodes are required (0 selected currently).',
@ -178,7 +182,8 @@ define([
var operatingSystemNodes = 1;
var virtualNodes = 1;
var valueSelector = '.statistics-block .cluster-info-value';
var total = controllerNodes + storageCinderNodes + computeNodes + operatingSystemNodes + virtualNodes;
var total = controllerNodes + storageCinderNodes + computeNodes + operatingSystemNodes +
virtualNodes;
return this.remote
.then(function() {
return common.addNodesToCluster(controllerNodes, ['Controller']);
@ -201,11 +206,13 @@ define([
.assertElementTextEquals(valueSelector + '.total', total,
'The number of Total nodes in statistics is updated according to added nodes')
.assertElementTextEquals(valueSelector + '.controller', controllerNodes,
'The number of controllerNodes nodes in statistics is updated according to added nodes')
'The number of controllerNodes nodes in statistics is updated according to ' +
'added nodes')
.assertElementTextEquals(valueSelector + '.compute', computeNodes,
'The number of Compute nodes in statistics is updated according to added nodes')
.assertElementTextEquals(valueSelector + '.base-os', operatingSystemNodes,
'The number of Operating Systems nodes in statistics is updated according to added nodes')
'The number of Operating Systems nodes in statistics is updated according to ' +
'added nodes')
.assertElementTextEquals(valueSelector + '.virt', virtualNodes,
'The number of Virtual nodes in statistics is updated according to added nodes')
.assertElementTextEquals(valueSelector + '.offline', 1,
@ -213,7 +220,8 @@ define([
.assertElementTextEquals(valueSelector + '.error', 1,
'The number of Error nodes in statistics is updated according to added nodes')
.assertElementTextEquals(valueSelector + '.pending_addition', total,
'The number of Pending Addition nodes in statistics is updated according to added nodes')
'The number of Pending Addition nodes in statistics is updated according to ' +
'added nodes')
.then(function() {
return dashboardPage.discardChanges();
});
@ -232,8 +240,10 @@ define([
.then(function() {
return dashboardPage.startDeployment();
})
.assertElementDisappears('.dashboard-block .progress', 60000, 'Progress bar disappears after deployment')
.assertElementAppears('.dashboard-tab .alert strong', 1000, 'Error message is shown when adding error node')
.assertElementDisappears('.dashboard-block .progress', 60000,
'Progress bar disappears after deployment')
.assertElementAppears('.dashboard-tab .alert strong', 1000,
'Error message is shown when adding error node')
.assertElementTextEquals('.dashboard-tab .alert strong', 'Error',
'Deployment failed in case of adding offline nodes')
.then(function() {

View File

@ -58,7 +58,8 @@ define([
},
'No deployment button when there are no nodes added': function() {
return this.remote
.assertElementNotExists(dashboardPage.deployButtonSelector, 'No deployment should be possible without nodes added');
.assertElementNotExists(dashboardPage.deployButtonSelector,
'No deployment should be possible without nodes added');
},
'Discard changes': function() {
return this.remote
@ -73,14 +74,16 @@ define([
.then(function() {
return modal.waitToOpen();
})
.assertElementContainsText('h4.modal-title', 'Discard Changes', 'Discard Changes confirmation modal expected')
.assertElementContainsText('h4.modal-title', 'Discard Changes',
'Discard Changes confirmation modal expected')
.then(function() {
return modal.clickFooterButton('Discard');
})
.then(function() {
return modal.waitToClose();
})
.assertElementAppears('.dashboard-block a.btn-add-nodes', 2000, 'All changes discarded, add nodes button gets visible in deploy readiness block');
.assertElementAppears('.dashboard-block a.btn-add-nodes', 2000,
'All changes discarded, add nodes button gets visible in deploy readiness block');
},
'Start/stop deployment': function() {
this.timeout = 100000;
@ -96,14 +99,18 @@ define([
return dashboardPage.startDeployment();
})
.assertElementAppears('div.deploy-process div.progress', 2000, 'Deployment started')
.assertElementAppears('button.stop-deployment-btn:not(:disabled)', 5000, 'Stop button appears')
.assertElementAppears('button.stop-deployment-btn:not(:disabled)', 5000,
'Stop button appears')
.then(function() {
return dashboardPage.stopDeployment();
})
.assertElementDisappears('div.deploy-process div.progress', 20000, 'Deployment stopped')
.assertElementAppears(dashboardPage.deployButtonSelector, 1000, 'Deployment button available')
.assertElementContainsText('div.alert-warning strong', 'Success', 'Deployment successfully stopped alert is expected')
.assertElementNotExists('.go-to-healthcheck', 'Healthcheck link is not visible after stopped deploy')
.assertElementAppears(dashboardPage.deployButtonSelector, 1000,
'Deployment button available')
.assertElementContainsText('div.alert-warning strong', 'Success',
'Deployment successfully stopped alert is expected')
.assertElementNotExists('.go-to-healthcheck',
'Healthcheck link is not visible after stopped deploy')
// Reset environment button is available
.then(function() {
return clusterPage.resetEnvironment(clusterName);
@ -134,7 +141,8 @@ define([
.then(function() {
return dashboardPage.startDeployment();
})
.assertElementDisappears('.dashboard-block .progress', 60000, 'Progress bar disappears after deployment')
.assertElementDisappears('.dashboard-block .progress', 60000,
'Progress bar disappears after deployment')
.assertElementAppears('.links-block', 5000, 'Deployment completed')
.assertElementExists('.go-to-healthcheck', 'Healthcheck link is visible after deploy')
.findByLinkText('Horizon')
@ -149,7 +157,8 @@ define([
.then(function(isLocked) {
assert.isTrue(isLocked, 'Networks tab should turn locked after deployment');
})
.assertElementEnabled('.add-nodegroup-btn', 'Add Node network group button is enabled after cluster deploy')
.assertElementEnabled('.add-nodegroup-btn',
'Add Node network group button is enabled after cluster deploy')
.then(function() {
return clusterPage.isTabLocked('Settings');
})

View File

@ -64,11 +64,16 @@ define([
return clusterPage.goToTab('Health Check');
})
.assertElementsAppear('.healthcheck-table', 5000, 'Healthcheck tables are rendered')
.assertElementDisabled('.custom-tumbler input[type=checkbox]', 'Test checkbox is disabled')
.assertElementContainsText('.alert-warning', 'Before you can test an OpenStack environment, you must deploy the OpenStack environment',
.assertElementDisabled('.custom-tumbler input[type=checkbox]',
'Test checkbox is disabled')
.assertElementContainsText('.alert-warning',
'Before you can test an OpenStack environment, ' +
'you must deploy the OpenStack environment',
'Warning to deploy cluster is shown')
.assertElementNotExists('.run-tests-btn', 'Run tests button is not shown in new OpenStack environment')
.assertElementNotExists('.stop-tests-btn', 'Stop tests button is not shown in new OpenStack environment');
.assertElementNotExists('.run-tests-btn',
'Run tests button is not shown in new OpenStack environment')
.assertElementNotExists('.stop-tests-btn',
'Stop tests button is not shown in new OpenStack environment');
},
//@TODO (morale): imitate tests stop
'Check Healthcheck tab manipulations after deploy': function() {
@ -86,9 +91,11 @@ define([
.then(function() {
return clusterPage.goToTab('Health Check');
})
.assertElementEnabled('.custom-tumbler input[type=checkbox]', 'Test checkbox is enabled after deploy')
.assertElementEnabled('.custom-tumbler input[type=checkbox]',
'Test checkbox is enabled after deploy')
// 'run tests' button interactions
.assertElementDisabled('.run-tests-btn', 'Run tests button is disabled if no tests checked')
.assertElementDisabled('.run-tests-btn',
'Run tests button is disabled if no tests checked')
.assertElementNotExists('.stop-tests-btn',
'Stop tests button is not visible if no tests checked')
.assertElementExists('.toggle-credentials', 'Toggle credentials button is visible')
@ -120,7 +127,8 @@ define([
.assertElementNotExists('.run-tests-btn',
'Run tests button is not shown if tests are running')
.assertElementEnabled('.stop-tests-btn', 'Stop tests button is enabled during tests run')
.assertElementsAppear('.glyphicon-refresh.animate-spin', 1000, 'Running status is reflected')
.assertElementsAppear('.glyphicon-refresh.animate-spin', 1000,
'Running status is reflected')
.assertElementsAppear('.glyphicon-time', 1000, 'Waiting to run status is reflected')
.assertElementsAppear('.healthcheck-status-skipped', 1000, 'Skipped status is reflected')
.assertElementsAppear('.healthcheck-status-stopped', 1000, 'Stopped status is reflected');
@ -140,9 +148,11 @@ define([
.then(function() {
return clusterPage.goToTab('Health Check');
})
.assertElementNotExists('.stop-tests-btn', 'Stop tests button is not shown if tests are finished')
.assertElementNotExists('.stop-tests-btn',
'Stop tests button is not shown if tests are finished')
.assertElementsAppear('.glyphicon-ok', 1000, 'Success status is reflected')
.assertElementsAppear('.glyphicon-remove', 1000, 'Error and Failure statuses are reflected');
.assertElementsAppear('.glyphicon-remove', 1000,
'Error and Failure statuses are reflected');
}
};
});

View File

@ -50,13 +50,16 @@ define([
'"Show" button availability and logs displaying': function() {
var showLogsButtonSelector = '.sticker button';
return this.remote
.assertElementsExist('.sticker select[name=source] > option', 'Check if "Source" dropdown exist')
.assertElementDisabled(showLogsButtonSelector, '"Show" button is disabled until source change')
.assertElementsExist('.sticker select[name=source] > option',
'Check if "Source" dropdown exist')
.assertElementDisabled(showLogsButtonSelector,
'"Show" button is disabled until source change')
// Change the selected value for the "Source" dropdown to Rest API
.clickByCssSelector('.sticker select[name=source] option[value=api]')
// Change the selected value for the "Level" dropdown to DEBUG
.clickByCssSelector('.sticker select[name=level] option[value=DEBUG]')
.assertElementEnabled(showLogsButtonSelector, '"Show" button is enabled after source change')
.assertElementEnabled(showLogsButtonSelector,
'"Show" button is enabled after source change')
.execute(function() {
window.fakeServer = sinon.fakeServer.create();
window.fakeServer.autoRespond = true;
@ -70,7 +73,8 @@ define([
]);
})
.clickByCssSelector(showLogsButtonSelector)
.assertElementDisappears('.logs-tab div.progress', 5000, 'Wait till Progress bar disappears')
.assertElementDisappears('.logs-tab div.progress', 5000,
'Wait till Progress bar disappears')
.assertElementsAppear('.log-entries > tbody > tr', 5000, 'Log entries are shown')
.execute(function() {
window.fakeServer.restore();

View File

@ -100,10 +100,13 @@ define([
return this.remote
.clickByCssSelector('.subtab-link-default')
.assertElementAppears('.storage', 2000, 'Storage network is shown')
.assertElementSelected('.storage .cidr input[type=checkbox]', 'Storage network has "cidr" notation by default')
.assertElementNotExists('.storage .ip_ranges input[type=text]:not(:disabled)', 'It is impossible to configure IP ranges for network with "cidr" notation')
.assertElementSelected('.storage .cidr input[type=checkbox]',
'Storage network has "cidr" notation by default')
.assertElementNotExists('.storage .ip_ranges input[type=text]:not(:disabled)',
'It is impossible to configure IP ranges for network with "cidr" notation')
.clickByCssSelector('.storage .cidr input[type=checkbox]')
.assertElementNotExists('.storage .ip_ranges input[type=text]:disabled', 'Network notation was changed to "ip_ranges"');
.assertElementNotExists('.storage .ip_ranges input[type=text]:disabled',
'Network notation was changed to "ip_ranges"');
},
'Testing cluster networks: save network changes': function() {
var cidrElementSelector = '.storage .cidr input[type=text]';
@ -112,12 +115,14 @@ define([
.clickByCssSelector(applyButtonSelector)
.assertElementsAppear('input:not(:disabled)', 2000, 'Inputs are not disabled')
.assertElementNotExists('.alert-error', 'Correct settings were saved successfully')
.assertElementDisabled(applyButtonSelector, 'Save changes button is disabled again after successful settings saving');
.assertElementDisabled(applyButtonSelector,
'Save changes button is disabled again after successful settings saving');
},
'Testing cluster networks: verification': function() {
return this.remote
.clickByCssSelector('.subtab-link-network_verification')
.assertElementDisabled('.verify-networks-btn', 'Verification button is disabled in case of no nodes')
.assertElementDisabled('.verify-networks-btn',
'Verification button is disabled in case of no nodes')
.assertElementTextEquals('.alert-warning',
'At least two online nodes are required to verify environment network configuration',
'Not enough nodes warning is shown')
@ -133,17 +138,20 @@ define([
.clickByCssSelector('.subtab-link-network_verification')
.clickByCssSelector('.verify-networks-btn')
.assertElementAppears('.alert-danger.network-alert', 4000, 'Verification error is shown')
.assertElementAppears('.alert-danger.network-alert', 'Address intersection', 'Verification result is shown in case of address intersection')
.assertElementAppears('.alert-danger.network-alert', 'Address intersection',
'Verification result is shown in case of address intersection')
// Testing cluster networks: verification task deletion
.clickByCssSelector('.subtab-link-default')
.setInputValue('.public input[name=gateway]', '172.16.0.5')
.clickByCssSelector('.subtab-link-network_verification')
.assertElementNotExists('.page-control-box .alert', 'Verification task was removed after settings has been changed')
.assertElementNotExists('.page-control-box .alert',
'Verification task was removed after settings has been changed')
.clickByCssSelector('.btn-revert-changes')
.clickByCssSelector('.verify-networks-btn')
.waitForElementDeletion('.animation-box .success.connect-1', 6000)
.assertElementAppears('.alert-success', 6000, 'Success verification message appears')
.assertElementContainsText('.alert-success', 'Verification succeeded', 'Success verification message appears with proper text')
.assertElementContainsText('.alert-success', 'Verification succeeded',
'Success verification message appears with proper text')
.clickByCssSelector('.btn-revert-changes')
.then(function() {
return clusterPage.goToTab('Dashboard');
@ -185,7 +193,8 @@ define([
var rangeSelector = '.public .ip_ranges ';
return this.remote
.clickByCssSelector(rangeSelector + '.ip-ranges-add')
.assertElementsExist(rangeSelector + '.ip-ranges-delete', 2, 'Remove ranges controls appear')
.assertElementsExist(rangeSelector + '.ip-ranges-delete', 2,
'Remove ranges controls appear')
.clickByCssSelector(applyButtonSelector)
.assertElementsExist(rangeSelector + '.range-row',
'Empty range row is removed after saving changes')
@ -216,7 +225,8 @@ define([
.then(function() {
return clusterPage.goToTab('Networks');
})
.assertElementNotExists('.private', 'Private Network is not visible for vlan segmentation type')
.assertElementNotExists('.private',
'Private Network is not visible for vlan segmentation type')
.assertElementTextEquals('.segmentation-type', '(Neutron with VLAN segmentation)',
'Segmentation type is correct for VLAN segmentation');
},
@ -277,7 +287,8 @@ define([
.then(function() {
return modal.waitToOpen();
})
.assertElementContainsText('h4.modal-title', 'Add New Node Network Group', 'Add New Node Network Group modal expected')
.assertElementContainsText('h4.modal-title', 'Add New Node Network Group',
'Add New Node Network Group modal expected')
.setInputValue('[name=node-network-group-name]', 'Node_Network_Group_1')
.then(function() {
return modal.clickFooterButton('Add Group');
@ -285,9 +296,11 @@ define([
.then(function() {
return modal.waitToClose();
})
.assertElementAppears('.node-network-groups-list', 2000, 'Node network groups title appears')
.assertElementAppears('.node-network-groups-list', 2000,
'Node network groups title appears')
.assertElementDisplayed('.subtab-link-Node_Network_Group_1', 'New subtab is shown')
.assertElementTextEquals('.network-group-name .btn-link', 'Node_Network_Group_1', 'New Node Network group title is shown');
.assertElementTextEquals('.network-group-name .btn-link', 'Node_Network_Group_1',
'New Node Network group title is shown');
},
'Verification is disabled for multirack': function() {
return this.remote
@ -306,26 +319,30 @@ define([
// Enter
.type('\uE007')
.end()
.assertElementDisplayed('.subtab-link-Node_Network_Group_2', 'Node network group was successfully renamed');
.assertElementDisplayed('.subtab-link-Node_Network_Group_2',
'Node network group was successfully renamed');
},
'Node network group deletion': function() {
return this.remote
.clickByCssSelector('.subtab-link-default')
.assertElementNotExists('.glyphicon-remove', 'It is not possible to delete default node network group')
.assertElementNotExists('.glyphicon-remove',
'It is not possible to delete default node network group')
.clickByCssSelector('.subtab-link-Node_Network_Group_2')
.assertElementAppears('.glyphicon-remove', 1000, 'Remove icon is shown')
.clickByCssSelector('.glyphicon-remove')
.then(function() {
return modal.waitToOpen();
})
.assertElementContainsText('h4.modal-title', 'Remove Node Network Group', 'Remove Node Network Group modal expected')
.assertElementContainsText('h4.modal-title', 'Remove Node Network Group',
'Remove Node Network Group modal expected')
.then(function() {
return modal.clickFooterButton('Delete');
})
.then(function() {
return modal.waitToClose();
})
.assertElementDisappears('.subtab-link-Node_Network_Group_2', 2000, 'Node network groups title disappears');
.assertElementDisappears('.subtab-link-Node_Network_Group_2', 2000,
'Node network groups title disappears');
},
'Node network group renaming in deployed environment': function() {
this.timeout = 100000;
@ -344,9 +361,11 @@ define([
return clusterPage.goToTab('Networks');
})
.clickByCssSelector('.subtab-link-default')
.assertElementNotExists('.glyphicon-pencil', 'Renaming of a node network group is fobidden in deployed environment')
.assertElementNotExists('.glyphicon-pencil',
'Renaming of a node network group is fobidden in deployed environment')
.clickByCssSelector('.network-group-name .name')
.assertElementNotExists('.network-group-name input[type=text]', 'Renaming is not started on a node network group name click');
.assertElementNotExists('.network-group-name input[type=text]',
'Renaming is not started on a node network group name click');
}
};
});

View File

@ -49,24 +49,31 @@ define([
},
'Add Cluster Nodes': function() {
return this.remote
.assertElementExists('.node-list .alert-warning', 'Node list shows warning if there are no nodes in environment')
.assertElementExists('.node-list .alert-warning',
'Node list shows warning if there are no nodes in environment')
.clickByCssSelector('.btn-add-nodes')
.assertElementsAppear('.node', 2000, 'Unallocated nodes loaded')
.assertElementDisabled(applyButtonSelector, 'Apply button is disabled until both roles and nodes chosen')
.assertElementDisabled('.role-panel [type=checkbox][name=mongo]', 'Unavailable role has locked checkbox')
.assertElementExists('.role-panel .mongo i.tooltip-icon', 'Unavailable role has warning tooltip')
.assertElementDisabled(applyButtonSelector,
'Apply button is disabled until both roles and nodes chosen')
.assertElementDisabled('.role-panel [type=checkbox][name=mongo]',
'Unavailable role has locked checkbox')
.assertElementExists('.role-panel .mongo i.tooltip-icon',
'Unavailable role has warning tooltip')
.then(function() {
return clusterPage.checkNodeRoles(['Controller', 'Storage - Cinder']);
})
.assertElementDisabled('.role-panel [type=checkbox][name=compute]', 'Compute role can not be added together with selected roles')
.assertElementDisabled(applyButtonSelector, 'Apply button is disabled until both roles and nodes chosen')
.assertElementDisabled('.role-panel [type=checkbox][name=compute]',
'Compute role can not be added together with selected roles')
.assertElementDisabled(applyButtonSelector,
'Apply button is disabled until both roles and nodes chosen')
.then(function() {
return clusterPage.checkNodes(nodesAmount);
})
.clickByCssSelector(applyButtonSelector)
.waitForElementDeletion(applyButtonSelector, 2000)
.assertElementAppears('.nodes-group', 2000, 'Cluster node list loaded')
.assertElementsExist('.node-list .node', nodesAmount, nodesAmount + ' nodes were successfully added to the cluster')
.assertElementsExist('.node-list .node', nodesAmount, nodesAmount +
' nodes were successfully added to the cluster')
.assertElementExists('.nodes-group', 'One node group is present');
},
'Edit cluster node roles': function() {
@ -78,17 +85,22 @@ define([
// select all nodes
.clickByCssSelector('.select-all label')
.clickByCssSelector('.btn-edit-roles')
.assertElementDisappears('.btn-edit-roles', 2000, 'Cluster nodes screen unmounted')
.assertElementNotExists('.node-box [type=checkbox]:not(:disabled)', 'Node selection is locked on Edit Roles screen')
.assertElementNotExists('[name=select-all]:not(:disabled)', 'Select All checkboxes are locked on Edit Roles screen')
.assertElementExists('.role-panel [type=checkbox][name=controller]:indeterminate', 'Controller role checkbox has indeterminate state')
.assertElementDisappears('.btn-edit-roles', 2000,
'Cluster nodes screen unmounted')
.assertElementNotExists('.node-box [type=checkbox]:not(:disabled)',
'Node selection is locked on Edit Roles screen')
.assertElementNotExists('[name=select-all]:not(:disabled)',
'Select All checkboxes are locked on Edit Roles screen')
.assertElementExists('.role-panel [type=checkbox][name=controller]:indeterminate',
'Controller role checkbox has indeterminate state')
.then(function() {
// uncheck Cinder role
return clusterPage.checkNodeRoles(['Storage - Cinder', 'Storage - Cinder']);
})
.clickByCssSelector(applyButtonSelector)
.assertElementDisappears('.btn-apply', 2000, 'Role editing screen unmounted')
.assertElementsExist('.node-list .node', nodesAmount, 'One node was removed from cluster after editing roles');
.assertElementsExist('.node-list .node', nodesAmount,
'One node was removed from cluster after editing roles');
},
'Remove Cluster': function() {
return this.remote

View File

@ -56,7 +56,8 @@ define([
},
'Settings tab is rendered correctly': function() {
return this.remote
.assertElementNotExists('.nav .subtab-link-network', 'Subtab for Network settings is not presented in navigation')
.assertElementNotExists('.nav .subtab-link-network',
'Subtab for Network settings is not presented in navigation')
.assertElementEnabled('.btn-load-defaults', 'Load defaults button is enabled')
.assertElementDisabled('.btn-revert-changes', 'Cancel Changes button is disabled')
.assertElementDisabled('.btn-apply-changes', 'Save Settings button is disabled');
@ -65,10 +66,12 @@ define([
return this.remote
// introduce change
.clickByCssSelector('input[type=checkbox]')
.assertElementAppears('.btn-apply-changes:not(:disabled)', 200, 'Save Settings button is enabled if there are changes')
.assertElementAppears('.btn-apply-changes:not(:disabled)', 200,
'Save Settings button is enabled if there are changes')
// reset the change
.clickByCssSelector('input[type=checkbox]')
.assertElementAppears('.btn-apply-changes:disabled', 200, 'Save Settings button is disabled if there are no changes');
.assertElementAppears('.btn-apply-changes:disabled', 200,
'Save Settings button is disabled if there are no changes');
},
'Check Cancel Changes button': function() {
return this.remote
@ -86,7 +89,8 @@ define([
})
// reset changes
.clickByCssSelector('.btn-revert-changes')
.assertElementDisabled('.btn-apply-changes', 'Save Settings button is disabled after changes were cancelled');
.assertElementDisabled('.btn-apply-changes',
'Save Settings button is disabled after changes were cancelled');
},
'Check changes saving': function() {
return this.remote
@ -97,7 +101,8 @@ define([
.then(function() {
return settingsPage.waitForRequestCompleted();
})
.assertElementDisabled('.btn-revert-changes', 'Cancel Changes button is disabled after changes were saved successfully');
.assertElementDisabled('.btn-revert-changes',
'Cancel Changes button is disabled after changes were saved successfully');
},
'Check loading of defaults': function() {
return this.remote
@ -106,12 +111,15 @@ define([
.then(function() {
return settingsPage.waitForRequestCompleted();
})
.assertElementEnabled('.btn-apply-changes', 'Save Settings button is enabled after defaults were loaded')
.assertElementEnabled('.btn-revert-changes', 'Cancel Changes button is enabled after defaults were loaded')
.assertElementEnabled('.btn-apply-changes',
'Save Settings button is enabled after defaults were loaded')
.assertElementEnabled('.btn-revert-changes',
'Cancel Changes button is enabled after defaults were loaded')
// revert the change
.clickByCssSelector('.btn-revert-changes');
},
'The choice of subgroup is preserved when user navigates through the cluster tabs': function() {
'The choice of subgroup is preserved when user navigates through the cluster tabs':
function() {
return this.remote
.clickLinkByText('Logging')
.then(function() {
@ -120,20 +128,26 @@ define([
.then(function() {
return clusterPage.goToTab('Settings');
})
.assertElementExists('.nav-pills li.active a.subtab-link-logging', 'The choice of subgroup is preserved when user navigates through the cluster tabs');
.assertElementExists('.nav-pills li.active a.subtab-link-logging',
'The choice of subgroup is preserved when user navigates through the cluster tabs');
},
'The page reacts on invalid input': function() {
return this.remote
.clickLinkByText('General')
// "nova" is forbidden username
.setInputValue('[type=text][name=user]', 'nova')
.assertElementAppears('.setting-section .form-group.has-error', 200, 'Invalid field marked as error')
.assertElementExists('.settings-tab .nav-pills > li.active i.glyphicon-danger-sign', 'Subgroup with invalid field marked as invalid')
.assertElementDisabled('.btn-apply-changes', 'Save Settings button is disabled in case of validation error')
.assertElementAppears('.setting-section .form-group.has-error', 200,
'Invalid field marked as error')
.assertElementExists('.settings-tab .nav-pills > li.active i.glyphicon-danger-sign',
'Subgroup with invalid field marked as invalid')
.assertElementDisabled('.btn-apply-changes',
'Save Settings button is disabled in case of validation error')
// revert the change
.clickByCssSelector('.btn-revert-changes')
.assertElementNotExists('.setting-section .form-group.has-error', 'Validation error is cleared after resetting changes')
.assertElementNotExists('.settings-tab .nav-pills > li.active i.glyphicon-danger-sign', 'Subgroup menu has default layout after resetting changes');
.assertElementNotExists('.setting-section .form-group.has-error',
'Validation error is cleared after resetting changes')
.assertElementNotExists('.settings-tab .nav-pills > li.active i.glyphicon-danger-sign',
'Subgroup menu has default layout after resetting changes');
},
'Test repositories custom control': function() {
var repoAmount;
@ -146,18 +160,22 @@ define([
repoAmount = elements.length;
})
.end()
.assertElementNotExists('.repos .form-inline:nth-of-type(1) .btn-link', 'The first repo can not be deleted')
.assertElementNotExists('.repos .form-inline:nth-of-type(1) .btn-link',
'The first repo can not be deleted')
// delete some repo
.clickByCssSelector('.repos .form-inline .btn-link')
.then(function() {
return self.remote.assertElementsExist('.repos .form-inline', repoAmount - 1, 'Repo was deleted');
return self.remote.assertElementsExist('.repos .form-inline', repoAmount - 1,
'Repo was deleted');
})
// add new repo
.clickByCssSelector('.btn-add-repo')
.then(function() {
return self.remote.assertElementsExist('.repos .form-inline', repoAmount, 'New repo placeholder was added');
return self.remote.assertElementsExist('.repos .form-inline', repoAmount,
'New repo placeholder was added');
})
.assertElementExists('.repos .form-inline .repo-name.has-error', 'Empty repo marked as invalid')
.assertElementExists('.repos .form-inline .repo-name.has-error',
'Empty repo marked as invalid')
// revert the change
.clickByCssSelector('.btn-revert-changes');
}

View File

@ -69,18 +69,25 @@ define([
'Check action buttons': function() {
return this.remote
.assertElementNotExists('.node .btn-discard', 'No discard changes button on a node')
.assertElementExists('.node.offline .node-remove-button', 'Removing of offline nodes is available on the page')
.assertElementExists('.node.offline .node-remove-button',
'Removing of offline nodes is available on the page')
.clickByCssSelector('.node.pending_addition > label')
.assertElementNotExists('.control-buttons-box .btn', 'No management buttons for selected node')
.assertElementExists('.node-list-management-buttons .btn-labels:not(:disabled)', 'Nodes can be labelled on the page')
.assertElementsExist('.node.pending_addition .btn-view-logs', 4, 'View logs button is presented for assigned to any environment nodes')
.assertElementNotExists('.node:not(.pending_addition) .btn-view-logs', 'View logs button is not presented for unallocated nodes')
.assertElementNotExists('.control-buttons-box .btn',
'No management buttons for selected node')
.assertElementExists('.node-list-management-buttons .btn-labels:not(:disabled)',
'Nodes can be labelled on the page')
.assertElementsExist('.node.pending_addition .btn-view-logs', 4,
'View logs button is presented for assigned to any environment nodes')
.assertElementNotExists('.node:not(.pending_addition) .btn-view-logs',
'View logs button is not presented for unallocated nodes')
.clickByCssSelector('.node .node-settings')
.then(function() {
return modal.waitToOpen();
})
.assertElementNotExists('.btn-edit-disks', 'No disks configuration buttons in node pop-up')
.assertElementNotExists('.btn-edit-networks', 'No interfaces configuration buttons in node pop-up')
.assertElementNotExists('.btn-edit-disks',
'No disks configuration buttons in node pop-up')
.assertElementNotExists('.btn-edit-networks',
'No interfaces configuration buttons in node pop-up')
.then(function() {
return modal.close();
})
@ -88,7 +95,8 @@ define([
.then(function() {
return node.openCompactNodeExtendedView();
})
.assertElementNotExists('.node-popover .node-buttons .btn:not(.btn-view-logs)', 'No action buttons in node extended view in compact mode');
.assertElementNotExists('.node-popover .node-buttons .btn:not(.btn-view-logs)',
'No action buttons in node extended view in compact mode');
}
};
});

View File

@ -42,14 +42,16 @@ define([
.then(function() {
return loginPage.login('login', '*****');
})
.assertElementAppears('div.login-error', 1000, 'Error message is expected to get displayed');
.assertElementAppears('div.login-error', 1000,
'Error message is expected to get displayed');
},
'Login with proper credentials': function() {
return this.remote
.then(function() {
return loginPage.login();
})
.assertElementDisappears('.login-btn', 2000, 'Login button disappears after successful login');
.assertElementDisappears('.login-btn', 2000,
'Login button disappears after successful login');
}
};
});

View File

@ -88,9 +88,13 @@ define([
});
})
.end()
.assertElementExists(sdaDisk + ' .disk-visual [data-volume=image] .close-btn', 'Button Close for Image Storage volume is present')
.assertElementNotExists(sdaDisk + ' .disk-visual [data-volume=os] .close-btn', 'Button Close for Base system volume is not present')
.assertElementExists(sdaDisk + ' .disk-details [data-volume=os] .volume-group-notice.text-info', 'Notice about "Minimal size" is present');
.assertElementExists(sdaDisk + ' .disk-visual [data-volume=image] .close-btn',
'Button Close for Image Storage volume is present')
.assertElementNotExists(sdaDisk + ' .disk-visual [data-volume=os] .close-btn',
'Button Close for Base system volume is not present')
.assertElementExists(sdaDisk +
' .disk-details [data-volume=os] .volume-group-notice.text-info',
'Notice about "Minimal size" is present');
},
'Testing Apply and Load Defaults buttons behaviour': function() {
return this.remote
@ -101,7 +105,8 @@ define([
.assertElementDisappears('.btn-load-defaults:disabled', 2000, 'Wait for changes applied')
.clickByCssSelector(loadDefaultsButtonSelector)
.assertElementDisappears('.btn-load-defaults:disabled', 2000, 'Wait for defaults loaded')
.assertElementPropertyEquals(sdaDisk + ' input[type=number][name=image]', 'value', initialImageSize, 'Image Storage size restored to default')
.assertElementPropertyEquals(sdaDisk + ' input[type=number][name=image]', 'value',
initialImageSize, 'Image Storage size restored to default')
.assertElementEnabled(cancelButtonSelector, 'Cancel button is enabled')
.assertElementEnabled(applyButtonSelector, 'Apply button is enabled')
.clickByCssSelector(applyButtonSelector);
@ -113,7 +118,8 @@ define([
.then(function(element) {
return element.getSize()
.then(function(sizes) {
assert.isTrue(sizes.width > 0, 'Expected positive width for Image Storage visual');
assert.isTrue(sizes.width > 0,
'Expected positive width for Image Storage visual');
});
})
.end()
@ -128,18 +134,21 @@ define([
});
})
.end()
.assertElementPropertyEquals(sdaDisk + ' input[type=number][name=image]', 'value', 0, 'Image Storage volume was removed successfully')
.assertElementPropertyEquals(sdaDisk + ' input[type=number][name=image]', 'value', 0,
'Image Storage volume was removed successfully')
.findByCssSelector(sdaDisk + ' .disk-visual [data-volume=unallocated]')
// check that there is unallocated space after Image Storage removal
.then(function(element) {
return element.getSize()
.then(function(sizes) {
assert.isTrue(sizes.width > 0, 'There is unallocated space after Image Storage removal');
assert.isTrue(sizes.width > 0,
'There is unallocated space after Image Storage removal');
});
})
.end()
.clickByCssSelector(cancelButtonSelector)
.assertElementPropertyEquals(sdaDisk + ' input[type=number][name=image]', 'value', initialImageSize, 'Image Storage volume control contains correct value')
.assertElementPropertyEquals(sdaDisk + ' input[type=number][name=image]', 'value',
initialImageSize, 'Image Storage volume control contains correct value')
.assertElementDisabled(applyButtonSelector, 'Apply button is disabled');
},
'Test volume size validation': function() {
@ -148,8 +157,11 @@ define([
.setInputValue(sdaDisk + ' input[type=number][name=image]', '5')
// set Base OS volume size lower than required
.setInputValue(sdaDisk + ' input[type=number][name=os]', '5')
.assertElementExists(sdaDisk + ' .disk-details [data-volume=os] .volume-group-notice.text-danger', 'Validation error exists if volume size is less than required.')
.assertElementDisabled(applyButtonSelector, 'Apply button is disabled in case of validation error')
.assertElementExists(sdaDisk +
' .disk-details [data-volume=os] .volume-group-notice.text-danger',
'Validation error exists if volume size is less than required.')
.assertElementDisabled(applyButtonSelector,
'Apply button is disabled in case of validation error')
.clickByCssSelector(cancelButtonSelector);
}
};

View File

@ -68,7 +68,8 @@ define([
.then(function() {
return interfacesPage.assignNetworkToInterface('Public', 'eth0');
})
.assertElementExists('div.ifc-error', 'Untagged networks can not be assigned to the same interface message should appear');
.assertElementExists('div.ifc-error',
'Untagged networks can not be assigned to the same interface message should appear');
},
'Bond interfaces with different speeds': function() {
return this.remote
@ -78,7 +79,8 @@ define([
.then(function() {
return interfacesPage.selectInterface('eth3');
})
.assertElementExists('div.alert.alert-warning', 'Interfaces with different speeds bonding not recommended message should appear')
.assertElementExists('div.alert.alert-warning',
'Interfaces with different speeds bonding not recommended message should appear')
.assertElementEnabled('.btn-bond', 'Bonding button should still be enabled');
},
'Interfaces bonding': function() {
@ -137,7 +139,9 @@ define([
return interfacesPage.selectInterface('bond1');
})
.assertElementDisabled('.btn-bond', 'Making sure bond button is disabled')
.assertElementContainsText('.alert.alert-warning', ' network interface is already bonded with other network interfaces.', 'Warning message should appear when intended to bond bonds');
.assertElementContainsText('.alert.alert-warning',
' network interface is already bonded with other network interfaces.',
'Warning message should appear when intended to bond bonds');
}
};
});

View File

@ -50,10 +50,14 @@ define([
},
'Test management controls state in new environment': function() {
return this.remote
.assertElementDisabled(searchButtonSelector, 'Search button is locked if there are no nodes in environment')
.assertElementDisabled(sortingButtonSelector, 'Sorting button is locked if there are no nodes in environment')
.assertElementDisabled(filtersButtonSelector, 'Filters button is locked if there are no nodes in environment')
.assertElementNotExists('.active-sorters-filters', 'Applied sorters and filters are not shown for empty environment');
.assertElementDisabled(searchButtonSelector,
'Search button is locked if there are no nodes in environment')
.assertElementDisabled(sortingButtonSelector,
'Sorting button is locked if there are no nodes in environment')
.assertElementDisabled(filtersButtonSelector,
'Filters button is locked if there are no nodes in environment')
.assertElementNotExists('.active-sorters-filters',
'Applied sorters and filters are not shown for empty environment');
},
'Test management controls behaviour': {
setup: function() {
@ -87,12 +91,15 @@ define([
.sleep(300)
.assertElementsExist('.node-list .node', 3, 'Search was successfull')
.clickByCssSelector('.page-title')
.assertElementNotExists(searchButtonSelector, 'Active search control remains open when clicking outside the input')
.assertElementNotExists(searchButtonSelector,
'Active search control remains open when clicking outside the input')
.clickByCssSelector('.node-management-panel .btn-clear-search')
.assertElementsExist('.node-list .node', 4, 'Search was reset')
.assertElementNotExists(searchButtonSelector, 'Search input is still shown after search reset')
.assertElementNotExists(searchButtonSelector,
'Search input is still shown after search reset')
.clickByCssSelector('.node-list')
.assertElementExists(searchButtonSelector, 'Empty search control is closed when clicking outside the input');
.assertElementExists(searchButtonSelector,
'Empty search control is closed when clicking outside the input');
},
'Test node list sorting': function() {
var activeSortersPanelSelector = '.active-sorters';
@ -100,13 +107,18 @@ define([
var firstNodeName;
var self = this;
return this.remote
.assertElementExists(activeSortersPanelSelector, 'Active sorters panel is shown if there are nodes in cluster')
.assertElementNotExists(activeSortersPanelSelector + '.btn-reset-sorting', 'Default sorting can not be reset from active sorters panel')
.assertElementExists(activeSortersPanelSelector,
'Active sorters panel is shown if there are nodes in cluster')
.assertElementNotExists(activeSortersPanelSelector + '.btn-reset-sorting',
'Default sorting can not be reset from active sorters panel')
.clickByCssSelector(sortingButtonSelector)
.assertElementExists('.sorters .sorter-control', 'Cluster node list has one sorting by default')
.assertElementExists('.sorters .sorter-control',
'Cluster node list has one sorting by default')
.assertElementExists('.sorters .sort-by-roles-asc', 'Check default sorting by roles')
.assertElementNotExists('.sorters .sorter-control .btn-remove-sorting', 'Node list should have at least one applied sorting')
.assertElementNotExists('.sorters .btn-reset-sorting', 'Default sorting can not be reset')
.assertElementNotExists('.sorters .sorter-control .btn-remove-sorting',
'Node list should have at least one applied sorting')
.assertElementNotExists('.sorters .btn-reset-sorting',
'Default sorting can not be reset')
.findByCssSelector('.node-list .node-name .name p')
.getVisibleText().then(function(text) {
firstNodeName = text;
@ -115,20 +127,24 @@ define([
.clickByCssSelector('.sorters .sort-by-roles-asc button')
.findByCssSelector('.node-list .node-name .name p')
.getVisibleText().then(function(text) {
assert.notEqual(text, firstNodeName, 'Order of sorting by roles was changed to desc');
assert.notEqual(text, firstNodeName,
'Order of sorting by roles was changed to desc');
})
.end()
.clickByCssSelector('.sorters .sort-by-roles-desc button')
.then(function() {
return self.remote.assertElementTextEquals('.node-list .node-name .name p', firstNodeName, 'Order of sorting by roles was changed to asc (default)');
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', 12, '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
.clickByCssSelector(moreControlSelector + ' .popover [name=manufacturer]')
.assertElementsExist('.nodes-group', 4, 'New sorting was applied and nodes were grouped')
.assertElementsExist('.nodes-group', 4,
'New sorting was applied and nodes were grouped')
// remove sorting by manufacturer
.clickByCssSelector('.sorters .sort-by-manufacturer-asc .btn-remove-sorting')
.assertElementsExist('.nodes-group', 3, 'Particular sorting removal works')
@ -144,15 +160,20 @@ define([
var activeFiltersPanelSelector = '.active-filters';
var moreControlSelector = '.filters .more-control';
return this.remote
.assertElementNotExists(activeFiltersPanelSelector, 'Environment has no active filters by default')
.assertElementNotExists(activeFiltersPanelSelector,
'Environment has no active filters by default')
.clickByCssSelector(filtersButtonSelector)
.assertElementsExist('.filters .filter-control', 2, 'Filters panel has 2 default filters')
.assertElementsExist('.filters .filter-control', 2,
'Filters panel has 2 default filters')
.clickByCssSelector('.filter-by-roles')
.assertElementNotExists('.filter-by-roles [type=checkbox]:checked', 'There are no active options in Roles filter')
.assertElementNotExists('.filters .filter-control .btn-remove-filter', 'Default filters can not be deleted from filters panel')
.assertElementNotExists('.filter-by-roles [type=checkbox]:checked',
'There are no active options in Roles filter')
.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', 8, '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')
@ -160,10 +181,13 @@ define([
.assertElementsExist('.filters .filter-control', 2, 'Particular filter removal works')
.clickByCssSelector(moreControlSelector + ' button')
.clickByCssSelector(moreControlSelector + ' [name=disks_amount]')
.assertElementsExist('.filters .filter-by-disks_amount input[type=number]:not(:disabled)', 2, 'Number filter has 2 fields to set min and max value')
.assertElementsExist('.filters ' +
'.filter-by-disks_amount input[type=number]:not(:disabled)', 2,
'Number filter has 2 fields to set min and max value')
// set min value more than max value
.setInputValue('.filters .filter-by-disks_amount input[type=number][name=start]', '100')
.assertElementsAppear('.filters .filter-by-disks_amount .form-group.has-error', 2000, 'Validation works for Number range filter')
.assertElementsAppear('.filters .filter-by-disks_amount .form-group.has-error', 2000,
'Validation works for Number range filter')
.assertElementNotExists('.node-list .node', 'No nodes match invalid filter')
.clickByCssSelector('.filters .btn-reset-filters')
.assertElementsExist('.node-list .node', 4, 'Node filtration was successfully reset')
@ -173,8 +197,10 @@ define([
.clickByCssSelector('.filters .filter-by-status [name=pending_addition]')
.assertElementsExist('.node-list .node', 4, 'All nodes shown')
.clickByCssSelector(filtersButtonSelector)
.assertElementExists(activeFiltersPanelSelector, 'Applied filter is reflected in active filters panel')
.assertElementExists('.active-filters .btn-reset-filters', 'Reset filters button exists in active filters panel');
.assertElementExists(activeFiltersPanelSelector,
'Applied filter is reflected in active filters panel')
.assertElementExists('.active-filters .btn-reset-filters',
'Reset filters button exists in active filters panel');
}
}
};

View File

@ -57,7 +57,8 @@ define([
.clickByCssSelector('.node input[type=checkbox]')
.assertElementExists('.node.selected', 'Node gets selected upon clicking')
.assertElementExists('button.btn-delete-nodes:not(:disabled)', 'Delete Nodes and ...')
.assertElementExists('button.btn-edit-roles:not(:disabled)', '... Edit Roles buttons appear upon node selection')
.assertElementExists('button.btn-edit-roles:not(:disabled)',
'... Edit Roles buttons appear upon node selection')
.then(function() {
return node.renameNode(nodeNewName);
})
@ -82,9 +83,11 @@ define([
.then(function() {
return node.openNodePopup();
})
.assertElementTextEquals('.modal-header h4.modal-title', nodeNewName, 'Node pop-up has updated node name')
.assertElementTextEquals('.modal-header h4.modal-title', nodeNewName,
'Node pop-up has updated node name')
.assertElementExists('.modal .btn-edit-disks', 'Disks can be configured for cluster node')
.assertElementExists('.modal .btn-edit-networks', 'Interfaces can be configured for cluster node')
.assertElementExists('.modal .btn-edit-networks',
'Interfaces can be configured for cluster node')
.clickByCssSelector('.change-hostname .btn-link')
// change the hostname
.findByCssSelector('.change-hostname [type=text]')
@ -92,8 +95,10 @@ define([
.type(newHostname)
.pressKeys('\uE007')
.end()
.assertElementDisappears('.change-hostname [type=text]', 2000, 'Hostname input disappears after submit')
.assertElementTextEquals('span.node-hostname', newHostname, 'Node hostname has been updated')
.assertElementDisappears('.change-hostname [type=text]', 2000,
'Hostname input disappears after submit')
.assertElementTextEquals('span.node-hostname', newHostname,
'Node hostname has been updated')
.then(function() {
return modal.close();
});
@ -107,8 +112,10 @@ define([
.assertElementExists('i.glyphicon-ok', 'Self node is selectable')
.end()
.clickByCssSelector('.compact-node .node-name p')
.assertElementNotExists('.compact-node .node-name-input', 'Node can not be renamed from compact panel')
.assertElementNotExists('.compact-node .role-list', 'Role list is not shown on node compact panel');
.assertElementNotExists('.compact-node .node-name-input',
'Node can not be renamed from compact panel')
.assertElementNotExists('.compact-node .role-list',
'Role list is not shown on node compact panel');
},
'Compact node extended view': function() {
var newName = 'Node new new name';
@ -117,7 +124,8 @@ define([
return node.openCompactNodeExtendedView();
})
.clickByCssSelector('.node-popover .node-name input[type=checkbox]')
.assertElementExists('.compact-node .node-checkbox i.glyphicon-ok', 'Node compact panel is checked')
.assertElementExists('.compact-node .node-checkbox i.glyphicon-ok',
'Node compact panel is checked')
.then(function() {
return node.openNodePopup(true);
})
@ -131,12 +139,14 @@ define([
})
.findByCssSelector('.node-popover')
.assertElementExists('.role-list', 'Role list is shown in cluster node extended view')
.assertElementExists('.node-buttons', 'Cluster node action buttons are presented in extended view')
.assertElementExists('.node-buttons',
'Cluster node action buttons are presented in extended view')
.end()
.then(function() {
return node.renameNode(newName, true);
})
.assertElementTextEquals('.node-popover .name p', newName, 'Node name has been updated from extended view')
.assertElementTextEquals('.node-popover .name p', newName,
'Node name has been updated from extended view')
.then(function() {
return node.discardNode(true);
})
@ -152,13 +162,17 @@ define([
.then(function() {
return node.openCompactNodeExtendedView();
})
.assertElementNotExists('.node-popover .role-list', 'Unallocated node does not have roles assigned')
.assertElementNotExists('.node-popover .node-buttons .btn', 'There are no action buttons in unallocated node extended view')
.assertElementNotExists('.node-popover .role-list',
'Unallocated node does not have roles assigned')
.assertElementNotExists('.node-popover .node-buttons .btn',
'There are no action buttons in unallocated node extended view')
.then(function() {
return node.openNodePopup(true);
})
.assertElementNotExists('.modal .btn-edit-disks', 'Disks can not be configured for unallocated node')
.assertElementNotExists('.modal .btn-edit-networks', 'Interfaces can not be configured for unallocated node')
.assertElementNotExists('.modal .btn-edit-disks',
'Disks can not be configured for unallocated node')
.assertElementNotExists('.modal .btn-edit-networks',
'Interfaces can not be configured for unallocated node')
.then(function() {
return modal.close();
});

View File

@ -38,14 +38,18 @@ define([
},
'Notification Page': function() {
return this.remote
.assertElementDisplayed('.notifications-icon .badge', 'Badge notification indicator is shown in navigation')
.assertElementDisplayed('.notifications-icon .badge',
'Badge notification indicator is shown in navigation')
// Go to Notification page
.clickByCssSelector('.notifications-icon')
.clickLinkByText('View all')
.assertElementAppears('.notifications-page', 2000, 'Notification page is rendered')
.assertElementExists('.notifications-page .notification', 'There is the start notification on the page')
.assertElementTextEquals('.notification-group .title', 'Today', 'Notification group has "Today" label')
.assertElementNotDisplayed('.notifications-icon .badge', 'Badge notification indicator is hidden');
.assertElementExists('.notifications-page .notification',
'There is the start notification on the page')
.assertElementTextEquals('.notification-group .title', 'Today',
'Notification group has "Today" label')
.assertElementNotDisplayed('.notifications-icon .badge',
'Badge notification indicator is hidden');
},
'Notification badge behaviour': function() {
var clusterName = common.pickRandomName('Test Cluster');
@ -61,9 +65,11 @@ define([
.then(function() {
return common.removeCluster(clusterName);
})
.assertElementAppears('.notifications-icon .badge.visible', 3000, 'New notification appear after the cluster removal')
.assertElementAppears('.notifications-icon .badge.visible', 3000,
'New notification appear after the cluster removal')
.clickByCssSelector('.notifications-icon')
.assertElementAppears('.notifications-popover .notification.clickable', 20000, 'Discovered node notification uploaded')
.assertElementAppears('.notifications-popover .notification.clickable', 20000,
'Discovered node notification uploaded')
// Check if Node Information dialog is shown
.clickByCssSelector('.notifications-popover .notification.clickable p')
.then(function() {

View File

@ -62,9 +62,12 @@ define([
var self = this;
var zabbixInitialVersion, zabbixTextInputValue;
return this.remote
.assertElementEnabled(zabbixSectionSelector + 'h3 input[type=checkbox]', 'Plugin is changeable')
.assertElementNotSelected(zabbixSectionSelector + 'h3 input[type=checkbox]', 'Plugin is not actvated')
.assertElementNotExists(zabbixSectionSelector + '> div input:not(:disabled)', 'Inactive plugin attributes can not be changes')
.assertElementEnabled(zabbixSectionSelector + 'h3 input[type=checkbox]',
'Plugin is changeable')
.assertElementNotSelected(zabbixSectionSelector + 'h3 input[type=checkbox]',
'Plugin is not actvated')
.assertElementNotExists(zabbixSectionSelector + '> div input:not(:disabled)',
'Inactive plugin attributes can not be changes')
// activate plugin
.clickByCssSelector(zabbixSectionSelector + 'h3 input[type=checkbox]')
// save changes
@ -85,9 +88,12 @@ define([
})
.end()
// change plugin version
.clickByCssSelector(zabbixSectionSelector + '.plugin-versions input[type=radio]:not(:checked)')
.assertElementPropertyNotEquals(zabbixSectionSelector + '[name=zabbix_text_1]', 'value', zabbixTextInputValue, 'Plugin version was changed')
.assertElementExists('.subtab-link-other .glyphicon-danger-sign', 'Plugin atributes validation works')
.clickByCssSelector(zabbixSectionSelector +
'.plugin-versions input[type=radio]:not(:checked)')
.assertElementPropertyNotEquals(zabbixSectionSelector + '[name=zabbix_text_1]', 'value',
zabbixTextInputValue, 'Plugin version was changed')
.assertElementExists('.subtab-link-other .glyphicon-danger-sign',
'Plugin atributes validation works')
// fix validation error
.setInputValue(zabbixSectionSelector + '[name=zabbix_text_with_regex]', 'aa-aa')
.waitForElementDeletion('.subtab-link-other .glyphicon-danger-sign', 1000)
@ -95,7 +101,9 @@ define([
// reset plugin version change
.clickByCssSelector('.btn-revert-changes')
.then(function() {
return self.remote.assertElementPropertyEquals(zabbixSectionSelector + '.plugin-versions input[type=radio]:checked', 'value', zabbixInitialVersion, 'Plugin version change can be reset');
return self.remote.assertElementPropertyEquals(zabbixSectionSelector +
'.plugin-versions input[type=radio]:checked', 'value', zabbixInitialVersion,
'Plugin version change can be reset');
});
},
'Check plugin in deployed environment': function() {
@ -124,8 +132,12 @@ define([
.end()
// activate plugin
.clickByCssSelector(zabbixSectionSelector + 'h3 input[type=checkbox]')
.assertElementExists(zabbixSectionSelector + '.plugin-versions input[type=radio]:not(:disabled)', 'Some plugin versions are hotluggable')
.assertElementPropertyNotEquals(zabbixSectionSelector + '.plugin-versions input[type=radio]:checked', 'value', zabbixInitialVersion, 'Plugin hotpluggable version is automatically chosen')
.assertElementExists(zabbixSectionSelector +
'.plugin-versions input[type=radio]:not(:disabled)',
'Some plugin versions are hotluggable')
.assertElementPropertyNotEquals(zabbixSectionSelector +
'.plugin-versions input[type=radio]:checked', 'value', zabbixInitialVersion,
'Plugin hotpluggable version is automatically chosen')
// fix validation error
.setInputValue(zabbixSectionSelector + '[name=zabbix_text_with_regex]', 'aa-aa')
.waitForElementDeletion('.subtab-link-other .glyphicon-danger-sign', 1000)
@ -133,7 +145,9 @@ define([
// deactivate plugin
.clickByCssSelector(zabbixSectionSelector + 'h3 input[type=checkbox]')
.then(function() {
return self.remote.assertElementPropertyEquals(zabbixSectionSelector + '.plugin-versions input[type=radio]:checked', 'value', zabbixInitialVersion, 'Initial plugin version is set for deactivated plugin');
return self.remote.assertElementPropertyEquals(zabbixSectionSelector +
'.plugin-versions input[type=radio]:checked', 'value', zabbixInitialVersion,
'Initial plugin version is set for deactivated plugin');
})
.assertElementDisabled('.btn-apply-changes', 'The change as reset successfully');
},
@ -144,12 +158,16 @@ define([
.clickByCssSelector(loggingSectionSelector + 'h3 input[type=checkbox]')
// activate Zabbix plugin
.clickByCssSelector(zabbixSectionSelector + 'h3 input[type=checkbox]')
.assertElementEnabled(loggingSectionSelector + '[name=logging_text]', 'No conflict with default Zabix plugin version')
.assertElementEnabled(loggingSectionSelector + '[name=logging_text]',
'No conflict with default Zabix plugin version')
// change Zabbix plugin version
.clickByCssSelector(zabbixSectionSelector + '.plugin-versions input[type=radio]:not(:checked)')
.assertElementNotSelected(zabbixSectionSelector + '[name=zabbix_checkbox]', 'Zabbix checkbox is not activated')
.clickByCssSelector(zabbixSectionSelector +
'.plugin-versions input[type=radio]:not(:checked)')
.assertElementNotSelected(zabbixSectionSelector + '[name=zabbix_checkbox]',
'Zabbix checkbox is not activated')
.clickByCssSelector(zabbixSectionSelector + '[name=zabbix_checkbox]')
.assertElementDisabled(loggingSectionSelector + '[name=logging_text]', 'Conflict with Zabbix checkbox')
.assertElementDisabled(loggingSectionSelector + '[name=logging_text]',
'Conflict with Zabbix checkbox')
// reset changes
.clickByCssSelector('.btn-revert-changes');
}

View File

@ -48,7 +48,8 @@ define([
.assertElementExists('.capacity-audit', 'Capacity Audit block is present')
.assertElementExists('.tracking', 'Statistics block is present')
.assertElementSelected(sendStatisticsCheckbox, 'Save Staticstics checkbox is checked')
.assertElementDisabled(saveStatisticsSettingsButton, '"Save changes" button is disabled until statistics checkbox uncheck');
.assertElementDisabled(saveStatisticsSettingsButton,
'"Save changes" button is disabled until statistics checkbox uncheck');
},
'Diagnostic snapshot link generation': function() {
return this.remote
@ -59,9 +60,12 @@ define([
return this.remote
// Uncheck "Send usage statistics" checkbox
.clickByCssSelector(sendStatisticsCheckbox)
.assertElementEnabled(saveStatisticsSettingsButton, '"Save changes" button is enabled after changing "Send usage statistics" checkbox value')
.assertElementEnabled(saveStatisticsSettingsButton,
'"Save changes" button is enabled after changing "Send usage statistics" ' +
'checkbox value')
.clickByCssSelector(saveStatisticsSettingsButton)
.assertElementDisabled(saveStatisticsSettingsButton, '"Save changes" button is disabled after saving changes');
.assertElementDisabled(saveStatisticsSettingsButton,
'"Save changes" button is disabled after saving changes');
},
'Discard changes': function() {
return this.remote
@ -87,7 +91,8 @@ define([
.assertElementAppears('.clusters-page', 1000, 'Redirecting to Environments')
// Go back to Support Page and ...
.clickLinkByText('Support')
.assertElementSelected(sendStatisticsCheckbox, 'Changes saved successfully and save staticstics checkbox is checked')
.assertElementSelected(sendStatisticsCheckbox,
'Changes saved successfully and save staticstics checkbox is checked')
// Uncheck the "Send usage statistics" checkbox value
.clickByCssSelector(sendStatisticsCheckbox)
// Go to another page with not saved changes
@ -105,7 +110,8 @@ define([
.assertElementAppears('.clusters-page', 1000, 'Redirecting to Environments')
// Go back to Support Page and ...
.clickLinkByText('Support')
.assertElementSelected(sendStatisticsCheckbox, 'Changes was not saved and save staticstics checkbox is checked')
.assertElementSelected(sendStatisticsCheckbox,
'Changes was not saved and save staticstics checkbox is checked')
// Uncheck the "Send usage statistics" checkbox value
.clickByCssSelector(sendStatisticsCheckbox)
// Go to another page with not saved changes
@ -120,7 +126,8 @@ define([
.then(function() {
return modal.waitToClose();
})
.assertElementNotSelected(sendStatisticsCheckbox, 'We are still on the Support page, and checkbox is unchecked');
.assertElementNotSelected(sendStatisticsCheckbox,
'We are still on the Support page, and checkbox is unchecked');
}
};
});

View File

@ -52,7 +52,8 @@ define([
},
'Test steps manipulations': function() {
return this.remote
.assertElementExists('.wizard-step.active', 'There is only one active and available step at the beginning')
.assertElementExists('.wizard-step.active',
'There is only one active and available step at the beginning')
// Compute
.pressKeys('\uE007')
// Network

View File

@ -68,7 +68,8 @@ suite('Expression', () => {
['"unknown-role" in release:roles', false],
['settings:common.libvirt_type.value', hypervisor],
['settings:common.libvirt_type.value == "' + hypervisor + '"', true],
['cluster:mode == "ha_compact" and not (settings:common.libvirt_type.value != "' + hypervisor + '")', true],
['cluster:mode == "ha_compact" and not (settings:common.libvirt_type.value != "' +
hypervisor + '")', true],
// test nonexistent keys
['cluster:nonexistentkey', Error],
['cluster:nonexistentkey == null', true, false],
@ -86,9 +87,11 @@ suite('Expression', () => {
_.each(testCases, ([expression, result, strict]) => {
var options = {strict};
if (result === Error) {
assert.throws(_.partial(evaluate, expression, options), Error, '', expression + ' throws an error');
assert.throws(_.partial(evaluate, expression, options), Error, '',
expression + ' throws an error');
} else {
assert.strictEqual(evaluate(expression, options), result, expression + ' evaluates correctly');
assert.strictEqual(evaluate(expression, options), result,
expression + ' evaluates correctly');
}
});
});

View File

@ -36,7 +36,8 @@ suite('File Control', () => {
var initialState = input.getInitialState();
assert.equal(input.props.type, 'file', 'Input type should be equal to file');
assert.equal(initialState.fileName, 'certificate.crt', 'Default file name must correspond to provided one');
assert.equal(initialState.fileName, 'certificate.crt',
'Default file name must correspond to provided one');
assert.equal(initialState.content, 'CERTIFICATE', 'Content should be equal to the default');
});
@ -48,7 +49,8 @@ suite('File Control', () => {
});
input.pickFile();
assert.ok(clickSpy.calledOnce, 'When icon clicked input control should be clicked too to open select file dialog');
assert.ok(clickSpy.calledOnce,
'When icon clicked input control should be clicked too to open select file dialog');
});
test('File fetching', () => {

View File

@ -24,39 +24,48 @@ suite('Test models', () => {
filters = {status: []};
result = ['running', 'pending', 'ready', 'error'];
assert.deepEqual(task.extendStatuses(filters), result, 'All task statuses are acceptable if "status" filter not specified');
assert.deepEqual(task.extendStatuses(filters), result,
'All task statuses are acceptable if "status" filter not specified');
filters = {status: 'ready'};
result = ['ready'];
assert.deepEqual(task.extendStatuses(filters), result, '"status" filter can have string as a value');
assert.deepEqual(task.extendStatuses(filters), result,
'"status" filter can have string as a value');
filters = {status: ['ready', 'running']};
result = ['ready', 'running'];
assert.deepEqual(task.extendStatuses(filters), result, '"status" filter can have list of strings as a value');
assert.deepEqual(task.extendStatuses(filters), result,
'"status" filter can have list of strings as a value');
filters = {status: ['ready'], active: true};
result = [];
assert.deepEqual(task.extendStatuses(filters), result, '"status" and "active" filters are not intersected');
assert.deepEqual(task.extendStatuses(filters), result,
'"status" and "active" filters are not intersected');
filters = {status: ['running'], active: true};
result = ['running'];
assert.deepEqual(task.extendStatuses(filters), result, '"status" and "active" filters have intersection');
assert.deepEqual(task.extendStatuses(filters), result,
'"status" and "active" filters have intersection');
filters = {status: ['running'], active: false};
result = [];
assert.deepEqual(task.extendStatuses(filters), result, '"status" and "active" filters are not intersected');
assert.deepEqual(task.extendStatuses(filters), result,
'"status" and "active" filters are not intersected');
filters = {status: ['ready', 'running'], active: false};
result = ['ready'];
assert.deepEqual(task.extendStatuses(filters), result, '"status" and "active" filters have intersection');
assert.deepEqual(task.extendStatuses(filters), result,
'"status" and "active" filters have intersection');
filters = {active: true};
result = ['running', 'pending'];
assert.deepEqual(task.extendStatuses(filters), result, 'True value of "active" filter parsed correctly');
assert.deepEqual(task.extendStatuses(filters), result,
'True value of "active" filter parsed correctly');
filters = {active: false};
result = ['ready', 'error'];
assert.deepEqual(task.extendStatuses(filters), result, 'False value of \'active\' filter parsed correctly');
assert.deepEqual(task.extendStatuses(filters), result,
'False value of \'active\' filter parsed correctly');
});
test('Test extendGroups method', () => {
@ -66,39 +75,48 @@ suite('Test models', () => {
filters = {name: []};
result = allTaskNames;
assert.deepEqual(task.extendGroups(filters), result, 'All task names are acceptable if "name" filter not specified');
assert.deepEqual(task.extendGroups(filters), result,
'All task names are acceptable if "name" filter not specified');
filters = {group: []};
result = allTaskNames;
assert.deepEqual(task.extendGroups(filters), result, 'All task names are acceptable if "group" filter not specified');
assert.deepEqual(task.extendGroups(filters), result,
'All task names are acceptable if "group" filter not specified');
filters = {name: 'deploy'};
result = ['deploy'];
assert.deepEqual(task.extendGroups(filters), result, '"name" filter can have string as a value');
assert.deepEqual(task.extendGroups(filters), result,
'"name" filter can have string as a value');
filters = {name: 'dump'};
result = ['dump'];
assert.deepEqual(task.extendGroups(filters), result, 'Tasks, that are not related to any task group, handled properly');
assert.deepEqual(task.extendGroups(filters), result,
'Tasks, that are not related to any task group, handled properly');
filters = {name: ['deploy', 'check_networks']};
result = ['deploy', 'check_networks'];
assert.deepEqual(task.extendGroups(filters), result, '"name" filter can have list of strings as a value');
assert.deepEqual(task.extendGroups(filters), result,
'"name" filter can have list of strings as a value');
filters = {group: 'deployment'};
result = task.groups.deployment;
assert.deepEqual(task.extendGroups(filters), result, '"group" filter can have string as a value');
assert.deepEqual(task.extendGroups(filters), result,
'"group" filter can have string as a value');
filters = {group: ['deployment', 'network']};
result = allTaskNames;
assert.deepEqual(task.extendGroups(filters), result, '"group" filter can have list of strings as a value');
assert.deepEqual(task.extendGroups(filters), result,
'"group" filter can have list of strings as a value');
filters = {name: 'deploy', group: 'deployment'};
result = ['deploy'];
assert.deepEqual(task.extendGroups(filters), result, '"name" and "group" filters have intersection');
assert.deepEqual(task.extendGroups(filters), result,
'"name" and "group" filters have intersection');
filters = {name: 'deploy', group: 'network'};
result = [];
assert.deepEqual(task.extendGroups(filters), result, '"name" and "group" filters are not intersected');
assert.deepEqual(task.extendGroups(filters), result,
'"name" and "group" filters are not intersected');
});
});
});

View File

@ -13,17 +13,20 @@
* License for the specific language governing permissions and limitations
* under the License.
**/
import OffloadingModes from 'views/cluster_page_tabs/nodes_tab_screens/offloading_modes_control';
import OffloadingModes from
'views/cluster_page_tabs/nodes_tab_screens/offloading_modes_control';
var offloadingModesConrol, TestMode22, TestMode31, fakeOffloadingModes;
var fakeInterface = {
offloading_modes: fakeOffloadingModes,
get(key) {
assert.equal(key, 'offloading_modes', '"offloading_modes" interface property should be used to get data');
assert.equal(key, 'offloading_modes',
'"offloading_modes" interface property should be used to get data');
return fakeOffloadingModes;
},
set(key, value) {
assert.equal(key, 'offloading_modes', '"offloading_modes" interface property should be used to set data');
assert.equal(key, 'offloading_modes',
'"offloading_modes" interface property should be used to set data');
fakeOffloadingModes = value;
}
};
@ -70,18 +73,21 @@ suite('Offloadning Modes control', () => {
test('Set submodes states logic', () => {
var mode = offloadingModesConrol.findMode('TestName1', fakeOffloadingModes);
offloadingModesConrol.setModeState(mode, false);
assert.strictEqual(TestMode31.state, false, 'Parent state changing leads to all child modes states changing');
assert.strictEqual(TestMode31.state, false,
'Parent state changing leads to all child modes states changing');
});
test('Disabled reversed logic', () => {
var mode = offloadingModesConrol.findMode('TestName2', fakeOffloadingModes);
offloadingModesConrol.setModeState(TestMode22, true);
offloadingModesConrol.checkModes(null, fakeOffloadingModes);
assert.strictEqual(mode.state, null, 'Parent state changing leads to all child modes states changing');
assert.strictEqual(mode.state, null,
'Parent state changing leads to all child modes states changing');
});
test('All Modes option logic', () => {
var enableAllModes = offloadingModesConrol.onModeStateChange('All Modes', true);
enableAllModes();
var mode = offloadingModesConrol.findMode('TestName2', fakeOffloadingModes);
assert.strictEqual(mode.state, true, 'All Modes option state changing leads to all parent modes states changing');
assert.strictEqual(mode.state, true,
'All Modes option state changing leads to all parent modes states changing');
});
});

View File

@ -25,19 +25,24 @@ suite('Test utils', () => {
var serverUnavailableMessage = i18n('dialog.error_dialog.server_unavailable');
response = {status: 500, responseText: 'Server error occured'};
assert.equal(getResponseText(response), serverErrorMessage, 'HTTP 500 is treated as a server error');
assert.equal(getResponseText(response), serverErrorMessage,
'HTTP 500 is treated as a server error');
response = {status: 502, responseText: 'Bad gateway'};
assert.equal(getResponseText(response), serverUnavailableMessage, 'HTTP 502 is treated as server unavailability');
assert.equal(getResponseText(response), serverUnavailableMessage,
'HTTP 502 is treated as server unavailability');
response = {status: 0, responseText: 'error'};
assert.equal(getResponseText(response), serverUnavailableMessage, 'XHR object with no status is treated as server unavailability');
assert.equal(getResponseText(response), serverUnavailableMessage,
'XHR object with no status is treated as server unavailability');
response = {status: 400, responseText: 'Bad request'};
assert.equal(getResponseText(response), serverErrorMessage, 'HTTP 400 with plain text response is treated as a server error');
assert.equal(getResponseText(response), serverErrorMessage,
'HTTP 400 with plain text response is treated as a server error');
response = {status: 400, responseText: JSON.stringify({message: '123'})};
assert.equal(getResponseText(response), '123', 'HTTP 400 with JSON response is treated correctly');
assert.equal(getResponseText(response), '123',
'HTTP 400 with JSON response is treated correctly');
});
test('Test comparison', () => {
@ -67,15 +72,20 @@ suite('Test utils', () => {
assert.equal(compare(model1, model1, {attr: 'number'}), 0, 'Number comparison a=b');
assert.equal(compare(model1, model2, {attr: 'boolean'}), -1, 'Boolean comparison true and false');
assert.equal(compare(model1, model2, {attr: 'boolean'}), -1,
'Boolean comparison true and false');
assert.equal(compare(model2, model1, {attr: 'boolean'}), 1, 'Boolean comparison false and true');
assert.equal(compare(model2, model1, {attr: 'boolean'}), 1,
'Boolean comparison false and true');
assert.equal(compare(model1, model1, {attr: 'boolean'}), 0, 'Boolean comparison true and true');
assert.equal(compare(model1, model1, {attr: 'boolean'}), 0,
'Boolean comparison true and true');
assert.equal(compare(model2, model2, {attr: 'boolean'}), 0, 'Boolean comparison false and false');
assert.equal(compare(model2, model2, {attr: 'boolean'}), 0,
'Boolean comparison false and false');
assert.equal(compare(model1, model2, {attr: 'booleanFlagWithNull'}), 0, 'Comparison null and false');
assert.equal(compare(model1, model2, {attr: 'booleanFlagWithNull'}), 0,
'Comparison null and false');
});
test('Test highlightTestStep', () => {
@ -136,17 +146,22 @@ suite('Test utils', () => {
assert.equal(getGateway('172.16.0.0/24'), '172.16.0.1', 'Getting default gateway for CIDR');
assert.equal(getGateway('192.168.0.0/10'), '192.128.0.1', 'Getting default gateway for CIDR');
assert.equal(getGateway('172.16.0.0/31'), '', 'No gateway returned for inappropriate CIDR (network is too small)');
assert.equal(getGateway('172.16.0.0/31'), '',
'No gateway returned for inappropriate CIDR (network is too small)');
assert.equal(getGateway('172.16.0.0/'), '', 'No gateway returned for invalid CIDR');
});
test('Test getDefaultIPRangeForCidr', () => {
var getRange = utils.getDefaultIPRangeForCidr;
assert.deepEqual(getRange('172.16.0.0/24'), [['172.16.0.1', '172.16.0.254']], 'Getting default IP range for CIDR');
assert.deepEqual(getRange('192.168.0.0/10', true), [['192.128.0.2', '192.191.255.254']], 'Gateway address excluded from default IP range');
assert.deepEqual(getRange('172.16.0.0/31'), [['', '']], 'No IP range returned for inappropriate CIDR (network is too small)');
assert.deepEqual(getRange('172.16.0.0/', true), [['', '']], 'No IP range returned for invalid CIDR');
assert.deepEqual(getRange('172.16.0.0/24'), [['172.16.0.1', '172.16.0.254']],
'Getting default IP range for CIDR');
assert.deepEqual(getRange('192.168.0.0/10', true), [['192.128.0.2', '192.191.255.254']],
'Gateway address excluded from default IP range');
assert.deepEqual(getRange('172.16.0.0/31'), [['', '']],
'No IP range returned for inappropriate CIDR (network is too small)');
assert.deepEqual(getRange('172.16.0.0/', true), [['', '']],
'No IP range returned for invalid CIDR');
});
test('Test validateIpCorrespondsToCIDR', () => {
@ -156,6 +171,7 @@ suite('Test utils', () => {
assert.ok(validate('172.16.0.5/24', '172.16.0.2'), 'Check IP, that corresponds to CIDR');
assert.notOk(validate('172.16.0.0/20', '172.16.15.255'), 'Check broadcast address');
assert.notOk(validate('172.16.0.0/20', '172.16.0.0'), 'Check network address');
assert.notOk(validate('192.168.0.0/10', '192.231.255.254'), 'Check IP, that does not correspond to CIDR');
assert.notOk(validate('192.168.0.0/10', '192.231.255.254'),
'Check IP, that does not correspond to CIDR');
});
});

View File

@ -26,12 +26,14 @@ import {ErrorDialog} from 'views/dialogs';
import models from 'models';
var utils = {
/*eslint-disable max-len*/
regexes: {
url: /(?:https?:\/\/([\-\w\.]+)+(:\d+)?(\/([\w\/_\-\.]*(\?[\w\/_\-\.&%]*)?(#[\w\/_\-\.&%]*)?)?)?)/,
ip: /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/,
mac: /^([0-9a-f]{1,2}[\.:-]){5}([0-9a-f]{1,2})$/,
cidr: /^(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\/([1-9]|[1-2]\d|3[0-2])$/
},
/*eslint-enable max-len*/
serializeTabOptions(options) {
return _.map(options, (value, key) => key + ':' + value).join(';');
},
@ -55,14 +57,16 @@ var utils = {
return '<a target="_blank" href="' + url + '">' + url + '</a>';
},
urlify(text) {
return utils.linebreaks(text).replace(new RegExp(utils.regexes.url.source, 'g'), utils.composeLink);
return utils.linebreaks(text).replace(new RegExp(utils.regexes.url.source, 'g'),
utils.composeLink);
},
composeList(value) {
return _.isUndefined(value) ? [] : _.isArray(value) ? value : [value];
},
// FIXME(vkramskikh): moved here from healthcheck_tab to make testable
highlightTestStep(text, step) {
return text.replace(new RegExp('(^|\\s*)(' + step + '\\.[\\s\\S]*?)(\\s*\\d+\\.|$)'), '$1<b>$2</b>$3');
return text.replace(new RegExp('(^|\\s*)(' + step + '\\.[\\s\\S]*?)(\\s*\\d+\\.|$)'),
'$1<b>$2</b>$3');
},
classNames: classNames,
parseModelPath(path, models) {
@ -129,7 +133,8 @@ var utils = {
break;
}
}
return (result ? result.toFixed(1) : result) + ' ' + i18n('common.size.' + unit, {count: result});
return (result ? result.toFixed(1) : result) + ' ' + i18n('common.size.' + unit,
{count: result});
},
showMemorySize(bytes) {
return utils.showSize(bytes, 1024);
@ -142,7 +147,8 @@ var utils = {
return Math.pow(2, 32 - parseInt(_.last(cidr.split('/')), 10));
},
formatNumber(n) {
return String(n).replace(/\d/g, (c, i, a) => i > 0 && c !== '.' && (a.length - i) % 3 === 0 ? ',' + c : c);
return String(n).replace(/\d/g, (c, i, a) => i > 0 && c !== '.' &&
(a.length - i) % 3 === 0 ? ',' + c : c);
},
floor(n, decimals) {
return Math.floor(n * Math.pow(10, decimals)) / Math.pow(10, decimals);
@ -152,7 +158,8 @@ var utils = {
},
validateVlan(vlan, forbiddenVlans, field, disallowNullValue) {
var error = {};
if ((_.isNull(vlan) && disallowNullValue) || (!_.isNull(vlan) && (!utils.isNaturalNumber(vlan) || vlan < 1 || vlan > 4094))) {
if ((_.isNull(vlan) && disallowNullValue) || (!_.isNull(vlan) &&
(!utils.isNaturalNumber(vlan) || vlan < 1 || vlan > 4094))) {
error[field] = i18n('cluster_page.network_tab.validation.invalid_vlan');
return error;
}
@ -218,7 +225,8 @@ var utils = {
} else if (existingRanges.length) {
var intersection = utils.checkIPRangesIntersection(range, existingRanges);
if (intersection) {
error.start = error.end = warnings.IP_RANGES_INTERSECTION + intersection.join(' - ');
error.start = error.end = warnings.IP_RANGES_INTERSECTION +
intersection.join(' - ');
}
}
}
@ -240,13 +248,16 @@ var utils = {
checkIPRangesIntersection([startIP, endIP], existingRanges) {
var startIPInt = IP.toLong(startIP);
var endIPInt = IP.toLong(endIP);
return _.find(existingRanges, ([ip1, ip2]) => IP.toLong(ip2) >= startIPInt && IP.toLong(ip1) <= endIPInt);
return _.find(existingRanges, ([ip1, ip2]) => {
return IP.toLong(ip2) >= startIPInt && IP.toLong(ip1) <= endIPInt;
});
},
validateIpCorrespondsToCIDR(cidr, ip) {
if (!cidr) return true;
var networkData = IP.cidrSubnet(cidr);
var ipInt = IP.toLong(ip);
return ipInt >= IP.toLong(networkData.firstAddress) && ipInt <= IP.toLong(networkData.lastAddress);
return ipInt >= IP.toLong(networkData.firstAddress) &&
ipInt <= IP.toLong(networkData.lastAddress);
},
validateVlanRange(vlanStart, vlanEnd, vlan) {
return vlan >= vlanStart && vlan <= vlanEnd;

View File

@ -62,10 +62,15 @@ var ClusterPage = React.createClass({
['home', '#'],
['environments', '#clusters'],
[cluster.get('name'), '#cluster/' + cluster.get('id'), {skipTranslation: true}],
[i18n('cluster_page.tabs.' + pageOptions.activeTab), '#cluster/' + cluster.get('id') + '/' + pageOptions.activeTab, {active: !addScreenBreadcrumb}]
[
i18n('cluster_page.tabs.' + pageOptions.activeTab),
'#cluster/' + cluster.get('id') + '/' + pageOptions.activeTab,
{active: !addScreenBreadcrumb}
]
];
if (addScreenBreadcrumb) {
breadcrumbs.push([i18n('cluster_page.nodes_tab.breadcrumbs.' + tabOptions), null, {active: true}]);
breadcrumbs.push([i18n('cluster_page.nodes_tab.breadcrumbs.' + tabOptions), null,
{active: true}]);
}
return breadcrumbs;
},
@ -94,7 +99,8 @@ var ClusterPage = React.createClass({
if (currentClusterId == id) {
// just another tab has been chosen, do not load cluster again
cluster = app.page.props.cluster;
promise = tab.fetchData ? tab.fetchData({cluster: cluster, tabOptions: tabOptions}) : $.Deferred().resolve();
promise = tab.fetchData ? tab.fetchData({cluster: cluster, tabOptions: tabOptions}) :
$.Deferred().resolve();
} else {
cluster = new models.Cluster({id: id});
@ -111,7 +117,8 @@ var ClusterPage = React.createClass({
cluster.set({pluginLinks: pluginLinks});
cluster.get('nodes').fetch = function(options) {
return this.constructor.__super__.fetch.call(this, _.extend({data: {cluster_id: id}}, options));
return this.constructor.__super__.fetch.call(this,
_.extend({data: {cluster_id: id}}, options));
};
promise = $.when(
cluster.fetch(),
@ -124,12 +131,16 @@ var ClusterPage = React.createClass({
)
.then(() => {
var networkConfiguration = new models.NetworkConfiguration();
networkConfiguration.url = _.result(cluster, 'url') + '/network_configuration/' + cluster.get('net_provider');
networkConfiguration.url = _.result(cluster, 'url') + '/network_configuration/' +
cluster.get('net_provider');
cluster.set({
networkConfiguration: networkConfiguration,
release: new models.Release({id: cluster.get('release_id')})
});
return $.when(cluster.get('networkConfiguration').fetch(), cluster.get('release').fetch());
return $.when(
cluster.get('networkConfiguration').fetch(),
cluster.get('release').fetch()
);
})
.then(() => {
var useVcenter = cluster.get('settings').get('common.use_vcenter.value');
@ -141,7 +152,8 @@ var ClusterPage = React.createClass({
return vcenter.fetch();
})
.then(() => {
return tab.fetchData ? tab.fetchData({cluster: cluster, tabOptions: tabOptions}) : $.Deferred().resolve();
return tab.fetchData ? tab.fetchData({cluster: cluster, tabOptions: tabOptions}) :
$.Deferred().resolve();
});
}
return promise.then((data) => {
@ -232,7 +244,8 @@ var ClusterPage = React.createClass({
var selectedLogs;
if (props.tabOptions[0]) {
selectedLogs = utils.deserializeTabOptions(_.compact(props.tabOptions).join('/'));
selectedLogs.level = selectedLogs.level ? selectedLogs.level.toUpperCase() : props.defaultLogLevel;
selectedLogs.level = selectedLogs.level ? selectedLogs.level.toUpperCase() :
props.defaultLogLevel;
this.setState({selectedLogs: selectedLogs});
}
}
@ -283,7 +296,11 @@ var ClusterPage = React.createClass({
<div className='page-title'>
<h1 className='title'>
{cluster.get('name')}
<div className='title-node-count'>({i18n('common.node', {count: cluster.get('nodes').length})})</div>
<div
className='title-node-count'
>
({i18n('common.node', {count: cluster.get('nodes').length})})
</div>
</h1>
</div>
<div className='tabs-box'>
@ -292,7 +309,12 @@ var ClusterPage = React.createClass({
return (
<a
key={url}
className={url + ' ' + utils.classNames({'cluster-tab': true, active: this.props.activeTab == url})}
className={
url + ' ' + utils.classNames({
'cluster-tab': true,
active: this.props.activeTab == url
})
}
href={'#cluster/' + cluster.id + '/' + url}
>
<div className='icon'></div>

View File

@ -21,14 +21,18 @@ import ReactDOM from 'react-dom';
import utils from 'utils';
import dispatcher from 'dispatcher';
import {Input, ProgressBar, Tooltip} from 'views/controls';
import {DiscardNodeChangesDialog, DeployChangesDialog, ProvisionVMsDialog, RemoveClusterDialog, ResetEnvironmentDialog, StopDeploymentDialog} from 'views/dialogs';
import {
DiscardNodeChangesDialog, DeployChangesDialog, ProvisionVMsDialog,
RemoveClusterDialog, ResetEnvironmentDialog, StopDeploymentDialog
} from 'views/dialogs';
import {backboneMixin, pollingMixin, renamingMixin} from 'component_mixins';
var namespace = 'cluster_page.dashboard_tab.';
var DashboardTab = React.createClass({
mixins: [
// this is needed to somehow handle the case when verification is in progress and user pressed Deploy
// this is needed to somehow handle the case when verification
// is in progress and user pressed Deploy
backboneMixin({
modelOrCollection: (props) => props.cluster.get('tasks'),
renderOn: 'update change'
@ -254,9 +258,13 @@ var DeploymentResult = React.createClass({
<br />
<span dangerouslySetInnerHTML={{__html: utils.urlify(summary)}} />
<div className={utils.classNames({'task-result-details': true, hidden: !details})}>
<pre className='collapse result-details' dangerouslySetInnerHTML={{__html: utils.urlify(details)}} />
<pre
className='collapse result-details'
dangerouslySetInnerHTML={{__html: utils.urlify(details)}}
/>
<button className='btn-link' data-toggle='collapse' data-target='.result-details'>
{this.state.collapsed ? i18n('cluster_page.hide_details_button') : i18n('cluster_page.show_details_button')}
{this.state.collapsed ? i18n('cluster_page.hide_details_button') :
i18n('cluster_page.show_details_button')}
</button>
</div>
</div>
@ -288,14 +296,29 @@ var DocumentationLinks = React.createClass({
<div className='documentation col-xs-12'>
{isMirantisIso ?
[
this.renderDocumentationLinks('https://www.mirantis.com/openstack-documentation/', 'mos_documentation'),
this.renderDocumentationLinks(utils.composeDocumentationLink('plugin-dev.html#plugin-dev'), 'plugin_documentation'),
this.renderDocumentationLinks('https://software.mirantis.com/mirantis-openstack-technical-bulletins/', 'technical_bulletins')
this.renderDocumentationLinks(
'https://www.mirantis.com/openstack-documentation/',
'mos_documentation'
),
this.renderDocumentationLinks(
utils.composeDocumentationLink('plugin-dev.html#plugin-dev'),
'plugin_documentation'
),
this.renderDocumentationLinks(
'https://software.mirantis.com/mirantis-openstack-technical-bulletins/',
'technical_bulletins'
)
]
:
[
this.renderDocumentationLinks('http://docs.openstack.org/', 'openstack_documentation'),
this.renderDocumentationLinks('https://wiki.openstack.org/wiki/Fuel/Plugins', 'plugin_documentation')
this.renderDocumentationLinks(
'http://docs.openstack.org/',
'openstack_documentation'
),
this.renderDocumentationLinks(
'https://wiki.openstack.org/wiki/Fuel/Plugins',
'plugin_documentation'
)
]
}
</div>
@ -308,7 +331,8 @@ var DocumentationLinks = React.createClass({
// it should be refactored to provide proper logics separation and decoupling
var DeployReadinessBlock = React.createClass({
mixins: [
// this is needed to somehow handle the case when verification is in progress and user pressed Deploy
// this is needed to somehow handle the case when verification
// is in progress and user pressed Deploy
backboneMixin({
modelOrCollection(props) {
return props.cluster.get('tasks');
@ -332,7 +356,8 @@ var DeployReadinessBlock = React.createClass({
validate(cluster) {
return _.reduce(
this.validations,
(accumulator, validator) => _.merge(accumulator, validator.call(this, cluster), (a, b) => a.concat(_.compact(b))),
(accumulator, validator) => _.merge(accumulator, validator.call(this, cluster), (a, b) =>
a.concat(_.compact(b))),
{blocker: [], error: [], warning: []}
);
},
@ -398,9 +423,16 @@ var DeployReadinessBlock = React.createClass({
function(cluster) {
var configModels = this.getConfigModels();
var roleModels = cluster.get('roles');
var validRoleModels = roleModels.filter((role) => !role.checkRestrictions(configModels).result);
var limitValidations = _.zipObject(validRoleModels.map((role) => [role.get('name'), role.checkLimits(configModels, cluster.get('nodes'))]));
var limitRecommendations = _.zipObject(validRoleModels.map((role) => [role.get('name'), role.checkLimits(configModels, cluster.get('nodes'), true, ['recommended'])]));
var validRoleModels = roleModels.filter((role) => {
return !role.checkRestrictions(configModels).result;
});
var limitValidations = _.zipObject(validRoleModels.map((role) => {
return [role.get('name'), role.checkLimits(configModels, cluster.get('nodes'))];
}));
var limitRecommendations = _.zipObject(validRoleModels.map((role) => {
return [role.get('name'), role.checkLimits(configModels, cluster.get('nodes'), true,
['recommended'])];
}));
return {
blocker: roleModels.map((role) => {
var name = role.get('name');
@ -461,7 +493,9 @@ var DeployReadinessBlock = React.createClass({
var nodes = cluster.get('nodes');
var alerts = this.validate(cluster);
var isDeploymentPossible = cluster.isDeploymentPossible() && !alerts.blocker.length;
var isVMsProvisioningAvailable = nodes.any((node) => node.get('pending_addition') && node.hasRole('virt'));
var isVMsProvisioningAvailable = nodes.any((node) => {
return node.get('pending_addition') && node.hasRole('virt');
});
return (
<div className='row'>
@ -471,9 +505,18 @@ var DeployReadinessBlock = React.createClass({
<div>
<h4>{i18n(namespace + 'changes_header')}</h4>
<ul>
{this.renderChangedNodesAmount(nodes.where({pending_addition: true}), 'added_node')}
{this.renderChangedNodesAmount(nodes.where({status: 'provisioned'}), 'provisioned_node')}
{this.renderChangedNodesAmount(nodes.where({pending_deletion: true}), 'deleted_node')}
{this.renderChangedNodesAmount(
nodes.where({pending_addition: true}),
'added_node'
)}
{this.renderChangedNodesAmount(
nodes.where({status: 'provisioned'}),
'provisioned_node'
)}
{this.renderChangedNodesAmount(
nodes.where({pending_deletion: true}),
'deleted_node'
)}
</ul>
</div>
}
@ -489,7 +532,10 @@ var DeployReadinessBlock = React.createClass({
<button
className={utils.classNames({
'btn btn-primary deploy-btn': true,
'btn-warning': _.isEmpty(alerts.blocker) && (!_.isEmpty(alerts.error) || !_.isEmpty(alerts.warning))
'btn-warning': (
_.isEmpty(alerts.blocker) &&
(!_.isEmpty(alerts.error) || !_.isEmpty(alerts.warning))
)
})}
onClick={_.partial(this.showDialog, DeployChangesDialog)}
disabled={!isDeploymentPossible}
@ -652,7 +698,8 @@ var ClusterInfo = React.createClass({
},
renderLegend(fieldsData, isRole) {
var result = _.map(fieldsData, (field) => {
var numberOfNodes = isRole ? this.getNumberOfNodesWithRole(field) : this.getNumberOfNodesWithStatus(field);
var numberOfNodes = isRole ? this.getNumberOfNodesWithRole(field) :
this.getNumberOfNodesWithStatus(field);
return numberOfNodes ?
<div key={field}>
<div className='col-xs-10'>
@ -683,8 +730,10 @@ var ClusterInfo = React.createClass({
renderStatistics() {
var hasNodes = !!this.props.cluster.get('nodes').length;
var fieldRoles = _.union(['total'], this.props.cluster.get('roles').pluck('name'));
var fieldStatuses = ['offline', 'error', 'pending_addition', 'pending_deletion', 'ready', 'provisioned',
'provisioning', 'deploying', 'removing'];
var fieldStatuses = [
'offline', 'error', 'pending_addition', 'pending_deletion', 'ready',
'provisioned', 'provisioning', 'deploying', 'removing'
];
return (
<div className='row statistics-block'>
<div className='title'>{i18n(namespace + 'cluster_info_fields.statistics')}</div>
@ -905,7 +954,8 @@ var ResetEnvironmentAction = React.createClass({
<Tooltip
key='reset-tooltip'
placement='right'
text={!isLocked ? i18n(namespace + 'reset_environment_warning') : i18n(namespace + this.getDescriptionKey())}
text={!isLocked ? i18n(namespace + 'reset_environment_warning') :
i18n(namespace + this.getDescriptionKey())}
>
<i className='glyphicon glyphicon-info-sign' />
</Tooltip>

View File

@ -95,14 +95,14 @@ var HealthcheckTabContent = React.createClass({
return {
actionInProgress: false,
credentialsVisible: null,
credentials: _.transform(this.props.cluster.get('settings').get('access'), (result, value, key) => {
result[key] = value.value;
})
credentials: _.transform(this.props.cluster.get('settings').get('access'),
(result, value, key) => result[key] = value.value)
};
},
isLocked() {
var cluster = this.props.cluster;
return cluster.get('status') != 'operational' || !!cluster.task({group: 'deployment', active: true});
return cluster.get('status') != 'operational' || !!cluster.task({group: 'deployment',
active: true});
},
getNumberOfCheckedTests() {
return this.props.tests.where({checked: true}).length;
@ -248,14 +248,17 @@ var HealthcheckTabContent = React.createClass({
}
<div>
{(this.props.cluster.get('status') == 'new') &&
<div className='alert alert-warning'>{i18n('cluster_page.healthcheck_tab.deploy_alert')}</div>
<div className='alert alert-warning'>
{i18n('cluster_page.healthcheck_tab.deploy_alert')}
</div>
}
<div key='testsets'>
{this.props.testsets.map((testset) => {
return <TestSet
key={testset.id}
testset={testset}
testrun={this.props.testruns.findWhere({testset: testset.id}) || new models.TestRun({testset: testset.id})}
testrun={this.props.testruns.findWhere({testset: testset.id}) ||
new models.TestRun({testset: testset.id})}
tests={new Backbone.Collection(this.props.tests.where({testset: testset.id}))}
disabled={disabledState || hasRunningTests}
/>;
@ -312,7 +315,10 @@ var TestSet = React.createClass({
this.props.tests.invoke('on', 'change:checked', this.updateTestsetCheckbox, this);
},
updateTestsetCheckbox() {
this.props.testset.set('checked', this.props.tests.where({checked: true}).length == this.props.tests.length);
this.props.testset.set(
'checked',
this.props.tests.where({checked: true}).length == this.props.tests.length
);
},
render() {
var classes = {
@ -434,7 +440,8 @@ var Test = React.createClass({
</td>
<td className='healthcheck-col-status'>
<div className={currentStatusClassName}>
{iconClasses[status] ? <i className={iconClasses[status]} /> : String.fromCharCode(0x2014)}
{iconClasses[status] ? <i className={iconClasses[status]} /> :
String.fromCharCode(0x2014)}
</div>
</td>
</tr>

View File

@ -67,9 +67,11 @@ var LogsTab = React.createClass({
},
showLogs(params) {
this.stopPolling();
var logOptions = this.props.selectedLogs.type == 'remote' ? _.extend({}, this.props.selectedLogs) : _.omit(this.props.selectedLogs, 'node');
var logOptions = this.props.selectedLogs.type == 'remote' ?
_.extend({}, this.props.selectedLogs) : _.omit(this.props.selectedLogs, 'node');
logOptions.level = logOptions.level.toLowerCase();
app.navigate('#cluster/' + this.props.cluster.id + '/logs/' + utils.serializeTabOptions(logOptions), {trigger: false, replace: true});
app.navigate('#cluster/' + this.props.cluster.id + '/logs/' +
utils.serializeTabOptions(logOptions), {trigger: false, replace: true});
params = params || {};
this.fetchLogs(params)
.done((data) => {
@ -131,7 +133,8 @@ var LogsTab = React.createClass({
});
var LogFilterBar = React.createClass({
// PureRenderMixin added for prevention the rerender LogFilterBar (because of polling) in Mozilla browser
// PureRenderMixin added for prevention the rerender LogFilterBar
// (because of polling) in Mozilla browser
mixins: [PureRenderMixin],
getInitialState() {
return _.extend({}, this.props.selectedLogs, {
@ -150,9 +153,13 @@ var LogFilterBar = React.createClass({
:
this.sources.fetch();
this.sources.deferred.done(() => {
var filteredSources = this.sources.filter((source) => source.get('remote') == (type != 'local'));
var chosenSource = _.findWhere(filteredSources, {id: this.state.source}) || _.first(filteredSources);
var chosenLevelId = chosenSource ? _.contains(chosenSource.get('levels'), this.state.level) ? this.state.level : _.first(chosenSource.get('levels')) : null;
var filteredSources = this.sources.filter((source) => {
return source.get('remote') == (type != 'local');
});
var chosenSource = _.findWhere(filteredSources, {id: this.state.source}) ||
_.first(filteredSources);
var chosenLevelId = chosenSource ? _.contains(chosenSource.get('levels'), this.state.level) ?
this.state.level : _.first(chosenSource.get('levels')) : null;
this.setState({
type: type,
sources: this.sources,
@ -168,7 +175,8 @@ var LogFilterBar = React.createClass({
type: type,
sources: {},
sourcesLoadingState: 'fail',
sourcesLoadingError: utils.getResponseText(response, i18n('cluster_page.logs_tab.source_alert')),
sourcesLoadingError: utils.getResponseText(response,
i18n('cluster_page.logs_tab.source_alert')),
locked: false
});
});
@ -310,7 +318,8 @@ var LogFilterBar = React.createClass({
</div>;
},
renderSourceSelect() {
var sourceOptions = this.state.type == 'local' ? this.getLocalSources() : this.getRemoteSources();
var sourceOptions = this.state.type == 'local' ? this.getLocalSources() :
this.getRemoteSources();
return <div className='col-md-2 col-sm-3'>
<Input
type='select'
@ -397,7 +406,13 @@ var LogsTable = React.createClass({
<span>{i18n('cluster_page.logs_tab.bottom_text')}</span>:
{
[100, 500, 1000, 5000].map((count) => {
return <button className='btn btn-link show-more-entries' onClick={_.bind(this.handleShowMoreClick, this, count)} key={count}>{count}</button>;
return <button
key={count}
className='btn btn-link show-more-entries'
onClick={_.bind(this.handleShowMoreClick, this, count)}
>
{count}
</button>;
})
}
</div>

View File

@ -30,7 +30,10 @@ import CSSTransitionGroup from 'react-addons-transition-group';
var parametersNS = 'cluster_page.network_tab.networking_parameters.';
var networkTabNS = 'cluster_page.network_tab.';
var defaultNetworkSubtabs = ['neutron_l2', 'neutron_l3', 'network_settings', 'network_verification', 'nova_configuration'];
var defaultNetworkSubtabs = [
'neutron_l2', 'neutron_l3', 'network_settings',
'network_verification', 'nova_configuration'
];
var NetworkModelManipulationMixin = {
setValue(attribute, value, options) {
@ -98,7 +101,8 @@ var NetworkInputsMixin = {
var error;
if (this.props.network) {
try {
error = validationError.networks[this.props.currentNodeNetworkGroup.id][this.props.network.id][attribute];
error = validationError
.networks[this.props.currentNodeNetworkGroup.id][this.props.network.id][attribute];
} catch (e) {}
return error || null;
}
@ -139,7 +143,11 @@ var Range = React.createClass({
componentDidUpdate() {
// this glitch is needed to fix
// when pressing '+' or '-' buttons button remains focused
if (this.props.extendable && this.state.elementToFocus && this.getModel().get(this.props.name).length) {
if (
this.props.extendable &&
this.state.elementToFocus &&
this.getModel().get(this.props.name).length
) {
$(this.refs[this.state.elementToFocus].getInputDOMNode()).focus();
this.setState({elementToFocus: null});
}
@ -244,7 +252,10 @@ var Range = React.createClass({
onFocus={_.partial(this.autoCompleteIPRange, rangeError && rangeError.start, range[0])}
disabled={this.props.disabled || !!this.props.autoIncreaseWith}
placeholder={rangeError.end ? '' : this.props.placeholder}
extraContent={!this.props.hiddenControls && this.renderRangeControls(attributeName, index, ranges.length)}
extraContent={
!this.props.hiddenControls &&
this.renderRangeControls(attributeName, index, ranges.length)
}
/>
<div className='validation-error text-danger pull-left'>
<span className='help-inline'>
@ -306,7 +317,8 @@ var Range = React.createClass({
<div className='col-xs-12'>
<label>{this.props.label}</label>
{
// TODO: renderExtendableRanges & renderRanges methods should be refactored to avoid copy-paste
// TODO: renderExtendableRanges & renderRanges methods
// should be refactored to avoid copy-paste
this.props.extendable ?
this.renderExtendableRanges({error, attributeName, ranges, verificationError})
:
@ -546,7 +558,8 @@ var NetworkTab = React.createClass({
configModels: {
cluster: this.props.cluster,
settings: settings,
networking_parameters: this.props.cluster.get('networkConfiguration').get('networking_parameters'),
networking_parameters:
this.props.cluster.get('networkConfiguration').get('networking_parameters'),
version: app.version,
release: this.props.cluster.get('release'),
default: settings
@ -560,7 +573,11 @@ var NetworkTab = React.createClass({
componentDidMount() {
this.props.cluster.get('networkConfiguration').isValid();
this.props.cluster.get('settings').isValid({models: this.state.configModels});
this.props.cluster.get('tasks').on('change:status change:unsaved', this.destroyUnsavedNetworkVerificationTask, this);
this.props.cluster.get('tasks').on(
'change:status change:unsaved',
this.destroyUnsavedNetworkVerificationTask,
this
);
},
componentWillUnmount() {
this.loadInitialConfiguration();
@ -583,10 +600,16 @@ var NetworkTab = React.createClass({
clusterTasks.each((task) => task.get('unsaved') && clusterTasks.remove(task));
},
isNetworkConfigurationChanged() {
return !_.isEqual(this.state.initialConfiguration, this.props.cluster.get('networkConfiguration').toJSON());
return !_.isEqual(
this.state.initialConfiguration,
this.props.cluster.get('networkConfiguration').toJSON()
);
},
isNetworkSettingsChanged() {
return this.props.cluster.get('settings').hasChanges(this.state.initialSettingsAttributes, this.state.configModels);
return this.props.cluster.get('settings').hasChanges(
this.state.initialSettingsAttributes,
this.state.configModels
);
},
hasChanges() {
return this.isNetworkConfigurationChanged() || this.isNetworkSettingsChanged();
@ -602,17 +625,26 @@ var NetworkTab = React.createClass({
},
loadInitialConfiguration() {
var networkConfiguration = this.props.cluster.get('networkConfiguration');
networkConfiguration.get('networks').reset(_.cloneDeep(this.state.initialConfiguration.networks));
networkConfiguration.get('networking_parameters').set(_.cloneDeep(this.state.initialConfiguration.networking_parameters));
networkConfiguration.get('networks').reset(
_.cloneDeep(this.state.initialConfiguration.networks)
);
networkConfiguration.get('networking_parameters').set(
_.cloneDeep(this.state.initialConfiguration.networking_parameters)
);
},
loadInitialSettings() {
var settings = this.props.cluster.get('settings');
settings.set(_.cloneDeep(this.state.initialSettingsAttributes), {silent: true, validate: false});
settings.set(
_.cloneDeep(this.state.initialSettingsAttributes),
{silent: true, validate: false}
);
settings.mergePluginSettings();
settings.isValid({models: this.state.configModels});
},
updateInitialConfiguration() {
this.setState({initialConfiguration: _.cloneDeep(this.props.cluster.get('networkConfiguration').toJSON())});
this.setState({
initialConfiguration: _.cloneDeep(this.props.cluster.get('networkConfiguration').toJSON())
});
},
isLocked() {
return !!this.props.cluster.task({group: ['deployment', 'network'], active: true}) ||
@ -630,13 +662,16 @@ var NetworkTab = React.createClass({
});
var floatingRanges = networkConfiguration.get('networking_parameters').get('floating_ranges');
if (floatingRanges) {
networkConfiguration.get('networking_parameters').set({floating_ranges: removeEmptyRanges(floatingRanges)});
networkConfiguration.get('networking_parameters').set({
floating_ranges: removeEmptyRanges(floatingRanges)
});
}
},
onManagerChange(name, value) {
var networkConfiguration = this.props.cluster.get('networkConfiguration');
var networkingParameters = networkConfiguration.get('networking_parameters');
var fixedAmount = networkConfiguration.get('networking_parameters').get('fixed_networks_amount') || 1;
var fixedAmount =
networkConfiguration.get('networking_parameters').get('fixed_networks_amount') || 1;
networkingParameters.set({
net_manager: value,
fixed_networks_amount: value == 'FlatDHCPManager' ? 1 : fixedAmount
@ -770,7 +805,8 @@ var NetworkTab = React.createClass({
_.isNull(this.props.cluster.get('settings').validationError);
},
renderButtons() {
var isCancelChangesDisabled = this.state.actionInProgress || !!this.props.cluster.task({group: 'deployment', active: true}) || !this.hasChanges();
var isCancelChangesDisabled = this.state.actionInProgress ||
!!this.props.cluster.task({group: 'deployment', active: true}) || !this.hasChanges();
return (
<div className='well clearfix'>
<div className='btn-group pull-right'>
@ -795,7 +831,8 @@ var NetworkTab = React.createClass({
);
},
getVerificationErrors() {
var task = this.state.hideVerificationResult ? null : this.props.cluster.task({group: 'network', status: 'error'});
var task = this.state.hideVerificationResult ? null :
this.props.cluster.task({group: 'network', status: 'error'});
var fieldsWithVerificationErrors = [];
// @TODO(morale): soon response format will be changed and this part should be rewritten
if (task && task.get('result').length) {
@ -816,7 +853,9 @@ var NetworkTab = React.createClass({
showUnsavedChangesWarning: this.hasChanges()
})
.done(() => {
this.props.setActiveNetworkSectionName(this.nodeNetworkGroups.find({is_default: true}).get('name'));
this.props.setActiveNetworkSectionName(
this.nodeNetworkGroups.find({is_default: true}).get('name')
);
return nodeNetworkGroup
.destroy({wait: true})
.then(
@ -833,7 +872,11 @@ var NetworkTab = React.createClass({
if (hasChanges) {
utils.showErrorDialog({
title: i18n(networkTabNS + 'node_network_group_creation_error'),
message: <div><i className='glyphicon glyphicon-danger-sign' /> {i18n(networkTabNS + 'save_changes_warning')}</div>
message: <div>
<i className='glyphicon glyphicon-danger-sign' />
{' '}
{i18n(networkTabNS + 'save_changes_warning')}
</div>
});
return;
}
@ -879,7 +922,8 @@ var NetworkTab = React.createClass({
row: true,
'changes-locked': isLocked
};
var nodeNetworkGroups = this.nodeNetworkGroups = new models.NodeNetworkGroups(this.props.nodeNetworkGroups.where({cluster_id: cluster.id}));
var nodeNetworkGroups = this.nodeNetworkGroups =
new models.NodeNetworkGroups(this.props.nodeNetworkGroups.where({cluster_id: cluster.id}));
var isNovaEnvironment = cluster.get('net_provider') == 'nova_network';
var networks = networkConfiguration.get('networks');
var isMultiRack = nodeNetworkGroups.length > 1;
@ -922,7 +966,10 @@ var NetworkTab = React.createClass({
key='add_node_group'
className='btn btn-default add-nodegroup-btn pull-right'
onClick={_.partial(this.addNodeNetworkGroup, hasChanges)}
disabled={!!cluster.task({group: ['deployment', 'network'], active: true}) || this.state.actionInProgress}
disabled={
!!cluster.task({group: ['deployment', 'network'], active: true}) ||
this.state.actionInProgress
}
>
{hasChanges && <i className='glyphicon glyphicon-danger-sign'/>}
{i18n(networkTabNS + 'add_node_network_group')}
@ -1008,7 +1055,8 @@ var NetworkTab = React.createClass({
</div>
</div>
</div>
{!this.state.hideVerificationResult && networkCheckTask && networkCheckTask.match({status: 'error'}) &&
{!this.state.hideVerificationResult && networkCheckTask &&
networkCheckTask.match({status: 'error'}) &&
<div className='col-xs-12'>
<div className='alert alert-danger enable-selection col-xs-12 network-alert'>
{utils.renderMultilineText(networkCheckTask.get('message'))}
@ -1025,7 +1073,10 @@ var NetworkTab = React.createClass({
var NodeNetworkGroup = React.createClass({
render() {
var {cluster, networks, nodeNetworkGroup, nodeNetworkGroups, verificationErrors, validationError} = this.props;
var {
cluster, networks, nodeNetworkGroup, nodeNetworkGroups,
verificationErrors, validationError
} = this.props;
return (
<div>
<NodeNetworkGroupTitle
@ -1044,7 +1095,9 @@ var NodeNetworkGroup = React.createClass({
cluster={cluster}
validationError={(validationError || {}).networks}
disabled={this.props.locked}
verificationErrorField={_.pluck(_.where(verificationErrors, {network: network.id}), 'field')}
verificationErrorField={
_.pluck(_.where(verificationErrors, {network: network.id}), 'field')
}
currentNodeNetworkGroup={nodeNetworkGroup}
/>
);
@ -1069,11 +1122,20 @@ var NetworkSubtabs = React.createClass({
// is one of predefined sections selected (networking_parameters)
if (groupName == 'neutron_l2') {
isInvalid = !!_.intersection(NetworkingL2Parameters.renderedParameters, _.keys(networkParametersErrors)).length;
isInvalid = !!_.intersection(
NetworkingL2Parameters.renderedParameters,
_.keys(networkParametersErrors)
).length;
} else if (groupName == 'neutron_l3') {
isInvalid = !!_.intersection(NetworkingL3Parameters.renderedParameters, _.keys(networkParametersErrors)).length;
isInvalid = !!_.intersection(
NetworkingL3Parameters.renderedParameters,
_.keys(networkParametersErrors)
).length;
} else if (groupName == 'nova_configuration') {
isInvalid = !!_.intersection(NovaParameters.renderedParameters, _.keys(networkParametersErrors)).length;
isInvalid = !!_.intersection(
NovaParameters.renderedParameters,
_.keys(networkParametersErrors)
).length;
} else if (groupName == 'network_settings') {
var settings = cluster.get('settings');
isInvalid = _.any(_.keys(settings.validationError), (settingPath) => {
@ -1084,7 +1146,8 @@ var NetworkSubtabs = React.createClass({
}
if (isNetworkGroupPill) {
isInvalid = networksErrors && (isNovaEnvironment || !!networksErrors[nodeNetworkGroups.findWhere({name: groupName}).id]);
isInvalid = networksErrors && (isNovaEnvironment ||
!!networksErrors[nodeNetworkGroups.findWhere({name: groupName}).id]);
} else {
tabLabel = i18n(networkTabNS + 'tabs.' + groupName);
}
@ -1233,10 +1296,15 @@ var NodeNetworkGroupTitle = React.createClass({
}
{isDeletionPossible && (
currentNodeNetworkGroup.get('is_default') ?
<span className='explanation'>{i18n(networkTabNS + 'default_node_network_group_info')}</span>
<span className='explanation'>
{i18n(networkTabNS + 'default_node_network_group_info')}
</span>
:
!this.state.isRenaming &&
<i className='glyphicon glyphicon-remove' onClick={this.props.removeNodeNetworkGroup} />
<i
className='glyphicon glyphicon-remove'
onClick={this.props.removeNodeNetworkGroup}
/>
)}
</div>
);
@ -1339,7 +1407,9 @@ var NovaParameters = React.createClass({
wrapperClassName='clearfix vlan-id-range'
label={i18n(parametersNS + 'fixed_vlan_range')}
extendable={false}
autoIncreaseWith={parseInt(networkingParameters.get('fixed_networks_amount'), 10) || 0}
autoIncreaseWith={
parseInt(networkingParameters.get('fixed_networks_amount'), 10) || 0
}
integerValue
placeholder=''
mini
@ -1368,12 +1438,18 @@ var NetworkingL2Parameters = React.createClass({
]
},
render() {
var networkParameters = this.props.cluster.get('networkConfiguration').get('networking_parameters');
var networkParameters =
this.props.cluster.get('networkConfiguration').get('networking_parameters');
var idRangePrefix = networkParameters.get('segmentation_type') == 'vlan' ? 'vlan' : 'gre_id';
return (
<div className='forms-box' key='neutron-l2'>
<h3 className='networks'>{i18n(parametersNS + 'l2_configuration')}</h3>
<div className='network-description'>{i18n(networkTabNS + 'networking_parameters.l2_' + networkParameters.get('segmentation_type') + '_description')}</div>
<div className='network-description'>
{
i18n(networkTabNS + 'networking_parameters.l2_' +
networkParameters.get('segmentation_type') + '_description')
}
</div>
<div>
<Range
{...this.composeProps(idRangePrefix + '_range', true)}
@ -1429,9 +1505,13 @@ var NetworkingL3Parameters = React.createClass({
{networks.findWhere({name: 'baremetal'}) &&
<div className='forms-box' key='baremetal-net'>
<h3>
<span className='subtab-group-baremetal-net'>{i18n(networkTabNS + 'baremetal_net')}</span>
<span className='subtab-group-baremetal-net'>
{i18n(networkTabNS + 'baremetal_net')}
</span>
</h3>
<div className='network-description'>{i18n(networkTabNS + 'networking_parameters.baremetal_parameters_description')}</div>
<div className='network-description'>
{i18n(networkTabNS + 'networking_parameters.baremetal_parameters_description')}
</div>
<Range
key='baremetal_range'
{...this.composeProps('baremetal_range', true)}
@ -1443,9 +1523,13 @@ var NetworkingL3Parameters = React.createClass({
}
<div className='forms-box' key='dns-nameservers'>
<h3>
<span className='subtab-group-dns-nameservers'>{i18n(networkTabNS + 'dns_nameservers')}</span>
<span className='subtab-group-dns-nameservers'>
{i18n(networkTabNS + 'dns_nameservers')}
</span>
</h3>
<div className='network-description'>{i18n(networkTabNS + 'networking_parameters.dns_servers_description')}</div>
<div className='network-description'>
{i18n(networkTabNS + 'networking_parameters.dns_servers_description')}
</div>
<MultipleValuesInput {...this.composeProps('dns_nameservers', true)} />
</div>
</div>
@ -1465,14 +1549,17 @@ var NetworkSettings = React.createClass({
settings.isValid({models: this.props.configModels});
},
checkRestrictions(action, setting) {
return this.props.cluster.get('settings').checkRestrictions(this.props.configModels, action, setting);
return this.props.cluster.get('settings')
.checkRestrictions(this.props.configModels, action, setting);
},
render() {
var cluster = this.props.cluster;
var settings = cluster.get('settings');
var locked = this.props.locked || !!cluster.task({group: ['deployment', 'network'], active: true});
var locked = this.props.locked ||
!!cluster.task({group: ['deployment', 'network'], active: true});
var lockedCluster = !cluster.isAvailableForSettingsChanges();
var allocatedRoles = _.uniq(_.flatten(_.union(cluster.get('nodes').pluck('roles'), cluster.get('nodes').pluck('pending_roles'))));
var allocatedRoles = _.uniq(_.flatten(_.union(cluster.get('nodes').pluck('roles'),
cluster.get('nodes').pluck('pending_roles'))));
return (
<div className='forms-box network'>
{
@ -1481,7 +1568,8 @@ var NetworkSettings = React.createClass({
.filter(
(sectionName) => {
var section = settings.get(sectionName);
return (section.metadata.group == 'network' || _.any(section, {group: 'network'})) &&
return (section.metadata.group == 'network' ||
_.any(section, {group: 'network'})) &&
!this.checkRestrictions('hide', section.metadata).result;
}
)
@ -1501,7 +1589,10 @@ var NetworkSettings = React.createClass({
}));
if (_.isEmpty(settingsToDisplay) && !settings.isPlugin(section)) return null;
return <SettingSection
{... _.pick(this.props, 'cluster', 'initialAttributes', 'settingsForChecks', 'configModels')}
{... _.pick(
this.props,
'cluster', 'initialAttributes', 'settingsForChecks', 'configModels'
)}
key={sectionName}
sectionName={sectionName}
settingsToDisplay={settingsToDisplay}
@ -1558,7 +1649,11 @@ var NetworkVerificationResult = React.createClass({
<div className='animation-box'>
{_.times(3, (index) => {
++index;
return <div key={index} className={this.getConnectionStatus(task, index == 1) + ' connect-' + index}></div>;
return <div
key={index}
className={this.getConnectionStatus(task, index == 1) + ' connect-' + index}
>
</div>;
})}
</div>
<div className='nodes-box'>
@ -1620,7 +1715,12 @@ var NetworkVerificationResult = React.createClass({
var absentVlans = _.map(node.absent_vlans, (vlan) => {
return vlan || i18n(networkTabNS + 'untagged');
});
return [node.name || 'N/A', node.mac || 'N/A', node.interface, absentVlans.join(', ')];
return [
node.name || 'N/A',
node.mac || 'N/A',
node.interface,
absentVlans.join(', ')
];
})
}
/>

View File

@ -23,7 +23,8 @@ import ClusterNodesScreen from 'views/cluster_page_tabs/nodes_tab_screens/cluste
import AddNodesScreen from 'views/cluster_page_tabs/nodes_tab_screens/add_nodes_screen';
import EditNodesScreen from 'views/cluster_page_tabs/nodes_tab_screens/edit_nodes_screen';
import EditNodeDisksScreen from 'views/cluster_page_tabs/nodes_tab_screens/edit_node_disks_screen';
import EditNodeInterfacesScreen from 'views/cluster_page_tabs/nodes_tab_screens/edit_node_interfaces_screen';
import EditNodeInterfacesScreen from
'views/cluster_page_tabs/nodes_tab_screens/edit_node_interfaces_screen';
import ReactTransitionGroup from 'react-addons-transition-group';
var NodesTab = React.createClass({
@ -65,7 +66,10 @@ var NodesTab = React.createClass({
});
})
.fail(() => {
app.navigate('#cluster/' + this.props.cluster.id + '/nodes', {trigger: true, replace: true});
app.navigate(
'#cluster/' + this.props.cluster.id + '/nodes',
{trigger: true, replace: true}
);
});
},
getScreen(props) {
@ -111,7 +115,10 @@ var NodesTab = React.createClass({
>
<Screen
{...this.state.screenData}
{... _.pick(this.props, 'cluster', 'nodeNetworkGroups', 'selectedNodeIds', 'selectNodes')}
{..._.pick(
this.props,
'cluster', 'nodeNetworkGroups', 'selectedNodeIds', 'selectNodes'
)}
ref='screen'
screenOptions={this.state.screenOptions}
/>

View File

@ -24,7 +24,8 @@ var AddNodesScreen = React.createClass({
fetchData(options) {
var nodes = new models.Nodes();
nodes.fetch = function(options) {
return this.constructor.__super__.fetch.call(this, _.extend({data: {cluster_id: ''}}, options));
return this.constructor.__super__.fetch.call(this, _.extend({data: {cluster_id: ''}},
options));
};
return $.when(nodes.fetch(), options.cluster.get('roles').fetch(),
options.cluster.get('settings').fetch({cache: true})).then(() => ({nodes: nodes}));

View File

@ -69,7 +69,10 @@ var EditNodeDisksScreen = React.createClass({
this.setState({initialDisks: _.cloneDeep(this.props.nodes.at(0).disks.toJSON())});
},
hasChanges() {
return !this.isLocked() && !_.isEqual(_.pluck(this.props.disks.toJSON(), 'volumes'), _.pluck(this.state.initialDisks, 'volumes'));
return !this.isLocked() && !_.isEqual(
_.pluck(this.props.disks.toJSON(), 'volumes'),
_.pluck(this.state.initialDisks, 'volumes')
);
},
loadDefaults() {
this.setState({actionInProgress: true});
@ -168,7 +171,13 @@ var EditNodeDisksScreen = React.createClass({
<div className='edit-node-disks-screen'>
<div className='row'>
<div className='title'>
{i18n('cluster_page.nodes_tab.configure_disks.' + (locked ? 'read_only_' : '') + 'title', {count: this.props.nodes.length, name: this.props.nodes.length && this.props.nodes.at(0).get('name')})}
{i18n(
'cluster_page.nodes_tab.configure_disks.' + (locked ? 'read_only_' : '') + '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.length ?
@ -184,22 +193,44 @@ var EditNodeDisksScreen = React.createClass({
})
:
<div className='alert alert-warning'>
{i18n('cluster_page.nodes_tab.configure_disks.no_disks', {count: this.props.nodes.length})}
{i18n('cluster_page.nodes_tab.configure_disks.no_disks',
{count: this.props.nodes.length})}
</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/' + this.props.cluster.id + '/nodes'} disabled={this.state.actionInProgress}>
<a className='btn btn-default'
href={'#cluster/' + this.props.cluster.id + '/nodes'}
disabled={this.state.actionInProgress}
>
{i18n('cluster_page.nodes_tab.back_to_nodes_button')}
</a>
</div>
{!locked && !!this.props.disks.length &&
<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={!this.isSavingPossible()}>{i18n('common.apply_button')}</button>
<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={!this.isSavingPossible()}
>
{i18n('common.apply_button')}
</button>
</div>
}
</div>
@ -225,7 +256,8 @@ var NodeDisk = React.createClass({
if (size > volumeInfo.max) {
size = volumeInfo.max;
}
this.props.disk.get('volumes').findWhere({name: name}).set({size: size}).isValid({minimum: volumeInfo.min});
this.props.disk.get('volumes').findWhere({name: name}).set({size: size})
.isValid({minimum: volumeInfo.min});
this.props.disk.trigger('change', this.props.disk);
},
toggleDisk(name) {
@ -236,7 +268,8 @@ var NodeDisk = React.createClass({
var volumesInfo = this.props.volumesInfo;
var diskMetaData = this.props.diskMetaData;
var requiredDiskSize = _.sum(disk.get('volumes').map((volume) => {
return volume.getMinimalSize(this.props.volumes.findWhere({name: volume.get('name')}).get('min_size'));
return volume
.getMinimalSize(this.props.volumes.findWhere({name: volume.get('name')}).get('min_size'));
}));
var diskError = disk.get('size') < requiredDiskSize;
var sortOrder = ['name', 'model', 'size'];
@ -263,26 +296,44 @@ var NodeDisk = React.createClass({
data-volume={volumeName}
style={{width: volumesInfo[volumeName].width + '%'}}
>
<div className='text-center toggle' onClick={_.partial(this.toggleDisk, disk.get('name'))}>
<div
className='text-center toggle'
onClick={_.partial(this.toggleDisk, disk.get('name'))}
>
<div>{volume.get('label')}</div>
<div className='volume-group-size'>
{utils.showDiskSize(volumesInfo[volumeName].size, 2)}
</div>
</div>
{!this.props.disabled && volumesInfo[volumeName].min <= 0 && this.state.collapsed &&
<div className='close-btn' onClick={_.partial(this.updateDisk, volumeName, 0)}>&times;</div>
<div
className='close-btn'
onClick={_.partial(this.updateDisk, volumeName, 0)}
>
&times;
</div>
}
</div>
);
})}
<div className='volume-group pull-left' data-volume='unallocated' style={{width: volumesInfo.unallocated.width + '%'}}>
<div className='text-center toggle' onClick={_.partial(this.toggleDisk, disk.get('name'))}>
<div
className='volume-group pull-left'
data-volume='unallocated'
style={{width: volumesInfo.unallocated.width + '%'}}
>
<div
className='text-center toggle'
onClick={_.partial(this.toggleDisk, 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 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='row collapse disk-details' id={disk.get('name')} key='diskDetails'
ref={disk.get('name')}>
<div className='col-xs-5'>
{diskMetaData &&
<div>
@ -294,7 +345,10 @@ var NodeDisk = React.createClass({
<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]}
{propertyName == 'size' ?
utils.showDiskSize(diskMetaData[propertyName]) :
diskMetaData[propertyName]
}
</p>
</div>
</div>
@ -325,7 +379,12 @@ var NodeDisk = React.createClass({
<div key={'edit_' + volumeName} data-volume={volumeName}>
<div className='form-group volume-group row'>
<label className='col-xs-4 volume-group-label'>
<span ref={'volume-group-flag ' + volumeName} className={'volume-type-' + (index + 1)}> &nbsp; </span>
<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'>
@ -343,10 +402,14 @@ var NodeDisk = React.createClass({
error={validationError && ''}
value={value}
/>
<div className='col-xs-1 volume-group-size-label'>{i18n('common.size.mb')}</div>
<div className='col-xs-1 volume-group-size-label'>
{i18n('common.size.mb')}
</div>
</div>
{!!value && value == currentMinSize &&
<div className='volume-group-notice text-info'>{i18n(ns + 'minimum_reached')}</div>
<div className='volume-group-notice text-info'>
{i18n(ns + 'minimum_reached')}
</div>
}
{validationError &&
<div className='volume-group-notice text-danger'>{validationError}</div>
@ -355,7 +418,9 @@ var NodeDisk = React.createClass({
);
})}
{diskError &&
<div className='volume-group-notice text-danger'>{i18n(ns + 'not_enough_space')}</div>
<div className='volume-group-notice text-danger'>
{i18n(ns + 'not_enough_space')}
</div>
}
</div>
</div>

View File

@ -92,7 +92,10 @@ var EditNodeInterfacesScreen = React.createClass({
},
interfacesPickFromJSON(json) {
// Pick certain interface fields that have influence on hasChanges.
return _.pick(json, ['assigned_networks', 'mode', 'type', 'slaves', 'bond_properties', 'interface_properties', 'offloading_modes']);
return _.pick(json, [
'assigned_networks', 'mode', 'type', 'slaves', 'bond_properties',
'interface_properties', 'offloading_modes'
]);
},
interfacesToJSON(interfaces, remainingNodesMode) {
// Sometimes 'state' is sent from the API and sometimes not
@ -179,7 +182,9 @@ var EditNodeInterfacesScreen = React.createClass({
node.interfaces.each((ifc, index) => {
var updatedIfc = ifc.isBond() ? bondsByName[ifc.get('name')] : interfaces.at(index);
ifc.set({
assigned_networks: new models.InterfaceNetworks(updatedIfc.get('assigned_networks').toJSON()),
assigned_networks: new models.InterfaceNetworks(
updatedIfc.get('assigned_networks').toJSON()
),
interface_properties: updatedIfc.get('interface_properties')
});
if (ifc.isBond()) {
@ -197,7 +202,8 @@ var EditNodeInterfacesScreen = React.createClass({
return Backbone.sync('update', node.interfaces, {url: _.result(node, 'url') + '/interfaces'});
}))
.done(() => {
this.setState({initialInterfaces: _.cloneDeep(this.interfacesToJSON(this.props.interfaces))});
this.setState({initialInterfaces:
_.cloneDeep(this.interfacesToJSON(this.props.interfaces))});
dispatcher.trigger('networkConfigurationUpdated');
})
.fail((response) => {
@ -213,19 +219,21 @@ var EditNodeInterfacesScreen = React.createClass({
});
},
configurationTemplateExists() {
return !_.isEmpty(this.props.cluster.get('networkConfiguration').get('networking_parameters').get('configuration_template'));
return !_.isEmpty(this.props.cluster.get('networkConfiguration')
.get('networking_parameters').get('configuration_template'));
},
bondingAvailable() {
var availableBondTypes = this.getBondType();
return !!availableBondTypes && !this.configurationTemplateExists();
},
getBondType() {
return _.compact(_.flatten(_.map(this.props.bondingConfig.availability, (modeAvailabilityData) => {
return _.map(modeAvailabilityData, (condition, name) => {
var result = utils.evaluateExpression(condition, this.props.configModels).value;
return result && name;
});
})))[0];
return _.compact(_.flatten(_.map(this.props.bondingConfig.availability,
(modeAvailabilityData) => {
return _.map(modeAvailabilityData, (condition, name) => {
var result = utils.evaluateExpression(condition, this.props.configModels).value;
return result && name;
});
})))[0];
},
findOffloadingModesIntersection(set1, set2) {
return _.map(
@ -248,7 +256,9 @@ var EditNodeInterfacesScreen = React.createClass({
var offloadingModes = interfaces.map((ifc) => ifc.get('offloading_modes') || []);
if (!offloadingModes.length) return [];
return offloadingModes.reduce((result, modes) => this.findOffloadingModesIntersection(result, modes));
return offloadingModes.reduce((result, modes) => {
return this.findOffloadingModesIntersection(result, modes);
});
},
bondInterfaces() {
this.setState({actionInProgress: true});
@ -261,7 +271,8 @@ var EditNodeInterfacesScreen = React.createClass({
var bondMode = _.flatten(_.pluck(bondingProperties[this.getBondType()].mode, 'values'))[0];
bonds = new models.Interface({
type: 'bond',
name: this.props.interfaces.generateBondName(this.getBondType() == 'linux' ? 'bond' : 'ovs-bond'),
name: this.props.interfaces.generateBondName(this.getBondType() ==
'linux' ? 'bond' : 'ovs-bond'),
mode: bondMode,
assigned_networks: new models.InterfaceNetworks(),
slaves: _.invoke(interfaces, 'pick', 'name'),
@ -293,7 +304,9 @@ var EditNodeInterfacesScreen = React.createClass({
},
unbondInterfaces() {
this.setState({actionInProgress: true});
_.each(this.props.interfaces.where({checked: true}), (bond) => this.removeInterfaceFromBond(bond.get('name')));
_.each(this.props.interfaces.where({checked: true}), (bond) => {
return this.removeInterfaceFromBond(bond.get('name'));
});
this.setState({actionInProgress: false});
},
removeInterfaceFromBond(bondName, slaveInterfaceName) {
@ -320,7 +333,9 @@ var EditNodeInterfacesScreen = React.createClass({
if (slaveInterfaceName) {
var slavesUpdated = _.reject(slaves, {name: slaveInterfaceName});
var names = _.pluck(slavesUpdated, 'name');
var bondSlaveInterfaces = this.props.interfaces.filter((ifc) => _.contains(names, ifc.get('name')));
var bondSlaveInterfaces = this.props.interfaces.filter((ifc) => {
return _.contains(names, ifc.get('name'));
});
bond.set({
slaves: slavesUpdated,
@ -377,7 +392,8 @@ var EditNodeInterfacesScreen = React.createClass({
return _.uniq(speeds).length > 1 || !_.compact(speeds).length;
},
isSavingPossible() {
return !_.chain(this.state.interfaceErrors).values().some().value() && !this.state.actionInProgress && this.hasChanges();
return !_.chain(this.state.interfaceErrors).values().some().value() &&
!this.state.actionInProgress && this.hasChanges();
},
getIfcProperty(property) {
var {interfaces, nodes} = this.props;
@ -416,20 +432,25 @@ var EditNodeInterfacesScreen = React.createClass({
var slaveInterfaceNames = _.pluck(_.flatten(_.filter(interfaces.pluck('slaves'))), 'name');
var loadDefaultsEnabled = !this.state.actionInProgress;
var revertChangesEnabled = !this.state.actionInProgress && hasChanges;
var invalidSpeedsForBonding = bondingPossible && this.validateSpeedsForBonding(checkedBonds.concat(checkedInterfaces)) || interfaces.any((ifc) => {
return ifc.isBond() && this.validateSpeedsForBonding([ifc]);
});
var invalidSpeedsForBonding = bondingPossible &&
this.validateSpeedsForBonding(checkedBonds.concat(checkedInterfaces)) ||
interfaces.any((ifc) => {
return ifc.isBond() && this.validateSpeedsForBonding([ifc]);
});
var interfaceSpeeds = this.getIfcProperty('current_speed');
var interfaceNames = this.getIfcProperty('name');
return (
<div className='row'>
<div className='title'>
{i18n(ns + (locked ? 'read_only_' : '') + 'title', {count: nodes.length, name: nodeNames.join(', ')})}
{i18n(ns + (locked ? 'read_only_' : '') + 'title',
{count: nodes.length, name: nodeNames.join(', ')})}
</div>
{configurationTemplateExists &&
<div className='col-xs-12'>
<div className='alert alert-warning'>{i18n(ns + 'configuration_template_warning')}</div>
<div className='alert alert-warning'>
{i18n(ns + 'configuration_template_warning')}
</div>
</div>
}
{bondingAvailable && !locked &&
@ -437,10 +458,18 @@ var EditNodeInterfacesScreen = React.createClass({
<div className='page-buttons'>
<div className='well clearfix'>
<div className='btn-group pull-right'>
<button className='btn btn-default btn-bond' onClick={this.bondInterfaces} disabled={!bondingPossible}>
<button
className='btn btn-default btn-bond'
onClick={this.bondInterfaces}
disabled={!bondingPossible}
>
{i18n(ns + 'bond_button')}
</button>
<button className='btn btn-default btn-unbond' onClick={this.unbondInterfaces} disabled={!unbondingPossible}>
<button
className='btn btn-default btn-unbond'
onClick={this.unbondInterfaces}
disabled={!unbondingPossible}
>
{i18n(ns + 'unbond_button')}
</button>
</div>
@ -478,19 +507,35 @@ var EditNodeInterfacesScreen = React.createClass({
<div className='col-xs-12 page-buttons content-elements'>
<div className='well clearfix'>
<div className='btn-group'>
<a className='btn btn-default' href={'#cluster/' + this.props.cluster.id + '/nodes'} disabled={this.state.actionInProgress}>
<a
className='btn btn-default'
href={'#cluster/' + this.props.cluster.id + '/nodes'}
disabled={this.state.actionInProgress}
>
{i18n('cluster_page.nodes_tab.back_to_nodes_button')}
</a>
</div>
{!locked &&
<div className='btn-group pull-right'>
<button className='btn btn-default btn-defaults' onClick={this.loadDefaults} disabled={!loadDefaultsEnabled}>
<button
className='btn btn-default btn-defaults'
onClick={this.loadDefaults}
disabled={!loadDefaultsEnabled}
>
{i18n('common.load_defaults_button')}
</button>
<button className='btn btn-default btn-revert-changes' onClick={this.revertChanges} disabled={!revertChangesEnabled}>
<button
className='btn btn-default btn-revert-changes'
onClick={this.revertChanges}
disabled={!revertChangesEnabled}
>
{i18n('common.cancel_changes_button')}
</button>
<button className='btn btn-success btn-apply' onClick={this.applyChanges} disabled={!this.isSavingPossible()}>
<button
className='btn btn-success btn-apply'
onClick={this.applyChanges}
disabled={!this.isSavingPossible()}
>
{i18n('common.apply_button')}
</button>
</div>
@ -508,7 +553,8 @@ var NodeInterface = React.createClass({
drop(props, monitor) {
var targetInterface = props.interface;
var sourceInterface = props.interfaces.findWhere({name: monitor.getItem().interfaceName});
var network = sourceInterface.get('assigned_networks').findWhere({name: monitor.getItem().networkName});
var network = sourceInterface.get('assigned_networks')
.findWhere({name: monitor.getItem().networkName});
sourceInterface.get('assigned_networks').remove(network);
targetInterface.get('assigned_networks').add(network);
// trigger 'change' event to update screen buttons state
@ -542,7 +588,8 @@ var NodeInterface = React.createClass({
return _.contains(this.getBondPropertyValues('lacp_rate', 'for_modes'), this.getBondMode());
},
isHashPolicyNeeded() {
return _.contains(this.getBondPropertyValues('xmit_hash_policy', 'for_modes'), this.getBondMode());
return _.contains(this.getBondPropertyValues('xmit_hash_policy', 'for_modes'),
this.getBondMode());
},
getBondMode() {
var ifc = this.props.interface;
@ -552,11 +599,13 @@ var NodeInterface = React.createClass({
var modes = this.props.bondingProperties[this.props.bondType].mode;
var configModels = _.clone(this.props.configModels);
var availableModes = [];
var interfaces = this.props.interface.isBond() ? this.props.interface.getSlaveInterfaces() : [this.props.interface];
var interfaces = this.props.interface.isBond() ? this.props.interface.getSlaveInterfaces() :
[this.props.interface];
_.each(interfaces, (ifc) => {
configModels.interface = ifc;
availableModes.push(_.reduce(modes, (result, modeSet) => {
if (modeSet.condition && !utils.evaluateExpression(modeSet.condition, configModels).value) return result;
if (modeSet.condition &&
!utils.evaluateExpression(modeSet.condition, configModels).value) return result;
return result.concat(modeSet.values);
}, []));
});
@ -583,7 +632,8 @@ var NodeInterface = React.createClass({
this.props.interface.set({mode: value});
this.updateBondProperties({mode: value});
if (this.isHashPolicyNeeded()) {
this.updateBondProperties({xmit_hash_policy: this.getBondPropertyValues('xmit_hash_policy', 'values')[0]});
this.updateBondProperties({xmit_hash_policy: this.getBondPropertyValues('xmit_hash_policy',
'values')[0]});
}
if (this.isLacpRateAvailable()) {
this.updateBondProperties({lacp_rate: this.getBondPropertyValues('lacp_rate', 'values')[0]});
@ -684,7 +734,10 @@ var NodeInterface = React.createClass({
disabled={!bondingPossible}
onChange={this.onPolicyChange}
label={i18n(ns + 'bonding_policy')}
children={this.getBondingOptions(this.getBondPropertyValues('xmit_hash_policy', 'values'), 'hash_policy')}
children={this.getBondingOptions(
this.getBondPropertyValues('xmit_hash_policy', 'values'),
'hash_policy'
)}
wrapperClassName='pull-right'
/>
}
@ -695,7 +748,10 @@ var NodeInterface = React.createClass({
disabled={!bondingPossible}
onChange={this.onLacpChange}
label={i18n(ns + 'lacp_rate')}
children={this.getBondingOptions(this.getBondPropertyValues('lacp_rate', 'values'), 'lacp_rates')}
children={this.getBondingOptions(
this.getBondPropertyValues('lacp_rate', 'values'),
'lacp_rates'
)}
wrapperClassName='pull-right'
/>
}
@ -718,14 +774,21 @@ var NodeInterface = React.createClass({
<div className='pull-left'>
{_.map(slaveInterfaces, (slaveInterface, index) => {
return (
<div key={'info-' + slaveInterface.get('name')} className='ifc-info-block clearfix'>
<div
key={'info-' + slaveInterface.get('name')}
className='ifc-info-block clearfix'
>
<div className='ifc-connection pull-left'>
<div className={utils.classNames(connectionStatusClasses(slaveInterface))} />
<div
className={utils.classNames(connectionStatusClasses(slaveInterface))}
/>
</div>
<div className='ifc-info pull-left'>
{this.props.interfaceNames[index].length == 1 &&
<div>
{i18n(ns + 'name')}: <span className='ifc-name'>{this.props.interfaceNames[index]}</span>
{i18n(ns + 'name')}:
{' '}
<span className='ifc-name'>{this.props.interfaceNames[index]}</span>
</div>
}
{this.props.nodes.length == 1 &&
@ -735,7 +798,13 @@ var NodeInterface = React.createClass({
{i18n(ns + 'speed')}: {this.props.interfaceSpeeds[index].join(', ')}
</div>
{(bondingPossible && slaveInterfaces.length >= 3) &&
<button className='btn btn-link' onClick={_.partial(this.props.removeInterfaceFromBond, ifc.get('name'), slaveInterface.get('name'))}>
<button
className='btn btn-link'
onClick={_.partial(
this.props.removeInterfaceFromBond,
ifc.get('name'), slaveInterface.get('name')
)}
>
{i18n('common.remove_button')}
</button>
}
@ -789,7 +858,8 @@ var NodeInterface = React.createClass({
onClick={this.toggleOffloading}
disabled={locked}
className='btn btn-default toggle-offloading'>
{i18n(ns + (interfaceProperties.disable_offloading ? 'disable_offloading' : 'default_offloading'))}
{i18n(ns + (interfaceProperties.disable_offloading ? 'disable_offloading' :
'default_offloading'))}
</button>
}
</div>
@ -802,7 +872,11 @@ var NodeInterface = React.createClass({
}
});
var NodeInterfaceDropTarget = DropTarget('network', NodeInterface.target, NodeInterface.collect)(NodeInterface);
var NodeInterfaceDropTarget = DropTarget(
'network',
NodeInterface.target,
NodeInterface.collect
)(NodeInterface);
var Network = React.createClass({
statics: {
@ -838,7 +912,10 @@ 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.' + interfaceNetwork.get('name'),
{defaultValue: interfaceNetwork.get('name')}
)}
</div>
{vlanRange &&
<div className='vlan-id'>

View File

@ -30,7 +30,8 @@ var EditNodesScreen = React.createClass({
}
nodes.fetch = function(options) {
return this.constructor.__super__.fetch.call(this, _.extend({data: {cluster_id: cluster.id}}, options));
return this.constructor.__super__.fetch.call(this,
_.extend({data: {cluster_id: cluster.id}}, options));
};
nodes.parse = function() {
return this.getByIds(nodes.pluck('id'));

View File

@ -44,12 +44,21 @@ var Node = React.createClass({
var options = {type: 'remote', node: this.props.node.id};
if (status == 'discover') {
options.source = 'bootstrap/messages';
} else if (status == 'provisioning' || status == 'provisioned' || (status == 'error' && error == 'provision')) {
} else if (
status == 'provisioning' ||
status == 'provisioned' ||
(status == 'error' && error == 'provision')
) {
options.source = 'install/fuel-agent';
} else if (status == 'deploying' || status == 'ready' || (status == 'error' && error == 'deploy')) {
} else if (
status == 'deploying' ||
status == 'ready' ||
(status == 'error' && error == 'deploy')
) {
options.source = 'install/puppet';
}
return '#cluster/' + this.props.node.get('cluster') + '/logs/' + utils.serializeTabOptions(options);
return '#cluster/' + this.props.node.get('cluster') + '/logs/' +
utils.serializeTabOptions(options);
},
applyNewNodeName(newName) {
if (newName && newName != this.props.node.get('name')) {
@ -100,7 +109,8 @@ var Node = React.createClass({
.sync('delete', this.props.node)
.then(
(task) => {
dispatcher.trigger('networkConfigurationUpdated updateNodeStats updateNotifications labelsConfigurationUpdated');
dispatcher.trigger('networkConfigurationUpdated updateNodeStats ' +
'updateNotifications labelsConfigurationUpdated');
if (task.status == 'ready') {
// Do not send the 'DELETE' request again, just get rid
// of this node.
@ -129,7 +139,8 @@ var Node = React.createClass({
});
},
toggleExtendedNodePanel() {
var states = this.state.extendedView ? {extendedView: false, isRenaming: false} : {extendedView: true};
var states = this.state.extendedView ?
{extendedView: false, isRenaming: false} : {extendedView: true};
this.setState(states);
},
renderNameControl() {
@ -159,7 +170,8 @@ var Node = React.createClass({
return (
<span>
{i18n('cluster_page.nodes_tab.node.status.' + status, {
os: this.props.cluster && this.props.cluster.get('release').get('operating_system') || 'OS'
os: this.props.cluster && this.props.cluster.get('release').get('operating_system')
|| 'OS'
})}
</span>
);
@ -174,7 +186,12 @@ var Node = React.createClass({
{': ' + nodeProgress + '%'}
</div>
}
<div className='progress-bar' role='progressbar' style={{width: _.max([nodeProgress, 3]) + '%'}}></div>
<div
className='progress-bar'
role='progressbar'
style={{width: _.max([nodeProgress, 3]) + '%'}}
>
</div>
</div>
);
},
@ -184,16 +201,34 @@ var Node = React.createClass({
var ram = this.props.node.resource('ram');
return (
<div className='node-hardware'>
<span>{i18n('node_details.cpu')}: {this.props.node.resource('cores') || '0'} ({_.isUndefined(htCores) ? '?' : htCores})</span>
<span>{i18n('node_details.hdd')}: {_.isUndefined(hdd) ? '?' + i18n('common.size.gb') : utils.showDiskSize(hdd)}</span>
<span>{i18n('node_details.ram')}: {_.isUndefined(ram) ? '?' + i18n('common.size.gb') : utils.showMemorySize(ram)}</span>
<span>
{i18n('node_details.cpu')}
{': '}
{this.props.node.resource('cores') || '0'} ({_.isUndefined(htCores) ? '?' : htCores})
</span>
<span>
{i18n('node_details.hdd')}
{': '}
{_.isUndefined(hdd) ? '?' + i18n('common.size.gb') : utils.showDiskSize(hdd)}
</span>
<span>
{i18n('node_details.ram')}
{': '}
{_.isUndefined(ram) ? '?' + i18n('common.size.gb') : utils.showMemorySize(ram)}
</span>
</div>
);
},
renderLogsLink(iconRepresentation) {
return (
<Tooltip key='logs' text={iconRepresentation ? i18n('cluster_page.nodes_tab.node.view_logs') : null}>
<a className={'btn-view-logs ' + (iconRepresentation ? 'icon icon-logs' : 'btn')} href={this.getNodeLogsLink()}>
<Tooltip
key='logs'
text={iconRepresentation ? i18n('cluster_page.nodes_tab.node.view_logs') : null}
>
<a
className={'btn-view-logs ' + (iconRepresentation ? 'icon icon-logs' : 'btn')}
href={this.getNodeLogsLink()}
>
{!iconRepresentation && i18n('cluster_page.nodes_tab.node.view_logs')}
</a>
</Tooltip>
@ -269,7 +304,9 @@ var Node = React.createClass({
<label className='node-box'>
<div
className='node-box-inner clearfix'
onClick={isSelectable && _.partial(this.props.onNodeSelection, null, !this.props.checked)}
onClick={isSelectable &&
_.partial(this.props.onNodeSelection, null, !this.props.checked)
}
>
<div className='node-checkbox'>
{this.props.checked && <i className='glyphicon glyphicon-ok' />}
@ -290,9 +327,13 @@ var Node = React.createClass({
<span>
{node.resource('cores') || '0'} ({node.resource('ht_cores') || '?'})
</span> / <span>
{node.resource('hdd') ? utils.showDiskSize(node.resource('hdd')) : '?' + i18n('common.size.gb')}
{node.resource('hdd') ? utils.showDiskSize(node.resource('hdd')) : '?' +
i18n('common.size.gb')
}
</span> / <span>
{node.resource('ram') ? utils.showMemorySize(node.resource('ram')) : '?' + i18n('common.size.gb')}
{node.resource('ram') ? utils.showMemorySize(node.resource('ram')) : '?' +
i18n('common.size.gb')
}
</span>
</p>
<p className='btn btn-link' onClick={this.toggleExtendedNodePanel}>
@ -340,13 +381,21 @@ var Node = React.createClass({
{status == 'offline' && this.renderRemoveButton()}
{[
!!node.get('cluster') && this.renderLogsLink(),
this.props.renderActionButtons && node.hasChanges() && !this.props.locked &&
this.props.renderActionButtons && node.hasChanges() &&
!this.props.locked &&
<button
className='btn btn-discard'
key='btn-discard'
onClick={node.get('pending_deletion') ? this.discardNodeDeletion : this.showDeleteNodesDialog}
onClick={
node.get('pending_deletion') ?
this.discardNodeDeletion : this.showDeleteNodesDialog
}
>
{i18n(ns + (node.get('pending_deletion') ? 'discard_deletion' : 'delete_node'))}
{i18n(
ns + (node.get('pending_deletion') ?
'discard_deletion' : 'delete_node'
)
)}
</button>
]}
</div>
@ -404,11 +453,15 @@ var Node = React.createClass({
this.props.renderActionButtons && node.hasChanges() && !this.props.locked &&
<Tooltip
key={'pending_addition_' + node.id}
text={i18n(ns + (node.get('pending_deletion') ? 'discard_deletion' : 'delete_node'))}
text={
i18n(ns + (node.get('pending_deletion') ? 'discard_deletion' : 'delete_node'))
}
>
<div
className='icon btn-discard'
onClick={node.get('pending_deletion') ? this.discardNodeDeletion : this.showDeleteNodesDialog}
onClick={node.get('pending_deletion') ?
this.discardNodeDeletion : this.showDeleteNodesDialog
}
/>
</Tooltip>
]}
@ -439,7 +492,9 @@ var Node = React.createClass({
var node = this.props.node;
var isSelectable = node.isSelectable() && !this.props.locked && this.props.mode != 'edit';
var status = node.getStatusSummary();
var roles = this.props.cluster ? node.sortedRoles(this.props.cluster.get('roles').pluck('name')) : [];
var roles = this.props.cluster ? node.sortedRoles(
this.props.cluster.get('roles').pluck('name')
) : [];
// compose classes
var nodePanelClasses = {
@ -470,9 +525,13 @@ var Node = React.createClass({
}[status];
statusClasses[statusClass] = true;
var renderMethod = this.props.viewMode == 'compact' ? this.renderCompactNode : this.renderStandardNode;
var renderMethod = this.props.viewMode == 'compact' ? this.renderCompactNode :
this.renderStandardNode;
return renderMethod({ns, status, roles, nodePanelClasses, logoClasses, statusClasses, isSelectable});
return renderMethod({
ns, status, roles, nodePanelClasses,
logoClasses, statusClasses, isSelectable
});
}
});

View File

@ -27,13 +27,17 @@ import {DeleteNodesDialog} from 'views/dialogs';
import {backboneMixin, pollingMixin, dispatcherMixin, unsavedChangesMixin} from 'component_mixins';
import Node from 'views/cluster_page_tabs/nodes_tab_screens/node';
var NodeListScreen, MultiSelectControl, NumberRangeControl, ManagementPanel, NodeLabelsPanel, RolePanel, SelectAllMixin, NodeList, NodeGroup;
var NodeListScreen, MultiSelectControl, NumberRangeControl, ManagementPanel,
NodeLabelsPanel, RolePanel, SelectAllMixin, NodeList, NodeGroup;
class Sorter {
constructor(name, order, isLabel) {
this.name = name;
this.order = order;
this.title = isLabel ? name : i18n('cluster_page.nodes_tab.sorters.' + name, {defaultValue: name});
this.title = isLabel ? name : i18n(
'cluster_page.nodes_tab.sorters.' + name,
{defaultValue: name}
);
this.isLabel = isLabel;
return this;
}
@ -52,9 +56,13 @@ class Filter {
constructor(name, values, isLabel) {
this.name = name;
this.values = values;
this.title = isLabel ? name : i18n('cluster_page.nodes_tab.filters.' + name, {defaultValue: name});
this.title = isLabel ? name : i18n(
'cluster_page.nodes_tab.filters.' + name,
{defaultValue: name}
);
this.isLabel = isLabel;
this.isNumberRange = !isLabel && !_.contains(['roles', 'status', 'manufacturer', 'group_id', 'cluster'], name);
this.isNumberRange = !isLabel &&
!_.contains(['roles', 'status', 'manufacturer', 'group_id', 'cluster'], name);
return this;
}
@ -76,7 +84,10 @@ class Filter {
var resources = nodes.invoke('resource', this.name);
limits = [_.min(resources), _.max(resources)];
if (this.name == 'hdd' || this.name == 'ram') {
limits = [Math.floor(limits[0] / Math.pow(1024, 3)), Math.ceil(limits[1] / Math.pow(1024, 3))];
limits = [
Math.floor(limits[0] / Math.pow(1024, 3)),
Math.ceil(limits[1] / Math.pow(1024, 3))
];
}
}
this.limits = limits;
@ -133,15 +144,20 @@ NodeListScreen = React.createClass({
var viewMode = uiSettings.view_mode;
var isLabelsPanelOpen = false;
var states = {search, activeSorters, activeFilters, availableSorters, availableFilters, viewMode, isLabelsPanelOpen};
var states = {search, activeSorters, activeFilters, availableSorters, availableFilters,
viewMode, isLabelsPanelOpen};
// Equipment page
if (!cluster) return states;
// additonal Nodes tab states (Cluster page)
var roles = cluster.get('roles').pluck('name');
var selectedRoles = nodes.length ? _.filter(roles, (role) => !nodes.any((node) => !node.hasRole(role))) : [];
var indeterminateRoles = nodes.length ? _.filter(roles, (role) => !_.contains(selectedRoles, role) && nodes.any((node) => node.hasRole(role))) : [];
var selectedRoles = nodes.length ? _.filter(roles, (role) => !nodes.any((node) => {
return !node.hasRole(role);
})) : [];
var indeterminateRoles = nodes.length ? _.filter(roles, (role) => {
return !_.contains(selectedRoles, role) && nodes.any((node) => node.hasRole(role));
}) : [];
var configModels = {
cluster: cluster,
@ -180,8 +196,12 @@ NodeListScreen = React.createClass({
var filter = _.clone(activeFilter);
if (filter.values.length) {
if (filter.isLabel) {
filter.values = _.intersection(filter.values, this.props.nodes.getLabelValues(filter.name));
} else if (checkStandardNodeFilters && _.contains(['manufacturer', 'group_id', 'cluster'], filter.name)) {
filter.values = _.intersection(
filter.values,
this.props.nodes.getLabelValues(filter.name)
);
} else if (checkStandardNodeFilters &&
_.contains(['manufacturer', 'group_id', 'cluster'], filter.name)) {
filter.values = _.filter(filter.values, (value) => {
return this.props.nodes.any((node) => node.get(filter.name) == value);
}, this);
@ -189,7 +209,12 @@ NodeListScreen = React.createClass({
}
return filter;
}, this);
if (!_.isEqual(_.pluck(normalizedFilters, 'values'), _.pluck(this.state.activeFilters, 'values'))) {
if (
!_.isEqual(
_.pluck(normalizedFilters, 'values'),
_.pluck(this.state.activeFilters, 'values')
)
) {
this.updateFilters(normalizedFilters);
}
}
@ -218,14 +243,21 @@ NodeListScreen = React.createClass({
var processedRoleLimits = {};
var selectedNodes = this.props.nodes.filter((node) => this.props.selectedNodeIds[node.id]);
var clusterNodes = this.props.cluster.get('nodes').filter((node) => !_.contains(this.props.selectedNodeIds, node.id));
var clusterNodes = this.props.cluster.get('nodes').filter((node) => {
return !_.contains(this.props.selectedNodeIds, node.id);
});
var nodesForLimitCheck = new models.Nodes(_.union(selectedNodes, clusterNodes));
cluster.get('roles').each((role) => {
if ((role.get('limits') || {}).max) {
var roleName = role.get('name');
var isRoleAlreadyAssigned = nodesForLimitCheck.any((node) => node.hasRole(roleName));
processedRoleLimits[roleName] = role.checkLimits(this.state.configModels, nodesForLimitCheck, !isRoleAlreadyAssigned, ['max']);
processedRoleLimits[roleName] = role.checkLimits(
this.state.configModels,
nodesForLimitCheck,
!isRoleAlreadyAssigned,
['max']
);
}
});
@ -238,11 +270,13 @@ NodeListScreen = React.createClass({
// need to cache roles with limits in order to avoid calculating this twice on the RolePanel
processedRoleLimits: processedRoleLimits,
// real number of nodes to add used by Select All controls
maxNumberOfNodes: maxNumberOfNodes.length ? _.min(maxNumberOfNodes) - _.size(this.props.selectedNodeIds) : null
maxNumberOfNodes: maxNumberOfNodes.length ?
_.min(maxNumberOfNodes) - _.size(this.props.selectedNodeIds) : null
};
},
updateInitialRoles() {
this.initialRoles = _.zipObject(this.props.nodes.pluck('id'), this.props.nodes.pluck('pending_roles'));
this.initialRoles = _.zipObject(this.props.nodes.pluck('id'),
this.props.nodes.pluck('pending_roles'));
},
checkRoleAssignment(node, roles, options) {
if (!options.assign) node.set({pending_roles: node.previous('pending_roles')}, {assign: true});
@ -309,7 +343,8 @@ NodeListScreen = React.createClass({
return values.map((value) => {
return {
name: value,
label: _.isNull(value) ? i18n(ns + 'label_value_not_specified') : value === false ? i18n(ns + 'label_not_assigned') : value
label: _.isNull(value) ? i18n(ns + 'label_value_not_specified') : value === false ?
i18n(ns + 'label_not_assigned') : value
};
});
}
@ -317,7 +352,8 @@ NodeListScreen = React.createClass({
var options;
switch (filter.name) {
case 'status':
var os = this.props.cluster && this.props.cluster.get('release').get('operating_system') || 'OS';
var os = this.props.cluster && this.props.cluster.get('release').get('operating_system') ||
'OS';
options = this.props.statusesToFilter.map((status) => {
return {
name: status,
@ -343,7 +379,12 @@ NodeListScreen = React.createClass({
return {
name: groupId,
label: nodeNetworkGroup ?
nodeNetworkGroup.get('name') + (this.props.cluster ? '' : ' (' + this.props.clusters.get(nodeNetworkGroup.get('cluster_id')).get('name') + ')')
nodeNetworkGroup.get('name') +
(
this.props.cluster ?
'' :
' (' + this.props.clusters.get(nodeNetworkGroup.get('cluster_id')).get('name') + ')'
)
:
i18n('common.not_specified')
};
@ -353,7 +394,8 @@ NodeListScreen = React.createClass({
options = _.uniq(this.props.nodes.pluck('cluster')).map((clusterId) => {
return {
name: clusterId,
label: clusterId ? this.props.clusters.get(clusterId).get('name') : i18n('cluster_page.nodes_tab.node.unallocated')
label: clusterId ? this.props.clusters.get(clusterId).get('name') :
i18n('cluster_page.nodes_tab.node.unallocated')
};
});
break;
@ -433,8 +475,11 @@ NodeListScreen = React.createClass({
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]);
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;
}
return result;
@ -446,15 +491,24 @@ NodeListScreen = React.createClass({
var processedRoleData = cluster ? this.processRoleLimits() : {};
// labels to manage in labels panel
var selectedNodes = new models.Nodes(this.props.nodes.filter((node) => this.props.selectedNodeIds[node.id]));
var selectedNodeLabels = _.chain(selectedNodes.pluck('labels')).flatten().map(_.keys).flatten().uniq().value();
var selectedNodes = new models.Nodes(this.props.nodes.filter((node) => {
return this.props.selectedNodeIds[node.id];
}));
var selectedNodeLabels = _.chain(selectedNodes.pluck('labels'))
.flatten()
.map(_.keys)
.flatten()
.uniq()
.value();
// filter nodes
var filteredNodes = nodes.filter((node) => {
// search field
if (this.state.search) {
var search = this.state.search.toLowerCase();
if (!_.any(node.pick('name', 'mac', 'ip'), (attribute) => _.contains((attribute || '').toLowerCase(), search))) {
if (!_.any(node.pick('name', 'mac', 'ip'), (attribute) => {
return _.contains((attribute || '').toLowerCase(), search);
})) {
return false;
}
}
@ -480,13 +534,21 @@ NodeListScreen = React.createClass({
</div>
}
<ManagementPanel
{... _.pick(this.state, 'viewMode', 'search', 'activeSorters', 'activeFilters', 'availableSorters', 'availableFilters', 'isLabelsPanelOpen')}
{... _.pick(this.props, 'cluster', 'mode', 'defaultSorting', 'statusesToFilter', 'defaultFilters')}
{... _.pick(this, 'addSorting', 'removeSorting', 'resetSorters', 'changeSortingOrder')}
{... _.pick(this, 'addFilter', 'changeFilter', 'removeFilter', 'resetFilters', 'getFilterOptions')}
{... _.pick(this, 'toggleLabelsPanel')}
{... _.pick(this, 'changeSearch', 'clearSearchField')}
{... _.pick(this, 'changeViewMode')}
{... _.pick(
this.state,
'viewMode', 'search', 'activeSorters', 'activeFilters', 'availableSorters',
'availableFilters', 'isLabelsPanelOpen'
)}
{... _.pick(
this.props,
'cluster', 'mode', 'defaultSorting', 'statusesToFilter', 'defaultFilters'
)}
{... _.pick(
this,
'addSorting', 'removeSorting', 'resetSorters', 'changeSortingOrder',
'addFilter', 'changeFilter', 'removeFilter', 'resetFilters', 'getFilterOptions',
'toggleLabelsPanel', 'changeSearch', 'clearSearchField', 'changeViewMode'
)}
labelSorters={screenNodesLabels.map((name) => new Sorter(name, 'asc', true))}
labelFilters={screenNodesLabels.map((name) => new Filter(name, [], true))}
nodes={selectedNodes}
@ -508,7 +570,9 @@ NodeListScreen = React.createClass({
}
<NodeList
{... _.pick(this.state, 'viewMode', 'activeSorters', 'selectedRoles')}
{... _.pick(this.props, 'cluster', 'mode', 'statusesToFilter', 'selectedNodeIds', 'clusters', 'roles', 'nodeNetworkGroups')}
{... _.pick(this.props, 'cluster', 'mode', 'statusesToFilter', 'selectedNodeIds',
'clusters', 'roles', 'nodeNetworkGroups')
}
{... _.pick(processedRoleData, 'maxNumberOfNodes', 'processedRoleLimits')}
nodes={filteredNodes}
totalNodesLength={nodes.length}
@ -524,7 +588,10 @@ MultiSelectControl = React.createClass({
propTypes: {
name: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.bool]),
options: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
values: React.PropTypes.arrayOf(React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.bool])),
values: React.PropTypes.arrayOf(React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.bool
])),
label: React.PropTypes.node.isRequired,
dynamicValues: React.PropTypes.bool,
onChange: React.PropTypes.func,
@ -559,7 +626,10 @@ MultiSelectControl = React.createClass({
var label = this.props.label;
if (!this.props.dynamicValues && valuesAmount) {
label = this.props.label + ': ' + (valuesAmount > 3 ?
i18n('cluster_page.nodes_tab.node_management_panel.selected_options', {label: this.props.label, count: valuesAmount})
i18n(
'cluster_page.nodes_tab.node_management_panel.selected_options',
{label: this.props.label, count: valuesAmount}
)
:
_.map(this.props.values, (itemName) => {
return _.find(this.props.options, {name: itemName}).label;
@ -592,7 +662,9 @@ MultiSelectControl = React.createClass({
return (
<div className={utils.classNames(classNames)} tabIndex='-1' onKeyDown={this.closeOnEscapeKey}>
<button
className={'btn dropdown-toggle ' + ((this.props.dynamicValues && !this.props.isOpen) ? 'btn-link' : 'btn-default')}
className={'btn dropdown-toggle ' + ((this.props.dynamicValues && !this.props.isOpen) ?
'btn-link' : 'btn-default')
}
onClick={this.props.toggle}
>
{label} <span className='caret'></span>
@ -627,7 +699,9 @@ MultiSelectControl = React.createClass({
onChange={_.partialRight(this.onChange, false)}
/>;
})}
{!!attributes.length && !!labels.length && <div key='divider' className='divider' />}
{!!attributes.length && !!labels.length &&
<div key='divider' className='divider' />
}
{_.map(labels, (option) => {
return <Input {...optionProps(option)}
key={'label-' + option.name}
@ -687,7 +761,9 @@ NumberRangeControl = React.createClass({
return (
<div className={utils.classNames(classNames)} tabIndex='-1' onKeyDown={this.closeOnEscapeKey}>
<button className='btn btn-default dropdown-toggle' onClick={this.props.toggle}>
{this.props.label + ': ' + _.uniq(this.props.values).join(' - ')} <span className='caret'></span>
{this.props.label + ': ' + _.uniq(this.props.values).join(' - ')}
{' '}
<span className='caret' />
</button>
{this.props.isOpen &&
<Popover toggle={this.props.toggle}>
@ -735,7 +811,10 @@ ManagementPanel = React.createClass({
var ns = 'cluster_page.nodes_tab.node_management_panel.node_management_error.';
utils.showErrorDialog({
title: i18n(ns + 'title'),
message: <div><i className='glyphicon glyphicon-danger-sign' /> {i18n(ns + action + '_configuration_warning')}</div>
message: <div>
<i className='glyphicon glyphicon-danger-sign' />
{i18n(ns + action + '_configuration_warning')}
</div>
});
return;
}
@ -743,7 +822,9 @@ ManagementPanel = React.createClass({
},
showDeleteNodesDialog() {
DeleteNodesDialog.show({nodes: this.props.nodes, cluster: this.props.cluster})
.done(_.partial(this.props.selectNodes, _.pluck(this.props.nodes.where({status: 'ready'}), 'id'), null, true));
.done(_.partial(this.props.selectNodes,
_.pluck(this.props.nodes.where({status: 'ready'}), 'id'), null, true)
);
},
hasChanges() {
return this.props.hasChanges;
@ -761,7 +842,9 @@ ManagementPanel = React.createClass({
var nodes = new models.Nodes(this.props.nodes.map((node) => {
var data = {id: node.id, pending_roles: node.get('pending_roles')};
if (node.get('pending_roles').length) {
if (this.props.mode == 'add') return _.extend(data, {cluster_id: this.props.cluster.id, pending_addition: true});
if (this.props.mode == 'add') {
return _.extend(data, {cluster_id: this.props.cluster.id, pending_addition: true});
}
} else if (node.get('pending_addition')) {
return _.extend(data, {cluster_id: null, pending_addition: false});
}
@ -771,7 +854,8 @@ ManagementPanel = React.createClass({
.done(() => {
$.when(this.props.cluster.fetch(), this.props.cluster.fetchRelated('nodes')).always(() => {
if (this.props.mode == 'add') {
dispatcher.trigger('updateNodeStats networkConfigurationUpdated labelsConfigurationUpdated');
dispatcher.trigger('updateNodeStats networkConfigurationUpdated ' +
'labelsConfigurationUpdated');
this.props.selectNodes();
}
});
@ -779,7 +863,8 @@ ManagementPanel = React.createClass({
.fail((response) => {
this.setState({actionInProgress: false});
utils.showErrorDialog({
message: i18n('cluster_page.nodes_tab.node_management_panel.node_management_error.saving_warning'),
message: i18n('cluster_page.nodes_tab.node_management_panel.' +
'node_management_error.saving_warning'),
response: response
});
});
@ -800,7 +885,8 @@ ManagementPanel = React.createClass({
activateSearch() {
this.setState({activeSearch: true});
$('html').on('click.search', (e) => {
if (!this.props.search && this.refs.search && !$(e.target).closest(ReactDOM.findDOMNode(this.refs.search)).length) {
if (!this.props.search && this.refs.search &&
!$(e.target).closest(ReactDOM.findDOMNode(this.refs.search)).length) {
this.setState({activeSearch: false});
}
});
@ -838,17 +924,20 @@ ManagementPanel = React.createClass({
},
toggleMoreFilterControl(visible) {
this.setState({
isMoreFilterControlVisible: _.isBoolean(visible) ? visible : !this.state.isMoreFilterControlVisible,
isMoreFilterControlVisible: _.isBoolean(visible) ? visible :
!this.state.isMoreFilterControlVisible,
openFilter: null
});
},
toggleMoreSorterControl(visible) {
this.setState({
isMoreSorterControlVisible: _.isBoolean(visible) ? visible : !this.state.isMoreSorterControlVisible
isMoreSorterControlVisible: _.isBoolean(visible) ? visible :
!this.state.isMoreSorterControlVisible
});
},
isFilterOpen(filter) {
return !_.isNull(this.state.openFilter) && this.state.openFilter.name == filter.name && this.state.openFilter.isLabel == filter.isLabel;
return !_.isNull(this.state.openFilter) &&
this.state.openFilter.name == filter.name && this.state.openFilter.isLabel == filter.isLabel;
},
addFilter(filter) {
this.props.addFilter(filter);
@ -889,7 +978,10 @@ ManagementPanel = React.createClass({
renderDeleteFilterButton(filter) {
if (!filter.isLabel && _.contains(_.keys(this.props.defaultFilters), filter.name)) return null;
return (
<i className='btn btn-link glyphicon glyphicon-minus-sign btn-remove-filter' onClick={_.partial(this.removeFilter, filter)} />
<i
className='btn btn-link glyphicon glyphicon-minus-sign btn-remove-filter'
onClick={_.partial(this.removeFilter, filter)}
/>
);
},
toggleLabelsPanel() {
@ -902,7 +994,10 @@ ManagementPanel = React.createClass({
},
renderDeleteSorterButton(sorter) {
return (
<i className='btn btn-link glyphicon glyphicon-minus-sign btn-remove-sorting' onClick={_.partial(this.removeSorting, sorter)} />
<i
className='btn btn-link glyphicon glyphicon-minus-sign btn-remove-sorting'
onClick={_.partial(this.removeSorting, sorter)}
/>
);
},
render() {
@ -925,14 +1020,32 @@ ManagementPanel = React.createClass({
};
if (this.props.mode != 'edit') {
var checkSorter = (sorter, isLabel) => !_.any(this.props.activeSorters, {name: sorter.name, isLabel: isLabel});
inactiveSorters = _.union(_.filter(this.props.availableSorters, _.partial(checkSorter, _, false)), _.filter(this.props.labelSorters, _.partial(checkSorter, _, true)))
.sort((sorter1, sorter2) => 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 checkSorter = (sorter, isLabel) => {
return !_.any(this.props.activeSorters, {name: sorter.name, isLabel: isLabel});
};
inactiveSorters = _.union(
_.filter(this.props.availableSorters, _.partial(checkSorter, _, false)),
_.filter(this.props.labelSorters, _.partial(checkSorter, _, true))
)
.sort((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 = (filter, isLabel) => !_.any(this.props.activeFilters, {name: filter.name, isLabel: isLabel});
inactiveFilters = _.union(_.filter(this.props.availableFilters, _.partial(checkFilter, _, false)), _.filter(this.props.labelFilters, _.partial(checkFilter, _, true)))
.sort((filter1, filter2) => utils.natsort(filter1.title, filter2.title, {insensitive: true}));
var checkFilter = (filter, isLabel) => {
return !_.any(this.props.activeFilters, {name: filter.name, isLabel: isLabel});
};
inactiveFilters = _.union(
_.filter(this.props.availableFilters, _.partial(checkFilter, _, false)),
_.filter(this.props.labelFilters, _.partial(checkFilter, _, true))
)
.sort((filter1, filter2) => {
return utils.natsort(filter1.title, filter2.title, {insensitive: true});
});
appliedFilters = _.reject(this.props.activeFilters, (filter) => !filter.values.length);
}
@ -948,8 +1061,12 @@ ManagementPanel = React.createClass({
return (
<Tooltip key={mode + '-view'} text={i18n(ns + mode + '_mode_tooltip')}>
<label
className={utils.classNames(managementButtonClasses(mode == this.props.viewMode, mode))}
onClick={mode != this.props.viewMode && _.partial(this.props.changeViewMode, 'view_mode', mode)}
className={utils.classNames(
managementButtonClasses(mode == this.props.viewMode, mode)
)}
onClick={mode != this.props.viewMode &&
_.partial(this.props.changeViewMode, 'view_mode', mode)
}
>
<input type='radio' name='view_mode' value={mode} />
<i
@ -970,7 +1087,9 @@ ManagementPanel = React.createClass({
<button
disabled={!this.props.nodes.length}
onClick={this.props.nodes.length && this.toggleLabelsPanel}
className={utils.classNames(managementButtonClasses(this.props.isLabelsPanelOpen, 'btn-labels'))}
className={utils.classNames(
managementButtonClasses(this.props.isLabelsPanelOpen, 'btn-labels')
)}
>
<i className='glyphicon glyphicon-tag' />
</button>
@ -979,7 +1098,9 @@ ManagementPanel = React.createClass({
<button
disabled={!this.props.screenNodes.length}
onClick={this.toggleSorters}
className={utils.classNames(managementButtonClasses(this.state.areSortersVisible, 'btn-sorters'))}
className={utils.classNames(
managementButtonClasses(this.state.areSortersVisible, 'btn-sorters')
)}
>
<i className='glyphicon glyphicon-sort' />
</button>
@ -988,7 +1109,9 @@ ManagementPanel = React.createClass({
<button
disabled={!this.props.screenNodes.length}
onClick={this.toggleFilters}
className={utils.classNames(managementButtonClasses(this.state.areFiltersVisible, 'btn-filters'))}
className={utils.classNames(
managementButtonClasses(this.state.areFiltersVisible, 'btn-filters')
)}
>
<i className='glyphicon glyphicon-filter' />
</button>
@ -1018,7 +1141,12 @@ ManagementPanel = React.createClass({
autoFocus
/>
{this.state.isSearchButtonVisible &&
<button className='close btn-clear-search' onClick={this.clearSearchField}>&times;</button>
<button
className='close btn-clear-search'
onClick={this.clearSearchField}
>
&times;
</button>
}
</div>
)
@ -1055,19 +1183,27 @@ ManagementPanel = React.createClass({
onClick={_.bind(this.goToConfigurationScreen, this, 'disks', disksConflict)}
>
{disksConflict && <i className='glyphicon glyphicon-danger-sign' />}
{i18n('dialog.show_node.disk_configuration' + (_.all(this.props.nodes.invoke('areDisksConfigurable')) ? '_action' : ''))}
{i18n('dialog.show_node.disk_configuration' +
(_.all(this.props.nodes.invoke('areDisksConfigurable')) ? '_action' : ''))
}
</button>
<button
className='btn btn-default btn-configure-interfaces'
disabled={!this.props.nodes.length}
onClick={_.bind(this.goToConfigurationScreen, this, 'interfaces', interfaceConflict)}
onClick={_.bind(this.goToConfigurationScreen, this, 'interfaces',
interfaceConflict)
}
>
{interfaceConflict && <i className='glyphicon glyphicon-danger-sign' />}
{i18n('dialog.show_node.network_configuration' + (_.all(this.props.nodes.invoke('areInterfacesConfigurable')) ? '_action' : ''))}
{i18n('dialog.show_node.network_configuration' +
(_.all(this.props.nodes.invoke('areInterfacesConfigurable')) ?
'_action' : ''))
}
</button>
</div>,
<div className='btn-group' role='group' key='role-management-buttons'>
{!this.props.locked && !!this.props.nodes.length && this.props.nodes.any({pending_deletion: false}) &&
{!this.props.locked && !!this.props.nodes.length &&
this.props.nodes.any({pending_deletion: false}) &&
<button
className='btn btn-danger btn-delete-nodes'
onClick={this.showDeleteNodesDialog}
@ -1076,7 +1212,8 @@ ManagementPanel = React.createClass({
{i18n('common.delete_button')}
</button>
}
{!!this.props.nodes.length && !this.props.nodes.any({pending_addition: false}) &&
{!!this.props.nodes.length &&
!this.props.nodes.any({pending_addition: false}) &&
<button
className='btn btn-success btn-edit-roles'
onClick={_.bind(this.changeScreen, this, 'edit', true)}
@ -1113,7 +1250,10 @@ ManagementPanel = React.createClass({
<div className='well-heading'>
<i className='glyphicon glyphicon-sort' /> {i18n(ns + 'sort_by')}
{canResetSorters &&
<button className='btn btn-link pull-right btn-reset-sorting' onClick={this.resetSorters}>
<button
className='btn btn-link pull-right btn-reset-sorting'
onClick={this.resetSorters}
>
<i className='glyphicon glyphicon-remove-sign' /> {i18n(ns + 'reset')}
</button>
}
@ -1128,7 +1268,10 @@ ManagementPanel = React.createClass({
['sort-by-' + sorter.name + '-' + sorter.order]: !sorter.isLabel
})}
>
<button className='btn btn-default' onClick={_.partial(this.props.changeSortingOrder, sorter)}>
<button
className='btn btn-default'
onClick={_.partial(this.props.changeSortingOrder, sorter)}
>
{sorter.title}
<i
className={utils.classNames({
@ -1138,7 +1281,9 @@ ManagementPanel = React.createClass({
})}
/>
</button>
{this.props.activeSorters.length > 1 && this.renderDeleteSorterButton(sorter)}
{this.props.activeSorters.length > 1 &&
this.renderDeleteSorterButton(sorter)
}
</div>
);
})}
@ -1160,7 +1305,10 @@ ManagementPanel = React.createClass({
<div className='well-heading'>
<i className='glyphicon glyphicon-filter' /> {i18n(ns + 'filter_by')}
{!!appliedFilters.length &&
<button className='btn btn-link pull-right btn-reset-filters' onClick={this.resetFilters}>
<button
className='btn btn-link pull-right btn-reset-filters'
onClick={this.resetFilters}
>
<i className='glyphicon glyphicon-remove-sign' /> {i18n(ns + 'reset')}
</button>
}
@ -1178,15 +1326,25 @@ ManagementPanel = React.createClass({
label: filter.title,
extraContent: this.renderDeleteFilterButton(filter),
onChange: _.partial(this.props.changeFilter, filter),
prefix: i18n('cluster_page.nodes_tab.filters.prefixes.' + filter.name, {defaultValue: ''}),
prefix: i18n(
'cluster_page.nodes_tab.filters.prefixes.' + filter.name,
{defaultValue: ''}
),
isOpen: this.isFilterOpen(filter),
toggle: _.partial(this.toggleFilter, filter)
};
if (filter.isNumberRange) {
return <NumberRangeControl {...props} min={filter.limits[0]} max={filter.limits[1]} />;
return <NumberRangeControl
{...props}
min={filter.limits[0]}
max={filter.limits[1]}
/>;
}
return <MultiSelectControl {...props} options={this.props.getFilterOptions(filter)} />;
return <MultiSelectControl
{...props}
options={this.props.getFilterOptions(filter)}
/>;
})}
<MultiSelectControl
name='filter-more'
@ -1203,7 +1361,8 @@ ManagementPanel = React.createClass({
]}
{this.props.mode != 'edit' && !!this.props.screenNodes.length &&
<div className='col-xs-12'>
{(!this.state.areSortersVisible || !this.state.areFiltersVisible && !!appliedFilters.length) &&
{(!this.state.areSortersVisible || !this.state.areFiltersVisible &&
!!appliedFilters.length) &&
<div className='active-sorters-filters'>
{!this.state.areFiltersVisible && !!appliedFilters.length &&
<div className='active-filters row' onClick={this.toggleFilters}>
@ -1214,7 +1373,8 @@ ManagementPanel = React.createClass({
total: this.props.screenNodes.length
})}
{_.map(appliedFilters, (filter) => {
var options = filter.isNumberRange ? null : this.props.getFilterOptions(filter);
var options = filter.isNumberRange ? null :
this.props.getFilterOptions(filter);
return (
<div key={filter.name}>
<strong>{filter.title}{!!filter.values.length && ':'} </strong>
@ -1223,7 +1383,9 @@ ManagementPanel = React.createClass({
_.uniq(filter.values).join(' - ')
:
_.pluck(
_.filter(options, (option) => _.contains(filter.values, option.name))
_.filter(options, (option) => {
return _.contains(filter.values, option.name);
})
, 'label').join(', ')
}
</span>
@ -1231,7 +1393,10 @@ ManagementPanel = React.createClass({
);
}, this)}
</div>
<button className='btn btn-link btn-reset-filters' onClick={this.resetFilters}>
<button
className='btn btn-link btn-reset-filters'
onClick={this.resetFilters}
>
<i className='glyphicon glyphicon-remove-sign' />
</button>
</div>
@ -1258,7 +1423,10 @@ ManagementPanel = React.createClass({
})}
</div>
{canResetSorters &&
<button className='btn btn-link btn-reset-sorting' onClick={this.resetSorters}>
<button
className='btn btn-link btn-reset-sorting'
onClick={this.resetSorters}
>
<i className='glyphicon glyphicon-remove-sign' />
</button>
}
@ -1355,7 +1523,8 @@ NodeLabelsPanel = React.createClass({
});
},
isSavingPossible() {
return !this.state.actionInProgress && this.hasChanges() && _.all(_.pluck(this.state.labels, 'error'), _.isNull);
return !this.state.actionInProgress && this.hasChanges() &&
_.all(_.pluck(this.state.labels, 'error'), _.isNull);
},
revertChanges() {
return this.props.toggleLabelsPanel();
@ -1409,7 +1578,10 @@ NodeLabelsPanel = React.createClass({
})
.fail((response) => {
utils.showErrorDialog({
message: i18n('cluster_page.nodes_tab.node_management_panel.node_management_error.labels_warning'),
message: i18n(
'cluster_page.nodes_tab.node_management_panel.' +
'node_management_error.labels_warning'
),
response: response
});
});
@ -1443,7 +1615,10 @@ NodeLabelsPanel = React.createClass({
var showControlLabels = index == 0;
return (
<div className={utils.classNames({clearfix: true, 'has-label': showControlLabels})} key={index}>
<div
className={utils.classNames({clearfix: true, 'has-label': showControlLabels})}
key={index}
>
<Input
type='checkbox'
ref={labelData.key}
@ -1547,12 +1722,19 @@ RolePanel = React.createClass({
.value();
var messages = [];
if (restrictionsCheck.result && restrictionsCheck.message) messages.push(restrictionsCheck.message);
if (roleLimitsCheckResults && !roleLimitsCheckResults.valid && roleLimitsCheckResults.message) messages.push(roleLimitsCheckResults.message);
if (restrictionsCheck.result && restrictionsCheck.message) {
messages.push(restrictionsCheck.message);
}
if (roleLimitsCheckResults && !roleLimitsCheckResults.valid && roleLimitsCheckResults.message) {
messages.push(roleLimitsCheckResults.message);
}
if (_.contains(conflicts, name)) messages.push(i18n('cluster_page.nodes_tab.role_conflict'));
return {
result: restrictionsCheck.result || _.contains(conflicts, name) || (roleLimitsCheckResults && !roleLimitsCheckResults.valid && !_.contains(this.props.selectedRoles, name)),
result: restrictionsCheck.result || _.contains(conflicts, name) ||
(roleLimitsCheckResults && !roleLimitsCheckResults.valid &&
!_.contains(this.props.selectedRoles, name)
),
message: messages.join(' ')
};
},
@ -1563,7 +1745,8 @@ RolePanel = React.createClass({
{this.props.cluster.get('roles').map((role) => {
if (!role.checkRestrictions(this.props.configModels, 'hide').result) {
var name = role.get('name');
var processedRestrictions = this.props.nodes.length ? this.processRestrictions(role, this.props.configModels) : {};
var processedRestrictions = this.props.nodes.length ?
this.processRestrictions(role, this.props.configModels) : {};
return (
<Input
key={name}
@ -1590,11 +1773,14 @@ SelectAllMixin = {
componentDidUpdate() {
if (this.refs['select-all']) {
var input = this.refs['select-all'].getInputDOMNode();
input.indeterminate = !input.checked && _.any(this.props.nodes, (node) => this.props.selectedNodeIds[node.id]);
input.indeterminate = !input.checked && _.any(this.props.nodes, (node) => {
return this.props.selectedNodeIds[node.id];
});
}
},
renderSelectAllCheckbox() {
var checked = this.props.mode == 'edit' || (this.props.nodes.length && !_.any(this.props.nodes, (node) => !this.props.selectedNodeIds[node.id]));
var checked = this.props.mode == 'edit' || (this.props.nodes.length &&
!_.any(this.props.nodes, (node) => !this.props.selectedNodeIds[node.id]));
return (
<Input
ref='select-all'
@ -1603,7 +1789,8 @@ SelectAllMixin = {
checked={checked}
disabled={
this.props.mode == 'edit' || this.props.locked || !this.props.nodes.length ||
!checked && !_.isNull(this.props.maxNumberOfNodes) && this.props.maxNumberOfNodes < this.props.nodes.length
!checked && !_.isNull(this.props.maxNumberOfNodes) &&
this.props.maxNumberOfNodes < this.props.nodes.length
}
label={i18n('common.select_all')}
wrapperClassName='select-all pull-right'
@ -1622,7 +1809,8 @@ NodeList = React.createClass({
var diskSizes = node.resource('disks');
return i18n('node_details.disks_amount', {
count: diskSizes.length,
size: diskSizes.map((size) => utils.showDiskSize(size) + ' ' + i18n('node_details.hdd')).join(', ')
size: diskSizes.map((size) => utils.showDiskSize(size) + ' ' +
i18n('node_details.hdd')).join(', ')
});
};
@ -1655,14 +1843,27 @@ NodeList = React.createClass({
group_id: () => {
var nodeNetworkGroup = this.props.nodeNetworkGroups.get(node.get('group_id'));
return nodeNetworkGroup && i18n(ns + 'node_network_group', {
group: nodeNetworkGroup.get('name') + (this.props.cluster ? '' : ' (' + cluster.get('name') + ')')
group: nodeNetworkGroup.get('name') +
(this.props.cluster ? '' : ' (' + cluster.get('name') + ')')
}) || i18n(ns + 'no_node_network_group');
},
cluster: () => cluster && i18n(ns + 'cluster', {cluster: cluster.get('name')}) || i18n(ns + 'unallocated'),
hdd: () => i18n('node_details.total_hdd', {total: utils.showDiskSize(node.resource('hdd'))}),
cluster: () => cluster && i18n(
ns + 'cluster',
{cluster: cluster.get('name')}
) || i18n(ns + 'unallocated'),
hdd: () => i18n(
'node_details.total_hdd',
{total: utils.showDiskSize(node.resource('hdd'))}
),
disks: () => composeNodeDiskSizesLabel(node),
ram: () => i18n('node_details.total_ram', {total: utils.showMemorySize(node.resource('ram'))}),
interfaces: () => i18n('node_details.interfaces_amount', {count: node.resource('interfaces')}),
ram: () => i18n(
'node_details.total_ram',
{total: utils.showMemorySize(node.resource('ram'))}
),
interfaces: () => i18n(
'node_details.interfaces_amount',
{count: node.resource('interfaces')}
),
default: () => i18n('node_details.' + sorter.name, {count: node.resource(sorter.name)})
};
return (sorterNameFormatters[sorter.name] || sorterNameFormatters.default)();
@ -1698,7 +1899,8 @@ NodeList = React.createClass({
if (node1Label && node2Label) {
result = utils.natsort(node1Label, node2Label, {insensitive: true});
} else {
result = node1Label === node2Label ? 0 : _.isString(node1Label) ? -1 : _.isNull(node1Label) ? -1 : 1;
result = node1Label === node2Label ? 0 : _.isString(node1Label) ? -1 :
_.isNull(node1Label) ? -1 : 1;
}
} else {
var comparators = {
@ -1711,19 +1913,22 @@ NodeList = React.createClass({
else if (!roles2.length) result = -1;
else {
while (!order && roles1.length && roles2.length) {
order = _.indexOf(preferredRolesOrder, roles1.shift()) - _.indexOf(preferredRolesOrder, roles2.shift());
order = _.indexOf(preferredRolesOrder, roles1.shift()) -
_.indexOf(preferredRolesOrder, roles2.shift());
}
result = order || roles1.length - roles2.length;
}
},
status: () => {
result = _.indexOf(this.props.statusesToFilter, node1.getStatusSummary()) - _.indexOf(this.props.statusesToFilter, node2.getStatusSummary());
result = _.indexOf(this.props.statusesToFilter, node1.getStatusSummary()) -
_.indexOf(this.props.statusesToFilter, node2.getStatusSummary());
},
manufacturer: () => {
result = utils.compare(node1, node2, {attr: sorter.name});
},
disks: () => {
result = utils.natsort(composeNodeDiskSizesLabel(node1), composeNodeDiskSizesLabel(node2));
result = utils.natsort(composeNodeDiskSizesLabel(node1),
composeNodeDiskSizesLabel(node2));
},
group_id: () => {
var nodeGroup1 = node1.get('group_id');
@ -1735,7 +1940,9 @@ NodeList = React.createClass({
var cluster1 = node1.get('cluster');
var cluster2 = node2.get('cluster');
result = cluster1 == cluster2 ? 0 :
!cluster1 ? 1 : !cluster2 ? -1 : utils.natsort(this.props.clusters.get(cluster1).get('name'), this.props.clusters.get(cluster2).get('name'));
!cluster1 ? 1 : !cluster2 ? -1 :
utils.natsort(this.props.clusters.get(cluster1).get('name'),
this.props.clusters.get(cluster2).get('name'));
},
default: () => {
result = node1.resource(sorter.name) - node2.resource(sorter.name);
@ -1754,11 +1961,15 @@ NodeList = React.createClass({
},
render() {
var groups = this.groupNodes();
var rolesWithLimitReached = _.keys(_.omit(this.props.processedRoleLimits, (roleLimit, roleName) => {
return roleLimit.valid || !_.contains(this.props.selectedRoles, roleName);
}));
var rolesWithLimitReached = _.keys(_.omit(this.props.processedRoleLimits,
(roleLimit, roleName) => {
return roleLimit.valid || !_.contains(this.props.selectedRoles, roleName);
}
));
return (
<div className={utils.classNames({'node-list row': true, compact: this.props.viewMode == 'compact'})}>
<div className={utils.classNames({
'node-list row': true, compact: this.props.viewMode == 'compact'
})}>
{groups.length > 1 &&
<div className='col-xs-12 node-list-header'>
{this.renderSelectAllCheckbox()}
@ -1783,7 +1994,11 @@ NodeList = React.createClass({
:
<div className='alert alert-warning'>
{utils.renderMultilineText(
i18n('cluster_page.nodes_tab.' + (this.props.mode == 'add' ? 'no_nodes_in_fuel' : 'no_nodes_in_environment'))
i18n(
'cluster_page.nodes_tab.' + (this.props.mode == 'add' ?
'no_nodes_in_fuel' : 'no_nodes_in_environment'
)
)
)}
</div>
}

View File

@ -30,7 +30,8 @@ var SettingSection = React.createClass({
// FIXME: hack for #1442475 to lock images_ceph in env with controllers
if (settingName == 'images_ceph') {
if (_.contains(_.flatten(this.props.cluster.get('nodes').pluck('pending_roles')), 'controller')) {
if (_.contains(_.flatten(this.props.cluster.get('nodes').pluck('pending_roles')),
'controller')) {
result = true;
messages.push(i18n('cluster_page.settings_tab.images_ceph_warning'));
}
@ -46,8 +47,22 @@ var SettingSection = React.createClass({
var dependentRoles = this.checkDependentRoles(sectionName, settingName);
var dependentSettings = this.checkDependentSettings(sectionName, settingName);
if (dependentRoles.length) messages.push(i18n('cluster_page.settings_tab.dependent_role_warning', {roles: dependentRoles.join(', '), count: dependentRoles.length}));
if (dependentSettings.length) messages.push(i18n('cluster_page.settings_tab.dependent_settings_warning', {settings: dependentSettings.join(', '), count: dependentSettings.length}));
if (dependentRoles.length) {
messages.push(
i18n(
'cluster_page.settings_tab.dependent_role_warning',
{roles: dependentRoles.join(', '), count: dependentRoles.length}
)
);
}
if (dependentSettings.length) {
messages.push(
i18n(
'cluster_page.settings_tab.dependent_settings_warning',
{settings: dependentSettings.join(', '), count: dependentSettings.length}
)
);
}
return {
result: !!dependentRoles.length || !!dependentSettings.length,
@ -58,13 +73,18 @@ var SettingSection = React.createClass({
return setting.toggleable || _.contains(['checkbox', 'radio'], setting.type);
},
getValuesToCheck(setting, valueAttribute) {
return setting.values ? _.without(_.pluck(setting.values, 'data'), setting[valueAttribute]) : [!setting[valueAttribute]];
return setting.values ? _.without(_.pluck(setting.values, 'data'), setting[valueAttribute]) :
[!setting[valueAttribute]];
},
checkValues(values, path, currentValue, restriction) {
var extraModels = {settings: this.props.settingsForChecks};
var result = _.all(values, (value) => {
this.props.settingsForChecks.set(path, value);
return new Expression(restriction.condition, this.props.configModels, restriction).evaluate(extraModels);
return new Expression(
restriction.condition,
this.props.configModels,
restriction
).evaluate(extraModels);
});
this.props.settingsForChecks.set(path, currentValue);
return result;
@ -82,7 +102,12 @@ var SettingSection = React.createClass({
var role = roles.findWhere({name: roleName});
if (_.any(role.get('restrictions'), (restriction) => {
restriction = utils.expandRestriction(restriction);
if (_.contains(restriction.condition, 'settings:' + path) && !(new Expression(restriction.condition, this.props.configModels, restriction).evaluate())) {
if (_.contains(restriction.condition, 'settings:' + path) &&
!(new Expression(
restriction.condition,
this.props.configModels,
restriction
).evaluate())) {
return this.checkValues(valuesToCheck, pathToCheck, setting[valueAttribute], restriction);
}
})) return role.get('label');
@ -95,7 +120,10 @@ var SettingSection = React.createClass({
var dependentRestrictions = {};
var addDependentRestrictions = (setting, label) => {
var result = _.filter(_.map(setting.restrictions, utils.expandRestriction),
(restriction) => restriction.action == 'disable' && _.contains(restriction.condition, 'settings:' + path)
(restriction) => {
return restriction.action == 'disable' &&
_.contains(restriction.condition, 'settings:' + path);
}
);
if (result.length) {
dependentRestrictions[label] = result.concat(dependentRestrictions[label] || []);
@ -106,7 +134,8 @@ var SettingSection = React.createClass({
// don't take into account hidden dependent settings
if (this.props.checkRestrictions('hide', section.metadata).result) return;
_.each(section, (setting, settingName) => {
// we support dependecies on checkboxes, toggleable setting groups, dropdowns and radio groups
// we support dependecies on checkboxes,
// toggleable setting groups, dropdowns and radio groups
if (!this.areCalculationsPossible(setting) ||
this.props.makePath(sectionName, settingName) == path ||
this.props.checkRestrictions('hide', setting).result
@ -124,7 +153,10 @@ var SettingSection = React.createClass({
var valueAttribute = this.props.getValueAttribute(settingName);
var pathToCheck = this.props.makePath(path, valueAttribute);
var valuesToCheck = this.getValuesToCheck(currentSetting, valueAttribute);
var checkValues = _.partial(this.checkValues, valuesToCheck, pathToCheck, currentSetting[valueAttribute]);
var checkValues = _.partial(
this.checkValues,
valuesToCheck, pathToCheck, currentSetting[valueAttribute]
);
return _.compact(_.map(dependentRestrictions, (restrictions, label) => {
if (_.any(restrictions, checkValues)) return label;
}));
@ -155,18 +187,25 @@ var SettingSection = React.createClass({
var pluginMetadata = this.props.settings.get(pluginName).metadata;
if (enabled) {
// check for editable plugin version
var chosenVersionData = _.find(pluginMetadata.versions, (version) => version.metadata.plugin_id == pluginMetadata.chosen_id);
var chosenVersionData = _.find(pluginMetadata.versions, (version) => {
return version.metadata.plugin_id == pluginMetadata.chosen_id;
});
if (this.props.lockedCluster && !chosenVersionData.metadata.always_editable) {
var editableVersion = _.find(pluginMetadata.versions, (version) => version.metadata.always_editable).metadata.plugin_id;
var editableVersion = _.find(pluginMetadata.versions, (version) => {
return version.metadata.always_editable;
}).metadata.plugin_id;
this.onPluginVersionChange(pluginName, editableVersion);
}
} else {
var initialVersion = this.props.initialAttributes[pluginName].metadata.chosen_id;
if (pluginMetadata.chosen_id !== initialVersion) this.onPluginVersionChange(pluginName, initialVersion);
if (pluginMetadata.chosen_id !== initialVersion) {
this.onPluginVersionChange(pluginName, initialVersion);
}
}
},
renderTitle(options) {
var {metadata, sectionName, isGroupDisabled, processedGroupDependencies, showSettingGroupWarning, groupWarning, isPlugin} = options;
var {metadata, sectionName, isGroupDisabled, processedGroupDependencies,
showSettingGroupWarning, groupWarning, isPlugin} = options;
return metadata.toggleable ?
<Input
type='checkbox'
@ -178,10 +217,20 @@ var SettingSection = React.createClass({
onChange={isPlugin ? _.partial(this.togglePlugin, sectionName) : this.props.onChange}
/>
:
<span className={'subtab-group-' + sectionName}>{sectionName == 'common' ? i18n('cluster_page.settings_tab.groups.common') : metadata.label || sectionName}</span>;
<span
className={'subtab-group-' + sectionName}
>
{
sectionName == 'common' ?
i18n('cluster_page.settings_tab.groups.common') : metadata.label || sectionName
}
</span>;
},
renderCustomControl(options) {
var {setting, settingKey, error, isSettingDisabled, showSettingWarning, settingWarning, CustomControl, path} = options;
var {
setting, settingKey, error, isSettingDisabled, showSettingWarning,
settingWarning, CustomControl, path
} = options;
return <CustomControl
{...setting}
{... _.pick(this.props, 'cluster', 'settings', 'configModels')}
@ -193,7 +242,8 @@ var SettingSection = React.createClass({
/>;
},
renderRadioGroup(options) {
var {setting, settingKey, error, isSettingDisabled, showSettingWarning, settingWarning, settingName} = options;
var {setting, settingKey, error, isSettingDisabled, showSettingWarning, settingWarning,
settingName} = options;
var values = _.chain(_.cloneDeep(setting.values))
.map((value) => {
var processedValueRestrictions = this.props.checkRestrictions('disable', value);
@ -218,7 +268,8 @@ var SettingSection = React.createClass({
);
},
renderInput(options) {
var {setting, settingKey, error, isSettingDisabled, showSettingWarning, settingWarning, settingName} = options;
var {setting, settingKey, error, isSettingDisabled, showSettingWarning, settingWarning,
settingName} = options;
var settingDescription = setting.description &&
<span dangerouslySetInnerHTML={{__html: utils.urlify(_.escape(setting.description))}} />;
return <Input
@ -242,18 +293,25 @@ var SettingSection = React.createClass({
var section = settings.get(sectionName);
var isPlugin = settings.isPlugin(section);
var metadata = section.metadata;
var sortedSettings = _.sortBy(this.props.settingsToDisplay, (settingName) => section[settingName].weight);
var sortedSettings = _.sortBy(this.props.settingsToDisplay, (settingName) => {
return section[settingName].weight;
});
var processedGroupRestrictions = this.processRestrictions(metadata);
var processedGroupDependencies = this.checkDependencies(sectionName, 'metadata');
var isGroupAlwaysEditable = isPlugin ? _.any(metadata.versions, (version) => version.metadata.always_editable) : metadata.always_editable;
var isGroupDisabled = this.props.locked || (this.props.lockedCluster && !isGroupAlwaysEditable) || processedGroupRestrictions.result;
var isGroupAlwaysEditable = isPlugin ? _.any(metadata.versions, (version) => {
return version.metadata.always_editable;
}) : metadata.always_editable;
var isGroupDisabled = this.props.locked ||
(this.props.lockedCluster && !isGroupAlwaysEditable) || processedGroupRestrictions.result;
var showSettingGroupWarning = !this.props.lockedCluster || metadata.always_editable;
var groupWarning = _.compact([processedGroupRestrictions.message, processedGroupDependencies.message]).join(' ');
var groupWarning = _.compact([processedGroupRestrictions.message,
processedGroupDependencies.message]).join(' ');
return (
<div className={'setting-section setting-section-' + sectionName}>
<h3>
{this.renderTitle({metadata, sectionName, isGroupDisabled, processedGroupDependencies, showSettingGroupWarning, groupWarning, isPlugin})}
{this.renderTitle({metadata, sectionName, isGroupDisabled, processedGroupDependencies,
showSettingGroupWarning, groupWarning, isPlugin})}
</h3>
<div>
{isPlugin &&
@ -267,7 +325,9 @@ var SettingSection = React.createClass({
data: version.metadata.plugin_id,
label: version.metadata.plugin_version,
defaultChecked: version.metadata.plugin_id == metadata.chosen_id,
disabled: this.props.locked || (this.props.lockedCluster && !version.metadata.always_editable) || processedGroupRestrictions.result || (metadata.toggleable && !metadata.enabled)
disabled: this.props.locked || (this.props.lockedCluster &&
!version.metadata.always_editable) || processedGroupRestrictions.result ||
(metadata.toggleable && !metadata.enabled)
};
}, this)}
onChange={this.onPluginVersionChange}
@ -281,11 +341,16 @@ var SettingSection = React.createClass({
var error = (settings.validationError || {})[path];
var processedSettingRestrictions = this.processRestrictions(setting, settingName);
var processedSettingDependencies = this.checkDependencies(sectionName, settingName);
var isSettingDisabled = isGroupDisabled || (metadata.toggleable && !metadata.enabled) || processedSettingRestrictions.result || processedSettingDependencies.result;
var showSettingWarning = showSettingGroupWarning && !isGroupDisabled && (!metadata.toggleable || metadata.enabled);
var settingWarning = _.compact([processedSettingRestrictions.message, processedSettingDependencies.message]).join(' ');
var isSettingDisabled = isGroupDisabled ||
(metadata.toggleable && !metadata.enabled) ||
processedSettingRestrictions.result || processedSettingDependencies.result;
var showSettingWarning = showSettingGroupWarning && !isGroupDisabled &&
(!metadata.toggleable || metadata.enabled);
var settingWarning = _.compact([processedSettingRestrictions.message,
processedSettingDependencies.message]).join(' ');
var renderOptions = {setting, settingKey, error, isSettingDisabled, showSettingWarning, settingWarning};
var renderOptions = {setting, settingKey, error, isSettingDisabled,
showSettingWarning, settingWarning};
// support of custom controls
var CustomControl = customControls[setting.type];

View File

@ -52,7 +52,8 @@ var SettingsTab = React.createClass({
configModels: {
cluster: this.props.cluster,
settings: settings,
networking_parameters: this.props.cluster.get('networkConfiguration').get('networking_parameters'),
networking_parameters: this.props.cluster.get('networkConfiguration')
.get('networking_parameters'),
version: app.version,
release: this.props.cluster.get('release'),
default: settings
@ -69,7 +70,10 @@ var SettingsTab = React.createClass({
this.loadInitialSettings();
},
hasChanges() {
return this.props.cluster.get('settings').hasChanges(this.state.initialAttributes, this.state.configModels);
return this.props.cluster.get('settings').hasChanges(
this.state.initialAttributes,
this.state.configModels
);
},
applyChanges() {
if (!this.isSavingPossible()) return $.Deferred().reject();
@ -110,14 +114,16 @@ var SettingsTab = React.createClass({
var settings = this.props.cluster.get('settings');
var lockedCluster = !this.props.cluster.isAvailableForSettingsChanges();
var defaultSettings = new models.Settings();
var deferred = defaultSettings.fetch({url: _.result(this.props.cluster, 'url') + '/attributes/defaults'});
var deferred = defaultSettings.fetch({url: _.result(this.props.cluster, 'url') +
'/attributes/defaults'});
if (deferred) {
this.setState({actionInProgress: true});
deferred
.done(() => {
_.each(settings.attributes, (section, sectionName) => {
if ((!lockedCluster || section.metadata.always_editable) && section.metadata.group != 'network') {
if ((!lockedCluster || section.metadata.always_editable) &&
section.metadata.group != 'network') {
_.each(section, (setting, settingName) => {
// do not update hidden settings (hack for #1442143),
// the same for settings with group network
@ -164,11 +170,18 @@ var SettingsTab = React.createClass({
settings.isValid({models: this.state.configModels});
},
checkRestrictions(action, setting) {
return this.props.cluster.get('settings').checkRestrictions(this.state.configModels, action, setting);
return this.props.cluster.get('settings').checkRestrictions(
this.state.configModels,
action,
setting
);
},
isSavingPossible() {
var settings = this.props.cluster.get('settings');
var locked = this.state.actionInProgress || !!this.props.cluster.task({group: 'deployment', active: true});
var locked = this.state.actionInProgress || !!this.props.cluster.task({
group: 'deployment',
active: true
});
// network settings are shown on Networks tab, so they should not block
// saving of changes on Settings tab
var areSettingsValid = !_.any(_.keys(settings.validationError), (settingPath) => {
@ -184,9 +197,15 @@ var SettingsTab = React.createClass({
var settingsGroupList = settings.getGroupList();
var locked = this.state.actionInProgress || !!cluster.task({group: 'deployment', active: true});
var lockedCluster = !cluster.isAvailableForSettingsChanges();
var someSettingsEditable = _.any(settings.attributes, (group) => group.metadata.always_editable);
var someSettingsEditable = _.any(
settings.attributes,
(group) => group.metadata.always_editable
);
var hasChanges = this.hasChanges();
var allocatedRoles = _.uniq(_.flatten(_.union(cluster.get('nodes').pluck('roles'), cluster.get('nodes').pluck('pending_roles'))));
var allocatedRoles = _.uniq(_.flatten(_.union(
cluster.get('nodes').pluck('roles'),
cluster.get('nodes').pluck('pending_roles')
)));
var classes = {
row: true,
'changes-locked': lockedCluster
@ -237,7 +256,10 @@ var SettingsTab = React.createClass({
return (settings.validationError || {})[settings.makePath(sectionName, settingName)];
});
if (!_.isEmpty(pickedSettings)) {
groupedSettings[calculatedGroup][sectionName] = {settings: pickedSettings, invalid: hasErrors};
groupedSettings[calculatedGroup][sectionName] = {
settings: pickedSettings,
invalid: hasErrors
};
}
});
}
@ -261,7 +283,9 @@ var SettingsTab = React.createClass({
{_.map(groupedSettings, (selectedGroup, groupName) => {
if (groupName != this.props.activeSettingsSectionName) return null;
var sortedSections = _.sortBy(_.keys(selectedGroup), (name) => settings.get(name + '.metadata.weight'));
var sortedSections = _.sortBy(
_.keys(selectedGroup), (name) => settings.get(name + '.metadata.weight')
);
return (
<div className={'col-xs-10 forms-box ' + groupName} key={groupName}>
{_.map(sortedSections, (sectionName) => {
@ -295,13 +319,25 @@ var SettingsTab = React.createClass({
<div className='col-xs-12 page-buttons content-elements'>
<div className='well clearfix'>
<div className='btn-group pull-right'>
<button className='btn btn-default btn-load-defaults' onClick={this.loadDefaults} disabled={locked || (lockedCluster && !someSettingsEditable)}>
<button
className='btn btn-default btn-load-defaults'
onClick={this.loadDefaults}
disabled={locked || (lockedCluster && !someSettingsEditable)}
>
{i18n('common.load_defaults_button')}
</button>
<button className='btn btn-default btn-revert-changes' onClick={this.revertChanges} disabled={locked || !hasChanges}>
<button
className='btn btn-default btn-revert-changes'
onClick={this.revertChanges}
disabled={locked || !hasChanges}
>
{i18n('common.cancel_changes_button')}
</button>
<button className='btn btn-success btn-apply-changes' onClick={this.applyChanges} disabled={!this.isSavingPossible()}>
<button
className='btn btn-success btn-apply-changes'
onClick={this.applyChanges}
disabled={!this.isSavingPossible()}
>
{i18n('common.save_settings_button')}
</button>
</div>
@ -316,7 +352,11 @@ var SettingSubtabs = React.createClass({
render() {
return (
<div className='col-xs-2'>
<CSSTransitionGroup component='ul' transitionName='subtab-item' className='nav nav-pills nav-stacked'>
<CSSTransitionGroup
component='ul'
transitionName='subtab-item'
className='nav nav-pills nav-stacked'
>
{
this.props.settingsGroupList.map((groupName) => {
if (!this.props.groupedSettings[groupName]) return null;
@ -326,7 +366,9 @@ var SettingSubtabs = React.createClass({
<li
key={groupName}
role='presentation'
className={utils.classNames({active: groupName == this.props.activeSettingsSectionName})}
className={utils.classNames({
active: groupName == this.props.activeSettingsSectionName
})}
onClick={_.partial(this.props.setActiveSettingsGroupName, groupName)}
>
<a className={'subtab-link-' + groupName}>

View File

@ -58,7 +58,11 @@ ClustersPage = React.createClass({
ClusterList = React.createClass({
mixins: [backboneMixin('clusters')],
createCluster() {
CreateClusterWizard.show({clusters: this.props.clusters, modalClass: 'wizard', backdrop: 'static'});
CreateClusterWizard.show({
clusters: this.props.clusters,
modalClass: 'wizard',
backdrop: 'static'
});
},
render() {
return (
@ -126,7 +130,8 @@ Cluster = React.createClass({
var cluster = this.props.cluster;
var status = cluster.get('status');
var nodes = cluster.get('nodes');
var isClusterDeleting = !!cluster.task({name: 'cluster_deletion', active: true}) || !!cluster.task({name: 'cluster_deletion', status: 'ready'});
var isClusterDeleting = !!cluster.task({name: 'cluster_deletion', active: true}) ||
!!cluster.task({name: 'cluster_deletion', status: 'ready'});
var deploymentTask = cluster.task({group: 'deployment', active: true});
var Tag = isClusterDeleting ? 'div' : 'a';
return (
@ -140,15 +145,29 @@ Cluster = React.createClass({
>
<div className='name'>{cluster.get('name')}</div>
<div className='tech-info'>
<div key='nodes-title' className='item'>{i18n('clusters_page.cluster_hardware_nodes')}</div>
<div key='nodes-title' className='item'>
{i18n('clusters_page.cluster_hardware_nodes')}
</div>
<div key='nodes-value' className='value'>{nodes.length}</div>
{!!nodes.length && [
<div key='cpu-title' className='item'>{i18n('clusters_page.cluster_hardware_cpu')}</div>,
<div key='cpu-value' className='value'>{nodes.resources('cores')} ({nodes.resources('ht_cores')})</div>,
<div key='hdd-title' className='item'>{i18n('clusters_page.cluster_hardware_hdd')}</div>,
<div key='hdd-value' className='value'>{nodes.resources('hdd') ? utils.showDiskSize(nodes.resources('hdd')) : '?GB'}</div>,
<div key='ram-title' className='item'>{i18n('clusters_page.cluster_hardware_ram')}</div>,
<div key='ram-value' className='value'>{nodes.resources('ram') ? utils.showMemorySize(nodes.resources('ram')) : '?GB'}</div>
<div key='cpu-title' className='item'>
{i18n('clusters_page.cluster_hardware_cpu')}
</div>,
<div key='cpu-value' className='value'>
{nodes.resources('cores')} ({nodes.resources('ht_cores')})
</div>,
<div key='hdd-title' className='item'>
{i18n('clusters_page.cluster_hardware_hdd')}
</div>,
<div key='hdd-value' className='value'>
{nodes.resources('hdd') ? utils.showDiskSize(nodes.resources('hdd')) : '?GB'}
</div>,
<div key='ram-title' className='item'>
{i18n('clusters_page.cluster_hardware_ram')}
</div>,
<div key='ram-value' className='value'>
{nodes.resources('ram') ? utils.showMemorySize(nodes.resources('ram')) : '?GB'}
</div>
]}
</div>
<div className='status text-info'>
@ -157,9 +176,14 @@ Cluster = React.createClass({
<div
className={utils.classNames({
'progress-bar': true,
'progress-bar-warning': _.contains(['stop_deployment', 'reset_environment'], deploymentTask.get('name'))
'progress-bar-warning': _.contains(
['stop_deployment', 'reset_environment'],
deploymentTask.get('name')
)
})}
style={{width: (deploymentTask.get('progress') > 3 ? deploymentTask.get('progress') : 3) + '%'}}
style={{width: (
deploymentTask.get('progress') > 3 ? deploymentTask.get('progress') : 3
) + '%'}}
></div>
</div>
:

View File

@ -30,7 +30,10 @@ import {outerClickMixin} from 'component_mixins';
export var Input = React.createClass({
propTypes: {
type: React.PropTypes.oneOf(['text', 'password', 'textarea', 'checkbox', 'radio', 'select', 'hidden', 'number', 'range', 'file']).isRequired,
type: React.PropTypes.oneOf([
'text', 'password', 'textarea', 'checkbox', 'radio',
'select', 'hidden', 'number', 'range', 'file'
]).isRequired,
name: React.PropTypes.node,
label: React.PropTypes.node,
debounce: React.PropTypes.bool,
@ -147,19 +150,35 @@ export var Input = React.createClass({
className='form-control file-name'
type='text'
placeholder={i18n('file.placeholder')}
value={this.state.fileName && '[' + utils.showSize(this.state.content.length) + '] ' + this.state.fileName}
value={
this.state.fileName && (
'[' + utils.showSize(this.state.content.length) + '] ' + this.state.fileName
)
}
onClick={this.pickFile}
disabled={this.props.disabled}
readOnly
/>
<div className='input-group-addon' onClick={this.state.fileName ? this.removeFile : this.pickFile}>
<i className={this.state.fileName && !this.props.disabled ? 'glyphicon glyphicon-remove' : 'glyphicon glyphicon-file'} />
<div
className='input-group-addon'
onClick={this.state.fileName ? this.removeFile : this.pickFile}
>
<i
className={this.state.fileName && !this.props.disabled ?
'glyphicon glyphicon-remove' : 'glyphicon glyphicon-file'
}
/>
</div>
</div>
}
{this.props.toggleable &&
<div className='input-group-addon' onClick={this.togglePassword}>
<i className={this.state.visible ? 'glyphicon glyphicon-eye-close' : 'glyphicon glyphicon-eye-open'} />
<i
className={
this.state.visible ?
'glyphicon glyphicon-eye-close' : 'glyphicon glyphicon-eye-open'
}
/>
</div>
}
{isCheckboxOrRadio && <span>&nbsp;</span>}
@ -182,7 +201,8 @@ export var Input = React.createClass({
);
},
renderDescription() {
var text = !_.isUndefined(this.props.error) && !_.isNull(this.props.error) ? this.props.error : this.props.description || '';
var text = !_.isUndefined(this.props.error) && !_.isNull(this.props.error) ? this.props.error :
this.props.description || '';
return <span key='description' className='help-block'>{text}</span>;
},
renderWrapper(children) {
@ -372,7 +392,9 @@ export var Tooltip = React.createClass({
$(ReactDOM.findDOMNode(this.refs.tooltip)).tooltip('destroy');
},
render() {
if (!this.props.wrap) return React.cloneElement(React.Children.only(this.props.children), {ref: 'tooltip'});
if (!this.props.wrap) {
return React.cloneElement(React.Children.only(this.props.children), {ref: 'tooltip'});
}
return (
<div className={this.props.wrapperClassName} ref='tooltip'>
{this.props.children}

View File

@ -40,7 +40,13 @@ customControls.custom_repo_configuration = React.createClass({
error.uri = i18n(ns + 'invalid_repo');
}
var priority = repo.priority;
if (_.isNaN(priority) || !_.isNull(priority) && (!(priority == _.parseInt(priority, 10)) || os == 'CentOS' && (priority < 1 || priority > 99))) {
if (
_.isNaN(priority) ||
!_.isNull(priority) && (
!(priority == _.parseInt(priority, 10)) ||
os == 'CentOS' && (priority < 1 || priority > 99)
)
) {
error.priority = i18n(ns + 'invalid_priority');
}
return _.isEmpty(error) ? null : error;
@ -48,7 +54,9 @@ customControls.custom_repo_configuration = React.createClass({
return _.compact(errors).length ? errors : null;
},
repoToString(repo, os) {
var repoData = _.compact(this.defaultProps.repoAttributes[os].map((attribute) => repo[attribute]));
var repoData = _.compact(this.defaultProps.repoAttributes[os].map(
(attribute) => repo[attribute]
));
if (!repoData.length) return ''; // in case of new repo
return repoData.join(' ');
}
@ -58,10 +66,12 @@ customControls.custom_repo_configuration = React.createClass({
},
getDefaultProps() {
return {
/* eslint-disable max-len */
repoRegexes: {
Ubuntu: /^(deb|deb-src)\s+(\w+:\/\/[\w\-.\/]+(?::\d+)?[\w\-.\/]+)\s+([\w\-.\/]+)(?:\s+([\w\-.\/\s]+))?$/i,
CentOS: /^(\w+:\/\/[\w\-.\/]+(?::\d+)?[\w\-.\/]+)\s*$/i
},
/* eslint-enable max-len */
repoAttributes: {
Ubuntu: ['type', 'uri', 'suite', 'section'],
CentOS: ['uri']
@ -130,7 +140,12 @@ customControls.custom_repo_configuration = React.createClass({
return (
<div className='repos' key={this.state.key}>
{this.props.description &&
<span className='help-block' dangerouslySetInnerHTML={{__html: utils.urlify(utils.linebreaks(_.escape(this.props.description)))}} />
<span
className='help-block'
dangerouslySetInnerHTML={{
__html: utils.urlify(utils.linebreaks(_.escape(this.props.description)))
}}
/>
}
{this.props.value.map((repo, index) => {
var error = (this.props.error || {})[index];
@ -162,7 +177,9 @@ customControls.custom_repo_configuration = React.createClass({
<Input
{...props}
defaultValue={repo.priority}
error={error && (error.priority ? (error.name || error.uri) ? '' : error.priority : null)}
error={
error && (error.priority ? (error.name || error.uri) ? '' : error.priority : null)
}
wrapperClassName='repo-priority'
onChange={this.changeRepos.bind(this, 'change_priority')}
extraContent={index > 0 && this.renderDeleteButton(index)}
@ -174,7 +191,12 @@ customControls.custom_repo_configuration = React.createClass({
);
})}
<div className='buttons'>
<button key='addExtraRepo' className='btn btn-default btn-add-repo' onClick={this.changeRepos.bind(this, 'add')} disabled={this.props.disabled}>
<button
key='addExtraRepo'
className='btn btn-default btn-add-repo'
onClick={this.changeRepos.bind(this, 'add')}
disabled={this.props.disabled}
>
{i18n(ns + 'add_repo_button')}
</button>
</div>

View File

@ -67,14 +67,20 @@ export var dialogMixin = {
}
return result;
} else {
return ReactDOM.render(React.createElement(this, dialogOptions), $('#modal-container')[0]).getResult();
return ReactDOM.render(
React.createElement(this, dialogOptions),
$('#modal-container')[0]
).getResult();
}
}
},
updateProps(partialProps) {
var props;
props = _.extend({}, this.props, partialProps);
ReactDOM.render(React.createElement(this.constructor, props), ReactDOM.findDOMNode(this).parentNode);
ReactDOM.render(
React.createElement(this.constructor, props),
ReactDOM.findDOMNode(this).parentNode
);
},
getInitialState() {
return {
@ -118,7 +124,11 @@ export var dialogMixin = {
if (e.target.tagName == 'A' && !e.target.target && e.target.href) this.close();
},
closeOnEscapeKey(e) {
if (this.props.keyboard !== false && this.props.closeable !== false && e.key == 'Escape') this.close();
if (
this.props.keyboard !== false &&
this.props.closeable !== false &&
e.key == 'Escape'
) this.close();
if (_.isFunction(this.onKeyDown)) this.onKeyDown(e);
},
showError(response, message) {
@ -137,7 +147,12 @@ export var dialogMixin = {
var classes = {'modal fade': true};
classes[this.props.modalClass] = this.props.modalClass;
return (
<div className={utils.classNames(classes)} tabIndex='-1' onClick={this.closeOnLinkClick} onKeyDown={this.closeOnEscapeKey}>
<div
className={utils.classNames(classes)}
tabIndex='-1'
onClick={this.closeOnLinkClick}
onKeyDown={this.closeOnEscapeKey}
>
<div className='modal-dialog'>
<div className='modal-content'>
<div className='modal-header'>
@ -146,7 +161,13 @@ export var dialogMixin = {
<span aria-hidden='true'>&times;</span>
</button>
}
<h4 className='modal-title'>{this.props.title || this.state.title || (this.props.error ? i18n('dialog.error_dialog.title') : '')}</h4>
<h4 className='modal-title'>
{
this.props.title ||
this.state.title ||
(this.props.error ? i18n('dialog.error_dialog.title') : '')
}
</h4>
</div>
<div className='modal-body'>
{this.props.error ?
@ -159,7 +180,9 @@ export var dialogMixin = {
{this.renderFooter && !this.props.error ?
this.renderFooter()
:
<button className='btn btn-default' onClick={this.close}>{i18n('common.close_button')}</button>
<button className='btn btn-default' onClick={this.close}>
{i18n('common.close_button')}
</button>
}
</div>
</div>
@ -219,7 +242,9 @@ export var NailgunUnavailabilityDialog = React.createClass({
this.startCountdown();
},
componentDidMount() {
$(ReactDOM.findDOMNode(this)).on('shown.bs.modal', () => $(ReactDOM.findDOMNode(this.refs['retry-button'])).focus());
$(ReactDOM.findDOMNode(this)).on('shown.bs.modal', () => {
return $(ReactDOM.findDOMNode(this.refs['retry-button'])).focus();
});
},
startCountdown() {
this.activeTimeout = _.delay(this.countdown, 1000);
@ -242,7 +267,9 @@ export var NailgunUnavailabilityDialog = React.createClass({
reinitializeUI() {
app.initialize().then(this.close, () => {
var {retryDelayIntervals} = this.props;
var nextDelay = retryDelayIntervals[retryDelayIntervals.indexOf(this.state.currentDelayInterval) + 1] || _.last(retryDelayIntervals);
var nextDelay = retryDelayIntervals[
retryDelayIntervals.indexOf(this.state.currentDelayInterval) + 1
] || _.last(retryDelayIntervals);
_.defer(() => this.setState({
actionInProgress: false,
currentDelay: nextDelay,
@ -266,7 +293,10 @@ export var NailgunUnavailabilityDialog = React.createClass({
{i18n('dialog.nailgun_unavailability.unavailability_message')}
{' '}
{this.state.currentDelay ?
i18n('dialog.nailgun_unavailability.retry_delay_message', {count: this.state.currentDelay})
i18n(
'dialog.nailgun_unavailability.retry_delay_message',
{count: this.state.currentDelay}
)
:
i18n('dialog.nailgun_unavailability.retrying')
}
@ -315,7 +345,8 @@ export var DiscardNodeChangesDialog = React.createClass({
Backbone.sync('update', nodes)
.then(() => this.props.cluster.fetchRelated('nodes'))
.done(() => {
dispatcher.trigger('updateNodeStats networkConfigurationUpdated labelsConfigurationUpdated');
dispatcher
.trigger('updateNodeStats networkConfigurationUpdated labelsConfigurationUpdated');
this.state.result.resolve();
this.close();
})
@ -333,8 +364,22 @@ export var DiscardNodeChangesDialog = React.createClass({
},
renderFooter() {
return ([
<button key='cancel' className='btn btn-default' onClick={this.close} disabled={this.state.actionInProgress}>{i18n('common.cancel_button')}</button>,
<button key='discard' className='btn btn-danger' disabled={this.state.actionInProgress} onClick={this.discardNodeChanges}>{i18n('dialog.discard_changes.discard_button')}</button>
<button
key='cancel'
className='btn btn-default'
onClick={this.close}
disabled={this.state.actionInProgress}
>
{i18n('common.cancel_button')}
</button>,
<button
key='discard'
className='btn btn-danger'
disabled={this.state.actionInProgress}
onClick={this.discardNodeChanges}
>
{i18n('dialog.discard_changes.discard_button')}
</button>
]);
}
});
@ -342,7 +387,8 @@ export var DiscardNodeChangesDialog = React.createClass({
export var DeployChangesDialog = React.createClass({
mixins: [
dialogMixin,
// this is needed to somehow handle the case when verification is in progress and user pressed Deploy
// this is needed to somehow handle the case when
// verification is in progress and user pressed Deploy
backboneMixin({
modelOrCollection(props) {
return props.cluster.get('tasks');
@ -400,7 +446,14 @@ export var DeployChangesDialog = React.createClass({
},
renderFooter() {
return ([
<button key='cancel' className='btn btn-default' onClick={this.close} disabled={this.state.actionInProgress}>{i18n('common.cancel_button')}</button>,
<button
key='cancel'
className='btn btn-default'
onClick={this.close}
disabled={this.state.actionInProgress}
>
{i18n('common.cancel_button')}
</button>,
<button key='deploy'
className='btn start-deployment-btn btn-success'
disabled={this.state.actionInProgress || this.state.isInvalid}
@ -435,8 +488,22 @@ export var ProvisionVMsDialog = React.createClass({
},
renderFooter() {
return ([
<button key='cancel' className='btn btn-default' onClick={this.close} disabled={this.state.actionInProgress}>{i18n('common.cancel_button')}</button>,
<button key='provision' className='btn btn-success' disabled={this.state.actionInProgress} onClick={this.startProvisioning}>{i18n('common.start_button')}</button>
<button
key='cancel'
className='btn btn-default'
onClick={this.close}
disabled={this.state.actionInProgress}
>
{i18n('common.cancel_button')}
</button>,
<button
key='provision'
className='btn btn-success'
disabled={this.state.actionInProgress}
onClick={this.startProvisioning}
>
{i18n('common.start_button')}
</button>
]);
}
});
@ -455,21 +522,40 @@ export var StopDeploymentDialog = React.createClass({
dispatcher.trigger('deploymentTaskStarted');
})
.fail((response) => {
this.showError(response, i18n('dialog.stop_deployment.stop_deployment_error.stop_deployment_warning'));
this.showError(
response,
i18n('dialog.stop_deployment.stop_deployment_error.stop_deployment_warning')
);
});
},
renderBody() {
return (
<div className='text-danger'>
{this.renderImportantLabel()}
{i18n('dialog.stop_deployment.' + (this.props.cluster.get('nodes').where({status: 'provisioning'}).length ? 'provisioning_warning' : 'text'))}
{i18n('dialog.stop_deployment.' +
(this.props.cluster.get('nodes').where({status: 'provisioning'}).length ?
'provisioning_warning' : 'text'))}
</div>
);
},
renderFooter() {
return ([
<button key='cancel' className='btn btn-default' onClick={this.close} disabled={this.state.actionInProgress}>{i18n('common.cancel_button')}</button>,
<button key='deploy' className='btn stop-deployment-btn btn-danger' disabled={this.state.actionInProgress} onClick={this.stopDeployment}>{i18n('common.stop_button')}</button>
<button
key='cancel'
className='btn btn-default'
onClick={this.close}
disabled={this.state.actionInProgress}
>
{i18n('common.cancel_button')}
</button>,
<button
key='deploy'
className='btn stop-deployment-btn btn-danger'
disabled={this.state.actionInProgress}
onClick={this.stopDeployment}
>
{i18n('common.stop_button')}
</button>
]);
}
});
@ -537,12 +623,21 @@ export var RemoveClusterDialog = React.createClass({
},
renderFooter() {
return ([
<button key='cancel' className='btn btn-default' onClick={this.close} disabled={this.state.actionInProgress}>{i18n('common.cancel_button')}</button>,
<button
key='cancel'
className='btn btn-default'
onClick={this.close}
disabled={this.state.actionInProgress}
>
{i18n('common.cancel_button')}
</button>,
<button
key='remove'
className='btn btn-danger remove-cluster-btn'
disabled={this.state.actionInProgress || this.state.confirmation && _.isUndefined(this.state.confirmationError) || this.state.confirmationError}
onClick={this.props.cluster.get('status') == 'new' || this.state.confirmation ? this.removeCluster : this.showConfirmationForm}
disabled={this.state.actionInProgress || this.state.confirmation &&
_.isUndefined(this.state.confirmationError) || this.state.confirmationError}
onClick={this.props.cluster.get('status') == 'new' || this.state.confirmation ?
this.removeCluster : this.showConfirmationForm}
>
{i18n('common.delete_button')}
</button>
@ -602,11 +697,19 @@ export var ResetEnvironmentDialog = React.createClass({
},
renderFooter() {
return ([
<button key='cancel' className='btn btn-default' disabled={this.state.actionInProgress} onClick={this.close}>{i18n('common.cancel_button')}</button>,
<button
key='cancel'
className='btn btn-default'
disabled={this.state.actionInProgress}
onClick={this.close}
>
{i18n('common.cancel_button')}
</button>,
<button
key='reset'
className='btn btn-danger reset-environment-btn'
disabled={this.state.actionInProgress || this.state.confirmation && _.isUndefined(this.state.confirmationError) || this.state.confirmationError}
disabled={this.state.actionInProgress || this.state.confirmation &&
_.isUndefined(this.state.confirmationError) || this.state.confirmationError}
onClick={this.state.confirmation ? this.resetEnvironment : this.showConfirmationForm}
>
{i18n('common.reset_button')}
@ -634,7 +737,11 @@ export var ShowNodeInfoDialog = React.createClass({
},
goToConfigurationScreen(url) {
this.close();
app.navigate('#cluster/' + this.props.node.get('cluster') + '/nodes/' + url + '/' + utils.serializeTabOptions({nodes: this.props.node.id}), {trigger: true});
app.navigate(
'#cluster/' + this.props.node.get('cluster') + '/nodes/' + url + '/' +
utils.serializeTabOptions({nodes: this.props.node.id}),
{trigger: true}
);
},
showSummary(meta, group) {
var summary = '';
@ -647,21 +754,27 @@ export var ShowNodeInfoDialog = React.createClass({
if (_.isArray(meta.memory.devices) && meta.memory.devices.length) {
var sizes = _.countBy(_.pluck(meta.memory.devices, 'size'), utils.showMemorySize);
summary = _.map(_.keys(sizes).sort(), (size) => sizes[size] + ' x ' + size).join(', ');
summary += ', ' + utils.showMemorySize(meta.memory.total) + ' ' + i18n('dialog.show_node.total');
} else summary = utils.showMemorySize(meta.memory.total) + ' ' + i18n('dialog.show_node.total');
summary += ', ' + utils.showMemorySize(meta.memory.total) + ' ' +
i18n('dialog.show_node.total');
} else summary = utils.showMemorySize(meta.memory.total) + ' ' +
i18n('dialog.show_node.total');
break;
case 'disks':
summary = meta.disks.length + ' ';
summary += i18n('dialog.show_node.drive', {count: meta.disks.length});
summary += ', ' + utils.showDiskSize(_.reduce(_.pluck(meta.disks, 'size'), (sum, n) => sum + n, 0)) + ' ' + i18n('dialog.show_node.total');
summary += ', ' + utils.showDiskSize(_.reduce(_.pluck(meta.disks, 'size'), (sum, n) =>
sum + n, 0)) + ' ' + i18n('dialog.show_node.total');
break;
case 'cpu':
var frequencies = _.countBy(_.pluck(meta.cpu.spec, 'frequency'), utils.showFrequency);
summary = _.map(_.keys(frequencies).sort(), (frequency) => frequencies[frequency] + ' x ' + frequency).join(', ');
summary = _.map(_.keys(frequencies).sort(), (frequency) => frequencies[frequency] +
' x ' + frequency).join(', ');
break;
case 'interfaces':
var bandwidths = _.countBy(_.pluck(meta.interfaces, 'current_speed'), utils.showBandwidth);
summary = _.map(_.keys(bandwidths).sort(), (bandwidth) => bandwidths[bandwidth] + ' x ' + bandwidth).join(', ');
var bandwidths = _.countBy(_.pluck(meta.interfaces, 'current_speed'),
utils.showBandwidth);
summary = _.map(_.keys(bandwidths).sort(), (bandwidth) => bandwidths[bandwidth] +
' x ' + bandwidth).join(', ');
break;
}
} catch (ignore) {}
@ -714,8 +827,10 @@ export var ShowNodeInfoDialog = React.createClass({
},
assignAccordionEvents() {
$('.panel-collapse', ReactDOM.findDOMNode(this))
.on('show.bs.collapse', (e) => $(e.currentTarget).siblings('.panel-heading').find('i').removeClass('glyphicon-plus').addClass('glyphicon-minus'))
.on('hide.bs.collapse', (e) => $(e.currentTarget).siblings('.panel-heading').find('i').removeClass('glyphicon-minus').addClass('glyphicon-plus'))
.on('show.bs.collapse', (e) => $(e.currentTarget).siblings('.panel-heading').find('i')
.removeClass('glyphicon-plus').addClass('glyphicon-minus'))
.on('hide.bs.collapse', (e) => $(e.currentTarget).siblings('.panel-heading').find('i')
.removeClass('glyphicon-minus').addClass('glyphicon-plus'))
.on('hidden.bs.collapse', (e) => e.stopPropagation());
},
toggle(groupIndex) {
@ -777,7 +892,8 @@ export var ShowNodeInfoDialog = React.createClass({
var groups = _.sortBy(_.keys(meta), (group) => _.indexOf(groupOrder, group));
var sortOrder = {
disks: ['name', 'model', 'size'],
interfaces: ['name', 'mac', 'state', 'ip', 'netmask', 'current_speed', 'max_speed', 'driver', 'bus_info']
interfaces: ['name', 'mac', 'state', 'ip', 'netmask', 'current_speed', 'max_speed',
'driver', 'bus_info']
};
if (this.state.VMsConf) groups.push('config');
@ -787,17 +903,26 @@ export var ShowNodeInfoDialog = React.createClass({
<div className='col-xs-5'><div className='node-image-outline' /></div>
<div className='col-xs-7 node-summary'>
{this.props.cluster &&
<div><strong>{i18n('dialog.show_node.cluster')}: </strong>{this.props.cluster.get('name')}</div>
<div><strong>{i18n('dialog.show_node.cluster')}: </strong>
{this.props.cluster.get('name')}
</div>
}
<div><strong>{i18n('dialog.show_node.manufacturer_label')}: </strong>{node.get('manufacturer') || i18n('common.not_available')}</div>
<div><strong>{i18n('dialog.show_node.manufacturer_label')}: </strong>
{node.get('manufacturer') || i18n('common.not_available')}
</div>
{this.props.nodeNetworkGroup &&
<div>
<strong>{i18n('dialog.show_node.node_network_group')}: </strong>
{this.props.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><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'>
<strong>{i18n('dialog.show_node.hostname_label')}: </strong>
{this.state.isRenaming ?
@ -832,28 +957,57 @@ export var ShowNodeInfoDialog = React.createClass({
{_.map(groups, (group, groupIndex) => {
var groupEntries = meta[group];
var subEntries = [];
if (group == 'interfaces' || group == 'disks') groupEntries = _.sortBy(groupEntries, 'name');
if (_.isPlainObject(groupEntries)) subEntries = _.find(_.values(groupEntries), _.isArray);
if (group == 'interfaces' || group == 'disks') {
groupEntries = _.sortBy(groupEntries, 'name');
}
if (_.isPlainObject(groupEntries)) {
subEntries = _.find(_.values(groupEntries), _.isArray);
}
return (
<div className='panel panel-default' key={group}>
<div className='panel-heading' role='tab' id={'heading' + group} onClick={this.toggle.bind(this, groupIndex)}>
<div
className='panel-heading'
role='tab'
id={'heading' + group}
onClick={this.toggle.bind(this, groupIndex)}
>
<div className='panel-title'>
<div data-parent='#accordion' aria-expanded='true' aria-controls={'body' + group}>
<strong>{i18n('node_details.' + group, {defaultValue: group})}</strong> {this.showSummary(meta, group)}
<div
data-parent='#accordion'
aria-expanded='true'
aria-controls={'body' + group}
>
<strong>{i18n('node_details.' + group, {defaultValue: group})}</strong>
{this.showSummary(meta, group)}
<i className='glyphicon glyphicon-plus pull-right' />
</div>
</div>
</div>
<div className='panel-collapse collapse' role='tabpanel' aria-labelledby={'heading' + group} ref={'togglable_' + groupIndex}>
<div
className='panel-collapse collapse'
role='tabpanel'
aria-labelledby={'heading' + group}
ref={'togglable_' + groupIndex}
>
<div className='panel-body enable-selection'>
{_.isArray(groupEntries) &&
<div>
{_.map(groupEntries, (entry, entryIndex) => {
return (
<div className='nested-object' key={'entry_' + groupIndex + entryIndex}>
{_.map(utils.sortEntryProperties(entry, sortOrder[group]), (propertyName) => {
if (!_.isPlainObject(entry[propertyName]) && !_.isArray(entry[propertyName])) return this.renderNodeInfo(propertyName, this.showPropertyValue(group, propertyName, entry[propertyName]));
})}
{_.map(utils.sortEntryProperties(entry, sortOrder[group]),
(propertyName) => {
if (!_.isPlainObject(entry[propertyName]) &&
!_.isArray(entry[propertyName])) {
return this.renderNodeInfo(
propertyName,
this.showPropertyValue(
group, propertyName, entry[propertyName]
)
);
}
}
)}
</div>
);
})}
@ -862,15 +1016,25 @@ export var ShowNodeInfoDialog = React.createClass({
{_.isPlainObject(groupEntries) &&
<div>
{_.map(groupEntries, (propertyValue, propertyName) => {
if (!_.isPlainObject(propertyValue) && !_.isArray(propertyValue) && !_.isNumber(propertyName)) return this.renderNodeInfo(propertyName, this.showPropertyValue(group, propertyName, propertyValue));
if (!_.isPlainObject(propertyValue) && !_.isArray(propertyValue) &&
!_.isNumber(propertyName)) return this.renderNodeInfo(propertyName,
this.showPropertyValue(group, propertyName, propertyValue));
})}
{!_.isEmpty(subEntries) &&
<div>
{_.map(subEntries, (subentry, subentrysIndex) => {
return (
<div className='nested-object' key={'subentries_' + groupIndex + subentrysIndex}>
<div
className='nested-object'
key={'subentries_' + groupIndex + subentrysIndex}
>
{_.map(utils.sortEntryProperties(subentry), (propertyName) => {
if (!_.isPlainObject(subentry[propertyName]) && !_.isArray(subentry[propertyName])) return this.renderNodeInfo(propertyName, this.showPropertyValue(group, propertyName, subentry[propertyName]));
return this.renderNodeInfo(
propertyName,
this.showPropertyValue(
group, propertyName, subentry[propertyName]
)
);
})}
</div>
);
@ -879,7 +1043,8 @@ export var ShowNodeInfoDialog = React.createClass({
}
</div>
}
{(!_.isPlainObject(groupEntries) && !_.isArray(groupEntries) && !_.isUndefined(groupEntries)) &&
{(!_.isPlainObject(groupEntries) && !_.isArray(groupEntries) &&
!_.isUndefined(groupEntries)) &&
<div>{groupEntries}</div>
}
{group == 'config' &&
@ -895,7 +1060,8 @@ export var ShowNodeInfoDialog = React.createClass({
<button
className='btn btn-success'
onClick={this.saveVMsConf}
disabled={this.state.VMsConfValidationError || this.state.actionInProgress}
disabled={this.state.VMsConfValidationError ||
this.state.actionInProgress}
>
{i18n('common.save_settings_button')}
</button>
@ -915,16 +1081,29 @@ export var ShowNodeInfoDialog = React.createClass({
<div>
{this.props.renderActionButtons && this.props.node.get('cluster') &&
<div className='btn-group' role='group'>
<button className='btn btn-default btn-edit-disks' onClick={_.partial(this.goToConfigurationScreen, 'disks')}>
{i18n('dialog.show_node.disk_configuration' + (this.props.node.areDisksConfigurable() ? '_action' : ''))}
<button
className='btn btn-default btn-edit-disks'
onClick={_.partial(this.goToConfigurationScreen, 'disks')}
>
{i18n('dialog.show_node.disk_configuration' +
(this.props.node.areDisksConfigurable() ? '_action' : ''))}
</button>
<button className='btn btn-default btn-edit-networks' onClick={_.partial(this.goToConfigurationScreen, 'interfaces')}>
{i18n('dialog.show_node.network_configuration' + (this.props.node.areInterfacesConfigurable() ? '_action' : ''))}
<button
className='btn btn-default btn-edit-networks'
onClick={_.partial(this.goToConfigurationScreen, 'interfaces')}
>
{i18n('dialog.show_node.network_configuration' +
(this.props.node.areInterfacesConfigurable() ? '_action' : ''))}
</button>
</div>
}
<div className='btn-group' role='group'>
<button className='btn btn-default' onClick={this.close}>{i18n('common.close_button')}</button>
<button
className='btn btn-default'
onClick={this.close}
>
{i18n('common.close_button')}
</button>
</div>
</div>
);
@ -932,7 +1111,9 @@ export var ShowNodeInfoDialog = React.createClass({
renderNodeInfo(name, value) {
return (
<div key={name + value} className='node-details-row'>
<label>{i18n('dialog.show_node.' + name, {defaultValue: this.showPropertyName(name)})}</label>
<label>
{i18n('dialog.show_node.' + name, {defaultValue: this.showPropertyName(name)})}
</label>
{value}
</div>
);
@ -1042,7 +1223,8 @@ export var DeleteNodesDialog = React.createClass({
{this.renderImportantLabel()}
{i18n(ns + 'common_message', {count: this.props.nodes.length})}
<br/>
{!!notDeployedNodesAmount && i18n(ns + 'not_deployed_nodes_message', {count: notDeployedNodesAmount})}
{!!notDeployedNodesAmount && i18n(ns + 'not_deployed_nodes_message',
{count: notDeployedNodesAmount})}
{' '}
{!!deployedNodesAmount && i18n(ns + 'deployed_nodes_message', {count: deployedNodesAmount})}
</div>
@ -1050,8 +1232,18 @@ export var DeleteNodesDialog = React.createClass({
},
renderFooter() {
return [
<button key='cancel' className='btn btn-default' onClick={this.close}>{i18n('common.cancel_button')}</button>,
<button key='delete' className='btn btn-danger btn-delete' onClick={this.deleteNodes} disabled={this.state.actionInProgress}>{i18n('common.delete_button')}</button>
<button
key='cancel'
className='btn btn-default'
onClick={this.close}>{i18n('common.cancel_button')}
</button>,
<button
key='delete'
className='btn btn-danger btn-delete'
onClick={this.deleteNodes} disabled={this.state.actionInProgress}
>
{i18n('common.delete_button')}
</button>
];
},
deleteNodes() {
@ -1075,12 +1267,14 @@ export var DeleteNodesDialog = React.createClass({
return this.props.cluster.fetchRelated('nodes');
})
.done(() => {
dispatcher.trigger('updateNodeStats networkConfigurationUpdated labelsConfigurationUpdated');
dispatcher.trigger('updateNodeStats networkConfigurationUpdated ' +
'labelsConfigurationUpdated');
this.state.result.resolve();
this.close();
})
.fail((response) => {
this.showError(response, i18n('cluster_page.nodes_tab.node_deletion_error.node_deletion_warning'));
this.showError(response, i18n('cluster_page.nodes_tab.node_deletion_error.' +
'node_deletion_warning'));
});
}
});
@ -1106,7 +1300,9 @@ export var ChangePasswordDialog = React.createClass({
},
getError(name) {
var ns = 'dialog.change_password.';
if (name == 'currentPassword' && this.state.validationError) return i18n(ns + 'wrong_current_password');
if (name == 'currentPassword' && this.state.validationError) {
return i18n(ns + 'wrong_current_password');
}
if (this.state.newPassword != this.state.confirmationPassword) {
if (name == 'confirmationPassword') return i18n(ns + 'new_password_mismatch');
if (name == 'newPassword') return '';
@ -1140,7 +1336,12 @@ export var ChangePasswordDialog = React.createClass({
},
renderFooter() {
return [
<button key='cancel' className='btn btn-default' onClick={this.close} disabled={this.state.actionInProgress}>
<button
key='cancel'
className='btn btn-default'
onClick={this.close}
disabled={this.state.actionInProgress}
>
{i18n('common.cancel_button')}
</button>,
<button key='apply' className='btn btn-success' onClick={this.changePassword}
@ -1177,7 +1378,8 @@ export var ChangePasswordDialog = React.createClass({
this.setState({actionInProgress: true});
keystoneClient.changePassword(this.state.currentPassword, this.state.newPassword)
.done(() => {
dispatcher.trigger(this.state.newPassword == keystoneClient.DEFAULT_PASSWORD ? 'showDefaultPasswordWarning' : 'hideDefaultPasswordWarning');
dispatcher.trigger(this.state.newPassword == keystoneClient.DEFAULT_PASSWORD ?
'showDefaultPasswordWarning' : 'hideDefaultPasswordWarning');
app.user.set({token: keystoneClient.token});
this.close();
})
@ -1225,7 +1427,9 @@ export var RegistrationDialog = React.createClass({
onChange(inputName, value) {
var registrationForm = this.props.registrationForm;
var name = registrationForm.makePath('credentials', inputName, 'value');
if (registrationForm.validationError) delete registrationForm.validationError['credentials.' + inputName];
if (registrationForm.validationError) {
delete registrationForm.validationError['credentials.' + inputName];
}
registrationForm.set(name, value);
},
composeOptions(values) {
@ -1238,14 +1442,21 @@ export var RegistrationDialog = React.createClass({
});
},
getAgreementLink(link) {
return (<span>{i18n('dialog.registration.i_agree')} <a href={link} target='_blank'>{i18n('dialog.registration.terms_and_conditions')}</a></span>);
return (
<span>
{i18n('dialog.registration.i_agree')}
<a href={link} target='_blank'>
{i18n('dialog.registration.terms_and_conditions')}
</a>
</span>);
},
validateRegistrationForm() {
var registrationForm = this.props.registrationForm;
var isValid = registrationForm.isValid();
if (!registrationForm.attributes.credentials.agree.value) {
if (!registrationForm.validationError) registrationForm.validationError = {};
registrationForm.validationError['credentials.agree'] = i18n('dialog.registration.agree_error');
registrationForm.validationError['credentials.agree'] =
i18n('dialog.registration.agree_error');
isValid = false;
}
this.setState({
@ -1263,7 +1474,10 @@ export var RegistrationDialog = React.createClass({
var collector = (path) => {
return (name) => {
this.props.settings.set(this.props.settings.makePath(path, name, 'value'), response[name]);
this.props.settings.set(
this.props.settings.makePath(path, name, 'value'),
response[name]
);
};
};
_.each(['company', 'name', 'email'], collector('statistics'));
@ -1346,7 +1560,12 @@ export var RegistrationDialog = React.createClass({
</button>
];
if (!this.state.loading) buttons.push(
<button key='apply' className='btn btn-success' disabled={this.state.actionInProgress || this.state.connectionError} onClick={this.validateRegistrationForm}>
<button
key='apply'
className='btn btn-success'
disabled={this.state.actionInProgress || this.state.connectionError}
onClick={this.validateRegistrationForm}
>
{i18n('welcome_page.register.create_account')}
</button>
);
@ -1386,7 +1605,9 @@ export var RetrievePasswordDialog = React.createClass({
},
onChange(inputName, value) {
var remoteRetrievePasswordForm = this.props.remoteRetrievePasswordForm;
if (remoteRetrievePasswordForm.validationError) delete remoteRetrievePasswordForm.validationError['credentials.email'];
if (remoteRetrievePasswordForm.validationError) {
delete remoteRetrievePasswordForm.validationError['credentials.email'];
}
remoteRetrievePasswordForm.set('credentials.email.value', value);
},
retrievePassword() {
@ -1411,7 +1632,8 @@ export var RetrievePasswordDialog = React.createClass({
var error = this.state.error;
var actionInProgress = this.state.actionInProgress;
var input = (remoteRetrievePasswordForm.get('credentials') || {}).email;
var inputError = remoteRetrievePasswordForm ? (remoteRetrievePasswordForm.validationError || {})['credentials.email'] : null;
var inputError = remoteRetrievePasswordForm ? (remoteRetrievePasswordForm.validationError ||
{})['credentials.email'] : null;
return (
<div className='retrieve-password-content'>
{!this.state.passwordSent ?
@ -1457,7 +1679,12 @@ export var RetrievePasswordDialog = React.createClass({
</button>
];
if (!this.state.loading) buttons.push(
<button key='apply' className='btn btn-success' disabled={this.state.actionInProgress || this.state.connectionError} onClick={this.retrievePassword}>
<button
key='apply'
className='btn btn-success'
disabled={this.state.actionInProgress || this.state.connectionError}
onClick={this.retrievePassword}
>
{i18n('dialog.retrieve_password.send_new_password')}
</button>
);
@ -1498,10 +1725,20 @@ export var CreateNodeNetworkGroupDialog = React.createClass({
},
renderFooter() {
return [
<button key='cancel' className='btn btn-default' onClick={this.close} disabled={this.state.actionInProgress}>
<button
key='cancel'
className='btn btn-default'
onClick={this.close}
disabled={this.state.actionInProgress}
>
{i18n('common.cancel_button')}
</button>,
<button key='apply' className='btn btn-success' onClick={this.createNodeNetworkGroup} disabled={this.state.actionInProgress || this.state.error}>
<button
key='apply'
className='btn btn-success'
onClick={this.createNodeNetworkGroup}
disabled={this.state.actionInProgress || this.state.error}
>
{i18n(this.props.ns + 'add')}
</button>
];
@ -1556,7 +1793,9 @@ export var RemoveNodeNetworkGroupDialog = React.createClass({
<div>
<div className='text-danger'>
{this.renderImportantLabel()}
{this.props.showUnsavedChangesWarning && (i18n('dialog.remove_node_network_group.unsaved_changes_alert') + ' ')}
{this.props.showUnsavedChangesWarning &&
(i18n('dialog.remove_node_network_group.unsaved_changes_alert') + ' ')
}
{i18n('dialog.remove_node_network_group.confirmation')}
</div>
</div>

View File

@ -52,7 +52,10 @@ export var Navbar = React.createClass({
return this.props.user.get('authenticated');
},
fetchData() {
return $.when(this.props.statistics.fetch(), this.props.notifications.fetch({limit: this.props.notificationsDisplayCount}));
return $.when(
this.props.statistics.fetch(),
this.props.notifications.fetch({limit: this.props.notificationsDisplayCount})
);
},
updateNodeStats() {
return this.props.statistics.fetch();
@ -91,7 +94,8 @@ export var Navbar = React.createClass({
},
render() {
var unreadNotificationsCount = this.props.notifications.where({status: 'unread'}).length;
var authenticationEnabled = this.props.version.get('auth_required') && this.props.user.get('authenticated');
var authenticationEnabled = this.props.version.get('auth_required') &&
this.props.user.get('authenticated');
return (
<div className='navigation-box'>
<div className='navbar-bg'></div>
@ -104,7 +108,12 @@ export var Navbar = React.createClass({
<ul className='nav navbar-nav pull-left'>
{_.map(this.props.elements, (element) => {
return (
<li className={utils.classNames({active: this.props.activeElement == element.url.slice(1)})} key={element.label}>
<li
className={utils.classNames({
active: this.props.activeElement == element.url.slice(1)
})}
key={element.label}
>
<a href={element.url}>
{i18n('navbar.' + element.label, {defaultValue: element.label})}
</a>
@ -128,7 +137,10 @@ export var Navbar = React.createClass({
</li>
<li
key='statistics-icon'
className={'statistics-icon ' + (this.props.statistics.get('unallocated') ? '' : 'no-unallocated')}
className={
'statistics-icon ' +
(this.props.statistics.get('unallocated') ? '' : 'no-unallocated')
}
onClick={this.togglePopover('statistics')}
>
{!!this.props.statistics.get('unallocated') &&
@ -148,7 +160,9 @@ export var Navbar = React.createClass({
className='notifications-icon'
onClick={this.togglePopover('notifications')}
>
<span className={utils.classNames({badge: true, visible: unreadNotificationsCount})}>
<span
className={utils.classNames({badge: true, visible: unreadNotificationsCount})}
>
{unreadNotificationsCount}
</span>
</li>
@ -258,7 +272,10 @@ var UserPopover = React.createClass({
<div className='username'>{i18n('common.username')}:</div>
<h3 className='name'>{this.props.user.get('username')}</h3>
<div className='clearfix'>
<button className='btn btn-default btn-sm pull-left' onClick={this.showChangePasswordDialog}>
<button
className='btn btn-default btn-sm pull-left'
onClick={this.showChangePasswordDialog}
>
<i className='glyphicon glyphicon-user'></i>
{i18n('common.change_password')}
</button>
@ -281,7 +298,9 @@ var NotificationsPopover = React.createClass({
ShowNodeInfoDialog.show({node: node});
},
markAsRead() {
var notificationsToMark = new models.Notifications(this.props.notifications.where({status: 'unread'}));
var notificationsToMark = new models.Notifications(
this.props.notifications.where({status: 'unread'})
);
if (notificationsToMark.length) {
this.setState({unreadNotificationsIds: notificationsToMark.pluck('id')});
notificationsToMark.toJSON = function() {
@ -307,7 +326,8 @@ var NotificationsPopover = React.createClass({
'text-danger': topic == 'error',
'text-warning': topic == 'warning',
clickable: nodeId,
unread: notification.get('status') == 'unread' || _.contains(this.state.unreadNotificationsIds, notification.id)
unread: notification.get('status') == 'unread' ||
_.contains(this.state.unreadNotificationsIds, notification.id)
};
var iconClass = {
error: 'glyphicon-exclamation-sign',
@ -347,7 +367,12 @@ export var Footer = React.createClass({
return (
<div className='footer'>
{_.contains(version.get('feature_groups'), 'mirantis') && [
<a key='logo' className='mirantis-logo-white' href='http://www.mirantis.com/' target='_blank'></a>,
<a
key='logo'
className='mirantis-logo-white'
href='http://www.mirantis.com/'
target='_blank'
/>,
<div key='copyright'>{i18n('common.copyright')}</div>
]}
<div key='version'>{i18n('common.version')}: {version.get('release')}</div>
@ -365,7 +390,8 @@ export var Breadcrumbs = React.createClass({
},
getBreadcrumbsPath() {
var page = this.props.Page;
return _.isFunction(page.breadcrumbsPath) ? page.breadcrumbsPath(this.props.pageOptions) : page.breadcrumbsPath;
return _.isFunction(page.breadcrumbsPath) ? page.breadcrumbsPath(this.props.pageOptions) :
page.breadcrumbsPath;
},
refresh() {
this.setState({path: this.getBreadcrumbsPath()});

View File

@ -62,7 +62,8 @@ var LoginForm = React.createClass({
var error = 'login_error';
if (status == 401) {
error = 'credentials_error';
} else if (!status || String(status)[0] == '5') { // no status (connection refused) or 5xx error
// no status (connection refused) or 5xx error
} else if (!status || String(status)[0] == '5') {
error = 'keystone_unavailable_error';
}
this.setState({error: i18n('login_page.' + error)});
@ -126,7 +127,14 @@ var LoginForm = React.createClass({
<i className='glyphicon glyphicon-user'></i>
</label>
<div className='col-xs-8'>
<input className='form-control input-sm' type='text' name='username' ref='username' placeholder={i18n('login_page.username')} onChange={this.onChange} />
<input
className='form-control input-sm'
type='text'
name='username'
ref='username'
placeholder={i18n('login_page.username')}
onChange={this.onChange}
/>
</div>
</div>
<div className='form-group'>
@ -134,7 +142,14 @@ var LoginForm = React.createClass({
<i className='glyphicon glyphicon-lock'></i>
</label>
<div className='col-xs-8'>
<input className='form-control input-sm' type='password' name='password' ref='password' placeholder={i18n('login_page.password')} onChange={this.onChange} />
<input
className='form-control input-sm'
type='password'
name='password'
ref='password'
placeholder={i18n('login_page.password')}
onChange={this.onChange}
/>
</div>
</div>
{!httpsUsed &&

View File

@ -110,7 +110,12 @@ Notification = React.createClass({
<div className='notification-time'>{this.props.notification.get('time')}</div>
<div className='notification-type'><i className={'glyphicon ' + iconClass} /></div>
<div className='notification-message'>
<span className={this.props.notification.get('node_id') && 'btn btn-link'} dangerouslySetInnerHTML={{__html: utils.urlify(this.props.notification.escape('message'))}}></span>
<span
className={this.props.notification.get('node_id') && 'btn btn-link'}
dangerouslySetInnerHTML={{
__html: utils.urlify(this.props.notification.escape('message'))
}}
/>
</div>
</div>
);

View File

@ -103,7 +103,9 @@ var PluginsPage = React.createClass({
render() {
var isMirantisIso = _.contains(app.version.get('feature_groups'), 'mirantis');
var links = {
catalog: isMirantisIso ? 'https://www.mirantis.com/products/openstack-drivers-and-plugins/fuel-plugins/' : 'http://stackalytics.com/report/driverlog?project_id=openstack%2Ffuel',
catalog: isMirantisIso ?
'https://www.mirantis.com/products/openstack-drivers-and-plugins/fuel-plugins/' :
'http://stackalytics.com/report/driverlog?project_id=openstack%2Ffuel',
documentation: utils.composeDocumentationLink('plugin-dev.html')
};
return (

View File

@ -68,10 +68,23 @@ var RootComponent = React.createClass({
<div id='content-wrapper'>
<div className={utils.classNames(layoutClasses)}>
{!Page.hiddenLayout && [
<Navbar key='navbar' ref='navbar' activeElement={Page.navbarActiveElement} {...this.props} />,
<Navbar
key='navbar'
ref='navbar'
activeElement={Page.navbarActiveElement}
{...this.props}
/>,
<Breadcrumbs key='breadcrumbs' ref='breadcrumbs' {...this.state} />,
showDefaultPasswordWarning && <DefaultPasswordWarning key='password-warning' close={this.hideDefaultPasswordWarning} />,
fuelSettings.get('bootstrap.error.value') && <BootstrapError key='bootstrap-error' text={fuelSettings.get('bootstrap.error.value')} />
showDefaultPasswordWarning &&
<DefaultPasswordWarning
key='password-warning'
close={this.hideDefaultPasswordWarning}
/>,
fuelSettings.get('bootstrap.error.value') &&
<BootstrapError
key='bootstrap-error'
text={fuelSettings.get('bootstrap.error.value')}
/>
]}
<div id='content'>
<Page ref='page' {...this.state.pageOptions} />

View File

@ -109,7 +109,11 @@ export default {
}
},
checkRestrictions(name, action = 'disable') {
return this.props.settings.checkRestrictions(this.configModels, action, this.props.settings.get('statistics').name);
return this.props.settings.checkRestrictions(
this.configModels,
action,
this.props.settings.get('statistics').name
);
},
componentWillMount() {
var model = this.props.statistics || this.props.tracking;
@ -129,9 +133,15 @@ export default {
renderInput(settingName, wrapperClassName, disabledState) {
var model = this.props.statistics || this.props.tracking;
var setting = model.get(model.makePath('statistics', settingName));
if (this.checkRestrictions('metadata', 'hide').result || this.checkRestrictions(settingName, 'hide').result || setting.type == 'hidden') return null;
if (
this.checkRestrictions('metadata', 'hide').result ||
this.checkRestrictions(settingName, 'hide').result ||
setting.type == 'hidden'
) return null;
var error = this.getError(model, settingName);
var disabled = this.checkRestrictions('metadata').result || this.checkRestrictions(settingName).result || disabledState;
var disabled = this.checkRestrictions('metadata').result ||
this.checkRestrictions(settingName).result ||
disabledState;
return <Input
key={settingName}
type={setting.type}
@ -198,8 +208,16 @@ export default {
return (
<div>
<div className='statistics-text-box'>
<div className={utils.classNames({notice: isMirantisIso})}>{this.getText(ns + 'help_to_improve')}</div>
<button className='btn-link' data-toggle='collapse' data-target='.statistics-disclaimer-box'>{i18n(ns + 'learn_whats_collected')}</button>
<div className={utils.classNames({notice: isMirantisIso})}>
{this.getText(ns + 'help_to_improve')}
</div>
<button
className='btn-link'
data-toggle='collapse'
data-target='.statistics-disclaimer-box'
>
{i18n(ns + 'learn_whats_collected')}
</button>
<div className='collapse statistics-disclaimer-box'>
<p>{i18n(ns + 'statistics_includes_full')}</p>
{_.map(lists, this.renderList)}
@ -258,10 +276,16 @@ export default {
/>;
})}
<div className='links-container'>
<button className='btn btn-link create-account pull-left' onClick={this.showRegistrationDialog}>
<button
className='btn btn-link create-account pull-left'
onClick={this.showRegistrationDialog}
>
{i18n('welcome_page.register.create_account')}
</button>
<button className='btn btn-link retrive-password pull-right' onClick={this.showRetrievePasswordDialog}>
<button
className='btn btn-link retrive-password pull-right'
onClick={this.showRetrievePasswordDialog}
>
{i18n('welcome_page.register.retrieve_password')}
</button>
</div>

View File

@ -46,17 +46,33 @@ var SupportPage = React.createClass({
render() {
var elements = [
<DocumentationLink key='DocumentationLink' />,
<DiagnosticSnapshot key='DiagnosticSnapshot' tasks={this.props.tasks} task={this.props.tasks.findTask({name: 'dump'})} />,
<DiagnosticSnapshot
key='DiagnosticSnapshot'
tasks={this.props.tasks}
task={this.props.tasks.findTask({name: 'dump'})}
/>,
<CapacityAudit key='CapacityAudit' />
];
if (_.contains(app.version.get('feature_groups'), 'mirantis')) {
elements.unshift(
<RegistrationInfo key='RegistrationInfo' settings={this.props.settings} tracking={this.props.tracking}/>,
<StatisticsSettings key='StatisticsSettings' settings={this.props.settings} statistics={this.props.statistics}/>,
<RegistrationInfo
key='RegistrationInfo'
settings={this.props.settings}
tracking={this.props.tracking}
/>,
<StatisticsSettings
key='StatisticsSettings'
settings={this.props.settings}
statistics={this.props.statistics}
/>,
<SupportContacts key='SupportContacts' />
);
} else {
elements.push(<StatisticsSettings key='StatisticsSettings' settings={this.props.settings} statistics={this.props.statistics}/>);
elements.push(<StatisticsSettings
key='StatisticsSettings'
settings={this.props.settings}
statistics={this.props.statistics}
/>);
}
return (
<div className='support-page'>
@ -92,7 +108,8 @@ var SupportPageElement = React.createClass({
var DocumentationLink = React.createClass({
render() {
var ns = 'support_page.' + (_.contains(app.version.get('feature_groups'), 'mirantis') ? 'mirantis' : 'community') + '_';
var ns = 'support_page.' + (_.contains(app.version.get('feature_groups'), 'mirantis') ?
'mirantis' : 'community') + '_';
return (
<SupportPageElement
className='img-documentation-link'
@ -100,7 +117,11 @@ var DocumentationLink = React.createClass({
text={i18n(ns + 'text')}
>
<p>
<a className='btn btn-default documentation-link' href='https://www.mirantis.com/openstack-documentation/' target='_blank'>
<a
className='btn btn-default documentation-link'
href='https://www.mirantis.com/openstack-documentation/'
target='_blank'
>
{i18n('support_page.documentation_link')}
</a>
</p>
@ -124,12 +145,26 @@ var RegistrationInfo = React.createClass({
>
<div className='registeredData enable-selection'>
{_.map(['name', 'email', 'company'], (value) => {
return <div key={value}><b>{i18n('statistics.setting_labels.' + value)}:</b> {this.props.tracking.get('statistics')[value].value}</div>;
return (
<div key={value}>
<b>{i18n('statistics.setting_labels.' + value)}:</b>
{' '}
{this.props.tracking.get('statistics')[value].value}
</div>
);
})}
<div><b>{i18n('support_page.master_node_uuid')}:</b> {this.props.tracking.get('master_node_uid')}</div>
<div>
<b>{i18n('support_page.master_node_uuid')}:</b>
{' '}
{this.props.tracking.get('master_node_uid')}
</div>
</div>
<p>
<a className='btn btn-default' href='https://software.mirantis.com/account/' target='_blank'>
<a
className='btn btn-default'
href='https://software.mirantis.com/account/'
target='_blank'
>
{i18n('support_page.manage_account')}
</a>
</p>
@ -142,9 +177,18 @@ var RegistrationInfo = React.createClass({
text={i18n('support_page.register_fuel_content')}
>
<div className='tracking'>
{this.renderRegistrationForm(this.props.tracking, this.state.actionInProgress, this.state.error, this.state.actionInProgress)}
{this.renderRegistrationForm(
this.props.tracking,
this.state.actionInProgress,
this.state.error,
this.state.actionInProgress
)}
<p>
<button className='btn btn-default' onClick={this.connectToMirantis} disabled={this.state.actionInProgress} target='_blank'>
<button
className='btn btn-default'
onClick={this.connectToMirantis}
disabled={this.state.actionInProgress} target='_blank'
>
{i18n('support_page.register_fuel_title')}
</button>
</p>
@ -212,9 +256,17 @@ var SupportContacts = React.createClass({
title={i18n('support_page.contact_support')}
text={i18n('support_page.contact_text')}
>
<p>{i18n('support_page.irc_text')} <strong>#fuel</strong> on <a href='http://freenode.net' target='_blank'>freenode.net</a>.</p>
<p>
<a className='btn btn-default' href='http://support.mirantis.com/requests/new' target='_blank'>
{i18n('support_page.irc_text')}
{' '}
<strong>#fuel</strong> on <a href='http://freenode.net' target='_blank'>freenode.net</a>.
</p>
<p>
<a
className='btn btn-default'
href='http://support.mirantis.com/requests/new'
target='_blank'
>
{i18n('support_page.contact_support')}
</a>
</p>
@ -244,7 +296,7 @@ var DiagnosticSnapshot = React.createClass({
},
downloadLogs() {
this.setState({generating: true});
(new models.LogsPackage()).save({}, {method: 'PUT'}).always(_.bind(this.props.tasks.fetch, this.props.tasks));
(new models.LogsPackage()).save({}, {method: 'PUT'}).always(() => this.props.tasks.fetch());
},
componentDidUpdate() {
this.startPolling();
@ -260,7 +312,8 @@ var DiagnosticSnapshot = React.createClass({
>
<p className='snapshot'>
<button className='btn btn-default' disabled={generating} onClick={this.downloadLogs}>
{generating ? i18n('support_page.gen_logs_snapshot_text') : i18n('support_page.gen_diagnostic_snapshot_text')}
{generating ? i18n('support_page.gen_logs_snapshot_text') :
i18n('support_page.gen_diagnostic_snapshot_text')}
</button>
{' '}
{!generating && task &&

View File

@ -80,13 +80,25 @@ var WelcomePage = React.createClass({
<div className='happy-cloud'>
<div className='cloud-smile' />
<div className='row'>
<div className='col-xs-8 col-xs-offset-2'>{i18n(ns + 'register.welcome_phrase.thanks')}{username ? ' ' + username : ''}, {i18n(ns + 'register.welcome_phrase.content')}</div>
<div className='col-xs-8 col-xs-offset-2'>
{i18n(ns + 'register.welcome_phrase.thanks')}
{username ? ' ' + username : ''}
{', '}
{i18n(ns + 'register.welcome_phrase.content')}
</div>
</div>
</div>
:
<div>
<p className='register_installation'>{i18n(ns + 'register.register_installation')}</p>
{this.renderRegistrationForm(this.props.tracking, disabled, this.state.error, this.state.actionInProgress && !this.state.locked)}
<p className='register_installation'>
{i18n(ns + 'register.register_installation')}
</p>
{this.renderRegistrationForm(
this.props.tracking,
disabled,
this.state.error,
this.state.actionInProgress && !this.state.locked
)}
</div>
}
</div>
@ -96,7 +108,9 @@ var WelcomePage = React.createClass({
{this.renderInput('send_user_info', 'welcome-checkbox-box', disabled)}
<div>
<div className='notice'>{i18n(ns + 'privacy_policy')}</div>
<div><a href={privacyPolicyLink} target='_blank'>{i18n(ns + 'privacy_policy_link')}</a></div>
<div>
<a href={privacyPolicyLink} target='_blank'>{i18n(ns + 'privacy_policy_link')}</a>
</div>
</div>
</div>
:

View File

@ -111,7 +111,10 @@ var ComponentRadioGroup = React.createClass({
var ClusterWizardPanesMixin = {
componentWillMount() {
if (this.props.allComponents) {
this.components = this.props.allComponents.getComponentsByType(this.constructor.componentType, {sorted: true});
this.components = this.props.allComponents.getComponentsByType(
this.constructor.componentType,
{sorted: true}
);
this.processRestrictions(this.components);
}
},
@ -167,7 +170,8 @@ var ClusterWizardPanesMixin = {
});
component.set({
isCompatible: isCompatible,
warnings: isCompatible ? i18n('dialog.create_cluster_wizard.compatible') : i18n('dialog.create_cluster_wizard.incompatible_list') + warnings.join(', '),
warnings: isCompatible ? i18n('dialog.create_cluster_wizard.compatible') :
i18n('dialog.create_cluster_wizard.incompatible_list') + warnings.join(', '),
availability: (isCompatible ? 'compatible' : 'available')
});
});
@ -181,7 +185,10 @@ var ClusterWizardPanesMixin = {
var warnings = [];
_.each(incompatibles, (incompatible) => {
var type = incompatible.component.get('type');
var isInStopList = _.find(stopList, (component) => component.id == incompatible.component.id);
var isInStopList = _.find(
stopList,
(component) => component.id == incompatible.component.id
);
if (!_.contains(types, type) || isInStopList) {
// ignore forward incompatibilities
return;
@ -261,10 +268,15 @@ var NameAndRelease = React.createClass({
},
isValid() {
var wizard = this.props.wizard;
var [name, cluster, clusters] = [wizard.get('name'), wizard.get('cluster'), wizard.get('clusters')];
var [name, cluster, clusters] = [
wizard.get('name'),
wizard.get('cluster'),
wizard.get('clusters')
];
// test cluster name is already taken
if (clusters.findWhere({name: name})) {
var error = i18n('dialog.create_cluster_wizard.name_release.existing_environment', {name: name});
var error = i18n('dialog.create_cluster_wizard.name_release.existing_environment',
{name: name});
wizard.set({name_error: error});
return false;
}
@ -286,7 +298,9 @@ var NameAndRelease = React.createClass({
return null;
}
var os = release.get('operating_system');
var connectivityAlert = i18n('dialog.create_cluster_wizard.name_release.' + os + '_connectivity_alert');
var connectivityAlert = i18n(
'dialog.create_cluster_wizard.name_release.' + os + '_connectivity_alert'
);
return (
<div className='create-cluster-form name-and-release'>
<Input
@ -345,7 +359,9 @@ var Compute = React.createClass({
return _.contains(this.constructor.vCenterNetworkBackends, component.id);
});
if (!hasCompatibleBackends) {
var vCenter = _.find(allComponents.models, (component) => component.id == this.constructor.vCenterPath);
var vCenter = _.find(allComponents.models, (component) => {
return component.id == this.constructor.vCenterPath;
});
vCenter.set({
disabled: true,
warnings: i18n('dialog.create_cluster_wizard.compute.vcenter_requires_network_backend')
@ -363,7 +379,9 @@ var Compute = React.createClass({
onChange={this.props.onChange}
/>
{this.constructor.hasErrors(this.props.wizard) &&
<div className='alert alert-warning'>{i18n('dialog.create_cluster_wizard.compute.empty_choice')}</div>
<div className='alert alert-warning'>
{i18n('dialog.create_cluster_wizard.compute.empty_choice')}
</div>
}
</div>
);
@ -405,10 +423,17 @@ var Network = React.createClass({
var monolithic = _.filter(this.components, (component) => !component.isML2Driver());
var hasMl2 = _.any(this.components, (component) => component.isML2Driver());
if (!hasMl2) {
monolithic = _.filter(monolithic, (component) => component.id != this.constructor.ml2CorePath);
monolithic = _.filter(monolithic, (component) => {
return component.id != this.constructor.ml2CorePath;
});
}
this.processRestrictions(monolithic, this.constructor.panesForRestrictions);
this.processCompatible(this.props.allComponents, monolithic, this.constructor.panesForRestrictions, monolithic);
this.processCompatible(
this.props.allComponents,
monolithic,
this.constructor.panesForRestrictions,
monolithic
);
this.selectActiveComponent(monolithic);
return (
<ComponentRadioGroup
@ -438,7 +463,9 @@ var Network = React.createClass({
{this.renderML2DriverControls()}
</div>
{this.constructor.hasErrors(this.props.wizard) &&
<div className='alert alert-warning'>{i18n('dialog.create_cluster_wizard.network.ml2_empty_choice')}</div>
<div className='alert alert-warning'>
{i18n('dialog.create_cluster_wizard.network.ml2_empty_choice')}
</div>
}
</div>
);
@ -456,8 +483,17 @@ var Storage = React.createClass({
renderSection(components, type) {
var sectionComponents = _.filter(components, (component) => component.get('subtype') == type);
var isRadio = this.areComponentsMutuallyExclusive(sectionComponents);
this.processRestrictions(sectionComponents, this.constructor.panesForRestrictions, (isRadio ? sectionComponents : []));
this.processCompatible(this.props.allComponents, sectionComponents, this.constructor.panesForRestrictions, isRadio ? sectionComponents : []);
this.processRestrictions(
sectionComponents,
this.constructor.panesForRestrictions,
(isRadio ? sectionComponents : [])
);
this.processCompatible(
this.props.allComponents,
sectionComponents,
this.constructor.panesForRestrictions,
isRadio ? sectionComponents : []
);
return (
React.createElement((isRadio ? ComponentRadioGroup : ComponentCheckboxGroup), {
groupName: type,
@ -468,7 +504,11 @@ var Storage = React.createClass({
},
render() {
this.processRestrictions(this.components, this.constructor.panesForRestrictions);
this.processCompatible(this.props.allComponents, this.components, this.constructor.panesForRestrictions);
this.processCompatible(
this.props.allComponents,
this.components,
this.constructor.panesForRestrictions
);
return (
<div className='wizard-storage-pane'>
<div className='row'>
@ -506,7 +546,11 @@ var AdditionalServices = React.createClass({
},
render() {
this.processRestrictions(this.components, this.constructor.panesForRestrictions);
this.processCompatible(this.props.allComponents, this.components, this.constructor.panesForRestrictions);
this.processCompatible(
this.props.allComponents,
this.components,
this.constructor.panesForRestrictions
);
return (
<div className='wizard-compute-pane'>
<ComponentCheckboxGroup
@ -591,7 +635,8 @@ var CreateClusterWizard = React.createClass({
},
updateState(nextState) {
var numberOfPanes = this.getEnabledPanes().length;
var nextActivePaneIndex = _.isNumber(nextState.activePaneIndex) ? nextState.activePaneIndex : this.state.activePaneIndex;
var nextActivePaneIndex = _.isNumber(nextState.activePaneIndex) ? nextState.activePaneIndex :
this.state.activePaneIndex;
var pane = clusterWizardPanes[nextActivePaneIndex];
var paneHasErrors = _.isFunction(pane.hasErrors) ? pane.hasErrors(this.wizard) : false;
@ -714,7 +759,10 @@ var CreateClusterWizard = React.createClass({
break;
default:
maxAvailablePaneIndex = this.state.activePaneIndex;
var panesToRestore = this.getListOfTypesToRestore(this.state.activePaneIndex, this.state.maxAvailablePaneIndex);
var panesToRestore = this.getListOfTypesToRestore(
this.state.activePaneIndex,
this.state.maxAvailablePaneIndex
);
if (panesToRestore.length > 0) {
this.components.restoreDefaultValues(panesToRestore);
}
@ -790,11 +838,17 @@ var CreateClusterWizard = React.createClass({
var actionInProgress = this.state.actionInProgress;
return (
<div className='wizard-footer'>
<button className={utils.classNames('btn btn-default pull-left', {disabled: actionInProgress})} data-dismiss='modal'>
<button
className={utils.classNames('btn btn-default pull-left', {disabled: actionInProgress})}
data-dismiss='modal'
>
{i18n('common.cancel_button')}
</button>
<button
className={utils.classNames('btn btn-default prev-pane-btn', {disabled: !this.state.previousEnabled || actionInProgress})}
className={utils.classNames(
'btn btn-default prev-pane-btn',
{disabled: !this.state.previousEnabled || actionInProgress}
)}
onClick={this.prevPane}
>
<i className='glyphicon glyphicon-arrow-left' aria-hidden='true'></i>
@ -803,7 +857,10 @@ var CreateClusterWizard = React.createClass({
</button>
{this.state.nextVisible &&
<button
className={utils.classNames('btn btn-default btn-success next-pane-btn', {disabled: !this.state.nextEnabled || actionInProgress})}
className={utils.classNames(
'btn btn-default btn-success next-pane-btn',
{disabled: !this.state.nextEnabled || actionInProgress}
)}
onClick={this.nextPane}
>
<span>{i18n('dialog.create_cluster_wizard.next')}</span>
@ -813,7 +870,10 @@ var CreateClusterWizard = React.createClass({
}
{this.state.createVisible &&
<button
className={utils.classNames('btn btn-default btn-success finish-btn', {disabled: actionInProgress})}
className={utils.classNames(
'btn btn-default btn-success finish-btn',
{disabled: actionInProgress}
)}
onClick={this.saveCluster}
autoFocus
>

View File

@ -22,7 +22,10 @@ module.exports = {
},
{test: /\/expression\/parser\.js$/, loader: 'exports?parser'},
{test: require.resolve('jquery'), loader: 'expose?jQuery!expose?$'},
{test: /\/sinon\.js$/, loader: 'imports?this=>window,define=>false,exports=>false,module=>false,require=>false'},
{
test: /\/sinon\.js$/,
loader: 'imports?this=>window,define=>false,exports=>false,module=>false,require=>false'
},
{test: /\.css$/, loader: 'style!css!postcss'},
{test: /\.less$/, loader: 'style!css!postcss!less'},
{test: /\.html$/, loader: 'raw'},