PageRenderTime 58ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/src/main/webapp/scripts/xnat/admin/usersGroups.js

https://bitbucket.org/radiologics/xnat-web-old-v2
JavaScript | 1796 lines | 1431 code | 136 blank | 229 comment | 110 complexity | b4934aaa5810205cac3ee5f7207b75ec MD5 | raw file
  1. /*
  2. * web: usersGroups.js
  3. * XNAT http://www.xnat.org
  4. * Copyright (c) 2005-2017, Washington University School of Medicine and Howard Hughes Medical Institute
  5. * All Rights Reserved
  6. *
  7. * Released under the Simplified BSD.
  8. */
  9. console.log('usersGroups.js');
  10. var XNAT = getObject(XNAT);
  11. (function(factory){
  12. if (typeof define === 'function' && define.amd) {
  13. define(factory);
  14. }
  15. else if (typeof exports === 'object') {
  16. module.exports = factory();
  17. }
  18. else {
  19. return factory();
  20. }
  21. }(function(){
  22. var undefined, usersGroups, newUser = '',
  23. BASE_URL = '/xapi/users',
  24. passwordComplexity =
  25. XNAT.data.siteConfig.passwordComplexity
  26. .replace(/^\^*/, '^')
  27. .replace(/\$*$/, '$'),
  28. passwordComplexityMessage = XNAT.data.siteConfig.passwordComplexityMessage,
  29. activeUsers = XNAT.data['/xapi/users/active'];
  30. var userTableContainer = 'div#user-table-container';
  31. XNAT.admin = getObject(XNAT.admin || {});
  32. XNAT.admin.usersGroups = usersGroups =
  33. getObject(XNAT.admin.usersGroups || {});
  34. XNAT.usersGroups = extend(true, usersGroups, XNAT.usersGroups);
  35. var userAdminPage = (XNAT.page && XNAT.page.userAdminPage) || false;
  36. // usersGroups.showAdvanced = true;
  37. usersGroups.showAdvanced = userAdminPage;
  38. usersGroups.adminControls = userAdminPage;
  39. // return a url for doing a rest call
  40. function setUrl(part1, part2, part3){
  41. var fullUrl = BASE_URL +
  42. (part1 ? ('/' + part1) : '') +
  43. (part2 ? ('/' + part2) : '') +
  44. (part3 ? ('/' + part3) : '');
  45. return XNAT.url.rootUrl(fullUrl)
  46. }
  47. usersGroups.setUrl = setUrl;
  48. // create urls based on minimal input
  49. function usersUrl(user){
  50. // resolve url arguments
  51. function urlArgs(username, path, param){
  52. username = username || user || '';
  53. param = param || username || '';
  54. username = (username||'') === param ? user : username;
  55. return setUrl(username, path, param)
  56. }
  57. return {
  58. allUsers: function(){
  59. return setUrl()
  60. },
  61. userProfile: function(username){
  62. return setUrl(username||user)
  63. },
  64. allUserProfiles: function(){
  65. return setUrl('profiles')
  66. },
  67. activeUser: function(username){
  68. return setUrl('active', username||user)
  69. },
  70. allActiveUsers: function(){
  71. return setUrl('active')
  72. },
  73. enabledUser: function(username, flag){
  74. return urlArgs(username, 'enabled', flag)
  75. },
  76. // XNAT.admin.usersGroups.url('bob').groups()
  77. // XNAT.admin.usersGroups.url().groups('bob')
  78. // --> '/xapi/users/bob/groups'
  79. groups: function(username, group){
  80. return urlArgs(username, 'groups', group)
  81. },
  82. roles: function(username, role){
  83. return urlArgs(username, 'roles', role)
  84. },
  85. verified: function(username, flag){
  86. return urlArgs(username, 'verified', flag)
  87. }
  88. }
  89. }
  90. usersGroups.url = usersUrl;
  91. function xapiUsers(method, url, opts){
  92. method = method || 'get';
  93. var methodGET = /^get$/i.test(method);
  94. opts = cloneObject(opts);
  95. opts.url = opts.url || url;
  96. if (methodGET){
  97. // the 'restuUrl' method adds a cache-busting parameter
  98. opts.url = XNAT.url.restUrl(opts.url);
  99. }
  100. else {
  101. opts.url = XNAT.url.rootUrl(opts.url);
  102. }
  103. return XNAT.xhr[method](opts).done(function(data){
  104. // cache returned 'get' data at URL
  105. if (methodGET) XNAT.data[url] = data;
  106. });
  107. }
  108. // get or set data via REST
  109. usersGroups.userData = function(user){
  110. var methods = {
  111. // generic method for any kind of request
  112. request: function(method, path, data, opts){
  113. path = [].concat(path);
  114. opts = getObject(opts);
  115. opts.data = data || opts.data || '';
  116. return xapiUsers(method, setUrl.apply(null, path), opts)
  117. },
  118. userList: function(opts){
  119. return xapiUsers('get', setUrl(), opts);
  120. },
  121. allProfiles: function(opts){
  122. return xapiUsers('get', setUrl('profiles'), opts);
  123. },
  124. // XNAT.usersGroups.userData('bob').profile().done(someCallbackFunction)
  125. getProfile: function(opts){
  126. return xapiUsers('get', setUrl('profile/' + user), opts);
  127. },
  128. createProfile: function(data, opts){
  129. opts = getObject(opts);
  130. opts.data = data || opts.data || '';
  131. return xapiUsers('postJSON', setUrl(user), opts)
  132. },
  133. updateProfile: function(data, opts){
  134. opts = getObject(opts);
  135. opts.data = data || opts.data || '';
  136. return xapiUsers('putJSON', setUrl(user), opts)
  137. },
  138. activeUsers: function(opts){
  139. return xapiUsers('get', setUrl('active'), opts)
  140. }
  141. };
  142. methods.allUsers = methods.userList;
  143. methods.usernames = methods.userList;
  144. methods.profiles = methods.allProfiles;
  145. methods.userProfiles = methods.allProfiles;
  146. methods.allUserProfiles = methods.allProfiles;
  147. // decide whether to GET or POST
  148. // based on presence of 'data' argument
  149. methods.profile = function(data, opts){
  150. var method = 'createProfile';
  151. if (!data && !opts) {
  152. method = 'getProfile'
  153. }
  154. return methods[method](data, opts);
  155. };
  156. methods.createUser = methods.createProfile;
  157. methods.updateUser = methods.updateProfile;
  158. return methods;
  159. };
  160. // RETURNS A DOM ELEMENT
  161. usersGroups.userSwitchElement = function(username, type, status){
  162. return XNAT.ui.input.switchbox({
  163. value: firstDefined(status, '') + '',
  164. element: {
  165. name: type,
  166. className: 'user-' + type,
  167. checked: status,
  168. title: username + ':' + type
  169. },
  170. onText: 'Yes',
  171. offText: 'No'
  172. }).get();
  173. };
  174. // use this to spawn the user account form separately
  175. function renderUserAccountForm(data, container){
  176. return XNAT.spawner.spawn({
  177. userAccountForm: userAccountForm(data)
  178. }).render(container)
  179. }
  180. function saveUserData(form, opts){
  181. var $form = $$(form);
  182. // var username = $form.find('input#username').val();
  183. opts = cloneObject(opts);
  184. opts.cache = false;
  185. var successMsg = opts.msg || 'User info saved.';
  186. var doSubmit = $form.submitJSON(opts);
  187. if (doSubmit.done) {
  188. doSubmit.done(function(){
  189. // xmodal.loading.open();
  190. XNAT.ui.banner.top(2000, successMsg, 'success')
  191. });
  192. }
  193. if (doSubmit.fail) {
  194. doSubmit.fail(function(e){
  195. XNAT.ui.dialog.alert('' +
  196. 'An error occurred saving user data.' +
  197. '<br>' +
  198. '<div class="error">' + e + '</div>' +
  199. '');
  200. });
  201. }
  202. return doSubmit;
  203. }
  204. // define the user properties dialog
  205. function editUserDialog(data, onclose){
  206. if (data && !data.username) {
  207. return XNAT.dialog.message('Error', 'An error occurred displaying user data.');
  208. }
  209. // define the <form> Spawner element
  210. function userForm(){
  211. return {
  212. userForm: {
  213. tag: 'div.user-account-info',
  214. //kind: 'panel.multiForm',
  215. //classes: 'user-details',
  216. // label: 'User Details',
  217. //header: false,
  218. //footer: false,
  219. contents: {
  220. userAccountForm: userAccountForm(data)//,
  221. //userProjects: userProjects(),
  222. //userSecurity: userSecurity()
  223. }
  224. }
  225. }
  226. }
  227. // TODO: replace old 'advanced' project settings with this
  228. function userProjects(){
  229. return {
  230. kind: 'panel.form',
  231. title: 'Project Membership & Roles',
  232. contents: {
  233. projectMembership: {
  234. tag: 'div.project-membership',
  235. content: '<i>project membership and role menus go here</i>'
  236. }
  237. }
  238. }
  239. }
  240. // TODO: replace old 'advanced' security settings with this
  241. function userSecurity(){
  242. return {
  243. kind: 'panel.form',
  244. name: 'securitySettings',
  245. title: 'Security Settings',
  246. // action: '#!',
  247. // action: '/xapi/users/' + data.username + '/roles',
  248. // action: '/data/user/' + data.username + '/roles',
  249. // _action: '/app/action/ModifyUserGroups',
  250. contents: {
  251. systemRolesSubhead: {
  252. kind: 'panel.subhead',
  253. label: 'System Roles'
  254. },
  255. csrfToken: {
  256. kind: 'input.hidden',
  257. name: 'XNAT_CSRF',
  258. value: csrfToken
  259. },
  260. siteManager: {
  261. kind: 'panel.input.switchbox',
  262. label: 'Site Manager',
  263. id: 'custom-role-administrator',
  264. name: 'custom_role',
  265. value: 'Administrator',
  266. element: {
  267. checked: (data.roles.indexOf('Administrator') > -1)
  268. },
  269. description: '<p>This allows users to access the Administrative pages of the web interface.</p>' +
  270. '<div class="warning">' +
  271. '<b>WARNING:</b> Granting administrative privileges allows this user great power ' +
  272. 'over the entire site.' +
  273. '</div>'
  274. },
  275. nonExpiring: {
  276. kind: 'panel.input.switchbox',
  277. label: 'Non-Expiring',
  278. id: 'custom-role-non-expiring',
  279. name: 'custom_role',
  280. value: 'non_expiring',
  281. element: {
  282. checked: (data.roles.indexOf('non_expiring') > -1)
  283. },
  284. description: '<p>This prevents this accounts password from expiring.</p>' +
  285. '<div class="warning">' +
  286. '<b>WARNING:</b> Granting a user account a non-expiring password is a security risk ' +
  287. 'and should be limited to accounts that perform automated system tasks. In addition, ' +
  288. 'if any users are designated as non-expiring access to the user list should be ' +
  289. 'restricted to administrators.' +
  290. '</div>'
  291. },
  292. allDataSubhead: {
  293. kind: 'panel.subhead',
  294. label: 'Allow All Data Access'
  295. },
  296. allDataRadios: {
  297. tag: 'div.all-data-radios',
  298. contents: {
  299. noRadio: {
  300. kind: 'input.radio',
  301. id: 'data_none',
  302. name: 'xdat:user.groups.groupID[0].groupID',
  303. label: 'No',
  304. value: 'NULL'//,
  305. // afterElement: 'No'
  306. },
  307. readOnlyRadio: {
  308. kind: 'input.radio',
  309. id: 'data_access',
  310. name: 'xdat:user.groups.groupID[0].groupID',
  311. label: 'Read Only',
  312. value: 'ALL_DATA_ACCESS'
  313. },
  314. readEditDeleteRadio: {
  315. kind: 'input.radio',
  316. id: 'data_admin',
  317. name: 'xdat:user.groups.groupID[0].groupID',
  318. label: 'Read, Edit, Delete',
  319. value: 'ALL_DATA_ADMIN'
  320. }
  321. }
  322. },
  323. allDataWarning: {
  324. tag: 'div.warning',
  325. content: 'WARNING: Allowing full access to data will allow this user to see ALL data ' +
  326. 'stored in this system. It supersedes project membership. Most accounts on your server ' +
  327. 'should NOT have All Data Access allowed.'
  328. }
  329. }
  330. }
  331. }
  332. var updated = false;
  333. return XNAT.dialog.open({
  334. width: 600,
  335. // height: 600,
  336. speed: 200,
  337. esc: true,
  338. enter: false,
  339. title: 'User Properties for <b>' + data.username + '</b>',
  340. content: XNAT.spawner.spawn(userForm()).get(),
  341. buttons: [
  342. {
  343. label: 'Save',
  344. close: false,
  345. isDefault: true,
  346. action: function(obj){
  347. var $form = obj.$modal.find('form');
  348. if (XNAT.validate.form($form)){
  349. var doSave = saveUserData($form);
  350. doSave.done(function(){
  351. updated = true;
  352. obj.close();
  353. });
  354. }
  355. }
  356. },
  357. {
  358. label: 'Cancel',
  359. close: true
  360. }
  361. ],
  362. onClose: function(obj){
  363. if (typeof onclose === 'function') {
  364. onclose(obj)
  365. }
  366. if (updated) {
  367. updateUserData(data.username);
  368. // renderUsersTable();
  369. }
  370. }
  371. })
  372. }
  373. // get user data and return AJAX promise object
  374. function getUserData(username){
  375. var _url = XNAT.url.restUrl('/xapi/users/profile/' + username);
  376. delete XNAT.data['/xapi/users/profile/' + username];
  377. delete XNAT.data['/xapi/users/profiles'];
  378. delete XNAT.data['/xapi/users/current'];
  379. delete XNAT.data['/xapi/users/' + username];
  380. return XNAT.xhr.get(_url)
  381. }
  382. // get user roles and return AJAX promise object
  383. function getUserRoles(username){
  384. var _url = XNAT.url.restUrl('/xapi/users/' + username + '/roles');
  385. return XNAT.xhr.get(_url);
  386. }
  387. function getActiveUsers(success, failure){
  388. // console.log(getActiveUsers.name);
  389. return XNAT.xhr.get({
  390. url: XNAT.url.restUrl('/xapi/users/active'),
  391. success: function(data){
  392. XNAT.data['/xapi/users/active'] = data;
  393. if (isFunction(success)) {
  394. success.apply(this, arguments);
  395. }
  396. },
  397. failure: failure
  398. })
  399. }
  400. function showUsersTable(){
  401. // console.log('showUsersTable');
  402. // $$($table).removeClass('hidden');
  403. // $$($table).show();
  404. }
  405. usersGroups.showUsersTable = showUsersTable;
  406. // render or update the users table
  407. function renderUsersTable(container, url){
  408. // console.log('renderUsersTable');
  409. var $container = container ? $$(container) : $(userTableContainer);
  410. if ($container.length) {
  411. setTimeout(function(){
  412. $container.html('loading...');
  413. setTimeout(function(){
  414. XNAT.dialog.loadingBar.show();
  415. XNAT.spawner.spawn({
  416. usersTable: usersGroups.spawnUsersTable(url)
  417. }).render($container.empty(), function(){
  418. XNAT.dialog.loadingBar.hide();
  419. });
  420. }, 10);
  421. }, 1);
  422. // return _usersTable;
  423. }
  424. }
  425. usersGroups.renderUsersTable = renderUsersTable;
  426. // TODO: re-render *only* the table rows, not the whole table
  427. function updateUsersTable(refresh, all){
  428. var URL = all ? '/xapi/users/profiles' : '/xapi/users/current';
  429. if (!refresh && XNAT.data[URL]) {
  430. getActiveUsers(function(){
  431. renderUsersTable();
  432. });
  433. return {
  434. done: function(callback){
  435. callback.call()
  436. }
  437. }
  438. }
  439. return XNAT.xhr.get({
  440. url: XNAT.url.restUrl(URL),
  441. success: function(data){
  442. XNAT.data[URL] = data;
  443. // console.log(data);
  444. getActiveUsers(function(){
  445. renderUsersTable();
  446. });
  447. }
  448. })
  449. }
  450. usersGroups.updateUsersTable = updateUsersTable;
  451. // get profile data for ALL users
  452. function getUserProfiles(success, failure){
  453. return XNAT.xhr.get({
  454. url: XNAT.url.restUrl('/xapi/users/profiles'),
  455. success: function(data){
  456. XNAT.data['/xapi/users/profiles'] = data;
  457. if (isFunction(success)) {
  458. success.apply(this, arguments);
  459. }
  460. },
  461. failure: failure
  462. })
  463. }
  464. usersGroups.getUserProfiles = getUserProfiles;
  465. // get profile data for users considered 'current'
  466. function getCurrentUsers(success, failure){
  467. return XNAT.xhr.get({
  468. url: XNAT.url.restUrl('/xapi/users/current'),
  469. success: function(data){
  470. XNAT.data['/xapi/users/current'] = data;
  471. if (isFunction(success)) {
  472. success.apply(this, arguments);
  473. }
  474. },
  475. failure: failure
  476. })
  477. }
  478. usersGroups.getCurrentUsers = getCurrentUsers;
  479. // renders cells for 'verified' and 'enabled' status
  480. function userStatusInfo(type, off){
  481. var username = escapeHtml(this.username || '');
  482. var status = realValue(this[type]);
  483. var SORTER = status ? 1 : 0;
  484. var iconClass = status ? '.fa-check' : '';
  485. return spawn('!', [
  486. ['i.hidden.sorting.filtering.' + type, SORTER+''],
  487. ['a.user-' + type + '-status.edit-user', {
  488. title: username + ': ' + (status ? type : off),
  489. href: '#!',
  490. style: { display: 'block', padding: '2px' }
  491. }, [['i.fa' + iconClass ]]]
  492. ]);
  493. }
  494. // renders cells for 'active' users column
  495. function activeUserInfo(){
  496. var username = escapeHtml(this.username || '');
  497. var sessionCount = 0;
  498. activeUsers = XNAT.data['/xapi/users/active'];
  499. if (username && activeUsers && activeUsers[username] && activeUsers[username].sessions.length) {
  500. sessionCount = activeUsers[username].sessions.length
  501. }
  502. if (sessionCount) {
  503. return spawn('!', [
  504. ['i.hidden.sorting', zeroPad(sessionCount, 6)],
  505. ['a.active-user', {
  506. title: username + ': kill ' + sessionCount + ' active session(s)',
  507. href: '#!',
  508. style: { display: 'block', padding: '2px' }
  509. }, [['i.fa.fa-user-circle']]]
  510. ])
  511. }
  512. }
  513. // renders cells for last login column
  514. function lastLoginInfo(value){
  515. value = realValue(value) || 0;
  516. return spawn('!', [
  517. ['i.last-login.hidden.sorting', (9999999999999-value || '9999999999999')+''],
  518. ['input.hidden.last-login.timestamp.filtering|type=hidden', { value: value }],
  519. ['span.date-time', (value ? (new Date(value)).toLocaleString() : '')]
  520. ])
  521. }
  522. // set unique id attribute for <tr> elements
  523. // by combining username and user id
  524. function setRowId(profile){
  525. return profile.username + '-' + profile.id;
  526. }
  527. // update ONLY the row of the edited user
  528. function updateUserRow(profile){
  529. var rowId = setRowId(profile);
  530. var $row = $(document.getElementById(rowId));
  531. // full name column
  532. $row.find('a.full-name')
  533. .html(escapeHtml(profile.lastName + ', ' + profile.firstName));
  534. // email column
  535. $row.find('a.send-email')
  536. .attr('title', profile.email + ': send email')
  537. .html(escapeHtml(profile.email));
  538. // verified status column
  539. $row.find('td.verified')
  540. .empty()
  541. .append(userStatusInfo.call(profile, 'verified', 'unverified'));
  542. // enabled status column
  543. $row.find('td.enabled')
  544. .empty()
  545. .append(userStatusInfo.call(profile, 'enabled', 'disabled'));
  546. // active status column
  547. $row.find('td.active')
  548. .empty()
  549. .append(activeUserInfo.call(profile));
  550. // last login column
  551. $row.find('td.last-login')
  552. .empty()
  553. .append(lastLoginInfo(profile.lastSuccessfulLogin));
  554. }
  555. function updateUserData(username, delay){
  556. var USERNAME = typeof username === 'string' ? username : this.title.split(':')[0];
  557. var USER_URL = USERNAME === 'profiles' ? '/xapi/users/profiles' : '/xapi/users/profile/' + USERNAME;
  558. return XNAT.xhr.get({
  559. url: XNAT.url.restUrl(USER_URL),
  560. success: function(DATA){
  561. // XNAT.data[USER_URL] = DATA;
  562. if (userAdminPage) {
  563. window.setTimeout(function(){
  564. getActiveUsers().done(function(ACTIVE){
  565. XNAT.data['/xapi/users/active'] = ACTIVE;
  566. forEach([].concat(DATA), function(profile, i){
  567. updateUserRow(profile);
  568. });
  569. })
  570. }, delay||100);
  571. }
  572. }
  573. })
  574. }
  575. usersGroups.changePasswordDialog = function(data){
  576. function changePasswordForm(){
  577. var form = {
  578. kind: 'panel.form',
  579. name: 'changeUserPasswordForm',
  580. id: 'change-user-password-form',
  581. label: 'Change Password',
  582. footer: false,
  583. validate: true,
  584. method: 'PUT',
  585. contentType: 'json',
  586. refresh: false,
  587. reload: true,
  588. action: '~/xapi/users/' + data.username,
  589. element: {
  590. autocomplete: 'off'
  591. },
  592. contents: {
  593. username: {
  594. kind: 'panel.input.hidden',
  595. value: data.username
  596. },
  597. password: {
  598. kind: 'panel.input.password',
  599. label: 'Password',
  600. element: {
  601. placeholder: '*****',
  602. autocomplete: 'off',
  603. data: { message: passwordComplexityMessage }
  604. },
  605. validate: 'required pattern:'+passwordComplexity+' max-length:255'
  606. },
  607. confirmPassword: {
  608. kind: 'panel.input.password',
  609. label: 'Confirm Password',
  610. element: {
  611. placeholder: '*****',
  612. autocomplete: 'off',
  613. data: { message: 'Password fields must match' }
  614. },
  615. validate: 'matches:[name=password]'
  616. }
  617. }
  618. };
  619. return form;
  620. }
  621. var cpForm$ = null;
  622. var updated = false;
  623. var formContainer$ = null;
  624. XNAT.dialog.open({
  625. title: 'Change Password for '+data.username,
  626. width: 500,
  627. content: '<div id="change-password-form"></div>',
  628. beforeShow: function(obj){
  629. formContainer$ = obj.dialog$.find('#change-password-form');
  630. XNAT.spawner.spawn({
  631. changePasswordForm: changePasswordForm()
  632. }).render(formContainer$);
  633. },
  634. afterShow: function(){
  635. cpForm$ = formContainer$.find('form');
  636. },
  637. buttons: [
  638. {
  639. label: 'Update Password',
  640. close: false,
  641. isDefault: true,
  642. action: function(obj){
  643. var doSave = saveUserData(cpForm$, { msg: 'User password updated successfully' });
  644. doSave.done(function(){
  645. updated = true;
  646. obj.close();
  647. });
  648. }
  649. },
  650. {
  651. label: 'Cancel',
  652. close: true
  653. }
  654. ]
  655. });
  656. };
  657. function userAccountForm(data){
  658. data = data || {};
  659. var usernameEsc = data.username = escapeHtml(data.username || '');
  660. var _load = data ? XNAT.url.restUrl('/xapi/users/profile/' + data.username) : false;
  661. var doEdit = _load && data.username;
  662. // username could be text or input element
  663. function usernameField(username){
  664. var obj = {
  665. label: 'Username'
  666. };
  667. if (data && data.username) {
  668. obj.kind = 'panel.element';
  669. obj.contents = {
  670. usernameText: {
  671. kind: 'html',
  672. content: usernameEsc
  673. },
  674. usernameInput: {
  675. kind: 'input.hidden',
  676. name: 'username',
  677. value: data.username || ''
  678. }
  679. };
  680. }
  681. else {
  682. obj.kind = 'panel.input.text';
  683. obj.name = 'username';
  684. obj.validate = 'username required';
  685. obj.value = '';
  686. }
  687. return obj;
  688. }
  689. function passwordField(){
  690. var obj = {
  691. label: 'Password'
  692. };
  693. if (data && data.username) {
  694. obj.kind = 'panel.element';
  695. obj.contents = {
  696. changePasswordLink: {
  697. kind: 'html',
  698. content:
  699. '<a href="#!" class="change-password" style="display: inline-block; margin: -4px 0 4px;" data-username="'+data.username+'">' +
  700. '<button class="btn btn-sm">Change User Password</button></a>'
  701. }
  702. }
  703. }
  704. else {
  705. obj.kind = 'panel.input.password';
  706. obj.element = {
  707. placeholder: '*****',
  708. autocomplete: 'off',
  709. data: { message: passwordComplexityMessage }
  710. };
  711. obj.validate = 'allow-empty pattern:'+passwordComplexity+' max-length:255'
  712. }
  713. return obj;
  714. }
  715. function confirmPasswordField(){
  716. if (data && data.username) {
  717. return false;
  718. }
  719. else {
  720. return {
  721. kind: 'panel.input.password',
  722. label: 'Confirm Password',
  723. element: {
  724. placeholder: '*****',
  725. autocomplete: 'off',
  726. data: { message: 'Password fields must match' }
  727. },
  728. validate: 'matches:[name=password]'
  729. }
  730. }
  731. }
  732. var userVerified = data.verified || 'false';
  733. var userEnabled = data.enabled || 'false';
  734. var form = {
  735. kind: 'panel.form',
  736. name: 'userAccountForm',
  737. id: 'user-account-form',
  738. label: 'Account Information',
  739. footer: false,
  740. validate: true,
  741. method: doEdit ? 'PUT' : 'POST',
  742. contentType: 'json',
  743. load: doEdit ? '$? *' + _load : '',
  744. refresh: true,
  745. reload: true,
  746. action: '~/xapi/users' + (doEdit ? ('/' + data.username) : ''),
  747. element: {
  748. autocomplete: 'off'
  749. },
  750. contents: {
  751. // details: {
  752. // kind: 'panel.subhead',
  753. // label: 'User Details'
  754. // },
  755. // id: {
  756. // kind: 'panel.input.hidden',
  757. // validate: _load ? 'number required' : 'allow-empty',
  758. // value: data.id || ''
  759. // },
  760. // pad: {
  761. // html: '<br>'
  762. // },
  763. usernameField: usernameField(),
  764. password: passwordField(),
  765. confirmPassword: confirmPasswordField(),
  766. firstName: {
  767. kind: 'panel.input.text',
  768. label: 'First Name',
  769. validate: 'name-safe required',
  770. value: data.firstName || ''
  771. },
  772. lastName: {
  773. kind: 'panel.input.text',
  774. label: 'Last Name',
  775. validate: 'name-safe required',
  776. value: data.lastName || ''
  777. },
  778. email: {
  779. kind: 'panel.input.email',
  780. label: 'Email',
  781. validate: 'email required',
  782. value: data.email || ''
  783. }
  784. }
  785. };
  786. // only show admin controls if this form is being shown from within the Admin Users UI
  787. if (usersGroups.adminControls) {
  788. form.contents.verified = {
  789. kind: 'panel.input.switchbox',
  790. label: 'Verified',
  791. options: 'true|false',
  792. value: userVerified,
  793. // checked: !/false/i.test(userVerified)//,
  794. element: {
  795. // disabled: !!_load,
  796. checked: !/false/i.test(userVerified)//,
  797. // title: username + ':verified'//,
  798. // on: { click: _load ? setVerified : diddly }
  799. }
  800. };
  801. form.contents.enabled = {
  802. kind: 'panel.input.switchbox',
  803. label: 'Enabled',
  804. options: 'true|false',
  805. value: userEnabled,
  806. // checked: !/false/i.test(userEnabled)//,
  807. element: {
  808. //disabled: !!_load,
  809. checked: !/false/i.test(userEnabled)//,
  810. //title: username + ':enabled'//,
  811. //on: { click: _load ? setEnabled : diddly }
  812. }
  813. }
  814. }
  815. // add 'Advanced Settings' when editing existing user
  816. if (doEdit && usersGroups.showAdvanced) {
  817. form.contents.advancedSettings = {
  818. kind: 'panel.element',
  819. label: 'Advanced',
  820. contents: {
  821. advancedLink: {
  822. tag: 'a.edit-advanced-settings.link',
  823. element: {
  824. href: '#!',
  825. title: data.username + ':advanced',
  826. on: {
  827. click: function(e){
  828. e.preventDefault();
  829. var modalId = $(this).closest('div.xnat-dialog').attr('data-dialog');
  830. XNAT.dialog.getDialog(modalId).close();
  831. window.top.usernameForUserThatIsCurrentlyBeingEdited = data.username;
  832. userProjectsAndSecurity(e, data.username);
  833. }
  834. }
  835. },
  836. content: 'Edit Advanced Settings'
  837. },
  838. description: {
  839. tag: 'div.description',
  840. content: "Edit this user's project and security settings."
  841. }
  842. }
  843. }
  844. }
  845. // Add a submit button if this form is being displayed outside the Admin Users UI
  846. if (doEdit && !usersGroups.adminControls) {
  847. form.contents.submitButton = {
  848. kind: 'panel.element',
  849. contents: {
  850. submitUserFormButton: {
  851. kind: 'html',
  852. content: '<button class="btn primary" id="save-user-profile">Update Profile</button>'
  853. }
  854. }
  855. }
  856. }
  857. usersGroups.showAdvanced = true;
  858. return form;
  859. }
  860. usersGroups.userAccountForm = userAccountForm;
  861. // open a separate dialog to edit user password
  862. $(document).on('click','.change-password',function(){
  863. var username = $(this).data('username');
  864. usersGroups.changePasswordDialog({ username: username });
  865. });
  866. // external user profile form validation and submission
  867. $(document).on('click','#save-user-profile',function(){
  868. var $form = $(this).parents('form'), updated;
  869. if (XNAT.validate.form($form)){
  870. var doSave = saveUserData($form);
  871. doSave.done(function(){
  872. updated = true;
  873. });
  874. }
  875. });
  876. // open a dialog to edit user properties
  877. function editUser(e, onclose){
  878. e.preventDefault();
  879. var username =
  880. (this.title || '').split(':')[0] ||
  881. $(this).data('username') ||
  882. $(this).closest('tr').data('username') ||
  883. (this.innerText || '').trim();
  884. window.top.usernameForUserThatIsCurrentlyBeingEdited = username;
  885. getUserData(username).done(function(data){
  886. getUserRoles(username).done(function(roles){
  887. data.roles = roles;
  888. // save the data to namespaced object before opening dialog
  889. XNAT.data['/xapi/users/profile/' + username] = data;
  890. editUserDialog(data, onclose);
  891. })
  892. });
  893. }
  894. usersGroups.editUser = editUser;
  895. // immediately toggles user's "Verified" status
  896. function setVerified(e, username, flag){
  897. username = username || this.title.split(':')[0];
  898. flag = flag || this.checked;
  899. return XNAT.xhr.put({
  900. url: XNAT.url.rootUrl('/xapi/users/' + username + '/verified/' + flag),
  901. success: function(){
  902. XNAT.ui.banner.top(2000, 'User has been set to ' + (flag ? '"verified"' : '"unverified"') + '.', 'success')
  903. },
  904. error: function(){
  905. XNAT.ui.banner.top(3000, 'An error occurred setting "verified" status.', 'error')
  906. }
  907. })
  908. }
  909. usersGroups.setVerified = setVerified;
  910. // immediately toggles user's "Enabled" status
  911. function setEnabled(e, username, flag){
  912. username = username || this.title.split(':')[0];
  913. flag = flag || this.checked;
  914. return XNAT.xhr.put({
  915. url: XNAT.url.rootUrl('/xapi/users/' + username + '/enabled/' + flag),
  916. success: function(){
  917. XNAT.ui.banner.top(2000, 'User status has been set to ' + (flag ? '"enabled"' : '"disabled"') + '.', 'success')
  918. },
  919. error: function(){
  920. XNAT.ui.banner.top(3000, 'An error occurred setting "enabled" status.', 'error')
  921. }
  922. })
  923. }
  924. usersGroups.setEnabled = setEnabled;
  925. function userProjectsAndSecurity(e, usr){
  926. var _username = usr || $(this).data('username');
  927. var _url = XNAT.url.rootUrl('/app/action/DisplayItemAction/search_value/' + _username + '/search_element/xdat:user/search_field/xdat:user.login/popup/true');
  928. return xmodal.iframe({
  929. src: _url,
  930. name: 'advanced-user-settings',
  931. width: 800,
  932. height: '80%',
  933. title: 'Edit User Info',
  934. // footer: false,
  935. okLabel: 'Close',
  936. cancel: false,
  937. beforeShow: function(){
  938. XNAT.dialog.close(XNAT.dialog.topUID, true);
  939. },
  940. onClose: function(){
  941. var editedUser = window.top.usernameForUserThatIsCurrentlyBeingEdited;
  942. if (!window.top.XNAT.page.userAdminPage) {
  943. return false;
  944. }
  945. if (editedUser){
  946. usersGroups.userData(editedUser).getProfile().done(function(profileData){
  947. updateUserRow(profileData)
  948. });
  949. }
  950. else {
  951. updateUsersTable(true);
  952. }
  953. }
  954. })
  955. }
  956. // open a dialog for creating a new user
  957. function newUserDialog(){
  958. var updated = false;
  959. var formContainer$ = null;
  960. return XNAT.dialog.open({
  961. width: 600,
  962. // height: 500,
  963. speed: 200,
  964. title: 'Create New User',
  965. content: (function(){
  966. formContainer$ = $.spawn('div.new-user-form');
  967. renderUserAccountForm(null, formContainer$);
  968. return formContainer$[0];
  969. })(),
  970. buttons: [
  971. {
  972. label: 'Save',
  973. close: false,
  974. isDefault: true,
  975. action: function(obj){
  976. // userAccountForm
  977. var userForm$ = formContainer$.find('form');
  978. var _username = userForm$.find('input[name="username"]').val();
  979. // make sure new username is not a duplicate
  980. var getUserList = usersGroups.userData().usernames();
  981. getUserList.done(function(users){
  982. if (users.indexOf(_username) > -1) {
  983. XNAT.ui.dialog.alert('The username ' +
  984. '<b>' + _username + '</b> is already in use. ' +
  985. 'Please enter a different username value and ' +
  986. 'try again.');
  987. return false;
  988. }
  989. var doSave = saveUserData(userForm$);
  990. doSave.done(function(){
  991. updated = true;
  992. obj.close();
  993. });
  994. });
  995. }
  996. }
  997. ],
  998. onClose: function(){
  999. // always update the whole table when adding a user.
  1000. // usersGroups.spawnTabs();
  1001. // renderUsersTable();
  1002. if (updated) {
  1003. updateUsersTable(true);
  1004. }
  1005. }
  1006. })
  1007. }
  1008. function setupTabs(){
  1009. // console.log(setupTabs.name);
  1010. function tabs(){
  1011. return {
  1012. kind: 'tabs',
  1013. layout: 'top', // trying a top tabs layout
  1014. contents: {
  1015. usersTab: usersTab(),
  1016. groupsTab: groupsTab()
  1017. }
  1018. }
  1019. }
  1020. function usersTab(){
  1021. return {
  1022. kind: 'tab',
  1023. name: 'users',
  1024. label: 'Users',
  1025. active: true,
  1026. contents: usersTabContents()
  1027. }
  1028. }
  1029. function createUserButton(){
  1030. return {
  1031. tag: 'button#create-new-user',
  1032. element: {
  1033. html: 'Create New User',
  1034. on: {
  1035. click: function(e){
  1036. // console.log('clicked');
  1037. newUserDialog();
  1038. }
  1039. }
  1040. }
  1041. }
  1042. }
  1043. function reloadUserListButton(){
  1044. return {
  1045. tag: 'button#reload-user-list',
  1046. element: {
  1047. html: 'Reload User List',
  1048. style: {
  1049. marginLeft: '20px'
  1050. },
  1051. on: {
  1052. click: function(e){
  1053. // console.log('clicked');
  1054. // updateUserData('profiles', 10);
  1055. // var $container = $('#user-table-container');
  1056. // $container.empty().html('loading...');
  1057. // spawnUsersTable().render($container.empty());
  1058. // usersGroups.spawnTabs();
  1059. setTimeout(function(){
  1060. XNAT.dialog.loadingBar.show();
  1061. updateUsersTable(true);
  1062. }, 1);
  1063. }
  1064. }
  1065. }
  1066. }
  1067. }
  1068. function loadAllUsersButton(){
  1069. return {
  1070. tag: 'button#load-all-users',
  1071. element: {
  1072. html: 'Load All Users',
  1073. style: {
  1074. marginLeft: '10px'
  1075. },
  1076. on: {
  1077. click: function(e){
  1078. setTimeout(function(){
  1079. XNAT.dialog.loadingBar.show();
  1080. renderUsersTable($(userTableContainer), '/xapi/users/profiles');
  1081. }, 1);
  1082. }
  1083. }
  1084. }
  1085. }
  1086. }
  1087. function allUsersButtonInfoText(){
  1088. return {
  1089. tag: 'span.tip.shadowed',
  1090. element: {
  1091. style: {
  1092. width: '350px',
  1093. left: '-385px',
  1094. top: '-75px',
  1095. // backgroundColor: '#ffc',
  1096. zIndex: 10000
  1097. }
  1098. },
  1099. content: [
  1100. 'By default, the table below only shows "current" users: users that are <b>enabled</b>, or who ' +
  1101. 'may be <b>disabled</b> but have logged in during the past year, or have an unused account created less ' +
  1102. 'than a year ago.' +
  1103. '<br><br>' +
  1104. 'Click the <b>"Load All Users"</b> button to view <i>all</i> users that have accounts on this system, ' +
  1105. 'even if their account is not "current."'
  1106. ],
  1107. filler: null
  1108. }
  1109. }
  1110. // var alternateInfoText = '' +
  1111. //
  1112. // 'By default, the table below only shows "current" users: users that are <b>enabled</b>, users that have ' +
  1113. // 'logged in during the past year but are <b>disabled</b>, or users with accounts that were ' +
  1114. // 'created created in the past year but never used.' +
  1115. // '<br><br>' +
  1116. // 'Click the <b>"Load All Users"</b> button to view <i>all</i> users that have accounts on this system, ' +
  1117. // 'even if their account is not "current."' +
  1118. //
  1119. // '';
  1120. function allUsersButtonInfo(){
  1121. return {
  1122. tag: 'span.tip_icon',
  1123. element: {
  1124. style: {
  1125. marginRight: '3px',
  1126. left: '2px',
  1127. top: '3px'
  1128. }
  1129. },
  1130. contents: {
  1131. allUsersButtonInfoText: allUsersButtonInfoText()
  1132. }
  1133. }
  1134. }
  1135. function allUsersButtonElements(){
  1136. return {
  1137. tag: 'div',
  1138. element: {
  1139. style: {
  1140. float: 'right'
  1141. }
  1142. },
  1143. contents: {
  1144. allUsersButtonInfo: allUsersButtonInfo(),
  1145. loadAllUsersButton: loadAllUsersButton()
  1146. }
  1147. }
  1148. }
  1149. function selectAllUsers(){
  1150. var $this = $(this);
  1151. var $table = $this.closest('table');
  1152. var $inputs = $table.find('input.select-user');
  1153. if ($this.hasClass('selected')) {
  1154. $inputs.prop('checked', false);
  1155. $this.removeClass('selected');
  1156. }
  1157. else {
  1158. $inputs.prop('checked', true);
  1159. $this.addClass('selected');
  1160. }
  1161. }
  1162. function usersTabContents(){
  1163. return {
  1164. tag: 'div.manage-users',
  1165. contents: {
  1166. style: {
  1167. tag: 'style',
  1168. content: '#user-profiles .new-user { background: #ffc }'
  1169. },
  1170. // actions: {
  1171. // kind: 'panel',
  1172. // label: 'Actions',
  1173. // footer: false,
  1174. // contents: {
  1175. // viewActiveUsers: {
  1176. // tag: 'button#view-active-users.btn1',
  1177. // element: {
  1178. // type: 'button',
  1179. // html: 'View Active Users'
  1180. // }
  1181. // }//,
  1182. // // things: {
  1183. // // kind: 'html',
  1184. // // content: '' +
  1185. // // '<div class="active-users">' +
  1186. // // '<button type="button" id="view-active-users" class="btn1">View Active Users</button>' +
  1187. // // '</div>'//,
  1188. // // element: spawn('div.active-users', {
  1189. // // style: { marginBottom: '20px' }
  1190. // // }, [['a#view-active-users|href=#!', 'View Active Users']])
  1191. // // }
  1192. // }
  1193. // },
  1194. userActions: {
  1195. tag: 'div.user-actions',
  1196. element: {
  1197. style: {
  1198. position: 'relative',
  1199. marginBottom: '30px',
  1200. paddingTop: '30px',
  1201. borderTop: '1px solid #c8c8c8'
  1202. }
  1203. },
  1204. contents: {
  1205. createUserButton: createUserButton(),
  1206. reloadUserListButton: reloadUserListButton(),
  1207. allUsersButtonElements: allUsersButtonElements()
  1208. }
  1209. },
  1210. usersTablePanel: {
  1211. tag: 'div#user-accounts',
  1212. contents: {
  1213. tableContainer: {
  1214. tag: userTableContainer,
  1215. contents: {
  1216. usersTable: spawnUsersTable()
  1217. }
  1218. }
  1219. }
  1220. }
  1221. }
  1222. }
  1223. }
  1224. // kill all active sessions for the specified user
  1225. function killActiveSessions(e){
  1226. e.preventDefault();
  1227. var username = this.title.split(':')[0].trim();
  1228. return XNAT.dialog.confirm({
  1229. title: 'Kill Active Sessions?',
  1230. content: 'Kill all active sessions for <b>' + username + '</b>?',
  1231. okClose: false,
  1232. okAction: function(obj){
  1233. XNAT.xhr['delete']({
  1234. url: XNAT.url.rootUrl('/xapi/users/active/' + username),
  1235. success: function(){
  1236. obj.close();
  1237. XNAT.ui.banner.top(2000, 'Sessions closed', 'success');
  1238. // wait a few seconds before updating the row
  1239. updateUserData(username, 2000);
  1240. // updateUsersTable(true);
  1241. }
  1242. })
  1243. }
  1244. });
  1245. }
  1246. // TODO: view active sessions
  1247. function viewSessionInfo(e){
  1248. e.preventDefault();
  1249. var username = this.title;
  1250. }
  1251. function goToEmail(){
  1252. var _email = this.title.split(':')[0];
  1253. var _url = XNAT.url.rootUrl('/app/template/XDATScreen_email.vm/emailTo/');
  1254. window.location.href = _url + _email;
  1255. }
  1256. // set up custom filter menus
  1257. function filterMenuElement(prop){
  1258. if (!prop) return false;
  1259. // call this function in context of the table
  1260. var $userProfilesTable = $(this);
  1261. var FILTERCLASS = 'filter-' + prop;
  1262. // var FILTERCLASS = 'hidden';
  1263. return {
  1264. id: 'user-filter-select-' + prop,
  1265. // style: { width: '100%' },
  1266. on: {
  1267. change: function(){
  1268. var selectedValue = $(this).val();
  1269. // console.log(selectedValue);
  1270. $userProfilesTable.find('i.filtering.'+prop).each(function(){
  1271. var $row = $(this).closest('tr');
  1272. if (selectedValue === 'all') {
  1273. $row.removeClass(FILTERCLASS);
  1274. return;
  1275. }
  1276. $row.addClass(FILTERCLASS);
  1277. if (selectedValue == this.textContent) {
  1278. $row.removeClass(FILTERCLASS);
  1279. }
  1280. // if (!this.checked && selectedValue === notProp) {
  1281. // $row.removeClass(FILTERCLASS);
  1282. // }
  1283. })
  1284. }
  1285. }
  1286. };
  1287. }
  1288. // Spawner element config for the users list table
  1289. function spawnUsersTable(url){
  1290. // console.log(spawnUsersTable.name);
  1291. //var _data = XNAT.xapi.users.profiles || XNAT.data['/xapi/users/profiles'];
  1292. var $dataRows = [];
  1293. // load 'current' users initially
  1294. var URL = XNAT.url.restUrl(url || '/xapi/users/current');
  1295. // TODO:
  1296. // TODO: set min-width as well as max-width
  1297. // TODO:
  1298. var styles = {
  1299. id: (60-24)+'px',
  1300. username: (160-24)+'px',
  1301. name: (280-24) + 'px',
  1302. email: (222-24) + 'px',
  1303. verified: (104-24) + 'px',
  1304. enabled: (96-24) +'px',
  1305. active: (92-24) + 'px',
  1306. login: (118-24) + 'px'
  1307. };
  1308. // var altStyles = {};
  1309. // forOwn(styles, function(name, val){
  1310. // altStyles[name] = (val * 0.8)
  1311. // });
  1312. return {
  1313. kind: 'table.dataTable',
  1314. name: 'userProfiles',
  1315. id: 'user-profiles',
  1316. load: URL,
  1317. before: {
  1318. filterCss: {
  1319. tag: 'style|type=text/css',
  1320. content: '\n' +
  1321. '#user-profiles td.user-id { width: ' + styles.id + '; } \n' +
  1322. '#user-profiles td.username .truncate { width: 120px; } \n' +
  1323. '#user-profiles td.fullName .truncate { width: 180px; } \n' +
  1324. '#user-profiles td.email .truncate { width: 220px; } \n' +
  1325. '#user-profiles td.verified { width: ' + styles.verified + '; } \n' +
  1326. '#user-profiles td.enabled { width: ' + styles.enabled + '; } \n' +
  1327. '#user-profiles td.ACTIVE { width: ' + styles.active + '; } \n' +
  1328. '#user-profiles td.lastSuccessfulLogin { width: ' + styles.login + '; } \n' +
  1329. '#user-profiles tr.filter-verified, \n' +
  1330. '#user-profiles tr.filter-enabled, \n' +
  1331. '#user-profiles tr.filter-active, \n' +
  1332. '#user-profiles tr.filter-login { display: none; } \n' +
  1333. '@media screen and (max-width: 1200px) { \n' +
  1334. ' #user-profiles td.username .truncate { width: 90px; } \n' +
  1335. ' #user-profiles td.fullName .truncate { width: 100px; } \n' +
  1336. ' #user-profiles td.email .truncate { width: 140px; } \n' +
  1337. '} \n' +
  1338. '#user-account-form, #user-account-form-panel { border: none; margin: 0; }'
  1339. }
  1340. },
  1341. onRender: showUsersTable,
  1342. table: {
  1343. classes: 'highlight hidden',
  1344. on: [
  1345. ['click', 'a.user-id', updateUserData],
  1346. ['click', 'a.select-all', selectAllUsers],
  1347. ['click', 'a.edit-user', editUser],
  1348. // ['click', 'a.full-name', userProjectsAndSecurity],
  1349. ['click', 'a.send-email', goToEmail],
  1350. // ['change', 'input.user-verified', setVerified],
  1351. // ['change', 'input.user-enabled', setEnabled],
  1352. ['click', 'a.active-user', killActiveSessions],
  1353. ['click', 'a.session-info', viewSessionInfo]
  1354. ]
  1355. },
  1356. trs: function(tr, data){
  1357. // 'this' is the currently iterating <tr>
  1358. data.userId = setRowId(data);
  1359. tr.id = data.userId;
  1360. addDataAttrs(tr, { filter: '0' });
  1361. },
  1362. sortable: 'username, fullName, email',
  1363. filter: 'fullName, email',
  1364. items: {
  1365. // _id: '~data-id',
  1366. // _username: '~data-username',
  1367. // _select: {
  1368. // label: 'Select',
  1369. // th: { html: '<a href="#!" class="select-all link">Select</a>' },
  1370. // td: { className: 'centered' },
  1371. // apply: function(){
  1372. // return spawn('input.select-user', {
  1373. // type: 'checkbox',
  1374. // checked: false,
  1375. // title: this.username + ':select'
  1376. // });
  1377. // //return '<a href="#!" class="username link">' + this.username + '</a>'
  1378. // }
  1379. // },
  1380. //
  1381. id: {
  1382. label: 'ID',
  1383. sort: true,
  1384. // th: { style: { width: styles.id }},
  1385. td: {
  1386. // style: { width: styles.id },
  1387. className: 'user-id center'
  1388. },
  1389. apply: function(id){
  1390. return spawn('a.user-id.link', {
  1391. href: '#!',
  1392. title: this.username + ': refresh'
  1393. }, [['i.hidden', zeroPad(id, 6)], id]);
  1394. }
  1395. },
  1396. username: {
  1397. label: 'Username',
  1398. filter: true, // add filter: true to individual items to add a filter
  1399. // th: { style: { width: styles.username }},
  1400. // td: { style: { width: styles.username }},
  1401. apply: function(){
  1402. // var _username = truncateText(username);
  1403. return spawn('a.username.link.truncate.edit-user', {
  1404. href: '#!',
  1405. title: this.username + ': details',
  1406. // html: _username,
  1407. html: escapeHtml(this.username)//,
  1408. // style: { width: styles.username },
  1409. // data: { username: username }
  1410. });
  1411. }
  1412. },
  1413. fullName: {
  1414. label: 'Name',
  1415. // th: { style: { width: styles.name }},
  1416. // td: { style: { width: styles.name }},
  1417. apply: function(){
  1418. // var _fullName = truncateText(this.lastName + ', ' + this.firstName);
  1419. return spawn('a.full-name.link.truncate.edit-user', {
  1420. href: '#!',
  1421. title: this.username + ': project and security settings',
  1422. html: escapeHtml(this.lastName + ', ' + this.firstName)//,
  1423. // style: { width: styles.name },
  1424. // data: { username: this.username }
  1425. });
  1426. //return this.lastName + ', ' + this.firstName
  1427. }
  1428. },
  1429. email: {
  1430. label: 'Email',
  1431. // th: { style: { width: styles.email }},
  1432. // td: { style: { width: styles.email }},
  1433. apply: function(){
  1434. return spawn('a.send-email.link.truncate.edit-user', {
  1435. href: '#!',
  1436. title: this.email + ': send email',
  1437. // style: { width: styles.email },
  1438. // title: 'Send email to: ' + email,
  1439. // html: _email
  1440. html: escapeHtml(this.email)
  1441. })
  1442. }
  1443. },
  1444. verified: {
  1445. label: 'Verified',
  1446. // th: { style: { width: styles.verified }},
  1447. td: {
  1448. // style: { width: styles.verified },
  1449. className: 'verified center'
  1450. },
  1451. // custom filter menu
  1452. filter: function(table){
  1453. return spawn('div.center', [XNAT.ui.select.menu({
  1454. value: 'all',
  1455. options: [
  1456. { label: 'All', value: 'all' },
  1457. { label: 'Verified', value: '1' },
  1458. { label: 'Unverified', value: '0' }
  1459. ],
  1460. element: filterMenuElement.call(table, 'verified')
  1461. }).element])
  1462. },
  1463. apply: function(){
  1464. return userStatusInfo.call(this, 'verified', 'unverified');
  1465. }
  1466. },
  1467. enabled: {
  1468. label: 'Enabled',
  1469. // th: { style: { width: styles.enabled }},
  1470. td: {
  1471. // style: { width: styles.enabled },
  1472. className: 'enabled center'
  1473. },
  1474. filter: function(table){
  1475. return spawn('div.center', [XNAT.ui.select.menu({
  1476. value: 'all',
  1477. options: [
  1478. { label: 'All', value: 'all' },
  1479. { label: 'Enabled', value: '1' },
  1480. { label: 'Disabled', value: '0' }
  1481. ],
  1482. element: filterMenuElement.call(table, 'enabled')
  1483. }).element])
  1484. },
  1485. apply: function(){
  1486. return userStatusInfo.call(this, 'enabled', 'disabled');
  1487. }
  1488. },
  1489. // by convention, name 'custom' columns with ALL CAPS
  1490. // 'custom' columns do not correspond directly with
  1491. // a data item
  1492. ACTIVE: {
  1493. label: 'Active',
  1494. sort: true,
  1495. // th: { style: { width: styles.active }},
  1496. td: {
  1497. // style: { width: styles.active },
  1498. className: 'active center mono'
  1499. },
  1500. filter: function(table){
  1501. var $table = $(table);
  1502. return spawn('div.center', [XNAT.ui.select.menu({
  1503. value: 'all',
  1504. options: {
  1505. all: 'All',
  1506. active: 'Active',
  1507. inactive: 'Inactive'
  1508. },
  1509. element: {
  1510. id: 'user-filter-select-active',
  1511. on: {
  1512. change: function(){
  1513. var selectedValue = $(this).val();
  1514. var $rows = $table.find('tbody').find('tr');
  1515. var FILTERCLASS = 'filter-active';
  1516. if (selectedValue === 'all') {
  1517. $rows.removeClass(FILTERCLASS);
  1518. return;
  1519. }
  1520. $rows.addClass(FILTERCLASS);
  1521. $rows.each(function(){
  1522. var $row = $(this);
  1523. var isActive = $row.find('a.active-user').length > 0;
  1524. if (isActive && selectedValue === 'active') {
  1525. $row.removeClass(FILTERCLASS);
  1526. return;
  1527. }
  1528. if (!isActive && selectedValue === 'inactive') {
  1529. $row.removeClass(FILTERCLASS);
  1530. }
  1531. });
  1532. }
  1533. }
  1534. }
  1535. }).element])
  1536. },
  1537. apply: activeUserInfo
  1538. },
  1539. lastSuccessfulLogin: {
  1540. label: 'Last Login',
  1541. sort: true,
  1542. th: { className: 'last-login center' },
  1543. td: { className: 'last-login center mono' },
  1544. filter: function(table){
  1545. var MIN = 60*1000;
  1546. var HOUR = MIN*60;
  1547. var X8HRS = HOUR*8;
  1548. var X24HRS = HOUR*24;
  1549. var X7DAYS = X24HRS*7;
  1550. var X30DAYS = X24HRS*30;
  1551. return spawn('div.center', [XNAT.ui.select.menu({
  1552. value: 0,
  1553. options: {
  1554. all: {
  1555. label: 'All',
  1556. value: 0,
  1557. selected: true
  1558. },
  1559. lastHour: {
  1560. label: 'Last Hour',
  1561. value: HOUR
  1562. },
  1563. last8hours: {
  1564. label: 'Last 8 Hrs',
  1565. value: X8HRS
  1566. },
  1567. last24hours: {
  1568. label: 'Last 24 Hrs',
  1569. value: X24HRS
  1570. },
  1571. lastWeek: {
  1572. label: 'Last Week',
  1573. value: X7DAYS
  1574. },
  1575. last30days: {
  1576. label: 'Last 30 days',
  1577. value: X30DAYS
  1578. },
  1579. never: {
  1580. label: 'Never',
  1581. value: -1
  1582. }
  1583. },
  1584. element: {
  1585. id: 'user-filter-select-last-login',
  1586. on: {
  1587. change: function(){
  1588. var FILTERCLASS = 'filter-login';
  1589. var selectedValue = parseInt(this.value, 10);
  1590. var currentTime = Date.now();
  1591. $dataRows = $dataRows.length ? $dataRows : $$(table).find('tbody').find('tr');
  1592. if (selectedValue === 0) {
  1593. $dataRows.removeClass(FILTERCLASS);
  1594. }
  1595. else {
  1596. $dataRows.addClass(FILTERCLASS).filter(function(){
  1597. var timestamp = this.querySelector('input.last-login.timestamp');
  1598. var lastLogin = +(timestamp.value);
  1599. if (selectedValue === -1) {
  1600. // special handling for users that have never logged in
  1601. return lastLogin === 0;
  1602. }
  1603. else {
  1604. return selectedValue === lastLogin-1 || selectedValue > (currentTime - lastLogin);
  1605. }
  1606. }).removeClass(FILTERCLASS);
  1607. }
  1608. }
  1609. }
  1610. }
  1611. }).element])
  1612. },
  1613. apply: lastLoginInfo
  1614. }
  1615. }
  1616. }
  1617. }
  1618. usersGroups.spawnUsersTable = spawnUsersTable;
  1619. function groupsTab(){
  1620. return {
  1621. kind: 'tab',
  1622. name: 'groups',
  1623. label: 'Groups',
  1624. active: false,
  1625. contents: {
  1626. temp: {
  1627. tag: 'i',
  1628. content: '(groups will show up here)'
  1629. }
  1630. }
  1631. }
  1632. }
  1633. return {
  1634. //tabs: tabs()
  1635. usersTabContents: usersTabContents()
  1636. }
  1637. }
  1638. usersGroups.setupTabs = setupTabs;
  1639. usersGroups.spawnTabs = function(container){
  1640. // console.log('usersGroups.spawnTabs');
  1641. var tabsConfig = setupTabs();
  1642. var $container = $$(container || XNAT.tabs.container);
  1643. $container.html('loading...');
  1644. setTimeout(function(){
  1645. XNAT.dialog.loadingBar.show();
  1646. usersGroups.tabs = XNAT.spawner.spawn(tabsConfig);
  1647. usersGroups.tabs.render($container.empty(), function(){
  1648. XNAT.dialog.loadingBar.hide()
  1649. });
  1650. }, 1);
  1651. // usersGroups.tabs.done(usersGroups.showUsersTable);
  1652. };
  1653. // only render tabs if XNAT.tabs.container is defined
  1654. if (XNAT.tabs && XNAT.tabs.container) {
  1655. usersGroups.spawnTabs();
  1656. }
  1657. // this script has loaded
  1658. usersGroups.loaded = true;
  1659. return XNAT.admin.usersGroups = XNAT.usersGroups = usersGroups;
  1660. }));