users.js 32 KB

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