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.

settings.js 41KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506
  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. var onCompletion = jQuery.Deferred();
  695. $tr = this.newStorage(storageConfig, onCompletion);
  696. $tr.find('.applicableToAllUsers').prop('checked', false).trigger('change');
  697. onCompletion.resolve();
  698. $tr.find('td.configuration').children().not('[type=hidden]').first().focus();
  699. this.saveStorageConfig($tr);
  700. },
  701. _onSelectAuthMechanism: function(event) {
  702. var $target = $(event.target);
  703. var $tr = $target.closest('tr');
  704. var authMechanism = $target.val();
  705. var onCompletion = jQuery.Deferred();
  706. this.configureAuthMechanism($tr, authMechanism, onCompletion);
  707. onCompletion.resolve();
  708. this.saveStorageConfig($tr);
  709. },
  710. _onChangeApplicableToAllUsers: function(event) {
  711. var $target = $(event.target);
  712. var $tr = $target.closest('tr');
  713. var checked = $target.is(':checked');
  714. $tr.find('.applicableUsersContainer').toggleClass('hidden', checked);
  715. if (!checked) {
  716. $tr.find('.applicableUsers').select2('val', '', true);
  717. }
  718. this.saveStorageConfig($tr);
  719. },
  720. /**
  721. * Configure the storage config with a new authentication mechanism
  722. *
  723. * @param {jQuery} $tr config row
  724. * @param {string} authMechanism
  725. * @param {jQuery.Deferred} onCompletion
  726. */
  727. configureAuthMechanism: function($tr, authMechanism, onCompletion) {
  728. var authMechanismConfiguration = this._allAuthMechanisms[authMechanism];
  729. var $td = $tr.find('td.configuration');
  730. $td.find('.auth-param').remove();
  731. $.each(authMechanismConfiguration['configuration'], _.partial(
  732. this.writeParameterInput, $td, _, _, ['auth-param']
  733. ).bind(this));
  734. this.trigger('selectAuthMechanism',
  735. $tr, authMechanism, authMechanismConfiguration['scheme'], onCompletion
  736. );
  737. },
  738. /**
  739. * Create a config row for a new storage
  740. *
  741. * @param {StorageConfig} storageConfig storage config to pull values from
  742. * @param {jQuery.Deferred} onCompletion
  743. * @param {boolean} deferAppend
  744. * @return {jQuery} created row
  745. */
  746. newStorage: function(storageConfig, onCompletion, deferAppend) {
  747. var mountPoint = storageConfig.mountPoint;
  748. var backend = this._allBackends[storageConfig.backend];
  749. if (!backend) {
  750. backend = {
  751. name: 'Unknown: ' + storageConfig.backend,
  752. invalid: true
  753. };
  754. }
  755. // FIXME: Replace with a proper Handlebar template
  756. var $template = this.$el.find('tr#addMountPoint');
  757. var $tr = $template.clone();
  758. if (!deferAppend) {
  759. $tr.insertBefore($template);
  760. }
  761. $tr.data('storageConfig', storageConfig);
  762. $tr.show();
  763. $tr.find('td.mountOptionsToggle, td.save, td.remove').removeClass('hidden');
  764. $tr.find('td').last().removeAttr('style');
  765. $tr.removeAttr('id');
  766. $tr.find('select#selectBackend');
  767. if (!deferAppend) {
  768. initApplicableUsersMultiselect($tr.find('.applicableUsers'), this._userListLimit);
  769. }
  770. if (storageConfig.id) {
  771. $tr.data('id', storageConfig.id);
  772. }
  773. $tr.find('.backend').text(backend.name);
  774. if (mountPoint === '') {
  775. mountPoint = this._suggestMountPoint(backend.name);
  776. }
  777. $tr.find('.mountPoint input').val(mountPoint);
  778. $tr.addClass(backend.identifier);
  779. $tr.find('.backend').data('identifier', backend.identifier);
  780. if (backend.invalid || (backend.identifier === 'local' && !this._canCreateLocal)) {
  781. $tr.find('[name=mountPoint]').prop('disabled', true);
  782. $tr.find('.applicable,.mountOptionsToggle').empty();
  783. $tr.find('.save').empty();
  784. if (backend.invalid) {
  785. this.updateStatus($tr, false, 'Unknown backend: ' + backend.name);
  786. }
  787. return $tr;
  788. }
  789. var selectAuthMechanism = $('<select class="selectAuthMechanism"></select>');
  790. var neededVisibility = (this._isPersonal) ? StorageConfig.Visibility.PERSONAL : StorageConfig.Visibility.ADMIN;
  791. $.each(this._allAuthMechanisms, function(authIdentifier, authMechanism) {
  792. if (backend.authSchemes[authMechanism.scheme] && (authMechanism.visibility & neededVisibility)) {
  793. selectAuthMechanism.append(
  794. $('<option value="'+authMechanism.identifier+'" data-scheme="'+authMechanism.scheme+'">'+authMechanism.name+'</option>')
  795. );
  796. }
  797. });
  798. if (storageConfig.authMechanism) {
  799. selectAuthMechanism.val(storageConfig.authMechanism);
  800. } else {
  801. storageConfig.authMechanism = selectAuthMechanism.val();
  802. }
  803. $tr.find('td.authentication').append(selectAuthMechanism);
  804. var $td = $tr.find('td.configuration');
  805. $.each(backend.configuration, _.partial(this.writeParameterInput, $td).bind(this));
  806. this.trigger('selectBackend', $tr, backend.identifier, onCompletion);
  807. this.configureAuthMechanism($tr, storageConfig.authMechanism, onCompletion);
  808. if (storageConfig.backendOptions) {
  809. $td.find('input, select').each(function() {
  810. var input = $(this);
  811. var val = storageConfig.backendOptions[input.data('parameter')];
  812. if (val !== undefined) {
  813. if(input.is('input:checkbox')) {
  814. input.prop('checked', val);
  815. }
  816. input.val(storageConfig.backendOptions[input.data('parameter')]);
  817. highlightInput(input);
  818. }
  819. });
  820. }
  821. var applicable = [];
  822. if (storageConfig.applicableUsers) {
  823. applicable = applicable.concat(storageConfig.applicableUsers);
  824. }
  825. if (storageConfig.applicableGroups) {
  826. applicable = applicable.concat(
  827. _.map(storageConfig.applicableGroups, function(group) {
  828. return group+'(group)';
  829. })
  830. );
  831. }
  832. if (applicable.length) {
  833. $tr.find('.applicableUsers').val(applicable).trigger('change')
  834. $tr.find('.applicableUsersContainer').removeClass('hidden');
  835. } else {
  836. // applicable to all
  837. $tr.find('.applicableUsersContainer').addClass('hidden');
  838. }
  839. $tr.find('.applicableToAllUsers').prop('checked', !applicable.length);
  840. var priorityEl = $('<input type="hidden" class="priority" value="' + backend.priority + '" />');
  841. $tr.append(priorityEl);
  842. if (storageConfig.mountOptions) {
  843. $tr.find('input.mountOptions').val(JSON.stringify(storageConfig.mountOptions));
  844. } else {
  845. // FIXME default backend mount options
  846. $tr.find('input.mountOptions').val(JSON.stringify({
  847. 'encrypt': true,
  848. 'previews': true,
  849. 'enable_sharing': false,
  850. 'filesystem_check_changes': 1,
  851. 'encoding_compatibility': false,
  852. 'readonly': false,
  853. }));
  854. }
  855. return $tr;
  856. },
  857. /**
  858. * Load storages into config rows
  859. */
  860. loadStorages: function() {
  861. var self = this;
  862. var onLoaded1 = $.Deferred();
  863. var onLoaded2 = $.Deferred();
  864. this.$el.find('.externalStorageLoading').removeClass('hidden');
  865. $.when(onLoaded1, onLoaded2).always(() => {
  866. self.$el.find('.externalStorageLoading').addClass('hidden');
  867. })
  868. if (this._isPersonal) {
  869. // load userglobal storages
  870. $.ajax({
  871. type: 'GET',
  872. url: OC.generateUrl('apps/files_external/userglobalstorages'),
  873. data: {'testOnly' : true},
  874. contentType: 'application/json',
  875. success: function(result) {
  876. var onCompletion = jQuery.Deferred();
  877. var $rows = $();
  878. Object.values(result).forEach(function(storageParams) {
  879. var storageConfig;
  880. var isUserGlobal = storageParams.type === 'system' && self._isPersonal;
  881. storageParams.mountPoint = storageParams.mountPoint.substr(1); // trim leading slash
  882. if (isUserGlobal) {
  883. storageConfig = new UserGlobalStorageConfig();
  884. } else {
  885. storageConfig = new self._storageConfigClass();
  886. }
  887. _.extend(storageConfig, storageParams);
  888. var $tr = self.newStorage(storageConfig, onCompletion,true);
  889. // userglobal storages must be at the top of the list
  890. $tr.detach();
  891. self.$el.prepend($tr);
  892. var $authentication = $tr.find('.authentication');
  893. $authentication.text($authentication.find('select option:selected').text());
  894. // disable any other inputs
  895. $tr.find('.mountOptionsToggle, .remove').empty();
  896. $tr.find('input:not(.user_provided), select:not(.user_provided)').attr('disabled', 'disabled');
  897. if (isUserGlobal) {
  898. $tr.find('.configuration').find(':not(.user_provided)').remove();
  899. } else {
  900. // userglobal storages do not expose configuration data
  901. $tr.find('.configuration').text(t('files_external', 'Admin defined'));
  902. }
  903. $rows = $rows.add($tr);
  904. });
  905. initApplicableUsersMultiselect(self.$el.find('.applicableUsers'), this._userListLimit);
  906. self.$el.find('tr#addMountPoint').before($rows);
  907. var mainForm = $('#files_external');
  908. if (result.length === 0 && mainForm.attr('data-can-create') === 'false') {
  909. mainForm.hide();
  910. $('a[href="#external-storage"]').parent().hide();
  911. $('.emptycontent').show();
  912. }
  913. onCompletion.resolve();
  914. onLoaded1.resolve();
  915. }
  916. });
  917. } else {
  918. onLoaded1.resolve();
  919. }
  920. var url = this._storageConfigClass.prototype._url;
  921. $.ajax({
  922. type: 'GET',
  923. url: OC.generateUrl(url),
  924. contentType: 'application/json',
  925. success: function(result) {
  926. result = Object.values(result);
  927. var onCompletion = jQuery.Deferred();
  928. var $rows = $();
  929. result.forEach(function(storageParams) {
  930. storageParams.mountPoint = (storageParams.mountPoint === '/')? '/' : storageParams.mountPoint.substr(1); // trim leading slash
  931. var storageConfig = new self._storageConfigClass();
  932. _.extend(storageConfig, storageParams);
  933. var $tr = self.newStorage(storageConfig, onCompletion, true);
  934. // don't recheck config automatically when there are a large number of storages
  935. if (result.length < 20) {
  936. self.recheckStorageConfig($tr);
  937. } else {
  938. 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'));
  939. }
  940. $rows = $rows.add($tr);
  941. });
  942. initApplicableUsersMultiselect($rows.find('.applicableUsers'), this._userListLimit);
  943. self.$el.find('tr#addMountPoint').before($rows);
  944. onCompletion.resolve();
  945. onLoaded2.resolve();
  946. }
  947. });
  948. },
  949. /**
  950. * @param {jQuery} $td
  951. * @param {string} parameter
  952. * @param {string} placeholder
  953. * @param {Array} classes
  954. * @return {jQuery} newly created input
  955. */
  956. writeParameterInput: function($td, parameter, placeholder, classes) {
  957. var hasFlag = function(flag) {
  958. return (placeholder.flags & flag) === flag;
  959. };
  960. classes = $.isArray(classes) ? classes : [];
  961. classes.push('added');
  962. if (hasFlag(MountConfigListView.ParameterFlags.OPTIONAL)) {
  963. classes.push('optional');
  964. }
  965. if (hasFlag(MountConfigListView.ParameterFlags.USER_PROVIDED)) {
  966. if (this._isPersonal) {
  967. classes.push('user_provided');
  968. } else {
  969. return;
  970. }
  971. }
  972. var newElement;
  973. var trimmedPlaceholder = placeholder.value;
  974. if (placeholder.type === MountConfigListView.ParameterTypes.PASSWORD) {
  975. newElement = $('<input type="password" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" placeholder="'+ trimmedPlaceholder+'" />');
  976. } else if (placeholder.type === MountConfigListView.ParameterTypes.BOOLEAN) {
  977. var checkboxId = _.uniqueId('checkbox_');
  978. newElement = $('<div><label><input type="checkbox" id="'+checkboxId+'" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" />'+ trimmedPlaceholder+'</label></div>');
  979. } else if (placeholder.type === MountConfigListView.ParameterTypes.HIDDEN) {
  980. newElement = $('<input type="hidden" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" />');
  981. } else {
  982. newElement = $('<input type="text" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" placeholder="'+ trimmedPlaceholder+'" />');
  983. }
  984. if (placeholder.defaultValue) {
  985. if (placeholder.type === MountConfigListView.ParameterTypes.BOOLEAN) {
  986. newElement.find('input').prop('checked', placeholder.defaultValue);
  987. } else {
  988. newElement.val(placeholder.defaultValue);
  989. }
  990. }
  991. if (placeholder.tooltip) {
  992. newElement.attr('title', placeholder.tooltip);
  993. }
  994. highlightInput(newElement);
  995. $td.append(newElement);
  996. return newElement;
  997. },
  998. /**
  999. * Gets the storage model from the given row
  1000. *
  1001. * @param $tr row element
  1002. * @return {OCA.Files_External.StorageConfig} storage model instance
  1003. */
  1004. getStorageConfig: function($tr) {
  1005. var storageId = $tr.data('id');
  1006. if (!storageId) {
  1007. // new entry
  1008. storageId = null;
  1009. }
  1010. var storage = $tr.data('storageConfig');
  1011. if (!storage) {
  1012. storage = new this._storageConfigClass(storageId);
  1013. }
  1014. storage.errors = null;
  1015. storage.mountPoint = $tr.find('.mountPoint input').val();
  1016. storage.backend = $tr.find('.backend').data('identifier');
  1017. storage.authMechanism = $tr.find('.selectAuthMechanism').val();
  1018. var classOptions = {};
  1019. var configuration = $tr.find('.configuration input');
  1020. var missingOptions = [];
  1021. $.each(configuration, function(index, input) {
  1022. var $input = $(input);
  1023. var parameter = $input.data('parameter');
  1024. if ($input.attr('type') === 'button') {
  1025. return;
  1026. }
  1027. if (!isInputValid($input) && !$input.hasClass('optional')) {
  1028. missingOptions.push(parameter);
  1029. return;
  1030. }
  1031. if ($(input).is(':checkbox')) {
  1032. if ($(input).is(':checked')) {
  1033. classOptions[parameter] = true;
  1034. } else {
  1035. classOptions[parameter] = false;
  1036. }
  1037. } else {
  1038. classOptions[parameter] = $(input).val();
  1039. }
  1040. });
  1041. storage.backendOptions = classOptions;
  1042. if (missingOptions.length) {
  1043. storage.errors = {
  1044. backendOptions: missingOptions
  1045. };
  1046. }
  1047. // gather selected users and groups
  1048. if (!this._isPersonal) {
  1049. var multiselect = getSelectedApplicable($tr);
  1050. var users = multiselect.users || [];
  1051. var groups = multiselect.groups || [];
  1052. var isApplicableToAllUsers = $tr.find('.applicableToAllUsers').is(':checked');
  1053. if (isApplicableToAllUsers) {
  1054. storage.applicableUsers = [];
  1055. storage.applicableGroups = [];
  1056. } else {
  1057. storage.applicableUsers = users;
  1058. storage.applicableGroups = groups;
  1059. if (!storage.applicableUsers.length && !storage.applicableGroups.length) {
  1060. if (!storage.errors) {
  1061. storage.errors = {};
  1062. }
  1063. storage.errors['requiredApplicable'] = true;
  1064. }
  1065. }
  1066. storage.priority = parseInt($tr.find('input.priority').val() || '100', 10);
  1067. }
  1068. var mountOptions = $tr.find('input.mountOptions').val();
  1069. if (mountOptions) {
  1070. storage.mountOptions = JSON.parse(mountOptions);
  1071. }
  1072. return storage;
  1073. },
  1074. /**
  1075. * Deletes the storage from the given tr
  1076. *
  1077. * @param $tr storage row
  1078. * @param Function callback callback to call after save
  1079. */
  1080. deleteStorageConfig: function($tr) {
  1081. var self = this;
  1082. var configId = $tr.data('id');
  1083. if (!_.isNumber(configId)) {
  1084. // deleting unsaved storage
  1085. $tr.remove();
  1086. return;
  1087. }
  1088. var storage = new this._storageConfigClass(configId);
  1089. 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.', {
  1090. storage: this.mountPoint
  1091. }), t('files_external', 'Delete storage?'), function(confirm) {
  1092. if (confirm) {
  1093. self.updateStatus($tr, StorageConfig.Status.IN_PROGRESS);
  1094. storage.destroy({
  1095. success: function () {
  1096. $tr.remove();
  1097. },
  1098. error: function () {
  1099. self.updateStatus($tr, StorageConfig.Status.ERROR);
  1100. }
  1101. });
  1102. }
  1103. });
  1104. },
  1105. /**
  1106. * Saves the storage from the given tr
  1107. *
  1108. * @param $tr storage row
  1109. * @param Function callback callback to call after save
  1110. * @param concurrentTimer only update if the timer matches this
  1111. */
  1112. saveStorageConfig:function($tr, callback, concurrentTimer) {
  1113. var self = this;
  1114. var storage = this.getStorageConfig($tr);
  1115. if (!storage || !storage.validate()) {
  1116. return false;
  1117. }
  1118. this.updateStatus($tr, StorageConfig.Status.IN_PROGRESS);
  1119. storage.save({
  1120. success: function(result) {
  1121. if (concurrentTimer === undefined
  1122. || $tr.data('save-timer') === concurrentTimer
  1123. ) {
  1124. self.updateStatus($tr, result.status);
  1125. $tr.data('id', result.id);
  1126. if (_.isFunction(callback)) {
  1127. callback(storage);
  1128. }
  1129. }
  1130. },
  1131. error: function() {
  1132. if (concurrentTimer === undefined
  1133. || $tr.data('save-timer') === concurrentTimer
  1134. ) {
  1135. self.updateStatus($tr, StorageConfig.Status.ERROR);
  1136. }
  1137. }
  1138. });
  1139. },
  1140. /**
  1141. * Recheck storage availability
  1142. *
  1143. * @param {jQuery} $tr storage row
  1144. * @return {boolean} success
  1145. */
  1146. recheckStorageConfig: function($tr) {
  1147. var self = this;
  1148. var storage = this.getStorageConfig($tr);
  1149. if (!storage.validate()) {
  1150. return false;
  1151. }
  1152. this.updateStatus($tr, StorageConfig.Status.IN_PROGRESS);
  1153. storage.recheck({
  1154. success: function(result) {
  1155. self.updateStatus($tr, result.status, result.statusMessage);
  1156. },
  1157. error: function() {
  1158. self.updateStatus($tr, StorageConfig.Status.ERROR);
  1159. }
  1160. });
  1161. },
  1162. /**
  1163. * Update status display
  1164. *
  1165. * @param {jQuery} $tr
  1166. * @param {number} status
  1167. * @param {string} message
  1168. */
  1169. updateStatus: function($tr, status, message) {
  1170. var $statusSpan = $tr.find('.status span');
  1171. switch (status) {
  1172. case null:
  1173. // remove status
  1174. break;
  1175. case StorageConfig.Status.IN_PROGRESS:
  1176. $statusSpan.attr('class', 'icon-loading-small');
  1177. break;
  1178. case StorageConfig.Status.SUCCESS:
  1179. $statusSpan.attr('class', 'success icon-checkmark-white');
  1180. break;
  1181. case StorageConfig.Status.INDETERMINATE:
  1182. $statusSpan.attr('class', 'indeterminate icon-info-white');
  1183. break;
  1184. default:
  1185. $statusSpan.attr('class', 'error icon-error-white');
  1186. }
  1187. if (typeof message === 'string') {
  1188. $statusSpan.attr('title', message);
  1189. }
  1190. },
  1191. /**
  1192. * Suggest mount point name that doesn't conflict with the existing names in the list
  1193. *
  1194. * @param {string} defaultMountPoint default name
  1195. */
  1196. _suggestMountPoint: function(defaultMountPoint) {
  1197. var $el = this.$el;
  1198. var pos = defaultMountPoint.indexOf('/');
  1199. if (pos !== -1) {
  1200. defaultMountPoint = defaultMountPoint.substring(0, pos);
  1201. }
  1202. defaultMountPoint = defaultMountPoint.replace(/\s+/g, '');
  1203. var i = 1;
  1204. var append = '';
  1205. var match = true;
  1206. while (match && i < 20) {
  1207. match = false;
  1208. $el.find('tbody td.mountPoint input').each(function(index, mountPoint) {
  1209. if ($(mountPoint).val() === defaultMountPoint+append) {
  1210. match = true;
  1211. return false;
  1212. }
  1213. });
  1214. if (match) {
  1215. append = i;
  1216. i++;
  1217. } else {
  1218. break;
  1219. }
  1220. }
  1221. return defaultMountPoint + append;
  1222. },
  1223. /**
  1224. * Toggles the mount options dropdown
  1225. *
  1226. * @param {Object} $tr configuration row
  1227. */
  1228. _showMountOptionsDropdown: function($tr) {
  1229. var self = this;
  1230. var storage = this.getStorageConfig($tr);
  1231. var $toggle = $tr.find('.mountOptionsToggle');
  1232. var dropDown = new MountOptionsDropdown();
  1233. var visibleOptions = [
  1234. 'previews',
  1235. 'filesystem_check_changes',
  1236. 'enable_sharing',
  1237. 'encoding_compatibility',
  1238. 'readonly',
  1239. 'delete'
  1240. ];
  1241. if (this._encryptionEnabled) {
  1242. visibleOptions.push('encrypt');
  1243. }
  1244. dropDown.show($toggle, storage.mountOptions || [], visibleOptions);
  1245. $('body').on('mouseup.mountOptionsDropdown', function(event) {
  1246. var $target = $(event.target);
  1247. if ($target.closest('.popovermenu').length) {
  1248. return;
  1249. }
  1250. dropDown.hide();
  1251. });
  1252. dropDown.$el.on('hide', function() {
  1253. var mountOptions = dropDown.getOptions();
  1254. $('body').off('mouseup.mountOptionsDropdown');
  1255. $tr.find('input.mountOptions').val(JSON.stringify(mountOptions));
  1256. $tr.find('td.mountOptionsToggle>.icon-more').attr('aria-expanded', 'false');
  1257. self.saveStorageConfig($tr);
  1258. });
  1259. }
  1260. }, OC.Backbone.Events);
  1261. window.addEventListener('DOMContentLoaded', function() {
  1262. var enabled = $('#files_external').attr('data-encryption-enabled');
  1263. var canCreateLocal = $('#files_external').attr('data-can-create-local');
  1264. var encryptionEnabled = (enabled ==='true')? true: false;
  1265. var mountConfigListView = new MountConfigListView($('#externalStorage'), {
  1266. encryptionEnabled: encryptionEnabled,
  1267. canCreateLocal: (canCreateLocal === 'true') ? true: false,
  1268. });
  1269. mountConfigListView.loadStorages();
  1270. // TODO: move this into its own View class
  1271. var $allowUserMounting = $('#allowUserMounting');
  1272. $allowUserMounting.bind('change', function() {
  1273. OC.msg.startSaving('#userMountingMsg');
  1274. if (this.checked) {
  1275. OCP.AppConfig.setValue('files_external', 'allow_user_mounting', 'yes');
  1276. $('input[name="allowUserMountingBackends\\[\\]"]').prop('checked', true);
  1277. $('#userMountingBackends').removeClass('hidden');
  1278. $('input[name="allowUserMountingBackends\\[\\]"]').eq(0).trigger('change');
  1279. } else {
  1280. OCP.AppConfig.setValue('files_external', 'allow_user_mounting', 'no');
  1281. $('#userMountingBackends').addClass('hidden');
  1282. }
  1283. OC.msg.finishedSaving('#userMountingMsg', {status: 'success', data: {message: t('files_external', 'Saved')}});
  1284. });
  1285. $('input[name="allowUserMountingBackends\\[\\]"]').bind('change', function() {
  1286. OC.msg.startSaving('#userMountingMsg');
  1287. var userMountingBackends = $('input[name="allowUserMountingBackends\\[\\]"]:checked').map(function(){
  1288. return $(this).val();
  1289. }).get();
  1290. var deprecatedBackends = $('input[name="allowUserMountingBackends\\[\\]"][data-deprecate-to]').map(function(){
  1291. if ($.inArray($(this).data('deprecate-to'), userMountingBackends) !== -1) {
  1292. return $(this).val();
  1293. }
  1294. return null;
  1295. }).get();
  1296. userMountingBackends = userMountingBackends.concat(deprecatedBackends);
  1297. OCP.AppConfig.setValue('files_external', 'user_mounting_backends', userMountingBackends.join());
  1298. OC.msg.finishedSaving('#userMountingMsg', {status: 'success', data: {message: t('files_external', 'Saved')}});
  1299. // disable allowUserMounting
  1300. if(userMountingBackends.length === 0) {
  1301. $allowUserMounting.prop('checked', false);
  1302. $allowUserMounting.trigger('change');
  1303. }
  1304. });
  1305. $('#global_credentials').on('submit', function() {
  1306. var $form = $(this);
  1307. var uid = $form.find('[name=uid]').val();
  1308. var user = $form.find('[name=username]').val();
  1309. var password = $form.find('[name=password]').val();
  1310. var $submit = $form.find('[type=submit]');
  1311. $submit.val(t('files_external', 'Saving …'));
  1312. $.ajax({
  1313. type: 'POST',
  1314. contentType: 'application/json',
  1315. data: JSON.stringify({
  1316. uid: uid,
  1317. user: user,
  1318. password: password
  1319. }),
  1320. url: OC.generateUrl('apps/files_external/globalcredentials'),
  1321. dataType: 'json',
  1322. success: function() {
  1323. $submit.val(t('files_external', 'Saved'));
  1324. setTimeout(function(){
  1325. $submit.val(t('files_external', 'Save'));
  1326. }, 2500);
  1327. }
  1328. });
  1329. return false;
  1330. });
  1331. // global instance
  1332. OCA.Files_External.Settings.mountConfig = mountConfigListView;
  1333. /**
  1334. * Legacy
  1335. *
  1336. * @namespace
  1337. * @deprecated use OCA.Files_External.Settings.mountConfig instead
  1338. */
  1339. OC.MountConfig = {
  1340. saveStorage: _.bind(mountConfigListView.saveStorageConfig, mountConfigListView)
  1341. };
  1342. });
  1343. // export
  1344. OCA.Files_External = OCA.Files_External || {};
  1345. /**
  1346. * @namespace
  1347. */
  1348. OCA.Files_External.Settings = OCA.Files_External.Settings || {};
  1349. OCA.Files_External.Settings.GlobalStorageConfig = GlobalStorageConfig;
  1350. OCA.Files_External.Settings.UserStorageConfig = UserStorageConfig;
  1351. OCA.Files_External.Settings.MountConfigListView = MountConfigListView;
  1352. })();