wizardTabGeneric.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642
  1. /**
  2. * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors
  3. * SPDX-FileCopyrightText: 2015-2016 ownCloud, Inc.
  4. * SPDX-License-Identifier: AGPL-3.0-or-later
  5. */
  6. OCA = OCA || {};
  7. (function() {
  8. /**
  9. * @classdesc An abstract tab view
  10. * @abstract
  11. */
  12. var WizardTabGeneric = OCA.LDAP.Wizard.WizardObject.subClass({
  13. isActive: false,
  14. /**
  15. * @property {string} - class that identifies a multiselect-plugin
  16. * control.
  17. */
  18. multiSelectPluginClass: 'multiSelectPlugin',
  19. /**
  20. * @property {string} - class that identifies a multiselect-plugin
  21. * control.
  22. */
  23. bjQuiButtonClass: 'ui-button',
  24. /**
  25. * @property {boolean} - indicates whether a filter mode toggle operation
  26. * is still in progress
  27. */
  28. isToggling: false,
  29. /** @inheritdoc */
  30. init: function(tabIndex, tabID) {
  31. this.tabIndex = tabIndex;
  32. this.tabID = tabID;
  33. this.spinner = $('.ldapSpinner').first().clone().removeClass('hidden');
  34. _.bindAll(this, '_toggleRawFilterMode', '_toggleRawFilterModeConfirmation');
  35. },
  36. /**
  37. * sets the configuration items that are managed by that view.
  38. *
  39. * The parameter contains key-value pairs the key being the
  40. * configuration keys and the value being its setter method.
  41. *
  42. * @param {object} managedItems
  43. */
  44. setManagedItems: function(managedItems) {
  45. this.managedItems = managedItems;
  46. this._enableAutoSave();
  47. this._enableSaveButton();
  48. },
  49. /**
  50. * Sets the config model. The concrete view likely wants to subscribe
  51. * to events as well.
  52. *
  53. * @param {OCA.LDAP.Wizard.ConfigModel} configModel
  54. */
  55. setModel: function(configModel) {
  56. this.configModel = configModel;
  57. this.parsedFilterMode = this.configModel.FILTER_MODE_ASSISTED;
  58. this.configModel.on('detectionStarted', this.onDetectionStarted, this);
  59. this.configModel.on('detectionCompleted', this.onDetectionCompleted, this);
  60. this.configModel.on('serverError', this.onServerError, this);
  61. this.configModel.on('setCompleted', this.onItemSaved, this);
  62. this.configModel.on('configUpdated', this.onConfigLoaded, this);
  63. },
  64. /**
  65. * the method can be used to display a different error/information
  66. * message than provided by the Nextcloud server response. The concrete
  67. * Tab View may optionally implement it. Returning an empty string will
  68. * avoid any notification.
  69. *
  70. * @param {string} message
  71. * @param {string} key
  72. * @returns {string}
  73. */
  74. overrideErrorMessage: function(message, key) {
  75. if(message === 'LDAP authentication method rejected'
  76. && !this.configModel.configuration.ldap_dn)
  77. {
  78. message = t('user_ldap', 'Anonymous bind is not allowed. Please provide a User DN and Password.');
  79. } else if (message === 'LDAP Operations error'
  80. && !this.configModel.configuration.ldap_dn
  81. && !this.configModel.configuration.ldap_agent_password)
  82. {
  83. message = t('user_ldap', 'LDAP Operations error. Anonymous bind might not be allowed.');
  84. }
  85. return message;
  86. },
  87. /**
  88. * this is called by the main view, if the tab is being switched to.
  89. */
  90. onActivate: function() {
  91. if(!_.isUndefined(this.filterModeKey)
  92. && this.configModel.configuration.ldap_experienced_admin === '1') {
  93. this.setFilterMode(this.configModel.FILTER_MODE_RAW);
  94. }
  95. },
  96. /**
  97. * updates the tab when the model loaded a configuration and notified
  98. * this view.
  99. *
  100. * @param {WizardTabGeneric} view - this instance
  101. * @param {Object} configuration
  102. */
  103. onConfigLoaded: function(view, configuration) {
  104. for(var key in view.managedItems){
  105. if(!_.isUndefined(configuration[key])) {
  106. var value = configuration[key];
  107. var methodName = view.managedItems[key].setMethod;
  108. if(!_.isUndefined(view[methodName])) {
  109. view[methodName](value);
  110. }
  111. }
  112. }
  113. },
  114. /**
  115. * reacts on a set action on the model and updates the tab with the
  116. * valid value.
  117. *
  118. * @param {WizardTabGeneric} view
  119. * @param {Object} result
  120. */
  121. onItemSaved: function(view, result) {
  122. if(!_.isUndefined(view.managedItems[result.key])) {
  123. var methodName = view.managedItems[result.key].setMethod;
  124. view[methodName](result.value);
  125. if(!result.isSuccess) {
  126. OC.Notification.showTemporary(t('user_ldap', 'Saving failed. Please make sure the database is in Operation. Reload before continuing.'));
  127. console.warn(result.errorMessage);
  128. }
  129. }
  130. },
  131. /**
  132. * displays server error messages.
  133. *
  134. * @param {any} view -
  135. * @param {any} payload -
  136. */
  137. onServerError: function(view, payload) {
  138. if ( !_.isUndefined(view.managedItems[payload.relatedKey])) {
  139. var message = view.overrideErrorMessage(payload.message, payload.relatedKey);
  140. if(message) {
  141. OC.Notification.showTemporary(message);
  142. }
  143. }
  144. },
  145. /**
  146. * disables affected, managed fields if a detector is running against them
  147. *
  148. * @param {WizardTabGeneric} view
  149. * @param {string} key
  150. */
  151. onDetectionStarted: function(view, key) {
  152. if(!_.isUndefined(view.managedItems[key])) {
  153. view.disableElement(view.managedItems[key].$element);
  154. if(!_.isUndefined(view.managedItems[key].$relatedElements)){
  155. view.disableElement(view.managedItems[key].$relatedElements);
  156. }
  157. view.attachSpinner(view.managedItems[key].$element.attr('id'));
  158. }
  159. },
  160. /**
  161. * enables affected, managed fields after a detector was run against them
  162. *
  163. * @param {WizardTabGeneric} view
  164. * @param {string} key
  165. */
  166. onDetectionCompleted: function(view, key) {
  167. if(!_.isUndefined(view.managedItems[key])) {
  168. view.enableElement(view.managedItems[key].$element);
  169. if(!_.isUndefined(view.managedItems[key].$relatedElements)){
  170. view.enableElement(view.managedItems[key].$relatedElements);
  171. }
  172. view.removeSpinner(view.managedItems[key].$element.attr('id'));
  173. }
  174. },
  175. /**
  176. * sets the value to an HTML element. Checkboxes, text areas and (text)
  177. * input fields are supported.
  178. *
  179. * @param {jQuery} $element - the target element
  180. * @param {string|number|Array} value
  181. */
  182. setElementValue: function($element, value) {
  183. // deal with check box
  184. if ($element.is('input[type=checkbox]')) {
  185. this._setCheckBox($element, value);
  186. return;
  187. }
  188. // special cases: deal with text area and multiselect
  189. if ($element.is('textarea') && $.isArray(value)) {
  190. value = value.join("\n");
  191. } else if($element.hasClass(this.multiSelectPluginClass)) {
  192. if(!_.isArray(value)) {
  193. value = value.split("\n");
  194. }
  195. }
  196. if ($element.is('span')) {
  197. $element.text(value);
  198. } else {
  199. $element.val(value);
  200. }
  201. },
  202. /**
  203. * replaces options on a multiselect element
  204. *
  205. * @param {jQuery} $element - the multiselect element
  206. * @param {Array} options
  207. */
  208. equipMultiSelect: function($element, options) {
  209. if($element.find('option').length === 0) {
  210. $element.empty();
  211. for (var i in options) {
  212. var name = options[i];
  213. $element.append($('<option>').val(name).text(name).attr('title', name));
  214. }
  215. }
  216. if(!$element.hasClass('ldapGroupList')) {
  217. $element.multiselect('refresh');
  218. this.enableElement($element);
  219. }
  220. },
  221. /**
  222. * enables the specified HTML element
  223. *
  224. * @param {jQuery} $element
  225. */
  226. enableElement: function($element) {
  227. var isMS = $element.is('select[multiple]');
  228. var hasOptions = isMS ? ($element.find('option').length > 0) : false;
  229. if($element.hasClass(this.multiSelectPluginClass) && hasOptions) {
  230. $element.multiselect("enable");
  231. } else if ($element.hasClass(this.bjQuiButtonClass)) {
  232. $element.button("enable");
  233. }
  234. else if(!isMS || (isMS && hasOptions)) {
  235. $element.prop('disabled', false);
  236. }
  237. },
  238. /**
  239. * disables the specified HTML element
  240. *
  241. * @param {jQuery} $element
  242. */
  243. disableElement: function($element) {
  244. if($element.hasClass(this.multiSelectPluginClass)) {
  245. $element.multiselect("disable");
  246. } else if ($element.hasClass(this.bjQuiButtonClass)) {
  247. $element.button("disable");
  248. } else {
  249. $element.prop('disabled', 'disabled');
  250. }
  251. },
  252. /**
  253. * attaches a spinner icon to the HTML element specified by ID
  254. *
  255. * @param {string} elementID
  256. */
  257. attachSpinner: function(elementID) {
  258. if($('#' + elementID + ' + .ldapSpinner').length == 0) {
  259. var spinner = this.spinner.clone();
  260. var $element = $('#' + elementID);
  261. $(spinner).insertAfter($element);
  262. // and special treatment for multiselects:
  263. if ($element.is('select[multiple]')) {
  264. $('#' + elementID + " + img + button").css('display', 'none');
  265. }
  266. }
  267. },
  268. /**
  269. * removes the spinner icon from the HTML element specified by ID
  270. *
  271. * @param {string} elementID
  272. */
  273. removeSpinner: function(elementID) {
  274. $('#' + elementID+' + .ldapSpinner').remove();
  275. // and special treatment for multiselects:
  276. $('#' + elementID + " + button").css('display', 'inline');
  277. },
  278. /**
  279. * whether the wizard works in experienced admin mode
  280. *
  281. * @returns {boolean}
  282. */
  283. isExperiencedMode: function() {
  284. return parseInt(this.configModel.configuration.ldap_experienced_admin, 10) === 1;
  285. },
  286. /**
  287. * sets up auto-save functionality to the managed items
  288. *
  289. * @private
  290. */
  291. _enableAutoSave: function() {
  292. var view = this;
  293. for(var id in this.managedItems) {
  294. if(_.isUndefined(this.managedItems[id].$element)
  295. || _.isUndefined(this.managedItems[id].setMethod)
  296. || (!_.isUndefined(this.managedItems[id].preventAutoSave)
  297. && this.managedItems[id].preventAutoSave === true)
  298. ) {
  299. continue;
  300. }
  301. var $element = this.managedItems[id].$element;
  302. if (!$element.is('select[multiple]')) {
  303. $element.change(function() {
  304. view._requestSave($(this));
  305. });
  306. }
  307. }
  308. },
  309. /**
  310. * set's up save-button behavior (essentially used for agent dn and pwd)
  311. *
  312. * @private
  313. */
  314. _enableSaveButton: function() {
  315. var view = this;
  316. // TODO: this is not nice, because it fires one request per change
  317. // in the scenario this happens twice, causes detectors to run
  318. // duplicated etc. To have this work properly, the wizard endpoint
  319. // must accept setting multiple changes. Instead of messing around
  320. // with old ajax/wizard.php use this opportunity and create a
  321. // Controller
  322. for(var id in this.managedItems) {
  323. if(_.isUndefined(this.managedItems[id].$element)
  324. || _.isUndefined(this.managedItems[id].$saveButton)
  325. ) {
  326. continue;
  327. }
  328. (function (item) {
  329. item.$saveButton.click(function(event) {
  330. event.preventDefault();
  331. view._requestSave(item.$element);
  332. item.$saveButton.removeClass('primary');
  333. });
  334. item.$element.change(function () {
  335. item.$saveButton.addClass('primary');
  336. });
  337. })(this.managedItems[id]);
  338. }
  339. },
  340. /**
  341. * initializes a multiSelect element
  342. *
  343. * @param {jQuery} $element
  344. * @param {string} caption
  345. * @private
  346. */
  347. _initMultiSelect: function($element, caption) {
  348. var view = this;
  349. $element.multiselect({
  350. header: false,
  351. selectedList: 9,
  352. noneSelectedText: caption,
  353. classes: this.multiSelectPluginClass,
  354. close: function() {
  355. view._requestSave($element);
  356. }
  357. });
  358. },
  359. /**
  360. * @typedef {object} viewSaveInfo
  361. * @property {Function} val
  362. * @property {Function} attr
  363. * @property {Function} is
  364. */
  365. /**
  366. * requests a save operation from the model for a given value
  367. * represented by a HTML element and its ID.
  368. *
  369. * @param {jQuery|viewSaveInfo} $element
  370. * @private
  371. */
  372. _requestSave: function($element) {
  373. var value = '';
  374. if($element.is('input[type=checkbox]')
  375. && !$element.is(':checked')) {
  376. value = 0;
  377. } else if ($element.is('select[multiple]')) {
  378. var entries = $element.multiselect("getChecked");
  379. for(var i = 0; i < entries.length; i++) {
  380. value = value + "\n" + entries[i].value;
  381. }
  382. value = $.trim(value);
  383. } else {
  384. value = $element.val();
  385. }
  386. this.configModel.set($element.attr('id'), value);
  387. },
  388. /**
  389. * updates a checkbox element according to the provided value
  390. *
  391. * @param {jQuery} $element
  392. * @param {string|number} value
  393. * @private
  394. */
  395. _setCheckBox: function($element, value) {
  396. if(parseInt(value, 10) === 1) {
  397. $element.prop('checked', 'checked');
  398. } else {
  399. $element.removeAttr('checked');
  400. }
  401. },
  402. /**
  403. * this is called when the filter mode is switched to assisted. The
  404. * concrete tab view should implement this, to load LDAP features
  405. * (e.g. object classes, groups, attributes…), if necessary.
  406. */
  407. considerFeatureRequests: function() {},
  408. /**
  409. * this is called when the filter mode is switched to Assisted. The
  410. * concrete tab view should request the compilation of the respective
  411. * filter.
  412. */
  413. requestCompileFilter: function() {
  414. this.configModel.requestWizard(this.filterName);
  415. },
  416. /**
  417. * sets the filter mode initially and resets the "isToggling" marker.
  418. * This method is called after a save operation against the mode key.
  419. *
  420. * @param {any} mode -
  421. */
  422. setFilterModeOnce: function(mode) {
  423. this.isToggling = false;
  424. if(!this.filterModeInitialized) {
  425. this.filterModeInitialized = true;
  426. this.setFilterMode(mode);
  427. }
  428. },
  429. /**
  430. * sets the filter mode according to the provided configuration value
  431. *
  432. * @param {string} mode
  433. */
  434. setFilterMode: function(mode) {
  435. if(parseInt(mode, 10) === this.configModel.FILTER_MODE_ASSISTED) {
  436. this.parsedFilterMode = this.configModel.FILTER_MODE_ASSISTED;
  437. this.considerFeatureRequests();
  438. this._setFilterModeAssisted();
  439. if(this.isActive) {
  440. // filter compilation should happen only, if the mode was
  441. // switched manually, but not when initiating the view
  442. this.requestCompileFilter();
  443. }
  444. } else {
  445. this._setFilterModeRaw();
  446. this.parsedFilterMode = this.configModel.FILTER_MODE_RAW;
  447. }
  448. },
  449. /**
  450. * updates the UI so that it represents the assisted mode setting
  451. *
  452. * @private
  453. */
  454. _setFilterModeAssisted: function() {
  455. var view = this;
  456. this.$filterModeRawContainer.addClass('invisible');
  457. var filter = this.$filterModeRawContainer.find('.ldapFilterInputElement').val();
  458. this.$filterModeRawContainer.siblings('.ldapReadOnlyFilterContainer').find('.ldapFilterReadOnlyElement').text(filter);
  459. this.$filterModeRawContainer.siblings('.ldapReadOnlyFilterContainer').removeClass('hidden');
  460. $.each(this.filterModeDisableableElements, function(i, $element) {
  461. view.enableElement($element);
  462. });
  463. if(!_.isUndefined(this.filterModeStateElement)) {
  464. if (this.filterModeStateElement.status === 'enabled') {
  465. this.enableElement(this.filterModeStateElement.$element);
  466. } else {
  467. this.filterModeStateElement.status = 'disabled';
  468. }
  469. }
  470. },
  471. /**
  472. * updates the UI so that it represents the raw mode setting
  473. *
  474. * @private
  475. */
  476. _setFilterModeRaw: function() {
  477. var view = this;
  478. this.$filterModeRawContainer.removeClass('invisible');
  479. this.$filterModeRawContainer.siblings('.ldapReadOnlyFilterContainer').addClass('hidden');
  480. $.each(this.filterModeDisableableElements, function (i, $element) {
  481. view.disableElement($element);
  482. });
  483. if(!_.isUndefined(this.filterModeStateElement)) {
  484. if(this.filterModeStateElement.$element.multiselect().attr('disabled') === 'disabled') {
  485. this.filterModeStateElement.status = 'disabled';
  486. } else {
  487. this.filterModeStateElement.status = 'enabled';
  488. }
  489. }
  490. if(!_.isUndefined(this.filterModeStateElement)) {
  491. this.disableElement(this.filterModeStateElement.$element);
  492. }
  493. },
  494. /**
  495. * @callback toggleConfirmCallback
  496. * @param {boolean} isConfirmed
  497. */
  498. /**
  499. * shows a confirmation dialogue before switching from raw to assisted
  500. * mode if experienced mode is enabled.
  501. *
  502. * @param {toggleConfirmCallback} toggleFnc
  503. * @private
  504. */
  505. _toggleRawFilterModeConfirmation: function(toggleFnc) {
  506. if( !this.isExperiencedMode()
  507. || this.parsedFilterMode === this.configModel.FILTER_MODE_ASSISTED
  508. ) {
  509. toggleFnc(true);
  510. } else {
  511. OC.dialogs.confirm(
  512. 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?'),
  513. t('user_ldap', 'Mode switch'),
  514. toggleFnc
  515. );
  516. }
  517. },
  518. /**
  519. * toggles the visibility of a raw filter container and so also the
  520. * state of the multi-select controls. The model is requested to save
  521. * the state.
  522. */
  523. _toggleRawFilterMode: function() {
  524. var view = this;
  525. this._toggleRawFilterModeConfirmation(function(isConfirmed) {
  526. if(!isConfirmed) {
  527. return;
  528. }
  529. /** var {number} */
  530. var mode;
  531. if (view.parsedFilterMode === view.configModel.FILTER_MODE_ASSISTED) {
  532. mode = view.configModel.FILTER_MODE_RAW;
  533. } else {
  534. mode = view.configModel.FILTER_MODE_ASSISTED;
  535. }
  536. view.setFilterMode(mode);
  537. /** @var {viewSaveInfo} */
  538. var saveInfo = {
  539. val: function () {
  540. return mode;
  541. },
  542. attr: function () {
  543. return view.filterModeKey;
  544. },
  545. is: function () {
  546. return false;
  547. }
  548. };
  549. view._requestSave(saveInfo);
  550. });
  551. },
  552. /**
  553. * @typedef {object} filterModeStateElementObj
  554. * @property {string} status - either "enabled" or "disabled"
  555. * @property {jQuery} $element
  556. */
  557. /**
  558. * initializes a raw filter mode switcher
  559. *
  560. * @param {jQuery} $switcher - the element receiving the click
  561. * @param {jQuery} $filterModeRawContainer - contains the raw filter
  562. * input elements
  563. * @param {jQuery[]} filterModeDisableableElements - an array of elements
  564. * not belonging to the raw filter part that shall be en/disabled.
  565. * @param {string} filterModeKey - the setting key that save the state
  566. * of the mode
  567. * @param {filterModeStateElementObj} [filterModeStateElement] - one element
  568. * which status (enabled or not) is tracked by a setting
  569. * @private
  570. */
  571. _initFilterModeSwitcher: function(
  572. $switcher,
  573. $filterModeRawContainer,
  574. filterModeDisableableElements,
  575. filterModeKey,
  576. filterModeStateElement
  577. ) {
  578. this.$filterModeRawContainer = $filterModeRawContainer;
  579. this.filterModeDisableableElements = filterModeDisableableElements;
  580. this.filterModeStateElement = filterModeStateElement;
  581. this.filterModeKey = filterModeKey;
  582. var view = this;
  583. $switcher.click(function() {
  584. if(view.isToggling) {
  585. return;
  586. }
  587. view.isToggling = true;
  588. view._toggleRawFilterMode();
  589. });
  590. },
  591. });
  592. OCA.LDAP.Wizard.WizardTabGeneric = WizardTabGeneric;
  593. })();