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
|
# to be fixed and enabled
|
||||||
eqeqeq: 0
|
eqeqeq: 0
|
||||||
max-len: [0, 120]
|
|
||||||
|
|
||||||
# extra rules
|
# extra rules
|
||||||
no-unexpected-multiline: 2
|
no-unexpected-multiline: 2
|
||||||
|
|
|
@ -26,12 +26,15 @@ function validate(translations, locales) {
|
||||||
});
|
});
|
||||||
|
|
||||||
function compareLocales(locale1, locale2) {
|
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) {
|
_.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 present in',
|
||||||
gutil.log(gutil.colors.red('The list of keys missing in', baseLocale, ':\n') + compareLocales(locale, baseLocale).join('\n'));
|
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'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
22
gulpfile.js
22
gulpfile.js
|
@ -80,7 +80,8 @@ gulp.task('selenium', ['selenium:fetch'], function(cb) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
child.on('exit', function() {
|
child.on('exit', function() {
|
||||||
if (seleniumProcess) {
|
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) {
|
['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) {
|
gulp.task('unit-tests', function(cb) {
|
||||||
runSequence('selenium', 'karma', function(err) {
|
runSequence('selenium', 'karma', function(err) {
|
||||||
|
@ -161,7 +164,8 @@ gulp.task('license', function(cb) {
|
||||||
_.each(data, function(moduleInfo) {
|
_.each(data, function(moduleInfo) {
|
||||||
var name = moduleInfo.name;
|
var name = moduleInfo.name;
|
||||||
var version = moduleInfo.version;
|
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);
|
var licenseOk = license.match(licenseRegexp);
|
||||||
if (!licenseOk) errors.push({libraryName: name, license: license});
|
if (!licenseOk) errors.push({libraryName: name, license: license});
|
||||||
gutil.log(
|
gutil.log(
|
||||||
|
@ -260,10 +264,11 @@ gulp.task('dev-server', function() {
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
_.extend(options, config.output);
|
_.extend(options, config.output);
|
||||||
new WebpackDevServer(webpack(config), options).listen(devServerPort, devServerHost, function(err) {
|
new WebpackDevServer(webpack(config), options).listen(devServerPort, devServerHost,
|
||||||
if (err) throw err;
|
function(err) {
|
||||||
gutil.log('Development server started at ' + devServerUrl);
|
if (err) throw err;
|
||||||
});
|
gutil.log('Development server started at ' + devServerUrl);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('build', function(cb) {
|
gulp.task('build', function(cb) {
|
||||||
|
@ -301,7 +306,8 @@ gulp.task('build', function(cb) {
|
||||||
rimraf.sync(config.output.path);
|
rimraf.sync(config.output.path);
|
||||||
|
|
||||||
var compiler = webpack(config);
|
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) {
|
run(function(err, stats) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
|
@ -70,11 +70,14 @@ class Router extends Backbone.Router {
|
||||||
var specialRoutes = [
|
var specialRoutes = [
|
||||||
{name: 'login', condition: () => {
|
{name: 'login', condition: () => {
|
||||||
var result = app.version.get('auth_required') && !app.user.get('authenticated');
|
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;
|
return result;
|
||||||
}},
|
}},
|
||||||
{name: 'welcome', condition: (previousUrl) => {
|
{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) => {
|
_.each(specialRoutes, (route) => {
|
||||||
|
@ -153,7 +156,8 @@ class App {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.initialized = false;
|
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});
|
$.ajaxSetup({cache: false});
|
||||||
|
|
||||||
this.router = new Router();
|
this.router = new Router();
|
||||||
|
@ -217,10 +221,11 @@ class App {
|
||||||
}
|
}
|
||||||
|
|
||||||
loadPage(Page, options = []) {
|
loadPage(Page, options = []) {
|
||||||
return (Page.fetchData ? Page.fetchData(...options) : $.Deferred().resolve()).done((pageOptions) => {
|
return (Page.fetchData ? Page.fetchData(...options) : $.Deferred().resolve())
|
||||||
if (!this.rootComponent) this.renderLayout();
|
.done((pageOptions) => {
|
||||||
this.setPage(Page, pageOptions);
|
if (!this.rootComponent) this.renderLayout();
|
||||||
});
|
this.setPage(Page, pageOptions);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setPage(Page, options) {
|
setPage(Page, options) {
|
||||||
|
|
|
@ -38,7 +38,8 @@ export function dispatcherMixin(events, callback) {
|
||||||
|
|
||||||
export var unsavedChangesMixin = {
|
export var unsavedChangesMixin = {
|
||||||
onBeforeunloadEvent() {
|
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() {
|
componentWillMount() {
|
||||||
this.eventName = _.uniqueId('unsavedchanges');
|
this.eventName = _.uniqueId('unsavedchanges');
|
||||||
|
@ -71,13 +72,15 @@ export function pollingMixin(updateInterval, delayedStart) {
|
||||||
updateInterval = updateInterval * 1000;
|
updateInterval = updateInterval * 1000;
|
||||||
return {
|
return {
|
||||||
scheduleDataFetch() {
|
scheduleDataFetch() {
|
||||||
var shouldDataBeFetched = !_.isFunction(this.shouldDataBeFetched) || this.shouldDataBeFetched();
|
var shouldDataBeFetched = !_.isFunction(this.shouldDataBeFetched) ||
|
||||||
|
this.shouldDataBeFetched();
|
||||||
if (this.isMounted() && !this.activeTimeout && shouldDataBeFetched) {
|
if (this.isMounted() && !this.activeTimeout && shouldDataBeFetched) {
|
||||||
this.activeTimeout = _.delay(this.startPolling, updateInterval);
|
this.activeTimeout = _.delay(this.startPolling, updateInterval);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
startPolling(force) {
|
startPolling(force) {
|
||||||
var shouldDataBeFetched = force || !_.isFunction(this.shouldDataBeFetched) || this.shouldDataBeFetched();
|
var shouldDataBeFetched = force || !_.isFunction(this.shouldDataBeFetched) ||
|
||||||
|
this.shouldDataBeFetched();
|
||||||
if (shouldDataBeFetched) {
|
if (shouldDataBeFetched) {
|
||||||
this.stopPolling();
|
this.stopPolling();
|
||||||
return this.fetchData().always(this.scheduleDataFetch);
|
return this.fetchData().always(this.scheduleDataFetch);
|
||||||
|
|
|
@ -43,7 +43,8 @@ class Expression {
|
||||||
getCompiledExpression() {
|
getCompiledExpression() {
|
||||||
var cacheEntry = expressionCache[this.expressionText];
|
var cacheEntry = expressionCache[this.expressionText];
|
||||||
if (!cacheEntry) {
|
if (!cacheEntry) {
|
||||||
cacheEntry = expressionCache[this.expressionText] = ExpressionParser.parse(this.expressionText);
|
cacheEntry = expressionCache[this.expressionText] =
|
||||||
|
ExpressionParser.parse(this.expressionText);
|
||||||
}
|
}
|
||||||
return cacheEntry;
|
return cacheEntry;
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,7 +90,10 @@ export class ModelPathWrapper {
|
||||||
var result = this.modelPath.get();
|
var result = this.modelPath.get();
|
||||||
if (_.isUndefined(result)) {
|
if (_.isUndefined(result)) {
|
||||||
if (expression.strict) {
|
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;
|
result = null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,11 @@ class KeystoneClient {
|
||||||
authenticate(username, password, options = {}) {
|
authenticate(username, password, options = {}) {
|
||||||
if (this.tokenUpdateRequest) return this.tokenUpdateRequest;
|
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();
|
return $.Deferred().resolve();
|
||||||
}
|
}
|
||||||
var data = {auth: {}};
|
var data = {auth: {}};
|
||||||
|
|
372
static/models.js
372
static/models.js
|
@ -72,11 +72,13 @@ _.each(collectionMethods, (method) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
var BaseModel = models.BaseModel = Backbone.Model.extend(superMixin);
|
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 = {
|
var cacheMixin = {
|
||||||
fetch(options) {
|
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 $.Deferred().resolve();
|
||||||
}
|
}
|
||||||
return this._super('fetch', arguments);
|
return this._super('fetch', arguments);
|
||||||
|
@ -98,7 +100,8 @@ models.cacheMixin = cacheMixin;
|
||||||
|
|
||||||
var restrictionMixin = models.restrictionMixin = {
|
var restrictionMixin = models.restrictionMixin = {
|
||||||
checkRestrictions(models, action, setting) {
|
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) {
|
if (action) {
|
||||||
restrictions = _.where(restrictions, {action: 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
|
* and validate current model checking the possibility of adding/removing node
|
||||||
* So if max = 1 and we have 1 node then:
|
* So if max = 1 and we have 1 node then:
|
||||||
* - the model is valid as is (return true) -- case for checkLimitIsReached = true
|
* - 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'
|
* 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;
|
comparator = (a, b) => a < b;
|
||||||
}
|
}
|
||||||
limitValue = parseInt(evaluateExpressionHelper(obj[limitType], models).value, 10);
|
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;
|
limitValues[limitType] = limitValue;
|
||||||
checkedLimitTypes[limitType] = true;
|
checkedLimitTypes[limitType] = true;
|
||||||
if (comparator(count, limitValue)) {
|
if (comparator(count, limitValue)) {
|
||||||
return {
|
return {
|
||||||
type: limitType,
|
type: limitType,
|
||||||
value: limitValue,
|
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) => {
|
_.each(role.conflicts, (conflictRoleName) => {
|
||||||
var conflictingRole = this.findWhere({name: 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);
|
return this.get('tasks') && this.get('tasks').filterTasks(filters);
|
||||||
},
|
},
|
||||||
needsRedeployment() {
|
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) {
|
fetchRelated(related, options) {
|
||||||
return this.get(related).fetch(_.extend({data: {cluster_id: this.id}}, options));
|
return this.get(related).fetch(_.extend({data: {cluster_id: this.id}}, options));
|
||||||
|
@ -354,7 +363,8 @@ models.Cluster = BaseModel.extend({
|
||||||
isDeploymentPossible() {
|
isDeploymentPossible() {
|
||||||
var nodes = this.get('nodes');
|
var nodes = this.get('nodes');
|
||||||
return this.get('release').get('state') != 'unavailable' && !!nodes.length &&
|
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') {
|
} else if (resourceName == 'ht_cores') {
|
||||||
resource = this.get('meta').cpu.total;
|
resource = this.get('meta').cpu.total;
|
||||||
} else if (resourceName == 'hdd') {
|
} 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') {
|
} else if (resourceName == 'ram') {
|
||||||
resource = this.get('meta').memory.total;
|
resource = this.get('meta').memory.total;
|
||||||
} else if (resourceName == 'disks') {
|
} else if (resourceName == 'disks') {
|
||||||
|
@ -412,7 +424,8 @@ models.Node = BaseModel.extend({
|
||||||
return this.get('status') != 'removing';
|
return this.get('status') != 'removing';
|
||||||
},
|
},
|
||||||
hasRole(role, onlyDeployedRoles) {
|
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);
|
return _.contains(roles, role);
|
||||||
},
|
},
|
||||||
hasChanges() {
|
hasChanges() {
|
||||||
|
@ -505,7 +518,8 @@ models.Nodes = BaseCollection.extend({
|
||||||
var roles = _.union(this.at(0).get('roles'), this.at(0).get('pending_roles'));
|
var roles = _.union(this.at(0).get('roles'), this.at(0).get('pending_roles'));
|
||||||
var disks = this.at(0).resource('disks');
|
var disks = this.at(0).resource('disks');
|
||||||
return !this.any((node) => {
|
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'));
|
return roleConflict || !_.isEqual(disks, node.resource('disks'));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -556,10 +570,12 @@ models.Task = BaseModel.extend({
|
||||||
match(filters) {
|
match(filters) {
|
||||||
filters = filters || {};
|
filters = filters || {};
|
||||||
if (!_.isEmpty(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;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -607,121 +623,134 @@ models.Notifications = BaseCollection.extend({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
models.Settings = Backbone.DeepModel.extend(superMixin).extend(cacheMixin).extend(restrictionMixin).extend({
|
models.Settings = Backbone.DeepModel
|
||||||
constructorName: 'Settings',
|
.extend(superMixin)
|
||||||
urlRoot: '/api/clusters/',
|
.extend(cacheMixin)
|
||||||
root: 'editable',
|
.extend(restrictionMixin)
|
||||||
cacheFor: 60 * 1000,
|
.extend({
|
||||||
groupList: ['general', 'security', 'compute', 'network', 'storage', 'logging', 'openstack_services', 'other'],
|
constructorName: 'Settings',
|
||||||
isNew() {
|
urlRoot: '/api/clusters/',
|
||||||
return false;
|
root: 'editable',
|
||||||
},
|
cacheFor: 60 * 1000,
|
||||||
isPlugin(section) {
|
groupList: ['general', 'security', 'compute', 'network', 'storage',
|
||||||
return (section.metadata || {}).class == 'plugin';
|
'logging', 'openstack_services', 'other'],
|
||||||
},
|
isNew() {
|
||||||
parse(response) {
|
return false;
|
||||||
return response[this.root];
|
},
|
||||||
},
|
isPlugin(section) {
|
||||||
mergePluginSettings() {
|
return (section.metadata || {}).class == 'plugin';
|
||||||
_.each(this.attributes, (section, sectionName) => {
|
},
|
||||||
if (this.isPlugin(section)) {
|
parse(response) {
|
||||||
var chosenVersionData = section.metadata.versions.find(
|
return response[this.root];
|
||||||
(version) => version.metadata.plugin_id == section.metadata.chosen_id
|
},
|
||||||
);
|
mergePluginSettings() {
|
||||||
// merge metadata of a chosen plugin version
|
_.each(this.attributes, (section, sectionName) => {
|
||||||
_.extend(section.metadata, _.omit(chosenVersionData.metadata, 'plugin_id', 'plugin_version'));
|
if (this.isPlugin(section)) {
|
||||||
// merge settings of a chosen plugin version
|
var chosenVersionData = section.metadata.versions.find(
|
||||||
this.attributes[sectionName] = _.extend(_.pick(section, 'metadata'), _.omit(chosenVersionData, 'metadata'));
|
(version) => version.metadata.plugin_id == section.metadata.chosen_id
|
||||||
}
|
);
|
||||||
}, this);
|
// merge metadata of a chosen plugin version
|
||||||
},
|
_.extend(section.metadata,
|
||||||
toJSON() {
|
_.omit(chosenVersionData.metadata, 'plugin_id', 'plugin_version'));
|
||||||
var settings = this._super('toJSON', arguments);
|
// merge settings of a chosen plugin version
|
||||||
if (!this.root) return settings;
|
this.attributes[sectionName] = _.extend(_.pick(section, 'metadata'),
|
||||||
|
_.omit(chosenVersionData, 'metadata'));
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
},
|
||||||
|
toJSON() {
|
||||||
|
var settings = this._super('toJSON', arguments);
|
||||||
|
if (!this.root) return settings;
|
||||||
|
|
||||||
// update plugin settings
|
// update plugin settings
|
||||||
_.each(settings, (section, sectionName) => {
|
_.each(settings, (section, sectionName) => {
|
||||||
if (this.isPlugin(section)) {
|
if (this.isPlugin(section)) {
|
||||||
var chosenVersionData = section.metadata.versions.find(
|
var chosenVersionData = section.metadata.versions.find(
|
||||||
(version) => version.metadata.plugin_id == section.metadata.chosen_id
|
(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,
|
||||||
_.each(section, (setting, settingName) => {
|
_.without(_.keys(chosenVersionData.metadata), 'plugin_id', 'plugin_version'));
|
||||||
if (settingName != 'metadata') chosenVersionData[settingName].value = setting.value;
|
_.each(section, (setting, settingName) => {
|
||||||
});
|
if (settingName != 'metadata') chosenVersionData[settingName].value = setting.value;
|
||||||
settings[sectionName] = _.pick(section, 'metadata');
|
});
|
||||||
}
|
settings[sectionName] = _.pick(section, 'metadata');
|
||||||
});
|
|
||||||
return {[this.root]: settings};
|
|
||||||
},
|
|
||||||
initialize() {
|
|
||||||
this.once('change', this.mergePluginSettings, this);
|
|
||||||
},
|
|
||||||
validate(attrs, options) {
|
|
||||||
var errors = {};
|
|
||||||
var models = options ? options.models : {};
|
|
||||||
var checkRestrictions = (setting) => this.checkRestrictions(models, null, setting);
|
|
||||||
_.each(attrs, (group, groupName) => {
|
|
||||||
if ((group.metadata || {}).enabled === false || checkRestrictions(group.metadata).result) return;
|
|
||||||
_.each(group, (setting, settingName) => {
|
|
||||||
if (checkRestrictions(setting).result) return;
|
|
||||||
var path = this.makePath(groupName, settingName);
|
|
||||||
// support of custom controls
|
|
||||||
var CustomControl = customControls[setting.type];
|
|
||||||
if (CustomControl) {
|
|
||||||
var error = CustomControl.validate(setting, models);
|
|
||||||
if (error) errors[path] = error;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
return {[this.root]: settings};
|
||||||
|
},
|
||||||
|
initialize() {
|
||||||
|
this.once('change', this.mergePluginSettings, this);
|
||||||
|
},
|
||||||
|
validate(attrs, options) {
|
||||||
|
var errors = {};
|
||||||
|
var models = options ? options.models : {};
|
||||||
|
var checkRestrictions = (setting) => this.checkRestrictions(models, null, setting);
|
||||||
|
_.each(attrs, (group, groupName) => {
|
||||||
|
if ((group.metadata || {}).enabled === false ||
|
||||||
|
checkRestrictions(group.metadata).result) return;
|
||||||
|
_.each(group, (setting, settingName) => {
|
||||||
|
if (checkRestrictions(setting).result) return;
|
||||||
|
var path = this.makePath(groupName, settingName);
|
||||||
|
// support of custom controls
|
||||||
|
var CustomControl = customControls[setting.type];
|
||||||
|
if (CustomControl) {
|
||||||
|
var error = CustomControl.validate(setting, models);
|
||||||
|
if (error) errors[path] = error;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!(setting.regex || {}).source) return;
|
if (!(setting.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;
|
|
||||||
},
|
|
||||||
makePath(...args) {
|
|
||||||
return args.join('.');
|
|
||||||
},
|
|
||||||
getValueAttribute(settingName) {
|
|
||||||
return settingName == 'metadata' ? 'enabled' : 'value';
|
|
||||||
},
|
|
||||||
hasChanges(initialAttributes, models) {
|
|
||||||
return _.any(this.attributes, (section, sectionName) => {
|
|
||||||
var metadata = section.metadata;
|
|
||||||
var result = false;
|
|
||||||
if (metadata) {
|
|
||||||
if (this.checkRestrictions(models, null, metadata).result) return result;
|
|
||||||
if (!_.isUndefined(metadata.enabled)) {
|
|
||||||
result = metadata.enabled != initialAttributes[sectionName].metadata.enabled;
|
|
||||||
}
|
|
||||||
if (!result && this.isPlugin(section)) {
|
|
||||||
result = metadata.chosen_id != initialAttributes[sectionName].metadata.chosen_id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result || (metadata || {}).enabled !== false && _.any(section, (setting, settingName) => {
|
|
||||||
if (this.checkRestrictions(models, null, setting).result) return false;
|
|
||||||
return !_.isEqual(setting.value, (initialAttributes[sectionName][settingName] || {}).value);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
sanitizeGroup(group) {
|
|
||||||
return _.contains(this.groupList, group) ? group : 'other';
|
|
||||||
},
|
|
||||||
getGroupList() {
|
|
||||||
var groups = [];
|
|
||||||
_.each(this.attributes, (section) => {
|
|
||||||
if (section.metadata.group) {
|
|
||||||
groups.push(this.sanitizeGroup(section.metadata.group));
|
|
||||||
} else {
|
|
||||||
_.each(section, (setting, settingName) => {
|
|
||||||
if (settingName != 'metadata') groups.push(this.sanitizeGroup(setting.group));
|
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
});
|
return _.isEmpty(errors) ? null : errors;
|
||||||
return _.intersection(this.groupList, groups);
|
},
|
||||||
}
|
makePath(...args) {
|
||||||
});
|
return args.join('.');
|
||||||
|
},
|
||||||
|
getValueAttribute(settingName) {
|
||||||
|
return settingName == 'metadata' ? 'enabled' : 'value';
|
||||||
|
},
|
||||||
|
hasChanges(initialAttributes, models) {
|
||||||
|
return _.any(this.attributes, (section, sectionName) => {
|
||||||
|
var metadata = section.metadata;
|
||||||
|
var result = false;
|
||||||
|
if (metadata) {
|
||||||
|
if (this.checkRestrictions(models, null, metadata).result) return result;
|
||||||
|
if (!_.isUndefined(metadata.enabled)) {
|
||||||
|
result = metadata.enabled != initialAttributes[sectionName].metadata.enabled;
|
||||||
|
}
|
||||||
|
if (!result && this.isPlugin(section)) {
|
||||||
|
result = metadata.chosen_id != initialAttributes[sectionName].metadata.chosen_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result || (metadata || {}).enabled !== false &&
|
||||||
|
_.any(section, (setting, settingName) => {
|
||||||
|
if (this.checkRestrictions(models, null, setting).result) return false;
|
||||||
|
return !_.isEqual(setting.value,
|
||||||
|
(initialAttributes[sectionName][settingName] || {}).value);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
sanitizeGroup(group) {
|
||||||
|
return _.contains(this.groupList, group) ? group : 'other';
|
||||||
|
},
|
||||||
|
getGroupList() {
|
||||||
|
var groups = [];
|
||||||
|
_.each(this.attributes, (section) => {
|
||||||
|
if (section.metadata.group) {
|
||||||
|
groups.push(this.sanitizeGroup(section.metadata.group));
|
||||||
|
} else {
|
||||||
|
_.each(section, (setting, settingName) => {
|
||||||
|
if (settingName != 'metadata') groups.push(this.sanitizeGroup(setting.group));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return _.intersection(this.groupList, groups);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
models.FuelSettings = models.Settings.extend({
|
models.FuelSettings = models.Settings.extend({
|
||||||
constructorName: 'FuelSettings',
|
constructorName: 'FuelSettings',
|
||||||
|
@ -741,7 +770,8 @@ models.Disk = BaseModel.extend({
|
||||||
return response;
|
return response;
|
||||||
},
|
},
|
||||||
toJSON(options) {
|
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) {
|
getUnallocatedSpace(options) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
|
@ -755,7 +785,8 @@ models.Disk = BaseModel.extend({
|
||||||
var error;
|
var error;
|
||||||
var unallocatedSpace = this.getUnallocatedSpace({volumes: attrs.volumes});
|
var unallocatedSpace = this.getUnallocatedSpace({volumes: attrs.volumes});
|
||||||
if (unallocatedSpace < 0) {
|
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;
|
return error;
|
||||||
}
|
}
|
||||||
|
@ -776,7 +807,8 @@ models.Volume = BaseModel.extend({
|
||||||
var groupAllocatedSpace = 0;
|
var groupAllocatedSpace = 0;
|
||||||
if (currentDisk && currentDisk.collection)
|
if (currentDisk && currentDisk.collection)
|
||||||
groupAllocatedSpace = currentDisk.collection.reduce((sum, disk) => {
|
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);
|
}, 0);
|
||||||
return minimum - groupAllocatedSpace;
|
return minimum - groupAllocatedSpace;
|
||||||
},
|
},
|
||||||
|
@ -790,7 +822,8 @@ models.Volume = BaseModel.extend({
|
||||||
validate(attrs, options) {
|
validate(attrs, options) {
|
||||||
var min = this.getMinimalSize(options.minimum);
|
var min = this.getMinimalSize(options.minimum);
|
||||||
if (attrs.size < min) {
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -826,11 +859,15 @@ models.Interface = BaseModel.extend({
|
||||||
},
|
},
|
||||||
validate(attrs) {
|
validate(attrs) {
|
||||||
var errors = [];
|
var errors = [];
|
||||||
var networks = new models.Networks(this.get('assigned_networks').invoke('getFullNetwork', attrs.networks));
|
var networks = new models.Networks(this.get('assigned_networks')
|
||||||
var untaggedNetworks = networks.filter((network) => _.isNull(network.getVlanRange(attrs.networkingParameters)));
|
.invoke('getFullNetwork', attrs.networks));
|
||||||
|
var untaggedNetworks = networks.filter((network) => {
|
||||||
|
return _.isNull(network.getVlanRange(attrs.networkingParameters));
|
||||||
|
});
|
||||||
var ns = 'cluster_page.nodes_tab.configure_interfaces.validation.';
|
var ns = 'cluster_page.nodes_tab.configure_interfaces.validation.';
|
||||||
// public and floating networks are allowed to be assigned to the same interface
|
// 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) {
|
if (untaggedNetworks.length > maxUntaggedNetworksCount) {
|
||||||
errors.push(i18n(ns + 'too_many_untagged_networks'));
|
errors.push(i18n(ns + 'too_many_untagged_networks'));
|
||||||
}
|
}
|
||||||
|
@ -853,7 +890,8 @@ models.Interface = BaseModel.extend({
|
||||||
if (
|
if (
|
||||||
_.any(vlanRanges,
|
_.any(vlanRanges,
|
||||||
(currentRange) => _.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'));
|
) 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({
|
models.InterfaceNetwork = BaseModel.extend({
|
||||||
constructorName: 'InterfaceNetwork',
|
constructorName: 'InterfaceNetwork',
|
||||||
|
@ -899,8 +938,11 @@ models.Network = BaseModel.extend({
|
||||||
getVlanRange(networkingParameters) {
|
getVlanRange(networkingParameters) {
|
||||||
if (!this.get('meta').neutron_vlan_range) {
|
if (!this.get('meta').neutron_vlan_range) {
|
||||||
var externalNetworkData = this.get('meta').ext_net_data;
|
var externalNetworkData = this.get('meta').ext_net_data;
|
||||||
var vlanStart = externalNetworkData ? networkingParameters.get(externalNetworkData[0]) : this.get('vlan_start');
|
var vlanStart = externalNetworkData ?
|
||||||
return _.isNull(vlanStart) ? vlanStart : [vlanStart, externalNetworkData ? vlanStart + networkingParameters.get(externalNetworkData[1]) - 1 : vlanStart];
|
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');
|
return networkingParameters.get('vlan_range');
|
||||||
}
|
}
|
||||||
|
@ -923,7 +965,9 @@ models.NetworkConfiguration = BaseModel.extend(cacheMixin).extend({
|
||||||
cacheFor: 60 * 1000,
|
cacheFor: 60 * 1000,
|
||||||
parse(response) {
|
parse(response) {
|
||||||
response.networks = new models.Networks(response.networks);
|
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;
|
return response;
|
||||||
},
|
},
|
||||||
toJSON() {
|
toJSON() {
|
||||||
|
@ -982,7 +1026,8 @@ models.NetworkConfiguration = BaseModel.extend(cacheMixin).extend({
|
||||||
var forbiddenVlans = novaNetManager ? networksToCheck.map((net) => {
|
var forbiddenVlans = novaNetManager ? networksToCheck.map((net) => {
|
||||||
return net.id != network.id ? net.get('vlan_start') : null;
|
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)) {
|
if (!_.isEmpty(networkErrors)) {
|
||||||
nodeNetworkGroupErrors[network.id] = networkErrors;
|
nodeNetworkGroupErrors[network.id] = networkErrors;
|
||||||
|
@ -995,7 +1040,9 @@ models.NetworkConfiguration = BaseModel.extend(cacheMixin).extend({
|
||||||
} else if (!cidrError && !utils.validateIpCorrespondsToCIDR(cidr, baremetalGateway)) {
|
} else if (!cidrError && !utils.validateIpCorrespondsToCIDR(cidr, baremetalGateway)) {
|
||||||
networkingParametersErrors.baremetal_gateway = i18n(ns + 'gateway_does_not_match_cidr');
|
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) {
|
if (baremetalRangeErrors.length) {
|
||||||
var [{start, end}] = baremetalRangeErrors;
|
var [{start, end}] = baremetalRangeErrors;
|
||||||
networkingParametersErrors.baremetal_range = [start, end];
|
networkingParametersErrors.baremetal_range = [start, end];
|
||||||
|
@ -1014,13 +1061,15 @@ models.NetworkConfiguration = BaseModel.extend(cacheMixin).extend({
|
||||||
|
|
||||||
// validate networking parameters
|
// validate networking parameters
|
||||||
if (novaNetManager) {
|
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 fixedAmount = networkParameters.get('fixed_networks_amount');
|
||||||
var fixedVlan = networkParameters.get('fixed_networks_vlan_start');
|
var fixedVlan = networkParameters.get('fixed_networks_vlan_start');
|
||||||
if (!utils.isNaturalNumber(parseInt(fixedAmount, 10))) {
|
if (!utils.isNaturalNumber(parseInt(fixedAmount, 10))) {
|
||||||
networkingParametersErrors.fixed_networks_amount = i18n(ns + 'invalid_amount');
|
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);
|
_.extend(networkingParametersErrors, vlanErrors);
|
||||||
if (_.isEmpty(vlanErrors)) {
|
if (_.isEmpty(vlanErrors)) {
|
||||||
if (!networkingParametersErrors.fixed_networks_amount && fixedAmount > 4095 - fixedVlan) {
|
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');
|
networkingParametersErrors.base_mac = i18n(ns + 'invalid_mac');
|
||||||
}
|
}
|
||||||
var cidr = networkParameters.get('internal_cidr');
|
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');
|
var gateway = networkParameters.get('internal_gateway');
|
||||||
if (!utils.validateIP(gateway)) {
|
if (!utils.validateIP(gateway)) {
|
||||||
networkingParametersErrors.internal_gateway = i18n(ns + 'invalid_gateway');
|
networkingParametersErrors.internal_gateway = i18n(ns + 'invalid_gateway');
|
||||||
|
@ -1100,22 +1150,29 @@ models.NetworkConfiguration = BaseModel.extend(cacheMixin).extend({
|
||||||
var networkToCheckFloatingRangeData = networkToCheckFloatingRange ? {
|
var networkToCheckFloatingRangeData = networkToCheckFloatingRange ? {
|
||||||
cidr: networkToCheckFloatingRange.get('cidr'),
|
cidr: networkToCheckFloatingRange.get('cidr'),
|
||||||
network: _.capitalize(networkToCheckFloatingRange.get('name')),
|
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 ?
|
||||||
var ipRangeError = false;
|
_.filter(networkToCheckFloatingRange.get('ip_ranges'), (range, index) => {
|
||||||
try {
|
var ipRangeError = false;
|
||||||
ipRangeError = !_.all(range) || !!_.find(errors.networks[networkToCheckFloatingRange.get('group_id')][networkToCheckFloatingRange.id].ip_ranges, {index: index});
|
try {
|
||||||
} catch (error) {}
|
ipRangeError = !_.all(range) ||
|
||||||
return !ipRangeError;
|
!!_.find(errors
|
||||||
}) : [];
|
.networks[networkToCheckFloatingRange
|
||||||
|
.get('group_id')][networkToCheckFloatingRange.id].ip_ranges, {index: index});
|
||||||
|
} catch (error) {}
|
||||||
|
return !ipRangeError;
|
||||||
|
}) : [];
|
||||||
|
|
||||||
floatingRangesErrors = utils.validateIPRanges(
|
floatingRangesErrors = utils.validateIPRanges(
|
||||||
floatingRanges,
|
floatingRanges,
|
||||||
networkToCheckFloatingRangeData.cidr,
|
networkToCheckFloatingRangeData.cidr,
|
||||||
networkToCheckFloatingRangeIPRanges,
|
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')
|
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 = [];
|
var errors = [];
|
||||||
_.each(options.config, (attributeConfig, attribute) => {
|
_.each(options.config, (attributeConfig, attribute) => {
|
||||||
if (!(attributeConfig.regex && attributeConfig.regex.source)) return;
|
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
|
// this probably will be changed when other controls need validation
|
||||||
return !utils.evaluateExpression(restriction.condition, {default: this}).value;
|
return !utils.evaluateExpression(restriction.condition, {default: this}).value;
|
||||||
});
|
});
|
||||||
|
@ -1291,14 +1349,17 @@ models.WizardModel = Backbone.DeepModel.extend({
|
||||||
|
|
||||||
models.MirantisCredentials = Backbone.DeepModel.extend(superMixin).extend({
|
models.MirantisCredentials = Backbone.DeepModel.extend(superMixin).extend({
|
||||||
constructorName: 'MirantisCredentials',
|
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) {
|
validate(attrs) {
|
||||||
var errors = {};
|
var errors = {};
|
||||||
_.each(attrs, (group, groupName) => {
|
_.each(attrs, (group, groupName) => {
|
||||||
_.each(group, (setting, settingName) => {
|
_.each(group, (setting, settingName) => {
|
||||||
var path = this.makePath(groupName, settingName);
|
var path = this.makePath(groupName, settingName);
|
||||||
if (!setting.regex || !setting.regex.source) return;
|
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;
|
return _.isEmpty(errors) ? null : errors;
|
||||||
|
@ -1414,7 +1475,8 @@ models.ComponentModel = BaseModel.extend({
|
||||||
var expandProperty = (propertyName, components) => {
|
var expandProperty = (propertyName, components) => {
|
||||||
var expandedComponents = [];
|
var expandedComponents = [];
|
||||||
_.each(this.get(propertyName), (patternDescription) => {
|
_.each(this.get(propertyName), (patternDescription) => {
|
||||||
var patternName = _.isString(patternDescription) ? patternDescription : patternDescription.name;
|
var patternName = _.isString(patternDescription) ? patternDescription :
|
||||||
|
patternDescription.name;
|
||||||
var pattern = new ComponentPattern(patternName);
|
var pattern = new ComponentPattern(patternName);
|
||||||
components.each((component) => {
|
components.each((component) => {
|
||||||
if (pattern.match(component.id)) {
|
if (pattern.match(component.id)) {
|
||||||
|
|
|
@ -36,67 +36,74 @@ function testRegex(regexText, value) {
|
||||||
return regexCache[regexText].test(value);
|
return regexCache[regexText].test(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
var BaseModel = Backbone.Model.extend(models.superMixin).extend(models.cacheMixin).extend(models.restrictionMixin).extend({
|
var BaseModel = Backbone.Model
|
||||||
constructorName: 'BaseModel',
|
.extend(models.superMixin)
|
||||||
cacheFor: 60 * 1000,
|
.extend(models.cacheMixin)
|
||||||
toJSON() {
|
.extend(models.restrictionMixin)
|
||||||
return _.omit(this.attributes, 'metadata');
|
.extend({
|
||||||
},
|
constructorName: 'BaseModel',
|
||||||
validate() {
|
cacheFor: 60 * 1000,
|
||||||
var result = {};
|
toJSON() {
|
||||||
_.each(this.attributes.metadata, (field) => {
|
return _.omit(this.attributes, 'metadata');
|
||||||
if (!VmWareModels.isRegularField(field) || field.type == 'checkbox') {
|
},
|
||||||
return;
|
validate() {
|
||||||
}
|
var result = {};
|
||||||
var isDisabled = this.checkRestrictions(restrictionModels, undefined, field);
|
_.each(this.attributes.metadata, (field) => {
|
||||||
if (isDisabled.result) {
|
if (!VmWareModels.isRegularField(field) || field.type == 'checkbox') {
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
var value = this.get(field.name);
|
|
||||||
if (field.regex) {
|
|
||||||
if (!testRegex(field.regex.source, value)) {
|
|
||||||
result[field.name] = field.regex.error;
|
|
||||||
}
|
}
|
||||||
}
|
var isDisabled = this.checkRestrictions(restrictionModels, undefined, field);
|
||||||
});
|
if (isDisabled.result) {
|
||||||
return _.isEmpty(result) ? null : result;
|
return;
|
||||||
},
|
}
|
||||||
testRestrictions() {
|
var value = this.get(field.name);
|
||||||
var results = {
|
if (field.regex) {
|
||||||
hide: {},
|
if (!testRegex(field.regex.source, value)) {
|
||||||
disable: {}
|
result[field.name] = field.regex.error;
|
||||||
};
|
}
|
||||||
var metadata = this.get('metadata');
|
}
|
||||||
_.each(metadata, (field) => {
|
});
|
||||||
var disableResult = this.checkRestrictions(restrictionModels, undefined, field);
|
return _.isEmpty(result) ? null : result;
|
||||||
results.disable[field.name] = disableResult;
|
},
|
||||||
|
testRestrictions() {
|
||||||
|
var results = {
|
||||||
|
hide: {},
|
||||||
|
disable: {}
|
||||||
|
};
|
||||||
|
var metadata = this.get('metadata');
|
||||||
|
_.each(metadata, (field) => {
|
||||||
|
var disableResult = this.checkRestrictions(restrictionModels, undefined, field);
|
||||||
|
results.disable[field.name] = disableResult;
|
||||||
|
|
||||||
var hideResult = this.checkRestrictions(restrictionModels, 'hide', field);
|
var hideResult = this.checkRestrictions(restrictionModels, 'hide', field);
|
||||||
results.hide[field.name] = hideResult;
|
results.hide[field.name] = hideResult;
|
||||||
});
|
});
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var BaseCollection = Backbone.Collection.extend(models.superMixin).extend(models.cacheMixin).extend({
|
var BaseCollection = Backbone.Collection
|
||||||
constructorName: 'BaseCollection',
|
.extend(models.superMixin)
|
||||||
model: BaseModel,
|
.extend(models.cacheMixin)
|
||||||
cacheFor: 60 * 1000,
|
.extend({
|
||||||
isValid() {
|
constructorName: 'BaseCollection',
|
||||||
this.validationError = this.validate();
|
model: BaseModel,
|
||||||
return this.validationError;
|
cacheFor: 60 * 1000,
|
||||||
},
|
isValid() {
|
||||||
validate() {
|
this.validationError = this.validate();
|
||||||
var errors = _.compact(this.models.map((model) => {
|
return this.validationError;
|
||||||
model.isValid();
|
},
|
||||||
return model.validationError;
|
validate() {
|
||||||
}));
|
var errors = _.compact(this.models.map((model) => {
|
||||||
return _.isEmpty(errors) ? null : errors;
|
model.isValid();
|
||||||
},
|
return model.validationError;
|
||||||
testRestrictions() {
|
}));
|
||||||
_.invoke(this.models, 'testRestrictions', restrictionModels);
|
return _.isEmpty(errors) ? null : errors;
|
||||||
}
|
},
|
||||||
});
|
testRestrictions() {
|
||||||
|
_.invoke(this.models, 'testRestrictions', restrictionModels);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
VmWareModels.NovaCompute = BaseModel.extend({
|
VmWareModels.NovaCompute = BaseModel.extend({
|
||||||
constructorName: 'NovaCompute',
|
constructorName: 'NovaCompute',
|
||||||
|
@ -209,7 +216,8 @@ VmWareModels.Glance = BaseModel.extend({constructorName: 'Glance'});
|
||||||
VmWareModels.VCenter = BaseModel.extend({
|
VmWareModels.VCenter = BaseModel.extend({
|
||||||
constructorName: 'VCenter',
|
constructorName: 'VCenter',
|
||||||
url() {
|
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) {
|
parse(response) {
|
||||||
if (!response.editable || !response.editable.metadata || !response.editable.value) {
|
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) => {
|
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) {
|
if (unassignedNodes.length > 0) {
|
||||||
errors.unassigned_nodes = unassignedNodes;
|
errors.unassigned_nodes = unassignedNodes;
|
||||||
|
|
|
@ -236,7 +236,13 @@ var AvailabilityZones = React.createClass({
|
||||||
}
|
}
|
||||||
</h3>
|
</h3>
|
||||||
{this.props.collection.map((model) => {
|
{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>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -310,7 +316,8 @@ var VmWareTab = React.createClass({
|
||||||
this.model.setModels({
|
this.model.setModels({
|
||||||
cluster: this.props.cluster,
|
cluster: this.props.cluster,
|
||||||
settings: this.props.cluster.get('settings'),
|
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
|
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='col-xs-12 page-buttons content-elements'>
|
||||||
<div className='well clearfix'>
|
<div className='well clearfix'>
|
||||||
<div className='btn-group pull-right'>
|
<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')}
|
{i18n('vmware.reset_to_defaults')}
|
||||||
</button>
|
</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')}
|
{i18n('vmware.cancel')}
|
||||||
</button>
|
</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')}
|
{i18n('vmware.apply')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -125,7 +125,8 @@ define([
|
||||||
var dragAndDrop = (function() {
|
var dragAndDrop = (function() {
|
||||||
var dispatchEvent, createEvent;
|
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) {
|
if (document.createEvent) {
|
||||||
dispatchEvent = function(element, eventName, event) {
|
dispatchEvent = function(element, eventName, event) {
|
||||||
element.dispatchEvent(event);
|
element.dispatchEvent(event);
|
||||||
|
@ -215,8 +216,8 @@ define([
|
||||||
|
|
||||||
try {
|
try {
|
||||||
event = createEvent('MouseEvent');
|
event = createEvent('MouseEvent');
|
||||||
event.initMouseEvent(eventName, true, true, window, 0, screenX, screenY, clientX, clientY,
|
event.initMouseEvent(eventName, true, true, window, 0, screenX, screenY, clientX,
|
||||||
false, false, false, false, 0, null);
|
clientY, false, false, false, false, 0, null);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
event = createCustomEvent(eventName, screenX, screenY, clientX, clientY);
|
event = createCustomEvent(eventName, screenX, screenY, clientX, clientY);
|
||||||
}
|
}
|
||||||
|
@ -230,7 +231,8 @@ define([
|
||||||
|
|
||||||
function simulateEvent(element, eventName, dragStartEvent, options) {
|
function simulateEvent(element, eventName, dragStartEvent, options) {
|
||||||
var dataTransfer = dragStartEvent ? dragStartEvent.dataTransfer : null;
|
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);
|
var event = createEvent(eventName, options, dataTransfer);
|
||||||
return dispatchEvent(element, eventName, event);
|
return dispatchEvent(element, eventName, event);
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,7 +86,9 @@ function(_, assert, Helpers, pollUntil, LoginPage, WelcomePage, ClusterPage, Clu
|
||||||
return self.clusterPage.removeCluster(clusterName);
|
return self.clusterPage.removeCluster(clusterName);
|
||||||
})
|
})
|
||||||
.catch(function(e) {
|
.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) {
|
doesClusterExist: function(clusterName) {
|
||||||
|
@ -115,7 +117,8 @@ function(_, assert, Helpers, pollUntil, LoginPage, WelcomePage, ClusterPage, Clu
|
||||||
.clickByCssSelector('button.btn-add-nodes')
|
.clickByCssSelector('button.btn-add-nodes')
|
||||||
.waitForCssSelector('.node', 3000)
|
.waitForCssSelector('.node', 3000)
|
||||||
.then(pollUntil(function() {
|
.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))
|
}, 3000))
|
||||||
.then(function() {
|
.then(function() {
|
||||||
if (nodeNameFilter) return self.clusterPage.searchForNode(nodeNameFilter);
|
if (nodeNameFilter) return self.clusterPage.searchForNode(nodeNameFilter);
|
||||||
|
|
|
@ -51,7 +51,8 @@ define([
|
||||||
duration: '20s',
|
duration: '20s',
|
||||||
message: null,
|
message: null,
|
||||||
id: 'fuel_health.tests.check_disk',
|
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,
|
status: null,
|
||||||
|
@ -73,7 +74,8 @@ define([
|
||||||
duration: '30s.',
|
duration: '30s.',
|
||||||
message: null,
|
message: null,
|
||||||
id: 'fuel_health.tests.general',
|
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,
|
status: null,
|
||||||
|
@ -84,7 +86,8 @@ define([
|
||||||
duration: '1sec',
|
duration: '1sec',
|
||||||
message: null,
|
message: null,
|
||||||
id: 'fuel_health.tests.credentials',
|
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,
|
status: null,
|
||||||
|
@ -95,7 +98,8 @@ define([
|
||||||
duration: '',
|
duration: '',
|
||||||
message: null,
|
message: null,
|
||||||
id: 'fuel_health.tests.credentials_change',
|
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,
|
status: null,
|
||||||
|
@ -117,7 +121,8 @@ define([
|
||||||
duration: '',
|
duration: '',
|
||||||
message: null,
|
message: null,
|
||||||
id: 'fuel_health.tests.credentials_erros',
|
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',
|
duration: '20s',
|
||||||
message: null,
|
message: null,
|
||||||
id: 'fuel_health.tests.check_disk',
|
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',
|
status: 'stopped',
|
||||||
|
@ -176,7 +182,8 @@ define([
|
||||||
duration: '30s.',
|
duration: '30s.',
|
||||||
message: 'Fast fail with message',
|
message: 'Fast fail with message',
|
||||||
id: 'fuel_health.tests.general',
|
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',
|
status: 'wait_running',
|
||||||
|
@ -187,7 +194,8 @@ define([
|
||||||
duration: '1sec',
|
duration: '1sec',
|
||||||
message: 'failure text message',
|
message: 'failure text message',
|
||||||
id: 'fuel_health.tests.credentials',
|
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',
|
status: 'running',
|
||||||
|
@ -198,7 +206,8 @@ define([
|
||||||
duration: '',
|
duration: '',
|
||||||
message: 'Error message',
|
message: 'Error message',
|
||||||
id: 'fuel_health.tests.credentials_change',
|
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',
|
status: 'wait_running',
|
||||||
|
@ -220,7 +229,8 @@ define([
|
||||||
duration: '',
|
duration: '',
|
||||||
message: null,
|
message: null,
|
||||||
id: 'fuel_health.tests.credentials_erros',
|
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',
|
duration: '20s',
|
||||||
message: null,
|
message: null,
|
||||||
id: 'fuel_health.tests.check_disk',
|
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',
|
status: 'stopped',
|
||||||
|
@ -266,7 +277,8 @@ define([
|
||||||
duration: '30s.',
|
duration: '30s.',
|
||||||
message: 'Fast fail with message',
|
message: 'Fast fail with message',
|
||||||
id: 'fuel_health.tests.general',
|
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',
|
status: 'wait_running',
|
||||||
|
@ -277,7 +289,8 @@ define([
|
||||||
duration: '1sec',
|
duration: '1sec',
|
||||||
message: 'failure text message',
|
message: 'failure text message',
|
||||||
id: 'fuel_health.tests.credentials',
|
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',
|
status: 'running',
|
||||||
|
@ -288,7 +301,9 @@ define([
|
||||||
duration: '',
|
duration: '',
|
||||||
message: 'Error message',
|
message: 'Error message',
|
||||||
id: 'fuel_health.tests.credentials_change',
|
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',
|
status: 'wait_running',
|
||||||
|
@ -310,7 +325,8 @@ define([
|
||||||
duration: '',
|
duration: '',
|
||||||
message: null,
|
message: null,
|
||||||
id: 'fuel_health.tests.credentials_erros',
|
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',
|
duration: '20s',
|
||||||
message: null,
|
message: null,
|
||||||
id: 'fuel_health.tests.check_disk',
|
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',
|
status: 'stopped',
|
||||||
|
@ -367,7 +384,9 @@ define([
|
||||||
duration: '30s.',
|
duration: '30s.',
|
||||||
message: 'Fast fail with message',
|
message: 'Fast fail with message',
|
||||||
id: 'fuel_health.tests.general',
|
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',
|
status: 'skipped',
|
||||||
|
@ -378,7 +397,8 @@ define([
|
||||||
duration: '1sec',
|
duration: '1sec',
|
||||||
message: 'failure text message',
|
message: 'failure text message',
|
||||||
id: 'fuel_health.tests.credentials',
|
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',
|
status: 'success',
|
||||||
|
@ -389,7 +409,9 @@ define([
|
||||||
duration: '',
|
duration: '',
|
||||||
message: 'Error message',
|
message: 'Error message',
|
||||||
id: 'fuel_health.tests.credentials_change',
|
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',
|
status: 'failure',
|
||||||
|
@ -411,7 +433,8 @@ define([
|
||||||
duration: '',
|
duration: '',
|
||||||
message: null,
|
message: null,
|
||||||
id: 'fuel_health.tests.credentials_erros',
|
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',
|
duration: '20s',
|
||||||
message: null,
|
message: null,
|
||||||
id: 'fuel_health.tests.check_disk',
|
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',
|
status: 'stopped',
|
||||||
|
@ -457,7 +481,9 @@ define([
|
||||||
duration: '30s.',
|
duration: '30s.',
|
||||||
message: 'Fast fail with message',
|
message: 'Fast fail with message',
|
||||||
id: 'fuel_health.tests.general',
|
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',
|
status: 'skipped',
|
||||||
|
@ -468,7 +494,9 @@ define([
|
||||||
duration: '1sec',
|
duration: '1sec',
|
||||||
message: 'failure text message',
|
message: 'failure text message',
|
||||||
id: 'fuel_health.tests.credentials',
|
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',
|
status: 'success',
|
||||||
|
@ -479,7 +507,9 @@ define([
|
||||||
duration: '',
|
duration: '',
|
||||||
message: 'Error message',
|
message: 'Error message',
|
||||||
id: 'fuel_health.tests.credentials_change',
|
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',
|
status: 'failure',
|
||||||
|
@ -501,7 +531,8 @@ define([
|
||||||
duration: '',
|
duration: '',
|
||||||
message: null,
|
message: null,
|
||||||
id: 'fuel_health.tests.credentials_erros',
|
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
|
return bondElement
|
||||||
.findAllByCssSelector('.ifc-name')
|
.findAllByCssSelector('.ifc-name')
|
||||||
.then(function(ifcNamesElements) {
|
.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(
|
return ifcNamesElements.forEach(
|
||||||
function(ifcNameElement) {
|
function(ifcNameElement) {
|
||||||
|
|
|
@ -35,7 +35,8 @@ define([
|
||||||
},
|
},
|
||||||
checkTitle: function(expectedTitle) {
|
checkTitle: function(expectedTitle) {
|
||||||
return this.remote
|
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() {
|
close: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
|
@ -86,8 +86,10 @@ define([
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return dashboardPage.setClusterName(initialName);
|
return dashboardPage.setClusterName(initialName);
|
||||||
})
|
})
|
||||||
.assertElementAppears('.rename-block.has-error', 1000, 'Error style for duplicate name is applied')
|
.assertElementAppears('.rename-block.has-error', 1000,
|
||||||
.assertElementTextEquals('.rename-block .text-danger', 'Environment with this name already exists',
|
'Error style for duplicate name is applied')
|
||||||
|
.assertElementTextEquals('.rename-block .text-danger',
|
||||||
|
'Environment with this name already exists',
|
||||||
'Duplicate name error text appears'
|
'Duplicate name error text appears'
|
||||||
)
|
)
|
||||||
.findByCssSelector(renameInputSelector)
|
.findByCssSelector(renameInputSelector)
|
||||||
|
@ -129,7 +131,8 @@ define([
|
||||||
return clusterPage.goToTab('Dashboard');
|
return clusterPage.goToTab('Dashboard');
|
||||||
})
|
})
|
||||||
.assertElementContainsText('.warnings-block',
|
.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() {
|
.then(function() {
|
||||||
return dashboardPage.discardChanges();
|
return dashboardPage.discardChanges();
|
||||||
});
|
});
|
||||||
|
@ -143,7 +146,8 @@ define([
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return clusterPage.goToTab('Dashboard');
|
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')
|
.assertElementExists('div.instruction.invalid', 'Invalid configuration message is shown')
|
||||||
.assertElementContainsText('.environment-alerts ul.text-danger li',
|
.assertElementContainsText('.environment-alerts ul.text-danger li',
|
||||||
'At least 1 Controller nodes are required (0 selected currently).',
|
'At least 1 Controller nodes are required (0 selected currently).',
|
||||||
|
@ -178,7 +182,8 @@ define([
|
||||||
var operatingSystemNodes = 1;
|
var operatingSystemNodes = 1;
|
||||||
var virtualNodes = 1;
|
var virtualNodes = 1;
|
||||||
var valueSelector = '.statistics-block .cluster-info-value';
|
var valueSelector = '.statistics-block .cluster-info-value';
|
||||||
var total = controllerNodes + storageCinderNodes + computeNodes + operatingSystemNodes + virtualNodes;
|
var total = controllerNodes + storageCinderNodes + computeNodes + operatingSystemNodes +
|
||||||
|
virtualNodes;
|
||||||
return this.remote
|
return this.remote
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return common.addNodesToCluster(controllerNodes, ['Controller']);
|
return common.addNodesToCluster(controllerNodes, ['Controller']);
|
||||||
|
@ -201,11 +206,13 @@ define([
|
||||||
.assertElementTextEquals(valueSelector + '.total', total,
|
.assertElementTextEquals(valueSelector + '.total', total,
|
||||||
'The number of Total nodes in statistics is updated according to added nodes')
|
'The number of Total nodes in statistics is updated according to added nodes')
|
||||||
.assertElementTextEquals(valueSelector + '.controller', controllerNodes,
|
.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,
|
.assertElementTextEquals(valueSelector + '.compute', computeNodes,
|
||||||
'The number of Compute nodes in statistics is updated according to added nodes')
|
'The number of Compute nodes in statistics is updated according to added nodes')
|
||||||
.assertElementTextEquals(valueSelector + '.base-os', operatingSystemNodes,
|
.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,
|
.assertElementTextEquals(valueSelector + '.virt', virtualNodes,
|
||||||
'The number of Virtual nodes in statistics is updated according to added nodes')
|
'The number of Virtual nodes in statistics is updated according to added nodes')
|
||||||
.assertElementTextEquals(valueSelector + '.offline', 1,
|
.assertElementTextEquals(valueSelector + '.offline', 1,
|
||||||
|
@ -213,7 +220,8 @@ define([
|
||||||
.assertElementTextEquals(valueSelector + '.error', 1,
|
.assertElementTextEquals(valueSelector + '.error', 1,
|
||||||
'The number of Error nodes in statistics is updated according to added nodes')
|
'The number of Error nodes in statistics is updated according to added nodes')
|
||||||
.assertElementTextEquals(valueSelector + '.pending_addition', total,
|
.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() {
|
.then(function() {
|
||||||
return dashboardPage.discardChanges();
|
return dashboardPage.discardChanges();
|
||||||
});
|
});
|
||||||
|
@ -232,8 +240,10 @@ define([
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return dashboardPage.startDeployment();
|
return dashboardPage.startDeployment();
|
||||||
})
|
})
|
||||||
.assertElementDisappears('.dashboard-block .progress', 60000, 'Progress bar disappears after deployment')
|
.assertElementDisappears('.dashboard-block .progress', 60000,
|
||||||
.assertElementAppears('.dashboard-tab .alert strong', 1000, 'Error message is shown when adding error node')
|
'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',
|
.assertElementTextEquals('.dashboard-tab .alert strong', 'Error',
|
||||||
'Deployment failed in case of adding offline nodes')
|
'Deployment failed in case of adding offline nodes')
|
||||||
.then(function() {
|
.then(function() {
|
||||||
|
|
|
@ -58,7 +58,8 @@ define([
|
||||||
},
|
},
|
||||||
'No deployment button when there are no nodes added': function() {
|
'No deployment button when there are no nodes added': function() {
|
||||||
return this.remote
|
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() {
|
'Discard changes': function() {
|
||||||
return this.remote
|
return this.remote
|
||||||
|
@ -73,14 +74,16 @@ define([
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return modal.waitToOpen();
|
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() {
|
.then(function() {
|
||||||
return modal.clickFooterButton('Discard');
|
return modal.clickFooterButton('Discard');
|
||||||
})
|
})
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return modal.waitToClose();
|
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() {
|
'Start/stop deployment': function() {
|
||||||
this.timeout = 100000;
|
this.timeout = 100000;
|
||||||
|
@ -96,14 +99,18 @@ define([
|
||||||
return dashboardPage.startDeployment();
|
return dashboardPage.startDeployment();
|
||||||
})
|
})
|
||||||
.assertElementAppears('div.deploy-process div.progress', 2000, 'Deployment started')
|
.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() {
|
.then(function() {
|
||||||
return dashboardPage.stopDeployment();
|
return dashboardPage.stopDeployment();
|
||||||
})
|
})
|
||||||
.assertElementDisappears('div.deploy-process div.progress', 20000, 'Deployment stopped')
|
.assertElementDisappears('div.deploy-process div.progress', 20000, 'Deployment stopped')
|
||||||
.assertElementAppears(dashboardPage.deployButtonSelector, 1000, 'Deployment button available')
|
.assertElementAppears(dashboardPage.deployButtonSelector, 1000,
|
||||||
.assertElementContainsText('div.alert-warning strong', 'Success', 'Deployment successfully stopped alert is expected')
|
'Deployment button available')
|
||||||
.assertElementNotExists('.go-to-healthcheck', 'Healthcheck link is not visible after stopped deploy')
|
.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
|
// Reset environment button is available
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return clusterPage.resetEnvironment(clusterName);
|
return clusterPage.resetEnvironment(clusterName);
|
||||||
|
@ -134,7 +141,8 @@ define([
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return dashboardPage.startDeployment();
|
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')
|
.assertElementAppears('.links-block', 5000, 'Deployment completed')
|
||||||
.assertElementExists('.go-to-healthcheck', 'Healthcheck link is visible after deploy')
|
.assertElementExists('.go-to-healthcheck', 'Healthcheck link is visible after deploy')
|
||||||
.findByLinkText('Horizon')
|
.findByLinkText('Horizon')
|
||||||
|
@ -149,7 +157,8 @@ define([
|
||||||
.then(function(isLocked) {
|
.then(function(isLocked) {
|
||||||
assert.isTrue(isLocked, 'Networks tab should turn locked after deployment');
|
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() {
|
.then(function() {
|
||||||
return clusterPage.isTabLocked('Settings');
|
return clusterPage.isTabLocked('Settings');
|
||||||
})
|
})
|
||||||
|
|
|
@ -64,11 +64,16 @@ define([
|
||||||
return clusterPage.goToTab('Health Check');
|
return clusterPage.goToTab('Health Check');
|
||||||
})
|
})
|
||||||
.assertElementsAppear('.healthcheck-table', 5000, 'Healthcheck tables are rendered')
|
.assertElementsAppear('.healthcheck-table', 5000, 'Healthcheck tables are rendered')
|
||||||
.assertElementDisabled('.custom-tumbler input[type=checkbox]', 'Test checkbox is disabled')
|
.assertElementDisabled('.custom-tumbler input[type=checkbox]',
|
||||||
.assertElementContainsText('.alert-warning', 'Before you can test an OpenStack environment, you must deploy the OpenStack environment',
|
'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')
|
'Warning to deploy cluster is shown')
|
||||||
.assertElementNotExists('.run-tests-btn', 'Run tests button is not shown in new OpenStack environment')
|
.assertElementNotExists('.run-tests-btn',
|
||||||
.assertElementNotExists('.stop-tests-btn', 'Stop tests button is not shown in new OpenStack environment');
|
'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
|
//@TODO (morale): imitate tests stop
|
||||||
'Check Healthcheck tab manipulations after deploy': function() {
|
'Check Healthcheck tab manipulations after deploy': function() {
|
||||||
|
@ -86,9 +91,11 @@ define([
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return clusterPage.goToTab('Health Check');
|
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
|
// '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',
|
.assertElementNotExists('.stop-tests-btn',
|
||||||
'Stop tests button is not visible if no tests checked')
|
'Stop tests button is not visible if no tests checked')
|
||||||
.assertElementExists('.toggle-credentials', 'Toggle credentials button is visible')
|
.assertElementExists('.toggle-credentials', 'Toggle credentials button is visible')
|
||||||
|
@ -120,7 +127,8 @@ define([
|
||||||
.assertElementNotExists('.run-tests-btn',
|
.assertElementNotExists('.run-tests-btn',
|
||||||
'Run tests button is not shown if tests are running')
|
'Run tests button is not shown if tests are running')
|
||||||
.assertElementEnabled('.stop-tests-btn', 'Stop tests button is enabled during tests run')
|
.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('.glyphicon-time', 1000, 'Waiting to run status is reflected')
|
||||||
.assertElementsAppear('.healthcheck-status-skipped', 1000, 'Skipped status is reflected')
|
.assertElementsAppear('.healthcheck-status-skipped', 1000, 'Skipped status is reflected')
|
||||||
.assertElementsAppear('.healthcheck-status-stopped', 1000, 'Stopped status is reflected');
|
.assertElementsAppear('.healthcheck-status-stopped', 1000, 'Stopped status is reflected');
|
||||||
|
@ -140,9 +148,11 @@ define([
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return clusterPage.goToTab('Health Check');
|
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-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() {
|
'"Show" button availability and logs displaying': function() {
|
||||||
var showLogsButtonSelector = '.sticker button';
|
var showLogsButtonSelector = '.sticker button';
|
||||||
return this.remote
|
return this.remote
|
||||||
.assertElementsExist('.sticker select[name=source] > option', 'Check if "Source" dropdown exist')
|
.assertElementsExist('.sticker select[name=source] > option',
|
||||||
.assertElementDisabled(showLogsButtonSelector, '"Show" button is disabled until source change')
|
'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
|
// Change the selected value for the "Source" dropdown to Rest API
|
||||||
.clickByCssSelector('.sticker select[name=source] option[value=api]')
|
.clickByCssSelector('.sticker select[name=source] option[value=api]')
|
||||||
// Change the selected value for the "Level" dropdown to DEBUG
|
// Change the selected value for the "Level" dropdown to DEBUG
|
||||||
.clickByCssSelector('.sticker select[name=level] option[value=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() {
|
.execute(function() {
|
||||||
window.fakeServer = sinon.fakeServer.create();
|
window.fakeServer = sinon.fakeServer.create();
|
||||||
window.fakeServer.autoRespond = true;
|
window.fakeServer.autoRespond = true;
|
||||||
|
@ -70,7 +73,8 @@ define([
|
||||||
]);
|
]);
|
||||||
})
|
})
|
||||||
.clickByCssSelector(showLogsButtonSelector)
|
.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')
|
.assertElementsAppear('.log-entries > tbody > tr', 5000, 'Log entries are shown')
|
||||||
.execute(function() {
|
.execute(function() {
|
||||||
window.fakeServer.restore();
|
window.fakeServer.restore();
|
||||||
|
|
|
@ -100,10 +100,13 @@ define([
|
||||||
return this.remote
|
return this.remote
|
||||||
.clickByCssSelector('.subtab-link-default')
|
.clickByCssSelector('.subtab-link-default')
|
||||||
.assertElementAppears('.storage', 2000, 'Storage network is shown')
|
.assertElementAppears('.storage', 2000, 'Storage network is shown')
|
||||||
.assertElementSelected('.storage .cidr input[type=checkbox]', 'Storage network has "cidr" notation by default')
|
.assertElementSelected('.storage .cidr input[type=checkbox]',
|
||||||
.assertElementNotExists('.storage .ip_ranges input[type=text]:not(:disabled)', 'It is impossible to configure IP ranges for network with "cidr" notation')
|
'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]')
|
.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() {
|
'Testing cluster networks: save network changes': function() {
|
||||||
var cidrElementSelector = '.storage .cidr input[type=text]';
|
var cidrElementSelector = '.storage .cidr input[type=text]';
|
||||||
|
@ -112,12 +115,14 @@ define([
|
||||||
.clickByCssSelector(applyButtonSelector)
|
.clickByCssSelector(applyButtonSelector)
|
||||||
.assertElementsAppear('input:not(:disabled)', 2000, 'Inputs are not disabled')
|
.assertElementsAppear('input:not(:disabled)', 2000, 'Inputs are not disabled')
|
||||||
.assertElementNotExists('.alert-error', 'Correct settings were saved successfully')
|
.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() {
|
'Testing cluster networks: verification': function() {
|
||||||
return this.remote
|
return this.remote
|
||||||
.clickByCssSelector('.subtab-link-network_verification')
|
.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',
|
.assertElementTextEquals('.alert-warning',
|
||||||
'At least two online nodes are required to verify environment network configuration',
|
'At least two online nodes are required to verify environment network configuration',
|
||||||
'Not enough nodes warning is shown')
|
'Not enough nodes warning is shown')
|
||||||
|
@ -133,17 +138,20 @@ define([
|
||||||
.clickByCssSelector('.subtab-link-network_verification')
|
.clickByCssSelector('.subtab-link-network_verification')
|
||||||
.clickByCssSelector('.verify-networks-btn')
|
.clickByCssSelector('.verify-networks-btn')
|
||||||
.assertElementAppears('.alert-danger.network-alert', 4000, 'Verification error is shown')
|
.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
|
// Testing cluster networks: verification task deletion
|
||||||
.clickByCssSelector('.subtab-link-default')
|
.clickByCssSelector('.subtab-link-default')
|
||||||
.setInputValue('.public input[name=gateway]', '172.16.0.5')
|
.setInputValue('.public input[name=gateway]', '172.16.0.5')
|
||||||
.clickByCssSelector('.subtab-link-network_verification')
|
.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('.btn-revert-changes')
|
||||||
.clickByCssSelector('.verify-networks-btn')
|
.clickByCssSelector('.verify-networks-btn')
|
||||||
.waitForElementDeletion('.animation-box .success.connect-1', 6000)
|
.waitForElementDeletion('.animation-box .success.connect-1', 6000)
|
||||||
.assertElementAppears('.alert-success', 6000, 'Success verification message appears')
|
.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')
|
.clickByCssSelector('.btn-revert-changes')
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return clusterPage.goToTab('Dashboard');
|
return clusterPage.goToTab('Dashboard');
|
||||||
|
@ -185,7 +193,8 @@ define([
|
||||||
var rangeSelector = '.public .ip_ranges ';
|
var rangeSelector = '.public .ip_ranges ';
|
||||||
return this.remote
|
return this.remote
|
||||||
.clickByCssSelector(rangeSelector + '.ip-ranges-add')
|
.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)
|
.clickByCssSelector(applyButtonSelector)
|
||||||
.assertElementsExist(rangeSelector + '.range-row',
|
.assertElementsExist(rangeSelector + '.range-row',
|
||||||
'Empty range row is removed after saving changes')
|
'Empty range row is removed after saving changes')
|
||||||
|
@ -216,7 +225,8 @@ define([
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return clusterPage.goToTab('Networks');
|
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)',
|
.assertElementTextEquals('.segmentation-type', '(Neutron with VLAN segmentation)',
|
||||||
'Segmentation type is correct for VLAN segmentation');
|
'Segmentation type is correct for VLAN segmentation');
|
||||||
},
|
},
|
||||||
|
@ -277,7 +287,8 @@ define([
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return modal.waitToOpen();
|
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')
|
.setInputValue('[name=node-network-group-name]', 'Node_Network_Group_1')
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return modal.clickFooterButton('Add Group');
|
return modal.clickFooterButton('Add Group');
|
||||||
|
@ -285,9 +296,11 @@ define([
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return modal.waitToClose();
|
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')
|
.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() {
|
'Verification is disabled for multirack': function() {
|
||||||
return this.remote
|
return this.remote
|
||||||
|
@ -306,26 +319,30 @@ define([
|
||||||
// Enter
|
// Enter
|
||||||
.type('\uE007')
|
.type('\uE007')
|
||||||
.end()
|
.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() {
|
'Node network group deletion': function() {
|
||||||
return this.remote
|
return this.remote
|
||||||
.clickByCssSelector('.subtab-link-default')
|
.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')
|
.clickByCssSelector('.subtab-link-Node_Network_Group_2')
|
||||||
.assertElementAppears('.glyphicon-remove', 1000, 'Remove icon is shown')
|
.assertElementAppears('.glyphicon-remove', 1000, 'Remove icon is shown')
|
||||||
.clickByCssSelector('.glyphicon-remove')
|
.clickByCssSelector('.glyphicon-remove')
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return modal.waitToOpen();
|
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() {
|
.then(function() {
|
||||||
return modal.clickFooterButton('Delete');
|
return modal.clickFooterButton('Delete');
|
||||||
})
|
})
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return modal.waitToClose();
|
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() {
|
'Node network group renaming in deployed environment': function() {
|
||||||
this.timeout = 100000;
|
this.timeout = 100000;
|
||||||
|
@ -344,9 +361,11 @@ define([
|
||||||
return clusterPage.goToTab('Networks');
|
return clusterPage.goToTab('Networks');
|
||||||
})
|
})
|
||||||
.clickByCssSelector('.subtab-link-default')
|
.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')
|
.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() {
|
'Add Cluster Nodes': function() {
|
||||||
return this.remote
|
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')
|
.clickByCssSelector('.btn-add-nodes')
|
||||||
.assertElementsAppear('.node', 2000, 'Unallocated nodes loaded')
|
.assertElementsAppear('.node', 2000, 'Unallocated nodes loaded')
|
||||||
.assertElementDisabled(applyButtonSelector, 'Apply button is disabled until both roles and nodes chosen')
|
.assertElementDisabled(applyButtonSelector,
|
||||||
.assertElementDisabled('.role-panel [type=checkbox][name=mongo]', 'Unavailable role has locked checkbox')
|
'Apply button is disabled until both roles and nodes chosen')
|
||||||
.assertElementExists('.role-panel .mongo i.tooltip-icon', 'Unavailable role has warning tooltip')
|
.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() {
|
.then(function() {
|
||||||
return clusterPage.checkNodeRoles(['Controller', 'Storage - Cinder']);
|
return clusterPage.checkNodeRoles(['Controller', 'Storage - Cinder']);
|
||||||
})
|
})
|
||||||
.assertElementDisabled('.role-panel [type=checkbox][name=compute]', 'Compute role can not be added together with selected roles')
|
.assertElementDisabled('.role-panel [type=checkbox][name=compute]',
|
||||||
.assertElementDisabled(applyButtonSelector, 'Apply button is disabled until both roles and nodes chosen')
|
'Compute role can not be added together with selected roles')
|
||||||
|
.assertElementDisabled(applyButtonSelector,
|
||||||
|
'Apply button is disabled until both roles and nodes chosen')
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return clusterPage.checkNodes(nodesAmount);
|
return clusterPage.checkNodes(nodesAmount);
|
||||||
})
|
})
|
||||||
.clickByCssSelector(applyButtonSelector)
|
.clickByCssSelector(applyButtonSelector)
|
||||||
.waitForElementDeletion(applyButtonSelector, 2000)
|
.waitForElementDeletion(applyButtonSelector, 2000)
|
||||||
.assertElementAppears('.nodes-group', 2000, 'Cluster node list loaded')
|
.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');
|
.assertElementExists('.nodes-group', 'One node group is present');
|
||||||
},
|
},
|
||||||
'Edit cluster node roles': function() {
|
'Edit cluster node roles': function() {
|
||||||
|
@ -78,17 +85,22 @@ define([
|
||||||
// select all nodes
|
// select all nodes
|
||||||
.clickByCssSelector('.select-all label')
|
.clickByCssSelector('.select-all label')
|
||||||
.clickByCssSelector('.btn-edit-roles')
|
.clickByCssSelector('.btn-edit-roles')
|
||||||
.assertElementDisappears('.btn-edit-roles', 2000, 'Cluster nodes screen unmounted')
|
.assertElementDisappears('.btn-edit-roles', 2000,
|
||||||
.assertElementNotExists('.node-box [type=checkbox]:not(:disabled)', 'Node selection is locked on Edit Roles screen')
|
'Cluster nodes screen unmounted')
|
||||||
.assertElementNotExists('[name=select-all]:not(:disabled)', 'Select All checkboxes are locked on Edit Roles screen')
|
.assertElementNotExists('.node-box [type=checkbox]:not(:disabled)',
|
||||||
.assertElementExists('.role-panel [type=checkbox][name=controller]:indeterminate', 'Controller role checkbox has indeterminate state')
|
'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() {
|
.then(function() {
|
||||||
// uncheck Cinder role
|
// uncheck Cinder role
|
||||||
return clusterPage.checkNodeRoles(['Storage - Cinder', 'Storage - Cinder']);
|
return clusterPage.checkNodeRoles(['Storage - Cinder', 'Storage - Cinder']);
|
||||||
})
|
})
|
||||||
.clickByCssSelector(applyButtonSelector)
|
.clickByCssSelector(applyButtonSelector)
|
||||||
.assertElementDisappears('.btn-apply', 2000, 'Role editing screen unmounted')
|
.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() {
|
'Remove Cluster': function() {
|
||||||
return this.remote
|
return this.remote
|
||||||
|
|
|
@ -56,7 +56,8 @@ define([
|
||||||
},
|
},
|
||||||
'Settings tab is rendered correctly': function() {
|
'Settings tab is rendered correctly': function() {
|
||||||
return this.remote
|
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')
|
.assertElementEnabled('.btn-load-defaults', 'Load defaults button is enabled')
|
||||||
.assertElementDisabled('.btn-revert-changes', 'Cancel Changes button is disabled')
|
.assertElementDisabled('.btn-revert-changes', 'Cancel Changes button is disabled')
|
||||||
.assertElementDisabled('.btn-apply-changes', 'Save Settings button is disabled');
|
.assertElementDisabled('.btn-apply-changes', 'Save Settings button is disabled');
|
||||||
|
@ -65,10 +66,12 @@ define([
|
||||||
return this.remote
|
return this.remote
|
||||||
// introduce change
|
// introduce change
|
||||||
.clickByCssSelector('input[type=checkbox]')
|
.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
|
// reset the change
|
||||||
.clickByCssSelector('input[type=checkbox]')
|
.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() {
|
'Check Cancel Changes button': function() {
|
||||||
return this.remote
|
return this.remote
|
||||||
|
@ -86,7 +89,8 @@ define([
|
||||||
})
|
})
|
||||||
// reset changes
|
// reset changes
|
||||||
.clickByCssSelector('.btn-revert-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() {
|
'Check changes saving': function() {
|
||||||
return this.remote
|
return this.remote
|
||||||
|
@ -97,7 +101,8 @@ define([
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return settingsPage.waitForRequestCompleted();
|
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() {
|
'Check loading of defaults': function() {
|
||||||
return this.remote
|
return this.remote
|
||||||
|
@ -106,12 +111,15 @@ define([
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return settingsPage.waitForRequestCompleted();
|
return settingsPage.waitForRequestCompleted();
|
||||||
})
|
})
|
||||||
.assertElementEnabled('.btn-apply-changes', 'Save Settings button is enabled after defaults were loaded')
|
.assertElementEnabled('.btn-apply-changes',
|
||||||
.assertElementEnabled('.btn-revert-changes', 'Cancel Changes button is enabled after defaults were loaded')
|
'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
|
// revert the change
|
||||||
.clickByCssSelector('.btn-revert-changes');
|
.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
|
return this.remote
|
||||||
.clickLinkByText('Logging')
|
.clickLinkByText('Logging')
|
||||||
.then(function() {
|
.then(function() {
|
||||||
|
@ -120,20 +128,26 @@ define([
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return clusterPage.goToTab('Settings');
|
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() {
|
'The page reacts on invalid input': function() {
|
||||||
return this.remote
|
return this.remote
|
||||||
.clickLinkByText('General')
|
.clickLinkByText('General')
|
||||||
// "nova" is forbidden username
|
// "nova" is forbidden username
|
||||||
.setInputValue('[type=text][name=user]', 'nova')
|
.setInputValue('[type=text][name=user]', 'nova')
|
||||||
.assertElementAppears('.setting-section .form-group.has-error', 200, 'Invalid field marked as error')
|
.assertElementAppears('.setting-section .form-group.has-error', 200,
|
||||||
.assertElementExists('.settings-tab .nav-pills > li.active i.glyphicon-danger-sign', 'Subgroup with invalid field marked as invalid')
|
'Invalid field marked as error')
|
||||||
.assertElementDisabled('.btn-apply-changes', 'Save Settings button is disabled in case of validation 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
|
// revert the change
|
||||||
.clickByCssSelector('.btn-revert-changes')
|
.clickByCssSelector('.btn-revert-changes')
|
||||||
.assertElementNotExists('.setting-section .form-group.has-error', 'Validation error is cleared after resetting changes')
|
.assertElementNotExists('.setting-section .form-group.has-error',
|
||||||
.assertElementNotExists('.settings-tab .nav-pills > li.active i.glyphicon-danger-sign', 'Subgroup menu has default layout after resetting changes');
|
'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() {
|
'Test repositories custom control': function() {
|
||||||
var repoAmount;
|
var repoAmount;
|
||||||
|
@ -146,18 +160,22 @@ define([
|
||||||
repoAmount = elements.length;
|
repoAmount = elements.length;
|
||||||
})
|
})
|
||||||
.end()
|
.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
|
// delete some repo
|
||||||
.clickByCssSelector('.repos .form-inline .btn-link')
|
.clickByCssSelector('.repos .form-inline .btn-link')
|
||||||
.then(function() {
|
.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
|
// add new repo
|
||||||
.clickByCssSelector('.btn-add-repo')
|
.clickByCssSelector('.btn-add-repo')
|
||||||
.then(function() {
|
.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
|
// revert the change
|
||||||
.clickByCssSelector('.btn-revert-changes');
|
.clickByCssSelector('.btn-revert-changes');
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,18 +69,25 @@ define([
|
||||||
'Check action buttons': function() {
|
'Check action buttons': function() {
|
||||||
return this.remote
|
return this.remote
|
||||||
.assertElementNotExists('.node .btn-discard', 'No discard changes button on a node')
|
.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')
|
.clickByCssSelector('.node.pending_addition > label')
|
||||||
.assertElementNotExists('.control-buttons-box .btn', 'No management buttons for selected node')
|
.assertElementNotExists('.control-buttons-box .btn',
|
||||||
.assertElementExists('.node-list-management-buttons .btn-labels:not(:disabled)', 'Nodes can be labelled on the page')
|
'No management buttons for selected node')
|
||||||
.assertElementsExist('.node.pending_addition .btn-view-logs', 4, 'View logs button is presented for assigned to any environment nodes')
|
.assertElementExists('.node-list-management-buttons .btn-labels:not(:disabled)',
|
||||||
.assertElementNotExists('.node:not(.pending_addition) .btn-view-logs', 'View logs button is not presented for unallocated nodes')
|
'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')
|
.clickByCssSelector('.node .node-settings')
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return modal.waitToOpen();
|
return modal.waitToOpen();
|
||||||
})
|
})
|
||||||
.assertElementNotExists('.btn-edit-disks', 'No disks configuration buttons in node pop-up')
|
.assertElementNotExists('.btn-edit-disks',
|
||||||
.assertElementNotExists('.btn-edit-networks', 'No interfaces configuration buttons in node pop-up')
|
'No disks configuration buttons in node pop-up')
|
||||||
|
.assertElementNotExists('.btn-edit-networks',
|
||||||
|
'No interfaces configuration buttons in node pop-up')
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return modal.close();
|
return modal.close();
|
||||||
})
|
})
|
||||||
|
@ -88,7 +95,8 @@ define([
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return node.openCompactNodeExtendedView();
|
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() {
|
.then(function() {
|
||||||
return loginPage.login('login', '*****');
|
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() {
|
'Login with proper credentials': function() {
|
||||||
return this.remote
|
return this.remote
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return loginPage.login();
|
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()
|
.end()
|
||||||
.assertElementExists(sdaDisk + ' .disk-visual [data-volume=image] .close-btn', 'Button Close for Image Storage volume is present')
|
.assertElementExists(sdaDisk + ' .disk-visual [data-volume=image] .close-btn',
|
||||||
.assertElementNotExists(sdaDisk + ' .disk-visual [data-volume=os] .close-btn', 'Button Close for Base system volume is not present')
|
'Button Close for Image Storage volume is present')
|
||||||
.assertElementExists(sdaDisk + ' .disk-details [data-volume=os] .volume-group-notice.text-info', 'Notice about "Minimal size" 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() {
|
'Testing Apply and Load Defaults buttons behaviour': function() {
|
||||||
return this.remote
|
return this.remote
|
||||||
|
@ -101,7 +105,8 @@ define([
|
||||||
.assertElementDisappears('.btn-load-defaults:disabled', 2000, 'Wait for changes applied')
|
.assertElementDisappears('.btn-load-defaults:disabled', 2000, 'Wait for changes applied')
|
||||||
.clickByCssSelector(loadDefaultsButtonSelector)
|
.clickByCssSelector(loadDefaultsButtonSelector)
|
||||||
.assertElementDisappears('.btn-load-defaults:disabled', 2000, 'Wait for defaults loaded')
|
.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(cancelButtonSelector, 'Cancel button is enabled')
|
||||||
.assertElementEnabled(applyButtonSelector, 'Apply button is enabled')
|
.assertElementEnabled(applyButtonSelector, 'Apply button is enabled')
|
||||||
.clickByCssSelector(applyButtonSelector);
|
.clickByCssSelector(applyButtonSelector);
|
||||||
|
@ -113,7 +118,8 @@ define([
|
||||||
.then(function(element) {
|
.then(function(element) {
|
||||||
return element.getSize()
|
return element.getSize()
|
||||||
.then(function(sizes) {
|
.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()
|
.end()
|
||||||
|
@ -128,18 +134,21 @@ define([
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.end()
|
.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]')
|
.findByCssSelector(sdaDisk + ' .disk-visual [data-volume=unallocated]')
|
||||||
// check that there is unallocated space after Image Storage removal
|
// check that there is unallocated space after Image Storage removal
|
||||||
.then(function(element) {
|
.then(function(element) {
|
||||||
return element.getSize()
|
return element.getSize()
|
||||||
.then(function(sizes) {
|
.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()
|
.end()
|
||||||
.clickByCssSelector(cancelButtonSelector)
|
.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');
|
.assertElementDisabled(applyButtonSelector, 'Apply button is disabled');
|
||||||
},
|
},
|
||||||
'Test volume size validation': function() {
|
'Test volume size validation': function() {
|
||||||
|
@ -148,8 +157,11 @@ define([
|
||||||
.setInputValue(sdaDisk + ' input[type=number][name=image]', '5')
|
.setInputValue(sdaDisk + ' input[type=number][name=image]', '5')
|
||||||
// set Base OS volume size lower than required
|
// set Base OS volume size lower than required
|
||||||
.setInputValue(sdaDisk + ' input[type=number][name=os]', '5')
|
.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.')
|
.assertElementExists(sdaDisk +
|
||||||
.assertElementDisabled(applyButtonSelector, 'Apply button is disabled in case of validation error')
|
' .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);
|
.clickByCssSelector(cancelButtonSelector);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -68,7 +68,8 @@ define([
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return interfacesPage.assignNetworkToInterface('Public', 'eth0');
|
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() {
|
'Bond interfaces with different speeds': function() {
|
||||||
return this.remote
|
return this.remote
|
||||||
|
@ -78,7 +79,8 @@ define([
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return interfacesPage.selectInterface('eth3');
|
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');
|
.assertElementEnabled('.btn-bond', 'Bonding button should still be enabled');
|
||||||
},
|
},
|
||||||
'Interfaces bonding': function() {
|
'Interfaces bonding': function() {
|
||||||
|
@ -137,7 +139,9 @@ define([
|
||||||
return interfacesPage.selectInterface('bond1');
|
return interfacesPage.selectInterface('bond1');
|
||||||
})
|
})
|
||||||
.assertElementDisabled('.btn-bond', 'Making sure bond button is disabled')
|
.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() {
|
'Test management controls state in new environment': function() {
|
||||||
return this.remote
|
return this.remote
|
||||||
.assertElementDisabled(searchButtonSelector, 'Search button is locked if there are no nodes in environment')
|
.assertElementDisabled(searchButtonSelector,
|
||||||
.assertElementDisabled(sortingButtonSelector, 'Sorting button is locked if there are no nodes in environment')
|
'Search button is locked if there are no nodes in environment')
|
||||||
.assertElementDisabled(filtersButtonSelector, 'Filters button is locked if there are no nodes in environment')
|
.assertElementDisabled(sortingButtonSelector,
|
||||||
.assertElementNotExists('.active-sorters-filters', 'Applied sorters and filters are not shown for empty environment');
|
'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': {
|
'Test management controls behaviour': {
|
||||||
setup: function() {
|
setup: function() {
|
||||||
|
@ -87,12 +91,15 @@ define([
|
||||||
.sleep(300)
|
.sleep(300)
|
||||||
.assertElementsExist('.node-list .node', 3, 'Search was successfull')
|
.assertElementsExist('.node-list .node', 3, 'Search was successfull')
|
||||||
.clickByCssSelector('.page-title')
|
.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')
|
.clickByCssSelector('.node-management-panel .btn-clear-search')
|
||||||
.assertElementsExist('.node-list .node', 4, 'Search was reset')
|
.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')
|
.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() {
|
'Test node list sorting': function() {
|
||||||
var activeSortersPanelSelector = '.active-sorters';
|
var activeSortersPanelSelector = '.active-sorters';
|
||||||
|
@ -100,13 +107,18 @@ define([
|
||||||
var firstNodeName;
|
var firstNodeName;
|
||||||
var self = this;
|
var self = this;
|
||||||
return this.remote
|
return this.remote
|
||||||
.assertElementExists(activeSortersPanelSelector, 'Active sorters panel is shown if there are nodes in cluster')
|
.assertElementExists(activeSortersPanelSelector,
|
||||||
.assertElementNotExists(activeSortersPanelSelector + '.btn-reset-sorting', 'Default sorting can not be reset from active sorters panel')
|
'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)
|
.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')
|
.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 .sorter-control .btn-remove-sorting',
|
||||||
.assertElementNotExists('.sorters .btn-reset-sorting', 'Default sorting can not be reset')
|
'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')
|
.findByCssSelector('.node-list .node-name .name p')
|
||||||
.getVisibleText().then(function(text) {
|
.getVisibleText().then(function(text) {
|
||||||
firstNodeName = text;
|
firstNodeName = text;
|
||||||
|
@ -115,20 +127,24 @@ define([
|
||||||
.clickByCssSelector('.sorters .sort-by-roles-asc button')
|
.clickByCssSelector('.sorters .sort-by-roles-asc button')
|
||||||
.findByCssSelector('.node-list .node-name .name p')
|
.findByCssSelector('.node-list .node-name .name p')
|
||||||
.getVisibleText().then(function(text) {
|
.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()
|
.end()
|
||||||
.clickByCssSelector('.sorters .sort-by-roles-desc button')
|
.clickByCssSelector('.sorters .sort-by-roles-desc button')
|
||||||
.then(function() {
|
.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')
|
.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)
|
// add sorting by CPU (real)
|
||||||
.clickByCssSelector(moreControlSelector + ' .popover [name=cores]')
|
.clickByCssSelector(moreControlSelector + ' .popover [name=cores]')
|
||||||
// add sorting by manufacturer
|
// add sorting by manufacturer
|
||||||
.clickByCssSelector(moreControlSelector + ' .popover [name=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
|
// remove sorting by manufacturer
|
||||||
.clickByCssSelector('.sorters .sort-by-manufacturer-asc .btn-remove-sorting')
|
.clickByCssSelector('.sorters .sort-by-manufacturer-asc .btn-remove-sorting')
|
||||||
.assertElementsExist('.nodes-group', 3, 'Particular sorting removal works')
|
.assertElementsExist('.nodes-group', 3, 'Particular sorting removal works')
|
||||||
|
@ -144,15 +160,20 @@ define([
|
||||||
var activeFiltersPanelSelector = '.active-filters';
|
var activeFiltersPanelSelector = '.active-filters';
|
||||||
var moreControlSelector = '.filters .more-control';
|
var moreControlSelector = '.filters .more-control';
|
||||||
return this.remote
|
return this.remote
|
||||||
.assertElementNotExists(activeFiltersPanelSelector, 'Environment has no active filters by default')
|
.assertElementNotExists(activeFiltersPanelSelector,
|
||||||
|
'Environment has no active filters by default')
|
||||||
.clickByCssSelector(filtersButtonSelector)
|
.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')
|
.clickByCssSelector('.filter-by-roles')
|
||||||
.assertElementNotExists('.filter-by-roles [type=checkbox]:checked', 'There are no active options in Roles filter')
|
.assertElementNotExists('.filter-by-roles [type=checkbox]:checked',
|
||||||
.assertElementNotExists('.filters .filter-control .btn-remove-filter', 'Default filters can not be deleted from filters panel')
|
'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')
|
.assertElementNotExists('.filters .btn-reset-filters', 'No filters to be reset')
|
||||||
.clickByCssSelector(moreControlSelector + ' button')
|
.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]')
|
.clickByCssSelector(moreControlSelector + ' [name=cores]')
|
||||||
.assertElementsExist('.filters .filter-control', 3, 'New Cores (real) filter was added')
|
.assertElementsExist('.filters .filter-control', 3, 'New Cores (real) filter was added')
|
||||||
.assertElementExists('.filter-by-cores .popover-content', 'New filter is open')
|
.assertElementExists('.filter-by-cores .popover-content', 'New filter is open')
|
||||||
|
@ -160,10 +181,13 @@ define([
|
||||||
.assertElementsExist('.filters .filter-control', 2, 'Particular filter removal works')
|
.assertElementsExist('.filters .filter-control', 2, 'Particular filter removal works')
|
||||||
.clickByCssSelector(moreControlSelector + ' button')
|
.clickByCssSelector(moreControlSelector + ' button')
|
||||||
.clickByCssSelector(moreControlSelector + ' [name=disks_amount]')
|
.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
|
// set min value more than max value
|
||||||
.setInputValue('.filters .filter-by-disks_amount input[type=number][name=start]', '100')
|
.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')
|
.assertElementNotExists('.node-list .node', 'No nodes match invalid filter')
|
||||||
.clickByCssSelector('.filters .btn-reset-filters')
|
.clickByCssSelector('.filters .btn-reset-filters')
|
||||||
.assertElementsExist('.node-list .node', 4, 'Node filtration was successfully reset')
|
.assertElementsExist('.node-list .node', 4, 'Node filtration was successfully reset')
|
||||||
|
@ -173,8 +197,10 @@ define([
|
||||||
.clickByCssSelector('.filters .filter-by-status [name=pending_addition]')
|
.clickByCssSelector('.filters .filter-by-status [name=pending_addition]')
|
||||||
.assertElementsExist('.node-list .node', 4, 'All nodes shown')
|
.assertElementsExist('.node-list .node', 4, 'All nodes shown')
|
||||||
.clickByCssSelector(filtersButtonSelector)
|
.clickByCssSelector(filtersButtonSelector)
|
||||||
.assertElementExists(activeFiltersPanelSelector, 'Applied filter is reflected in active filters panel')
|
.assertElementExists(activeFiltersPanelSelector,
|
||||||
.assertElementExists('.active-filters .btn-reset-filters', 'Reset filters button exists in active filters panel');
|
'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]')
|
.clickByCssSelector('.node input[type=checkbox]')
|
||||||
.assertElementExists('.node.selected', 'Node gets selected upon clicking')
|
.assertElementExists('.node.selected', 'Node gets selected upon clicking')
|
||||||
.assertElementExists('button.btn-delete-nodes:not(:disabled)', 'Delete Nodes and ...')
|
.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() {
|
.then(function() {
|
||||||
return node.renameNode(nodeNewName);
|
return node.renameNode(nodeNewName);
|
||||||
})
|
})
|
||||||
|
@ -82,9 +83,11 @@ define([
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return node.openNodePopup();
|
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-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')
|
.clickByCssSelector('.change-hostname .btn-link')
|
||||||
// change the hostname
|
// change the hostname
|
||||||
.findByCssSelector('.change-hostname [type=text]')
|
.findByCssSelector('.change-hostname [type=text]')
|
||||||
|
@ -92,8 +95,10 @@ define([
|
||||||
.type(newHostname)
|
.type(newHostname)
|
||||||
.pressKeys('\uE007')
|
.pressKeys('\uE007')
|
||||||
.end()
|
.end()
|
||||||
.assertElementDisappears('.change-hostname [type=text]', 2000, 'Hostname input disappears after submit')
|
.assertElementDisappears('.change-hostname [type=text]', 2000,
|
||||||
.assertElementTextEquals('span.node-hostname', newHostname, 'Node hostname has been updated')
|
'Hostname input disappears after submit')
|
||||||
|
.assertElementTextEquals('span.node-hostname', newHostname,
|
||||||
|
'Node hostname has been updated')
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return modal.close();
|
return modal.close();
|
||||||
});
|
});
|
||||||
|
@ -107,8 +112,10 @@ define([
|
||||||
.assertElementExists('i.glyphicon-ok', 'Self node is selectable')
|
.assertElementExists('i.glyphicon-ok', 'Self node is selectable')
|
||||||
.end()
|
.end()
|
||||||
.clickByCssSelector('.compact-node .node-name p')
|
.clickByCssSelector('.compact-node .node-name p')
|
||||||
.assertElementNotExists('.compact-node .node-name-input', 'Node can not be renamed from compact panel')
|
.assertElementNotExists('.compact-node .node-name-input',
|
||||||
.assertElementNotExists('.compact-node .role-list', 'Role list is not shown on node compact panel');
|
'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() {
|
'Compact node extended view': function() {
|
||||||
var newName = 'Node new new name';
|
var newName = 'Node new new name';
|
||||||
|
@ -117,7 +124,8 @@ define([
|
||||||
return node.openCompactNodeExtendedView();
|
return node.openCompactNodeExtendedView();
|
||||||
})
|
})
|
||||||
.clickByCssSelector('.node-popover .node-name input[type=checkbox]')
|
.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() {
|
.then(function() {
|
||||||
return node.openNodePopup(true);
|
return node.openNodePopup(true);
|
||||||
})
|
})
|
||||||
|
@ -131,12 +139,14 @@ define([
|
||||||
})
|
})
|
||||||
.findByCssSelector('.node-popover')
|
.findByCssSelector('.node-popover')
|
||||||
.assertElementExists('.role-list', 'Role list is shown in cluster node extended view')
|
.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()
|
.end()
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return node.renameNode(newName, true);
|
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() {
|
.then(function() {
|
||||||
return node.discardNode(true);
|
return node.discardNode(true);
|
||||||
})
|
})
|
||||||
|
@ -152,13 +162,17 @@ define([
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return node.openCompactNodeExtendedView();
|
return node.openCompactNodeExtendedView();
|
||||||
})
|
})
|
||||||
.assertElementNotExists('.node-popover .role-list', 'Unallocated node does not have roles assigned')
|
.assertElementNotExists('.node-popover .role-list',
|
||||||
.assertElementNotExists('.node-popover .node-buttons .btn', 'There are no action buttons in unallocated node extended view')
|
'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() {
|
.then(function() {
|
||||||
return node.openNodePopup(true);
|
return node.openNodePopup(true);
|
||||||
})
|
})
|
||||||
.assertElementNotExists('.modal .btn-edit-disks', 'Disks can not be configured for unallocated node')
|
.assertElementNotExists('.modal .btn-edit-disks',
|
||||||
.assertElementNotExists('.modal .btn-edit-networks', 'Interfaces can not be configured for unallocated node')
|
'Disks can not be configured for unallocated node')
|
||||||
|
.assertElementNotExists('.modal .btn-edit-networks',
|
||||||
|
'Interfaces can not be configured for unallocated node')
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return modal.close();
|
return modal.close();
|
||||||
});
|
});
|
||||||
|
|
|
@ -38,14 +38,18 @@ define([
|
||||||
},
|
},
|
||||||
'Notification Page': function() {
|
'Notification Page': function() {
|
||||||
return this.remote
|
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
|
// Go to Notification page
|
||||||
.clickByCssSelector('.notifications-icon')
|
.clickByCssSelector('.notifications-icon')
|
||||||
.clickLinkByText('View all')
|
.clickLinkByText('View all')
|
||||||
.assertElementAppears('.notifications-page', 2000, 'Notification page is rendered')
|
.assertElementAppears('.notifications-page', 2000, 'Notification page is rendered')
|
||||||
.assertElementExists('.notifications-page .notification', 'There is the start notification on the page')
|
.assertElementExists('.notifications-page .notification',
|
||||||
.assertElementTextEquals('.notification-group .title', 'Today', 'Notification group has "Today" label')
|
'There is the start notification on the page')
|
||||||
.assertElementNotDisplayed('.notifications-icon .badge', 'Badge notification indicator is hidden');
|
.assertElementTextEquals('.notification-group .title', 'Today',
|
||||||
|
'Notification group has "Today" label')
|
||||||
|
.assertElementNotDisplayed('.notifications-icon .badge',
|
||||||
|
'Badge notification indicator is hidden');
|
||||||
},
|
},
|
||||||
'Notification badge behaviour': function() {
|
'Notification badge behaviour': function() {
|
||||||
var clusterName = common.pickRandomName('Test Cluster');
|
var clusterName = common.pickRandomName('Test Cluster');
|
||||||
|
@ -61,9 +65,11 @@ define([
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return common.removeCluster(clusterName);
|
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')
|
.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
|
// Check if Node Information dialog is shown
|
||||||
.clickByCssSelector('.notifications-popover .notification.clickable p')
|
.clickByCssSelector('.notifications-popover .notification.clickable p')
|
||||||
.then(function() {
|
.then(function() {
|
||||||
|
|
|
@ -62,9 +62,12 @@ define([
|
||||||
var self = this;
|
var self = this;
|
||||||
var zabbixInitialVersion, zabbixTextInputValue;
|
var zabbixInitialVersion, zabbixTextInputValue;
|
||||||
return this.remote
|
return this.remote
|
||||||
.assertElementEnabled(zabbixSectionSelector + 'h3 input[type=checkbox]', 'Plugin is changeable')
|
.assertElementEnabled(zabbixSectionSelector + 'h3 input[type=checkbox]',
|
||||||
.assertElementNotSelected(zabbixSectionSelector + 'h3 input[type=checkbox]', 'Plugin is not actvated')
|
'Plugin is changeable')
|
||||||
.assertElementNotExists(zabbixSectionSelector + '> div input:not(:disabled)', 'Inactive plugin attributes can not be changes')
|
.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
|
// activate plugin
|
||||||
.clickByCssSelector(zabbixSectionSelector + 'h3 input[type=checkbox]')
|
.clickByCssSelector(zabbixSectionSelector + 'h3 input[type=checkbox]')
|
||||||
// save changes
|
// save changes
|
||||||
|
@ -85,9 +88,12 @@ define([
|
||||||
})
|
})
|
||||||
.end()
|
.end()
|
||||||
// change plugin version
|
// change plugin version
|
||||||
.clickByCssSelector(zabbixSectionSelector + '.plugin-versions input[type=radio]:not(:checked)')
|
.clickByCssSelector(zabbixSectionSelector +
|
||||||
.assertElementPropertyNotEquals(zabbixSectionSelector + '[name=zabbix_text_1]', 'value', zabbixTextInputValue, 'Plugin version was changed')
|
'.plugin-versions input[type=radio]:not(:checked)')
|
||||||
.assertElementExists('.subtab-link-other .glyphicon-danger-sign', 'Plugin atributes validation works')
|
.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
|
// fix validation error
|
||||||
.setInputValue(zabbixSectionSelector + '[name=zabbix_text_with_regex]', 'aa-aa')
|
.setInputValue(zabbixSectionSelector + '[name=zabbix_text_with_regex]', 'aa-aa')
|
||||||
.waitForElementDeletion('.subtab-link-other .glyphicon-danger-sign', 1000)
|
.waitForElementDeletion('.subtab-link-other .glyphicon-danger-sign', 1000)
|
||||||
|
@ -95,7 +101,9 @@ define([
|
||||||
// reset plugin version change
|
// reset plugin version change
|
||||||
.clickByCssSelector('.btn-revert-changes')
|
.clickByCssSelector('.btn-revert-changes')
|
||||||
.then(function() {
|
.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() {
|
'Check plugin in deployed environment': function() {
|
||||||
|
@ -124,8 +132,12 @@ define([
|
||||||
.end()
|
.end()
|
||||||
// activate plugin
|
// activate plugin
|
||||||
.clickByCssSelector(zabbixSectionSelector + 'h3 input[type=checkbox]')
|
.clickByCssSelector(zabbixSectionSelector + 'h3 input[type=checkbox]')
|
||||||
.assertElementExists(zabbixSectionSelector + '.plugin-versions input[type=radio]:not(:disabled)', 'Some plugin versions are hotluggable')
|
.assertElementExists(zabbixSectionSelector +
|
||||||
.assertElementPropertyNotEquals(zabbixSectionSelector + '.plugin-versions input[type=radio]:checked', 'value', zabbixInitialVersion, 'Plugin hotpluggable version is automatically chosen')
|
'.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
|
// fix validation error
|
||||||
.setInputValue(zabbixSectionSelector + '[name=zabbix_text_with_regex]', 'aa-aa')
|
.setInputValue(zabbixSectionSelector + '[name=zabbix_text_with_regex]', 'aa-aa')
|
||||||
.waitForElementDeletion('.subtab-link-other .glyphicon-danger-sign', 1000)
|
.waitForElementDeletion('.subtab-link-other .glyphicon-danger-sign', 1000)
|
||||||
|
@ -133,7 +145,9 @@ define([
|
||||||
// deactivate plugin
|
// deactivate plugin
|
||||||
.clickByCssSelector(zabbixSectionSelector + 'h3 input[type=checkbox]')
|
.clickByCssSelector(zabbixSectionSelector + 'h3 input[type=checkbox]')
|
||||||
.then(function() {
|
.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');
|
.assertElementDisabled('.btn-apply-changes', 'The change as reset successfully');
|
||||||
},
|
},
|
||||||
|
@ -144,12 +158,16 @@ define([
|
||||||
.clickByCssSelector(loggingSectionSelector + 'h3 input[type=checkbox]')
|
.clickByCssSelector(loggingSectionSelector + 'h3 input[type=checkbox]')
|
||||||
// activate Zabbix plugin
|
// activate Zabbix plugin
|
||||||
.clickByCssSelector(zabbixSectionSelector + 'h3 input[type=checkbox]')
|
.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
|
// change Zabbix plugin version
|
||||||
.clickByCssSelector(zabbixSectionSelector + '.plugin-versions input[type=radio]:not(:checked)')
|
.clickByCssSelector(zabbixSectionSelector +
|
||||||
.assertElementNotSelected(zabbixSectionSelector + '[name=zabbix_checkbox]', 'Zabbix checkbox is not activated')
|
'.plugin-versions input[type=radio]:not(:checked)')
|
||||||
|
.assertElementNotSelected(zabbixSectionSelector + '[name=zabbix_checkbox]',
|
||||||
|
'Zabbix checkbox is not activated')
|
||||||
.clickByCssSelector(zabbixSectionSelector + '[name=zabbix_checkbox]')
|
.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
|
// reset changes
|
||||||
.clickByCssSelector('.btn-revert-changes');
|
.clickByCssSelector('.btn-revert-changes');
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,8 @@ define([
|
||||||
.assertElementExists('.capacity-audit', 'Capacity Audit block is present')
|
.assertElementExists('.capacity-audit', 'Capacity Audit block is present')
|
||||||
.assertElementExists('.tracking', 'Statistics block is present')
|
.assertElementExists('.tracking', 'Statistics block is present')
|
||||||
.assertElementSelected(sendStatisticsCheckbox, 'Save Staticstics checkbox is checked')
|
.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() {
|
'Diagnostic snapshot link generation': function() {
|
||||||
return this.remote
|
return this.remote
|
||||||
|
@ -59,9 +60,12 @@ define([
|
||||||
return this.remote
|
return this.remote
|
||||||
// Uncheck "Send usage statistics" checkbox
|
// Uncheck "Send usage statistics" checkbox
|
||||||
.clickByCssSelector(sendStatisticsCheckbox)
|
.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)
|
.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() {
|
'Discard changes': function() {
|
||||||
return this.remote
|
return this.remote
|
||||||
|
@ -87,7 +91,8 @@ define([
|
||||||
.assertElementAppears('.clusters-page', 1000, 'Redirecting to Environments')
|
.assertElementAppears('.clusters-page', 1000, 'Redirecting to Environments')
|
||||||
// Go back to Support Page and ...
|
// Go back to Support Page and ...
|
||||||
.clickLinkByText('Support')
|
.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
|
// Uncheck the "Send usage statistics" checkbox value
|
||||||
.clickByCssSelector(sendStatisticsCheckbox)
|
.clickByCssSelector(sendStatisticsCheckbox)
|
||||||
// Go to another page with not saved changes
|
// Go to another page with not saved changes
|
||||||
|
@ -105,7 +110,8 @@ define([
|
||||||
.assertElementAppears('.clusters-page', 1000, 'Redirecting to Environments')
|
.assertElementAppears('.clusters-page', 1000, 'Redirecting to Environments')
|
||||||
// Go back to Support Page and ...
|
// Go back to Support Page and ...
|
||||||
.clickLinkByText('Support')
|
.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
|
// Uncheck the "Send usage statistics" checkbox value
|
||||||
.clickByCssSelector(sendStatisticsCheckbox)
|
.clickByCssSelector(sendStatisticsCheckbox)
|
||||||
// Go to another page with not saved changes
|
// Go to another page with not saved changes
|
||||||
|
@ -120,7 +126,8 @@ define([
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return modal.waitToClose();
|
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() {
|
'Test steps manipulations': function() {
|
||||||
return this.remote
|
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
|
// Compute
|
||||||
.pressKeys('\uE007')
|
.pressKeys('\uE007')
|
||||||
// Network
|
// Network
|
||||||
|
|
|
@ -68,7 +68,8 @@ suite('Expression', () => {
|
||||||
['"unknown-role" in release:roles', false],
|
['"unknown-role" in release:roles', false],
|
||||||
['settings:common.libvirt_type.value', hypervisor],
|
['settings:common.libvirt_type.value', hypervisor],
|
||||||
['settings:common.libvirt_type.value == "' + hypervisor + '"', true],
|
['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
|
// test nonexistent keys
|
||||||
['cluster:nonexistentkey', Error],
|
['cluster:nonexistentkey', Error],
|
||||||
['cluster:nonexistentkey == null', true, false],
|
['cluster:nonexistentkey == null', true, false],
|
||||||
|
@ -86,9 +87,11 @@ suite('Expression', () => {
|
||||||
_.each(testCases, ([expression, result, strict]) => {
|
_.each(testCases, ([expression, result, strict]) => {
|
||||||
var options = {strict};
|
var options = {strict};
|
||||||
if (result === Error) {
|
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 {
|
} 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();
|
var initialState = input.getInitialState();
|
||||||
|
|
||||||
assert.equal(input.props.type, 'file', 'Input type should be equal to file');
|
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');
|
assert.equal(initialState.content, 'CERTIFICATE', 'Content should be equal to the default');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -48,7 +49,8 @@ suite('File Control', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
input.pickFile();
|
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', () => {
|
test('File fetching', () => {
|
||||||
|
|
|
@ -24,39 +24,48 @@ suite('Test models', () => {
|
||||||
|
|
||||||
filters = {status: []};
|
filters = {status: []};
|
||||||
result = ['running', 'pending', 'ready', 'error'];
|
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'};
|
filters = {status: 'ready'};
|
||||||
result = ['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']};
|
filters = {status: ['ready', 'running']};
|
||||||
result = ['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};
|
filters = {status: ['ready'], active: true};
|
||||||
result = [];
|
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};
|
filters = {status: ['running'], active: true};
|
||||||
result = ['running'];
|
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};
|
filters = {status: ['running'], active: false};
|
||||||
result = [];
|
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};
|
filters = {status: ['ready', 'running'], active: false};
|
||||||
result = ['ready'];
|
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};
|
filters = {active: true};
|
||||||
result = ['running', 'pending'];
|
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};
|
filters = {active: false};
|
||||||
result = ['ready', 'error'];
|
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', () => {
|
test('Test extendGroups method', () => {
|
||||||
|
@ -66,39 +75,48 @@ suite('Test models', () => {
|
||||||
|
|
||||||
filters = {name: []};
|
filters = {name: []};
|
||||||
result = allTaskNames;
|
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: []};
|
filters = {group: []};
|
||||||
result = allTaskNames;
|
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'};
|
filters = {name: 'deploy'};
|
||||||
result = ['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'};
|
filters = {name: 'dump'};
|
||||||
result = ['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']};
|
filters = {name: ['deploy', 'check_networks']};
|
||||||
result = ['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'};
|
filters = {group: 'deployment'};
|
||||||
result = task.groups.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']};
|
filters = {group: ['deployment', 'network']};
|
||||||
result = allTaskNames;
|
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'};
|
filters = {name: 'deploy', group: 'deployment'};
|
||||||
result = ['deploy'];
|
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'};
|
filters = {name: 'deploy', group: 'network'};
|
||||||
result = [];
|
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
|
* License for the specific language governing permissions and limitations
|
||||||
* under the License.
|
* 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 offloadingModesConrol, TestMode22, TestMode31, fakeOffloadingModes;
|
||||||
var fakeInterface = {
|
var fakeInterface = {
|
||||||
offloading_modes: fakeOffloadingModes,
|
offloading_modes: fakeOffloadingModes,
|
||||||
get(key) {
|
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;
|
return fakeOffloadingModes;
|
||||||
},
|
},
|
||||||
set(key, value) {
|
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;
|
fakeOffloadingModes = value;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -70,18 +73,21 @@ suite('Offloadning Modes control', () => {
|
||||||
test('Set submodes states logic', () => {
|
test('Set submodes states logic', () => {
|
||||||
var mode = offloadingModesConrol.findMode('TestName1', fakeOffloadingModes);
|
var mode = offloadingModesConrol.findMode('TestName1', fakeOffloadingModes);
|
||||||
offloadingModesConrol.setModeState(mode, false);
|
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', () => {
|
test('Disabled reversed logic', () => {
|
||||||
var mode = offloadingModesConrol.findMode('TestName2', fakeOffloadingModes);
|
var mode = offloadingModesConrol.findMode('TestName2', fakeOffloadingModes);
|
||||||
offloadingModesConrol.setModeState(TestMode22, true);
|
offloadingModesConrol.setModeState(TestMode22, true);
|
||||||
offloadingModesConrol.checkModes(null, fakeOffloadingModes);
|
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', () => {
|
test('All Modes option logic', () => {
|
||||||
var enableAllModes = offloadingModesConrol.onModeStateChange('All Modes', true);
|
var enableAllModes = offloadingModesConrol.onModeStateChange('All Modes', true);
|
||||||
enableAllModes();
|
enableAllModes();
|
||||||
var mode = offloadingModesConrol.findMode('TestName2', fakeOffloadingModes);
|
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');
|
var serverUnavailableMessage = i18n('dialog.error_dialog.server_unavailable');
|
||||||
|
|
||||||
response = {status: 500, responseText: 'Server error occured'};
|
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'};
|
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'};
|
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'};
|
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'})};
|
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', () => {
|
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, 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', () => {
|
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('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('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');
|
assert.equal(getGateway('172.16.0.0/'), '', 'No gateway returned for invalid CIDR');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Test getDefaultIPRangeForCidr', () => {
|
test('Test getDefaultIPRangeForCidr', () => {
|
||||||
var getRange = utils.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('172.16.0.0/24'), [['172.16.0.1', '172.16.0.254']],
|
||||||
assert.deepEqual(getRange('192.168.0.0/10', true), [['192.128.0.2', '192.191.255.254']], 'Gateway address excluded from default IP range');
|
'Getting default IP range for CIDR');
|
||||||
assert.deepEqual(getRange('172.16.0.0/31'), [['', '']], 'No IP range returned for inappropriate CIDR (network is too small)');
|
assert.deepEqual(getRange('192.168.0.0/10', true), [['192.128.0.2', '192.191.255.254']],
|
||||||
assert.deepEqual(getRange('172.16.0.0/', true), [['', '']], 'No IP range returned for invalid CIDR');
|
'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', () => {
|
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.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.15.255'), 'Check broadcast address');
|
||||||
assert.notOk(validate('172.16.0.0/20', '172.16.0.0'), 'Check network 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';
|
import models from 'models';
|
||||||
|
|
||||||
var utils = {
|
var utils = {
|
||||||
|
/*eslint-disable max-len*/
|
||||||
regexes: {
|
regexes: {
|
||||||
url: /(?:https?:\/\/([\-\w\.]+)+(:\d+)?(\/([\w\/_\-\.]*(\?[\w\/_\-\.&%]*)?(#[\w\/_\-\.&%]*)?)?)?)/,
|
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])$/,
|
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})$/,
|
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])$/
|
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) {
|
serializeTabOptions(options) {
|
||||||
return _.map(options, (value, key) => key + ':' + value).join(';');
|
return _.map(options, (value, key) => key + ':' + value).join(';');
|
||||||
},
|
},
|
||||||
|
@ -55,14 +57,16 @@ var utils = {
|
||||||
return '<a target="_blank" href="' + url + '">' + url + '</a>';
|
return '<a target="_blank" href="' + url + '">' + url + '</a>';
|
||||||
},
|
},
|
||||||
urlify(text) {
|
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) {
|
composeList(value) {
|
||||||
return _.isUndefined(value) ? [] : _.isArray(value) ? value : [value];
|
return _.isUndefined(value) ? [] : _.isArray(value) ? value : [value];
|
||||||
},
|
},
|
||||||
// FIXME(vkramskikh): moved here from healthcheck_tab to make testable
|
// FIXME(vkramskikh): moved here from healthcheck_tab to make testable
|
||||||
highlightTestStep(text, step) {
|
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,
|
classNames: classNames,
|
||||||
parseModelPath(path, models) {
|
parseModelPath(path, models) {
|
||||||
|
@ -129,7 +133,8 @@ var utils = {
|
||||||
break;
|
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) {
|
showMemorySize(bytes) {
|
||||||
return utils.showSize(bytes, 1024);
|
return utils.showSize(bytes, 1024);
|
||||||
|
@ -142,7 +147,8 @@ var utils = {
|
||||||
return Math.pow(2, 32 - parseInt(_.last(cidr.split('/')), 10));
|
return Math.pow(2, 32 - parseInt(_.last(cidr.split('/')), 10));
|
||||||
},
|
},
|
||||||
formatNumber(n) {
|
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) {
|
floor(n, decimals) {
|
||||||
return Math.floor(n * Math.pow(10, decimals)) / Math.pow(10, decimals);
|
return Math.floor(n * Math.pow(10, decimals)) / Math.pow(10, decimals);
|
||||||
|
@ -152,7 +158,8 @@ var utils = {
|
||||||
},
|
},
|
||||||
validateVlan(vlan, forbiddenVlans, field, disallowNullValue) {
|
validateVlan(vlan, forbiddenVlans, field, disallowNullValue) {
|
||||||
var error = {};
|
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');
|
error[field] = i18n('cluster_page.network_tab.validation.invalid_vlan');
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
@ -218,7 +225,8 @@ var utils = {
|
||||||
} else if (existingRanges.length) {
|
} else if (existingRanges.length) {
|
||||||
var intersection = utils.checkIPRangesIntersection(range, existingRanges);
|
var intersection = utils.checkIPRangesIntersection(range, existingRanges);
|
||||||
if (intersection) {
|
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) {
|
checkIPRangesIntersection([startIP, endIP], existingRanges) {
|
||||||
var startIPInt = IP.toLong(startIP);
|
var startIPInt = IP.toLong(startIP);
|
||||||
var endIPInt = IP.toLong(endIP);
|
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) {
|
validateIpCorrespondsToCIDR(cidr, ip) {
|
||||||
if (!cidr) return true;
|
if (!cidr) return true;
|
||||||
var networkData = IP.cidrSubnet(cidr);
|
var networkData = IP.cidrSubnet(cidr);
|
||||||
var ipInt = IP.toLong(ip);
|
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) {
|
validateVlanRange(vlanStart, vlanEnd, vlan) {
|
||||||
return vlan >= vlanStart && vlan <= vlanEnd;
|
return vlan >= vlanStart && vlan <= vlanEnd;
|
||||||
|
|
|
@ -62,10 +62,15 @@ var ClusterPage = React.createClass({
|
||||||
['home', '#'],
|
['home', '#'],
|
||||||
['environments', '#clusters'],
|
['environments', '#clusters'],
|
||||||
[cluster.get('name'), '#cluster/' + cluster.get('id'), {skipTranslation: true}],
|
[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) {
|
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;
|
return breadcrumbs;
|
||||||
},
|
},
|
||||||
|
@ -94,7 +99,8 @@ var ClusterPage = React.createClass({
|
||||||
if (currentClusterId == id) {
|
if (currentClusterId == id) {
|
||||||
// just another tab has been chosen, do not load cluster again
|
// just another tab has been chosen, do not load cluster again
|
||||||
cluster = app.page.props.cluster;
|
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 {
|
} else {
|
||||||
cluster = new models.Cluster({id: id});
|
cluster = new models.Cluster({id: id});
|
||||||
|
|
||||||
|
@ -111,7 +117,8 @@ var ClusterPage = React.createClass({
|
||||||
cluster.set({pluginLinks: pluginLinks});
|
cluster.set({pluginLinks: pluginLinks});
|
||||||
|
|
||||||
cluster.get('nodes').fetch = function(options) {
|
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(
|
promise = $.when(
|
||||||
cluster.fetch(),
|
cluster.fetch(),
|
||||||
|
@ -124,12 +131,16 @@ var ClusterPage = React.createClass({
|
||||||
)
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
var networkConfiguration = new models.NetworkConfiguration();
|
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({
|
cluster.set({
|
||||||
networkConfiguration: networkConfiguration,
|
networkConfiguration: networkConfiguration,
|
||||||
release: new models.Release({id: cluster.get('release_id')})
|
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(() => {
|
.then(() => {
|
||||||
var useVcenter = cluster.get('settings').get('common.use_vcenter.value');
|
var useVcenter = cluster.get('settings').get('common.use_vcenter.value');
|
||||||
|
@ -141,7 +152,8 @@ var ClusterPage = React.createClass({
|
||||||
return vcenter.fetch();
|
return vcenter.fetch();
|
||||||
})
|
})
|
||||||
.then(() => {
|
.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) => {
|
return promise.then((data) => {
|
||||||
|
@ -232,7 +244,8 @@ var ClusterPage = React.createClass({
|
||||||
var selectedLogs;
|
var selectedLogs;
|
||||||
if (props.tabOptions[0]) {
|
if (props.tabOptions[0]) {
|
||||||
selectedLogs = utils.deserializeTabOptions(_.compact(props.tabOptions).join('/'));
|
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});
|
this.setState({selectedLogs: selectedLogs});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -283,7 +296,11 @@ var ClusterPage = React.createClass({
|
||||||
<div className='page-title'>
|
<div className='page-title'>
|
||||||
<h1 className='title'>
|
<h1 className='title'>
|
||||||
{cluster.get('name')}
|
{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>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div className='tabs-box'>
|
<div className='tabs-box'>
|
||||||
|
@ -292,7 +309,12 @@ var ClusterPage = React.createClass({
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
key={url}
|
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}
|
href={'#cluster/' + cluster.id + '/' + url}
|
||||||
>
|
>
|
||||||
<div className='icon'></div>
|
<div className='icon'></div>
|
||||||
|
|
|
@ -21,14 +21,18 @@ import ReactDOM from 'react-dom';
|
||||||
import utils from 'utils';
|
import utils from 'utils';
|
||||||
import dispatcher from 'dispatcher';
|
import dispatcher from 'dispatcher';
|
||||||
import {Input, ProgressBar, Tooltip} from 'views/controls';
|
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';
|
import {backboneMixin, pollingMixin, renamingMixin} from 'component_mixins';
|
||||||
|
|
||||||
var namespace = 'cluster_page.dashboard_tab.';
|
var namespace = 'cluster_page.dashboard_tab.';
|
||||||
|
|
||||||
var DashboardTab = React.createClass({
|
var DashboardTab = React.createClass({
|
||||||
mixins: [
|
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({
|
backboneMixin({
|
||||||
modelOrCollection: (props) => props.cluster.get('tasks'),
|
modelOrCollection: (props) => props.cluster.get('tasks'),
|
||||||
renderOn: 'update change'
|
renderOn: 'update change'
|
||||||
|
@ -254,9 +258,13 @@ var DeploymentResult = React.createClass({
|
||||||
<br />
|
<br />
|
||||||
<span dangerouslySetInnerHTML={{__html: utils.urlify(summary)}} />
|
<span dangerouslySetInnerHTML={{__html: utils.urlify(summary)}} />
|
||||||
<div className={utils.classNames({'task-result-details': true, hidden: !details})}>
|
<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'>
|
<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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -288,14 +296,29 @@ var DocumentationLinks = React.createClass({
|
||||||
<div className='documentation col-xs-12'>
|
<div className='documentation col-xs-12'>
|
||||||
{isMirantisIso ?
|
{isMirantisIso ?
|
||||||
[
|
[
|
||||||
this.renderDocumentationLinks('https://www.mirantis.com/openstack-documentation/', 'mos_documentation'),
|
this.renderDocumentationLinks(
|
||||||
this.renderDocumentationLinks(utils.composeDocumentationLink('plugin-dev.html#plugin-dev'), 'plugin_documentation'),
|
'https://www.mirantis.com/openstack-documentation/',
|
||||||
this.renderDocumentationLinks('https://software.mirantis.com/mirantis-openstack-technical-bulletins/', 'technical_bulletins')
|
'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(
|
||||||
this.renderDocumentationLinks('https://wiki.openstack.org/wiki/Fuel/Plugins', 'plugin_documentation')
|
'http://docs.openstack.org/',
|
||||||
|
'openstack_documentation'
|
||||||
|
),
|
||||||
|
this.renderDocumentationLinks(
|
||||||
|
'https://wiki.openstack.org/wiki/Fuel/Plugins',
|
||||||
|
'plugin_documentation'
|
||||||
|
)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@ -308,7 +331,8 @@ var DocumentationLinks = React.createClass({
|
||||||
// it should be refactored to provide proper logics separation and decoupling
|
// it should be refactored to provide proper logics separation and decoupling
|
||||||
var DeployReadinessBlock = React.createClass({
|
var DeployReadinessBlock = React.createClass({
|
||||||
mixins: [
|
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({
|
backboneMixin({
|
||||||
modelOrCollection(props) {
|
modelOrCollection(props) {
|
||||||
return props.cluster.get('tasks');
|
return props.cluster.get('tasks');
|
||||||
|
@ -332,7 +356,8 @@ var DeployReadinessBlock = React.createClass({
|
||||||
validate(cluster) {
|
validate(cluster) {
|
||||||
return _.reduce(
|
return _.reduce(
|
||||||
this.validations,
|
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: []}
|
{blocker: [], error: [], warning: []}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -398,9 +423,16 @@ var DeployReadinessBlock = React.createClass({
|
||||||
function(cluster) {
|
function(cluster) {
|
||||||
var configModels = this.getConfigModels();
|
var configModels = this.getConfigModels();
|
||||||
var roleModels = cluster.get('roles');
|
var roleModels = cluster.get('roles');
|
||||||
var validRoleModels = roleModels.filter((role) => !role.checkRestrictions(configModels).result);
|
var validRoleModels = roleModels.filter((role) => {
|
||||||
var limitValidations = _.zipObject(validRoleModels.map((role) => [role.get('name'), role.checkLimits(configModels, cluster.get('nodes'))]));
|
return !role.checkRestrictions(configModels).result;
|
||||||
var limitRecommendations = _.zipObject(validRoleModels.map((role) => [role.get('name'), role.checkLimits(configModels, cluster.get('nodes'), true, ['recommended'])]));
|
});
|
||||||
|
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 {
|
return {
|
||||||
blocker: roleModels.map((role) => {
|
blocker: roleModels.map((role) => {
|
||||||
var name = role.get('name');
|
var name = role.get('name');
|
||||||
|
@ -461,7 +493,9 @@ var DeployReadinessBlock = React.createClass({
|
||||||
var nodes = cluster.get('nodes');
|
var nodes = cluster.get('nodes');
|
||||||
var alerts = this.validate(cluster);
|
var alerts = this.validate(cluster);
|
||||||
var isDeploymentPossible = cluster.isDeploymentPossible() && !alerts.blocker.length;
|
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 (
|
return (
|
||||||
<div className='row'>
|
<div className='row'>
|
||||||
|
@ -471,9 +505,18 @@ var DeployReadinessBlock = React.createClass({
|
||||||
<div>
|
<div>
|
||||||
<h4>{i18n(namespace + 'changes_header')}</h4>
|
<h4>{i18n(namespace + 'changes_header')}</h4>
|
||||||
<ul>
|
<ul>
|
||||||
{this.renderChangedNodesAmount(nodes.where({pending_addition: true}), 'added_node')}
|
{this.renderChangedNodesAmount(
|
||||||
{this.renderChangedNodesAmount(nodes.where({status: 'provisioned'}), 'provisioned_node')}
|
nodes.where({pending_addition: true}),
|
||||||
{this.renderChangedNodesAmount(nodes.where({pending_deletion: true}), 'deleted_node')}
|
'added_node'
|
||||||
|
)}
|
||||||
|
{this.renderChangedNodesAmount(
|
||||||
|
nodes.where({status: 'provisioned'}),
|
||||||
|
'provisioned_node'
|
||||||
|
)}
|
||||||
|
{this.renderChangedNodesAmount(
|
||||||
|
nodes.where({pending_deletion: true}),
|
||||||
|
'deleted_node'
|
||||||
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@ -489,7 +532,10 @@ var DeployReadinessBlock = React.createClass({
|
||||||
<button
|
<button
|
||||||
className={utils.classNames({
|
className={utils.classNames({
|
||||||
'btn btn-primary deploy-btn': true,
|
'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)}
|
onClick={_.partial(this.showDialog, DeployChangesDialog)}
|
||||||
disabled={!isDeploymentPossible}
|
disabled={!isDeploymentPossible}
|
||||||
|
@ -652,7 +698,8 @@ var ClusterInfo = React.createClass({
|
||||||
},
|
},
|
||||||
renderLegend(fieldsData, isRole) {
|
renderLegend(fieldsData, isRole) {
|
||||||
var result = _.map(fieldsData, (field) => {
|
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 ?
|
return numberOfNodes ?
|
||||||
<div key={field}>
|
<div key={field}>
|
||||||
<div className='col-xs-10'>
|
<div className='col-xs-10'>
|
||||||
|
@ -683,8 +730,10 @@ var ClusterInfo = React.createClass({
|
||||||
renderStatistics() {
|
renderStatistics() {
|
||||||
var hasNodes = !!this.props.cluster.get('nodes').length;
|
var hasNodes = !!this.props.cluster.get('nodes').length;
|
||||||
var fieldRoles = _.union(['total'], this.props.cluster.get('roles').pluck('name'));
|
var fieldRoles = _.union(['total'], this.props.cluster.get('roles').pluck('name'));
|
||||||
var fieldStatuses = ['offline', 'error', 'pending_addition', 'pending_deletion', 'ready', 'provisioned',
|
var fieldStatuses = [
|
||||||
'provisioning', 'deploying', 'removing'];
|
'offline', 'error', 'pending_addition', 'pending_deletion', 'ready',
|
||||||
|
'provisioned', 'provisioning', 'deploying', 'removing'
|
||||||
|
];
|
||||||
return (
|
return (
|
||||||
<div className='row statistics-block'>
|
<div className='row statistics-block'>
|
||||||
<div className='title'>{i18n(namespace + 'cluster_info_fields.statistics')}</div>
|
<div className='title'>{i18n(namespace + 'cluster_info_fields.statistics')}</div>
|
||||||
|
@ -905,7 +954,8 @@ var ResetEnvironmentAction = React.createClass({
|
||||||
<Tooltip
|
<Tooltip
|
||||||
key='reset-tooltip'
|
key='reset-tooltip'
|
||||||
placement='right'
|
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' />
|
<i className='glyphicon glyphicon-info-sign' />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
|
@ -95,14 +95,14 @@ var HealthcheckTabContent = React.createClass({
|
||||||
return {
|
return {
|
||||||
actionInProgress: false,
|
actionInProgress: false,
|
||||||
credentialsVisible: null,
|
credentialsVisible: null,
|
||||||
credentials: _.transform(this.props.cluster.get('settings').get('access'), (result, value, key) => {
|
credentials: _.transform(this.props.cluster.get('settings').get('access'),
|
||||||
result[key] = value.value;
|
(result, value, key) => result[key] = value.value)
|
||||||
})
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
isLocked() {
|
isLocked() {
|
||||||
var cluster = this.props.cluster;
|
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() {
|
getNumberOfCheckedTests() {
|
||||||
return this.props.tests.where({checked: true}).length;
|
return this.props.tests.where({checked: true}).length;
|
||||||
|
@ -248,14 +248,17 @@ var HealthcheckTabContent = React.createClass({
|
||||||
}
|
}
|
||||||
<div>
|
<div>
|
||||||
{(this.props.cluster.get('status') == 'new') &&
|
{(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'>
|
<div key='testsets'>
|
||||||
{this.props.testsets.map((testset) => {
|
{this.props.testsets.map((testset) => {
|
||||||
return <TestSet
|
return <TestSet
|
||||||
key={testset.id}
|
key={testset.id}
|
||||||
testset={testset}
|
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}))}
|
tests={new Backbone.Collection(this.props.tests.where({testset: testset.id}))}
|
||||||
disabled={disabledState || hasRunningTests}
|
disabled={disabledState || hasRunningTests}
|
||||||
/>;
|
/>;
|
||||||
|
@ -312,7 +315,10 @@ var TestSet = React.createClass({
|
||||||
this.props.tests.invoke('on', 'change:checked', this.updateTestsetCheckbox, this);
|
this.props.tests.invoke('on', 'change:checked', this.updateTestsetCheckbox, this);
|
||||||
},
|
},
|
||||||
updateTestsetCheckbox() {
|
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() {
|
render() {
|
||||||
var classes = {
|
var classes = {
|
||||||
|
@ -434,7 +440,8 @@ var Test = React.createClass({
|
||||||
</td>
|
</td>
|
||||||
<td className='healthcheck-col-status'>
|
<td className='healthcheck-col-status'>
|
||||||
<div className={currentStatusClassName}>
|
<div className={currentStatusClassName}>
|
||||||
{iconClasses[status] ? <i className={iconClasses[status]} /> : String.fromCharCode(0x2014)}
|
{iconClasses[status] ? <i className={iconClasses[status]} /> :
|
||||||
|
String.fromCharCode(0x2014)}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -67,9 +67,11 @@ var LogsTab = React.createClass({
|
||||||
},
|
},
|
||||||
showLogs(params) {
|
showLogs(params) {
|
||||||
this.stopPolling();
|
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();
|
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 || {};
|
params = params || {};
|
||||||
this.fetchLogs(params)
|
this.fetchLogs(params)
|
||||||
.done((data) => {
|
.done((data) => {
|
||||||
|
@ -131,7 +133,8 @@ var LogsTab = React.createClass({
|
||||||
});
|
});
|
||||||
|
|
||||||
var LogFilterBar = 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],
|
mixins: [PureRenderMixin],
|
||||||
getInitialState() {
|
getInitialState() {
|
||||||
return _.extend({}, this.props.selectedLogs, {
|
return _.extend({}, this.props.selectedLogs, {
|
||||||
|
@ -150,9 +153,13 @@ var LogFilterBar = React.createClass({
|
||||||
:
|
:
|
||||||
this.sources.fetch();
|
this.sources.fetch();
|
||||||
this.sources.deferred.done(() => {
|
this.sources.deferred.done(() => {
|
||||||
var filteredSources = this.sources.filter((source) => source.get('remote') == (type != 'local'));
|
var filteredSources = this.sources.filter((source) => {
|
||||||
var chosenSource = _.findWhere(filteredSources, {id: this.state.source}) || _.first(filteredSources);
|
return source.get('remote') == (type != 'local');
|
||||||
var chosenLevelId = chosenSource ? _.contains(chosenSource.get('levels'), this.state.level) ? this.state.level : _.first(chosenSource.get('levels')) : null;
|
});
|
||||||
|
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({
|
this.setState({
|
||||||
type: type,
|
type: type,
|
||||||
sources: this.sources,
|
sources: this.sources,
|
||||||
|
@ -168,7 +175,8 @@ var LogFilterBar = React.createClass({
|
||||||
type: type,
|
type: type,
|
||||||
sources: {},
|
sources: {},
|
||||||
sourcesLoadingState: 'fail',
|
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
|
locked: false
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -310,7 +318,8 @@ var LogFilterBar = React.createClass({
|
||||||
</div>;
|
</div>;
|
||||||
},
|
},
|
||||||
renderSourceSelect() {
|
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'>
|
return <div className='col-md-2 col-sm-3'>
|
||||||
<Input
|
<Input
|
||||||
type='select'
|
type='select'
|
||||||
|
@ -397,7 +406,13 @@ var LogsTable = React.createClass({
|
||||||
<span>{i18n('cluster_page.logs_tab.bottom_text')}</span>:
|
<span>{i18n('cluster_page.logs_tab.bottom_text')}</span>:
|
||||||
{
|
{
|
||||||
[100, 500, 1000, 5000].map((count) => {
|
[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>
|
</div>
|
||||||
|
|
|
@ -30,7 +30,10 @@ import CSSTransitionGroup from 'react-addons-transition-group';
|
||||||
|
|
||||||
var parametersNS = 'cluster_page.network_tab.networking_parameters.';
|
var parametersNS = 'cluster_page.network_tab.networking_parameters.';
|
||||||
var networkTabNS = 'cluster_page.network_tab.';
|
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 = {
|
var NetworkModelManipulationMixin = {
|
||||||
setValue(attribute, value, options) {
|
setValue(attribute, value, options) {
|
||||||
|
@ -98,7 +101,8 @@ var NetworkInputsMixin = {
|
||||||
var error;
|
var error;
|
||||||
if (this.props.network) {
|
if (this.props.network) {
|
||||||
try {
|
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) {}
|
} catch (e) {}
|
||||||
return error || null;
|
return error || null;
|
||||||
}
|
}
|
||||||
|
@ -139,7 +143,11 @@ var Range = React.createClass({
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
// this glitch is needed to fix
|
// this glitch is needed to fix
|
||||||
// when pressing '+' or '-' buttons button remains focused
|
// 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.refs[this.state.elementToFocus].getInputDOMNode()).focus();
|
||||||
this.setState({elementToFocus: null});
|
this.setState({elementToFocus: null});
|
||||||
}
|
}
|
||||||
|
@ -244,7 +252,10 @@ var Range = React.createClass({
|
||||||
onFocus={_.partial(this.autoCompleteIPRange, rangeError && rangeError.start, range[0])}
|
onFocus={_.partial(this.autoCompleteIPRange, rangeError && rangeError.start, range[0])}
|
||||||
disabled={this.props.disabled || !!this.props.autoIncreaseWith}
|
disabled={this.props.disabled || !!this.props.autoIncreaseWith}
|
||||||
placeholder={rangeError.end ? '' : this.props.placeholder}
|
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'>
|
<div className='validation-error text-danger pull-left'>
|
||||||
<span className='help-inline'>
|
<span className='help-inline'>
|
||||||
|
@ -306,7 +317,8 @@ var Range = React.createClass({
|
||||||
<div className='col-xs-12'>
|
<div className='col-xs-12'>
|
||||||
<label>{this.props.label}</label>
|
<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.props.extendable ?
|
||||||
this.renderExtendableRanges({error, attributeName, ranges, verificationError})
|
this.renderExtendableRanges({error, attributeName, ranges, verificationError})
|
||||||
:
|
:
|
||||||
|
@ -546,7 +558,8 @@ var NetworkTab = React.createClass({
|
||||||
configModels: {
|
configModels: {
|
||||||
cluster: this.props.cluster,
|
cluster: this.props.cluster,
|
||||||
settings: settings,
|
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,
|
version: app.version,
|
||||||
release: this.props.cluster.get('release'),
|
release: this.props.cluster.get('release'),
|
||||||
default: settings
|
default: settings
|
||||||
|
@ -560,7 +573,11 @@ var NetworkTab = React.createClass({
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.cluster.get('networkConfiguration').isValid();
|
this.props.cluster.get('networkConfiguration').isValid();
|
||||||
this.props.cluster.get('settings').isValid({models: this.state.configModels});
|
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() {
|
componentWillUnmount() {
|
||||||
this.loadInitialConfiguration();
|
this.loadInitialConfiguration();
|
||||||
|
@ -583,10 +600,16 @@ var NetworkTab = React.createClass({
|
||||||
clusterTasks.each((task) => task.get('unsaved') && clusterTasks.remove(task));
|
clusterTasks.each((task) => task.get('unsaved') && clusterTasks.remove(task));
|
||||||
},
|
},
|
||||||
isNetworkConfigurationChanged() {
|
isNetworkConfigurationChanged() {
|
||||||
return !_.isEqual(this.state.initialConfiguration, this.props.cluster.get('networkConfiguration').toJSON());
|
return !_.isEqual(
|
||||||
|
this.state.initialConfiguration,
|
||||||
|
this.props.cluster.get('networkConfiguration').toJSON()
|
||||||
|
);
|
||||||
},
|
},
|
||||||
isNetworkSettingsChanged() {
|
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() {
|
hasChanges() {
|
||||||
return this.isNetworkConfigurationChanged() || this.isNetworkSettingsChanged();
|
return this.isNetworkConfigurationChanged() || this.isNetworkSettingsChanged();
|
||||||
|
@ -602,17 +625,26 @@ var NetworkTab = React.createClass({
|
||||||
},
|
},
|
||||||
loadInitialConfiguration() {
|
loadInitialConfiguration() {
|
||||||
var networkConfiguration = this.props.cluster.get('networkConfiguration');
|
var networkConfiguration = this.props.cluster.get('networkConfiguration');
|
||||||
networkConfiguration.get('networks').reset(_.cloneDeep(this.state.initialConfiguration.networks));
|
networkConfiguration.get('networks').reset(
|
||||||
networkConfiguration.get('networking_parameters').set(_.cloneDeep(this.state.initialConfiguration.networking_parameters));
|
_.cloneDeep(this.state.initialConfiguration.networks)
|
||||||
|
);
|
||||||
|
networkConfiguration.get('networking_parameters').set(
|
||||||
|
_.cloneDeep(this.state.initialConfiguration.networking_parameters)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
loadInitialSettings() {
|
loadInitialSettings() {
|
||||||
var settings = this.props.cluster.get('settings');
|
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.mergePluginSettings();
|
||||||
settings.isValid({models: this.state.configModels});
|
settings.isValid({models: this.state.configModels});
|
||||||
},
|
},
|
||||||
updateInitialConfiguration() {
|
updateInitialConfiguration() {
|
||||||
this.setState({initialConfiguration: _.cloneDeep(this.props.cluster.get('networkConfiguration').toJSON())});
|
this.setState({
|
||||||
|
initialConfiguration: _.cloneDeep(this.props.cluster.get('networkConfiguration').toJSON())
|
||||||
|
});
|
||||||
},
|
},
|
||||||
isLocked() {
|
isLocked() {
|
||||||
return !!this.props.cluster.task({group: ['deployment', 'network'], active: true}) ||
|
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');
|
var floatingRanges = networkConfiguration.get('networking_parameters').get('floating_ranges');
|
||||||
if (floatingRanges) {
|
if (floatingRanges) {
|
||||||
networkConfiguration.get('networking_parameters').set({floating_ranges: removeEmptyRanges(floatingRanges)});
|
networkConfiguration.get('networking_parameters').set({
|
||||||
|
floating_ranges: removeEmptyRanges(floatingRanges)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onManagerChange(name, value) {
|
onManagerChange(name, value) {
|
||||||
var networkConfiguration = this.props.cluster.get('networkConfiguration');
|
var networkConfiguration = this.props.cluster.get('networkConfiguration');
|
||||||
var networkingParameters = networkConfiguration.get('networking_parameters');
|
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({
|
networkingParameters.set({
|
||||||
net_manager: value,
|
net_manager: value,
|
||||||
fixed_networks_amount: value == 'FlatDHCPManager' ? 1 : fixedAmount
|
fixed_networks_amount: value == 'FlatDHCPManager' ? 1 : fixedAmount
|
||||||
|
@ -770,7 +805,8 @@ var NetworkTab = React.createClass({
|
||||||
_.isNull(this.props.cluster.get('settings').validationError);
|
_.isNull(this.props.cluster.get('settings').validationError);
|
||||||
},
|
},
|
||||||
renderButtons() {
|
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 (
|
return (
|
||||||
<div className='well clearfix'>
|
<div className='well clearfix'>
|
||||||
<div className='btn-group pull-right'>
|
<div className='btn-group pull-right'>
|
||||||
|
@ -795,7 +831,8 @@ var NetworkTab = React.createClass({
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
getVerificationErrors() {
|
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 = [];
|
var fieldsWithVerificationErrors = [];
|
||||||
// @TODO(morale): soon response format will be changed and this part should be rewritten
|
// @TODO(morale): soon response format will be changed and this part should be rewritten
|
||||||
if (task && task.get('result').length) {
|
if (task && task.get('result').length) {
|
||||||
|
@ -816,7 +853,9 @@ var NetworkTab = React.createClass({
|
||||||
showUnsavedChangesWarning: this.hasChanges()
|
showUnsavedChangesWarning: this.hasChanges()
|
||||||
})
|
})
|
||||||
.done(() => {
|
.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
|
return nodeNetworkGroup
|
||||||
.destroy({wait: true})
|
.destroy({wait: true})
|
||||||
.then(
|
.then(
|
||||||
|
@ -833,7 +872,11 @@ var NetworkTab = React.createClass({
|
||||||
if (hasChanges) {
|
if (hasChanges) {
|
||||||
utils.showErrorDialog({
|
utils.showErrorDialog({
|
||||||
title: i18n(networkTabNS + 'node_network_group_creation_error'),
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -879,7 +922,8 @@ var NetworkTab = React.createClass({
|
||||||
row: true,
|
row: true,
|
||||||
'changes-locked': isLocked
|
'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 isNovaEnvironment = cluster.get('net_provider') == 'nova_network';
|
||||||
var networks = networkConfiguration.get('networks');
|
var networks = networkConfiguration.get('networks');
|
||||||
var isMultiRack = nodeNetworkGroups.length > 1;
|
var isMultiRack = nodeNetworkGroups.length > 1;
|
||||||
|
@ -922,7 +966,10 @@ var NetworkTab = React.createClass({
|
||||||
key='add_node_group'
|
key='add_node_group'
|
||||||
className='btn btn-default add-nodegroup-btn pull-right'
|
className='btn btn-default add-nodegroup-btn pull-right'
|
||||||
onClick={_.partial(this.addNodeNetworkGroup, hasChanges)}
|
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'/>}
|
{hasChanges && <i className='glyphicon glyphicon-danger-sign'/>}
|
||||||
{i18n(networkTabNS + 'add_node_network_group')}
|
{i18n(networkTabNS + 'add_node_network_group')}
|
||||||
|
@ -1008,7 +1055,8 @@ var NetworkTab = React.createClass({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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='col-xs-12'>
|
||||||
<div className='alert alert-danger enable-selection col-xs-12 network-alert'>
|
<div className='alert alert-danger enable-selection col-xs-12 network-alert'>
|
||||||
{utils.renderMultilineText(networkCheckTask.get('message'))}
|
{utils.renderMultilineText(networkCheckTask.get('message'))}
|
||||||
|
@ -1025,7 +1073,10 @@ var NetworkTab = React.createClass({
|
||||||
|
|
||||||
var NodeNetworkGroup = React.createClass({
|
var NodeNetworkGroup = React.createClass({
|
||||||
render() {
|
render() {
|
||||||
var {cluster, networks, nodeNetworkGroup, nodeNetworkGroups, verificationErrors, validationError} = this.props;
|
var {
|
||||||
|
cluster, networks, nodeNetworkGroup, nodeNetworkGroups,
|
||||||
|
verificationErrors, validationError
|
||||||
|
} = this.props;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<NodeNetworkGroupTitle
|
<NodeNetworkGroupTitle
|
||||||
|
@ -1044,7 +1095,9 @@ var NodeNetworkGroup = React.createClass({
|
||||||
cluster={cluster}
|
cluster={cluster}
|
||||||
validationError={(validationError || {}).networks}
|
validationError={(validationError || {}).networks}
|
||||||
disabled={this.props.locked}
|
disabled={this.props.locked}
|
||||||
verificationErrorField={_.pluck(_.where(verificationErrors, {network: network.id}), 'field')}
|
verificationErrorField={
|
||||||
|
_.pluck(_.where(verificationErrors, {network: network.id}), 'field')
|
||||||
|
}
|
||||||
currentNodeNetworkGroup={nodeNetworkGroup}
|
currentNodeNetworkGroup={nodeNetworkGroup}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -1069,11 +1122,20 @@ var NetworkSubtabs = React.createClass({
|
||||||
|
|
||||||
// is one of predefined sections selected (networking_parameters)
|
// is one of predefined sections selected (networking_parameters)
|
||||||
if (groupName == 'neutron_l2') {
|
if (groupName == 'neutron_l2') {
|
||||||
isInvalid = !!_.intersection(NetworkingL2Parameters.renderedParameters, _.keys(networkParametersErrors)).length;
|
isInvalid = !!_.intersection(
|
||||||
|
NetworkingL2Parameters.renderedParameters,
|
||||||
|
_.keys(networkParametersErrors)
|
||||||
|
).length;
|
||||||
} else if (groupName == 'neutron_l3') {
|
} else if (groupName == 'neutron_l3') {
|
||||||
isInvalid = !!_.intersection(NetworkingL3Parameters.renderedParameters, _.keys(networkParametersErrors)).length;
|
isInvalid = !!_.intersection(
|
||||||
|
NetworkingL3Parameters.renderedParameters,
|
||||||
|
_.keys(networkParametersErrors)
|
||||||
|
).length;
|
||||||
} else if (groupName == 'nova_configuration') {
|
} else if (groupName == 'nova_configuration') {
|
||||||
isInvalid = !!_.intersection(NovaParameters.renderedParameters, _.keys(networkParametersErrors)).length;
|
isInvalid = !!_.intersection(
|
||||||
|
NovaParameters.renderedParameters,
|
||||||
|
_.keys(networkParametersErrors)
|
||||||
|
).length;
|
||||||
} else if (groupName == 'network_settings') {
|
} else if (groupName == 'network_settings') {
|
||||||
var settings = cluster.get('settings');
|
var settings = cluster.get('settings');
|
||||||
isInvalid = _.any(_.keys(settings.validationError), (settingPath) => {
|
isInvalid = _.any(_.keys(settings.validationError), (settingPath) => {
|
||||||
|
@ -1084,7 +1146,8 @@ var NetworkSubtabs = React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNetworkGroupPill) {
|
if (isNetworkGroupPill) {
|
||||||
isInvalid = networksErrors && (isNovaEnvironment || !!networksErrors[nodeNetworkGroups.findWhere({name: groupName}).id]);
|
isInvalid = networksErrors && (isNovaEnvironment ||
|
||||||
|
!!networksErrors[nodeNetworkGroups.findWhere({name: groupName}).id]);
|
||||||
} else {
|
} else {
|
||||||
tabLabel = i18n(networkTabNS + 'tabs.' + groupName);
|
tabLabel = i18n(networkTabNS + 'tabs.' + groupName);
|
||||||
}
|
}
|
||||||
|
@ -1233,10 +1296,15 @@ var NodeNetworkGroupTitle = React.createClass({
|
||||||
}
|
}
|
||||||
{isDeletionPossible && (
|
{isDeletionPossible && (
|
||||||
currentNodeNetworkGroup.get('is_default') ?
|
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 &&
|
!this.state.isRenaming &&
|
||||||
<i className='glyphicon glyphicon-remove' onClick={this.props.removeNodeNetworkGroup} />
|
<i
|
||||||
|
className='glyphicon glyphicon-remove'
|
||||||
|
onClick={this.props.removeNodeNetworkGroup}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1339,7 +1407,9 @@ var NovaParameters = React.createClass({
|
||||||
wrapperClassName='clearfix vlan-id-range'
|
wrapperClassName='clearfix vlan-id-range'
|
||||||
label={i18n(parametersNS + 'fixed_vlan_range')}
|
label={i18n(parametersNS + 'fixed_vlan_range')}
|
||||||
extendable={false}
|
extendable={false}
|
||||||
autoIncreaseWith={parseInt(networkingParameters.get('fixed_networks_amount'), 10) || 0}
|
autoIncreaseWith={
|
||||||
|
parseInt(networkingParameters.get('fixed_networks_amount'), 10) || 0
|
||||||
|
}
|
||||||
integerValue
|
integerValue
|
||||||
placeholder=''
|
placeholder=''
|
||||||
mini
|
mini
|
||||||
|
@ -1368,12 +1438,18 @@ var NetworkingL2Parameters = React.createClass({
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
render() {
|
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';
|
var idRangePrefix = networkParameters.get('segmentation_type') == 'vlan' ? 'vlan' : 'gre_id';
|
||||||
return (
|
return (
|
||||||
<div className='forms-box' key='neutron-l2'>
|
<div className='forms-box' key='neutron-l2'>
|
||||||
<h3 className='networks'>{i18n(parametersNS + 'l2_configuration')}</h3>
|
<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>
|
<div>
|
||||||
<Range
|
<Range
|
||||||
{...this.composeProps(idRangePrefix + '_range', true)}
|
{...this.composeProps(idRangePrefix + '_range', true)}
|
||||||
|
@ -1429,9 +1505,13 @@ var NetworkingL3Parameters = React.createClass({
|
||||||
{networks.findWhere({name: 'baremetal'}) &&
|
{networks.findWhere({name: 'baremetal'}) &&
|
||||||
<div className='forms-box' key='baremetal-net'>
|
<div className='forms-box' key='baremetal-net'>
|
||||||
<h3>
|
<h3>
|
||||||
<span className='subtab-group-baremetal-net'>{i18n(networkTabNS + 'baremetal_net')}</span>
|
<span className='subtab-group-baremetal-net'>
|
||||||
|
{i18n(networkTabNS + 'baremetal_net')}
|
||||||
|
</span>
|
||||||
</h3>
|
</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
|
<Range
|
||||||
key='baremetal_range'
|
key='baremetal_range'
|
||||||
{...this.composeProps('baremetal_range', true)}
|
{...this.composeProps('baremetal_range', true)}
|
||||||
|
@ -1443,9 +1523,13 @@ var NetworkingL3Parameters = React.createClass({
|
||||||
}
|
}
|
||||||
<div className='forms-box' key='dns-nameservers'>
|
<div className='forms-box' key='dns-nameservers'>
|
||||||
<h3>
|
<h3>
|
||||||
<span className='subtab-group-dns-nameservers'>{i18n(networkTabNS + 'dns_nameservers')}</span>
|
<span className='subtab-group-dns-nameservers'>
|
||||||
|
{i18n(networkTabNS + 'dns_nameservers')}
|
||||||
|
</span>
|
||||||
</h3>
|
</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)} />
|
<MultipleValuesInput {...this.composeProps('dns_nameservers', true)} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1465,14 +1549,17 @@ var NetworkSettings = React.createClass({
|
||||||
settings.isValid({models: this.props.configModels});
|
settings.isValid({models: this.props.configModels});
|
||||||
},
|
},
|
||||||
checkRestrictions(action, setting) {
|
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() {
|
render() {
|
||||||
var cluster = this.props.cluster;
|
var cluster = this.props.cluster;
|
||||||
var settings = cluster.get('settings');
|
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 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 (
|
return (
|
||||||
<div className='forms-box network'>
|
<div className='forms-box network'>
|
||||||
{
|
{
|
||||||
|
@ -1481,7 +1568,8 @@ var NetworkSettings = React.createClass({
|
||||||
.filter(
|
.filter(
|
||||||
(sectionName) => {
|
(sectionName) => {
|
||||||
var section = settings.get(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;
|
!this.checkRestrictions('hide', section.metadata).result;
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -1501,7 +1589,10 @@ var NetworkSettings = React.createClass({
|
||||||
}));
|
}));
|
||||||
if (_.isEmpty(settingsToDisplay) && !settings.isPlugin(section)) return null;
|
if (_.isEmpty(settingsToDisplay) && !settings.isPlugin(section)) return null;
|
||||||
return <SettingSection
|
return <SettingSection
|
||||||
{... _.pick(this.props, 'cluster', 'initialAttributes', 'settingsForChecks', 'configModels')}
|
{... _.pick(
|
||||||
|
this.props,
|
||||||
|
'cluster', 'initialAttributes', 'settingsForChecks', 'configModels'
|
||||||
|
)}
|
||||||
key={sectionName}
|
key={sectionName}
|
||||||
sectionName={sectionName}
|
sectionName={sectionName}
|
||||||
settingsToDisplay={settingsToDisplay}
|
settingsToDisplay={settingsToDisplay}
|
||||||
|
@ -1558,7 +1649,11 @@ var NetworkVerificationResult = React.createClass({
|
||||||
<div className='animation-box'>
|
<div className='animation-box'>
|
||||||
{_.times(3, (index) => {
|
{_.times(3, (index) => {
|
||||||
++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>
|
||||||
<div className='nodes-box'>
|
<div className='nodes-box'>
|
||||||
|
@ -1620,7 +1715,12 @@ var NetworkVerificationResult = React.createClass({
|
||||||
var absentVlans = _.map(node.absent_vlans, (vlan) => {
|
var absentVlans = _.map(node.absent_vlans, (vlan) => {
|
||||||
return vlan || i18n(networkTabNS + 'untagged');
|
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 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 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 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';
|
import ReactTransitionGroup from 'react-addons-transition-group';
|
||||||
|
|
||||||
var NodesTab = React.createClass({
|
var NodesTab = React.createClass({
|
||||||
|
@ -65,7 +66,10 @@ var NodesTab = React.createClass({
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.fail(() => {
|
.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) {
|
getScreen(props) {
|
||||||
|
@ -111,7 +115,10 @@ var NodesTab = React.createClass({
|
||||||
>
|
>
|
||||||
<Screen
|
<Screen
|
||||||
{...this.state.screenData}
|
{...this.state.screenData}
|
||||||
{... _.pick(this.props, 'cluster', 'nodeNetworkGroups', 'selectedNodeIds', 'selectNodes')}
|
{..._.pick(
|
||||||
|
this.props,
|
||||||
|
'cluster', 'nodeNetworkGroups', 'selectedNodeIds', 'selectNodes'
|
||||||
|
)}
|
||||||
ref='screen'
|
ref='screen'
|
||||||
screenOptions={this.state.screenOptions}
|
screenOptions={this.state.screenOptions}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -24,7 +24,8 @@ var AddNodesScreen = React.createClass({
|
||||||
fetchData(options) {
|
fetchData(options) {
|
||||||
var nodes = new models.Nodes();
|
var nodes = new models.Nodes();
|
||||||
nodes.fetch = function(options) {
|
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(),
|
return $.when(nodes.fetch(), options.cluster.get('roles').fetch(),
|
||||||
options.cluster.get('settings').fetch({cache: true})).then(() => ({nodes: nodes}));
|
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())});
|
this.setState({initialDisks: _.cloneDeep(this.props.nodes.at(0).disks.toJSON())});
|
||||||
},
|
},
|
||||||
hasChanges() {
|
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() {
|
loadDefaults() {
|
||||||
this.setState({actionInProgress: true});
|
this.setState({actionInProgress: true});
|
||||||
|
@ -168,7 +171,13 @@ var EditNodeDisksScreen = React.createClass({
|
||||||
<div className='edit-node-disks-screen'>
|
<div className='edit-node-disks-screen'>
|
||||||
<div className='row'>
|
<div className='row'>
|
||||||
<div className='title'>
|
<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>
|
||||||
<div className='col-xs-12 node-disks'>
|
<div className='col-xs-12 node-disks'>
|
||||||
{this.props.disks.length ?
|
{this.props.disks.length ?
|
||||||
|
@ -184,22 +193,44 @@ var EditNodeDisksScreen = React.createClass({
|
||||||
})
|
})
|
||||||
:
|
:
|
||||||
<div className='alert alert-warning'>
|
<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>
|
</div>
|
||||||
<div className='col-xs-12 page-buttons content-elements'>
|
<div className='col-xs-12 page-buttons content-elements'>
|
||||||
<div className='well clearfix'>
|
<div className='well clearfix'>
|
||||||
<div className='btn-group'>
|
<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')}
|
{i18n('cluster_page.nodes_tab.back_to_nodes_button')}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{!locked && !!this.props.disks.length &&
|
{!locked && !!this.props.disks.length &&
|
||||||
<div className='btn-group pull-right'>
|
<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
|
||||||
<button className='btn btn-default btn-revert-changes' onClick={this.revertChanges} disabled={revertChangesDisabled}>{i18n('common.cancel_changes_button')}</button>
|
className='btn btn-default btn-defaults'
|
||||||
<button className='btn btn-success btn-apply' onClick={this.applyChanges} disabled={!this.isSavingPossible()}>{i18n('common.apply_button')}</button>
|
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>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@ -225,7 +256,8 @@ var NodeDisk = React.createClass({
|
||||||
if (size > volumeInfo.max) {
|
if (size > volumeInfo.max) {
|
||||||
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);
|
this.props.disk.trigger('change', this.props.disk);
|
||||||
},
|
},
|
||||||
toggleDisk(name) {
|
toggleDisk(name) {
|
||||||
|
@ -236,7 +268,8 @@ var NodeDisk = React.createClass({
|
||||||
var volumesInfo = this.props.volumesInfo;
|
var volumesInfo = this.props.volumesInfo;
|
||||||
var diskMetaData = this.props.diskMetaData;
|
var diskMetaData = this.props.diskMetaData;
|
||||||
var requiredDiskSize = _.sum(disk.get('volumes').map((volume) => {
|
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 diskError = disk.get('size') < requiredDiskSize;
|
||||||
var sortOrder = ['name', 'model', 'size'];
|
var sortOrder = ['name', 'model', 'size'];
|
||||||
|
@ -263,26 +296,44 @@ var NodeDisk = React.createClass({
|
||||||
data-volume={volumeName}
|
data-volume={volumeName}
|
||||||
style={{width: volumesInfo[volumeName].width + '%'}}
|
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>{volume.get('label')}</div>
|
||||||
<div className='volume-group-size'>
|
<div className='volume-group-size'>
|
||||||
{utils.showDiskSize(volumesInfo[volumeName].size, 2)}
|
{utils.showDiskSize(volumesInfo[volumeName].size, 2)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{!this.props.disabled && volumesInfo[volumeName].min <= 0 && this.state.collapsed &&
|
{!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>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
<div className='volume-group pull-left' data-volume='unallocated' style={{width: volumesInfo.unallocated.width + '%'}}>
|
<div
|
||||||
<div className='text-center toggle' onClick={_.partial(this.toggleDisk, disk.get('name'))}>
|
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-name'>{i18n(ns + 'unallocated')}</div>
|
||||||
<div className='volume-group-size'>{utils.showDiskSize(volumesInfo.unallocated.size, 2)}</div>
|
<div className='volume-group-size'>
|
||||||
|
{utils.showDiskSize(volumesInfo.unallocated.size, 2)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='row collapse disk-details' id={disk.get('name')} key='diskDetails' ref={disk.get('name')}>
|
<div className='row collapse disk-details' id={disk.get('name')} key='diskDetails'
|
||||||
|
ref={disk.get('name')}>
|
||||||
<div className='col-xs-5'>
|
<div className='col-xs-5'>
|
||||||
{diskMetaData &&
|
{diskMetaData &&
|
||||||
<div>
|
<div>
|
||||||
|
@ -294,7 +345,10 @@ var NodeDisk = React.createClass({
|
||||||
<label className='col-xs-2'>{propertyName.replace(/_/g, ' ')}</label>
|
<label className='col-xs-2'>{propertyName.replace(/_/g, ' ')}</label>
|
||||||
<div className='col-xs-10'>
|
<div className='col-xs-10'>
|
||||||
<p className='form-control-static'>
|
<p className='form-control-static'>
|
||||||
{propertyName == 'size' ? utils.showDiskSize(diskMetaData[propertyName]) : diskMetaData[propertyName]}
|
{propertyName == 'size' ?
|
||||||
|
utils.showDiskSize(diskMetaData[propertyName]) :
|
||||||
|
diskMetaData[propertyName]
|
||||||
|
}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -325,7 +379,12 @@ var NodeDisk = React.createClass({
|
||||||
<div key={'edit_' + volumeName} data-volume={volumeName}>
|
<div key={'edit_' + volumeName} data-volume={volumeName}>
|
||||||
<div className='form-group volume-group row'>
|
<div className='form-group volume-group row'>
|
||||||
<label className='col-xs-4 volume-group-label'>
|
<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')}
|
{volume.get('label')}
|
||||||
</label>
|
</label>
|
||||||
<div className='col-xs-4 volume-group-range'>
|
<div className='col-xs-4 volume-group-range'>
|
||||||
|
@ -343,10 +402,14 @@ var NodeDisk = React.createClass({
|
||||||
error={validationError && ''}
|
error={validationError && ''}
|
||||||
value={value}
|
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>
|
</div>
|
||||||
{!!value && value == currentMinSize &&
|
{!!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 &&
|
{validationError &&
|
||||||
<div className='volume-group-notice text-danger'>{validationError}</div>
|
<div className='volume-group-notice text-danger'>{validationError}</div>
|
||||||
|
@ -355,7 +418,9 @@ var NodeDisk = React.createClass({
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{diskError &&
|
{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>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -92,7 +92,10 @@ var EditNodeInterfacesScreen = React.createClass({
|
||||||
},
|
},
|
||||||
interfacesPickFromJSON(json) {
|
interfacesPickFromJSON(json) {
|
||||||
// Pick certain interface fields that have influence on hasChanges.
|
// 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) {
|
interfacesToJSON(interfaces, remainingNodesMode) {
|
||||||
// Sometimes 'state' is sent from the API and sometimes not
|
// Sometimes 'state' is sent from the API and sometimes not
|
||||||
|
@ -179,7 +182,9 @@ var EditNodeInterfacesScreen = React.createClass({
|
||||||
node.interfaces.each((ifc, index) => {
|
node.interfaces.each((ifc, index) => {
|
||||||
var updatedIfc = ifc.isBond() ? bondsByName[ifc.get('name')] : interfaces.at(index);
|
var updatedIfc = ifc.isBond() ? bondsByName[ifc.get('name')] : interfaces.at(index);
|
||||||
ifc.set({
|
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')
|
interface_properties: updatedIfc.get('interface_properties')
|
||||||
});
|
});
|
||||||
if (ifc.isBond()) {
|
if (ifc.isBond()) {
|
||||||
|
@ -197,7 +202,8 @@ var EditNodeInterfacesScreen = React.createClass({
|
||||||
return Backbone.sync('update', node.interfaces, {url: _.result(node, 'url') + '/interfaces'});
|
return Backbone.sync('update', node.interfaces, {url: _.result(node, 'url') + '/interfaces'});
|
||||||
}))
|
}))
|
||||||
.done(() => {
|
.done(() => {
|
||||||
this.setState({initialInterfaces: _.cloneDeep(this.interfacesToJSON(this.props.interfaces))});
|
this.setState({initialInterfaces:
|
||||||
|
_.cloneDeep(this.interfacesToJSON(this.props.interfaces))});
|
||||||
dispatcher.trigger('networkConfigurationUpdated');
|
dispatcher.trigger('networkConfigurationUpdated');
|
||||||
})
|
})
|
||||||
.fail((response) => {
|
.fail((response) => {
|
||||||
|
@ -213,19 +219,21 @@ var EditNodeInterfacesScreen = React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
configurationTemplateExists() {
|
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() {
|
bondingAvailable() {
|
||||||
var availableBondTypes = this.getBondType();
|
var availableBondTypes = this.getBondType();
|
||||||
return !!availableBondTypes && !this.configurationTemplateExists();
|
return !!availableBondTypes && !this.configurationTemplateExists();
|
||||||
},
|
},
|
||||||
getBondType() {
|
getBondType() {
|
||||||
return _.compact(_.flatten(_.map(this.props.bondingConfig.availability, (modeAvailabilityData) => {
|
return _.compact(_.flatten(_.map(this.props.bondingConfig.availability,
|
||||||
return _.map(modeAvailabilityData, (condition, name) => {
|
(modeAvailabilityData) => {
|
||||||
var result = utils.evaluateExpression(condition, this.props.configModels).value;
|
return _.map(modeAvailabilityData, (condition, name) => {
|
||||||
return result && name;
|
var result = utils.evaluateExpression(condition, this.props.configModels).value;
|
||||||
});
|
return result && name;
|
||||||
})))[0];
|
});
|
||||||
|
})))[0];
|
||||||
},
|
},
|
||||||
findOffloadingModesIntersection(set1, set2) {
|
findOffloadingModesIntersection(set1, set2) {
|
||||||
return _.map(
|
return _.map(
|
||||||
|
@ -248,7 +256,9 @@ var EditNodeInterfacesScreen = React.createClass({
|
||||||
var offloadingModes = interfaces.map((ifc) => ifc.get('offloading_modes') || []);
|
var offloadingModes = interfaces.map((ifc) => ifc.get('offloading_modes') || []);
|
||||||
if (!offloadingModes.length) return [];
|
if (!offloadingModes.length) return [];
|
||||||
|
|
||||||
return offloadingModes.reduce((result, modes) => this.findOffloadingModesIntersection(result, modes));
|
return offloadingModes.reduce((result, modes) => {
|
||||||
|
return this.findOffloadingModesIntersection(result, modes);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
bondInterfaces() {
|
bondInterfaces() {
|
||||||
this.setState({actionInProgress: true});
|
this.setState({actionInProgress: true});
|
||||||
|
@ -261,7 +271,8 @@ var EditNodeInterfacesScreen = React.createClass({
|
||||||
var bondMode = _.flatten(_.pluck(bondingProperties[this.getBondType()].mode, 'values'))[0];
|
var bondMode = _.flatten(_.pluck(bondingProperties[this.getBondType()].mode, 'values'))[0];
|
||||||
bonds = new models.Interface({
|
bonds = new models.Interface({
|
||||||
type: 'bond',
|
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,
|
mode: bondMode,
|
||||||
assigned_networks: new models.InterfaceNetworks(),
|
assigned_networks: new models.InterfaceNetworks(),
|
||||||
slaves: _.invoke(interfaces, 'pick', 'name'),
|
slaves: _.invoke(interfaces, 'pick', 'name'),
|
||||||
|
@ -293,7 +304,9 @@ var EditNodeInterfacesScreen = React.createClass({
|
||||||
},
|
},
|
||||||
unbondInterfaces() {
|
unbondInterfaces() {
|
||||||
this.setState({actionInProgress: true});
|
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});
|
this.setState({actionInProgress: false});
|
||||||
},
|
},
|
||||||
removeInterfaceFromBond(bondName, slaveInterfaceName) {
|
removeInterfaceFromBond(bondName, slaveInterfaceName) {
|
||||||
|
@ -320,7 +333,9 @@ var EditNodeInterfacesScreen = React.createClass({
|
||||||
if (slaveInterfaceName) {
|
if (slaveInterfaceName) {
|
||||||
var slavesUpdated = _.reject(slaves, {name: slaveInterfaceName});
|
var slavesUpdated = _.reject(slaves, {name: slaveInterfaceName});
|
||||||
var names = _.pluck(slavesUpdated, 'name');
|
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({
|
bond.set({
|
||||||
slaves: slavesUpdated,
|
slaves: slavesUpdated,
|
||||||
|
@ -377,7 +392,8 @@ var EditNodeInterfacesScreen = React.createClass({
|
||||||
return _.uniq(speeds).length > 1 || !_.compact(speeds).length;
|
return _.uniq(speeds).length > 1 || !_.compact(speeds).length;
|
||||||
},
|
},
|
||||||
isSavingPossible() {
|
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) {
|
getIfcProperty(property) {
|
||||||
var {interfaces, nodes} = this.props;
|
var {interfaces, nodes} = this.props;
|
||||||
|
@ -416,20 +432,25 @@ var EditNodeInterfacesScreen = React.createClass({
|
||||||
var slaveInterfaceNames = _.pluck(_.flatten(_.filter(interfaces.pluck('slaves'))), 'name');
|
var slaveInterfaceNames = _.pluck(_.flatten(_.filter(interfaces.pluck('slaves'))), 'name');
|
||||||
var loadDefaultsEnabled = !this.state.actionInProgress;
|
var loadDefaultsEnabled = !this.state.actionInProgress;
|
||||||
var revertChangesEnabled = !this.state.actionInProgress && hasChanges;
|
var revertChangesEnabled = !this.state.actionInProgress && hasChanges;
|
||||||
var invalidSpeedsForBonding = bondingPossible && this.validateSpeedsForBonding(checkedBonds.concat(checkedInterfaces)) || interfaces.any((ifc) => {
|
var invalidSpeedsForBonding = bondingPossible &&
|
||||||
return ifc.isBond() && this.validateSpeedsForBonding([ifc]);
|
this.validateSpeedsForBonding(checkedBonds.concat(checkedInterfaces)) ||
|
||||||
});
|
interfaces.any((ifc) => {
|
||||||
|
return ifc.isBond() && this.validateSpeedsForBonding([ifc]);
|
||||||
|
});
|
||||||
|
|
||||||
var interfaceSpeeds = this.getIfcProperty('current_speed');
|
var interfaceSpeeds = this.getIfcProperty('current_speed');
|
||||||
var interfaceNames = this.getIfcProperty('name');
|
var interfaceNames = this.getIfcProperty('name');
|
||||||
return (
|
return (
|
||||||
<div className='row'>
|
<div className='row'>
|
||||||
<div className='title'>
|
<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>
|
</div>
|
||||||
{configurationTemplateExists &&
|
{configurationTemplateExists &&
|
||||||
<div className='col-xs-12'>
|
<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>
|
</div>
|
||||||
}
|
}
|
||||||
{bondingAvailable && !locked &&
|
{bondingAvailable && !locked &&
|
||||||
|
@ -437,10 +458,18 @@ var EditNodeInterfacesScreen = React.createClass({
|
||||||
<div className='page-buttons'>
|
<div className='page-buttons'>
|
||||||
<div className='well clearfix'>
|
<div className='well clearfix'>
|
||||||
<div className='btn-group pull-right'>
|
<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')}
|
{i18n(ns + 'bond_button')}
|
||||||
</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')}
|
{i18n(ns + 'unbond_button')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -478,19 +507,35 @@ var EditNodeInterfacesScreen = React.createClass({
|
||||||
<div className='col-xs-12 page-buttons content-elements'>
|
<div className='col-xs-12 page-buttons content-elements'>
|
||||||
<div className='well clearfix'>
|
<div className='well clearfix'>
|
||||||
<div className='btn-group'>
|
<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')}
|
{i18n('cluster_page.nodes_tab.back_to_nodes_button')}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{!locked &&
|
{!locked &&
|
||||||
<div className='btn-group pull-right'>
|
<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')}
|
{i18n('common.load_defaults_button')}
|
||||||
</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')}
|
{i18n('common.cancel_changes_button')}
|
||||||
</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')}
|
{i18n('common.apply_button')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -508,7 +553,8 @@ var NodeInterface = React.createClass({
|
||||||
drop(props, monitor) {
|
drop(props, monitor) {
|
||||||
var targetInterface = props.interface;
|
var targetInterface = props.interface;
|
||||||
var sourceInterface = props.interfaces.findWhere({name: monitor.getItem().interfaceName});
|
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);
|
sourceInterface.get('assigned_networks').remove(network);
|
||||||
targetInterface.get('assigned_networks').add(network);
|
targetInterface.get('assigned_networks').add(network);
|
||||||
// trigger 'change' event to update screen buttons state
|
// 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());
|
return _.contains(this.getBondPropertyValues('lacp_rate', 'for_modes'), this.getBondMode());
|
||||||
},
|
},
|
||||||
isHashPolicyNeeded() {
|
isHashPolicyNeeded() {
|
||||||
return _.contains(this.getBondPropertyValues('xmit_hash_policy', 'for_modes'), this.getBondMode());
|
return _.contains(this.getBondPropertyValues('xmit_hash_policy', 'for_modes'),
|
||||||
|
this.getBondMode());
|
||||||
},
|
},
|
||||||
getBondMode() {
|
getBondMode() {
|
||||||
var ifc = this.props.interface;
|
var ifc = this.props.interface;
|
||||||
|
@ -552,11 +599,13 @@ var NodeInterface = React.createClass({
|
||||||
var modes = this.props.bondingProperties[this.props.bondType].mode;
|
var modes = this.props.bondingProperties[this.props.bondType].mode;
|
||||||
var configModels = _.clone(this.props.configModels);
|
var configModels = _.clone(this.props.configModels);
|
||||||
var availableModes = [];
|
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) => {
|
_.each(interfaces, (ifc) => {
|
||||||
configModels.interface = ifc;
|
configModels.interface = ifc;
|
||||||
availableModes.push(_.reduce(modes, (result, modeSet) => {
|
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);
|
return result.concat(modeSet.values);
|
||||||
}, []));
|
}, []));
|
||||||
});
|
});
|
||||||
|
@ -583,7 +632,8 @@ var NodeInterface = React.createClass({
|
||||||
this.props.interface.set({mode: value});
|
this.props.interface.set({mode: value});
|
||||||
this.updateBondProperties({mode: value});
|
this.updateBondProperties({mode: value});
|
||||||
if (this.isHashPolicyNeeded()) {
|
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()) {
|
if (this.isLacpRateAvailable()) {
|
||||||
this.updateBondProperties({lacp_rate: this.getBondPropertyValues('lacp_rate', 'values')[0]});
|
this.updateBondProperties({lacp_rate: this.getBondPropertyValues('lacp_rate', 'values')[0]});
|
||||||
|
@ -684,7 +734,10 @@ var NodeInterface = React.createClass({
|
||||||
disabled={!bondingPossible}
|
disabled={!bondingPossible}
|
||||||
onChange={this.onPolicyChange}
|
onChange={this.onPolicyChange}
|
||||||
label={i18n(ns + 'bonding_policy')}
|
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'
|
wrapperClassName='pull-right'
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
@ -695,7 +748,10 @@ var NodeInterface = React.createClass({
|
||||||
disabled={!bondingPossible}
|
disabled={!bondingPossible}
|
||||||
onChange={this.onLacpChange}
|
onChange={this.onLacpChange}
|
||||||
label={i18n(ns + 'lacp_rate')}
|
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'
|
wrapperClassName='pull-right'
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
@ -718,14 +774,21 @@ var NodeInterface = React.createClass({
|
||||||
<div className='pull-left'>
|
<div className='pull-left'>
|
||||||
{_.map(slaveInterfaces, (slaveInterface, index) => {
|
{_.map(slaveInterfaces, (slaveInterface, index) => {
|
||||||
return (
|
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='ifc-connection pull-left'>
|
||||||
<div className={utils.classNames(connectionStatusClasses(slaveInterface))} />
|
<div
|
||||||
|
className={utils.classNames(connectionStatusClasses(slaveInterface))}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='ifc-info pull-left'>
|
<div className='ifc-info pull-left'>
|
||||||
{this.props.interfaceNames[index].length == 1 &&
|
{this.props.interfaceNames[index].length == 1 &&
|
||||||
<div>
|
<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>
|
</div>
|
||||||
}
|
}
|
||||||
{this.props.nodes.length == 1 &&
|
{this.props.nodes.length == 1 &&
|
||||||
|
@ -735,7 +798,13 @@ var NodeInterface = React.createClass({
|
||||||
{i18n(ns + 'speed')}: {this.props.interfaceSpeeds[index].join(', ')}
|
{i18n(ns + 'speed')}: {this.props.interfaceSpeeds[index].join(', ')}
|
||||||
</div>
|
</div>
|
||||||
{(bondingPossible && slaveInterfaces.length >= 3) &&
|
{(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')}
|
{i18n('common.remove_button')}
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
|
@ -789,7 +858,8 @@ var NodeInterface = React.createClass({
|
||||||
onClick={this.toggleOffloading}
|
onClick={this.toggleOffloading}
|
||||||
disabled={locked}
|
disabled={locked}
|
||||||
className='btn btn-default toggle-offloading'>
|
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>
|
</button>
|
||||||
}
|
}
|
||||||
</div>
|
</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({
|
var Network = React.createClass({
|
||||||
statics: {
|
statics: {
|
||||||
|
@ -838,7 +912,10 @@ var Network = React.createClass({
|
||||||
return this.props.connectDragSource(
|
return this.props.connectDragSource(
|
||||||
<div className={utils.classNames(classes)}>
|
<div className={utils.classNames(classes)}>
|
||||||
<div className='network-name'>
|
<div className='network-name'>
|
||||||
{i18n('network.' + interfaceNetwork.get('name'), {defaultValue: interfaceNetwork.get('name')})}
|
{i18n(
|
||||||
|
'network.' + interfaceNetwork.get('name'),
|
||||||
|
{defaultValue: interfaceNetwork.get('name')}
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{vlanRange &&
|
{vlanRange &&
|
||||||
<div className='vlan-id'>
|
<div className='vlan-id'>
|
||||||
|
|
|
@ -30,7 +30,8 @@ var EditNodesScreen = React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
nodes.fetch = function(options) {
|
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() {
|
nodes.parse = function() {
|
||||||
return this.getByIds(nodes.pluck('id'));
|
return this.getByIds(nodes.pluck('id'));
|
||||||
|
|
|
@ -44,12 +44,21 @@ var Node = React.createClass({
|
||||||
var options = {type: 'remote', node: this.props.node.id};
|
var options = {type: 'remote', node: this.props.node.id};
|
||||||
if (status == 'discover') {
|
if (status == 'discover') {
|
||||||
options.source = 'bootstrap/messages';
|
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';
|
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';
|
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) {
|
applyNewNodeName(newName) {
|
||||||
if (newName && newName != this.props.node.get('name')) {
|
if (newName && newName != this.props.node.get('name')) {
|
||||||
|
@ -100,7 +109,8 @@ var Node = React.createClass({
|
||||||
.sync('delete', this.props.node)
|
.sync('delete', this.props.node)
|
||||||
.then(
|
.then(
|
||||||
(task) => {
|
(task) => {
|
||||||
dispatcher.trigger('networkConfigurationUpdated updateNodeStats updateNotifications labelsConfigurationUpdated');
|
dispatcher.trigger('networkConfigurationUpdated updateNodeStats ' +
|
||||||
|
'updateNotifications labelsConfigurationUpdated');
|
||||||
if (task.status == 'ready') {
|
if (task.status == 'ready') {
|
||||||
// Do not send the 'DELETE' request again, just get rid
|
// Do not send the 'DELETE' request again, just get rid
|
||||||
// of this node.
|
// of this node.
|
||||||
|
@ -129,7 +139,8 @@ var Node = React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
toggleExtendedNodePanel() {
|
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);
|
this.setState(states);
|
||||||
},
|
},
|
||||||
renderNameControl() {
|
renderNameControl() {
|
||||||
|
@ -159,7 +170,8 @@ var Node = React.createClass({
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
{i18n('cluster_page.nodes_tab.node.status.' + status, {
|
{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>
|
</span>
|
||||||
);
|
);
|
||||||
|
@ -174,7 +186,12 @@ var Node = React.createClass({
|
||||||
{': ' + nodeProgress + '%'}
|
{': ' + nodeProgress + '%'}
|
||||||
</div>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -184,16 +201,34 @@ var Node = React.createClass({
|
||||||
var ram = this.props.node.resource('ram');
|
var ram = this.props.node.resource('ram');
|
||||||
return (
|
return (
|
||||||
<div className='node-hardware'>
|
<div className='node-hardware'>
|
||||||
<span>{i18n('node_details.cpu')}: {this.props.node.resource('cores') || '0'} ({_.isUndefined(htCores) ? '?' : htCores})</span>
|
<span>
|
||||||
<span>{i18n('node_details.hdd')}: {_.isUndefined(hdd) ? '?' + i18n('common.size.gb') : utils.showDiskSize(hdd)}</span>
|
{i18n('node_details.cpu')}
|
||||||
<span>{i18n('node_details.ram')}: {_.isUndefined(ram) ? '?' + i18n('common.size.gb') : utils.showMemorySize(ram)}</span>
|
{': '}
|
||||||
|
{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>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
renderLogsLink(iconRepresentation) {
|
renderLogsLink(iconRepresentation) {
|
||||||
return (
|
return (
|
||||||
<Tooltip key='logs' text={iconRepresentation ? i18n('cluster_page.nodes_tab.node.view_logs') : null}>
|
<Tooltip
|
||||||
<a className={'btn-view-logs ' + (iconRepresentation ? 'icon icon-logs' : 'btn')} href={this.getNodeLogsLink()}>
|
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')}
|
{!iconRepresentation && i18n('cluster_page.nodes_tab.node.view_logs')}
|
||||||
</a>
|
</a>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
@ -269,7 +304,9 @@ var Node = React.createClass({
|
||||||
<label className='node-box'>
|
<label className='node-box'>
|
||||||
<div
|
<div
|
||||||
className='node-box-inner clearfix'
|
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'>
|
<div className='node-checkbox'>
|
||||||
{this.props.checked && <i className='glyphicon glyphicon-ok' />}
|
{this.props.checked && <i className='glyphicon glyphicon-ok' />}
|
||||||
|
@ -290,9 +327,13 @@ var Node = React.createClass({
|
||||||
<span>
|
<span>
|
||||||
{node.resource('cores') || '0'} ({node.resource('ht_cores') || '?'})
|
{node.resource('cores') || '0'} ({node.resource('ht_cores') || '?'})
|
||||||
</span> / <span>
|
</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>
|
</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>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<p className='btn btn-link' onClick={this.toggleExtendedNodePanel}>
|
<p className='btn btn-link' onClick={this.toggleExtendedNodePanel}>
|
||||||
|
@ -340,13 +381,21 @@ var Node = React.createClass({
|
||||||
{status == 'offline' && this.renderRemoveButton()}
|
{status == 'offline' && this.renderRemoveButton()}
|
||||||
{[
|
{[
|
||||||
!!node.get('cluster') && this.renderLogsLink(),
|
!!node.get('cluster') && this.renderLogsLink(),
|
||||||
this.props.renderActionButtons && node.hasChanges() && !this.props.locked &&
|
this.props.renderActionButtons && node.hasChanges() &&
|
||||||
|
!this.props.locked &&
|
||||||
<button
|
<button
|
||||||
className='btn btn-discard'
|
className='btn btn-discard'
|
||||||
key='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>
|
</button>
|
||||||
]}
|
]}
|
||||||
</div>
|
</div>
|
||||||
|
@ -404,11 +453,15 @@ var Node = React.createClass({
|
||||||
this.props.renderActionButtons && node.hasChanges() && !this.props.locked &&
|
this.props.renderActionButtons && node.hasChanges() && !this.props.locked &&
|
||||||
<Tooltip
|
<Tooltip
|
||||||
key={'pending_addition_' + node.id}
|
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
|
<div
|
||||||
className='icon btn-discard'
|
className='icon btn-discard'
|
||||||
onClick={node.get('pending_deletion') ? this.discardNodeDeletion : this.showDeleteNodesDialog}
|
onClick={node.get('pending_deletion') ?
|
||||||
|
this.discardNodeDeletion : this.showDeleteNodesDialog
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
]}
|
]}
|
||||||
|
@ -439,7 +492,9 @@ var Node = React.createClass({
|
||||||
var node = this.props.node;
|
var node = this.props.node;
|
||||||
var isSelectable = node.isSelectable() && !this.props.locked && this.props.mode != 'edit';
|
var isSelectable = node.isSelectable() && !this.props.locked && this.props.mode != 'edit';
|
||||||
var status = node.getStatusSummary();
|
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
|
// compose classes
|
||||||
var nodePanelClasses = {
|
var nodePanelClasses = {
|
||||||
|
@ -470,9 +525,13 @@ var Node = React.createClass({
|
||||||
}[status];
|
}[status];
|
||||||
statusClasses[statusClass] = true;
|
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 {backboneMixin, pollingMixin, dispatcherMixin, unsavedChangesMixin} from 'component_mixins';
|
||||||
import Node from 'views/cluster_page_tabs/nodes_tab_screens/node';
|
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 {
|
class Sorter {
|
||||||
constructor(name, order, isLabel) {
|
constructor(name, order, isLabel) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.order = order;
|
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;
|
this.isLabel = isLabel;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -52,9 +56,13 @@ class Filter {
|
||||||
constructor(name, values, isLabel) {
|
constructor(name, values, isLabel) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.values = values;
|
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.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;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,7 +84,10 @@ class Filter {
|
||||||
var resources = nodes.invoke('resource', this.name);
|
var resources = nodes.invoke('resource', this.name);
|
||||||
limits = [_.min(resources), _.max(resources)];
|
limits = [_.min(resources), _.max(resources)];
|
||||||
if (this.name == 'hdd' || this.name == 'ram') {
|
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;
|
this.limits = limits;
|
||||||
|
@ -133,15 +144,20 @@ NodeListScreen = React.createClass({
|
||||||
var viewMode = uiSettings.view_mode;
|
var viewMode = uiSettings.view_mode;
|
||||||
var isLabelsPanelOpen = false;
|
var isLabelsPanelOpen = false;
|
||||||
|
|
||||||
var states = {search, activeSorters, activeFilters, availableSorters, availableFilters, viewMode, isLabelsPanelOpen};
|
var states = {search, activeSorters, activeFilters, availableSorters, availableFilters,
|
||||||
|
viewMode, isLabelsPanelOpen};
|
||||||
|
|
||||||
// Equipment page
|
// Equipment page
|
||||||
if (!cluster) return states;
|
if (!cluster) return states;
|
||||||
|
|
||||||
// additonal Nodes tab states (Cluster page)
|
// additonal Nodes tab states (Cluster page)
|
||||||
var roles = cluster.get('roles').pluck('name');
|
var roles = cluster.get('roles').pluck('name');
|
||||||
var selectedRoles = nodes.length ? _.filter(roles, (role) => !nodes.any((node) => !node.hasRole(role))) : [];
|
var selectedRoles = nodes.length ? _.filter(roles, (role) => !nodes.any((node) => {
|
||||||
var indeterminateRoles = nodes.length ? _.filter(roles, (role) => !_.contains(selectedRoles, role) && nodes.any((node) => node.hasRole(role))) : [];
|
return !node.hasRole(role);
|
||||||
|
})) : [];
|
||||||
|
var indeterminateRoles = nodes.length ? _.filter(roles, (role) => {
|
||||||
|
return !_.contains(selectedRoles, role) && nodes.any((node) => node.hasRole(role));
|
||||||
|
}) : [];
|
||||||
|
|
||||||
var configModels = {
|
var configModels = {
|
||||||
cluster: cluster,
|
cluster: cluster,
|
||||||
|
@ -180,8 +196,12 @@ NodeListScreen = React.createClass({
|
||||||
var filter = _.clone(activeFilter);
|
var filter = _.clone(activeFilter);
|
||||||
if (filter.values.length) {
|
if (filter.values.length) {
|
||||||
if (filter.isLabel) {
|
if (filter.isLabel) {
|
||||||
filter.values = _.intersection(filter.values, this.props.nodes.getLabelValues(filter.name));
|
filter.values = _.intersection(
|
||||||
} else if (checkStandardNodeFilters && _.contains(['manufacturer', 'group_id', 'cluster'], filter.name)) {
|
filter.values,
|
||||||
|
this.props.nodes.getLabelValues(filter.name)
|
||||||
|
);
|
||||||
|
} else if (checkStandardNodeFilters &&
|
||||||
|
_.contains(['manufacturer', 'group_id', 'cluster'], filter.name)) {
|
||||||
filter.values = _.filter(filter.values, (value) => {
|
filter.values = _.filter(filter.values, (value) => {
|
||||||
return this.props.nodes.any((node) => node.get(filter.name) == value);
|
return this.props.nodes.any((node) => node.get(filter.name) == value);
|
||||||
}, this);
|
}, this);
|
||||||
|
@ -189,7 +209,12 @@ NodeListScreen = React.createClass({
|
||||||
}
|
}
|
||||||
return filter;
|
return filter;
|
||||||
}, this);
|
}, this);
|
||||||
if (!_.isEqual(_.pluck(normalizedFilters, 'values'), _.pluck(this.state.activeFilters, 'values'))) {
|
if (
|
||||||
|
!_.isEqual(
|
||||||
|
_.pluck(normalizedFilters, 'values'),
|
||||||
|
_.pluck(this.state.activeFilters, 'values')
|
||||||
|
)
|
||||||
|
) {
|
||||||
this.updateFilters(normalizedFilters);
|
this.updateFilters(normalizedFilters);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -218,14 +243,21 @@ NodeListScreen = React.createClass({
|
||||||
var processedRoleLimits = {};
|
var processedRoleLimits = {};
|
||||||
|
|
||||||
var selectedNodes = this.props.nodes.filter((node) => this.props.selectedNodeIds[node.id]);
|
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));
|
var nodesForLimitCheck = new models.Nodes(_.union(selectedNodes, clusterNodes));
|
||||||
|
|
||||||
cluster.get('roles').each((role) => {
|
cluster.get('roles').each((role) => {
|
||||||
if ((role.get('limits') || {}).max) {
|
if ((role.get('limits') || {}).max) {
|
||||||
var roleName = role.get('name');
|
var roleName = role.get('name');
|
||||||
var isRoleAlreadyAssigned = nodesForLimitCheck.any((node) => node.hasRole(roleName));
|
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
|
// need to cache roles with limits in order to avoid calculating this twice on the RolePanel
|
||||||
processedRoleLimits: processedRoleLimits,
|
processedRoleLimits: processedRoleLimits,
|
||||||
// real number of nodes to add used by Select All controls
|
// 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() {
|
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) {
|
checkRoleAssignment(node, roles, options) {
|
||||||
if (!options.assign) node.set({pending_roles: node.previous('pending_roles')}, {assign: true});
|
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 values.map((value) => {
|
||||||
return {
|
return {
|
||||||
name: value,
|
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;
|
var options;
|
||||||
switch (filter.name) {
|
switch (filter.name) {
|
||||||
case 'status':
|
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) => {
|
options = this.props.statusesToFilter.map((status) => {
|
||||||
return {
|
return {
|
||||||
name: status,
|
name: status,
|
||||||
|
@ -343,7 +379,12 @@ NodeListScreen = React.createClass({
|
||||||
return {
|
return {
|
||||||
name: groupId,
|
name: groupId,
|
||||||
label: nodeNetworkGroup ?
|
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')
|
i18n('common.not_specified')
|
||||||
};
|
};
|
||||||
|
@ -353,7 +394,8 @@ NodeListScreen = React.createClass({
|
||||||
options = _.uniq(this.props.nodes.pluck('cluster')).map((clusterId) => {
|
options = _.uniq(this.props.nodes.pluck('cluster')).map((clusterId) => {
|
||||||
return {
|
return {
|
||||||
name: clusterId,
|
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;
|
break;
|
||||||
|
@ -433,8 +475,11 @@ NodeListScreen = React.createClass({
|
||||||
default:
|
default:
|
||||||
// handle number ranges
|
// handle number ranges
|
||||||
var currentValue = node.resource(filter.name);
|
var currentValue = node.resource(filter.name);
|
||||||
if (filter.name == 'hdd' || filter.name == 'ram') currentValue = currentValue / Math.pow(1024, 3);
|
if (filter.name == 'hdd' || filter.name == 'ram') {
|
||||||
result = currentValue >= filter.values[0] && (_.isUndefined(filter.values[1]) || currentValue <= filter.values[1]);
|
currentValue = currentValue / Math.pow(1024, 3);
|
||||||
|
}
|
||||||
|
result = currentValue >= filter.values[0] &&
|
||||||
|
(_.isUndefined(filter.values[1]) || currentValue <= filter.values[1]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -446,15 +491,24 @@ NodeListScreen = React.createClass({
|
||||||
var processedRoleData = cluster ? this.processRoleLimits() : {};
|
var processedRoleData = cluster ? this.processRoleLimits() : {};
|
||||||
|
|
||||||
// labels to manage in labels panel
|
// labels to manage in labels panel
|
||||||
var selectedNodes = new models.Nodes(this.props.nodes.filter((node) => this.props.selectedNodeIds[node.id]));
|
var selectedNodes = new models.Nodes(this.props.nodes.filter((node) => {
|
||||||
var selectedNodeLabels = _.chain(selectedNodes.pluck('labels')).flatten().map(_.keys).flatten().uniq().value();
|
return this.props.selectedNodeIds[node.id];
|
||||||
|
}));
|
||||||
|
var selectedNodeLabels = _.chain(selectedNodes.pluck('labels'))
|
||||||
|
.flatten()
|
||||||
|
.map(_.keys)
|
||||||
|
.flatten()
|
||||||
|
.uniq()
|
||||||
|
.value();
|
||||||
|
|
||||||
// filter nodes
|
// filter nodes
|
||||||
var filteredNodes = nodes.filter((node) => {
|
var filteredNodes = nodes.filter((node) => {
|
||||||
// search field
|
// search field
|
||||||
if (this.state.search) {
|
if (this.state.search) {
|
||||||
var search = this.state.search.toLowerCase();
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -480,13 +534,21 @@ NodeListScreen = React.createClass({
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<ManagementPanel
|
<ManagementPanel
|
||||||
{... _.pick(this.state, 'viewMode', 'search', 'activeSorters', 'activeFilters', 'availableSorters', 'availableFilters', 'isLabelsPanelOpen')}
|
{... _.pick(
|
||||||
{... _.pick(this.props, 'cluster', 'mode', 'defaultSorting', 'statusesToFilter', 'defaultFilters')}
|
this.state,
|
||||||
{... _.pick(this, 'addSorting', 'removeSorting', 'resetSorters', 'changeSortingOrder')}
|
'viewMode', 'search', 'activeSorters', 'activeFilters', 'availableSorters',
|
||||||
{... _.pick(this, 'addFilter', 'changeFilter', 'removeFilter', 'resetFilters', 'getFilterOptions')}
|
'availableFilters', 'isLabelsPanelOpen'
|
||||||
{... _.pick(this, 'toggleLabelsPanel')}
|
)}
|
||||||
{... _.pick(this, 'changeSearch', 'clearSearchField')}
|
{... _.pick(
|
||||||
{... _.pick(this, 'changeViewMode')}
|
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))}
|
labelSorters={screenNodesLabels.map((name) => new Sorter(name, 'asc', true))}
|
||||||
labelFilters={screenNodesLabels.map((name) => new Filter(name, [], true))}
|
labelFilters={screenNodesLabels.map((name) => new Filter(name, [], true))}
|
||||||
nodes={selectedNodes}
|
nodes={selectedNodes}
|
||||||
|
@ -508,7 +570,9 @@ NodeListScreen = React.createClass({
|
||||||
}
|
}
|
||||||
<NodeList
|
<NodeList
|
||||||
{... _.pick(this.state, 'viewMode', 'activeSorters', 'selectedRoles')}
|
{... _.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')}
|
{... _.pick(processedRoleData, 'maxNumberOfNodes', 'processedRoleLimits')}
|
||||||
nodes={filteredNodes}
|
nodes={filteredNodes}
|
||||||
totalNodesLength={nodes.length}
|
totalNodesLength={nodes.length}
|
||||||
|
@ -524,7 +588,10 @@ MultiSelectControl = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
name: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.bool]),
|
name: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.bool]),
|
||||||
options: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
|
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,
|
label: React.PropTypes.node.isRequired,
|
||||||
dynamicValues: React.PropTypes.bool,
|
dynamicValues: React.PropTypes.bool,
|
||||||
onChange: React.PropTypes.func,
|
onChange: React.PropTypes.func,
|
||||||
|
@ -559,7 +626,10 @@ MultiSelectControl = React.createClass({
|
||||||
var label = this.props.label;
|
var label = this.props.label;
|
||||||
if (!this.props.dynamicValues && valuesAmount) {
|
if (!this.props.dynamicValues && valuesAmount) {
|
||||||
label = this.props.label + ': ' + (valuesAmount > 3 ?
|
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) => {
|
_.map(this.props.values, (itemName) => {
|
||||||
return _.find(this.props.options, {name: itemName}).label;
|
return _.find(this.props.options, {name: itemName}).label;
|
||||||
|
@ -592,7 +662,9 @@ MultiSelectControl = React.createClass({
|
||||||
return (
|
return (
|
||||||
<div className={utils.classNames(classNames)} tabIndex='-1' onKeyDown={this.closeOnEscapeKey}>
|
<div className={utils.classNames(classNames)} tabIndex='-1' onKeyDown={this.closeOnEscapeKey}>
|
||||||
<button
|
<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}
|
onClick={this.props.toggle}
|
||||||
>
|
>
|
||||||
{label} <span className='caret'></span>
|
{label} <span className='caret'></span>
|
||||||
|
@ -627,7 +699,9 @@ MultiSelectControl = React.createClass({
|
||||||
onChange={_.partialRight(this.onChange, false)}
|
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) => {
|
{_.map(labels, (option) => {
|
||||||
return <Input {...optionProps(option)}
|
return <Input {...optionProps(option)}
|
||||||
key={'label-' + option.name}
|
key={'label-' + option.name}
|
||||||
|
@ -687,7 +761,9 @@ NumberRangeControl = React.createClass({
|
||||||
return (
|
return (
|
||||||
<div className={utils.classNames(classNames)} tabIndex='-1' onKeyDown={this.closeOnEscapeKey}>
|
<div className={utils.classNames(classNames)} tabIndex='-1' onKeyDown={this.closeOnEscapeKey}>
|
||||||
<button className='btn btn-default dropdown-toggle' onClick={this.props.toggle}>
|
<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>
|
</button>
|
||||||
{this.props.isOpen &&
|
{this.props.isOpen &&
|
||||||
<Popover toggle={this.props.toggle}>
|
<Popover toggle={this.props.toggle}>
|
||||||
|
@ -735,7 +811,10 @@ ManagementPanel = React.createClass({
|
||||||
var ns = 'cluster_page.nodes_tab.node_management_panel.node_management_error.';
|
var ns = 'cluster_page.nodes_tab.node_management_panel.node_management_error.';
|
||||||
utils.showErrorDialog({
|
utils.showErrorDialog({
|
||||||
title: i18n(ns + 'title'),
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -743,7 +822,9 @@ ManagementPanel = React.createClass({
|
||||||
},
|
},
|
||||||
showDeleteNodesDialog() {
|
showDeleteNodesDialog() {
|
||||||
DeleteNodesDialog.show({nodes: this.props.nodes, cluster: this.props.cluster})
|
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() {
|
hasChanges() {
|
||||||
return this.props.hasChanges;
|
return this.props.hasChanges;
|
||||||
|
@ -761,7 +842,9 @@ ManagementPanel = React.createClass({
|
||||||
var nodes = new models.Nodes(this.props.nodes.map((node) => {
|
var nodes = new models.Nodes(this.props.nodes.map((node) => {
|
||||||
var data = {id: node.id, pending_roles: node.get('pending_roles')};
|
var data = {id: node.id, pending_roles: node.get('pending_roles')};
|
||||||
if (node.get('pending_roles').length) {
|
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')) {
|
} else if (node.get('pending_addition')) {
|
||||||
return _.extend(data, {cluster_id: null, pending_addition: false});
|
return _.extend(data, {cluster_id: null, pending_addition: false});
|
||||||
}
|
}
|
||||||
|
@ -771,7 +854,8 @@ ManagementPanel = React.createClass({
|
||||||
.done(() => {
|
.done(() => {
|
||||||
$.when(this.props.cluster.fetch(), this.props.cluster.fetchRelated('nodes')).always(() => {
|
$.when(this.props.cluster.fetch(), this.props.cluster.fetchRelated('nodes')).always(() => {
|
||||||
if (this.props.mode == 'add') {
|
if (this.props.mode == 'add') {
|
||||||
dispatcher.trigger('updateNodeStats networkConfigurationUpdated labelsConfigurationUpdated');
|
dispatcher.trigger('updateNodeStats networkConfigurationUpdated ' +
|
||||||
|
'labelsConfigurationUpdated');
|
||||||
this.props.selectNodes();
|
this.props.selectNodes();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -779,7 +863,8 @@ ManagementPanel = React.createClass({
|
||||||
.fail((response) => {
|
.fail((response) => {
|
||||||
this.setState({actionInProgress: false});
|
this.setState({actionInProgress: false});
|
||||||
utils.showErrorDialog({
|
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
|
response: response
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -800,7 +885,8 @@ ManagementPanel = React.createClass({
|
||||||
activateSearch() {
|
activateSearch() {
|
||||||
this.setState({activeSearch: true});
|
this.setState({activeSearch: true});
|
||||||
$('html').on('click.search', (e) => {
|
$('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});
|
this.setState({activeSearch: false});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -838,17 +924,20 @@ ManagementPanel = React.createClass({
|
||||||
},
|
},
|
||||||
toggleMoreFilterControl(visible) {
|
toggleMoreFilterControl(visible) {
|
||||||
this.setState({
|
this.setState({
|
||||||
isMoreFilterControlVisible: _.isBoolean(visible) ? visible : !this.state.isMoreFilterControlVisible,
|
isMoreFilterControlVisible: _.isBoolean(visible) ? visible :
|
||||||
|
!this.state.isMoreFilterControlVisible,
|
||||||
openFilter: null
|
openFilter: null
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
toggleMoreSorterControl(visible) {
|
toggleMoreSorterControl(visible) {
|
||||||
this.setState({
|
this.setState({
|
||||||
isMoreSorterControlVisible: _.isBoolean(visible) ? visible : !this.state.isMoreSorterControlVisible
|
isMoreSorterControlVisible: _.isBoolean(visible) ? visible :
|
||||||
|
!this.state.isMoreSorterControlVisible
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
isFilterOpen(filter) {
|
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) {
|
addFilter(filter) {
|
||||||
this.props.addFilter(filter);
|
this.props.addFilter(filter);
|
||||||
|
@ -889,7 +978,10 @@ ManagementPanel = React.createClass({
|
||||||
renderDeleteFilterButton(filter) {
|
renderDeleteFilterButton(filter) {
|
||||||
if (!filter.isLabel && _.contains(_.keys(this.props.defaultFilters), filter.name)) return null;
|
if (!filter.isLabel && _.contains(_.keys(this.props.defaultFilters), filter.name)) return null;
|
||||||
return (
|
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() {
|
toggleLabelsPanel() {
|
||||||
|
@ -902,7 +994,10 @@ ManagementPanel = React.createClass({
|
||||||
},
|
},
|
||||||
renderDeleteSorterButton(sorter) {
|
renderDeleteSorterButton(sorter) {
|
||||||
return (
|
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() {
|
render() {
|
||||||
|
@ -925,14 +1020,32 @@ ManagementPanel = React.createClass({
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.props.mode != 'edit') {
|
if (this.props.mode != 'edit') {
|
||||||
var checkSorter = (sorter, isLabel) => !_.any(this.props.activeSorters, {name: sorter.name, isLabel: isLabel});
|
var checkSorter = (sorter, isLabel) => {
|
||||||
inactiveSorters = _.union(_.filter(this.props.availableSorters, _.partial(checkSorter, _, false)), _.filter(this.props.labelSorters, _.partial(checkSorter, _, true)))
|
return !_.any(this.props.activeSorters, {name: sorter.name, isLabel: isLabel});
|
||||||
.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);
|
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});
|
var checkFilter = (filter, isLabel) => {
|
||||||
inactiveFilters = _.union(_.filter(this.props.availableFilters, _.partial(checkFilter, _, false)), _.filter(this.props.labelFilters, _.partial(checkFilter, _, true)))
|
return !_.any(this.props.activeFilters, {name: filter.name, isLabel: isLabel});
|
||||||
.sort((filter1, filter2) => utils.natsort(filter1.title, filter2.title, {insensitive: true}));
|
};
|
||||||
|
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);
|
appliedFilters = _.reject(this.props.activeFilters, (filter) => !filter.values.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -948,8 +1061,12 @@ ManagementPanel = React.createClass({
|
||||||
return (
|
return (
|
||||||
<Tooltip key={mode + '-view'} text={i18n(ns + mode + '_mode_tooltip')}>
|
<Tooltip key={mode + '-view'} text={i18n(ns + mode + '_mode_tooltip')}>
|
||||||
<label
|
<label
|
||||||
className={utils.classNames(managementButtonClasses(mode == this.props.viewMode, mode))}
|
className={utils.classNames(
|
||||||
onClick={mode != this.props.viewMode && _.partial(this.props.changeViewMode, 'view_mode', mode)}
|
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} />
|
<input type='radio' name='view_mode' value={mode} />
|
||||||
<i
|
<i
|
||||||
|
@ -970,7 +1087,9 @@ ManagementPanel = React.createClass({
|
||||||
<button
|
<button
|
||||||
disabled={!this.props.nodes.length}
|
disabled={!this.props.nodes.length}
|
||||||
onClick={this.props.nodes.length && this.toggleLabelsPanel}
|
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' />
|
<i className='glyphicon glyphicon-tag' />
|
||||||
</button>
|
</button>
|
||||||
|
@ -979,7 +1098,9 @@ ManagementPanel = React.createClass({
|
||||||
<button
|
<button
|
||||||
disabled={!this.props.screenNodes.length}
|
disabled={!this.props.screenNodes.length}
|
||||||
onClick={this.toggleSorters}
|
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' />
|
<i className='glyphicon glyphicon-sort' />
|
||||||
</button>
|
</button>
|
||||||
|
@ -988,7 +1109,9 @@ ManagementPanel = React.createClass({
|
||||||
<button
|
<button
|
||||||
disabled={!this.props.screenNodes.length}
|
disabled={!this.props.screenNodes.length}
|
||||||
onClick={this.toggleFilters}
|
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' />
|
<i className='glyphicon glyphicon-filter' />
|
||||||
</button>
|
</button>
|
||||||
|
@ -1018,7 +1141,12 @@ ManagementPanel = React.createClass({
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
{this.state.isSearchButtonVisible &&
|
{this.state.isSearchButtonVisible &&
|
||||||
<button className='close btn-clear-search' onClick={this.clearSearchField}>×</button>
|
<button
|
||||||
|
className='close btn-clear-search'
|
||||||
|
onClick={this.clearSearchField}
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -1055,19 +1183,27 @@ ManagementPanel = React.createClass({
|
||||||
onClick={_.bind(this.goToConfigurationScreen, this, 'disks', disksConflict)}
|
onClick={_.bind(this.goToConfigurationScreen, this, 'disks', disksConflict)}
|
||||||
>
|
>
|
||||||
{disksConflict && <i className='glyphicon glyphicon-danger-sign' />}
|
{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>
|
||||||
<button
|
<button
|
||||||
className='btn btn-default btn-configure-interfaces'
|
className='btn btn-default btn-configure-interfaces'
|
||||||
disabled={!this.props.nodes.length}
|
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' />}
|
{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>
|
</button>
|
||||||
</div>,
|
</div>,
|
||||||
<div className='btn-group' role='group' key='role-management-buttons'>
|
<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
|
<button
|
||||||
className='btn btn-danger btn-delete-nodes'
|
className='btn btn-danger btn-delete-nodes'
|
||||||
onClick={this.showDeleteNodesDialog}
|
onClick={this.showDeleteNodesDialog}
|
||||||
|
@ -1076,7 +1212,8 @@ ManagementPanel = React.createClass({
|
||||||
{i18n('common.delete_button')}
|
{i18n('common.delete_button')}
|
||||||
</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
|
<button
|
||||||
className='btn btn-success btn-edit-roles'
|
className='btn btn-success btn-edit-roles'
|
||||||
onClick={_.bind(this.changeScreen, this, 'edit', true)}
|
onClick={_.bind(this.changeScreen, this, 'edit', true)}
|
||||||
|
@ -1113,7 +1250,10 @@ ManagementPanel = React.createClass({
|
||||||
<div className='well-heading'>
|
<div className='well-heading'>
|
||||||
<i className='glyphicon glyphicon-sort' /> {i18n(ns + 'sort_by')}
|
<i className='glyphicon glyphicon-sort' /> {i18n(ns + 'sort_by')}
|
||||||
{canResetSorters &&
|
{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')}
|
<i className='glyphicon glyphicon-remove-sign' /> {i18n(ns + 'reset')}
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
|
@ -1128,7 +1268,10 @@ ManagementPanel = React.createClass({
|
||||||
['sort-by-' + sorter.name + '-' + sorter.order]: !sorter.isLabel
|
['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}
|
{sorter.title}
|
||||||
<i
|
<i
|
||||||
className={utils.classNames({
|
className={utils.classNames({
|
||||||
|
@ -1138,7 +1281,9 @@ ManagementPanel = React.createClass({
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
{this.props.activeSorters.length > 1 && this.renderDeleteSorterButton(sorter)}
|
{this.props.activeSorters.length > 1 &&
|
||||||
|
this.renderDeleteSorterButton(sorter)
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@ -1160,7 +1305,10 @@ ManagementPanel = React.createClass({
|
||||||
<div className='well-heading'>
|
<div className='well-heading'>
|
||||||
<i className='glyphicon glyphicon-filter' /> {i18n(ns + 'filter_by')}
|
<i className='glyphicon glyphicon-filter' /> {i18n(ns + 'filter_by')}
|
||||||
{!!appliedFilters.length &&
|
{!!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')}
|
<i className='glyphicon glyphicon-remove-sign' /> {i18n(ns + 'reset')}
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
|
@ -1178,15 +1326,25 @@ ManagementPanel = React.createClass({
|
||||||
label: filter.title,
|
label: filter.title,
|
||||||
extraContent: this.renderDeleteFilterButton(filter),
|
extraContent: this.renderDeleteFilterButton(filter),
|
||||||
onChange: _.partial(this.props.changeFilter, 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),
|
isOpen: this.isFilterOpen(filter),
|
||||||
toggle: _.partial(this.toggleFilter, filter)
|
toggle: _.partial(this.toggleFilter, filter)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (filter.isNumberRange) {
|
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
|
<MultiSelectControl
|
||||||
name='filter-more'
|
name='filter-more'
|
||||||
|
@ -1203,7 +1361,8 @@ ManagementPanel = React.createClass({
|
||||||
]}
|
]}
|
||||||
{this.props.mode != 'edit' && !!this.props.screenNodes.length &&
|
{this.props.mode != 'edit' && !!this.props.screenNodes.length &&
|
||||||
<div className='col-xs-12'>
|
<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'>
|
<div className='active-sorters-filters'>
|
||||||
{!this.state.areFiltersVisible && !!appliedFilters.length &&
|
{!this.state.areFiltersVisible && !!appliedFilters.length &&
|
||||||
<div className='active-filters row' onClick={this.toggleFilters}>
|
<div className='active-filters row' onClick={this.toggleFilters}>
|
||||||
|
@ -1214,7 +1373,8 @@ ManagementPanel = React.createClass({
|
||||||
total: this.props.screenNodes.length
|
total: this.props.screenNodes.length
|
||||||
})}
|
})}
|
||||||
{_.map(appliedFilters, (filter) => {
|
{_.map(appliedFilters, (filter) => {
|
||||||
var options = filter.isNumberRange ? null : this.props.getFilterOptions(filter);
|
var options = filter.isNumberRange ? null :
|
||||||
|
this.props.getFilterOptions(filter);
|
||||||
return (
|
return (
|
||||||
<div key={filter.name}>
|
<div key={filter.name}>
|
||||||
<strong>{filter.title}{!!filter.values.length && ':'} </strong>
|
<strong>{filter.title}{!!filter.values.length && ':'} </strong>
|
||||||
|
@ -1223,7 +1383,9 @@ ManagementPanel = React.createClass({
|
||||||
_.uniq(filter.values).join(' - ')
|
_.uniq(filter.values).join(' - ')
|
||||||
:
|
:
|
||||||
_.pluck(
|
_.pluck(
|
||||||
_.filter(options, (option) => _.contains(filter.values, option.name))
|
_.filter(options, (option) => {
|
||||||
|
return _.contains(filter.values, option.name);
|
||||||
|
})
|
||||||
, 'label').join(', ')
|
, 'label').join(', ')
|
||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
|
@ -1231,7 +1393,10 @@ ManagementPanel = React.createClass({
|
||||||
);
|
);
|
||||||
}, this)}
|
}, this)}
|
||||||
</div>
|
</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' />
|
<i className='glyphicon glyphicon-remove-sign' />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1258,7 +1423,10 @@ ManagementPanel = React.createClass({
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
{canResetSorters &&
|
{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' />
|
<i className='glyphicon glyphicon-remove-sign' />
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
|
@ -1355,7 +1523,8 @@ NodeLabelsPanel = React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
isSavingPossible() {
|
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() {
|
revertChanges() {
|
||||||
return this.props.toggleLabelsPanel();
|
return this.props.toggleLabelsPanel();
|
||||||
|
@ -1409,7 +1578,10 @@ NodeLabelsPanel = React.createClass({
|
||||||
})
|
})
|
||||||
.fail((response) => {
|
.fail((response) => {
|
||||||
utils.showErrorDialog({
|
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
|
response: response
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1443,7 +1615,10 @@ NodeLabelsPanel = React.createClass({
|
||||||
|
|
||||||
var showControlLabels = index == 0;
|
var showControlLabels = index == 0;
|
||||||
return (
|
return (
|
||||||
<div className={utils.classNames({clearfix: true, 'has-label': showControlLabels})} key={index}>
|
<div
|
||||||
|
className={utils.classNames({clearfix: true, 'has-label': showControlLabels})}
|
||||||
|
key={index}
|
||||||
|
>
|
||||||
<Input
|
<Input
|
||||||
type='checkbox'
|
type='checkbox'
|
||||||
ref={labelData.key}
|
ref={labelData.key}
|
||||||
|
@ -1547,12 +1722,19 @@ RolePanel = React.createClass({
|
||||||
.value();
|
.value();
|
||||||
var messages = [];
|
var messages = [];
|
||||||
|
|
||||||
if (restrictionsCheck.result && restrictionsCheck.message) messages.push(restrictionsCheck.message);
|
if (restrictionsCheck.result && restrictionsCheck.message) {
|
||||||
if (roleLimitsCheckResults && !roleLimitsCheckResults.valid && roleLimitsCheckResults.message) messages.push(roleLimitsCheckResults.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'));
|
if (_.contains(conflicts, name)) messages.push(i18n('cluster_page.nodes_tab.role_conflict'));
|
||||||
|
|
||||||
return {
|
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(' ')
|
message: messages.join(' ')
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -1563,7 +1745,8 @@ RolePanel = React.createClass({
|
||||||
{this.props.cluster.get('roles').map((role) => {
|
{this.props.cluster.get('roles').map((role) => {
|
||||||
if (!role.checkRestrictions(this.props.configModels, 'hide').result) {
|
if (!role.checkRestrictions(this.props.configModels, 'hide').result) {
|
||||||
var name = role.get('name');
|
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 (
|
return (
|
||||||
<Input
|
<Input
|
||||||
key={name}
|
key={name}
|
||||||
|
@ -1590,11 +1773,14 @@ SelectAllMixin = {
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
if (this.refs['select-all']) {
|
if (this.refs['select-all']) {
|
||||||
var input = this.refs['select-all'].getInputDOMNode();
|
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() {
|
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 (
|
return (
|
||||||
<Input
|
<Input
|
||||||
ref='select-all'
|
ref='select-all'
|
||||||
|
@ -1603,7 +1789,8 @@ SelectAllMixin = {
|
||||||
checked={checked}
|
checked={checked}
|
||||||
disabled={
|
disabled={
|
||||||
this.props.mode == 'edit' || this.props.locked || !this.props.nodes.length ||
|
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')}
|
label={i18n('common.select_all')}
|
||||||
wrapperClassName='select-all pull-right'
|
wrapperClassName='select-all pull-right'
|
||||||
|
@ -1622,7 +1809,8 @@ NodeList = React.createClass({
|
||||||
var diskSizes = node.resource('disks');
|
var diskSizes = node.resource('disks');
|
||||||
return i18n('node_details.disks_amount', {
|
return i18n('node_details.disks_amount', {
|
||||||
count: diskSizes.length,
|
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: () => {
|
group_id: () => {
|
||||||
var nodeNetworkGroup = this.props.nodeNetworkGroups.get(node.get('group_id'));
|
var nodeNetworkGroup = this.props.nodeNetworkGroups.get(node.get('group_id'));
|
||||||
return nodeNetworkGroup && i18n(ns + 'node_network_group', {
|
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');
|
}) || i18n(ns + 'no_node_network_group');
|
||||||
},
|
},
|
||||||
cluster: () => cluster && i18n(ns + 'cluster', {cluster: cluster.get('name')}) || i18n(ns + 'unallocated'),
|
cluster: () => cluster && i18n(
|
||||||
hdd: () => i18n('node_details.total_hdd', {total: utils.showDiskSize(node.resource('hdd'))}),
|
ns + 'cluster',
|
||||||
|
{cluster: cluster.get('name')}
|
||||||
|
) || i18n(ns + 'unallocated'),
|
||||||
|
hdd: () => i18n(
|
||||||
|
'node_details.total_hdd',
|
||||||
|
{total: utils.showDiskSize(node.resource('hdd'))}
|
||||||
|
),
|
||||||
disks: () => composeNodeDiskSizesLabel(node),
|
disks: () => composeNodeDiskSizesLabel(node),
|
||||||
ram: () => i18n('node_details.total_ram', {total: utils.showMemorySize(node.resource('ram'))}),
|
ram: () => i18n(
|
||||||
interfaces: () => i18n('node_details.interfaces_amount', {count: node.resource('interfaces')}),
|
'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)})
|
default: () => i18n('node_details.' + sorter.name, {count: node.resource(sorter.name)})
|
||||||
};
|
};
|
||||||
return (sorterNameFormatters[sorter.name] || sorterNameFormatters.default)();
|
return (sorterNameFormatters[sorter.name] || sorterNameFormatters.default)();
|
||||||
|
@ -1698,7 +1899,8 @@ NodeList = React.createClass({
|
||||||
if (node1Label && node2Label) {
|
if (node1Label && node2Label) {
|
||||||
result = utils.natsort(node1Label, node2Label, {insensitive: true});
|
result = utils.natsort(node1Label, node2Label, {insensitive: true});
|
||||||
} else {
|
} else {
|
||||||
result = node1Label === node2Label ? 0 : _.isString(node1Label) ? -1 : _.isNull(node1Label) ? -1 : 1;
|
result = node1Label === node2Label ? 0 : _.isString(node1Label) ? -1 :
|
||||||
|
_.isNull(node1Label) ? -1 : 1;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var comparators = {
|
var comparators = {
|
||||||
|
@ -1711,19 +1913,22 @@ NodeList = React.createClass({
|
||||||
else if (!roles2.length) result = -1;
|
else if (!roles2.length) result = -1;
|
||||||
else {
|
else {
|
||||||
while (!order && roles1.length && roles2.length) {
|
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;
|
result = order || roles1.length - roles2.length;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
status: () => {
|
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: () => {
|
manufacturer: () => {
|
||||||
result = utils.compare(node1, node2, {attr: sorter.name});
|
result = utils.compare(node1, node2, {attr: sorter.name});
|
||||||
},
|
},
|
||||||
disks: () => {
|
disks: () => {
|
||||||
result = utils.natsort(composeNodeDiskSizesLabel(node1), composeNodeDiskSizesLabel(node2));
|
result = utils.natsort(composeNodeDiskSizesLabel(node1),
|
||||||
|
composeNodeDiskSizesLabel(node2));
|
||||||
},
|
},
|
||||||
group_id: () => {
|
group_id: () => {
|
||||||
var nodeGroup1 = node1.get('group_id');
|
var nodeGroup1 = node1.get('group_id');
|
||||||
|
@ -1735,7 +1940,9 @@ NodeList = React.createClass({
|
||||||
var cluster1 = node1.get('cluster');
|
var cluster1 = node1.get('cluster');
|
||||||
var cluster2 = node2.get('cluster');
|
var cluster2 = node2.get('cluster');
|
||||||
result = cluster1 == cluster2 ? 0 :
|
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: () => {
|
default: () => {
|
||||||
result = node1.resource(sorter.name) - node2.resource(sorter.name);
|
result = node1.resource(sorter.name) - node2.resource(sorter.name);
|
||||||
|
@ -1754,11 +1961,15 @@ NodeList = React.createClass({
|
||||||
},
|
},
|
||||||
render() {
|
render() {
|
||||||
var groups = this.groupNodes();
|
var groups = this.groupNodes();
|
||||||
var rolesWithLimitReached = _.keys(_.omit(this.props.processedRoleLimits, (roleLimit, roleName) => {
|
var rolesWithLimitReached = _.keys(_.omit(this.props.processedRoleLimits,
|
||||||
return roleLimit.valid || !_.contains(this.props.selectedRoles, roleName);
|
(roleLimit, roleName) => {
|
||||||
}));
|
return roleLimit.valid || !_.contains(this.props.selectedRoles, roleName);
|
||||||
|
}
|
||||||
|
));
|
||||||
return (
|
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 &&
|
{groups.length > 1 &&
|
||||||
<div className='col-xs-12 node-list-header'>
|
<div className='col-xs-12 node-list-header'>
|
||||||
{this.renderSelectAllCheckbox()}
|
{this.renderSelectAllCheckbox()}
|
||||||
|
@ -1783,7 +1994,11 @@ NodeList = React.createClass({
|
||||||
:
|
:
|
||||||
<div className='alert alert-warning'>
|
<div className='alert alert-warning'>
|
||||||
{utils.renderMultilineText(
|
{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>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,8 @@ var SettingSection = React.createClass({
|
||||||
|
|
||||||
// FIXME: hack for #1442475 to lock images_ceph in env with controllers
|
// FIXME: hack for #1442475 to lock images_ceph in env with controllers
|
||||||
if (settingName == 'images_ceph') {
|
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;
|
result = true;
|
||||||
messages.push(i18n('cluster_page.settings_tab.images_ceph_warning'));
|
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 dependentRoles = this.checkDependentRoles(sectionName, settingName);
|
||||||
var dependentSettings = this.checkDependentSettings(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 (dependentRoles.length) {
|
||||||
if (dependentSettings.length) messages.push(i18n('cluster_page.settings_tab.dependent_settings_warning', {settings: dependentSettings.join(', '), count: dependentSettings.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 {
|
return {
|
||||||
result: !!dependentRoles.length || !!dependentSettings.length,
|
result: !!dependentRoles.length || !!dependentSettings.length,
|
||||||
|
@ -58,13 +73,18 @@ var SettingSection = React.createClass({
|
||||||
return setting.toggleable || _.contains(['checkbox', 'radio'], setting.type);
|
return setting.toggleable || _.contains(['checkbox', 'radio'], setting.type);
|
||||||
},
|
},
|
||||||
getValuesToCheck(setting, valueAttribute) {
|
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) {
|
checkValues(values, path, currentValue, restriction) {
|
||||||
var extraModels = {settings: this.props.settingsForChecks};
|
var extraModels = {settings: this.props.settingsForChecks};
|
||||||
var result = _.all(values, (value) => {
|
var result = _.all(values, (value) => {
|
||||||
this.props.settingsForChecks.set(path, 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);
|
this.props.settingsForChecks.set(path, currentValue);
|
||||||
return result;
|
return result;
|
||||||
|
@ -82,7 +102,12 @@ var SettingSection = React.createClass({
|
||||||
var role = roles.findWhere({name: roleName});
|
var role = roles.findWhere({name: roleName});
|
||||||
if (_.any(role.get('restrictions'), (restriction) => {
|
if (_.any(role.get('restrictions'), (restriction) => {
|
||||||
restriction = utils.expandRestriction(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 this.checkValues(valuesToCheck, pathToCheck, setting[valueAttribute], restriction);
|
||||||
}
|
}
|
||||||
})) return role.get('label');
|
})) return role.get('label');
|
||||||
|
@ -95,7 +120,10 @@ var SettingSection = React.createClass({
|
||||||
var dependentRestrictions = {};
|
var dependentRestrictions = {};
|
||||||
var addDependentRestrictions = (setting, label) => {
|
var addDependentRestrictions = (setting, label) => {
|
||||||
var result = _.filter(_.map(setting.restrictions, utils.expandRestriction),
|
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) {
|
if (result.length) {
|
||||||
dependentRestrictions[label] = result.concat(dependentRestrictions[label] || []);
|
dependentRestrictions[label] = result.concat(dependentRestrictions[label] || []);
|
||||||
|
@ -106,7 +134,8 @@ var SettingSection = React.createClass({
|
||||||
// don't take into account hidden dependent settings
|
// don't take into account hidden dependent settings
|
||||||
if (this.props.checkRestrictions('hide', section.metadata).result) return;
|
if (this.props.checkRestrictions('hide', section.metadata).result) return;
|
||||||
_.each(section, (setting, settingName) => {
|
_.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) ||
|
if (!this.areCalculationsPossible(setting) ||
|
||||||
this.props.makePath(sectionName, settingName) == path ||
|
this.props.makePath(sectionName, settingName) == path ||
|
||||||
this.props.checkRestrictions('hide', setting).result
|
this.props.checkRestrictions('hide', setting).result
|
||||||
|
@ -124,7 +153,10 @@ var SettingSection = React.createClass({
|
||||||
var valueAttribute = this.props.getValueAttribute(settingName);
|
var valueAttribute = this.props.getValueAttribute(settingName);
|
||||||
var pathToCheck = this.props.makePath(path, valueAttribute);
|
var pathToCheck = this.props.makePath(path, valueAttribute);
|
||||||
var valuesToCheck = this.getValuesToCheck(currentSetting, 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) => {
|
return _.compact(_.map(dependentRestrictions, (restrictions, label) => {
|
||||||
if (_.any(restrictions, checkValues)) return label;
|
if (_.any(restrictions, checkValues)) return label;
|
||||||
}));
|
}));
|
||||||
|
@ -155,18 +187,25 @@ var SettingSection = React.createClass({
|
||||||
var pluginMetadata = this.props.settings.get(pluginName).metadata;
|
var pluginMetadata = this.props.settings.get(pluginName).metadata;
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
// check for editable plugin version
|
// 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) {
|
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);
|
this.onPluginVersionChange(pluginName, editableVersion);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var initialVersion = this.props.initialAttributes[pluginName].metadata.chosen_id;
|
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) {
|
renderTitle(options) {
|
||||||
var {metadata, sectionName, isGroupDisabled, processedGroupDependencies, showSettingGroupWarning, groupWarning, isPlugin} = options;
|
var {metadata, sectionName, isGroupDisabled, processedGroupDependencies,
|
||||||
|
showSettingGroupWarning, groupWarning, isPlugin} = options;
|
||||||
return metadata.toggleable ?
|
return metadata.toggleable ?
|
||||||
<Input
|
<Input
|
||||||
type='checkbox'
|
type='checkbox'
|
||||||
|
@ -178,10 +217,20 @@ var SettingSection = React.createClass({
|
||||||
onChange={isPlugin ? _.partial(this.togglePlugin, sectionName) : this.props.onChange}
|
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) {
|
renderCustomControl(options) {
|
||||||
var {setting, settingKey, error, isSettingDisabled, showSettingWarning, settingWarning, CustomControl, path} = options;
|
var {
|
||||||
|
setting, settingKey, error, isSettingDisabled, showSettingWarning,
|
||||||
|
settingWarning, CustomControl, path
|
||||||
|
} = options;
|
||||||
return <CustomControl
|
return <CustomControl
|
||||||
{...setting}
|
{...setting}
|
||||||
{... _.pick(this.props, 'cluster', 'settings', 'configModels')}
|
{... _.pick(this.props, 'cluster', 'settings', 'configModels')}
|
||||||
|
@ -193,7 +242,8 @@ var SettingSection = React.createClass({
|
||||||
/>;
|
/>;
|
||||||
},
|
},
|
||||||
renderRadioGroup(options) {
|
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))
|
var values = _.chain(_.cloneDeep(setting.values))
|
||||||
.map((value) => {
|
.map((value) => {
|
||||||
var processedValueRestrictions = this.props.checkRestrictions('disable', value);
|
var processedValueRestrictions = this.props.checkRestrictions('disable', value);
|
||||||
|
@ -218,7 +268,8 @@ var SettingSection = React.createClass({
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
renderInput(options) {
|
renderInput(options) {
|
||||||
var {setting, settingKey, error, isSettingDisabled, showSettingWarning, settingWarning, settingName} = options;
|
var {setting, settingKey, error, isSettingDisabled, showSettingWarning, settingWarning,
|
||||||
|
settingName} = options;
|
||||||
var settingDescription = setting.description &&
|
var settingDescription = setting.description &&
|
||||||
<span dangerouslySetInnerHTML={{__html: utils.urlify(_.escape(setting.description))}} />;
|
<span dangerouslySetInnerHTML={{__html: utils.urlify(_.escape(setting.description))}} />;
|
||||||
return <Input
|
return <Input
|
||||||
|
@ -242,18 +293,25 @@ var SettingSection = React.createClass({
|
||||||
var section = settings.get(sectionName);
|
var section = settings.get(sectionName);
|
||||||
var isPlugin = settings.isPlugin(section);
|
var isPlugin = settings.isPlugin(section);
|
||||||
var metadata = section.metadata;
|
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 processedGroupRestrictions = this.processRestrictions(metadata);
|
||||||
var processedGroupDependencies = this.checkDependencies(sectionName, 'metadata');
|
var processedGroupDependencies = this.checkDependencies(sectionName, 'metadata');
|
||||||
var isGroupAlwaysEditable = isPlugin ? _.any(metadata.versions, (version) => version.metadata.always_editable) : metadata.always_editable;
|
var isGroupAlwaysEditable = isPlugin ? _.any(metadata.versions, (version) => {
|
||||||
var isGroupDisabled = this.props.locked || (this.props.lockedCluster && !isGroupAlwaysEditable) || processedGroupRestrictions.result;
|
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 showSettingGroupWarning = !this.props.lockedCluster || metadata.always_editable;
|
||||||
var groupWarning = _.compact([processedGroupRestrictions.message, processedGroupDependencies.message]).join(' ');
|
var groupWarning = _.compact([processedGroupRestrictions.message,
|
||||||
|
processedGroupDependencies.message]).join(' ');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'setting-section setting-section-' + sectionName}>
|
<div className={'setting-section setting-section-' + sectionName}>
|
||||||
<h3>
|
<h3>
|
||||||
{this.renderTitle({metadata, sectionName, isGroupDisabled, processedGroupDependencies, showSettingGroupWarning, groupWarning, isPlugin})}
|
{this.renderTitle({metadata, sectionName, isGroupDisabled, processedGroupDependencies,
|
||||||
|
showSettingGroupWarning, groupWarning, isPlugin})}
|
||||||
</h3>
|
</h3>
|
||||||
<div>
|
<div>
|
||||||
{isPlugin &&
|
{isPlugin &&
|
||||||
|
@ -267,7 +325,9 @@ var SettingSection = React.createClass({
|
||||||
data: version.metadata.plugin_id,
|
data: version.metadata.plugin_id,
|
||||||
label: version.metadata.plugin_version,
|
label: version.metadata.plugin_version,
|
||||||
defaultChecked: version.metadata.plugin_id == metadata.chosen_id,
|
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)}
|
}, this)}
|
||||||
onChange={this.onPluginVersionChange}
|
onChange={this.onPluginVersionChange}
|
||||||
|
@ -281,11 +341,16 @@ var SettingSection = React.createClass({
|
||||||
var error = (settings.validationError || {})[path];
|
var error = (settings.validationError || {})[path];
|
||||||
var processedSettingRestrictions = this.processRestrictions(setting, settingName);
|
var processedSettingRestrictions = this.processRestrictions(setting, settingName);
|
||||||
var processedSettingDependencies = this.checkDependencies(sectionName, settingName);
|
var processedSettingDependencies = this.checkDependencies(sectionName, settingName);
|
||||||
var isSettingDisabled = isGroupDisabled || (metadata.toggleable && !metadata.enabled) || processedSettingRestrictions.result || processedSettingDependencies.result;
|
var isSettingDisabled = isGroupDisabled ||
|
||||||
var showSettingWarning = showSettingGroupWarning && !isGroupDisabled && (!metadata.toggleable || metadata.enabled);
|
(metadata.toggleable && !metadata.enabled) ||
|
||||||
var settingWarning = _.compact([processedSettingRestrictions.message, processedSettingDependencies.message]).join(' ');
|
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
|
// support of custom controls
|
||||||
var CustomControl = customControls[setting.type];
|
var CustomControl = customControls[setting.type];
|
||||||
|
|
|
@ -52,7 +52,8 @@ var SettingsTab = React.createClass({
|
||||||
configModels: {
|
configModels: {
|
||||||
cluster: this.props.cluster,
|
cluster: this.props.cluster,
|
||||||
settings: settings,
|
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,
|
version: app.version,
|
||||||
release: this.props.cluster.get('release'),
|
release: this.props.cluster.get('release'),
|
||||||
default: settings
|
default: settings
|
||||||
|
@ -69,7 +70,10 @@ var SettingsTab = React.createClass({
|
||||||
this.loadInitialSettings();
|
this.loadInitialSettings();
|
||||||
},
|
},
|
||||||
hasChanges() {
|
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() {
|
applyChanges() {
|
||||||
if (!this.isSavingPossible()) return $.Deferred().reject();
|
if (!this.isSavingPossible()) return $.Deferred().reject();
|
||||||
|
@ -110,14 +114,16 @@ var SettingsTab = React.createClass({
|
||||||
var settings = this.props.cluster.get('settings');
|
var settings = this.props.cluster.get('settings');
|
||||||
var lockedCluster = !this.props.cluster.isAvailableForSettingsChanges();
|
var lockedCluster = !this.props.cluster.isAvailableForSettingsChanges();
|
||||||
var defaultSettings = new models.Settings();
|
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) {
|
if (deferred) {
|
||||||
this.setState({actionInProgress: true});
|
this.setState({actionInProgress: true});
|
||||||
deferred
|
deferred
|
||||||
.done(() => {
|
.done(() => {
|
||||||
_.each(settings.attributes, (section, sectionName) => {
|
_.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) => {
|
_.each(section, (setting, settingName) => {
|
||||||
// do not update hidden settings (hack for #1442143),
|
// do not update hidden settings (hack for #1442143),
|
||||||
// the same for settings with group network
|
// the same for settings with group network
|
||||||
|
@ -164,11 +170,18 @@ var SettingsTab = React.createClass({
|
||||||
settings.isValid({models: this.state.configModels});
|
settings.isValid({models: this.state.configModels});
|
||||||
},
|
},
|
||||||
checkRestrictions(action, setting) {
|
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() {
|
isSavingPossible() {
|
||||||
var settings = this.props.cluster.get('settings');
|
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
|
// network settings are shown on Networks tab, so they should not block
|
||||||
// saving of changes on Settings tab
|
// saving of changes on Settings tab
|
||||||
var areSettingsValid = !_.any(_.keys(settings.validationError), (settingPath) => {
|
var areSettingsValid = !_.any(_.keys(settings.validationError), (settingPath) => {
|
||||||
|
@ -184,9 +197,15 @@ var SettingsTab = React.createClass({
|
||||||
var settingsGroupList = settings.getGroupList();
|
var settingsGroupList = settings.getGroupList();
|
||||||
var locked = this.state.actionInProgress || !!cluster.task({group: 'deployment', active: true});
|
var locked = this.state.actionInProgress || !!cluster.task({group: 'deployment', active: true});
|
||||||
var lockedCluster = !cluster.isAvailableForSettingsChanges();
|
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 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 = {
|
var classes = {
|
||||||
row: true,
|
row: true,
|
||||||
'changes-locked': lockedCluster
|
'changes-locked': lockedCluster
|
||||||
|
@ -237,7 +256,10 @@ var SettingsTab = React.createClass({
|
||||||
return (settings.validationError || {})[settings.makePath(sectionName, settingName)];
|
return (settings.validationError || {})[settings.makePath(sectionName, settingName)];
|
||||||
});
|
});
|
||||||
if (!_.isEmpty(pickedSettings)) {
|
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) => {
|
{_.map(groupedSettings, (selectedGroup, groupName) => {
|
||||||
if (groupName != this.props.activeSettingsSectionName) return null;
|
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 (
|
return (
|
||||||
<div className={'col-xs-10 forms-box ' + groupName} key={groupName}>
|
<div className={'col-xs-10 forms-box ' + groupName} key={groupName}>
|
||||||
{_.map(sortedSections, (sectionName) => {
|
{_.map(sortedSections, (sectionName) => {
|
||||||
|
@ -295,13 +319,25 @@ var SettingsTab = React.createClass({
|
||||||
<div className='col-xs-12 page-buttons content-elements'>
|
<div className='col-xs-12 page-buttons content-elements'>
|
||||||
<div className='well clearfix'>
|
<div className='well clearfix'>
|
||||||
<div className='btn-group pull-right'>
|
<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')}
|
{i18n('common.load_defaults_button')}
|
||||||
</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')}
|
{i18n('common.cancel_changes_button')}
|
||||||
</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')}
|
{i18n('common.save_settings_button')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -316,7 +352,11 @@ var SettingSubtabs = React.createClass({
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className='col-xs-2'>
|
<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) => {
|
this.props.settingsGroupList.map((groupName) => {
|
||||||
if (!this.props.groupedSettings[groupName]) return null;
|
if (!this.props.groupedSettings[groupName]) return null;
|
||||||
|
@ -326,7 +366,9 @@ var SettingSubtabs = React.createClass({
|
||||||
<li
|
<li
|
||||||
key={groupName}
|
key={groupName}
|
||||||
role='presentation'
|
role='presentation'
|
||||||
className={utils.classNames({active: groupName == this.props.activeSettingsSectionName})}
|
className={utils.classNames({
|
||||||
|
active: groupName == this.props.activeSettingsSectionName
|
||||||
|
})}
|
||||||
onClick={_.partial(this.props.setActiveSettingsGroupName, groupName)}
|
onClick={_.partial(this.props.setActiveSettingsGroupName, groupName)}
|
||||||
>
|
>
|
||||||
<a className={'subtab-link-' + groupName}>
|
<a className={'subtab-link-' + groupName}>
|
||||||
|
|
|
@ -58,7 +58,11 @@ ClustersPage = React.createClass({
|
||||||
ClusterList = React.createClass({
|
ClusterList = React.createClass({
|
||||||
mixins: [backboneMixin('clusters')],
|
mixins: [backboneMixin('clusters')],
|
||||||
createCluster() {
|
createCluster() {
|
||||||
CreateClusterWizard.show({clusters: this.props.clusters, modalClass: 'wizard', backdrop: 'static'});
|
CreateClusterWizard.show({
|
||||||
|
clusters: this.props.clusters,
|
||||||
|
modalClass: 'wizard',
|
||||||
|
backdrop: 'static'
|
||||||
|
});
|
||||||
},
|
},
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
|
@ -126,7 +130,8 @@ Cluster = React.createClass({
|
||||||
var cluster = this.props.cluster;
|
var cluster = this.props.cluster;
|
||||||
var status = cluster.get('status');
|
var status = cluster.get('status');
|
||||||
var nodes = cluster.get('nodes');
|
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 deploymentTask = cluster.task({group: 'deployment', active: true});
|
||||||
var Tag = isClusterDeleting ? 'div' : 'a';
|
var Tag = isClusterDeleting ? 'div' : 'a';
|
||||||
return (
|
return (
|
||||||
|
@ -140,15 +145,29 @@ Cluster = React.createClass({
|
||||||
>
|
>
|
||||||
<div className='name'>{cluster.get('name')}</div>
|
<div className='name'>{cluster.get('name')}</div>
|
||||||
<div className='tech-info'>
|
<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>
|
<div key='nodes-value' className='value'>{nodes.length}</div>
|
||||||
{!!nodes.length && [
|
{!!nodes.length && [
|
||||||
<div key='cpu-title' className='item'>{i18n('clusters_page.cluster_hardware_cpu')}</div>,
|
<div key='cpu-title' className='item'>
|
||||||
<div key='cpu-value' className='value'>{nodes.resources('cores')} ({nodes.resources('ht_cores')})</div>,
|
{i18n('clusters_page.cluster_hardware_cpu')}
|
||||||
<div key='hdd-title' className='item'>{i18n('clusters_page.cluster_hardware_hdd')}</div>,
|
</div>,
|
||||||
<div key='hdd-value' className='value'>{nodes.resources('hdd') ? utils.showDiskSize(nodes.resources('hdd')) : '?GB'}</div>,
|
<div key='cpu-value' className='value'>
|
||||||
<div key='ram-title' className='item'>{i18n('clusters_page.cluster_hardware_ram')}</div>,
|
{nodes.resources('cores')} ({nodes.resources('ht_cores')})
|
||||||
<div key='ram-value' className='value'>{nodes.resources('ram') ? utils.showMemorySize(nodes.resources('ram')) : '?GB'}</div>
|
</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>
|
||||||
<div className='status text-info'>
|
<div className='status text-info'>
|
||||||
|
@ -157,9 +176,14 @@ Cluster = React.createClass({
|
||||||
<div
|
<div
|
||||||
className={utils.classNames({
|
className={utils.classNames({
|
||||||
'progress-bar': true,
|
'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>
|
||||||
</div>
|
</div>
|
||||||
:
|
:
|
||||||
|
|
|
@ -30,7 +30,10 @@ import {outerClickMixin} from 'component_mixins';
|
||||||
|
|
||||||
export var Input = React.createClass({
|
export var Input = React.createClass({
|
||||||
propTypes: {
|
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,
|
name: React.PropTypes.node,
|
||||||
label: React.PropTypes.node,
|
label: React.PropTypes.node,
|
||||||
debounce: React.PropTypes.bool,
|
debounce: React.PropTypes.bool,
|
||||||
|
@ -147,19 +150,35 @@ export var Input = React.createClass({
|
||||||
className='form-control file-name'
|
className='form-control file-name'
|
||||||
type='text'
|
type='text'
|
||||||
placeholder={i18n('file.placeholder')}
|
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}
|
onClick={this.pickFile}
|
||||||
disabled={this.props.disabled}
|
disabled={this.props.disabled}
|
||||||
readOnly
|
readOnly
|
||||||
/>
|
/>
|
||||||
<div className='input-group-addon' onClick={this.state.fileName ? this.removeFile : this.pickFile}>
|
<div
|
||||||
<i className={this.state.fileName && !this.props.disabled ? 'glyphicon glyphicon-remove' : 'glyphicon glyphicon-file'} />
|
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>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
{this.props.toggleable &&
|
{this.props.toggleable &&
|
||||||
<div className='input-group-addon' onClick={this.togglePassword}>
|
<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>
|
</div>
|
||||||
}
|
}
|
||||||
{isCheckboxOrRadio && <span> </span>}
|
{isCheckboxOrRadio && <span> </span>}
|
||||||
|
@ -182,7 +201,8 @@ export var Input = React.createClass({
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
renderDescription() {
|
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>;
|
return <span key='description' className='help-block'>{text}</span>;
|
||||||
},
|
},
|
||||||
renderWrapper(children) {
|
renderWrapper(children) {
|
||||||
|
@ -372,7 +392,9 @@ export var Tooltip = React.createClass({
|
||||||
$(ReactDOM.findDOMNode(this.refs.tooltip)).tooltip('destroy');
|
$(ReactDOM.findDOMNode(this.refs.tooltip)).tooltip('destroy');
|
||||||
},
|
},
|
||||||
render() {
|
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 (
|
return (
|
||||||
<div className={this.props.wrapperClassName} ref='tooltip'>
|
<div className={this.props.wrapperClassName} ref='tooltip'>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
|
|
|
@ -40,7 +40,13 @@ customControls.custom_repo_configuration = React.createClass({
|
||||||
error.uri = i18n(ns + 'invalid_repo');
|
error.uri = i18n(ns + 'invalid_repo');
|
||||||
}
|
}
|
||||||
var priority = repo.priority;
|
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');
|
error.priority = i18n(ns + 'invalid_priority');
|
||||||
}
|
}
|
||||||
return _.isEmpty(error) ? null : error;
|
return _.isEmpty(error) ? null : error;
|
||||||
|
@ -48,7 +54,9 @@ customControls.custom_repo_configuration = React.createClass({
|
||||||
return _.compact(errors).length ? errors : null;
|
return _.compact(errors).length ? errors : null;
|
||||||
},
|
},
|
||||||
repoToString(repo, os) {
|
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
|
if (!repoData.length) return ''; // in case of new repo
|
||||||
return repoData.join(' ');
|
return repoData.join(' ');
|
||||||
}
|
}
|
||||||
|
@ -58,10 +66,12 @@ customControls.custom_repo_configuration = React.createClass({
|
||||||
},
|
},
|
||||||
getDefaultProps() {
|
getDefaultProps() {
|
||||||
return {
|
return {
|
||||||
|
/* eslint-disable max-len */
|
||||||
repoRegexes: {
|
repoRegexes: {
|
||||||
Ubuntu: /^(deb|deb-src)\s+(\w+:\/\/[\w\-.\/]+(?::\d+)?[\w\-.\/]+)\s+([\w\-.\/]+)(?:\s+([\w\-.\/\s]+))?$/i,
|
Ubuntu: /^(deb|deb-src)\s+(\w+:\/\/[\w\-.\/]+(?::\d+)?[\w\-.\/]+)\s+([\w\-.\/]+)(?:\s+([\w\-.\/\s]+))?$/i,
|
||||||
CentOS: /^(\w+:\/\/[\w\-.\/]+(?::\d+)?[\w\-.\/]+)\s*$/i
|
CentOS: /^(\w+:\/\/[\w\-.\/]+(?::\d+)?[\w\-.\/]+)\s*$/i
|
||||||
},
|
},
|
||||||
|
/* eslint-enable max-len */
|
||||||
repoAttributes: {
|
repoAttributes: {
|
||||||
Ubuntu: ['type', 'uri', 'suite', 'section'],
|
Ubuntu: ['type', 'uri', 'suite', 'section'],
|
||||||
CentOS: ['uri']
|
CentOS: ['uri']
|
||||||
|
@ -130,7 +140,12 @@ customControls.custom_repo_configuration = React.createClass({
|
||||||
return (
|
return (
|
||||||
<div className='repos' key={this.state.key}>
|
<div className='repos' key={this.state.key}>
|
||||||
{this.props.description &&
|
{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) => {
|
{this.props.value.map((repo, index) => {
|
||||||
var error = (this.props.error || {})[index];
|
var error = (this.props.error || {})[index];
|
||||||
|
@ -162,7 +177,9 @@ customControls.custom_repo_configuration = React.createClass({
|
||||||
<Input
|
<Input
|
||||||
{...props}
|
{...props}
|
||||||
defaultValue={repo.priority}
|
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'
|
wrapperClassName='repo-priority'
|
||||||
onChange={this.changeRepos.bind(this, 'change_priority')}
|
onChange={this.changeRepos.bind(this, 'change_priority')}
|
||||||
extraContent={index > 0 && this.renderDeleteButton(index)}
|
extraContent={index > 0 && this.renderDeleteButton(index)}
|
||||||
|
@ -174,7 +191,12 @@ customControls.custom_repo_configuration = React.createClass({
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
<div className='buttons'>
|
<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')}
|
{i18n(ns + 'add_repo_button')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -67,14 +67,20 @@ export var dialogMixin = {
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
} else {
|
} 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) {
|
updateProps(partialProps) {
|
||||||
var props;
|
var props;
|
||||||
props = _.extend({}, this.props, partialProps);
|
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() {
|
getInitialState() {
|
||||||
return {
|
return {
|
||||||
|
@ -118,7 +124,11 @@ export var dialogMixin = {
|
||||||
if (e.target.tagName == 'A' && !e.target.target && e.target.href) this.close();
|
if (e.target.tagName == 'A' && !e.target.target && e.target.href) this.close();
|
||||||
},
|
},
|
||||||
closeOnEscapeKey(e) {
|
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);
|
if (_.isFunction(this.onKeyDown)) this.onKeyDown(e);
|
||||||
},
|
},
|
||||||
showError(response, message) {
|
showError(response, message) {
|
||||||
|
@ -137,7 +147,12 @@ export var dialogMixin = {
|
||||||
var classes = {'modal fade': true};
|
var classes = {'modal fade': true};
|
||||||
classes[this.props.modalClass] = this.props.modalClass;
|
classes[this.props.modalClass] = this.props.modalClass;
|
||||||
return (
|
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-dialog'>
|
||||||
<div className='modal-content'>
|
<div className='modal-content'>
|
||||||
<div className='modal-header'>
|
<div className='modal-header'>
|
||||||
|
@ -146,7 +161,13 @@ export var dialogMixin = {
|
||||||
<span aria-hidden='true'>×</span>
|
<span aria-hidden='true'>×</span>
|
||||||
</button>
|
</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>
|
||||||
<div className='modal-body'>
|
<div className='modal-body'>
|
||||||
{this.props.error ?
|
{this.props.error ?
|
||||||
|
@ -159,7 +180,9 @@ export var dialogMixin = {
|
||||||
{this.renderFooter && !this.props.error ?
|
{this.renderFooter && !this.props.error ?
|
||||||
this.renderFooter()
|
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>
|
||||||
</div>
|
</div>
|
||||||
|
@ -219,7 +242,9 @@ export var NailgunUnavailabilityDialog = React.createClass({
|
||||||
this.startCountdown();
|
this.startCountdown();
|
||||||
},
|
},
|
||||||
componentDidMount() {
|
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() {
|
startCountdown() {
|
||||||
this.activeTimeout = _.delay(this.countdown, 1000);
|
this.activeTimeout = _.delay(this.countdown, 1000);
|
||||||
|
@ -242,7 +267,9 @@ export var NailgunUnavailabilityDialog = React.createClass({
|
||||||
reinitializeUI() {
|
reinitializeUI() {
|
||||||
app.initialize().then(this.close, () => {
|
app.initialize().then(this.close, () => {
|
||||||
var {retryDelayIntervals} = this.props;
|
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({
|
_.defer(() => this.setState({
|
||||||
actionInProgress: false,
|
actionInProgress: false,
|
||||||
currentDelay: nextDelay,
|
currentDelay: nextDelay,
|
||||||
|
@ -266,7 +293,10 @@ export var NailgunUnavailabilityDialog = React.createClass({
|
||||||
{i18n('dialog.nailgun_unavailability.unavailability_message')}
|
{i18n('dialog.nailgun_unavailability.unavailability_message')}
|
||||||
{' '}
|
{' '}
|
||||||
{this.state.currentDelay ?
|
{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')
|
i18n('dialog.nailgun_unavailability.retrying')
|
||||||
}
|
}
|
||||||
|
@ -315,7 +345,8 @@ export var DiscardNodeChangesDialog = React.createClass({
|
||||||
Backbone.sync('update', nodes)
|
Backbone.sync('update', nodes)
|
||||||
.then(() => this.props.cluster.fetchRelated('nodes'))
|
.then(() => this.props.cluster.fetchRelated('nodes'))
|
||||||
.done(() => {
|
.done(() => {
|
||||||
dispatcher.trigger('updateNodeStats networkConfigurationUpdated labelsConfigurationUpdated');
|
dispatcher
|
||||||
|
.trigger('updateNodeStats networkConfigurationUpdated labelsConfigurationUpdated');
|
||||||
this.state.result.resolve();
|
this.state.result.resolve();
|
||||||
this.close();
|
this.close();
|
||||||
})
|
})
|
||||||
|
@ -333,8 +364,22 @@ export var DiscardNodeChangesDialog = React.createClass({
|
||||||
},
|
},
|
||||||
renderFooter() {
|
renderFooter() {
|
||||||
return ([
|
return ([
|
||||||
<button key='cancel' className='btn btn-default' onClick={this.close} disabled={this.state.actionInProgress}>{i18n('common.cancel_button')}</button>,
|
<button
|
||||||
<button key='discard' className='btn btn-danger' disabled={this.state.actionInProgress} onClick={this.discardNodeChanges}>{i18n('dialog.discard_changes.discard_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({
|
export var DeployChangesDialog = React.createClass({
|
||||||
mixins: [
|
mixins: [
|
||||||
dialogMixin,
|
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({
|
backboneMixin({
|
||||||
modelOrCollection(props) {
|
modelOrCollection(props) {
|
||||||
return props.cluster.get('tasks');
|
return props.cluster.get('tasks');
|
||||||
|
@ -400,7 +446,14 @@ export var DeployChangesDialog = React.createClass({
|
||||||
},
|
},
|
||||||
renderFooter() {
|
renderFooter() {
|
||||||
return ([
|
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'
|
<button key='deploy'
|
||||||
className='btn start-deployment-btn btn-success'
|
className='btn start-deployment-btn btn-success'
|
||||||
disabled={this.state.actionInProgress || this.state.isInvalid}
|
disabled={this.state.actionInProgress || this.state.isInvalid}
|
||||||
|
@ -435,8 +488,22 @@ export var ProvisionVMsDialog = React.createClass({
|
||||||
},
|
},
|
||||||
renderFooter() {
|
renderFooter() {
|
||||||
return ([
|
return ([
|
||||||
<button key='cancel' className='btn btn-default' onClick={this.close} disabled={this.state.actionInProgress}>{i18n('common.cancel_button')}</button>,
|
<button
|
||||||
<button key='provision' className='btn btn-success' disabled={this.state.actionInProgress} onClick={this.startProvisioning}>{i18n('common.start_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');
|
dispatcher.trigger('deploymentTaskStarted');
|
||||||
})
|
})
|
||||||
.fail((response) => {
|
.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() {
|
renderBody() {
|
||||||
return (
|
return (
|
||||||
<div className='text-danger'>
|
<div className='text-danger'>
|
||||||
{this.renderImportantLabel()}
|
{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>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
renderFooter() {
|
renderFooter() {
|
||||||
return ([
|
return ([
|
||||||
<button key='cancel' className='btn btn-default' onClick={this.close} disabled={this.state.actionInProgress}>{i18n('common.cancel_button')}</button>,
|
<button
|
||||||
<button key='deploy' className='btn stop-deployment-btn btn-danger' disabled={this.state.actionInProgress} onClick={this.stopDeployment}>{i18n('common.stop_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() {
|
renderFooter() {
|
||||||
return ([
|
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
|
<button
|
||||||
key='remove'
|
key='remove'
|
||||||
className='btn btn-danger remove-cluster-btn'
|
className='btn btn-danger remove-cluster-btn'
|
||||||
disabled={this.state.actionInProgress || this.state.confirmation && _.isUndefined(this.state.confirmationError) || this.state.confirmationError}
|
disabled={this.state.actionInProgress || this.state.confirmation &&
|
||||||
onClick={this.props.cluster.get('status') == 'new' || this.state.confirmation ? this.removeCluster : this.showConfirmationForm}
|
_.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')}
|
{i18n('common.delete_button')}
|
||||||
</button>
|
</button>
|
||||||
|
@ -602,11 +697,19 @@ export var ResetEnvironmentDialog = React.createClass({
|
||||||
},
|
},
|
||||||
renderFooter() {
|
renderFooter() {
|
||||||
return ([
|
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
|
<button
|
||||||
key='reset'
|
key='reset'
|
||||||
className='btn btn-danger reset-environment-btn'
|
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}
|
onClick={this.state.confirmation ? this.resetEnvironment : this.showConfirmationForm}
|
||||||
>
|
>
|
||||||
{i18n('common.reset_button')}
|
{i18n('common.reset_button')}
|
||||||
|
@ -634,7 +737,11 @@ export var ShowNodeInfoDialog = React.createClass({
|
||||||
},
|
},
|
||||||
goToConfigurationScreen(url) {
|
goToConfigurationScreen(url) {
|
||||||
this.close();
|
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) {
|
showSummary(meta, group) {
|
||||||
var summary = '';
|
var summary = '';
|
||||||
|
@ -647,21 +754,27 @@ export var ShowNodeInfoDialog = React.createClass({
|
||||||
if (_.isArray(meta.memory.devices) && meta.memory.devices.length) {
|
if (_.isArray(meta.memory.devices) && meta.memory.devices.length) {
|
||||||
var sizes = _.countBy(_.pluck(meta.memory.devices, 'size'), utils.showMemorySize);
|
var sizes = _.countBy(_.pluck(meta.memory.devices, 'size'), utils.showMemorySize);
|
||||||
summary = _.map(_.keys(sizes).sort(), (size) => sizes[size] + ' x ' + size).join(', ');
|
summary = _.map(_.keys(sizes).sort(), (size) => sizes[size] + ' x ' + size).join(', ');
|
||||||
summary += ', ' + utils.showMemorySize(meta.memory.total) + ' ' + i18n('dialog.show_node.total');
|
summary += ', ' + utils.showMemorySize(meta.memory.total) + ' ' +
|
||||||
} else summary = utils.showMemorySize(meta.memory.total) + ' ' + i18n('dialog.show_node.total');
|
i18n('dialog.show_node.total');
|
||||||
|
} else summary = utils.showMemorySize(meta.memory.total) + ' ' +
|
||||||
|
i18n('dialog.show_node.total');
|
||||||
break;
|
break;
|
||||||
case 'disks':
|
case 'disks':
|
||||||
summary = meta.disks.length + ' ';
|
summary = meta.disks.length + ' ';
|
||||||
summary += i18n('dialog.show_node.drive', {count: 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;
|
break;
|
||||||
case 'cpu':
|
case 'cpu':
|
||||||
var frequencies = _.countBy(_.pluck(meta.cpu.spec, 'frequency'), utils.showFrequency);
|
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;
|
break;
|
||||||
case 'interfaces':
|
case 'interfaces':
|
||||||
var bandwidths = _.countBy(_.pluck(meta.interfaces, 'current_speed'), utils.showBandwidth);
|
var bandwidths = _.countBy(_.pluck(meta.interfaces, 'current_speed'),
|
||||||
summary = _.map(_.keys(bandwidths).sort(), (bandwidth) => bandwidths[bandwidth] + ' x ' + bandwidth).join(', ');
|
utils.showBandwidth);
|
||||||
|
summary = _.map(_.keys(bandwidths).sort(), (bandwidth) => bandwidths[bandwidth] +
|
||||||
|
' x ' + bandwidth).join(', ');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} catch (ignore) {}
|
} catch (ignore) {}
|
||||||
|
@ -714,8 +827,10 @@ export var ShowNodeInfoDialog = React.createClass({
|
||||||
},
|
},
|
||||||
assignAccordionEvents() {
|
assignAccordionEvents() {
|
||||||
$('.panel-collapse', ReactDOM.findDOMNode(this))
|
$('.panel-collapse', ReactDOM.findDOMNode(this))
|
||||||
.on('show.bs.collapse', (e) => $(e.currentTarget).siblings('.panel-heading').find('i').removeClass('glyphicon-plus').addClass('glyphicon-minus'))
|
.on('show.bs.collapse', (e) => $(e.currentTarget).siblings('.panel-heading').find('i')
|
||||||
.on('hide.bs.collapse', (e) => $(e.currentTarget).siblings('.panel-heading').find('i').removeClass('glyphicon-minus').addClass('glyphicon-plus'))
|
.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());
|
.on('hidden.bs.collapse', (e) => e.stopPropagation());
|
||||||
},
|
},
|
||||||
toggle(groupIndex) {
|
toggle(groupIndex) {
|
||||||
|
@ -777,7 +892,8 @@ export var ShowNodeInfoDialog = React.createClass({
|
||||||
var groups = _.sortBy(_.keys(meta), (group) => _.indexOf(groupOrder, group));
|
var groups = _.sortBy(_.keys(meta), (group) => _.indexOf(groupOrder, group));
|
||||||
var sortOrder = {
|
var sortOrder = {
|
||||||
disks: ['name', 'model', 'size'],
|
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');
|
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-5'><div className='node-image-outline' /></div>
|
||||||
<div className='col-xs-7 node-summary'>
|
<div className='col-xs-7 node-summary'>
|
||||||
{this.props.cluster &&
|
{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 &&
|
{this.props.nodeNetworkGroup &&
|
||||||
<div>
|
<div>
|
||||||
<strong>{i18n('dialog.show_node.node_network_group')}: </strong>
|
<strong>{i18n('dialog.show_node.node_network_group')}: </strong>
|
||||||
{this.props.nodeNetworkGroup.get('name')}
|
{this.props.nodeNetworkGroup.get('name')}
|
||||||
</div>
|
</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.mac_address_label')}: </strong>
|
||||||
<div><strong>{i18n('dialog.show_node.fqdn_label')}: </strong>{(node.get('meta').system || {}).fqdn || node.get('fqdn') || i18n('common.not_available')}</div>
|
{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'>
|
<div className='change-hostname'>
|
||||||
<strong>{i18n('dialog.show_node.hostname_label')}: </strong>
|
<strong>{i18n('dialog.show_node.hostname_label')}: </strong>
|
||||||
{this.state.isRenaming ?
|
{this.state.isRenaming ?
|
||||||
|
@ -832,28 +957,57 @@ export var ShowNodeInfoDialog = React.createClass({
|
||||||
{_.map(groups, (group, groupIndex) => {
|
{_.map(groups, (group, groupIndex) => {
|
||||||
var groupEntries = meta[group];
|
var groupEntries = meta[group];
|
||||||
var subEntries = [];
|
var subEntries = [];
|
||||||
if (group == 'interfaces' || group == 'disks') groupEntries = _.sortBy(groupEntries, 'name');
|
if (group == 'interfaces' || group == 'disks') {
|
||||||
if (_.isPlainObject(groupEntries)) subEntries = _.find(_.values(groupEntries), _.isArray);
|
groupEntries = _.sortBy(groupEntries, 'name');
|
||||||
|
}
|
||||||
|
if (_.isPlainObject(groupEntries)) {
|
||||||
|
subEntries = _.find(_.values(groupEntries), _.isArray);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className='panel panel-default' key={group}>
|
<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 className='panel-title'>
|
||||||
<div data-parent='#accordion' aria-expanded='true' aria-controls={'body' + group}>
|
<div
|
||||||
<strong>{i18n('node_details.' + group, {defaultValue: group})}</strong> {this.showSummary(meta, group)}
|
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' />
|
<i className='glyphicon glyphicon-plus pull-right' />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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'>
|
<div className='panel-body enable-selection'>
|
||||||
{_.isArray(groupEntries) &&
|
{_.isArray(groupEntries) &&
|
||||||
<div>
|
<div>
|
||||||
{_.map(groupEntries, (entry, entryIndex) => {
|
{_.map(groupEntries, (entry, entryIndex) => {
|
||||||
return (
|
return (
|
||||||
<div className='nested-object' key={'entry_' + groupIndex + entryIndex}>
|
<div className='nested-object' key={'entry_' + groupIndex + entryIndex}>
|
||||||
{_.map(utils.sortEntryProperties(entry, sortOrder[group]), (propertyName) => {
|
{_.map(utils.sortEntryProperties(entry, sortOrder[group]),
|
||||||
if (!_.isPlainObject(entry[propertyName]) && !_.isArray(entry[propertyName])) return this.renderNodeInfo(propertyName, this.showPropertyValue(group, propertyName, entry[propertyName]));
|
(propertyName) => {
|
||||||
})}
|
if (!_.isPlainObject(entry[propertyName]) &&
|
||||||
|
!_.isArray(entry[propertyName])) {
|
||||||
|
return this.renderNodeInfo(
|
||||||
|
propertyName,
|
||||||
|
this.showPropertyValue(
|
||||||
|
group, propertyName, entry[propertyName]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@ -862,15 +1016,25 @@ export var ShowNodeInfoDialog = React.createClass({
|
||||||
{_.isPlainObject(groupEntries) &&
|
{_.isPlainObject(groupEntries) &&
|
||||||
<div>
|
<div>
|
||||||
{_.map(groupEntries, (propertyValue, propertyName) => {
|
{_.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) &&
|
{!_.isEmpty(subEntries) &&
|
||||||
<div>
|
<div>
|
||||||
{_.map(subEntries, (subentry, subentrysIndex) => {
|
{_.map(subEntries, (subentry, subentrysIndex) => {
|
||||||
return (
|
return (
|
||||||
<div className='nested-object' key={'subentries_' + groupIndex + subentrysIndex}>
|
<div
|
||||||
|
className='nested-object'
|
||||||
|
key={'subentries_' + groupIndex + subentrysIndex}
|
||||||
|
>
|
||||||
{_.map(utils.sortEntryProperties(subentry), (propertyName) => {
|
{_.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>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -879,7 +1043,8 @@ export var ShowNodeInfoDialog = React.createClass({
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
{(!_.isPlainObject(groupEntries) && !_.isArray(groupEntries) && !_.isUndefined(groupEntries)) &&
|
{(!_.isPlainObject(groupEntries) && !_.isArray(groupEntries) &&
|
||||||
|
!_.isUndefined(groupEntries)) &&
|
||||||
<div>{groupEntries}</div>
|
<div>{groupEntries}</div>
|
||||||
}
|
}
|
||||||
{group == 'config' &&
|
{group == 'config' &&
|
||||||
|
@ -895,7 +1060,8 @@ export var ShowNodeInfoDialog = React.createClass({
|
||||||
<button
|
<button
|
||||||
className='btn btn-success'
|
className='btn btn-success'
|
||||||
onClick={this.saveVMsConf}
|
onClick={this.saveVMsConf}
|
||||||
disabled={this.state.VMsConfValidationError || this.state.actionInProgress}
|
disabled={this.state.VMsConfValidationError ||
|
||||||
|
this.state.actionInProgress}
|
||||||
>
|
>
|
||||||
{i18n('common.save_settings_button')}
|
{i18n('common.save_settings_button')}
|
||||||
</button>
|
</button>
|
||||||
|
@ -915,16 +1081,29 @@ export var ShowNodeInfoDialog = React.createClass({
|
||||||
<div>
|
<div>
|
||||||
{this.props.renderActionButtons && this.props.node.get('cluster') &&
|
{this.props.renderActionButtons && this.props.node.get('cluster') &&
|
||||||
<div className='btn-group' role='group'>
|
<div className='btn-group' role='group'>
|
||||||
<button className='btn btn-default btn-edit-disks' onClick={_.partial(this.goToConfigurationScreen, 'disks')}>
|
<button
|
||||||
{i18n('dialog.show_node.disk_configuration' + (this.props.node.areDisksConfigurable() ? '_action' : ''))}
|
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>
|
||||||
<button className='btn btn-default btn-edit-networks' onClick={_.partial(this.goToConfigurationScreen, 'interfaces')}>
|
<button
|
||||||
{i18n('dialog.show_node.network_configuration' + (this.props.node.areInterfacesConfigurable() ? '_action' : ''))}
|
className='btn btn-default btn-edit-networks'
|
||||||
|
onClick={_.partial(this.goToConfigurationScreen, 'interfaces')}
|
||||||
|
>
|
||||||
|
{i18n('dialog.show_node.network_configuration' +
|
||||||
|
(this.props.node.areInterfacesConfigurable() ? '_action' : ''))}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div className='btn-group' role='group'>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -932,7 +1111,9 @@ export var ShowNodeInfoDialog = React.createClass({
|
||||||
renderNodeInfo(name, value) {
|
renderNodeInfo(name, value) {
|
||||||
return (
|
return (
|
||||||
<div key={name + value} className='node-details-row'>
|
<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}
|
{value}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1042,7 +1223,8 @@ export var DeleteNodesDialog = React.createClass({
|
||||||
{this.renderImportantLabel()}
|
{this.renderImportantLabel()}
|
||||||
{i18n(ns + 'common_message', {count: this.props.nodes.length})}
|
{i18n(ns + 'common_message', {count: this.props.nodes.length})}
|
||||||
<br/>
|
<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})}
|
{!!deployedNodesAmount && i18n(ns + 'deployed_nodes_message', {count: deployedNodesAmount})}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1050,8 +1232,18 @@ export var DeleteNodesDialog = React.createClass({
|
||||||
},
|
},
|
||||||
renderFooter() {
|
renderFooter() {
|
||||||
return [
|
return [
|
||||||
<button key='cancel' className='btn btn-default' onClick={this.close}>{i18n('common.cancel_button')}</button>,
|
<button
|
||||||
<button key='delete' className='btn btn-danger btn-delete' onClick={this.deleteNodes} disabled={this.state.actionInProgress}>{i18n('common.delete_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() {
|
deleteNodes() {
|
||||||
|
@ -1075,12 +1267,14 @@ export var DeleteNodesDialog = React.createClass({
|
||||||
return this.props.cluster.fetchRelated('nodes');
|
return this.props.cluster.fetchRelated('nodes');
|
||||||
})
|
})
|
||||||
.done(() => {
|
.done(() => {
|
||||||
dispatcher.trigger('updateNodeStats networkConfigurationUpdated labelsConfigurationUpdated');
|
dispatcher.trigger('updateNodeStats networkConfigurationUpdated ' +
|
||||||
|
'labelsConfigurationUpdated');
|
||||||
this.state.result.resolve();
|
this.state.result.resolve();
|
||||||
this.close();
|
this.close();
|
||||||
})
|
})
|
||||||
.fail((response) => {
|
.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) {
|
getError(name) {
|
||||||
var ns = 'dialog.change_password.';
|
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 (this.state.newPassword != this.state.confirmationPassword) {
|
||||||
if (name == 'confirmationPassword') return i18n(ns + 'new_password_mismatch');
|
if (name == 'confirmationPassword') return i18n(ns + 'new_password_mismatch');
|
||||||
if (name == 'newPassword') return '';
|
if (name == 'newPassword') return '';
|
||||||
|
@ -1140,7 +1336,12 @@ export var ChangePasswordDialog = React.createClass({
|
||||||
},
|
},
|
||||||
renderFooter() {
|
renderFooter() {
|
||||||
return [
|
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')}
|
{i18n('common.cancel_button')}
|
||||||
</button>,
|
</button>,
|
||||||
<button key='apply' className='btn btn-success' onClick={this.changePassword}
|
<button key='apply' className='btn btn-success' onClick={this.changePassword}
|
||||||
|
@ -1177,7 +1378,8 @@ export var ChangePasswordDialog = React.createClass({
|
||||||
this.setState({actionInProgress: true});
|
this.setState({actionInProgress: true});
|
||||||
keystoneClient.changePassword(this.state.currentPassword, this.state.newPassword)
|
keystoneClient.changePassword(this.state.currentPassword, this.state.newPassword)
|
||||||
.done(() => {
|
.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});
|
app.user.set({token: keystoneClient.token});
|
||||||
this.close();
|
this.close();
|
||||||
})
|
})
|
||||||
|
@ -1225,7 +1427,9 @@ export var RegistrationDialog = React.createClass({
|
||||||
onChange(inputName, value) {
|
onChange(inputName, value) {
|
||||||
var registrationForm = this.props.registrationForm;
|
var registrationForm = this.props.registrationForm;
|
||||||
var name = registrationForm.makePath('credentials', inputName, 'value');
|
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);
|
registrationForm.set(name, value);
|
||||||
},
|
},
|
||||||
composeOptions(values) {
|
composeOptions(values) {
|
||||||
|
@ -1238,14 +1442,21 @@ export var RegistrationDialog = React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getAgreementLink(link) {
|
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() {
|
validateRegistrationForm() {
|
||||||
var registrationForm = this.props.registrationForm;
|
var registrationForm = this.props.registrationForm;
|
||||||
var isValid = registrationForm.isValid();
|
var isValid = registrationForm.isValid();
|
||||||
if (!registrationForm.attributes.credentials.agree.value) {
|
if (!registrationForm.attributes.credentials.agree.value) {
|
||||||
if (!registrationForm.validationError) registrationForm.validationError = {};
|
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;
|
isValid = false;
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -1263,7 +1474,10 @@ export var RegistrationDialog = React.createClass({
|
||||||
|
|
||||||
var collector = (path) => {
|
var collector = (path) => {
|
||||||
return (name) => {
|
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'));
|
_.each(['company', 'name', 'email'], collector('statistics'));
|
||||||
|
@ -1346,7 +1560,12 @@ export var RegistrationDialog = React.createClass({
|
||||||
</button>
|
</button>
|
||||||
];
|
];
|
||||||
if (!this.state.loading) buttons.push(
|
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')}
|
{i18n('welcome_page.register.create_account')}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
@ -1386,7 +1605,9 @@ export var RetrievePasswordDialog = React.createClass({
|
||||||
},
|
},
|
||||||
onChange(inputName, value) {
|
onChange(inputName, value) {
|
||||||
var remoteRetrievePasswordForm = this.props.remoteRetrievePasswordForm;
|
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);
|
remoteRetrievePasswordForm.set('credentials.email.value', value);
|
||||||
},
|
},
|
||||||
retrievePassword() {
|
retrievePassword() {
|
||||||
|
@ -1411,7 +1632,8 @@ export var RetrievePasswordDialog = React.createClass({
|
||||||
var error = this.state.error;
|
var error = this.state.error;
|
||||||
var actionInProgress = this.state.actionInProgress;
|
var actionInProgress = this.state.actionInProgress;
|
||||||
var input = (remoteRetrievePasswordForm.get('credentials') || {}).email;
|
var input = (remoteRetrievePasswordForm.get('credentials') || {}).email;
|
||||||
var inputError = remoteRetrievePasswordForm ? (remoteRetrievePasswordForm.validationError || {})['credentials.email'] : null;
|
var inputError = remoteRetrievePasswordForm ? (remoteRetrievePasswordForm.validationError ||
|
||||||
|
{})['credentials.email'] : null;
|
||||||
return (
|
return (
|
||||||
<div className='retrieve-password-content'>
|
<div className='retrieve-password-content'>
|
||||||
{!this.state.passwordSent ?
|
{!this.state.passwordSent ?
|
||||||
|
@ -1457,7 +1679,12 @@ export var RetrievePasswordDialog = React.createClass({
|
||||||
</button>
|
</button>
|
||||||
];
|
];
|
||||||
if (!this.state.loading) buttons.push(
|
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')}
|
{i18n('dialog.retrieve_password.send_new_password')}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
@ -1498,10 +1725,20 @@ export var CreateNodeNetworkGroupDialog = React.createClass({
|
||||||
},
|
},
|
||||||
renderFooter() {
|
renderFooter() {
|
||||||
return [
|
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')}
|
{i18n('common.cancel_button')}
|
||||||
</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')}
|
{i18n(this.props.ns + 'add')}
|
||||||
</button>
|
</button>
|
||||||
];
|
];
|
||||||
|
@ -1556,7 +1793,9 @@ export var RemoveNodeNetworkGroupDialog = React.createClass({
|
||||||
<div>
|
<div>
|
||||||
<div className='text-danger'>
|
<div className='text-danger'>
|
||||||
{this.renderImportantLabel()}
|
{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')}
|
{i18n('dialog.remove_node_network_group.confirmation')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -52,7 +52,10 @@ export var Navbar = React.createClass({
|
||||||
return this.props.user.get('authenticated');
|
return this.props.user.get('authenticated');
|
||||||
},
|
},
|
||||||
fetchData() {
|
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() {
|
updateNodeStats() {
|
||||||
return this.props.statistics.fetch();
|
return this.props.statistics.fetch();
|
||||||
|
@ -91,7 +94,8 @@ export var Navbar = React.createClass({
|
||||||
},
|
},
|
||||||
render() {
|
render() {
|
||||||
var unreadNotificationsCount = this.props.notifications.where({status: 'unread'}).length;
|
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 (
|
return (
|
||||||
<div className='navigation-box'>
|
<div className='navigation-box'>
|
||||||
<div className='navbar-bg'></div>
|
<div className='navbar-bg'></div>
|
||||||
|
@ -104,7 +108,12 @@ export var Navbar = React.createClass({
|
||||||
<ul className='nav navbar-nav pull-left'>
|
<ul className='nav navbar-nav pull-left'>
|
||||||
{_.map(this.props.elements, (element) => {
|
{_.map(this.props.elements, (element) => {
|
||||||
return (
|
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}>
|
<a href={element.url}>
|
||||||
{i18n('navbar.' + element.label, {defaultValue: element.label})}
|
{i18n('navbar.' + element.label, {defaultValue: element.label})}
|
||||||
</a>
|
</a>
|
||||||
|
@ -128,7 +137,10 @@ export var Navbar = React.createClass({
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
key='statistics-icon'
|
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')}
|
onClick={this.togglePopover('statistics')}
|
||||||
>
|
>
|
||||||
{!!this.props.statistics.get('unallocated') &&
|
{!!this.props.statistics.get('unallocated') &&
|
||||||
|
@ -148,7 +160,9 @@ export var Navbar = React.createClass({
|
||||||
className='notifications-icon'
|
className='notifications-icon'
|
||||||
onClick={this.togglePopover('notifications')}
|
onClick={this.togglePopover('notifications')}
|
||||||
>
|
>
|
||||||
<span className={utils.classNames({badge: true, visible: unreadNotificationsCount})}>
|
<span
|
||||||
|
className={utils.classNames({badge: true, visible: unreadNotificationsCount})}
|
||||||
|
>
|
||||||
{unreadNotificationsCount}
|
{unreadNotificationsCount}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
|
@ -258,7 +272,10 @@ var UserPopover = React.createClass({
|
||||||
<div className='username'>{i18n('common.username')}:</div>
|
<div className='username'>{i18n('common.username')}:</div>
|
||||||
<h3 className='name'>{this.props.user.get('username')}</h3>
|
<h3 className='name'>{this.props.user.get('username')}</h3>
|
||||||
<div className='clearfix'>
|
<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>
|
<i className='glyphicon glyphicon-user'></i>
|
||||||
{i18n('common.change_password')}
|
{i18n('common.change_password')}
|
||||||
</button>
|
</button>
|
||||||
|
@ -281,7 +298,9 @@ var NotificationsPopover = React.createClass({
|
||||||
ShowNodeInfoDialog.show({node: node});
|
ShowNodeInfoDialog.show({node: node});
|
||||||
},
|
},
|
||||||
markAsRead() {
|
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) {
|
if (notificationsToMark.length) {
|
||||||
this.setState({unreadNotificationsIds: notificationsToMark.pluck('id')});
|
this.setState({unreadNotificationsIds: notificationsToMark.pluck('id')});
|
||||||
notificationsToMark.toJSON = function() {
|
notificationsToMark.toJSON = function() {
|
||||||
|
@ -307,7 +326,8 @@ var NotificationsPopover = React.createClass({
|
||||||
'text-danger': topic == 'error',
|
'text-danger': topic == 'error',
|
||||||
'text-warning': topic == 'warning',
|
'text-warning': topic == 'warning',
|
||||||
clickable: nodeId,
|
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 = {
|
var iconClass = {
|
||||||
error: 'glyphicon-exclamation-sign',
|
error: 'glyphicon-exclamation-sign',
|
||||||
|
@ -347,7 +367,12 @@ export var Footer = React.createClass({
|
||||||
return (
|
return (
|
||||||
<div className='footer'>
|
<div className='footer'>
|
||||||
{_.contains(version.get('feature_groups'), 'mirantis') && [
|
{_.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='copyright'>{i18n('common.copyright')}</div>
|
||||||
]}
|
]}
|
||||||
<div key='version'>{i18n('common.version')}: {version.get('release')}</div>
|
<div key='version'>{i18n('common.version')}: {version.get('release')}</div>
|
||||||
|
@ -365,7 +390,8 @@ export var Breadcrumbs = React.createClass({
|
||||||
},
|
},
|
||||||
getBreadcrumbsPath() {
|
getBreadcrumbsPath() {
|
||||||
var page = this.props.Page;
|
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() {
|
refresh() {
|
||||||
this.setState({path: this.getBreadcrumbsPath()});
|
this.setState({path: this.getBreadcrumbsPath()});
|
||||||
|
|
|
@ -62,7 +62,8 @@ var LoginForm = React.createClass({
|
||||||
var error = 'login_error';
|
var error = 'login_error';
|
||||||
if (status == 401) {
|
if (status == 401) {
|
||||||
error = 'credentials_error';
|
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';
|
error = 'keystone_unavailable_error';
|
||||||
}
|
}
|
||||||
this.setState({error: i18n('login_page.' + error)});
|
this.setState({error: i18n('login_page.' + error)});
|
||||||
|
@ -126,7 +127,14 @@ var LoginForm = React.createClass({
|
||||||
<i className='glyphicon glyphicon-user'></i>
|
<i className='glyphicon glyphicon-user'></i>
|
||||||
</label>
|
</label>
|
||||||
<div className='col-xs-8'>
|
<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>
|
</div>
|
||||||
<div className='form-group'>
|
<div className='form-group'>
|
||||||
|
@ -134,7 +142,14 @@ var LoginForm = React.createClass({
|
||||||
<i className='glyphicon glyphicon-lock'></i>
|
<i className='glyphicon glyphicon-lock'></i>
|
||||||
</label>
|
</label>
|
||||||
<div className='col-xs-8'>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
{!httpsUsed &&
|
{!httpsUsed &&
|
||||||
|
|
|
@ -110,7 +110,12 @@ Notification = React.createClass({
|
||||||
<div className='notification-time'>{this.props.notification.get('time')}</div>
|
<div className='notification-time'>{this.props.notification.get('time')}</div>
|
||||||
<div className='notification-type'><i className={'glyphicon ' + iconClass} /></div>
|
<div className='notification-type'><i className={'glyphicon ' + iconClass} /></div>
|
||||||
<div className='notification-message'>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -103,7 +103,9 @@ var PluginsPage = React.createClass({
|
||||||
render() {
|
render() {
|
||||||
var isMirantisIso = _.contains(app.version.get('feature_groups'), 'mirantis');
|
var isMirantisIso = _.contains(app.version.get('feature_groups'), 'mirantis');
|
||||||
var links = {
|
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')
|
documentation: utils.composeDocumentationLink('plugin-dev.html')
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -68,10 +68,23 @@ var RootComponent = React.createClass({
|
||||||
<div id='content-wrapper'>
|
<div id='content-wrapper'>
|
||||||
<div className={utils.classNames(layoutClasses)}>
|
<div className={utils.classNames(layoutClasses)}>
|
||||||
{!Page.hiddenLayout && [
|
{!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} />,
|
<Breadcrumbs key='breadcrumbs' ref='breadcrumbs' {...this.state} />,
|
||||||
showDefaultPasswordWarning && <DefaultPasswordWarning key='password-warning' close={this.hideDefaultPasswordWarning} />,
|
showDefaultPasswordWarning &&
|
||||||
fuelSettings.get('bootstrap.error.value') && <BootstrapError key='bootstrap-error' text={fuelSettings.get('bootstrap.error.value')} />
|
<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'>
|
<div id='content'>
|
||||||
<Page ref='page' {...this.state.pageOptions} />
|
<Page ref='page' {...this.state.pageOptions} />
|
||||||
|
|
|
@ -109,7 +109,11 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
checkRestrictions(name, action = 'disable') {
|
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() {
|
componentWillMount() {
|
||||||
var model = this.props.statistics || this.props.tracking;
|
var model = this.props.statistics || this.props.tracking;
|
||||||
|
@ -129,9 +133,15 @@ export default {
|
||||||
renderInput(settingName, wrapperClassName, disabledState) {
|
renderInput(settingName, wrapperClassName, disabledState) {
|
||||||
var model = this.props.statistics || this.props.tracking;
|
var model = this.props.statistics || this.props.tracking;
|
||||||
var setting = model.get(model.makePath('statistics', settingName));
|
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 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
|
return <Input
|
||||||
key={settingName}
|
key={settingName}
|
||||||
type={setting.type}
|
type={setting.type}
|
||||||
|
@ -198,8 +208,16 @@ export default {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className='statistics-text-box'>
|
<div className='statistics-text-box'>
|
||||||
<div className={utils.classNames({notice: isMirantisIso})}>{this.getText(ns + 'help_to_improve')}</div>
|
<div className={utils.classNames({notice: isMirantisIso})}>
|
||||||
<button className='btn-link' data-toggle='collapse' data-target='.statistics-disclaimer-box'>{i18n(ns + 'learn_whats_collected')}</button>
|
{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'>
|
<div className='collapse statistics-disclaimer-box'>
|
||||||
<p>{i18n(ns + 'statistics_includes_full')}</p>
|
<p>{i18n(ns + 'statistics_includes_full')}</p>
|
||||||
{_.map(lists, this.renderList)}
|
{_.map(lists, this.renderList)}
|
||||||
|
@ -258,10 +276,16 @@ export default {
|
||||||
/>;
|
/>;
|
||||||
})}
|
})}
|
||||||
<div className='links-container'>
|
<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')}
|
{i18n('welcome_page.register.create_account')}
|
||||||
</button>
|
</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')}
|
{i18n('welcome_page.register.retrieve_password')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -46,17 +46,33 @@ var SupportPage = React.createClass({
|
||||||
render() {
|
render() {
|
||||||
var elements = [
|
var elements = [
|
||||||
<DocumentationLink key='DocumentationLink' />,
|
<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' />
|
<CapacityAudit key='CapacityAudit' />
|
||||||
];
|
];
|
||||||
if (_.contains(app.version.get('feature_groups'), 'mirantis')) {
|
if (_.contains(app.version.get('feature_groups'), 'mirantis')) {
|
||||||
elements.unshift(
|
elements.unshift(
|
||||||
<RegistrationInfo key='RegistrationInfo' settings={this.props.settings} tracking={this.props.tracking}/>,
|
<RegistrationInfo
|
||||||
<StatisticsSettings key='StatisticsSettings' settings={this.props.settings} statistics={this.props.statistics}/>,
|
key='RegistrationInfo'
|
||||||
|
settings={this.props.settings}
|
||||||
|
tracking={this.props.tracking}
|
||||||
|
/>,
|
||||||
|
<StatisticsSettings
|
||||||
|
key='StatisticsSettings'
|
||||||
|
settings={this.props.settings}
|
||||||
|
statistics={this.props.statistics}
|
||||||
|
/>,
|
||||||
<SupportContacts key='SupportContacts' />
|
<SupportContacts key='SupportContacts' />
|
||||||
);
|
);
|
||||||
} else {
|
} 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 (
|
return (
|
||||||
<div className='support-page'>
|
<div className='support-page'>
|
||||||
|
@ -92,7 +108,8 @@ var SupportPageElement = React.createClass({
|
||||||
|
|
||||||
var DocumentationLink = React.createClass({
|
var DocumentationLink = React.createClass({
|
||||||
render() {
|
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 (
|
return (
|
||||||
<SupportPageElement
|
<SupportPageElement
|
||||||
className='img-documentation-link'
|
className='img-documentation-link'
|
||||||
|
@ -100,7 +117,11 @@ var DocumentationLink = React.createClass({
|
||||||
text={i18n(ns + 'text')}
|
text={i18n(ns + 'text')}
|
||||||
>
|
>
|
||||||
<p>
|
<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')}
|
{i18n('support_page.documentation_link')}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
@ -124,12 +145,26 @@ var RegistrationInfo = React.createClass({
|
||||||
>
|
>
|
||||||
<div className='registeredData enable-selection'>
|
<div className='registeredData enable-selection'>
|
||||||
{_.map(['name', 'email', 'company'], (value) => {
|
{_.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>
|
</div>
|
||||||
<p>
|
<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')}
|
{i18n('support_page.manage_account')}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
@ -142,9 +177,18 @@ var RegistrationInfo = React.createClass({
|
||||||
text={i18n('support_page.register_fuel_content')}
|
text={i18n('support_page.register_fuel_content')}
|
||||||
>
|
>
|
||||||
<div className='tracking'>
|
<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>
|
<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')}
|
{i18n('support_page.register_fuel_title')}
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
|
@ -212,9 +256,17 @@ var SupportContacts = React.createClass({
|
||||||
title={i18n('support_page.contact_support')}
|
title={i18n('support_page.contact_support')}
|
||||||
text={i18n('support_page.contact_text')}
|
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>
|
<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')}
|
{i18n('support_page.contact_support')}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
@ -244,7 +296,7 @@ var DiagnosticSnapshot = React.createClass({
|
||||||
},
|
},
|
||||||
downloadLogs() {
|
downloadLogs() {
|
||||||
this.setState({generating: true});
|
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() {
|
componentDidUpdate() {
|
||||||
this.startPolling();
|
this.startPolling();
|
||||||
|
@ -260,7 +312,8 @@ var DiagnosticSnapshot = React.createClass({
|
||||||
>
|
>
|
||||||
<p className='snapshot'>
|
<p className='snapshot'>
|
||||||
<button className='btn btn-default' disabled={generating} onClick={this.downloadLogs}>
|
<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>
|
</button>
|
||||||
{' '}
|
{' '}
|
||||||
{!generating && task &&
|
{!generating && task &&
|
||||||
|
|
|
@ -80,13 +80,25 @@ var WelcomePage = React.createClass({
|
||||||
<div className='happy-cloud'>
|
<div className='happy-cloud'>
|
||||||
<div className='cloud-smile' />
|
<div className='cloud-smile' />
|
||||||
<div className='row'>
|
<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>
|
</div>
|
||||||
:
|
:
|
||||||
<div>
|
<div>
|
||||||
<p className='register_installation'>{i18n(ns + 'register.register_installation')}</p>
|
<p className='register_installation'>
|
||||||
{this.renderRegistrationForm(this.props.tracking, disabled, this.state.error, this.state.actionInProgress && !this.state.locked)}
|
{i18n(ns + 'register.register_installation')}
|
||||||
|
</p>
|
||||||
|
{this.renderRegistrationForm(
|
||||||
|
this.props.tracking,
|
||||||
|
disabled,
|
||||||
|
this.state.error,
|
||||||
|
this.state.actionInProgress && !this.state.locked
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@ -96,7 +108,9 @@ var WelcomePage = React.createClass({
|
||||||
{this.renderInput('send_user_info', 'welcome-checkbox-box', disabled)}
|
{this.renderInput('send_user_info', 'welcome-checkbox-box', disabled)}
|
||||||
<div>
|
<div>
|
||||||
<div className='notice'>{i18n(ns + 'privacy_policy')}</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>
|
||||||
</div>
|
</div>
|
||||||
:
|
:
|
||||||
|
|
|
@ -111,7 +111,10 @@ var ComponentRadioGroup = React.createClass({
|
||||||
var ClusterWizardPanesMixin = {
|
var ClusterWizardPanesMixin = {
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
if (this.props.allComponents) {
|
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);
|
this.processRestrictions(this.components);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -167,7 +170,8 @@ var ClusterWizardPanesMixin = {
|
||||||
});
|
});
|
||||||
component.set({
|
component.set({
|
||||||
isCompatible: isCompatible,
|
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')
|
availability: (isCompatible ? 'compatible' : 'available')
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -181,7 +185,10 @@ var ClusterWizardPanesMixin = {
|
||||||
var warnings = [];
|
var warnings = [];
|
||||||
_.each(incompatibles, (incompatible) => {
|
_.each(incompatibles, (incompatible) => {
|
||||||
var type = incompatible.component.get('type');
|
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) {
|
if (!_.contains(types, type) || isInStopList) {
|
||||||
// ignore forward incompatibilities
|
// ignore forward incompatibilities
|
||||||
return;
|
return;
|
||||||
|
@ -261,10 +268,15 @@ var NameAndRelease = React.createClass({
|
||||||
},
|
},
|
||||||
isValid() {
|
isValid() {
|
||||||
var wizard = this.props.wizard;
|
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
|
// test cluster name is already taken
|
||||||
if (clusters.findWhere({name: name})) {
|
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});
|
wizard.set({name_error: error});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -286,7 +298,9 @@ var NameAndRelease = React.createClass({
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
var os = release.get('operating_system');
|
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 (
|
return (
|
||||||
<div className='create-cluster-form name-and-release'>
|
<div className='create-cluster-form name-and-release'>
|
||||||
<Input
|
<Input
|
||||||
|
@ -345,7 +359,9 @@ var Compute = React.createClass({
|
||||||
return _.contains(this.constructor.vCenterNetworkBackends, component.id);
|
return _.contains(this.constructor.vCenterNetworkBackends, component.id);
|
||||||
});
|
});
|
||||||
if (!hasCompatibleBackends) {
|
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({
|
vCenter.set({
|
||||||
disabled: true,
|
disabled: true,
|
||||||
warnings: i18n('dialog.create_cluster_wizard.compute.vcenter_requires_network_backend')
|
warnings: i18n('dialog.create_cluster_wizard.compute.vcenter_requires_network_backend')
|
||||||
|
@ -363,7 +379,9 @@ var Compute = React.createClass({
|
||||||
onChange={this.props.onChange}
|
onChange={this.props.onChange}
|
||||||
/>
|
/>
|
||||||
{this.constructor.hasErrors(this.props.wizard) &&
|
{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>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -405,10 +423,17 @@ var Network = React.createClass({
|
||||||
var monolithic = _.filter(this.components, (component) => !component.isML2Driver());
|
var monolithic = _.filter(this.components, (component) => !component.isML2Driver());
|
||||||
var hasMl2 = _.any(this.components, (component) => component.isML2Driver());
|
var hasMl2 = _.any(this.components, (component) => component.isML2Driver());
|
||||||
if (!hasMl2) {
|
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.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);
|
this.selectActiveComponent(monolithic);
|
||||||
return (
|
return (
|
||||||
<ComponentRadioGroup
|
<ComponentRadioGroup
|
||||||
|
@ -438,7 +463,9 @@ var Network = React.createClass({
|
||||||
{this.renderML2DriverControls()}
|
{this.renderML2DriverControls()}
|
||||||
</div>
|
</div>
|
||||||
{this.constructor.hasErrors(this.props.wizard) &&
|
{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>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -456,8 +483,17 @@ var Storage = React.createClass({
|
||||||
renderSection(components, type) {
|
renderSection(components, type) {
|
||||||
var sectionComponents = _.filter(components, (component) => component.get('subtype') == type);
|
var sectionComponents = _.filter(components, (component) => component.get('subtype') == type);
|
||||||
var isRadio = this.areComponentsMutuallyExclusive(sectionComponents);
|
var isRadio = this.areComponentsMutuallyExclusive(sectionComponents);
|
||||||
this.processRestrictions(sectionComponents, this.constructor.panesForRestrictions, (isRadio ? sectionComponents : []));
|
this.processRestrictions(
|
||||||
this.processCompatible(this.props.allComponents, sectionComponents, this.constructor.panesForRestrictions, isRadio ? sectionComponents : []);
|
sectionComponents,
|
||||||
|
this.constructor.panesForRestrictions,
|
||||||
|
(isRadio ? sectionComponents : [])
|
||||||
|
);
|
||||||
|
this.processCompatible(
|
||||||
|
this.props.allComponents,
|
||||||
|
sectionComponents,
|
||||||
|
this.constructor.panesForRestrictions,
|
||||||
|
isRadio ? sectionComponents : []
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
React.createElement((isRadio ? ComponentRadioGroup : ComponentCheckboxGroup), {
|
React.createElement((isRadio ? ComponentRadioGroup : ComponentCheckboxGroup), {
|
||||||
groupName: type,
|
groupName: type,
|
||||||
|
@ -468,7 +504,11 @@ var Storage = React.createClass({
|
||||||
},
|
},
|
||||||
render() {
|
render() {
|
||||||
this.processRestrictions(this.components, this.constructor.panesForRestrictions);
|
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 (
|
return (
|
||||||
<div className='wizard-storage-pane'>
|
<div className='wizard-storage-pane'>
|
||||||
<div className='row'>
|
<div className='row'>
|
||||||
|
@ -506,7 +546,11 @@ var AdditionalServices = React.createClass({
|
||||||
},
|
},
|
||||||
render() {
|
render() {
|
||||||
this.processRestrictions(this.components, this.constructor.panesForRestrictions);
|
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 (
|
return (
|
||||||
<div className='wizard-compute-pane'>
|
<div className='wizard-compute-pane'>
|
||||||
<ComponentCheckboxGroup
|
<ComponentCheckboxGroup
|
||||||
|
@ -591,7 +635,8 @@ var CreateClusterWizard = React.createClass({
|
||||||
},
|
},
|
||||||
updateState(nextState) {
|
updateState(nextState) {
|
||||||
var numberOfPanes = this.getEnabledPanes().length;
|
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 pane = clusterWizardPanes[nextActivePaneIndex];
|
||||||
var paneHasErrors = _.isFunction(pane.hasErrors) ? pane.hasErrors(this.wizard) : false;
|
var paneHasErrors = _.isFunction(pane.hasErrors) ? pane.hasErrors(this.wizard) : false;
|
||||||
|
|
||||||
|
@ -714,7 +759,10 @@ var CreateClusterWizard = React.createClass({
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
maxAvailablePaneIndex = this.state.activePaneIndex;
|
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) {
|
if (panesToRestore.length > 0) {
|
||||||
this.components.restoreDefaultValues(panesToRestore);
|
this.components.restoreDefaultValues(panesToRestore);
|
||||||
}
|
}
|
||||||
|
@ -790,11 +838,17 @@ var CreateClusterWizard = React.createClass({
|
||||||
var actionInProgress = this.state.actionInProgress;
|
var actionInProgress = this.state.actionInProgress;
|
||||||
return (
|
return (
|
||||||
<div className='wizard-footer'>
|
<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')}
|
{i18n('common.cancel_button')}
|
||||||
</button>
|
</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}
|
onClick={this.prevPane}
|
||||||
>
|
>
|
||||||
<i className='glyphicon glyphicon-arrow-left' aria-hidden='true'></i>
|
<i className='glyphicon glyphicon-arrow-left' aria-hidden='true'></i>
|
||||||
|
@ -803,7 +857,10 @@ var CreateClusterWizard = React.createClass({
|
||||||
</button>
|
</button>
|
||||||
{this.state.nextVisible &&
|
{this.state.nextVisible &&
|
||||||
<button
|
<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}
|
onClick={this.nextPane}
|
||||||
>
|
>
|
||||||
<span>{i18n('dialog.create_cluster_wizard.next')}</span>
|
<span>{i18n('dialog.create_cluster_wizard.next')}</span>
|
||||||
|
@ -813,7 +870,10 @@ var CreateClusterWizard = React.createClass({
|
||||||
}
|
}
|
||||||
{this.state.createVisible &&
|
{this.state.createVisible &&
|
||||||
<button
|
<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}
|
onClick={this.saveCluster}
|
||||||
autoFocus
|
autoFocus
|
||||||
>
|
>
|
||||||
|
|
|
@ -22,7 +22,10 @@ module.exports = {
|
||||||
},
|
},
|
||||||
{test: /\/expression\/parser\.js$/, loader: 'exports?parser'},
|
{test: /\/expression\/parser\.js$/, loader: 'exports?parser'},
|
||||||
{test: require.resolve('jquery'), loader: 'expose?jQuery!expose?$'},
|
{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: /\.css$/, loader: 'style!css!postcss'},
|
||||||
{test: /\.less$/, loader: 'style!css!postcss!less'},
|
{test: /\.less$/, loader: 'style!css!postcss!less'},
|
||||||
{test: /\.html$/, loader: 'raw'},
|
{test: /\.html$/, loader: 'raw'},
|
||||||
|
|
Loading…
Reference in New Issue