settings.js 43 KB

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