Forcing eslint max-len rule
Implements: blueprint converge-to-eslint-config-openstack Change-Id: I35e6abb96c4912608222cd85617764172858d191
This commit is contained in:
parent
d62ec107cd
commit
fbdc1ccf1f
|
@ -13,7 +13,6 @@
|
|||
|
||||
# to be fixed and enabled
|
||||
eqeqeq: 0
|
||||
max-len: [0, 120]
|
||||
|
||||
# extra rules
|
||||
no-unexpected-multiline: 2
|
||||
|
|
|
@ -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'));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
16
gulpfile.js
16
gulpfile.js
|
@ -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,7 +264,8 @@ gulp.task('dev-server', function() {
|
|||
]
|
||||
};
|
||||
_.extend(options, config.output);
|
||||
new WebpackDevServer(webpack(config), options).listen(devServerPort, devServerHost, function(err) {
|
||||
new WebpackDevServer(webpack(config), options).listen(devServerPort, devServerHost,
|
||||
function(err) {
|
||||
if (err) throw err;
|
||||
gutil.log('Development server started at ' + devServerUrl);
|
||||
});
|
||||
|
@ -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);
|
||||
|
|
|
@ -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,7 +221,8 @@ class App {
|
|||
}
|
||||
|
||||
loadPage(Page, options = []) {
|
||||
return (Page.fetchData ? Page.fetchData(...options) : $.Deferred().resolve()).done((pageOptions) => {
|
||||
return (Page.fetchData ? Page.fetchData(...options) : $.Deferred().resolve())
|
||||
.done((pageOptions) => {
|
||||
if (!this.rootComponent) this.renderLayout();
|
||||
this.setPage(Page, pageOptions);
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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: {}};
|
||||
|
|
158
static/models.js
158
static/models.js
|
@ -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,12 +623,17 @@ models.Notifications = BaseCollection.extend({
|
|||
}
|
||||
});
|
||||
|
||||
models.Settings = Backbone.DeepModel.extend(superMixin).extend(cacheMixin).extend(restrictionMixin).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'],
|
||||
groupList: ['general', 'security', 'compute', 'network', 'storage',
|
||||
'logging', 'openstack_services', 'other'],
|
||||
isNew() {
|
||||
return false;
|
||||
},
|
||||
|
@ -629,9 +650,11 @@ models.Settings = Backbone.DeepModel.extend(superMixin).extend(cacheMixin).exten
|
|||
(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'));
|
||||
_.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.attributes[sectionName] = _.extend(_.pick(section, 'metadata'),
|
||||
_.omit(chosenVersionData, 'metadata'));
|
||||
}
|
||||
}, this);
|
||||
},
|
||||
|
@ -645,7 +668,8 @@ models.Settings = Backbone.DeepModel.extend(superMixin).extend(cacheMixin).exten
|
|||
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'));
|
||||
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;
|
||||
});
|
||||
|
@ -662,7 +686,8 @@ models.Settings = Backbone.DeepModel.extend(superMixin).extend(cacheMixin).exten
|
|||
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;
|
||||
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);
|
||||
|
@ -675,7 +700,9 @@ models.Settings = Backbone.DeepModel.extend(superMixin).extend(cacheMixin).exten
|
|||
}
|
||||
|
||||
if (!(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;
|
||||
|
@ -699,9 +726,11 @@ models.Settings = Backbone.DeepModel.extend(superMixin).extend(cacheMixin).exten
|
|||
result = metadata.chosen_id != initialAttributes[sectionName].metadata.chosen_id;
|
||||
}
|
||||
}
|
||||
return result || (metadata || {}).enabled !== false && _.any(section, (setting, settingName) => {
|
||||
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);
|
||||
return !_.isEqual(setting.value,
|
||||
(initialAttributes[sectionName][settingName] || {}).value);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
@ -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,12 +1150,18 @@ 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 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});
|
||||
ipRangeError = !_.all(range) ||
|
||||
!!_.find(errors
|
||||
.networks[networkToCheckFloatingRange
|
||||
.get('group_id')][networkToCheckFloatingRange.id].ip_ranges, {index: index});
|
||||
} catch (error) {}
|
||||
return !ipRangeError;
|
||||
}) : [];
|
||||
|
@ -1115,7 +1171,8 @@ models.NetworkConfiguration = BaseModel.extend(cacheMixin).extend({
|
|||
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)) {
|
||||
|
|
|
@ -36,7 +36,11 @@ function testRegex(regexText, value) {
|
|||
return regexCache[regexText].test(value);
|
||||
}
|
||||
|
||||
var BaseModel = Backbone.Model.extend(models.superMixin).extend(models.cacheMixin).extend(models.restrictionMixin).extend({
|
||||
var BaseModel = Backbone.Model
|
||||
.extend(models.superMixin)
|
||||
.extend(models.cacheMixin)
|
||||
.extend(models.restrictionMixin)
|
||||
.extend({
|
||||
constructorName: 'BaseModel',
|
||||
cacheFor: 60 * 1000,
|
||||
toJSON() {
|
||||
|
@ -78,7 +82,10 @@ var BaseModel = Backbone.Model.extend(models.superMixin).extend(models.cacheMixi
|
|||
}
|
||||
});
|
||||
|
||||
var BaseCollection = Backbone.Collection.extend(models.superMixin).extend(models.cacheMixin).extend({
|
||||
var BaseCollection = Backbone.Collection
|
||||
.extend(models.superMixin)
|
||||
.extend(models.cacheMixin)
|
||||
.extend({
|
||||
constructorName: 'BaseCollection',
|
||||
model: BaseModel,
|
||||
cacheFor: 60 * 1000,
|
||||
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.'
|
||||
}
|
||||
]
|
||||
}]
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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');
|
||||
})
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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(', ')
|
||||
];
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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}));
|
||||
|
|
|
@ -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)}>×</div>
|
||||
<div
|
||||
className='close-btn'
|
||||
onClick={_.partial(this.updateDisk, volumeName, 0)}
|
||||
>
|
||||
×
|
||||
</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 className='row collapse disk-details' id={disk.get('name')} key='diskDetails' ref={disk.get('name')}>
|
||||
</div>
|
||||
<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)}> </span>
|
||||
<span
|
||||
ref={'volume-group-flag ' + volumeName}
|
||||
className={'volume-type-' + (index + 1)}
|
||||
>
|
||||
|
||||
</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>
|
||||
|
|
|
@ -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,14 +219,16 @@ 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 _.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;
|
||||
|
@ -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,7 +432,9 @@ 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) => {
|
||||
var invalidSpeedsForBonding = bondingPossible &&
|
||||
this.validateSpeedsForBonding(checkedBonds.concat(checkedInterfaces)) ||
|
||||
interfaces.any((ifc) => {
|
||||
return ifc.isBond() && this.validateSpeedsForBonding([ifc]);
|
||||
});
|
||||
|
||||
|
@ -425,11 +443,14 @@ var EditNodeInterfacesScreen = React.createClass({
|
|||
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'>
|
||||
|
|
|
@ -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'));
|
||||
|
|
|
@ -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
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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}>×</button>
|
||||
<button
|
||||
className='close btn-clear-search'
|
||||
onClick={this.clearSearchField}
|
||||
>
|
||||
×
|
||||
</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) => {
|
||||
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>
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -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>
|
||||
:
|
||||
|
|
|
@ -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> </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}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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'>×</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>
|
||||
|
|
|
@ -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()});
|
||||
|
|
|
@ -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 &&
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 &&
|
||||
|
|
|
@ -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>
|
||||
:
|
||||
|
|
|
@ -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
|
||||
>
|
||||
|
|
|
@ -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'},
|
||||
|
|
Loading…
Reference in New Issue