1
0

settings.js 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524
  1. /**
  2. * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
  3. * SPDX-FileCopyrightText: 2012-2016 ownCloud, Inc.
  4. * SPDX-License-Identifier: AGPL-3.0-or-later
  5. */
  6. (function(){
  7. /**
  8. * Returns the selection of applicable users in the given configuration row
  9. *
  10. * @param $row configuration row
  11. * @return array array of user names
  12. */
  13. function getSelection($row) {
  14. var values = $row.find('.applicableUsers').select2('val');
  15. if (!values || values.length === 0) {
  16. values = [];
  17. }
  18. return values;
  19. }
  20. function getSelectedApplicable($row) {
  21. var users = [];
  22. var groups = [];
  23. var multiselect = getSelection($row);
  24. $.each(multiselect, function(index, value) {
  25. // FIXME: don't rely on string parts to detect groups...
  26. var pos = (value.indexOf)?value.indexOf('(group)'): -1;
  27. if (pos !== -1) {
  28. groups.push(value.substr(0, pos));
  29. } else {
  30. users.push(value);
  31. }
  32. });
  33. // FIXME: this should be done in the multiselect change event instead
  34. $row.find('.applicable')
  35. .data('applicable-groups', groups)
  36. .data('applicable-users', users);
  37. return { users, groups };
  38. }
  39. function highlightBorder($element, highlight) {
  40. $element.toggleClass('warning-input', highlight);
  41. return highlight;
  42. }
  43. function isInputValid($input) {
  44. var optional = $input.hasClass('optional');
  45. switch ($input.attr('type')) {
  46. case 'text':
  47. case 'password':
  48. if ($input.val() === '' && !optional) {
  49. return false;
  50. }
  51. break;
  52. }
  53. return true;
  54. }
  55. function highlightInput($input) {
  56. switch ($input.attr('type')) {
  57. case 'text':
  58. case 'password':
  59. return highlightBorder($input, !isInputValid($input));
  60. }
  61. }
  62. /**
  63. * Initialize select2 plugin on the given elements
  64. *
  65. * @param {Array<Object>} array of jQuery elements
  66. * @param {number} userListLimit page size for result list
  67. */
  68. function initApplicableUsersMultiselect($elements, userListLimit) {
  69. var escapeHTML = function (text) {
  70. return text.toString()
  71. .split('&').join('&amp;')
  72. .split('<').join('&lt;')
  73. .split('>').join('&gt;')
  74. .split('"').join('&quot;')
  75. .split('\'').join('&#039;');
  76. };
  77. if (!$elements.length) {
  78. return;
  79. }
  80. return $elements.select2({
  81. placeholder: t('files_external', 'Type to select account or group.'),
  82. allowClear: true,
  83. multiple: true,
  84. toggleSelect: true,
  85. dropdownCssClass: 'files-external-select2',
  86. //minimumInputLength: 1,
  87. ajax: {
  88. url: OC.generateUrl('apps/files_external/applicable'),
  89. dataType: 'json',
  90. quietMillis: 100,
  91. data: function (term, page) { // page is the one-based page number tracked by Select2
  92. return {
  93. pattern: term, //search term
  94. limit: userListLimit, // page size
  95. offset: userListLimit*(page-1) // page number starts with 0
  96. };
  97. },
  98. results: function (data) {
  99. if (data.status === 'success') {
  100. var results = [];
  101. var userCount = 0; // users is an object
  102. // add groups
  103. $.each(data.groups, function(gid, group) {
  104. results.push({name:gid+'(group)', displayname:group, type:'group' });
  105. });
  106. // add users
  107. $.each(data.users, function(id, user) {
  108. userCount++;
  109. results.push({name:id, displayname:user, type:'user' });
  110. });
  111. var more = (userCount >= userListLimit) || (data.groups.length >= userListLimit);
  112. return {results: results, more: more};
  113. } else {
  114. //FIXME add error handling
  115. }
  116. }
  117. },
  118. initSelection: function(element, callback) {
  119. var users = {};
  120. users['users'] = [];
  121. var toSplit = element.val().split(",");
  122. for (var i = 0; i < toSplit.length; i++) {
  123. users['users'].push(toSplit[i]);
  124. }
  125. $.ajax(OC.generateUrl('displaynames'), {
  126. type: 'POST',
  127. contentType: 'application/json',
  128. data: JSON.stringify(users),
  129. dataType: 'json'
  130. }).done(function(data) {
  131. var results = [];
  132. if (data.status === 'success') {
  133. $.each(data.users, function(user, displayname) {
  134. if (displayname !== false) {
  135. results.push({name:user, displayname:displayname, type:'user'});
  136. }
  137. });
  138. callback(results);
  139. } else {
  140. //FIXME add error handling
  141. }
  142. });
  143. },
  144. id: function(element) {
  145. return element.name;
  146. },
  147. formatResult: function (element) {
  148. var $result = $('<span><div class="avatardiv"></div><span>'+escapeHTML(element.displayname)+'</span></span>');
  149. var $div = $result.find('.avatardiv')
  150. .attr('data-type', element.type)
  151. .attr('data-name', element.name)
  152. .attr('data-displayname', element.displayname);
  153. if (element.type === 'group') {
  154. var url = OC.imagePath('core','actions/group');
  155. $div.html('<img width="32" height="32" src="'+url+'">');
  156. }
  157. return $result.get(0).outerHTML;
  158. },
  159. formatSelection: function (element) {
  160. if (element.type === 'group') {
  161. return '<span title="'+escapeHTML(element.name)+'" class="group">'+escapeHTML(element.displayname+' '+t('files_external', '(Group)'))+'</span>';
  162. } else {
  163. return '<span title="'+escapeHTML(element.name)+'" class="user">'+escapeHTML(element.displayname)+'</span>';
  164. }
  165. },
  166. escapeMarkup: function (m) { return m; } // we escape the markup in formatResult and formatSelection
  167. }).on('select2-loaded', function() {
  168. $.each($('.avatardiv'), function(i, div) {
  169. var $div = $(div);
  170. if ($div.data('type') === 'user') {
  171. $div.avatar($div.data('name'),32);
  172. }
  173. });
  174. }).on('change', function(event) {
  175. highlightBorder($(event.target).closest('.applicableUsersContainer').find('.select2-choices'), !event.val.length);
  176. });
  177. }
  178. /**
  179. * @class OCA.Files_External.Settings.StorageConfig
  180. *
  181. * @classdesc External storage config
  182. */
  183. var StorageConfig = function(id) {
  184. this.id = id;
  185. this.backendOptions = {};
  186. };
  187. // Keep this in sync with \OCA\Files_External\MountConfig::STATUS_*
  188. StorageConfig.Status = {
  189. IN_PROGRESS: -1,
  190. SUCCESS: 0,
  191. ERROR: 1,
  192. INDETERMINATE: 2
  193. };
  194. StorageConfig.Visibility = {
  195. NONE: 0,
  196. PERSONAL: 1,
  197. ADMIN: 2,
  198. DEFAULT: 3
  199. };
  200. /**
  201. * @memberof OCA.Files_External.Settings
  202. */
  203. StorageConfig.prototype = {
  204. _url: null,
  205. /**
  206. * Storage id
  207. *
  208. * @type int
  209. */
  210. id: null,
  211. /**
  212. * Mount point
  213. *
  214. * @type string
  215. */
  216. mountPoint: '',
  217. /**
  218. * Backend
  219. *
  220. * @type string
  221. */
  222. backend: null,
  223. /**
  224. * Authentication mechanism
  225. *
  226. * @type string
  227. */
  228. authMechanism: null,
  229. /**
  230. * Backend-specific configuration
  231. *
  232. * @type Object.<string,object>
  233. */
  234. backendOptions: null,
  235. /**
  236. * Mount-specific options
  237. *
  238. * @type Object.<string,object>
  239. */
  240. mountOptions: null,
  241. /**
  242. * Creates or saves the storage.
  243. *
  244. * @param {Function} [options.success] success callback, receives result as argument
  245. * @param {Function} [options.error] error callback
  246. */
  247. save: function(options) {
  248. var self = this;
  249. var url = OC.generateUrl(this._url);
  250. var method = 'POST';
  251. if (_.isNumber(this.id)) {
  252. method = 'PUT';
  253. url = OC.generateUrl(this._url + '/{id}', {id: this.id});
  254. }
  255. $.ajax({
  256. type: method,
  257. url: url,
  258. contentType: 'application/json',
  259. data: JSON.stringify(this.getData()),
  260. success: function(result) {
  261. self.id = result.id;
  262. if (_.isFunction(options.success)) {
  263. options.success(result);
  264. }
  265. },
  266. error: options.error
  267. });
  268. },
  269. /**
  270. * Returns the data from this object
  271. *
  272. * @return {Array} JSON array of the data
  273. */
  274. getData: function() {
  275. var data = {
  276. mountPoint: this.mountPoint,
  277. backend: this.backend,
  278. authMechanism: this.authMechanism,
  279. backendOptions: this.backendOptions,
  280. testOnly: true
  281. };
  282. if (this.id) {
  283. data.id = this.id;
  284. }
  285. if (this.mountOptions) {
  286. data.mountOptions = this.mountOptions;
  287. }
  288. return data;
  289. },
  290. /**
  291. * Recheck the storage
  292. *
  293. * @param {Function} [options.success] success callback, receives result as argument
  294. * @param {Function} [options.error] error callback
  295. */
  296. recheck: function(options) {
  297. if (!_.isNumber(this.id)) {
  298. if (_.isFunction(options.error)) {
  299. options.error();
  300. }
  301. return;
  302. }
  303. $.ajax({
  304. type: 'GET',
  305. url: OC.generateUrl(this._url + '/{id}', {id: this.id}),
  306. data: {'testOnly': true},
  307. success: options.success,
  308. error: options.error
  309. });
  310. },
  311. /**
  312. * Deletes the storage
  313. *
  314. * @param {Function} [options.success] success callback
  315. * @param {Function} [options.error] error callback
  316. */
  317. destroy: function(options) {
  318. if (!_.isNumber(this.id)) {
  319. // the storage hasn't even been created => success
  320. if (_.isFunction(options.success)) {
  321. options.success();
  322. }
  323. return;
  324. }
  325. $.ajax({
  326. type: 'DELETE',
  327. url: OC.generateUrl(this._url + '/{id}', {id: this.id}),
  328. success: options.success,
  329. error: options.error
  330. });
  331. },
  332. /**
  333. * Validate this model
  334. *
  335. * @return {boolean} false if errors exist, true otherwise
  336. */
  337. validate: function() {
  338. if (this.mountPoint === '') {
  339. return false;
  340. }
  341. if (!this.backend) {
  342. return false;
  343. }
  344. if (this.errors) {
  345. return false;
  346. }
  347. return true;
  348. }
  349. };
  350. /**
  351. * @class OCA.Files_External.Settings.GlobalStorageConfig
  352. * @augments OCA.Files_External.Settings.StorageConfig
  353. *
  354. * @classdesc Global external storage config
  355. */
  356. var GlobalStorageConfig = function(id) {
  357. this.id = id;
  358. this.applicableUsers = [];
  359. this.applicableGroups = [];
  360. };
  361. /**
  362. * @memberOf OCA.Files_External.Settings
  363. */
  364. GlobalStorageConfig.prototype = _.extend({}, StorageConfig.prototype,
  365. /** @lends OCA.Files_External.Settings.GlobalStorageConfig.prototype */ {
  366. _url: 'apps/files_external/globalstorages',
  367. /**
  368. * Applicable users
  369. *
  370. * @type Array.<string>
  371. */
  372. applicableUsers: null,
  373. /**
  374. * Applicable groups
  375. *
  376. * @type Array.<string>
  377. */
  378. applicableGroups: null,
  379. /**
  380. * Storage priority
  381. *
  382. * @type int
  383. */
  384. priority: null,
  385. /**
  386. * Returns the data from this object
  387. *
  388. * @return {Array} JSON array of the data
  389. */
  390. getData: function() {
  391. var data = StorageConfig.prototype.getData.apply(this, arguments);
  392. return _.extend(data, {
  393. applicableUsers: this.applicableUsers,
  394. applicableGroups: this.applicableGroups,
  395. priority: this.priority,
  396. });
  397. }
  398. });
  399. /**
  400. * @class OCA.Files_External.Settings.UserStorageConfig
  401. * @augments OCA.Files_External.Settings.StorageConfig
  402. *
  403. * @classdesc User external storage config
  404. */
  405. var UserStorageConfig = function(id) {
  406. this.id = id;
  407. };
  408. UserStorageConfig.prototype = _.extend({}, StorageConfig.prototype,
  409. /** @lends OCA.Files_External.Settings.UserStorageConfig.prototype */ {
  410. _url: 'apps/files_external/userstorages'
  411. });
  412. /**
  413. * @class OCA.Files_External.Settings.UserGlobalStorageConfig
  414. * @augments OCA.Files_External.Settings.StorageConfig
  415. *
  416. * @classdesc User external storage config
  417. */
  418. var UserGlobalStorageConfig = function (id) {
  419. this.id = id;
  420. };
  421. UserGlobalStorageConfig.prototype = _.extend({}, StorageConfig.prototype,
  422. /** @lends OCA.Files_External.Settings.UserStorageConfig.prototype */ {
  423. _url: 'apps/files_external/userglobalstorages'
  424. });
  425. /**
  426. * @class OCA.Files_External.Settings.MountOptionsDropdown
  427. *
  428. * @classdesc Dropdown for mount options
  429. *
  430. * @param {Object} $container container DOM object
  431. */
  432. var MountOptionsDropdown = function() {
  433. };
  434. /**
  435. * @memberof OCA.Files_External.Settings
  436. */
  437. MountOptionsDropdown.prototype = {
  438. /**
  439. * Dropdown element
  440. *
  441. * @var Object
  442. */
  443. $el: null,
  444. /**
  445. * Show dropdown
  446. *
  447. * @param {Object} $container container
  448. * @param {Object} mountOptions mount options
  449. * @param {Array} visibleOptions enabled mount options
  450. */
  451. show: function($container, mountOptions, visibleOptions) {
  452. if (MountOptionsDropdown._last) {
  453. MountOptionsDropdown._last.hide();
  454. }
  455. var $el = $(OCA.Files_External.Templates.mountOptionsDropDown({
  456. mountOptionsEncodingLabel: t('files_external', 'Compatibility with Mac NFD encoding (slow)'),
  457. mountOptionsEncryptLabel: t('files_external', 'Enable encryption'),
  458. mountOptionsPreviewsLabel: t('files_external', 'Enable previews'),
  459. mountOptionsSharingLabel: t('files_external', 'Enable sharing'),
  460. mountOptionsFilesystemCheckLabel: t('files_external', 'Check for changes'),
  461. mountOptionsFilesystemCheckOnce: t('files_external', 'Never'),
  462. mountOptionsFilesystemCheckDA: t('files_external', 'Once every direct access'),
  463. mountOptionsReadOnlyLabel: t('files_external', 'Read only'),
  464. deleteLabel: t('files_external', 'Disconnect')
  465. }));
  466. this.$el = $el;
  467. var storage = $container[0].parentNode.className;
  468. this.setOptions(mountOptions, visibleOptions, storage);
  469. this.$el.appendTo($container);
  470. MountOptionsDropdown._last = this;
  471. this.$el.trigger('show');
  472. },
  473. hide: function() {
  474. if (this.$el) {
  475. this.$el.trigger('hide');
  476. this.$el.remove();
  477. this.$el = null;
  478. MountOptionsDropdown._last = null;
  479. }
  480. },
  481. /**
  482. * Returns the mount options from the dropdown controls
  483. *
  484. * @return {Object} options mount options
  485. */
  486. getOptions: function() {
  487. var options = {};
  488. this.$el.find('input, select').each(function() {
  489. var $this = $(this);
  490. var key = $this.attr('name');
  491. var value = null;
  492. if ($this.attr('type') === 'checkbox') {
  493. value = $this.prop('checked');
  494. } else {
  495. value = $this.val();
  496. }
  497. if ($this.attr('data-type') === 'int') {
  498. value = parseInt(value, 10);
  499. }
  500. options[key] = value;
  501. });
  502. return options;
  503. },
  504. /**
  505. * Sets the mount options to the dropdown controls
  506. *
  507. * @param {Object} options mount options
  508. * @param {Array} visibleOptions enabled mount options
  509. */
  510. setOptions: function(options, visibleOptions, storage) {
  511. if (storage === 'owncloud') {
  512. var ind = visibleOptions.indexOf('encrypt');
  513. if (ind > 0) {
  514. visibleOptions.splice(ind, 1);
  515. }
  516. }
  517. var $el = this.$el;
  518. _.each(options, function(value, key) {
  519. var $optionEl = $el.find('input, select').filterAttr('name', key);
  520. if ($optionEl.attr('type') === 'checkbox') {
  521. if (_.isString(value)) {
  522. value = (value === 'true');
  523. }
  524. $optionEl.prop('checked', !!value);
  525. } else {
  526. $optionEl.val(value);
  527. }
  528. });
  529. $el.find('.optionRow').each(function(i, row){
  530. var $row = $(row);
  531. var optionId = $row.find('input, select').attr('name');
  532. if (visibleOptions.indexOf(optionId) === -1 && !$row.hasClass('persistent')) {
  533. $row.hide();
  534. } else {
  535. $row.show();
  536. }
  537. });
  538. }
  539. };
  540. /**
  541. * @class OCA.Files_External.Settings.MountConfigListView
  542. *
  543. * @classdesc Mount configuration list view
  544. *
  545. * @param {Object} $el DOM object containing the list
  546. * @param {Object} [options]
  547. * @param {number} [options.userListLimit] page size in applicable users dropdown
  548. */
  549. var MountConfigListView = function($el, options) {
  550. this.initialize($el, options);
  551. };
  552. MountConfigListView.ParameterFlags = {
  553. OPTIONAL: 1,
  554. USER_PROVIDED: 2
  555. };
  556. MountConfigListView.ParameterTypes = {
  557. TEXT: 0,
  558. BOOLEAN: 1,
  559. PASSWORD: 2,
  560. HIDDEN: 3
  561. };
  562. /**
  563. * @memberOf OCA.Files_External.Settings
  564. */
  565. MountConfigListView.prototype = _.extend({
  566. /**
  567. * jQuery element containing the config list
  568. *
  569. * @type Object
  570. */
  571. $el: null,
  572. /**
  573. * Storage config class
  574. *
  575. * @type Class
  576. */
  577. _storageConfigClass: null,
  578. /**
  579. * Flag whether the list is about user storage configs (true)
  580. * or global storage configs (false)
  581. *
  582. * @type bool
  583. */
  584. _isPersonal: false,
  585. /**
  586. * Page size in applicable users dropdown
  587. *
  588. * @type int
  589. */
  590. _userListLimit: 30,
  591. /**
  592. * List of supported backends
  593. *
  594. * @type Object.<string,Object>
  595. */
  596. _allBackends: null,
  597. /**
  598. * List of all supported authentication mechanisms
  599. *
  600. * @type Object.<string,Object>
  601. */
  602. _allAuthMechanisms: null,
  603. _encryptionEnabled: false,
  604. /**
  605. * @param {Object} $el DOM object containing the list
  606. * @param {Object} [options]
  607. * @param {number} [options.userListLimit] page size in applicable users dropdown
  608. */
  609. initialize: function($el, options) {
  610. var self = this;
  611. this.$el = $el;
  612. this._isPersonal = ($el.data('admin') !== true);
  613. if (this._isPersonal) {
  614. this._storageConfigClass = OCA.Files_External.Settings.UserStorageConfig;
  615. } else {
  616. this._storageConfigClass = OCA.Files_External.Settings.GlobalStorageConfig;
  617. }
  618. if (options && !_.isUndefined(options.userListLimit)) {
  619. this._userListLimit = options.userListLimit;
  620. }
  621. this._encryptionEnabled = options.encryptionEnabled;
  622. this._canCreateLocal = options.canCreateLocal;
  623. // read the backend config that was carefully crammed
  624. // into the data-configurations attribute of the select
  625. this._allBackends = this.$el.find('.selectBackend').data('configurations');
  626. this._allAuthMechanisms = this.$el.find('#addMountPoint .authentication').data('mechanisms');
  627. this._initEvents();
  628. },
  629. /**
  630. * Custom JS event handlers
  631. * Trigger callback for all existing configurations
  632. */
  633. whenSelectBackend: function(callback) {
  634. this.$el.find('tbody tr:not(#addMountPoint):not(.externalStorageLoading)').each(function(i, tr) {
  635. var backend = $(tr).find('.backend').data('identifier');
  636. callback($(tr), backend);
  637. });
  638. this.on('selectBackend', callback);
  639. },
  640. whenSelectAuthMechanism: function(callback) {
  641. var self = this;
  642. this.$el.find('tbody tr:not(#addMountPoint):not(.externalStorageLoading)').each(function(i, tr) {
  643. var authMechanism = $(tr).find('.selectAuthMechanism').val();
  644. callback($(tr), authMechanism, self._allAuthMechanisms[authMechanism]['scheme']);
  645. });
  646. this.on('selectAuthMechanism', callback);
  647. },
  648. /**
  649. * Initialize DOM event handlers
  650. */
  651. _initEvents: function() {
  652. var self = this;
  653. var onChangeHandler = _.bind(this._onChange, this);
  654. //this.$el.on('input', 'td input', onChangeHandler);
  655. this.$el.on('keyup', 'td input', onChangeHandler);
  656. this.$el.on('paste', 'td input', onChangeHandler);
  657. this.$el.on('change', 'td input:checkbox', onChangeHandler);
  658. this.$el.on('change', '.applicable', onChangeHandler);
  659. this.$el.on('click', '.status>span', function() {
  660. self.recheckStorageConfig($(this).closest('tr'));
  661. });
  662. this.$el.on('click', 'td.mountOptionsToggle .icon-delete', function() {
  663. self.deleteStorageConfig($(this).closest('tr'));
  664. });
  665. this.$el.on('click', 'td.save>.icon-checkmark', function () {
  666. self.saveStorageConfig($(this).closest('tr'));
  667. });
  668. this.$el.on('click', 'td.mountOptionsToggle>.icon-more', function() {
  669. $(this).attr('aria-expanded', 'true');
  670. self._showMountOptionsDropdown($(this).closest('tr'));
  671. });
  672. this.$el.on('change', '.selectBackend', _.bind(this._onSelectBackend, this));
  673. this.$el.on('change', '.selectAuthMechanism', _.bind(this._onSelectAuthMechanism, this));
  674. this.$el.on('change', '.applicableToAllUsers', _.bind(this._onChangeApplicableToAllUsers, this));
  675. },
  676. _onChange: function(event) {
  677. var self = this;
  678. var $target = $(event.target);
  679. if ($target.closest('.dropdown').length) {
  680. // ignore dropdown events
  681. return;
  682. }
  683. highlightInput($target);
  684. var $tr = $target.closest('tr');
  685. this.updateStatus($tr, null);
  686. },
  687. _onSelectBackend: function(event) {
  688. var $target = $(event.target);
  689. var $tr = $target.closest('tr');
  690. var storageConfig = new this._storageConfigClass();
  691. storageConfig.mountPoint = $tr.find('.mountPoint input').val();
  692. storageConfig.backend = $target.val();
  693. $tr.find('.mountPoint input').val('');
  694. $tr.find('.selectBackend').prop('selectedIndex', 0)
  695. var onCompletion = jQuery.Deferred();
  696. $tr = this.newStorage(storageConfig, onCompletion);
  697. $tr.find('.applicableToAllUsers').prop('checked', false).trigger('change');
  698. onCompletion.resolve();
  699. $tr.find('td.configuration').children().not('[type=hidden]').first().focus();
  700. this.saveStorageConfig($tr);
  701. },
  702. _onSelectAuthMechanism: function(event) {
  703. var $target = $(event.target);
  704. var $tr = $target.closest('tr');
  705. var authMechanism = $target.val();
  706. var onCompletion = jQuery.Deferred();
  707. this.configureAuthMechanism($tr, authMechanism, onCompletion);
  708. onCompletion.resolve();
  709. this.saveStorageConfig($tr);
  710. },
  711. _onChangeApplicableToAllUsers: function(event) {
  712. var $target = $(event.target);
  713. var $tr = $target.closest('tr');
  714. var checked = $target.is(':checked');
  715. $tr.find('.applicableUsersContainer').toggleClass('hidden', checked);
  716. if (!checked) {
  717. $tr.find('.applicableUsers').select2('val', '', true);
  718. }
  719. this.saveStorageConfig($tr);
  720. },
  721. /**
  722. * Configure the storage config with a new authentication mechanism
  723. *
  724. * @param {jQuery} $tr config row
  725. * @param {string} authMechanism
  726. * @param {jQuery.Deferred} onCompletion
  727. */
  728. configureAuthMechanism: function($tr, authMechanism, onCompletion) {
  729. var authMechanismConfiguration = this._allAuthMechanisms[authMechanism];
  730. var $td = $tr.find('td.configuration');
  731. $td.find('.auth-param').remove();
  732. $.each(authMechanismConfiguration['configuration'], _.partial(
  733. this.writeParameterInput, $td, _, _, ['auth-param']
  734. ).bind(this));
  735. this.trigger('selectAuthMechanism',
  736. $tr, authMechanism, authMechanismConfiguration['scheme'], onCompletion
  737. );
  738. },
  739. /**
  740. * Create a config row for a new storage
  741. *
  742. * @param {StorageConfig} storageConfig storage config to pull values from
  743. * @param {jQuery.Deferred} onCompletion
  744. * @param {boolean} deferAppend
  745. * @return {jQuery} created row
  746. */
  747. newStorage: function(storageConfig, onCompletion, deferAppend) {
  748. var mountPoint = storageConfig.mountPoint;
  749. var backend = this._allBackends[storageConfig.backend];
  750. if (!backend) {
  751. backend = {
  752. name: 'Unknown: ' + storageConfig.backend,
  753. invalid: true
  754. };
  755. }
  756. // FIXME: Replace with a proper Handlebar template
  757. var $template = this.$el.find('tr#addMountPoint');
  758. var $tr = $template.clone();
  759. if (!deferAppend) {
  760. $tr.insertBefore($template);
  761. }
  762. $tr.data('storageConfig', storageConfig);
  763. $tr.show();
  764. $tr.find('td.mountOptionsToggle, td.save, td.remove').removeClass('hidden');
  765. $tr.find('td').last().removeAttr('style');
  766. $tr.removeAttr('id');
  767. $tr.find('select#selectBackend');
  768. if (!deferAppend) {
  769. initApplicableUsersMultiselect($tr.find('.applicableUsers'), this._userListLimit);
  770. }
  771. if (storageConfig.id) {
  772. $tr.data('id', storageConfig.id);
  773. }
  774. $tr.find('.backend').text(backend.name);
  775. if (mountPoint === '') {
  776. mountPoint = this._suggestMountPoint(backend.name);
  777. }
  778. $tr.find('.mountPoint input').val(mountPoint);
  779. $tr.addClass(backend.identifier);
  780. $tr.find('.backend').data('identifier', backend.identifier);
  781. if (backend.invalid || (backend.identifier === 'local' && !this._canCreateLocal)) {
  782. $tr.find('[name=mountPoint]').prop('disabled', true);
  783. $tr.find('.applicable,.mountOptionsToggle').empty();
  784. $tr.find('.save').empty();
  785. if (backend.invalid) {
  786. this.updateStatus($tr, false, t('files_external', 'Unknown backend: {backendName}', {backendName: backend.name}));
  787. }
  788. return $tr;
  789. }
  790. var selectAuthMechanism = $('<select class="selectAuthMechanism"></select>');
  791. var neededVisibility = (this._isPersonal) ? StorageConfig.Visibility.PERSONAL : StorageConfig.Visibility.ADMIN;
  792. $.each(this._allAuthMechanisms, function(authIdentifier, authMechanism) {
  793. if (backend.authSchemes[authMechanism.scheme] && (authMechanism.visibility & neededVisibility)) {
  794. selectAuthMechanism.append(
  795. $('<option value="'+authMechanism.identifier+'" data-scheme="'+authMechanism.scheme+'">'+authMechanism.name+'</option>')
  796. );
  797. }
  798. });
  799. if (storageConfig.authMechanism) {
  800. selectAuthMechanism.val(storageConfig.authMechanism);
  801. } else {
  802. storageConfig.authMechanism = selectAuthMechanism.val();
  803. }
  804. $tr.find('td.authentication').append(selectAuthMechanism);
  805. var $td = $tr.find('td.configuration');
  806. $.each(backend.configuration, _.partial(this.writeParameterInput, $td).bind(this));
  807. this.trigger('selectBackend', $tr, backend.identifier, onCompletion);
  808. this.configureAuthMechanism($tr, storageConfig.authMechanism, onCompletion);
  809. if (storageConfig.backendOptions) {
  810. $td.find('input, select').each(function() {
  811. var input = $(this);
  812. var val = storageConfig.backendOptions[input.data('parameter')];
  813. if (val !== undefined) {
  814. if(input.is('input:checkbox')) {
  815. input.prop('checked', val);
  816. }
  817. input.val(storageConfig.backendOptions[input.data('parameter')]);
  818. highlightInput(input);
  819. }
  820. });
  821. }
  822. var applicable = [];
  823. if (storageConfig.applicableUsers) {
  824. applicable = applicable.concat(storageConfig.applicableUsers);
  825. }
  826. if (storageConfig.applicableGroups) {
  827. applicable = applicable.concat(
  828. _.map(storageConfig.applicableGroups, function(group) {
  829. return group+'(group)';
  830. })
  831. );
  832. }
  833. if (applicable.length) {
  834. $tr.find('.applicableUsers').val(applicable).trigger('change')
  835. $tr.find('.applicableUsersContainer').removeClass('hidden');
  836. } else {
  837. // applicable to all
  838. $tr.find('.applicableUsersContainer').addClass('hidden');
  839. }
  840. $tr.find('.applicableToAllUsers').prop('checked', !applicable.length);
  841. var priorityEl = $('<input type="hidden" class="priority" value="' + backend.priority + '" />');
  842. $tr.append(priorityEl);
  843. if (storageConfig.mountOptions) {
  844. $tr.find('input.mountOptions').val(JSON.stringify(storageConfig.mountOptions));
  845. } else {
  846. // FIXME default backend mount options
  847. $tr.find('input.mountOptions').val(JSON.stringify({
  848. 'encrypt': true,
  849. 'previews': true,
  850. 'enable_sharing': false,
  851. 'filesystem_check_changes': 1,
  852. 'encoding_compatibility': false,
  853. 'readonly': false,
  854. }));
  855. }
  856. return $tr;
  857. },
  858. /**
  859. * Load storages into config rows
  860. */
  861. loadStorages: function() {
  862. var self = this;
  863. var onLoaded1 = $.Deferred();
  864. var onLoaded2 = $.Deferred();
  865. this.$el.find('.externalStorageLoading').removeClass('hidden');
  866. $.when(onLoaded1, onLoaded2).always(() => {
  867. self.$el.find('.externalStorageLoading').addClass('hidden');
  868. })
  869. if (this._isPersonal) {
  870. // load userglobal storages
  871. $.ajax({
  872. type: 'GET',
  873. url: OC.generateUrl('apps/files_external/userglobalstorages'),
  874. data: {'testOnly' : true},
  875. contentType: 'application/json',
  876. success: function(result) {
  877. result = Object.values(result);
  878. var onCompletion = jQuery.Deferred();
  879. var $rows = $();
  880. result.forEach(function(storageParams) {
  881. var storageConfig;
  882. var isUserGlobal = storageParams.type === 'system' && self._isPersonal;
  883. storageParams.mountPoint = storageParams.mountPoint.substr(1); // trim leading slash
  884. if (isUserGlobal) {
  885. storageConfig = new UserGlobalStorageConfig();
  886. } else {
  887. storageConfig = new self._storageConfigClass();
  888. }
  889. _.extend(storageConfig, storageParams);
  890. var $tr = self.newStorage(storageConfig, onCompletion,true);
  891. // userglobal storages must be at the top of the list
  892. $tr.detach();
  893. self.$el.prepend($tr);
  894. var $authentication = $tr.find('.authentication');
  895. $authentication.text($authentication.find('select option:selected').text());
  896. // disable any other inputs
  897. $tr.find('.mountOptionsToggle, .remove').empty();
  898. $tr.find('input:not(.user_provided), select:not(.user_provided)').attr('disabled', 'disabled');
  899. if (isUserGlobal) {
  900. $tr.find('.configuration').find(':not(.user_provided)').remove();
  901. } else {
  902. // userglobal storages do not expose configuration data
  903. $tr.find('.configuration').text(t('files_external', 'Admin defined'));
  904. }
  905. // don't recheck config automatically when there are a large number of storages
  906. if (result.length < 20) {
  907. self.recheckStorageConfig($tr);
  908. } else {
  909. self.updateStatus($tr, StorageConfig.Status.INDETERMINATE, t('files_external', 'Automatic status checking is disabled due to the large number of configured storages, click to check status'));
  910. }
  911. $rows = $rows.add($tr);
  912. });
  913. initApplicableUsersMultiselect(self.$el.find('.applicableUsers'), this._userListLimit);
  914. self.$el.find('tr#addMountPoint').before($rows);
  915. var mainForm = $('#files_external');
  916. if (result.length === 0 && mainForm.attr('data-can-create') === 'false') {
  917. mainForm.hide();
  918. $('a[href="#external-storage"]').parent().hide();
  919. $('.emptycontent').show();
  920. }
  921. onCompletion.resolve();
  922. onLoaded1.resolve();
  923. }
  924. });
  925. } else {
  926. onLoaded1.resolve();
  927. }
  928. var url = this._storageConfigClass.prototype._url;
  929. $.ajax({
  930. type: 'GET',
  931. url: OC.generateUrl(url),
  932. contentType: 'application/json',
  933. success: function(result) {
  934. result = Object.values(result);
  935. var onCompletion = jQuery.Deferred();
  936. var $rows = $();
  937. result.forEach(function(storageParams) {
  938. storageParams.mountPoint = (storageParams.mountPoint === '/')? '/' : storageParams.mountPoint.substr(1); // trim leading slash
  939. var storageConfig = new self._storageConfigClass();
  940. _.extend(storageConfig, storageParams);
  941. var $tr = self.newStorage(storageConfig, onCompletion, true);
  942. // don't recheck config automatically when there are a large number of storages
  943. if (result.length < 20) {
  944. self.recheckStorageConfig($tr);
  945. } else {
  946. self.updateStatus($tr, StorageConfig.Status.INDETERMINATE, t('files_external', 'Automatic status checking is disabled due to the large number of configured storages, click to check status'));
  947. }
  948. $rows = $rows.add($tr);
  949. });
  950. initApplicableUsersMultiselect($rows.find('.applicableUsers'), this._userListLimit);
  951. self.$el.find('tr#addMountPoint').before($rows);
  952. onCompletion.resolve();
  953. onLoaded2.resolve();
  954. }
  955. });
  956. },
  957. /**
  958. * @param {jQuery} $td
  959. * @param {string} parameter
  960. * @param {string} placeholder
  961. * @param {Array} classes
  962. * @return {jQuery} newly created input
  963. */
  964. writeParameterInput: function($td, parameter, placeholder, classes) {
  965. var hasFlag = function(flag) {
  966. return (placeholder.flags & flag) === flag;
  967. };
  968. classes = $.isArray(classes) ? classes : [];
  969. classes.push('added');
  970. if (hasFlag(MountConfigListView.ParameterFlags.OPTIONAL)) {
  971. classes.push('optional');
  972. }
  973. if (hasFlag(MountConfigListView.ParameterFlags.USER_PROVIDED)) {
  974. if (this._isPersonal) {
  975. classes.push('user_provided');
  976. } else {
  977. return;
  978. }
  979. }
  980. var newElement;
  981. var trimmedPlaceholder = placeholder.value;
  982. if (placeholder.type === MountConfigListView.ParameterTypes.PASSWORD) {
  983. newElement = $('<input type="password" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" placeholder="'+ trimmedPlaceholder+'" />');
  984. } else if (placeholder.type === MountConfigListView.ParameterTypes.BOOLEAN) {
  985. var checkboxId = _.uniqueId('checkbox_');
  986. newElement = $('<div><label><input type="checkbox" id="'+checkboxId+'" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" />'+ trimmedPlaceholder+'</label></div>');
  987. } else if (placeholder.type === MountConfigListView.ParameterTypes.HIDDEN) {
  988. newElement = $('<input type="hidden" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" />');
  989. } else {
  990. newElement = $('<input type="text" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" placeholder="'+ trimmedPlaceholder+'" />');
  991. }
  992. if (placeholder.defaultValue) {
  993. if (placeholder.type === MountConfigListView.ParameterTypes.BOOLEAN) {
  994. newElement.find('input').prop('checked', placeholder.defaultValue);
  995. } else {
  996. newElement.val(placeholder.defaultValue);
  997. }
  998. }
  999. if (placeholder.tooltip) {
  1000. newElement.attr('title', placeholder.tooltip);
  1001. }
  1002. highlightInput(newElement);
  1003. $td.append(newElement);
  1004. return newElement;
  1005. },
  1006. /**
  1007. * Gets the storage model from the given row
  1008. *
  1009. * @param $tr row element
  1010. * @return {OCA.Files_External.StorageConfig} storage model instance
  1011. */
  1012. getStorageConfig: function($tr) {
  1013. var storageId = $tr.data('id');
  1014. if (!storageId) {
  1015. // new entry
  1016. storageId = null;
  1017. }
  1018. var storage = $tr.data('storageConfig');
  1019. if (!storage) {
  1020. storage = new this._storageConfigClass(storageId);
  1021. }
  1022. storage.errors = null;
  1023. storage.mountPoint = $tr.find('.mountPoint input').val();
  1024. storage.backend = $tr.find('.backend').data('identifier');
  1025. storage.authMechanism = $tr.find('.selectAuthMechanism').val();
  1026. var classOptions = {};
  1027. var configuration = $tr.find('.configuration input');
  1028. var missingOptions = [];
  1029. $.each(configuration, function(index, input) {
  1030. var $input = $(input);
  1031. var parameter = $input.data('parameter');
  1032. if ($input.attr('type') === 'button') {
  1033. return;
  1034. }
  1035. if (!isInputValid($input) && !$input.hasClass('optional')) {
  1036. missingOptions.push(parameter);
  1037. return;
  1038. }
  1039. if ($(input).is(':checkbox')) {
  1040. if ($(input).is(':checked')) {
  1041. classOptions[parameter] = true;
  1042. } else {
  1043. classOptions[parameter] = false;
  1044. }
  1045. } else {
  1046. classOptions[parameter] = $(input).val();
  1047. }
  1048. });
  1049. storage.backendOptions = classOptions;
  1050. if (missingOptions.length) {
  1051. storage.errors = {
  1052. backendOptions: missingOptions
  1053. };
  1054. }
  1055. // gather selected users and groups
  1056. if (!this._isPersonal) {
  1057. var multiselect = getSelectedApplicable($tr);
  1058. var users = multiselect.users || [];
  1059. var groups = multiselect.groups || [];
  1060. var isApplicableToAllUsers = $tr.find('.applicableToAllUsers').is(':checked');
  1061. if (isApplicableToAllUsers) {
  1062. storage.applicableUsers = [];
  1063. storage.applicableGroups = [];
  1064. } else {
  1065. storage.applicableUsers = users;
  1066. storage.applicableGroups = groups;
  1067. if (!storage.applicableUsers.length && !storage.applicableGroups.length) {
  1068. if (!storage.errors) {
  1069. storage.errors = {};
  1070. }
  1071. storage.errors['requiredApplicable'] = true;
  1072. }
  1073. }
  1074. storage.priority = parseInt($tr.find('input.priority').val() || '100', 10);
  1075. }
  1076. var mountOptions = $tr.find('input.mountOptions').val();
  1077. if (mountOptions) {
  1078. storage.mountOptions = JSON.parse(mountOptions);
  1079. }
  1080. return storage;
  1081. },
  1082. /**
  1083. * Deletes the storage from the given tr
  1084. *
  1085. * @param $tr storage row
  1086. * @param Function callback callback to call after save
  1087. */
  1088. deleteStorageConfig: function($tr) {
  1089. var self = this;
  1090. var configId = $tr.data('id');
  1091. if (!_.isNumber(configId)) {
  1092. // deleting unsaved storage
  1093. $tr.remove();
  1094. return;
  1095. }
  1096. var storage = new this._storageConfigClass(configId);
  1097. OC.dialogs.confirm(t('files_external', 'Are you sure you want to disconnect this external storage? It will make the storage unavailable in Nextcloud and will lead to a deletion of these files and folders on any sync client that is currently connected but will not delete any files and folders on the external storage itself.', {
  1098. storage: this.mountPoint
  1099. }), t('files_external', 'Delete storage?'), function(confirm) {
  1100. if (confirm) {
  1101. self.updateStatus($tr, StorageConfig.Status.IN_PROGRESS);
  1102. storage.destroy({
  1103. success: function () {
  1104. $tr.remove();
  1105. },
  1106. error: function (result) {
  1107. const statusMessage = (result && result.responseJSON) ? result.responseJSON.message : undefined;
  1108. self.updateStatus($tr, StorageConfig.Status.ERROR, statusMessage);
  1109. }
  1110. });
  1111. }
  1112. });
  1113. },
  1114. /**
  1115. * Saves the storage from the given tr
  1116. *
  1117. * @param $tr storage row
  1118. * @param Function callback callback to call after save
  1119. * @param concurrentTimer only update if the timer matches this
  1120. */
  1121. saveStorageConfig:function($tr, callback, concurrentTimer) {
  1122. var self = this;
  1123. var storage = this.getStorageConfig($tr);
  1124. if (!storage || !storage.validate()) {
  1125. return false;
  1126. }
  1127. this.updateStatus($tr, StorageConfig.Status.IN_PROGRESS);
  1128. storage.save({
  1129. success: function(result) {
  1130. if (concurrentTimer === undefined
  1131. || $tr.data('save-timer') === concurrentTimer
  1132. ) {
  1133. self.updateStatus($tr, result.status, result.statusMessage);
  1134. $tr.data('id', result.id);
  1135. if (_.isFunction(callback)) {
  1136. callback(storage);
  1137. }
  1138. }
  1139. },
  1140. error: function(result) {
  1141. if (concurrentTimer === undefined
  1142. || $tr.data('save-timer') === concurrentTimer
  1143. ) {
  1144. const statusMessage = (result && result.responseJSON) ? result.responseJSON.message : undefined;
  1145. self.updateStatus($tr, StorageConfig.Status.ERROR, statusMessage);
  1146. }
  1147. }
  1148. });
  1149. },
  1150. /**
  1151. * Recheck storage availability
  1152. *
  1153. * @param {jQuery} $tr storage row
  1154. * @return {boolean} success
  1155. */
  1156. recheckStorageConfig: function($tr) {
  1157. var self = this;
  1158. var storage = this.getStorageConfig($tr);
  1159. if (!storage.validate()) {
  1160. return false;
  1161. }
  1162. this.updateStatus($tr, StorageConfig.Status.IN_PROGRESS);
  1163. storage.recheck({
  1164. success: function(result) {
  1165. self.updateStatus($tr, result.status, result.statusMessage);
  1166. },
  1167. error: function(result) {
  1168. const statusMessage = (result && result.responseJSON) ? result.responseJSON.message : undefined;
  1169. self.updateStatus($tr, StorageConfig.Status.ERROR, statusMessage);
  1170. }
  1171. });
  1172. },
  1173. /**
  1174. * Update status display
  1175. *
  1176. * @param {jQuery} $tr
  1177. * @param {number} status
  1178. * @param {string} message
  1179. */
  1180. updateStatus: function($tr, status, message) {
  1181. var $statusSpan = $tr.find('.status span');
  1182. switch (status) {
  1183. case null:
  1184. // remove status
  1185. $statusSpan.hide();
  1186. break;
  1187. case StorageConfig.Status.IN_PROGRESS:
  1188. $statusSpan.attr('class', 'icon-loading-small');
  1189. break;
  1190. case StorageConfig.Status.SUCCESS:
  1191. $statusSpan.attr('class', 'success icon-checkmark-white');
  1192. break;
  1193. case StorageConfig.Status.INDETERMINATE:
  1194. $statusSpan.attr('class', 'indeterminate icon-info-white');
  1195. break;
  1196. default:
  1197. $statusSpan.attr('class', 'error icon-error-white');
  1198. }
  1199. if (status !== null) {
  1200. $statusSpan.show();
  1201. }
  1202. if (typeof message !== 'string') {
  1203. message = t('files_external', 'Click to recheck the configuration');
  1204. }
  1205. $statusSpan.attr('title', message);
  1206. },
  1207. /**
  1208. * Suggest mount point name that doesn't conflict with the existing names in the list
  1209. *
  1210. * @param {string} defaultMountPoint default name
  1211. */
  1212. _suggestMountPoint: function(defaultMountPoint) {
  1213. var $el = this.$el;
  1214. var pos = defaultMountPoint.indexOf('/');
  1215. if (pos !== -1) {
  1216. defaultMountPoint = defaultMountPoint.substring(0, pos);
  1217. }
  1218. defaultMountPoint = defaultMountPoint.replace(/\s+/g, '');
  1219. var i = 1;
  1220. var append = '';
  1221. var match = true;
  1222. while (match && i < 20) {
  1223. match = false;
  1224. $el.find('tbody td.mountPoint input').each(function(index, mountPoint) {
  1225. if ($(mountPoint).val() === defaultMountPoint+append) {
  1226. match = true;
  1227. return false;
  1228. }
  1229. });
  1230. if (match) {
  1231. append = i;
  1232. i++;
  1233. } else {
  1234. break;
  1235. }
  1236. }
  1237. return defaultMountPoint + append;
  1238. },
  1239. /**
  1240. * Toggles the mount options dropdown
  1241. *
  1242. * @param {Object} $tr configuration row
  1243. */
  1244. _showMountOptionsDropdown: function($tr) {
  1245. var self = this;
  1246. var storage = this.getStorageConfig($tr);
  1247. var $toggle = $tr.find('.mountOptionsToggle');
  1248. var dropDown = new MountOptionsDropdown();
  1249. var visibleOptions = [
  1250. 'previews',
  1251. 'filesystem_check_changes',
  1252. 'enable_sharing',
  1253. 'encoding_compatibility',
  1254. 'readonly',
  1255. 'delete'
  1256. ];
  1257. if (this._encryptionEnabled) {
  1258. visibleOptions.push('encrypt');
  1259. }
  1260. dropDown.show($toggle, storage.mountOptions || [], visibleOptions);
  1261. $('body').on('mouseup.mountOptionsDropdown', function(event) {
  1262. var $target = $(event.target);
  1263. if ($target.closest('.popovermenu').length) {
  1264. return;
  1265. }
  1266. dropDown.hide();
  1267. });
  1268. dropDown.$el.on('hide', function() {
  1269. var mountOptions = dropDown.getOptions();
  1270. $('body').off('mouseup.mountOptionsDropdown');
  1271. $tr.find('input.mountOptions').val(JSON.stringify(mountOptions));
  1272. $tr.find('td.mountOptionsToggle>.icon-more').attr('aria-expanded', 'false');
  1273. self.saveStorageConfig($tr);
  1274. });
  1275. }
  1276. }, OC.Backbone.Events);
  1277. window.addEventListener('DOMContentLoaded', function() {
  1278. var enabled = $('#files_external').attr('data-encryption-enabled');
  1279. var canCreateLocal = $('#files_external').attr('data-can-create-local');
  1280. var encryptionEnabled = (enabled ==='true')? true: false;
  1281. var mountConfigListView = new MountConfigListView($('#externalStorage'), {
  1282. encryptionEnabled: encryptionEnabled,
  1283. canCreateLocal: (canCreateLocal === 'true') ? true: false,
  1284. });
  1285. mountConfigListView.loadStorages();
  1286. // TODO: move this into its own View class
  1287. var $allowUserMounting = $('#allowUserMounting');
  1288. $allowUserMounting.bind('change', function() {
  1289. OC.msg.startSaving('#userMountingMsg');
  1290. if (this.checked) {
  1291. OCP.AppConfig.setValue('files_external', 'allow_user_mounting', 'yes');
  1292. $('input[name="allowUserMountingBackends\\[\\]"]').prop('checked', true);
  1293. $('#userMountingBackends').removeClass('hidden');
  1294. $('input[name="allowUserMountingBackends\\[\\]"]').eq(0).trigger('change');
  1295. } else {
  1296. OCP.AppConfig.setValue('files_external', 'allow_user_mounting', 'no');
  1297. $('#userMountingBackends').addClass('hidden');
  1298. }
  1299. OC.msg.finishedSaving('#userMountingMsg', {status: 'success', data: {message: t('files_external', 'Saved')}});
  1300. });
  1301. $('input[name="allowUserMountingBackends\\[\\]"]').bind('change', function() {
  1302. OC.msg.startSaving('#userMountingMsg');
  1303. var userMountingBackends = $('input[name="allowUserMountingBackends\\[\\]"]:checked').map(function(){
  1304. return $(this).val();
  1305. }).get();
  1306. var deprecatedBackends = $('input[name="allowUserMountingBackends\\[\\]"][data-deprecate-to]').map(function(){
  1307. if ($.inArray($(this).data('deprecate-to'), userMountingBackends) !== -1) {
  1308. return $(this).val();
  1309. }
  1310. return null;
  1311. }).get();
  1312. userMountingBackends = userMountingBackends.concat(deprecatedBackends);
  1313. OCP.AppConfig.setValue('files_external', 'user_mounting_backends', userMountingBackends.join());
  1314. OC.msg.finishedSaving('#userMountingMsg', {status: 'success', data: {message: t('files_external', 'Saved')}});
  1315. // disable allowUserMounting
  1316. if(userMountingBackends.length === 0) {
  1317. $allowUserMounting.prop('checked', false);
  1318. $allowUserMounting.trigger('change');
  1319. }
  1320. });
  1321. $('#global_credentials').on('submit', function() {
  1322. var $form = $(this);
  1323. var uid = $form.find('[name=uid]').val();
  1324. var user = $form.find('[name=username]').val();
  1325. var password = $form.find('[name=password]').val();
  1326. var $submit = $form.find('[type=submit]');
  1327. $submit.val(t('files_external', 'Saving …'));
  1328. $.ajax({
  1329. type: 'POST',
  1330. contentType: 'application/json',
  1331. data: JSON.stringify({
  1332. uid: uid,
  1333. user: user,
  1334. password: password
  1335. }),
  1336. url: OC.generateUrl('apps/files_external/globalcredentials'),
  1337. dataType: 'json',
  1338. success: function() {
  1339. $submit.val(t('files_external', 'Saved'));
  1340. setTimeout(function(){
  1341. $submit.val(t('files_external', 'Save'));
  1342. }, 2500);
  1343. }
  1344. });
  1345. return false;
  1346. });
  1347. // global instance
  1348. OCA.Files_External.Settings.mountConfig = mountConfigListView;
  1349. /**
  1350. * Legacy
  1351. *
  1352. * @namespace
  1353. * @deprecated use OCA.Files_External.Settings.mountConfig instead
  1354. */
  1355. OC.MountConfig = {
  1356. saveStorage: _.bind(mountConfigListView.saveStorageConfig, mountConfigListView)
  1357. };
  1358. });
  1359. // export
  1360. OCA.Files_External = OCA.Files_External || {};
  1361. /**
  1362. * @namespace
  1363. */
  1364. OCA.Files_External.Settings = OCA.Files_External.Settings || {};
  1365. OCA.Files_External.Settings.GlobalStorageConfig = GlobalStorageConfig;
  1366. OCA.Files_External.Settings.UserStorageConfig = UserStorageConfig;
  1367. OCA.Files_External.Settings.MountConfigListView = MountConfigListView;
  1368. })();