Handle new plugin versions structure in UI
Related-Bug: #1518993
Closes-Bug: #1516976
Change-Id: Ia0d61a2e92b72018c830e9305c6c9102331d1c05
(cherry picked from commit 55c83a7651
)
This commit is contained in:
parent
d35b2728ba
commit
1a016e57a5
|
@ -101,19 +101,22 @@ define([
|
|||
models.cacheMixin = cacheMixin;
|
||||
|
||||
var restrictionMixin = models.restrictionMixin = {
|
||||
expandRestrictions: function(restrictions, path) {
|
||||
path = path || 'restrictions';
|
||||
expandRestrictions: function(restrictions, path = 'restrictions') {
|
||||
this.expandedRestrictions = this.expandedRestrictions || {};
|
||||
this.expandedRestrictions[path] = _.map(restrictions, utils.expandRestriction, this);
|
||||
},
|
||||
checkRestrictions: function(models, action, path) {
|
||||
path = path || 'restrictions';
|
||||
checkRestrictions: function(models, action, path = 'restrictions') {
|
||||
var restrictions = (this.expandedRestrictions || {})[path];
|
||||
if (action) restrictions = _.where(restrictions, {action: action});
|
||||
var satisfiedRestrictions = _.filter(restrictions, function(restriction) {
|
||||
return new Expression(restriction.condition, models, restriction).evaluate();
|
||||
});
|
||||
return {result: !!satisfiedRestrictions.length, message: _.compact(_.pluck(satisfiedRestrictions, 'message')).join(' ')};
|
||||
if (action) {
|
||||
restrictions = _.where(restrictions, {action: action});
|
||||
}
|
||||
var satisfiedRestrictions = _.filter(restrictions,
|
||||
(restriction) => new Expression(restriction.condition, models, restriction).evaluate()
|
||||
);
|
||||
return {
|
||||
result: !!satisfiedRestrictions.length,
|
||||
message: _.compact(_.pluck(satisfiedRestrictions, 'message')).join(' ')
|
||||
};
|
||||
},
|
||||
expandLimits: function(limits) {
|
||||
this.expandedLimits = this.expandedLimits || {};
|
||||
|
@ -625,24 +628,61 @@ define([
|
|||
isNew: function() {
|
||||
return false;
|
||||
},
|
||||
isPlugin: function(section) {
|
||||
return (section.metadata || {}).class == 'plugin';
|
||||
},
|
||||
parse: function(response) {
|
||||
return response[this.root];
|
||||
},
|
||||
mergePluginSettings: function() {
|
||||
_.each(this.attributes, (section, sectionName) => {
|
||||
if (this.isPlugin(section)) {
|
||||
var chosenVersionData = section.metadata.versions.find(
|
||||
(version) => version.metadata.plugin_id == section.metadata.chosen_id
|
||||
);
|
||||
// merge metadata of a chosen plugin version
|
||||
_.extend(section.metadata, _.omit(chosenVersionData.metadata, 'plugin_id', 'plugin_version'));
|
||||
// merge settings of a chosen plugin version
|
||||
this.attributes[sectionName] = _.extend(_.pick(section, 'metadata'), _.omit(chosenVersionData, 'metadata'));
|
||||
}
|
||||
}, this);
|
||||
},
|
||||
toJSON: function() {
|
||||
if (!this.root) return this._super('toJSON', arguments);
|
||||
var data = {};
|
||||
data[this.root] = this._super('toJSON', arguments);
|
||||
return data;
|
||||
var settings = this._super('toJSON', arguments);
|
||||
if (!this.root) return settings;
|
||||
|
||||
// update plugin settings
|
||||
_.each(settings, (section, sectionName) => {
|
||||
if (this.isPlugin(section)) {
|
||||
var chosenVersionData = section.metadata.versions.find(
|
||||
(version) => version.metadata.plugin_id == section.metadata.chosen_id
|
||||
);
|
||||
section.metadata = _.omit(section.metadata, _.without(_.keys(chosenVersionData.metadata), 'plugin_id', 'plugin_version'));
|
||||
_.each(section, (setting, settingName) => {
|
||||
if (settingName != 'metadata') chosenVersionData[settingName].value = setting.value;
|
||||
});
|
||||
settings[sectionName] = _.pick(section, 'metadata');
|
||||
}
|
||||
});
|
||||
return {[this.root]: settings};
|
||||
},
|
||||
processRestrictions: function() {
|
||||
_.each(this.attributes, function(group, groupName) {
|
||||
if (group.metadata) {
|
||||
this.expandRestrictions(group.metadata.restrictions, groupName + '.metadata');
|
||||
_.each(this.attributes, function(section, sectionName) {
|
||||
if (section.metadata) {
|
||||
this.expandRestrictions(section.metadata.restrictions, this.makePath(sectionName, 'metadata'));
|
||||
if (this.isPlugin(section)) {
|
||||
_.each(section.metadata.versions, (version) => {
|
||||
this.expandRestrictions(version.metadata.restrictions, this.makePath(sectionName, 'metadata'));
|
||||
_.each(version,
|
||||
(setting, settingName) => this.expandRestrictions(setting.restrictions, this.makePath(sectionName, settingName))
|
||||
);
|
||||
}, this);
|
||||
}
|
||||
}
|
||||
_.each(group, function(setting, settingName) {
|
||||
this.expandRestrictions(setting.restrictions, this.makePath(groupName, settingName));
|
||||
_.each(setting.values, function(value) {
|
||||
this.expandRestrictions(value.restrictions, this.makePath(groupName, settingName, value.data));
|
||||
_.each(section, (setting, settingName) => {
|
||||
this.expandRestrictions(setting.restrictions, this.makePath(sectionName, settingName));
|
||||
_.each(setting.values, (value) => {
|
||||
this.expandRestrictions(value.restrictions, this.makePath(sectionName, settingName, value.data));
|
||||
}, this);
|
||||
}, this);
|
||||
}, this);
|
||||
|
@ -650,14 +690,15 @@ define([
|
|||
initialize: function() {
|
||||
// FIXME(vkramskikh): this will work only if there won't be
|
||||
// any restrictions added later in the same model
|
||||
this.once('change', this.processRestrictions, this);
|
||||
this.once('change', () => {
|
||||
this.processRestrictions();
|
||||
this.mergePluginSettings();
|
||||
}, this);
|
||||
},
|
||||
validate: function(attrs, options) {
|
||||
var errors = {},
|
||||
models = options ? options.models : {},
|
||||
checkRestrictions = _.bind(function(path) {
|
||||
return this.checkRestrictions(models, null, path);
|
||||
}, this);
|
||||
checkRestrictions = (path) => this.checkRestrictions(models, null, path);
|
||||
_.each(attrs, function(group, groupName) {
|
||||
if ((group.metadata || {}).enabled === false || checkRestrictions(this.makePath(groupName, 'metadata')).result) return;
|
||||
_.each(group, function(setting, settingName) {
|
||||
|
@ -694,7 +735,7 @@ define([
|
|||
}
|
||||
return result || _.any(group, function(setting, settingName) {
|
||||
if (this.checkRestrictions(models, null, this.makePath(groupName, settingName)).result) return false;
|
||||
return !_.isEqual(setting.value, initialAttributes[groupName][settingName].value);
|
||||
return !_.isEqual(setting.value, (initialAttributes[groupName][settingName] || {}).value);
|
||||
}, this);
|
||||
}, this);
|
||||
},
|
||||
|
|
|
@ -3770,6 +3770,25 @@ input[type=range] {
|
|||
}
|
||||
}
|
||||
}
|
||||
.plugin-versions {
|
||||
margin-bottom: 25px;
|
||||
.radio-group {
|
||||
> h4, > div {
|
||||
display: block;
|
||||
float: left;
|
||||
margin: 0;
|
||||
}
|
||||
h4 {
|
||||
position: relative;
|
||||
top: -2px;
|
||||
}
|
||||
.custom-tumbler + span {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Settings tab
|
||||
|
@ -3782,6 +3801,11 @@ input[type=range] {
|
|||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
.plugin-versions {
|
||||
.radio-group {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Networks tab
|
||||
|
|
|
@ -575,7 +575,8 @@
|
|||
"invalid_repo": "Invalid repo format",
|
||||
"invalid_priority": "Invalid priority"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugin_versions": "Versions"
|
||||
},
|
||||
"dashboard_tab": {
|
||||
"delete_environment": "Delete Environment",
|
||||
|
|
|
@ -601,7 +601,10 @@ function($, _, i18n, Backbone, React, models, dispatcher, utils, dialogs, compon
|
|||
networkConfiguration.get('networking_parameters').set(_.cloneDeep(this.state.initialConfiguration.networking_parameters));
|
||||
},
|
||||
loadInitialSettings: function() {
|
||||
this.props.cluster.get('settings').set(_.cloneDeep(this.state.initialSettingsAttributes)).isValid({models: this.state.configModels});
|
||||
var settings = this.props.cluster.get('settings');
|
||||
settings.set(_.cloneDeep(this.state.initialSettingsAttributes), {silent: true, validate: false});
|
||||
settings.mergePluginSettings();
|
||||
settings.isValid({models: this.state.configModels});
|
||||
},
|
||||
updateInitialConfiguration: function() {
|
||||
this.setState({initialConfiguration: _.cloneDeep(this.props.cluster.get('networkConfiguration').toJSON())});
|
||||
|
@ -1500,7 +1503,7 @@ function($, _, i18n, Backbone, React, models, dispatcher, utils, dialogs, compon
|
|||
.filter(
|
||||
(sectionName) => {
|
||||
var section = settings.get(sectionName);
|
||||
return (section.metadata.group == 'network' || !section.metadata.group && _.any(section, {group: 'network'})) &&
|
||||
return (section.metadata.group == 'network' || _.any(section, {group: 'network'})) &&
|
||||
!this.checkRestrictions('hide', settings.makePath(sectionName, 'metadata')).result;
|
||||
}
|
||||
)
|
||||
|
@ -1518,7 +1521,7 @@ function($, _, i18n, Backbone, React, models, dispatcher, utils, dialogs, compon
|
|||
!this.checkRestrictions('hide', settings.makePath(sectionName, settingName)).result
|
||||
) return settingName;
|
||||
}));
|
||||
if (_.isEmpty(settingsToDisplay)) return null;
|
||||
if (_.isEmpty(settingsToDisplay) && !settings.isPlugin(section)) return null;
|
||||
return <SettingSection
|
||||
key={sectionName}
|
||||
cluster={this.props.cluster}
|
||||
|
|
|
@ -112,13 +112,13 @@ function(_, i18n, utils, React, Expression, controls, customControls) {
|
|||
}
|
||||
}, this);
|
||||
// collect dependencies
|
||||
_.each(this.props.settings.attributes, function(group, sectionName) {
|
||||
_.each(this.props.settings.attributes, function(section, sectionName) {
|
||||
// don't take into account hidden dependent settings
|
||||
if (this.props.checkRestrictions('hide', this.props.makePath(sectionName, 'metadata')).result) return;
|
||||
_.each(group, function(setting, settingName) {
|
||||
_.each(section, function(setting, settingName) {
|
||||
// we support dependecies on checkboxes, toggleable setting groups, dropdowns and radio groups
|
||||
var pathToCheck = this.props.makePath(sectionName, settingName);
|
||||
if (!this.areCalculationsPossible(setting) || pathToCheck == path || this.props.checkRestrictions('hide', pathToCheck).result) return;
|
||||
if (!this.areCalculationsPossible(setting) || pathToCheck == path || this.props.checkRestrictions('hide', sectionName, settingName).result) return;
|
||||
if (setting[this.props.getValueAttribute(settingName)] == true) {
|
||||
addDependentRestrictions(pathToCheck, setting.label);
|
||||
} else {
|
||||
|
@ -148,42 +148,70 @@ function(_, i18n, utils, React, Expression, controls, customControls) {
|
|||
);
|
||||
});
|
||||
},
|
||||
onPluginVersionChange: function(pluginName, version) {
|
||||
var settings = this.props.settings;
|
||||
// FIXME: the following hacks cause we can't pass {validate: true} option to set method
|
||||
// this form of validation isn't supported in Backbone DeepModel
|
||||
settings.validationError = null;
|
||||
settings.set(this.props.makePath(pluginName, 'metadata', 'chosen_id'), Number(version));
|
||||
settings.mergePluginSettings();
|
||||
settings.isValid({models: this.props.configModels});
|
||||
this.props.settingsForChecks.set(_.cloneDeep(settings.attributes));
|
||||
},
|
||||
render: function() {
|
||||
var group = this.props.settings.get(this.props.sectionName),
|
||||
metadata = group.metadata,
|
||||
sortedSettings = _.sortBy(this.props.settingsToDisplay, function(settingName) {return group[settingName].weight;}),
|
||||
processedGroupRestrictions = this.processRestrictions(this.props.sectionName, 'metadata'),
|
||||
processedGroupDependencies = this.checkDependencies(this.props.sectionName, 'metadata'),
|
||||
var {settings, sectionName} = this.props,
|
||||
section = settings.get(sectionName),
|
||||
isPlugin = settings.isPlugin(section),
|
||||
metadata = section.metadata,
|
||||
sortedSettings = _.sortBy(this.props.settingsToDisplay, (settingName) => section[settingName].weight),
|
||||
processedGroupRestrictions = this.processRestrictions(sectionName, 'metadata'),
|
||||
processedGroupDependencies = this.checkDependencies(sectionName, 'metadata'),
|
||||
isGroupDisabled = this.props.locked || (this.props.lockedCluster && !metadata.always_editable) || processedGroupRestrictions.result,
|
||||
showSettingGroupWarning = !this.props.lockedCluster || metadata.always_editable,
|
||||
groupWarning = _.compact([processedGroupRestrictions.message, processedGroupDependencies.message]).join(' ');
|
||||
|
||||
return (
|
||||
<div className='setting-section'>
|
||||
{showSettingGroupWarning && processedGroupRestrictions.message &&
|
||||
<div className='alert alert-warning'>{processedGroupRestrictions.message}</div>
|
||||
}
|
||||
<h3>
|
||||
{metadata.toggleable ?
|
||||
<controls.Input
|
||||
type='checkbox'
|
||||
name='metadata'
|
||||
label={metadata.label || this.props.sectionName}
|
||||
label={metadata.label || sectionName}
|
||||
defaultChecked={metadata.enabled}
|
||||
disabled={isGroupDisabled || processedGroupDependencies.result}
|
||||
tooltipText={showSettingGroupWarning && groupWarning}
|
||||
onChange={this.props.onChange}
|
||||
/>
|
||||
:
|
||||
<span className={'subtab-group-' + this.props.sectionName}>{this.props.sectionName == 'common' ? i18n('cluster_page.settings_tab.groups.common') : metadata.label || this.props.sectionName}</span>
|
||||
<span className={'subtab-group-' + sectionName}>{sectionName == 'common' ? i18n('cluster_page.settings_tab.groups.common') : metadata.label || sectionName}</span>
|
||||
}
|
||||
</h3>
|
||||
<div>
|
||||
{isPlugin &&
|
||||
<div className='plugin-versions clearfix'>
|
||||
<controls.RadioGroup
|
||||
name={sectionName}
|
||||
label={i18n('cluster_page.settings_tab.plugin_versions')}
|
||||
values={_.map(metadata.versions, (version) => {
|
||||
return {
|
||||
data: version.metadata.plugin_id,
|
||||
label: version.metadata.plugin_version,
|
||||
defaultChecked: version.metadata.plugin_id == metadata.chosen_id,
|
||||
disabled: isGroupDisabled || (metadata.toggleable && !metadata.enabled)
|
||||
};
|
||||
})}
|
||||
onChange={this.onPluginVersionChange}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
{_.map(sortedSettings, function(settingName) {
|
||||
var setting = group[settingName],
|
||||
path = this.props.makePath(this.props.sectionName, settingName),
|
||||
error = (this.props.settings.validationError || {})[path],
|
||||
processedSettingRestrictions = this.processRestrictions(this.props.sectionName, settingName),
|
||||
processedSettingDependencies = this.checkDependencies(this.props.sectionName, settingName),
|
||||
var setting = section[settingName],
|
||||
settingKey = settingName + (isPlugin ? '-' + metadata.chosen_id : ''),
|
||||
path = this.props.makePath(sectionName, settingName),
|
||||
error = (settings.validationError || {})[path],
|
||||
processedSettingRestrictions = this.processRestrictions(sectionName, settingName),
|
||||
processedSettingDependencies = this.checkDependencies(sectionName, settingName),
|
||||
isSettingDisabled = isGroupDisabled || (metadata.toggleable && !metadata.enabled) || processedSettingRestrictions.result || processedSettingDependencies.result,
|
||||
showSettingWarning = showSettingGroupWarning && !isGroupDisabled && (!metadata.toggleable || metadata.enabled),
|
||||
settingWarning = _.compact([processedSettingRestrictions.message, processedSettingDependencies.message]).join(' ');
|
||||
|
@ -194,7 +222,7 @@ function(_, i18n, utils, React, Expression, controls, customControls) {
|
|||
return <CustomControl
|
||||
{...setting}
|
||||
{... _.pick(this.props, 'cluster', 'settings', 'configModels')}
|
||||
key={settingName}
|
||||
key={settingKey}
|
||||
path={path}
|
||||
error={error}
|
||||
disabled={isSettingDisabled}
|
||||
|
@ -217,7 +245,7 @@ function(_, i18n, utils, React, Expression, controls, customControls) {
|
|||
.compact()
|
||||
.value();
|
||||
if (setting.type == 'radio') return <controls.RadioGroup {...this.props}
|
||||
key={settingName}
|
||||
key={settingKey}
|
||||
name={settingName}
|
||||
label={setting.label}
|
||||
values={values}
|
||||
|
@ -230,7 +258,7 @@ function(_, i18n, utils, React, Expression, controls, customControls) {
|
|||
<span dangerouslySetInnerHTML={{__html: utils.urlify(_.escape(setting.description))}} />;
|
||||
return <controls.Input
|
||||
{... _.pick(setting, 'type', 'label')}
|
||||
key={settingName}
|
||||
key={settingKey}
|
||||
name={settingName}
|
||||
description={settingDescription}
|
||||
children={setting.type == 'select' ? this.composeOptions(setting.values) : null}
|
||||
|
|
|
@ -69,14 +69,6 @@ function($, _, i18n, React, utils, models, componentMixins, SettingSection) {
|
|||
actionInProgress: false
|
||||
};
|
||||
},
|
||||
componentWillMount: function() {
|
||||
var settings = this.props.cluster.get('settings');
|
||||
if (this.checkRestrictions('hide', settings.makePath(this.props.activeSettingsSectionName, 'metadata')).result) {
|
||||
// FIXME: First group might also be hidded by restrictions
|
||||
// which would cause no group selected
|
||||
this.props.setActiveSettingsGroupName();
|
||||
}
|
||||
},
|
||||
componentDidMount: function() {
|
||||
this.props.cluster.get('settings').isValid({models: this.state.configModels});
|
||||
},
|
||||
|
@ -131,8 +123,8 @@ function($, _, i18n, React, utils, models, componentMixins, SettingSection) {
|
|||
if (deferred) {
|
||||
this.setState({actionInProgress: true});
|
||||
deferred
|
||||
.done(_.bind(function() {
|
||||
_.each(settings.attributes, function(section, sectionName) {
|
||||
.done(() => {
|
||||
_.each(settings.attributes, (section, sectionName) => {
|
||||
if ((!lockedCluster || section.metadata.always_editable) && section.metadata.group != 'network') {
|
||||
_.each(section, function(setting, settingName) {
|
||||
// do not update hidden settings (hack for #1442143),
|
||||
|
@ -143,20 +135,20 @@ function($, _, i18n, React, utils, models, componentMixins, SettingSection) {
|
|||
});
|
||||
}
|
||||
});
|
||||
|
||||
settings.mergePluginSettings();
|
||||
settings.isValid({models: this.state.configModels});
|
||||
this.setState({
|
||||
actionInProgress: false,
|
||||
key: _.now()
|
||||
});
|
||||
}, this))
|
||||
.fail(function(response) {
|
||||
utils.showErrorDialog({
|
||||
})
|
||||
.fail(
|
||||
(response) => utils.showErrorDialog({
|
||||
title: i18n('cluster_page.settings_tab.settings_error.title'),
|
||||
message: i18n('cluster_page.settings_tab.settings_error.load_defaults_warning'),
|
||||
response: response
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
revertChanges: function() {
|
||||
|
@ -164,7 +156,10 @@ function($, _, i18n, React, utils, models, componentMixins, SettingSection) {
|
|||
this.setState({key: _.now()});
|
||||
},
|
||||
loadInitialSettings: function() {
|
||||
this.props.cluster.get('settings').set(_.cloneDeep(this.state.initialAttributes)).isValid({models: this.state.configModels});
|
||||
var settings = this.props.cluster.get('settings');
|
||||
settings.set(_.cloneDeep(this.state.initialAttributes), {silent: true, validate: false});
|
||||
settings.mergePluginSettings();
|
||||
settings.isValid({models: this.state.configModels});
|
||||
},
|
||||
onChange: function(groupName, settingName, value) {
|
||||
var settings = this.props.cluster.get('settings'),
|
||||
|
@ -177,8 +172,7 @@ function($, _, i18n, React, utils, models, componentMixins, SettingSection) {
|
|||
settings.isValid({models: this.state.configModels});
|
||||
},
|
||||
checkRestrictions: function(action, path) {
|
||||
var settings = this.props.cluster.get('settings');
|
||||
return settings.checkRestrictions(this.state.configModels, action, path);
|
||||
return this.props.cluster.get('settings').checkRestrictions(this.state.configModels, action, path);
|
||||
},
|
||||
isSavingPossible: function() {
|
||||
var cluster = this.props.cluster,
|
||||
|
@ -207,16 +201,16 @@ function($, _, i18n, React, utils, models, componentMixins, SettingSection) {
|
|||
|
||||
// Prepare list of settings organized by groups
|
||||
var groupedSettings = {};
|
||||
_.each(settingsGroupList, function(group) {
|
||||
groupedSettings[group] = {};
|
||||
});
|
||||
_.each(settingsGroupList, (group) => groupedSettings[group] = {});
|
||||
_.each(settings.attributes, function(section, sectionName) {
|
||||
var isHidden = this.checkRestrictions('hide', settings.makePath(sectionName, 'metadata')).result;
|
||||
if (!isHidden) {
|
||||
var group = section.metadata.group,
|
||||
hasErrors = invalidSections[sectionName];
|
||||
if (group) {
|
||||
if (group != 'network') groupedSettings[settings.sanitizeGroup(group)][sectionName] = {invalid: hasErrors};
|
||||
if (group != 'network') {
|
||||
groupedSettings[settings.sanitizeGroup(group)][sectionName] = {invalid: hasErrors};
|
||||
}
|
||||
} else {
|
||||
// Settings like 'Common' can be splitted to different groups
|
||||
var settingGroups = _.chain(section)
|
||||
|
@ -225,6 +219,12 @@ function($, _, i18n, React, utils, models, componentMixins, SettingSection) {
|
|||
.unique()
|
||||
.without('network')
|
||||
.value();
|
||||
|
||||
// to support plugins without settings (just for user to be able to switch its version)
|
||||
if (!settingGroups.length && settings.isPlugin(section)) {
|
||||
groupedSettings.other[sectionName] = {settings: [], invalid: hasErrors};
|
||||
}
|
||||
|
||||
_.each(settingGroups, function(settingGroup) {
|
||||
var calculatedGroup = settings.sanitizeGroup(settingGroup),
|
||||
pickedSettings = _.compact(_.map(section, function(setting, settingName) {
|
||||
|
|
Loading…
Reference in New Issue