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.

1215 lines
37 KiB

12 years ago
12 years ago
12 years ago
12 years ago
10 years ago
12 years ago
12 years ago
10 years ago
11 years ago
11 years ago
  1. /**
  2. * Copyright (c) 2014, Arthur Schiwon <blizzz@owncloud.com>
  3. * Copyright (c) 2014, Raghu Nayyar <beingminimal@gmail.com>
  4. * Copyright (c) 2011, Robin Appelman <icewind1991@gmail.com>
  5. * This file is licensed under the Affero General Public License version 3 or later.
  6. * See the COPYING-README file.
  7. */
  8. /* globals escapeHTML, GroupList, DeleteHandler, UserManagementFilter */
  9. var $userList;
  10. var $userListBody;
  11. var $emptyContainer;
  12. var UserDeleteHandler;
  13. var UserList = {
  14. availableGroups: [],
  15. offset: 0,
  16. usersToLoad: 10, //So many users will be loaded when user scrolls down
  17. initialUsersToLoad: 50, //initial number of users to load
  18. currentGid: '',
  19. filter: '',
  20. /**
  21. * Initializes the user list
  22. * @param $el user list table element
  23. */
  24. initialize: function ($el) {
  25. this.$el = $el;
  26. // initially the list might already contain user entries (not fully ajaxified yet)
  27. // initialize these entries
  28. this.$el.find('.quota-user').singleSelect().on('change', this.onQuotaSelect);
  29. },
  30. /**
  31. * Add a user row from user object
  32. *
  33. * @param user object containing following keys:
  34. * {
  35. * 'name': 'username',
  36. * 'displayname': 'Users display name',
  37. * 'groups': ['group1', 'group2'],
  38. * 'subadmin': ['group4', 'group5'],
  39. * 'quota': '10 GB',
  40. * 'quota_bytes': '10737418240',
  41. * 'storageLocation': '/srv/www/owncloud/data/username',
  42. * 'lastLogin': '1418632333'
  43. * 'backend': 'LDAP',
  44. * 'email': 'username@example.org'
  45. * 'isRestoreDisabled':false
  46. * 'isEnabled': true,
  47. * 'size': 156789
  48. * }
  49. */
  50. add: function (user) {
  51. if (this.currentGid && this.currentGid !== '_everyone' && this.currentGid !== '_disabledUsers' && _.indexOf(user.groups, this.currentGid) < 0) {
  52. return false;
  53. }
  54. var $tr = $userListBody.find('tr:first-child').clone();
  55. // this removes just the `display:none` of the template row
  56. $tr.removeAttr('style');
  57. /**
  58. * Avatar or placeholder
  59. */
  60. if ($tr.find('div.avatardiv').length) {
  61. if (user.isAvatarAvailable === true) {
  62. $('div.avatardiv', $tr).avatar(user.name, 32, undefined, undefined, undefined, user.displayname);
  63. } else {
  64. $('div.avatardiv', $tr).imageplaceholder(user.displayname, undefined, 32);
  65. }
  66. }
  67. /**
  68. * add username and displayname to row (in data and visible markup)
  69. */
  70. $tr.data('uid', user.name);
  71. $tr.data('displayname', user.displayname);
  72. $tr.data('mailAddress', user.email);
  73. $tr.data('restoreDisabled', user.isRestoreDisabled);
  74. $tr.data('userEnabled', user.isEnabled);
  75. $tr.find('.name').text(user.name);
  76. $tr.find('td.displayName > span').text(user.displayname);
  77. $tr.find('td.mailAddress > span').text(user.email);
  78. $tr.find('td.displayName > .action').tooltip({placement: 'top'});
  79. $tr.find('td.mailAddress > .action').tooltip({placement: 'top'});
  80. $tr.find('td.password > .action').tooltip({placement: 'top'});
  81. /**
  82. * groups and subadmins
  83. */
  84. var $tdGroups = $tr.find('td.groups');
  85. this._updateGroupListLabel($tdGroups, user.groups);
  86. $tdGroups.find('.action').tooltip({placement: 'top'});
  87. var $tdSubadmins = $tr.find('td.subadmins');
  88. this._updateGroupListLabel($tdSubadmins, user.subadmin);
  89. $tdSubadmins.find('.action').tooltip({placement: 'top'});
  90. /**
  91. * user actions menu
  92. */
  93. if ($tr.find('td.userActions > span > img').length === 0 && OC.currentUser !== user.name) {
  94. var menuImage = $('<img class="svg action">').attr({
  95. src: OC.imagePath('core', 'actions/more')
  96. });
  97. var menuLink = $('<span class="toggleUserActions"></span>')
  98. .append(menuImage);
  99. $tr.find('td.userActions > span').replaceWith(menuLink);
  100. } else if (OC.currentUser === user.name) {
  101. $tr.find('td.userActions').empty();
  102. }
  103. /**
  104. * quota
  105. */
  106. UserList.updateQuotaProgressbar($tr, user.quota_bytes, user.size);
  107. $tr.data('size', user.size);
  108. var $quotaSelect = $tr.find('.quota-user');
  109. var humanSize = humanFileSize(user.size, true);
  110. $quotaSelect.tooltip({
  111. title: t('settings', '{size} used', {size: humanSize}, 0 , {escape: false}).replace('&lt;', '<'),
  112. delay: {
  113. show: 100,
  114. hide: 0
  115. }
  116. });
  117. if (user.quota === 'default') {
  118. $quotaSelect
  119. .data('previous', 'default')
  120. .find('option').attr('selected', null)
  121. .first().attr('selected', 'selected');
  122. } else {
  123. var $options = $quotaSelect.find('option');
  124. var $foundOption = $options.filterAttr('value', user.quota);
  125. if ($foundOption.length > 0) {
  126. $foundOption.attr('selected', 'selected');
  127. } else {
  128. // append before "Other" entry
  129. $options.last().before('<option value="' + escapeHTML(user.quota) + '" selected="selected">' + escapeHTML(user.quota) + '</option>');
  130. }
  131. }
  132. /**
  133. * storage location
  134. */
  135. $tr.find('td.storageLocation').text(user.storageLocation);
  136. /**
  137. * user backend
  138. */
  139. $tr.find('td.userBackend').text(user.backend);
  140. /**
  141. * last login
  142. */
  143. var lastLoginRel = t('settings', 'never');
  144. var lastLoginAbs = lastLoginRel;
  145. if (user.lastLogin !== 0) {
  146. lastLoginRel = OC.Util.relativeModifiedDate(user.lastLogin);
  147. lastLoginAbs = OC.Util.formatDate(user.lastLogin);
  148. }
  149. var $tdLastLogin = $tr.find('td.lastLogin');
  150. $tdLastLogin.text(lastLoginRel);
  151. $tdLastLogin.attr('title', lastLoginAbs);
  152. // setup tooltip with #app-content as container to prevent the td to resize on hover
  153. $tdLastLogin.tooltip({placement: 'top', container: '#app-content'});
  154. /**
  155. * append generated row to user list
  156. */
  157. $tr.appendTo($userList);
  158. $quotaSelect.on('change', UserList.onQuotaSelect);
  159. // defer init so the user first sees the list appear more quickly
  160. window.setTimeout(function () {
  161. $quotaSelect.singleSelect();
  162. }, 0);
  163. },
  164. // From http://my.opera.com/GreyWyvern/blog/show.dml/1671288
  165. alphanum: function (a, b) {
  166. function chunkify (t) {
  167. var tz = [], x = 0, y = -1, n = 0, i, j;
  168. while (i = (j = t.charAt(x++)).charCodeAt(0)) {
  169. var m = (i === 46 || (i >= 48 && i <= 57));
  170. if (m !== n) {
  171. tz[++y] = "";
  172. n = m;
  173. }
  174. tz[y] += j;
  175. }
  176. return tz;
  177. }
  178. var aa = chunkify(a.toLowerCase());
  179. var bb = chunkify(b.toLowerCase());
  180. for (var x = 0; aa[x] && bb[x]; x++) {
  181. if (aa[x] !== bb[x]) {
  182. var c = Number(aa[x]), d = Number(bb[x]);
  183. if (c === aa[x] && d === bb[x]) {
  184. return c - d;
  185. } else {
  186. return (aa[x] > bb[x]) ? 1 : -1;
  187. }
  188. }
  189. }
  190. return aa.length - bb.length;
  191. },
  192. preSortSearchString: function (a, b) {
  193. var pattern = this.filter;
  194. if (typeof pattern === 'undefined') {
  195. return undefined;
  196. }
  197. pattern = pattern.toLowerCase();
  198. var aMatches = false;
  199. var bMatches = false;
  200. if (typeof a === 'string' && a.toLowerCase().indexOf(pattern) === 0) {
  201. aMatches = true;
  202. }
  203. if (typeof b === 'string' && b.toLowerCase().indexOf(pattern) === 0) {
  204. bMatches = true;
  205. }
  206. if ((aMatches && bMatches) || (!aMatches && !bMatches)) {
  207. return undefined;
  208. }
  209. if (aMatches) {
  210. return -1;
  211. } else {
  212. return 1;
  213. }
  214. },
  215. doSort: function () {
  216. // some browsers like Chrome lose the scrolling information
  217. // when messing with the list elements
  218. var lastScrollTop = this.scrollArea.scrollTop();
  219. var lastScrollLeft = this.scrollArea.scrollLeft();
  220. var rows = $userListBody.find('tr').get();
  221. rows.sort(function (a, b) {
  222. // FIXME: inefficient way of getting the names,
  223. // better use a data attribute
  224. a = $(a).find('.name').text();
  225. b = $(b).find('.name').text();
  226. var firstSort = UserList.preSortSearchString(a, b);
  227. if (typeof firstSort !== 'undefined') {
  228. return firstSort;
  229. }
  230. return OC.Util.naturalSortCompare(a, b);
  231. });
  232. var items = [];
  233. $.each(rows, function (index, row) {
  234. items.push(row);
  235. if (items.length === 100) {
  236. $userListBody.append(items);
  237. items = [];
  238. }
  239. });
  240. if (items.length > 0) {
  241. $userListBody.append(items);
  242. }
  243. this.scrollArea.scrollTop(lastScrollTop);
  244. this.scrollArea.scrollLeft(lastScrollLeft);
  245. },
  246. checkUsersToLoad: function () {
  247. //30 shall be loaded initially, from then on always 10 upon scrolling
  248. if (UserList.isEmpty === false) {
  249. UserList.usersToLoad = 10;
  250. } else {
  251. UserList.usersToLoad = UserList.initialUsersToLoad;
  252. }
  253. },
  254. empty: function () {
  255. //one row needs to be kept, because it is cloned to add new rows
  256. $userListBody.find('tr:not(:first)').remove();
  257. var $tr = $userListBody.find('tr:first');
  258. $tr.hide();
  259. //on an update a user may be missing when the username matches with that
  260. //of the hidden row. So change this to a random string.
  261. $tr.data('uid', Math.random().toString(36).substring(2));
  262. UserList.isEmpty = true;
  263. UserList.offset = 0;
  264. UserList.checkUsersToLoad();
  265. },
  266. hide: function (uid) {
  267. UserList.getRow(uid).hide();
  268. },
  269. show: function (uid) {
  270. UserList.getRow(uid).show();
  271. },
  272. markRemove: function (uid) {
  273. var $tr = UserList.getRow(uid);
  274. var groups = $tr.find('.groups').data('groups');
  275. for (var i in groups) {
  276. var gid = groups[i];
  277. var $li = GroupList.getGroupLI(gid);
  278. var userCount = GroupList.getUserCount($li);
  279. GroupList.setUserCount($li, userCount - 1);
  280. }
  281. GroupList.decEveryoneCount();
  282. UserList.hide(uid);
  283. },
  284. remove: function (uid) {
  285. UserList.getRow(uid).remove();
  286. },
  287. undoRemove: function (uid) {
  288. var $tr = UserList.getRow(uid);
  289. var groups = $tr.find('.groups').data('groups');
  290. for (var i in groups) {
  291. var gid = groups[i];
  292. var $li = GroupList.getGroupLI(gid);
  293. var userCount = GroupList.getUserCount($li);
  294. GroupList.setUserCount($li, userCount + 1);
  295. }
  296. GroupList.incEveryoneCount();
  297. UserList.getRow(uid).show();
  298. },
  299. has: function (uid) {
  300. return UserList.getRow(uid).length > 0;
  301. },
  302. getRow: function (uid) {
  303. return $userListBody.find('tr').filter(function () {
  304. return UserList.getUID(this) === uid;
  305. });
  306. },
  307. getUID: function (element) {
  308. return ($(element).closest('tr').data('uid') || '').toString();
  309. },
  310. getDisplayName: function (element) {
  311. return ($(element).closest('tr').data('displayname') || '').toString();
  312. },
  313. getMailAddress: function (element) {
  314. return ($(element).closest('tr').data('mailAddress') || '').toString();
  315. },
  316. getRestoreDisabled: function (element) {
  317. return ($(element).closest('tr').data('restoreDisabled') || '');
  318. },
  319. getUserEnabled: function (element) {
  320. return ($(element).closest('tr').data('userEnabled') || '');
  321. },
  322. initDeleteHandling: function () {
  323. //set up handler
  324. UserDeleteHandler = new DeleteHandler('/settings/users/users', 'username',
  325. UserList.markRemove, UserList.remove);
  326. //configure undo
  327. OC.Notification.hide();
  328. var msg = escapeHTML(t('settings', 'deleted {userName}', {userName: '%oid'})) + '<span class="undo">' +
  329. escapeHTML(t('settings', 'undo')) + '</span>';
  330. UserDeleteHandler.setNotification(OC.Notification, 'deleteuser', msg,
  331. UserList.undoRemove);
  332. //when to mark user for delete
  333. $userListBody.on('click', '.action-remove', function () {
  334. // Call function for handling delete/undo
  335. var uid = UserList.getUID(this);
  336. if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
  337. OC.PasswordConfirmation.requirePasswordConfirmation(function () {
  338. UserDeleteHandler.mark(uid);
  339. });
  340. return;
  341. }
  342. UserDeleteHandler.mark(uid);
  343. });
  344. //delete a marked user when leaving the page
  345. $(window).on('beforeunload', function () {
  346. UserDeleteHandler.deleteEntry();
  347. });
  348. },
  349. update: function (gid, limit) {
  350. if (UserList.updating) {
  351. return;
  352. }
  353. if (!limit) {
  354. limit = UserList.usersToLoad;
  355. }
  356. $userList.siblings('.loading').css('visibility', 'visible');
  357. UserList.updating = true;
  358. if (gid === undefined) {
  359. gid = '';
  360. }
  361. UserList.currentGid = gid;
  362. var pattern = this.filter;
  363. $.get(
  364. OC.generateUrl('/settings/users/users'),
  365. {offset: UserList.offset, limit: limit, gid: gid, pattern: pattern},
  366. function (result) {
  367. //The offset does not mirror the amount of users available,
  368. //because it is backend-dependent. For correct retrieval,
  369. //always the limit(requested amount of users) needs to be added.
  370. $.each(result, function (index, user) {
  371. if (UserList.has(user.name)) {
  372. return true;
  373. }
  374. UserList.add(user);
  375. });
  376. if (result.length > 0) {
  377. UserList.doSort();
  378. $userList.siblings('.loading').css('visibility', 'hidden');
  379. // reset state on load
  380. UserList.noMoreEntries = false;
  381. $userListHead.show();
  382. $emptyContainer.hide();
  383. $emptyContainer.find('h2').text('');
  384. }
  385. else {
  386. UserList.noMoreEntries = true;
  387. $userList.siblings('.loading').remove();
  388. if (pattern !== "") {
  389. $userListHead.hide();
  390. $emptyContainer.show();
  391. $emptyContainer.find('h2').html(t('settings', 'No user found for <strong>{pattern}</strong>', {pattern: pattern}));
  392. }
  393. }
  394. UserList.offset += limit;
  395. }).always(function () {
  396. UserList.updating = false;
  397. });
  398. },
  399. applyGroupSelect: function (element, user, checked) {
  400. if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
  401. OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this.applyGroupSelect, this, element, user, checked));
  402. return;
  403. }
  404. var $element = $(element);
  405. var addUserToGroup = null,
  406. removeUserFromGroup = null;
  407. if (user) { // Only if in a user row, and not the #newusergroups select
  408. var handleUserGroupMembership = function (group, add) {
  409. if (user === OC.getCurrentUser().uid && group === 'admin') {
  410. return false;
  411. }
  412. if (!OC.isUserAdmin() && checked.length === 1 && checked[0] === group) {
  413. return false;
  414. }
  415. if (add && OC.isUserAdmin() && UserList.availableGroups.indexOf(group) === -1) {
  416. GroupList.createGroup(group);
  417. if (UserList.availableGroups.indexOf(group) === -1) {
  418. UserList.availableGroups.push(group);
  419. }
  420. }
  421. $.ajax({
  422. url: OC.linkToOCS('cloud/users/' + user, 2) + 'groups',
  423. data: {
  424. groupid: group
  425. },
  426. type: add ? 'POST' : 'DELETE',
  427. beforeSend: function (request) {
  428. request.setRequestHeader('Accept', 'application/json');
  429. },
  430. success: function () {
  431. GroupList.update();
  432. if (add && UserList.availableGroups.indexOf(group) === -1) {
  433. UserList.availableGroups.push(group);
  434. }
  435. if (add) {
  436. GroupList.incGroupCount(group);
  437. } else {
  438. GroupList.decGroupCount(group);
  439. }
  440. },
  441. error: function () {
  442. if (add) {
  443. OC.Notification.show(t('settings', 'Unable to add user to group {group}', {
  444. group: group
  445. }));
  446. } else {
  447. OC.Notification.show(t('settings', 'Unable to remove user from group {group}', {
  448. group: group
  449. }));
  450. }
  451. }
  452. });
  453. };
  454. addUserToGroup = function (group) {
  455. return handleUserGroupMembership(group, true);
  456. };
  457. removeUserFromGroup = function (group) {
  458. return handleUserGroupMembership(group, false);
  459. };
  460. }
  461. var addGroup = function (select, group) {
  462. GroupList.addGroup(escapeHTML(group));
  463. };
  464. var label;
  465. if (OC.isUserAdmin()) {
  466. label = t('settings', 'Add group');
  467. }
  468. else {
  469. label = null;
  470. }
  471. $element.multiSelect({
  472. createCallback: addGroup,
  473. createText: label,
  474. selectedFirst: true,
  475. checked: checked,
  476. oncheck: addUserToGroup,
  477. onuncheck: removeUserFromGroup,
  478. minWidth: 100
  479. });
  480. },
  481. applySubadminSelect: function (element, user, checked) {
  482. if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
  483. OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this.applySubadminSelect, this, element, user, checked));
  484. return;
  485. }
  486. var $element = $(element);
  487. var checkHandler = function (group) {
  488. if (group === 'admin') {
  489. return false;
  490. }
  491. $.post(
  492. OC.filePath('settings', 'ajax', 'togglesubadmins.php'),
  493. {
  494. username: user,
  495. group: group
  496. },
  497. function (response) {
  498. if (response.data !== undefined && response.data.message) {
  499. OC.Notification.show(response.data.message);
  500. }
  501. }
  502. );
  503. };
  504. $element.multiSelect({
  505. createText: null,
  506. checked: checked,
  507. oncheck: checkHandler,
  508. onuncheck: checkHandler,
  509. minWidth: 100
  510. });
  511. },
  512. _onScroll: function () {
  513. if (!!UserList.noMoreEntries) {
  514. return;
  515. }
  516. if (UserList.scrollArea.scrollTop() + UserList.scrollArea.height() > UserList.scrollArea.get(0).scrollHeight - 500) {
  517. UserList.update(UserList.currentGid);
  518. }
  519. },
  520. updateQuotaProgressbar: function ($tr, quota, size) {
  521. var usedQuota;
  522. if (quota > 0) {
  523. usedQuota = Math.min(100, Math.round(size / quota * 100));
  524. } else {
  525. var usedInGB = size / (10 * Math.pow(2, 30));
  526. //asymptotic curve approaching 50% at 10GB to visualize used stace with infinite quota
  527. usedQuota = 95 * (1 - (1 / (usedInGB + 1)));
  528. }
  529. $tr.find('.quota-user-progress').val(usedQuota);
  530. if (usedQuota > 80) {
  531. $tr.find('.quota-user-progress').addClass('warn');
  532. } else {
  533. $tr.find('.quota-user-progress').removeClass('warn');
  534. }
  535. },
  536. /**
  537. * Event handler for when a quota has been changed through a single select.
  538. * This will save the value.
  539. */
  540. onQuotaSelect: function (ev) {
  541. var $select = $(ev.target);
  542. var $tr = $select.closest('tr');
  543. const size = $tr.data('size');
  544. var uid = UserList.getUID($select);
  545. var quota = $select.val();
  546. if (quota === 'other') {
  547. return;
  548. }
  549. if ((quota !== 'default' && quota !== "none") && (!OC.Util.computerFileSize(quota))) {
  550. // the select component has added the bogus value, delete it again
  551. $select.find('option[selected]').remove();
  552. OC.Notification.showTemporary(t('core', 'Invalid quota value "{val}"', {val: quota}));
  553. return;
  554. }
  555. UserList._updateQuota(uid, quota, function (returnedQuota) {
  556. if (quota !== returnedQuota) {
  557. $select.find(':selected').text(returnedQuota);
  558. UserList.updateQuotaProgressbar($tr, OC.Util.computerFileSize(returnedQuota), size);
  559. }
  560. });
  561. UserList.updateQuotaProgressbar($tr, OC.Util.computerFileSize(quota), size);
  562. // remove the background color that the "other" option placed on the select
  563. $select.css('background-color', 'transparent');
  564. },
  565. /**
  566. * Saves the quota for the given user
  567. * @param {String} [uid] optional user id, sets default quota if empty
  568. * @param {String} quota quota value
  569. * @param {Function} ready callback after save
  570. */
  571. _updateQuota: function (uid, quota, ready) {
  572. if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
  573. OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this._updateQuota, this, uid, quota, ready));
  574. return;
  575. }
  576. $.post(
  577. OC.filePath('settings', 'ajax', 'setquota.php'),
  578. {username: uid, quota: quota},
  579. function (result) {
  580. if (result.status === 'error') {
  581. OC.Notification.showTemporary(result.data.message);
  582. } else {
  583. if (ready) {
  584. ready(result.data.quota);
  585. }
  586. }
  587. }
  588. );
  589. },
  590. /**
  591. * Creates a temporary jquery.multiselect selector on the given group field
  592. */
  593. _triggerGroupEdit: function ($td, isSubadminSelect) {
  594. var $groupsListContainer = $td.find('.groupsListContainer');
  595. var placeholder = $groupsListContainer.attr('data-placeholder') || t('settings', 'no group');
  596. var user = UserList.getUID($td);
  597. var checked = $td.data('groups') || [];
  598. var extraGroups = [].concat(checked);
  599. $td.find('.multiselectoptions').remove();
  600. // jquery.multiselect can only work with select+options in DOM ? We'll give jquery.multiselect what it wants...
  601. var $groupsSelect;
  602. if (isSubadminSelect) {
  603. $groupsSelect = $('<select multiple="multiple" class="groupsselect multiselect button" title="' + placeholder + '"></select>');
  604. } else {
  605. $groupsSelect = $('<select multiple="multiple" class="subadminsselect multiselect button" title="' + placeholder + '"></select>')
  606. }
  607. function createItem (group) {
  608. if (isSubadminSelect && group === 'admin') {
  609. // can't become subadmin of "admin" group
  610. return;
  611. }
  612. $groupsSelect.append($('<option value="' + escapeHTML(group) + '">' + escapeHTML(group) + '</option>'));
  613. }
  614. $.each(this.availableGroups, function (i, group) {
  615. // some new groups might be selected but not in the available groups list yet
  616. var extraIndex = extraGroups.indexOf(group);
  617. if (extraIndex >= 0) {
  618. // remove extra group as it was found
  619. extraGroups.splice(extraIndex, 1);
  620. }
  621. createItem(group);
  622. });
  623. $.each(extraGroups, function (i, group) {
  624. createItem(group);
  625. });
  626. $td.append($groupsSelect);
  627. if (isSubadminSelect) {
  628. UserList.applySubadminSelect($groupsSelect, user, checked);
  629. } else {
  630. UserList.applyGroupSelect($groupsSelect, user, checked);
  631. }
  632. $groupsListContainer.addClass('hidden');
  633. $td.find('.multiselect:not(.groupsListContainer):first').click();
  634. $groupsSelect.on('dropdownclosed', function (e) {
  635. $groupsSelect.remove();
  636. $td.find('.multiselect:not(.groupsListContainer)').parent().remove();
  637. $td.find('.multiselectoptions').remove();
  638. $groupsListContainer.removeClass('hidden');
  639. UserList._updateGroupListLabel($td, e.checked);
  640. });
  641. },
  642. /**
  643. * Updates the groups list td with the given groups selection
  644. */
  645. _updateGroupListLabel: function ($td, groups) {
  646. var placeholder = $td.find('.groupsListContainer').attr('data-placeholder');
  647. var $groupsEl = $td.find('.groupsList');
  648. $groupsEl.text(groups.join(', ') || placeholder || t('settings', 'no group'));
  649. $td.data('groups', groups);
  650. }
  651. };
  652. $(document).ready(function () {
  653. OC.Plugins.attach('OC.Settings.UserList', UserList);
  654. $userList = $('#userlist');
  655. $userListBody = $userList.find('tbody');
  656. $userListHead = $userList.find('thead');
  657. $emptyContainer = $userList.siblings('.emptycontent');
  658. UserList.initDeleteHandling();
  659. // Implements User Search
  660. OCA.Search.users = new UserManagementFilter(UserList, GroupList);
  661. UserList.scrollArea = $('#app-content');
  662. UserList.doSort();
  663. UserList.availableGroups = $userList.data('groups');
  664. UserList.scrollArea.scroll(function (e) {
  665. UserList._onScroll(e);
  666. });
  667. $userList.after($('<div class="loading" style="height: 200px; visibility: hidden;"></div>'));
  668. // TODO: move other init calls inside of initialize
  669. UserList.initialize($('#userlist'));
  670. var _submitPasswordChange = function (uid, password, recoveryPasswordVal, blurFunction) {
  671. if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
  672. OC.PasswordConfirmation.requirePasswordConfirmation(function () {
  673. _submitPasswordChange(uid, password, recoveryPasswordVal, blurFunction);
  674. });
  675. return;
  676. }
  677. $.post(
  678. OC.generateUrl('/settings/users/changepassword'),
  679. {
  680. username: uid,
  681. password: password,
  682. recoveryPassword: recoveryPasswordVal
  683. },
  684. function (result) {
  685. blurFunction();
  686. if (result.status === 'success') {
  687. OC.Notification.showTemporary(t('admin', 'Password successfully changed'));
  688. } else {
  689. OC.Notification.showTemporary(t('admin', result.data.message));
  690. }
  691. }
  692. ).fail(blurFunction);
  693. };
  694. $userListBody.on('click', '.password', function (event) {
  695. event.stopPropagation();
  696. var $td = $(this).closest('td');
  697. var $tr = $(this).closest('tr');
  698. var uid = UserList.getUID($td);
  699. var $input = $('<input type="password">');
  700. var isRestoreDisabled = UserList.getRestoreDisabled($td) === true;
  701. var blurFunction = function () {
  702. $(this).replaceWith($('<span>●●●●●●●</span>'));
  703. $td.find('img').show();
  704. // remove highlight class from users without recovery ability
  705. $tr.removeClass('row-warning');
  706. };
  707. blurFunction = _.bind(blurFunction, $input);
  708. if (isRestoreDisabled) {
  709. $tr.addClass('row-warning');
  710. // add tooltip if the password change could cause data loss - no recovery enabled
  711. $input.attr('title', t('settings', 'Changing the password will result in data loss, because data recovery is not available for this user'));
  712. $input.tooltip({placement: 'bottom'});
  713. }
  714. $td.find('img').hide();
  715. $td.children('span').replaceWith($input);
  716. $input
  717. .focus()
  718. .keypress(function (event) {
  719. if (event.keyCode === 13) {
  720. if ($(this).val().length > 0) {
  721. var recoveryPasswordVal = $('input:password[id="recoveryPassword"]').val();
  722. $input.off('blur');
  723. _submitPasswordChange(uid, $(this).val(), recoveryPasswordVal, blurFunction);
  724. } else {
  725. $input.blur();
  726. }
  727. }
  728. })
  729. .blur(blurFunction);
  730. });
  731. $('input:password[id="recoveryPassword"]').keyup(function () {
  732. OC.Notification.hide();
  733. });
  734. var _submitDisplayNameChange = function ($tr, uid, displayName, blurFunction) {
  735. var $div = $tr.find('div.avatardiv');
  736. if ($div.length) {
  737. $div.imageplaceholder(uid, displayName);
  738. }
  739. if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
  740. OC.PasswordConfirmation.requirePasswordConfirmation(function () {
  741. _submitDisplayNameChange($tr, uid, displayName, blurFunction);
  742. });
  743. return;
  744. }
  745. $.ajax({
  746. type: 'POST',
  747. url: OC.generateUrl('/settings/users/{id}/displayName', {id: uid}),
  748. data: {
  749. username: uid,
  750. displayName: displayName
  751. }
  752. }).success(function (result) {
  753. if (result && result.status === 'success' && $div.length) {
  754. $div.avatar(result.data.username, 32);
  755. }
  756. $tr.data('displayname', displayName);
  757. blurFunction();
  758. }).fail(function (result) {
  759. OC.Notification.showTemporary(result.responseJSON.message);
  760. $tr.find('.displayName input').blur(blurFunction);
  761. });
  762. };
  763. $userListBody.on('click', '.displayName', function (event) {
  764. event.stopPropagation();
  765. var $td = $(this).closest('td');
  766. var $tr = $td.closest('tr');
  767. var uid = UserList.getUID($td);
  768. var displayName = escapeHTML(UserList.getDisplayName($td));
  769. var $input = $('<input type="text" value="' + displayName + '">');
  770. var blurFunction = function () {
  771. var displayName = $tr.data('displayname');
  772. $input.replaceWith('<span>' + escapeHTML(displayName) + '</span>');
  773. $td.find('img').show();
  774. };
  775. $td.find('img').hide();
  776. $td.children('span').replaceWith($input);
  777. $input
  778. .focus()
  779. .keypress(function (event) {
  780. if (event.keyCode === 13) {
  781. if ($(this).val().length > 0) {
  782. $input.off('blur');
  783. _submitDisplayNameChange($tr, uid, $(this).val(), blurFunction);
  784. } else {
  785. $input.blur();
  786. }
  787. }
  788. })
  789. .blur(blurFunction);
  790. });
  791. var _submitEmailChange = function ($tr, $td, $input, uid, mailAddress, blurFunction) {
  792. if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
  793. OC.PasswordConfirmation.requirePasswordConfirmation(function () {
  794. _submitEmailChange($tr, $td, $input, uid, mailAddress, blurFunction);
  795. });
  796. return;
  797. }
  798. $.ajax({
  799. type: 'PUT',
  800. url: OC.generateUrl('/settings/users/{id}/mailAddress', {id: uid}),
  801. data: {
  802. mailAddress: mailAddress
  803. }
  804. }).success(function () {
  805. // set data attribute to new value
  806. // will in blur() be used to show the text instead of the input field
  807. $tr.data('mailAddress', mailAddress);
  808. $td.find('.loading-small').css('display', '');
  809. $input.removeAttr('disabled')
  810. .triggerHandler('blur'); // needed instead of $input.blur() for Firefox
  811. blurFunction();
  812. }).fail(function (result) {
  813. if (!_.isUndefined(result.responseJSON.data)) {
  814. OC.Notification.showTemporary(result.responseJSON.data.message);
  815. } else if (!_.isUndefined(result.responseJSON.message)) {
  816. OC.Notification.showTemporary(result.responseJSON.message);
  817. } else {
  818. OC.Notification.showTemporary(t('settings', 'Could not change the users email'));
  819. }
  820. $td.find('.loading-small').css('display', '');
  821. $input.removeAttr('disabled')
  822. .css('padding-right', '6px');
  823. $input.blur(blurFunction);
  824. });
  825. };
  826. $userListBody.on('click', '.mailAddress', function (event) {
  827. event.stopPropagation();
  828. var $td = $(this).closest('td');
  829. var $tr = $td.closest('tr');
  830. var uid = UserList.getUID($td);
  831. var mailAddress = escapeHTML(UserList.getMailAddress($td));
  832. var $input = $('<input type="text">').val(mailAddress);
  833. var blurFunction = function () {
  834. if ($td.find('.loading-small').css('display') === 'inline-block') {
  835. // in Chrome the blur event is fired too early by the browser - even if the request is still running
  836. return;
  837. }
  838. var $span = $('<span>').text($tr.data('mailAddress'));
  839. $input.replaceWith($span);
  840. $td.find('img').show();
  841. };
  842. $td.children('span').replaceWith($input);
  843. $td.find('img').hide();
  844. $input
  845. .focus()
  846. .keypress(function (event) {
  847. if (event.keyCode === 13) {
  848. // enter key
  849. $td.find('.loading-small').css('display', 'inline-block');
  850. $input.css('padding-right', '26px');
  851. $input.attr('disabled', 'disabled');
  852. $input.off('blur');
  853. _submitEmailChange($tr, $td, $input, uid, $(this).val(), blurFunction);
  854. }
  855. })
  856. .blur(blurFunction);
  857. });
  858. $('#newuser .groupsListContainer').on('click', function (event) {
  859. event.stopPropagation();
  860. var $div = $(this).closest('.groups');
  861. UserList._triggerGroupEdit($div);
  862. });
  863. $userListBody.on('click', '.groups .groupsListContainer, .subadmins .groupsListContainer', function (event) {
  864. event.stopPropagation();
  865. var $td = $(this).closest('td');
  866. var isSubadminSelect = $td.hasClass('subadmins');
  867. UserList._triggerGroupEdit($td, isSubadminSelect);
  868. });
  869. $userListBody.on('click', '.toggleUserActions', function (event) {
  870. event.stopPropagation();
  871. var $td = $(this).closest('td');
  872. var $tr = $($td).closest('tr');
  873. var menudiv = $tr.find('.popovermenu');
  874. if ($tr.is('.active')) {
  875. $tr.removeClass('active');
  876. return;
  877. }
  878. $('#userlist tr.active').removeClass('active');
  879. menudiv.find('.action-togglestate').empty();
  880. if ($tr.data('userEnabled')) {
  881. $('.action-togglestate', $td).html('<span class="icon icon-close"></span><span>' + t('settings', 'Disable') + '</span>');
  882. } else {
  883. $('.action-togglestate', $td).html('<span class="icon icon-add"></span><span>' + t('settings', 'Enable') + '</span>');
  884. }
  885. $tr.addClass('active');
  886. });
  887. $(document.body).click(function () {
  888. $('#userlist tr.active').removeClass('active');
  889. });
  890. $userListBody.on('click', '.action-togglestate', function (event) {
  891. event.stopPropagation();
  892. var $td = $(this).closest('td');
  893. var $tr = $td.closest('tr');
  894. var uid = UserList.getUID($td);
  895. var setEnabled = UserList.getUserEnabled($td) ? 0 : 1;
  896. $.post(
  897. OC.generateUrl('/settings/users/{id}/setEnabled', {id: uid}),
  898. {username: uid, enabled: setEnabled},
  899. function (result) {
  900. if (result && result.status === 'success') {
  901. var count = GroupList.getUserCount(GroupList.getGroupLI('_disabledUsers'));
  902. $tr.remove();
  903. if (result.data.enabled == 1) {
  904. $tr.data('userEnabled', true);
  905. GroupList.setUserCount(GroupList.getGroupLI('_disabledUsers'), count - 1);
  906. } else {
  907. $tr.data('userEnabled', false);
  908. GroupList.setUserCount(GroupList.getGroupLI('_disabledUsers'), count + 1);
  909. }
  910. } else {
  911. OC.dialogs.alert(result.data.message, t('settings', 'Error while changing status of {user}', {user: uid}));
  912. }
  913. }
  914. ).fail(function (result) {
  915. var message = 'Unknown error';
  916. if (result.responseJSON &&
  917. result.responseJSON.data &&
  918. result.responseJSON.data.message) {
  919. message = result.responseJSON.data.message;
  920. }
  921. OC.dialogs.alert(message, t('settings', 'Error while changing status of {user}', {user: uid}));
  922. });
  923. });
  924. // init the quota field select box after it is shown the first time
  925. $('#app-settings').one('show', function () {
  926. $(this).find('#default_quota').singleSelect().on('change', UserList.onQuotaSelect);
  927. });
  928. $('#newuser input').click(function () {
  929. // empty the container also here to avoid visual delay
  930. $emptyContainer.hide();
  931. OC.Search = new OCA.Search($('#searchbox'), $('#searchresults'));
  932. OC.Search.clear();
  933. });
  934. UserList._updateGroupListLabel($('#newuser .groups'), []);
  935. var _submitNewUserForm = function (event) {
  936. event.preventDefault();
  937. if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
  938. OC.PasswordConfirmation.requirePasswordConfirmation(function () {
  939. _submitNewUserForm(event);
  940. });
  941. return;
  942. }
  943. var username = $('#newusername').val();
  944. var password = $('#newuserpassword').val();
  945. var email = $('#newemail').val();
  946. if ($.trim(username) === '') {
  947. OC.Notification.showTemporary(t('settings', 'Error creating user: {message}', {
  948. message: t('settings', 'A valid username must be provided')
  949. }));
  950. return false;
  951. }
  952. if ($.trim(password) === '' && !$('#CheckboxMailOnUserCreate').is(':checked')) {
  953. OC.Notification.showTemporary(t('settings', 'Error creating user: {message}', {
  954. message: t('settings', 'A valid password must be provided')
  955. }));
  956. return false;
  957. }
  958. if (!$('#CheckboxMailOnUserCreate').is(':checked')) {
  959. email = '';
  960. }
  961. if ($('#CheckboxMailOnUserCreate').is(':checked') && $.trim(email) === '') {
  962. OC.Notification.showTemporary(t('settings', 'Error creating user: {message}', {
  963. message: t('settings', 'A valid email must be provided')
  964. }));
  965. return false;
  966. }
  967. var promise;
  968. if (UserDeleteHandler) {
  969. promise = UserDeleteHandler.deleteEntry();
  970. } else {
  971. promise = $.Deferred().resolve().promise();
  972. }
  973. promise.then(function () {
  974. var groups = $('#newuser .groups').data('groups') || [];
  975. $.post(
  976. OC.generateUrl('/settings/users/users'),
  977. {
  978. username: username,
  979. password: password,
  980. groups: groups,
  981. email: email
  982. },
  983. function (result) {
  984. if (result.groups) {
  985. for (var i in result.groups) {
  986. var gid = result.groups[i];
  987. if (UserList.availableGroups.indexOf(gid) === -1) {
  988. UserList.availableGroups.push(gid);
  989. }
  990. var $li = GroupList.getGroupLI(gid);
  991. var userCount = GroupList.getUserCount($li);
  992. GroupList.setUserCount($li, userCount + 1);
  993. }
  994. }
  995. if (!UserList.has(username)) {
  996. UserList.add(result);
  997. UserList.doSort();
  998. }
  999. $('#newusername').focus();
  1000. GroupList.incEveryoneCount();
  1001. }).fail(function (result) {
  1002. OC.Notification.showTemporary(t('settings', 'Error creating user: {message}', {
  1003. message: result.responseJSON.message
  1004. }, undefined, {escape: false}));
  1005. }).success(function () {
  1006. $('#newuser').get(0).reset();
  1007. });
  1008. });
  1009. };
  1010. $('#newuser').submit(_submitNewUserForm);
  1011. if ($('#CheckboxStorageLocation').is(':checked')) {
  1012. $("#userlist .storageLocation").show();
  1013. }
  1014. // Option to display/hide the "Storage location" column
  1015. $('#CheckboxStorageLocation').click(function () {
  1016. if ($('#CheckboxStorageLocation').is(':checked')) {
  1017. $("#userlist .storageLocation").show();
  1018. if (OC.isUserAdmin()) {
  1019. OCP.AppConfig.setValue('core', 'umgmt_show_storage_location', 'true');
  1020. }
  1021. } else {
  1022. $("#userlist .storageLocation").hide();
  1023. if (OC.isUserAdmin()) {
  1024. OCP.AppConfig.setValue('core', 'umgmt_show_storage_location', 'false');
  1025. }
  1026. }
  1027. });
  1028. if ($('#CheckboxLastLogin').is(':checked')) {
  1029. $("#userlist .lastLogin").show();
  1030. }
  1031. // Option to display/hide the "Last Login" column
  1032. $('#CheckboxLastLogin').click(function () {
  1033. if ($('#CheckboxLastLogin').is(':checked')) {
  1034. $("#userlist .lastLogin").show();
  1035. if (OC.isUserAdmin()) {
  1036. OCP.AppConfig.setValue('core', 'umgmt_show_last_login', 'true');
  1037. }
  1038. } else {
  1039. $("#userlist .lastLogin").hide();
  1040. if (OC.isUserAdmin()) {
  1041. OCP.AppConfig.setValue('core', 'umgmt_show_last_login', 'false');
  1042. }
  1043. }
  1044. });
  1045. if ($('#CheckboxEmailAddress').is(':checked')) {
  1046. $("#userlist .mailAddress").show();
  1047. }
  1048. // Option to display/hide the "Mail Address" column
  1049. $('#CheckboxEmailAddress').click(function () {
  1050. if ($('#CheckboxEmailAddress').is(':checked')) {
  1051. $("#userlist .mailAddress").show();
  1052. if (OC.isUserAdmin()) {
  1053. OCP.AppConfig.setValue('core', 'umgmt_show_email', 'true');
  1054. }
  1055. } else {
  1056. $("#userlist .mailAddress").hide();
  1057. if (OC.isUserAdmin()) {
  1058. OCP.AppConfig.setValue('core', 'umgmt_show_email', 'false');
  1059. }
  1060. }
  1061. });
  1062. if ($('#CheckboxUserBackend').is(':checked')) {
  1063. $("#userlist .userBackend").show();
  1064. }
  1065. // Option to display/hide the "User Backend" column
  1066. $('#CheckboxUserBackend').click(function () {
  1067. if ($('#CheckboxUserBackend').is(':checked')) {
  1068. $("#userlist .userBackend").show();
  1069. if (OC.isUserAdmin()) {
  1070. OCP.AppConfig.setValue('core', 'umgmt_show_backend', 'true');
  1071. }
  1072. } else {
  1073. $("#userlist .userBackend").hide();
  1074. if (OC.isUserAdmin()) {
  1075. OCP.AppConfig.setValue('core', 'umgmt_show_backend', 'false');
  1076. }
  1077. }
  1078. });
  1079. if ($('#CheckboxMailOnUserCreate').is(':checked')) {
  1080. $("#newemail").show();
  1081. }
  1082. // Option to display/hide the "E-Mail" input field
  1083. $('#CheckboxMailOnUserCreate').click(function () {
  1084. if ($('#CheckboxMailOnUserCreate').is(':checked')) {
  1085. $("#newemail").show();
  1086. if (OC.isUserAdmin()) {
  1087. OCP.AppConfig.setValue('core', 'umgmt_send_email', 'true');
  1088. }
  1089. } else {
  1090. $("#newemail").hide();
  1091. if (OC.isUserAdmin()) {
  1092. OCP.AppConfig.setValue('core', 'umgmt_send_email', 'false');
  1093. }
  1094. }
  1095. });
  1096. // calculate initial limit of users to load
  1097. var initialUserCountLimit = UserList.initialUsersToLoad,
  1098. containerHeight = $('#app-content').height();
  1099. if (containerHeight > 40) {
  1100. initialUserCountLimit = Math.floor(containerHeight / 40);
  1101. if (initialUserCountLimit < UserList.initialUsersToLoad) {
  1102. initialUserCountLimit = UserList.initialUsersToLoad;
  1103. }
  1104. }
  1105. //realign initialUserCountLimit with usersToLoad as a safeguard
  1106. while ((initialUserCountLimit % UserList.usersToLoad) !== 0) {
  1107. // must be a multiple of this, otherwise LDAP freaks out.
  1108. // FIXME: solve this in LDAP backend in 8.1
  1109. initialUserCountLimit = initialUserCountLimit + 1;
  1110. }
  1111. // trigger loading of users on startup
  1112. UserList.update(UserList.currentGid, initialUserCountLimit);
  1113. _.defer(function () {
  1114. $('#app-content').trigger($.Event('apprendered'));
  1115. });
  1116. });