You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
643 lines
18 KiB
643 lines
18 KiB
|
|
/** |
|
* Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com> |
|
* This file is licensed under the Affero General Public License version 3 or later. |
|
* See the COPYING-README file. |
|
*/ |
|
|
|
OCA = OCA || {}; |
|
|
|
(function() { |
|
|
|
/** |
|
* @classdesc An abstract tab view |
|
* @abstract |
|
*/ |
|
var WizardTabGeneric = OCA.LDAP.Wizard.WizardObject.subClass({ |
|
isActive: false, |
|
|
|
/** |
|
* @property {string} - class that identifies a multiselect-plugin |
|
* control. |
|
*/ |
|
multiSelectPluginClass: 'multiSelectPlugin', |
|
|
|
/** |
|
* @property {string} - class that identifies a multiselect-plugin |
|
* control. |
|
*/ |
|
bjQuiButtonClass: 'ui-button', |
|
|
|
/** |
|
* @property {bool} - indicates whether a filter mode toggle operation |
|
* is still in progress |
|
*/ |
|
isToggling: false, |
|
|
|
/** @inheritdoc */ |
|
init: function(tabIndex, tabID) { |
|
this.tabIndex = tabIndex; |
|
this.tabID = tabID; |
|
this.spinner = $('.ldapSpinner').first().clone().removeClass('hidden'); |
|
_.bindAll(this, '_toggleRawFilterMode', '_toggleRawFilterModeConfirmation'); |
|
}, |
|
|
|
/** |
|
* sets the configuration items that are managed by that view. |
|
* |
|
* The parameter contains key-value pairs the key being the |
|
* configuration keys and the value being its setter method. |
|
* |
|
* @param {object} managedItems |
|
*/ |
|
setManagedItems: function(managedItems) { |
|
this.managedItems = managedItems; |
|
this._enableAutoSave(); |
|
this._enableSaveButton(); |
|
}, |
|
|
|
/** |
|
* Sets the config model. The concrete view likely wants to subscribe |
|
* to events as well. |
|
* |
|
* @param {OCA.LDAP.Wizard.ConfigModel} configModel |
|
*/ |
|
setModel: function(configModel) { |
|
this.configModel = configModel; |
|
this.parsedFilterMode = this.configModel.FILTER_MODE_ASSISTED; |
|
this.configModel.on('detectionStarted', this.onDetectionStarted, this); |
|
this.configModel.on('detectionCompleted', this.onDetectionCompleted, this); |
|
this.configModel.on('serverError', this.onServerError, this); |
|
this.configModel.on('setCompleted', this.onItemSaved, this); |
|
this.configModel.on('configUpdated', this.onConfigLoaded, this); |
|
}, |
|
|
|
/** |
|
* the method can be used to display a different error/information |
|
* message than provided by the Nextcloud server response. The concrete |
|
* Tab View may optionally implement it. Returning an empty string will |
|
* avoid any notification. |
|
* |
|
* @param {string} message |
|
* @param {string} key |
|
* @returns {string} |
|
*/ |
|
overrideErrorMessage: function(message, key) { |
|
if(message === 'LDAP authentication method rejected' |
|
&& !this.configModel.configuration.ldap_dn) |
|
{ |
|
message = t('user_ldap', 'Anonymous bind is not allowed. Please provide a User DN and Password.'); |
|
} else if (message === 'LDAP Operations error' |
|
&& !this.configModel.configuration.ldap_dn |
|
&& !this.configModel.configuration.ldap_agent_password) |
|
{ |
|
message = t('user_ldap', 'LDAP Operations error. Anonymous bind might not be allowed.'); |
|
} |
|
|
|
return message; |
|
}, |
|
|
|
/** |
|
* this is called by the main view, if the tab is being switched to. |
|
*/ |
|
onActivate: function() { |
|
if(!_.isUndefined(this.filterModeKey) |
|
&& this.configModel.configuration.ldap_experienced_admin === '1') { |
|
this.setFilterMode(this.configModel.FILTER_MODE_RAW); |
|
} |
|
}, |
|
|
|
/** |
|
* updates the tab when the model loaded a configuration and notified |
|
* this view. |
|
* |
|
* @param {WizardTabGeneric} view - this instance |
|
* @param {Object} configuration |
|
*/ |
|
onConfigLoaded: function(view, configuration) { |
|
for(var key in view.managedItems){ |
|
if(!_.isUndefined(configuration[key])) { |
|
var value = configuration[key]; |
|
var methodName = view.managedItems[key].setMethod; |
|
if(!_.isUndefined(view[methodName])) { |
|
view[methodName](value); |
|
} |
|
} |
|
} |
|
}, |
|
|
|
/** |
|
* reacts on a set action on the model and updates the tab with the |
|
* valid value. |
|
* |
|
* @param {WizardTabGeneric} view |
|
* @param {Object} result |
|
*/ |
|
onItemSaved: function(view, result) { |
|
if(!_.isUndefined(view.managedItems[result.key])) { |
|
var methodName = view.managedItems[result.key].setMethod; |
|
view[methodName](result.value); |
|
if(!result.isSuccess) { |
|
OC.Notification.showTemporary(t('user_ldap', 'Saving failed. Please make sure the database is in Operation. Reload before continuing.')); |
|
console.warn(result.errorMessage); |
|
} |
|
} |
|
}, |
|
|
|
/** |
|
* displays server error messages. |
|
* |
|
* @param view |
|
* @param payload |
|
*/ |
|
onServerError: function(view, payload) { |
|
if ( !_.isUndefined(view.managedItems[payload.relatedKey])) { |
|
var message = view.overrideErrorMessage(payload.message, payload.relatedKey); |
|
if(message) { |
|
OC.Notification.showTemporary(message); |
|
} |
|
} |
|
}, |
|
|
|
/** |
|
* disables affected, managed fields if a detector is running against them |
|
* |
|
* @param {WizardTabGeneric} view |
|
* @param {string} key |
|
*/ |
|
onDetectionStarted: function(view, key) { |
|
if(!_.isUndefined(view.managedItems[key])) { |
|
view.disableElement(view.managedItems[key].$element); |
|
if(!_.isUndefined(view.managedItems[key].$relatedElements)){ |
|
view.disableElement(view.managedItems[key].$relatedElements); |
|
} |
|
view.attachSpinner(view.managedItems[key].$element.attr('id')); |
|
} |
|
}, |
|
|
|
/** |
|
* enables affected, managed fields after a detector was run against them |
|
* |
|
* @param {WizardTabGeneric} view |
|
* @param {string} key |
|
*/ |
|
onDetectionCompleted: function(view, key) { |
|
if(!_.isUndefined(view.managedItems[key])) { |
|
view.enableElement(view.managedItems[key].$element); |
|
if(!_.isUndefined(view.managedItems[key].$relatedElements)){ |
|
view.enableElement(view.managedItems[key].$relatedElements); |
|
} |
|
view.removeSpinner(view.managedItems[key].$element.attr('id')); |
|
} |
|
}, |
|
|
|
/** |
|
* sets the value to an HTML element. Checkboxes, text areas and (text) |
|
* input fields are supported. |
|
* |
|
* @param {jQuery} $element - the target element |
|
* @param {string|number|Array} value |
|
*/ |
|
setElementValue: function($element, value) { |
|
// deal with check box |
|
if ($element.is('input[type=checkbox]')) { |
|
this._setCheckBox($element, value); |
|
return; |
|
} |
|
|
|
// special cases: deal with text area and multiselect |
|
if ($element.is('textarea') && $.isArray(value)) { |
|
value = value.join("\n"); |
|
} else if($element.hasClass(this.multiSelectPluginClass)) { |
|
if(!_.isArray(value)) { |
|
value = value.split("\n"); |
|
} |
|
} |
|
|
|
if ($element.is('span')) { |
|
$element.text(value); |
|
} else { |
|
$element.val(value); |
|
} |
|
}, |
|
|
|
/** |
|
* replaces options on a multiselect element |
|
* |
|
* @param {jQuery} $element - the multiselect element |
|
* @param {Array} options |
|
*/ |
|
equipMultiSelect: function($element, options) { |
|
if($element.find('option').length === 0) { |
|
$element.empty(); |
|
for (var i in options) { |
|
var name = options[i]; |
|
$element.append($('<option>').val(name).text(name).attr('title', name)); |
|
} |
|
} |
|
if(!$element.hasClass('ldapGroupList')) { |
|
$element.multiselect('refresh'); |
|
this.enableElement($element); |
|
} |
|
}, |
|
|
|
/** |
|
* enables the specified HTML element |
|
* |
|
* @param {jQuery} $element |
|
*/ |
|
enableElement: function($element) { |
|
var isMS = $element.is('select[multiple]'); |
|
var hasOptions = isMS ? ($element.find('option').length > 0) : false; |
|
|
|
if($element.hasClass(this.multiSelectPluginClass) && hasOptions) { |
|
$element.multiselect("enable"); |
|
} else if ($element.hasClass(this.bjQuiButtonClass)) { |
|
$element.button("enable"); |
|
} |
|
else if(!isMS || (isMS && hasOptions)) { |
|
$element.prop('disabled', false); |
|
} |
|
}, |
|
|
|
/** |
|
* disables the specified HTML element |
|
* |
|
* @param {jQuery} $element |
|
*/ |
|
disableElement: function($element) { |
|
if($element.hasClass(this.multiSelectPluginClass)) { |
|
$element.multiselect("disable"); |
|
} else if ($element.hasClass(this.bjQuiButtonClass)) { |
|
$element.button("disable"); |
|
} else { |
|
$element.prop('disabled', 'disabled'); |
|
} |
|
}, |
|
|
|
/** |
|
* attaches a spinner icon to the HTML element specified by ID |
|
* |
|
* @param {string} elementID |
|
*/ |
|
attachSpinner: function(elementID) { |
|
if($('#' + elementID + ' + .ldapSpinner').length == 0) { |
|
var spinner = this.spinner.clone(); |
|
var $element = $('#' + elementID); |
|
$(spinner).insertAfter($element); |
|
// and special treatment for multiselects: |
|
if ($element.is('select[multiple]')) { |
|
$('#' + elementID + " + img + button").css('display', 'none'); |
|
} |
|
} |
|
}, |
|
|
|
/** |
|
* removes the spinner icon from the HTML element specified by ID |
|
* |
|
* @param {string} elementID |
|
*/ |
|
removeSpinner: function(elementID) { |
|
$('#' + elementID+' + .ldapSpinner').remove(); |
|
// and special treatment for multiselects: |
|
$('#' + elementID + " + button").css('display', 'inline'); |
|
}, |
|
|
|
/** |
|
* whether the wizard works in experienced admin mode |
|
* |
|
* @returns {boolean} |
|
*/ |
|
isExperiencedMode: function() { |
|
return parseInt(this.configModel.configuration.ldap_experienced_admin, 10) === 1; |
|
}, |
|
|
|
/** |
|
* sets up auto-save functionality to the managed items |
|
* |
|
* @private |
|
*/ |
|
_enableAutoSave: function() { |
|
var view = this; |
|
|
|
for(var id in this.managedItems) { |
|
if(_.isUndefined(this.managedItems[id].$element) |
|
|| _.isUndefined(this.managedItems[id].setMethod) |
|
|| (!_.isUndefined(this.managedItems[id].preventAutoSave) |
|
&& this.managedItems[id].preventAutoSave === true) |
|
) { |
|
continue; |
|
} |
|
var $element = this.managedItems[id].$element; |
|
if (!$element.is('select[multiple]')) { |
|
$element.change(function() { |
|
view._requestSave($(this)); |
|
}); |
|
} |
|
} |
|
}, |
|
|
|
/** |
|
* set's up save-button behavior (essentially used for agent dn and pwd) |
|
* |
|
* @private |
|
*/ |
|
_enableSaveButton: function() { |
|
var view = this; |
|
|
|
// TODO: this is not nice, because it fires one request per change |
|
// in the scenario this happens twice, causes detectors to run |
|
// duplicated etc. To have this work properly, the wizard endpoint |
|
// must accept setting multiple changes. Instead of messing around |
|
// with old ajax/wizard.php use this opportunity and create a |
|
// Controller |
|
for(var id in this.managedItems) { |
|
if(_.isUndefined(this.managedItems[id].$element) |
|
|| _.isUndefined(this.managedItems[id].$saveButton) |
|
) { |
|
continue; |
|
} |
|
(function (item) { |
|
item.$saveButton.click(function(event) { |
|
event.preventDefault(); |
|
view._requestSave(item.$element); |
|
item.$saveButton.removeClass('primary'); |
|
}); |
|
item.$element.change(function () { |
|
item.$saveButton.addClass('primary'); |
|
}); |
|
})(this.managedItems[id]); |
|
} |
|
}, |
|
|
|
/** |
|
* initializes a multiSelect element |
|
* |
|
* @param {jQuery} $element |
|
* @param {string} caption |
|
* @private |
|
*/ |
|
_initMultiSelect: function($element, caption) { |
|
var view = this; |
|
$element.multiselect({ |
|
header: false, |
|
selectedList: 9, |
|
noneSelectedText: caption, |
|
classes: this.multiSelectPluginClass, |
|
close: function() { |
|
view._requestSave($element); |
|
} |
|
}); |
|
}, |
|
|
|
/** |
|
* @typedef {object} viewSaveInfo |
|
* @property {function} val |
|
* @property {function} attr |
|
* @property {function} is |
|
*/ |
|
|
|
/** |
|
* requests a save operation from the model for a given value |
|
* represented by a HTML element and its ID. |
|
* |
|
* @param {jQuery|viewSaveInfo} $element |
|
* @private |
|
*/ |
|
_requestSave: function($element) { |
|
var value = ''; |
|
if($element.is('input[type=checkbox]') |
|
&& !$element.is(':checked')) { |
|
value = 0; |
|
} else if ($element.is('select[multiple]')) { |
|
var entries = $element.multiselect("getChecked"); |
|
for(var i = 0; i < entries.length; i++) { |
|
value = value + "\n" + entries[i].value; |
|
} |
|
value = $.trim(value); |
|
} else { |
|
value = $element.val(); |
|
} |
|
this.configModel.set($element.attr('id'), value); |
|
}, |
|
|
|
/** |
|
* updates a checkbox element according to the provided value |
|
* |
|
* @param {jQuery} $element |
|
* @param {string|number} value |
|
* @private |
|
*/ |
|
_setCheckBox: function($element, value) { |
|
if(parseInt(value, 10) === 1) { |
|
$element.prop('checked', 'checked'); |
|
} else { |
|
$element.removeAttr('checked'); |
|
} |
|
}, |
|
|
|
/** |
|
* this is called when the filter mode is switched to assisted. The |
|
* concrete tab view should implement this, to load LDAP features |
|
* (e.g. object classes, groups, attributes…), if necessary. |
|
*/ |
|
considerFeatureRequests: function() {}, |
|
|
|
/** |
|
* this is called when the filter mode is switched to Assisted. The |
|
* concrete tab view should request the compilation of the respective |
|
* filter. |
|
*/ |
|
requestCompileFilter: function() { |
|
this.configModel.requestWizard(this.filterName); |
|
}, |
|
|
|
/** |
|
* sets the filter mode initially and resets the "isToggling" marker. |
|
* This method is called after a save operation against the mode key. |
|
* |
|
* @param mode |
|
*/ |
|
setFilterModeOnce: function(mode) { |
|
this.isToggling = false; |
|
if(!this.filterModeInitialized) { |
|
this.filterModeInitialized = true; |
|
this.setFilterMode(mode); |
|
} |
|
}, |
|
|
|
/** |
|
* sets the filter mode according to the provided configuration value |
|
* |
|
* @param {string} mode |
|
*/ |
|
setFilterMode: function(mode) { |
|
if(parseInt(mode, 10) === this.configModel.FILTER_MODE_ASSISTED) { |
|
this.parsedFilterMode = this.configModel.FILTER_MODE_ASSISTED; |
|
this.considerFeatureRequests(); |
|
this._setFilterModeAssisted(); |
|
if(this.isActive) { |
|
// filter compilation should happen only, if the mode was |
|
// switched manually, but not when initiating the view |
|
this.requestCompileFilter(); |
|
} |
|
} else { |
|
this._setFilterModeRaw(); |
|
this.parsedFilterMode = this.configModel.FILTER_MODE_RAW; |
|
} |
|
}, |
|
|
|
/** |
|
* updates the UI so that it represents the assisted mode setting |
|
* |
|
* @private |
|
*/ |
|
_setFilterModeAssisted: function() { |
|
var view = this; |
|
this.$filterModeRawContainer.addClass('invisible'); |
|
var filter = this.$filterModeRawContainer.find('.ldapFilterInputElement').val(); |
|
this.$filterModeRawContainer.siblings('.ldapReadOnlyFilterContainer').find('.ldapFilterReadOnlyElement').text(filter); |
|
this.$filterModeRawContainer.siblings('.ldapReadOnlyFilterContainer').removeClass('hidden'); |
|
$.each(this.filterModeDisableableElements, function(i, $element) { |
|
view.enableElement($element); |
|
}); |
|
if(!_.isUndefined(this.filterModeStateElement)) { |
|
if (this.filterModeStateElement.status === 'enabled') { |
|
this.enableElement(this.filterModeStateElement.$element); |
|
} else { |
|
this.filterModeStateElement.status = 'disabled'; |
|
} |
|
} |
|
}, |
|
|
|
/** |
|
* updates the UI so that it represents the raw mode setting |
|
* |
|
* @private |
|
*/ |
|
_setFilterModeRaw: function() { |
|
var view = this; |
|
this.$filterModeRawContainer.removeClass('invisible'); |
|
this.$filterModeRawContainer.siblings('.ldapReadOnlyFilterContainer').addClass('hidden'); |
|
$.each(this.filterModeDisableableElements, function (i, $element) { |
|
view.disableElement($element); |
|
}); |
|
|
|
if(!_.isUndefined(this.filterModeStateElement)) { |
|
if(this.filterModeStateElement.$element.multiselect().attr('disabled') === 'disabled') { |
|
this.filterModeStateElement.status = 'disabled'; |
|
} else { |
|
this.filterModeStateElement.status = 'enabled'; |
|
} |
|
} |
|
if(!_.isUndefined(this.filterModeStateElement)) { |
|
this.disableElement(this.filterModeStateElement.$element); |
|
} |
|
}, |
|
|
|
/** |
|
* @callback toggleConfirmCallback |
|
* @param {boolean} isConfirmed |
|
*/ |
|
|
|
/** |
|
* shows a confirmation dialogue before switching from raw to assisted |
|
* mode if experienced mode is enabled. |
|
* |
|
* @param {toggleConfirmCallback} toggleFnc |
|
* @private |
|
*/ |
|
_toggleRawFilterModeConfirmation: function(toggleFnc) { |
|
if( !this.isExperiencedMode() |
|
|| this.parsedFilterMode === this.configModel.FILTER_MODE_ASSISTED |
|
) { |
|
toggleFnc(true); |
|
} else { |
|
OCdialogs.confirm( |
|
t('user_ldap', 'Switching the mode will enable automatic LDAP queries. Depending on your LDAP size they may take a while. Do you still want to switch the mode?'), |
|
t('user_ldap', 'Mode switch'), |
|
toggleFnc |
|
); |
|
} |
|
}, |
|
|
|
/** |
|
* toggles the visibility of a raw filter container and so also the |
|
* state of the multi-select controls. The model is requested to save |
|
* the state. |
|
*/ |
|
_toggleRawFilterMode: function() { |
|
var view = this; |
|
this._toggleRawFilterModeConfirmation(function(isConfirmed) { |
|
if(!isConfirmed) { |
|
return; |
|
} |
|
/** var {number} */ |
|
var mode; |
|
if (view.parsedFilterMode === view.configModel.FILTER_MODE_ASSISTED) { |
|
mode = view.configModel.FILTER_MODE_RAW; |
|
} else { |
|
mode = view.configModel.FILTER_MODE_ASSISTED; |
|
} |
|
view.setFilterMode(mode); |
|
/** @var {viewSaveInfo} */ |
|
var saveInfo = { |
|
val: function () { |
|
return mode; |
|
}, |
|
attr: function () { |
|
return view.filterModeKey; |
|
}, |
|
is: function () { |
|
return false; |
|
} |
|
}; |
|
view._requestSave(saveInfo); |
|
}); |
|
}, |
|
|
|
/** |
|
* @typedef {object} filterModeStateElementObj |
|
* @property {string} status - either "enabled" or "disabled" |
|
* @property {jQuery} $element |
|
*/ |
|
|
|
/** |
|
* initializes a raw filter mode switcher |
|
* |
|
* @param {jQuery} $switcher - the element receiving the click |
|
* @param {jQuery} $filterModeRawContainer - contains the raw filter |
|
* input elements |
|
* @param {jQuery[]} filterModeDisableableElements - an array of elements |
|
* not belonging to the raw filter part that shall be en/disabled. |
|
* @param {string} filterModeKey - the setting key that save the state |
|
* of the mode |
|
* @param {filterModeStateElementObj} [filterModeStateElement] - one element |
|
* which status (enabled or not) is tracked by a setting |
|
* @private |
|
*/ |
|
_initFilterModeSwitcher: function( |
|
$switcher, |
|
$filterModeRawContainer, |
|
filterModeDisableableElements, |
|
filterModeKey, |
|
filterModeStateElement |
|
) { |
|
this.$filterModeRawContainer = $filterModeRawContainer; |
|
this.filterModeDisableableElements = filterModeDisableableElements; |
|
this.filterModeStateElement = filterModeStateElement; |
|
this.filterModeKey = filterModeKey; |
|
var view = this; |
|
$switcher.click(function() { |
|
if(view.isToggling) { |
|
return; |
|
} |
|
view.isToggling = true; |
|
view._toggleRawFilterMode(); |
|
}); |
|
}, |
|
|
|
}); |
|
|
|
OCA.LDAP.Wizard.WizardTabGeneric = WizardTabGeneric; |
|
})();
|
|
|