fuel-ui/static/js/views/cluster_page_tabs/settings_tab.js

367 lines
19 KiB
JavaScript

/*
* Copyright 2013 Mirantis, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
**/
define(
[
'utils',
'models',
'view_mixins',
'views/common',
'jsx!views/dialogs',
'text!templates/cluster/settings_tab.html',
'text!templates/cluster/settings_group.html'
],
function(utils, models, viewMixins, commonViews, dialogViews, settingsTabTemplate, settingsGroupTemplate) {
'use strict';
var SettingsTab, SettingGroup;
SettingsTab = commonViews.Tab.extend({
template: _.template(settingsTabTemplate),
mixins: [viewMixins.toggleablePassword],
hasChanges: function() {
return !_.isEqual(this.settings.toJSON().editable, this.settings.initialAttributes);
},
events: {
'click .btn-apply-changes:not([disabled])': 'applyChanges',
'click .btn-revert-changes:not([disabled])': 'revertChanges',
'click .btn-load-defaults:not([disabled])': 'loadDefaults'
},
calculateButtonsState: function() {
var hasChanges = this.hasChanges();
this.$('.btn-revert-changes').attr('disabled', !hasChanges);
this.$('.btn-apply-changes').attr('disabled', !hasChanges || this.settings.validationError);
this.$('.btn-load-defaults').attr('disabled', this.isLocked());
},
disableControls: function() {
this.$('.btn, input, select').attr('disabled', true);
},
isLocked: function() {
return this.model.task({group: 'deployment', status: 'running'}) || !this.model.isAvailableForSettingsChanges();
},
applyChanges: function() {
this.disableControls();
return this.settings.save(null, {patch: true, wait: true})
.done(_.bind(this.updateInitialSettings, this))
.always(_.bind(function() {
this.render();
this.model.fetch();
}, this))
.fail(_.bind(function() {
this.calculateButtonsState();
utils.showErrorDialog({
title: $.t('cluster_page.settings_tab.settings_error.title'),
message: $.t('cluster_page.settings_tab.settings_error.saving_warning')
});
}, this));
},
revertChanges: function() {
this.loadInitialSettings();
},
beforeTearDown: function() {
this.loadInitialSettings();
},
loadDefaults: function() {
this.disableControls();
this.settings.fetch({url: _.result(this.settings, 'url') + '/defaults'})
.fail(function() {
utils.showErrorDialog({
title: $.t('cluster_page.settings_tab.settings_error.title'),
message: $.t('cluster_page.settings_tab.settings_error.load_defaults_warning')
});
})
.always(_.bind(this.render, this));
},
updateInitialSettings: function() {
this.settings.initialAttributes = this.settings.toJSON().editable;
},
loadInitialSettings: function() {
this.settings.set(_.cloneDeep(this.settings.initialAttributes));
this.updateSettings();
},
onSettingChange: function() {
this.$('input.error').removeClass('error');
this.$('.parameter-description').removeClass('hide');
this.$('.validation-error').addClass('hide');
this.settings.isValid();
this.calculateButtonsState();
},
composeBindings: function() {
var bindings = {};
_.each(this.settings.attributes, function(group, groupName) {
if (group.metadata.toggleable) {
bindings['input[name="' + groupName + '.enabled' + '"]'] = {
observe: groupName + '.metadata.enabled',
attributes: [{
name: 'disabled',
observe: groupName + '.metadata.disabled',
onGet: _.bind(function(value) {
return value || this.isLocked();
}, this)
}]
};
}
bindings['div[data-settings-group="' + groupName + '"]'] = {
observe: groupName + '.metadata.visible',
visible: true,
visibleFn: function($el, isVisible) {
$el.parents('.fieldset-group').toggle(isVisible);
}
};
var isGroupDisabled = _.bind(function(groupName) {
var groupData = this.settings.get(groupName + '.metadata');
if (groupData.enabled && (groupData.hasDependentRole || this.checkDependentSettings(groupName, 'metadata'))) {
return false;
}
return groupData.enabled === false || groupData.disabled;
}, this);
_.each(group, function(setting, settingName) {
if (settingName == 'metadata') {return;}
var settingPath = groupName + '.' + settingName;
bindings['[name="' + settingPath + '"]'] = {
observe: [settingPath + '.value', settingPath + '.visible'],
onGet: function(value) {
return value[0];
},
onSet: function(value) {
return [value, setting.visible];
},
visible: function() {
return setting.visible;
},
visibleFn: function($el, isVisible) {
$el.parents('.setting-container').toggleClass('hide', !isVisible);
},
attributes: [{
name: 'disabled',
observe: [groupName + '.metadata.*', settingPath + '.disabled'],
onGet: _.bind(function(value) {
var isSettingDisabled = value[1];
return this.isLocked() || isGroupDisabled(groupName) || isSettingDisabled;
}, this)
}]
};
_.each(setting.values, function(option, index) {
bindings['input[name="' + settingPath + '"][value="' + option.data + '"]'] = {
observe: [settingPath + '.visible', settingPath + '.values'],
onGet: function(value) {
return value[1];
},
visible: function(value) {
return setting.visible && value[index].visible;
},
visibleFn: function($el, isVisible) {
$el.parents('.parameter-box:first').toggleClass('hide', !isVisible);
},
attributes: [{
name: 'disabled',
observe: [groupName + '.metadata.*', settingPath + '.disabled', settingPath + '.values'],
onGet: _.bind(function(value) {
var isSettingDisabled = value[1];
var values = value[2];
return this.isLocked() || isGroupDisabled(groupName) || isSettingDisabled || values[index].disabled;
}, this)
}]
};
}, this);
}, this);
}, this);
this.stickit(this.settings, bindings);
},
getValueAttribute: function(settingName) {
return settingName == 'metadata' ? 'enabled' : 'value';
},
checkDependentSettings: function(groupName, settingName) {
var unsupportedTypes = ['text', 'password'];
var settingPath = groupName + '.' + settingName;
var processedSetting = this.settings.get(settingPath);
var valueAttribute = this.getValueAttribute(settingName);
var notToggleableGroup = settingName == 'metadata' && !processedSetting.toggleable;
if (notToggleableGroup || _.contains(unsupportedTypes, this.settings.get(settingPath).type)) {
return false;
}
var isDependent = function(restriction) {
return restriction.action == 'disable' && _.contains(restriction.condition, 'settings:' + settingPath);
};
// collect restrictions to check
var restrictions = [];
_.each(this.settings.attributes, function(group, groupName) {
// FIXME(ja): invisible dependent settings and options should not be checked also
if (this.checkRestrictions(group.metadata, 'hide')) { return; }
_.each(group, function(setting, settingName) {
if (_.contains(unsupportedTypes, setting.type) || groupName + '.' + settingName == settingPath) { return; }
if (setting[this.getValueAttribute(settingName)] == true) { // for checkboxes and toggleable setting groups
restrictions.push(_.find(setting.restrictions, isDependent));
} else {
var activeOption = _.find(setting.values, {data: setting.value}); // for dropdowns and radio groups
if (activeOption) {
restrictions.push(_.find(activeOption.restrictions, isDependent));
}
}
}, this);
}, this);
restrictions = _.compact(restrictions);
if (restrictions.length) {
var processedValues = _.without(_.pluck(processedSetting.values, 'data'), processedSetting[valueAttribute]) || [!processedSetting[valueAttribute]];
var configModels = _.extend({}, this.configModels, {settings: new models.Settings(this.settings.toJSON().editable)});
return _.any(restrictions, function(restriction) {
var suitableValues = _.filter(processedValues, function(value) {
configModels.settings.get(settingPath)[valueAttribute] = value;
return !utils.evaluateExpression(restriction.condition, configModels).value;
});
return !suitableValues.length;
});
}
return false;
},
checkRestrictions: function(setting, action) {
return _.any(_.where(setting.restrictions, {action: action}), function(restriction) {
return utils.evaluateExpression(restriction.condition, this.configModels).value;
}, this);
},
calculateSettingState: function(groupName, settingName) {
var settingPath = groupName + '.' + settingName;
var setting = this.settings.get(settingPath);
this.settings.set(settingPath + '.disabled', setting.hasDependentRole || this.checkRestrictions(setting, 'disable') || this.checkDependentSettings(groupName, settingName));
this.settings.set(settingPath + '.visible', !this.checkRestrictions(setting, 'hide'));
_.each(setting.values, function(value, index) {
var values = _.cloneDeep(setting.values);
values[index].disabled = this.checkRestrictions(values[index], 'disable');
values[index].visible = !this.checkRestrictions(values[index], 'hide');
this.settings.set(settingPath + '.values', values);
}, this);
},
composeListeners: function(groupName, settingName) {
var settingPath = groupName + '.' + settingName;
var callback = _.bind(this.calculateSettingState, this, groupName, settingName);
var collectRestrictions = function(setting) {
return _.compact(_.flatten(_.union(setting.restrictions, _.pluck(setting.values, 'restrictions'))));
};
_.each(collectRestrictions(this.settings.get(settingPath)), function(restriction) {
var evaluatedRestriction = utils.evaluateExpression(restriction.condition, this.configModels);
_.invoke(evaluatedRestriction.modelPaths, 'change', callback);
}, this);
// handle dependent settings
_.each(this.settings.attributes, function(group, groupName) {
_.each(group, function(setting, settingName) {
if (groupName + '.' + settingName == settingPath) { return; }
var isDependent = function(restriction) {
return _.contains(restriction.condition, 'settings:' + settingPath);
};
if (_.any(collectRestrictions(setting), isDependent)) {
this.settings.on('change:' + groupName + '.' + settingName + '.' + this.getValueAttribute(settingName), callback);
}
}, this);
}, this);
},
checkDependentRoles: function(groupName, settingName) {
var settingPath = groupName + '.' + settingName;
var rolesData = this.model.get('release').get('roles_metadata');
this.settings.get(settingPath).hasDependentRole = _.any(this.model.get('release').get('roles'), function(role) {
var roleDependencies = _.map(rolesData[role].depends, utils.expandRestriction);
var hasSatisfiedDependencies = _.any(roleDependencies, function(dependency) {
var evaluatedDependency = utils.evaluateExpression(dependency.condition, this.configModels);
return _.contains(dependency.condition, 'settings:' + settingPath) && evaluatedDependency.value;
}, this);
var assignedNodes = this.model.get('nodes').filter(function(node) { return node.hasRole(role); });
return hasSatisfiedDependencies && assignedNodes.length;
}, this);
},
renderSettingGroup: function(groupName) {
var settings = this.settings.get(groupName);
var settingGroupView = new SettingGroup({
settings: settings,
groupName: groupName,
locked: this.isLocked()
});
this.registerSubView(settingGroupView);
this.$('.settings').append(settingGroupView.render().el);
},
render: function() {
this.tearDownRegisteredSubViews();
this.$el.html(this.template({
loading: this.loading,
locked: this.isLocked()
})).i18n();
if (this.loading.state() != 'pending') {
this.$('.settings').html('');
var sortedSettings = _.sortBy(_.keys(this.settings.attributes), _.bind(function(setting) {
return this.settings.get(setting + '.metadata.weight');
}, this));
_.each(sortedSettings, this.renderSettingGroup, this);
this.composeBindings();
this.onSettingChange();
}
return this;
},
updateSettings: function() {
this.settings.expandRestrictions();
_.each(this.settings.attributes, function(group, groupName) {
_.each(group, function(setting, settingName) {
this.calculateSettingState(groupName, settingName);
}, this);
}, this);
},
initialize: function(options) {
this.model.on('change:status', this.render, this);
this.model.get('tasks').bindToView(this, [{group: 'deployment'}], function(task) {
task.on('change:status', this.render, this);
});
this.settings = this.model.get('settings');
this.settings.on('invalid', function(model, errors) {
_.each(errors, function(error) {
var input = this.$('input[name="' + error.field + '"]');
input.addClass('error');
input.parent().siblings('.description').addClass('hide');
input.parent().siblings('.validation-error').text(error.message).removeClass('hide');
}, this);
}, this);
(this.loading = $.when(this.settings.fetch({cache: true}), this.model.get('networkConfiguration').fetch({cache: true}))).done(_.bind(function() {
this.updateInitialSettings();
this.configModels = {
cluster: this.model,
settings: this.settings,
networking_parameters: this.model.get('networkConfiguration').get('networking_parameters'),
version: app.version,
default: this.settings
};
this.settings.expandRestrictions();
_.each(this.settings.attributes, function(group, groupName) {
_.each(group, function(setting, settingName) {
this.composeListeners(groupName, settingName);
this.checkDependentRoles(groupName, settingName);
this.calculateSettingState(groupName, settingName);
}, this);
}, this);
this.settings.on('change', this.onSettingChange, this);
this.settings.on('sync', this.updateSettings, this);
}, this));
if (this.loading.state() == 'pending') {
this.loading.done(_.bind(this.render, this));
}
}
});
SettingGroup = Backbone.View.extend({
template: _.template(settingsGroupTemplate),
className: 'fieldset-group wrapper',
render: function() {
this.$el.html(this.template(this.options)).i18n();
return this;
}
});
return SettingsTab;
});