PageRenderTime 60ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 1ms

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

https://bitbucket.org/mflorida/xnat-web-1.7-auth-perf-scratch
JavaScript | 1583 lines | 1245 code | 122 blank | 216 comment | 89 complexity | 234fa0c58a02deff7f1855ebe91de75b MD5 | raw file

Large files files are truncated, but you can click here to view the full 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. // return a url for doing a rest call
  38. function setUrl(part1, part2, part3){
  39. var fullUrl = BASE_URL +
  40. (part1 ? ('/' + part1) : '') +
  41. (part2 ? ('/' + part2) : '') +
  42. (part3 ? ('/' + part3) : '');
  43. return XNAT.url.rootUrl(fullUrl)
  44. }
  45. usersGroups.setUrl = setUrl;
  46. // create urls based on minimal input
  47. function usersUrl(user){
  48. // resolve url arguments
  49. function urlArgs(username, path, param){
  50. username = username || user || '';
  51. param = param || username || '';
  52. username = (username||'') === param ? user : username;
  53. return setUrl(username, path, param)
  54. }
  55. return {
  56. allUsers: function(){
  57. return setUrl()
  58. },
  59. userProfile: function(username){
  60. return setUrl(username||user)
  61. },
  62. allUserProfiles: function(){
  63. return setUrl('profiles')
  64. },
  65. activeUser: function(username){
  66. return setUrl('active', username||user)
  67. },
  68. allActiveUsers: function(){
  69. return setUrl('active')
  70. },
  71. enabledUser: function(username, flag){
  72. return urlArgs(username, 'enabled', flag)
  73. },
  74. // XNAT.admin.usersGroups.url('bob').groups()
  75. // XNAT.admin.usersGroups.url().groups('bob')
  76. // --> '/xapi/users/bob/groups'
  77. groups: function(username, group){
  78. return urlArgs(username, 'groups', group)
  79. },
  80. roles: function(username, role){
  81. return urlArgs(username, 'roles', role)
  82. },
  83. verified: function(username, flag){
  84. return urlArgs(username, 'verified', flag)
  85. }
  86. }
  87. }
  88. usersGroups.url = usersUrl;
  89. function xapiUsers(method, url, opts){
  90. method = method || 'get';
  91. var methodGET = /^get$/i.test(method);
  92. opts = cloneObject(opts);
  93. opts.url = opts.url || url;
  94. if (methodGET){
  95. // the 'restuUrl' method adds a cache-busting parameter
  96. opts.url = XNAT.url.restUrl(opts.url);
  97. }
  98. else {
  99. opts.url = XNAT.url.rootUrl(opts.url);
  100. }
  101. return XNAT.xhr[method](opts).done(function(data){
  102. // cache returned 'get' data at URL
  103. if (methodGET) XNAT.data[url] = data;
  104. });
  105. }
  106. // get or set data via REST
  107. usersGroups.userData = function(user){
  108. var methods = {
  109. // generic method for any kind of request
  110. request: function(method, path, data, opts){
  111. path = [].concat(path);
  112. opts = getObject(opts);
  113. opts.data = data || opts.data || '';
  114. return xapiUsers(method, setUrl.apply(null, path), opts)
  115. },
  116. userList: function(opts){
  117. return xapiUsers('get', setUrl(), opts);
  118. },
  119. allProfiles: function(opts){
  120. return xapiUsers('get', setUrl('profiles'), opts);
  121. },
  122. // XNAT.usersGroups.userData('bob').profile().done(someCallbackFunction)
  123. getProfile: function(opts){
  124. return xapiUsers('get', setUrl('profile/' + user), opts);
  125. },
  126. createProfile: function(data, opts){
  127. opts = getObject(opts);
  128. opts.data = data || opts.data || '';
  129. return xapiUsers('postJSON', setUrl(user), opts)
  130. },
  131. updateProfile: function(data, opts){
  132. opts = getObject(opts);
  133. opts.data = data || opts.data || '';
  134. return xapiUsers('putJSON', setUrl(user), opts)
  135. },
  136. activeUsers: function(opts){
  137. return xapiUsers('get', setUrl('active'), opts)
  138. }
  139. };
  140. methods.allUsers = methods.userList;
  141. methods.usernames = methods.userList;
  142. methods.profiles = methods.allProfiles;
  143. methods.userProfiles = methods.allProfiles;
  144. methods.allUserProfiles = methods.allProfiles;
  145. // decide whether to GET or POST
  146. // based on presence of 'data' argument
  147. methods.profile = function(data, opts){
  148. var method = 'createProfile';
  149. if (!data && !opts) {
  150. method = 'getProfile'
  151. }
  152. return methods[method](data, opts);
  153. };
  154. methods.createUser = methods.createProfile;
  155. methods.updateUser = methods.updateProfile;
  156. return methods;
  157. };
  158. // RETURNS A DOM ELEMENT
  159. usersGroups.userSwitchElement = function(username, type, status){
  160. return XNAT.ui.input.switchbox({
  161. value: realValue(status),
  162. element: {
  163. name: type,
  164. className: 'user-' + type,
  165. checked: status,
  166. title: username + ':' + type
  167. },
  168. onText: 'Yes',
  169. offText: 'No'
  170. });
  171. };
  172. // use this to spawn the user account form separately
  173. function renderUserAccountForm(data, container){
  174. return XNAT.spawner.spawn({
  175. userAccountForm: userAccountForm(data)
  176. }).render(container)
  177. }
  178. function saveUserData(form, opts){
  179. var $form = $$(form);
  180. var username = $form.find('input#username').val();
  181. opts = cloneObject(opts);
  182. var doSubmit = $form.submitJSON(opts);
  183. if (doSubmit.done) {
  184. doSubmit.done(function(){
  185. // xmodal.loading.open();
  186. XNAT.ui.banner.top(2000, 'User info saved.', 'success')
  187. });
  188. }
  189. if (doSubmit.fail) {
  190. doSubmit.fail(function(e){
  191. XNAT.ui.dialog.alert('' +
  192. 'An error occurred saving user data.' +
  193. '<br>' +
  194. '<div class="error">' + e + '</div>' +
  195. '');
  196. });
  197. }
  198. return doSubmit;
  199. }
  200. // define the user properties dialog
  201. function editUserDialog(data, onclose){
  202. if (data && !data.username) {
  203. return xmodal.message('Error', 'An error occurred displaying user data.');
  204. }
  205. // define the <form> Spawner element
  206. function userForm(){
  207. return {
  208. userForm: {
  209. tag: 'div.user-account-info',
  210. //kind: 'panel.multiForm',
  211. //classes: 'user-details',
  212. // label: 'User Details',
  213. //header: false,
  214. //footer: false,
  215. contents: {
  216. userAccountForm: userAccountForm(data)//,
  217. //userProjects: userProjects(),
  218. //userSecurity: userSecurity()
  219. }
  220. }
  221. }
  222. }
  223. // TODO: replace old 'advanced' project settings with this
  224. function userProjects(){
  225. return {
  226. kind: 'panel.form',
  227. title: 'Project Membership & Roles',
  228. contents: {
  229. projectMembership: {
  230. tag: 'div.project-membership',
  231. content: '<i>project membership and role menus go here</i>'
  232. }
  233. }
  234. }
  235. }
  236. // TODO: replace old 'advanced' security settings with this
  237. function userSecurity(){
  238. return {
  239. kind: 'panel.form',
  240. name: 'securitySettings',
  241. title: 'Security Settings',
  242. // action: '#!',
  243. // action: '/xapi/users/' + data.username + '/roles',
  244. // action: '/data/user/' + data.username + '/roles',
  245. // _action: '/app/action/ModifyUserGroups',
  246. contents: {
  247. systemRolesSubhead: {
  248. kind: 'panel.subhead',
  249. label: 'System Roles'
  250. },
  251. csrfToken: {
  252. kind: 'input.hidden',
  253. name: 'XNAT_CSRF',
  254. value: csrfToken
  255. },
  256. siteManager: {
  257. kind: 'panel.input.switchbox',
  258. label: 'Site Manager',
  259. id: 'custom-role-administrator',
  260. name: 'custom_role',
  261. value: 'Administrator',
  262. element: {
  263. checked: (data.roles.indexOf('Administrator') > -1)
  264. },
  265. description: '<p>This allows users to access the Administrative pages of the web interface.</p>' +
  266. '<div class="warning">' +
  267. '<b>WARNING:</b> Granting administrative privileges allows this user great power ' +
  268. 'over the entire site.' +
  269. '</div>'
  270. },
  271. nonExpiring: {
  272. kind: 'panel.input.switchbox',
  273. label: 'Non-Expiring',
  274. id: 'custom-role-non-expiring',
  275. name: 'custom_role',
  276. value: 'non_expiring',
  277. element: {
  278. checked: (data.roles.indexOf('non_expiring') > -1)
  279. },
  280. description: '<p>This prevents this accounts password from expiring.</p>' +
  281. '<div class="warning">' +
  282. '<b>WARNING:</b> Granting a user account a non-expiring password is a security risk ' +
  283. 'and should be limited to accounts that perform automated system tasks. In addition, ' +
  284. 'if any users are designated as non-expiring access to the user list should be ' +
  285. 'restricted to administrators.' +
  286. '</div>'
  287. },
  288. allDataSubhead: {
  289. kind: 'panel.subhead',
  290. label: 'Allow All Data Access'
  291. },
  292. allDataRadios: {
  293. tag: 'div.all-data-radios',
  294. contents: {
  295. noRadio: {
  296. kind: 'input.radio',
  297. id: 'data_none',
  298. name: 'xdat:user.groups.groupID[0].groupID',
  299. label: 'No',
  300. value: 'NULL'//,
  301. // afterElement: 'No'
  302. },
  303. readOnlyRadio: {
  304. kind: 'input.radio',
  305. id: 'data_access',
  306. name: 'xdat:user.groups.groupID[0].groupID',
  307. label: 'Read Only',
  308. value: 'ALL_DATA_ACCESS'
  309. },
  310. readEditDeleteRadio: {
  311. kind: 'input.radio',
  312. id: 'data_admin',
  313. name: 'xdat:user.groups.groupID[0].groupID',
  314. label: 'Read, Edit, Delete',
  315. value: 'ALL_DATA_ADMIN'
  316. }
  317. }
  318. },
  319. allDataWarning: {
  320. tag: 'div.warning',
  321. content: 'WARNING: Allowing full access to data will allow this user to see ALL data ' +
  322. 'stored in this system. It supersedes project membership. Most accounts on your server ' +
  323. 'should NOT have All Data Access allowed.'
  324. }
  325. }
  326. }
  327. }
  328. var updated = false;
  329. return xmodal.open({
  330. width: 600,
  331. height: 600,
  332. title: 'User Properties for <b>' + data.username + '</b>',
  333. content: '<div class="user-data"></div>',
  334. beforeShow: function(obj){
  335. var _userForm = XNAT.spawner.spawn(userForm());
  336. obj.$modal.find('div.user-data').append(_userForm.get())
  337. },
  338. okLabel: 'Save',
  339. okClose: false,
  340. okAction: function(obj){
  341. var $form = obj.$modal.find('form#user-account-form-panel');
  342. var doSave = saveUserData($form);
  343. doSave.done(function(){
  344. updated = true;
  345. obj.close();
  346. });
  347. },
  348. onClose: function(obj){
  349. if (typeof onclose === 'function') {
  350. onclose(obj)
  351. }
  352. if (updated) {
  353. updateUserData(data.username);
  354. // renderUsersTable();
  355. }
  356. }
  357. })
  358. }
  359. // get user data and return AJAX promise object
  360. function getUserData(username){
  361. var _url = XNAT.url.restUrl('/xapi/users/' + username);
  362. return XNAT.xhr.get(_url)
  363. }
  364. // get user roles and return AJAX promise object
  365. function getUserRoles(username){
  366. var _url = XNAT.url.restUrl('/xapi/users/' + username + '/roles');
  367. return XNAT.xhr.get(_url);
  368. }
  369. function getActiveUsers(success, failure){
  370. // console.log(getActiveUsers.name);
  371. return XNAT.xhr.get({
  372. url: XNAT.url.restUrl('/xapi/users/active'),
  373. success: function(data){
  374. XNAT.data['/xapi/users/active'] = data;
  375. if (isFunction(success)) {
  376. success.apply(this, arguments);
  377. }
  378. },
  379. failure: failure
  380. })
  381. }
  382. function showUsersTable(){
  383. // console.log('showUsersTable');
  384. // $$($table).removeClass('hidden');
  385. // $$($table).show();
  386. }
  387. usersGroups.showUsersTable = showUsersTable;
  388. // render or update the users table
  389. function renderUsersTable(container, url){
  390. // console.log('renderUsersTable');
  391. var $container = container ? $$(container) : $(userTableContainer);
  392. var _usersTable;
  393. if ($container.length) {
  394. setTimeout(function(){
  395. $container.html('loading...');
  396. }, 1);
  397. setTimeout(function(){
  398. _usersTable = XNAT.spawner.spawn({
  399. usersTable: usersGroups.spawnUsersTable(url)
  400. });
  401. _usersTable.done(function(){
  402. this.render($container.empty(), 20);
  403. });
  404. }, 10);
  405. // return _usersTable;
  406. }
  407. }
  408. usersGroups.renderUsersTable = renderUsersTable;
  409. // TODO: re-render *only* the table rows, not the whole table
  410. function updateUsersTable(refresh, all){
  411. var URL = all ? '/xapi/users/profiles' : '/xapi/users/current';
  412. if (!refresh && XNAT.data[URL]) {
  413. getActiveUsers(function(){
  414. renderUsersTable();
  415. });
  416. return {
  417. done: function(callback){
  418. callback.call()
  419. }
  420. }
  421. }
  422. return XNAT.xhr.get({
  423. url: XNAT.url.restUrl(URL),
  424. success: function(data){
  425. XNAT.data[URL] = data;
  426. // console.log(data);
  427. getActiveUsers(function(){
  428. renderUsersTable();
  429. });
  430. }
  431. })
  432. }
  433. usersGroups.updateUsersTable = updateUsersTable;
  434. // get profile data for ALL users
  435. function getUserProfiles(success, failure){
  436. return XNAT.xhr.get({
  437. url: XNAT.url.restUrl('/xapi/users/profiles'),
  438. success: function(data){
  439. XNAT.data['/xapi/users/profiles'] = data;
  440. if (isFunction(success)) {
  441. success.apply(this, arguments);
  442. }
  443. },
  444. failure: failure
  445. })
  446. }
  447. usersGroups.getUserProfiles = getUserProfiles;
  448. // get profile data for users considered 'current'
  449. function getCurrentUsers(success, failure){
  450. return XNAT.xhr.get({
  451. url: XNAT.url.restUrl('/xapi/users/current'),
  452. success: function(data){
  453. XNAT.data['/xapi/users/current'] = data;
  454. if (isFunction(success)) {
  455. success.apply(this, arguments);
  456. }
  457. },
  458. failure: failure
  459. })
  460. }
  461. usersGroups.getCurrentUsers = getCurrentUsers;
  462. // renders cells for 'verified' and 'enabled' status
  463. function userStatusInfo(type, off){
  464. var username = this.username;
  465. var status = realValue(this[type]);
  466. var SORTER = status ? 1 : 0;
  467. var IMG = status ? '/images/cg.gif' : '/images/cr.gif';
  468. return spawn('!', [
  469. ['i.hidden.sorting.filtering.' + type, SORTER+''],
  470. ['a.user-' + type + '-status.edit-user', {
  471. title: username + ': ' + (status ? type : off),
  472. href: '#!',
  473. style: { display: 'block', padding: '2px' }
  474. }, [['img', { src: XNAT.url.rootUrl(IMG) }]]]
  475. ]);
  476. }
  477. // renders cells for 'active' users column
  478. function activeUserInfo(){
  479. var username = this.username;
  480. var sessionCount = 0;
  481. activeUsers = XNAT.data['/xapi/users/active'];
  482. if (username && activeUsers && activeUsers[username] && activeUsers[username].sessions.length) {
  483. sessionCount = activeUsers[username].sessions.length
  484. }
  485. if (sessionCount) {
  486. return spawn('!', [
  487. ['i.hidden.sorting', zeroPad(sessionCount, 6)],
  488. ['a.active-user', {
  489. title: username + ': kill ' + sessionCount + ' active session(s)',
  490. href: '#!',
  491. style: { display: 'block', padding: '2px' }
  492. }, [['img', { src: XNAT.url.rootUrl('/images/cg.gif') }]]]
  493. ])
  494. }
  495. else {
  496. return '<i class="hidden">-1</i>&mdash;'
  497. }
  498. }
  499. // renders cells for last login column
  500. function lastLoginInfo(value){
  501. value = realValue(value) || 0;
  502. return spawn('!', [
  503. ['i.last-login.hidden.sorting', (9999999999999-value || '9999999999999')+''],
  504. ['input.hidden.last-login.timestamp.filtering|type=hidden', { value: value }],
  505. ['span.date-time', (value ? (new Date(value)).toLocaleString() : '&mdash;')]
  506. ])
  507. }
  508. // set unique id attribute for <tr> elements
  509. // by combining username and user id
  510. function setRowId(profile){
  511. return profile.username + '-' + profile.id;
  512. }
  513. // update ONLY the row of the edited user
  514. function updateUserRow(profile){
  515. var rowId = setRowId(profile);
  516. var $row = $(document.getElementById(rowId));
  517. // full name column
  518. $row.find('a.full-name')
  519. .text(profile.lastName + ', ' + profile.firstName);
  520. // email column
  521. $row.find('a.send-email')
  522. .attr('title', profile.email + ': send email')
  523. .text(profile.email);
  524. // verified status column
  525. $row.find('td.verified')
  526. .empty()
  527. .append(userStatusInfo.call(profile, 'verified', 'unverified'));
  528. // enabled status column
  529. $row.find('td.enabled')
  530. .empty()
  531. .append(userStatusInfo.call(profile, 'enabled', 'disabled'));
  532. // active status column
  533. $row.find('td.active')
  534. .empty()
  535. .append(activeUserInfo.call(profile));
  536. // last login column
  537. $row.find('td.last-login')
  538. .empty()
  539. .append(lastLoginInfo(profile.lastSuccessfulLogin));
  540. }
  541. function updateUserData(username, delay){
  542. var USERNAME = typeof username === 'string' ? username : this.title.split(':')[0];
  543. var USER_URL = USERNAME === 'profiles' ? '/xapi/users/profiles' : '/xapi/users/profile/' + USERNAME;
  544. return XNAT.xhr.get({
  545. url: XNAT.url.restUrl(USER_URL),
  546. success: function(DATA){
  547. XNAT.data[USER_URL] = DATA;
  548. if (userAdminPage) {
  549. window.setTimeout(function(){
  550. getActiveUsers().done(function(ACTIVE){
  551. XNAT.data['/xapi/users/active'] = ACTIVE;
  552. forEach([].concat(DATA), function(profile, i){
  553. updateUserRow(profile);
  554. });
  555. })
  556. }, delay||100);
  557. }
  558. }
  559. })
  560. }
  561. function userAccountForm(data){
  562. var _load = data ? serverRoot + '/xapi/users/' + data.username : false;
  563. data = data || {};
  564. // username could be text or input element
  565. function usernameField(){
  566. var obj = {
  567. label: 'Username'
  568. };
  569. if (data && data.username) {
  570. obj.kind = 'panel.element';
  571. obj.contents = {
  572. usernameText: {
  573. kind: 'html',
  574. content: data.username
  575. },
  576. usernameInput: {
  577. kind: 'input.hidden',
  578. name: 'username',
  579. value: data.username || ''
  580. }
  581. };
  582. }
  583. else {
  584. obj.kind = 'panel.input.text';
  585. obj.name = 'username';
  586. obj.validate = 'alpha-num-safe required';
  587. obj.value = '';
  588. }
  589. return obj;
  590. }
  591. var form = {
  592. kind: 'panel.form',
  593. label: 'Account Information',
  594. footer: false,
  595. validate: true,
  596. method: _load ? 'PUT' : 'POST',
  597. contentType: 'json',
  598. load: _load,
  599. refresh: false,
  600. action: '/xapi/users' + (_load ? '/' + data.username : ''),
  601. contents: {
  602. // details: {
  603. // kind: 'panel.subhead',
  604. // label: 'User Details'
  605. // },
  606. // id: {
  607. // kind: 'panel.input.hidden',
  608. // validate: _load ? 'number required' : 'allow-empty',
  609. // value: data.id || ''
  610. // },
  611. pad: {
  612. kind: 'html',
  613. content: '<br>'
  614. },
  615. usernameField: usernameField(),
  616. password: {
  617. kind: 'panel.input.password',
  618. label: 'Password',
  619. element: {
  620. placeholder: '********',
  621. data: { message: passwordComplexityMessage }
  622. },
  623. validate: 'allow-empty pattern:' + passwordComplexity + ' max-length:255'//,
  624. //value: data.password || ''
  625. },
  626. firstName: {
  627. kind: 'panel.input.text',
  628. label: 'First Name',
  629. validate: 'required',
  630. value: data.firstName || ''
  631. },
  632. lastName: {
  633. kind: 'panel.input.text',
  634. label: 'Last Name',
  635. validate: 'required',
  636. value: data.lastName || ''
  637. },
  638. email: {
  639. kind: 'panel.input.email',
  640. label: 'Email',
  641. validate: 'email required',
  642. value: data.email || ''
  643. },
  644. verified: {
  645. kind: 'panel.input.switchbox',
  646. label: 'Verified',
  647. value: data.verified !== undefined ? data.verified + '' : 'false',
  648. element: {
  649. //disabled: !!_load,
  650. title: data.username + ':verified'//,
  651. //on: { click: _load ? setVerified : diddly }
  652. }
  653. },
  654. enabled: {
  655. kind: 'panel.input.switchbox',
  656. label: 'Enabled',
  657. value: data.enabled !== undefined ? data.enabled + '' : 'false',
  658. element: {
  659. //disabled: !!_load,
  660. title: data.username + ':enabled'//,
  661. //on: { click: _load ? setEnabled : diddly }
  662. }
  663. }
  664. }
  665. };
  666. // add 'Advanced Settings' when editing existing user
  667. if (_load && usersGroups.showAdvanced) {
  668. form.contents.advancedSettings = {
  669. kind: 'panel.element',
  670. label: 'Advanced',
  671. contents: {
  672. advancedLink: {
  673. tag: 'a.edit-advanced-settings.link',
  674. element: {
  675. href: '#!',
  676. title: data.username + ':advanced',
  677. on: {
  678. click: function(e){
  679. e.preventDefault();
  680. var modalId = $(this).closest('div.xmodal').attr('id');
  681. xmodal.modals[modalId].close();
  682. window.top.usernameForUserThatIsCurrentlyBeingEdited = data.username;
  683. userProjectsAndSecurity(e, data.username);
  684. }
  685. }
  686. },
  687. content: 'Edit Advanced Settings'
  688. },
  689. description: {
  690. tag: 'div.description',
  691. content: "Edit this user's project and security settings."
  692. }
  693. }
  694. }
  695. }
  696. usersGroups.showAdvanced = true;
  697. return form;
  698. }
  699. // open a dialog to edit user properties
  700. function editUser(e, onclose){
  701. e.preventDefault();
  702. var username =
  703. (this.title || '').split(':')[0] ||
  704. $(this).data('username') ||
  705. $(this).closest('tr').data('username') ||
  706. (this.innerText || '').trim();
  707. window.top.usernameForUserThatIsCurrentlyBeingEdited = username;
  708. getUserData(username).done(function(data){
  709. getUserRoles(username).done(function(roles){
  710. data.roles = roles;
  711. // save the data to namespaced object before opening dialog
  712. XNAT.data['/xapi/users/profile/' + username] = data;
  713. editUserDialog(data, onclose);
  714. })
  715. });
  716. }
  717. usersGroups.editUser = editUser;
  718. // immediately toggles user's "Verified" status
  719. function setVerified(e, username, flag){
  720. username = username || this.title.split(':')[0];
  721. flag = flag || this.checked;
  722. return XNAT.xhr.put({
  723. url: XNAT.url.rootUrl('/xapi/users/' + username + '/verified/' + flag),
  724. success: function(){
  725. XNAT.ui.banner.top(2000, 'User has been set to ' + (flag ? '"verified"' : '"unverified"') + '.', 'success')
  726. },
  727. error: function(){
  728. XNAT.ui.banner.top(3000, 'An error occurred setting "verified" status.', 'error')
  729. }
  730. })
  731. }
  732. usersGroups.setVerified = setVerified;
  733. // immediately toggles user's "Enabled" status
  734. function setEnabled(e, username, flag){
  735. username = username || this.title.split(':')[0];
  736. flag = flag || this.checked;
  737. return XNAT.xhr.put({
  738. url: XNAT.url.rootUrl('/xapi/users/' + username + '/enabled/' + flag),
  739. success: function(){
  740. XNAT.ui.banner.top(2000, 'User status has been set to ' + (flag ? '"enabled"' : '"disabled"') + '.', 'success')
  741. },
  742. error: function(){
  743. XNAT.ui.banner.top(3000, 'An error occurred setting "enabled" status.', 'error')
  744. }
  745. })
  746. }
  747. usersGroups.setEnabled = setEnabled;
  748. function userProjectsAndSecurity(e, usr){
  749. var _username = usr || $(this).data('username');
  750. var _url = XNAT.url.rootUrl('/app/action/DisplayItemAction/search_value/' + _username + '/search_element/xdat:user/search_field/xdat:user.login/popup/true');
  751. return xmodal.iframe({
  752. src: _url,
  753. name: 'advanced-user-settings',
  754. width: 800,
  755. height: '100%',
  756. title: 'Edit User Info',
  757. // footer: false,
  758. okLabel: 'Close',
  759. cancel: false,
  760. onClose: function(){
  761. var editedUser = window.top.usernameForUserThatIsCurrentlyBeingEdited;
  762. if (!window.top.XNAT.page.userAdminPage) {
  763. return false;
  764. }
  765. if (editedUser){
  766. usersGroups.userData(editedUser).getProfile().done(function(profileData){
  767. updateUserRow(profileData)
  768. });
  769. }
  770. else {
  771. updateUsersTable(true);
  772. }
  773. }
  774. })
  775. }
  776. // open a dialog for creating a new user
  777. function newUserDialog(){
  778. var updated = false;
  779. return xmodal.open({
  780. width: 600,
  781. height: 500,
  782. title: 'Create New User',
  783. content: '<div class="new-user-form"></div>',
  784. beforeShow: function(){
  785. var _container = this.$modal.find('div.new-user-form');
  786. renderUserAccountForm(null, _container);
  787. },
  788. okLabel: 'Save',
  789. okClose: 'false',
  790. okAction: function(obj){
  791. var $form = obj.$modal.find('form#user-account-form-panel');
  792. var _username = $form.find('input[name="username"]').val();
  793. // make sure new username is not a duplicate
  794. var getUserList = usersGroups.userData().usernames();
  795. getUserList.done(function(users){
  796. if (users.indexOf(_username) > -1) {
  797. XNAT.ui.dialog.alert('The username ' +
  798. '<b>' + _username + '</b> is already in use. ' +
  799. 'Please enter a different username value and ' +
  800. 'try again.');
  801. return false;
  802. }
  803. var doSave = saveUserData($form);
  804. doSave.done(function(){
  805. updated = true;
  806. obj.close();
  807. });
  808. });
  809. },
  810. onClose: function(){
  811. // always update the whole table when adding a user.
  812. // usersGroups.spawnTabs();
  813. renderUsersTable();
  814. // if (updated) {
  815. // updateUsersTable(true);
  816. // }
  817. }
  818. })
  819. }
  820. function setupTabs(){
  821. // console.log(setupTabs.name);
  822. function tabs(){
  823. return {
  824. kind: 'tabs',
  825. layout: 'top', // trying a top tabs layout
  826. contents: {
  827. usersTab: usersTab(),
  828. groupsTab: groupsTab()
  829. }
  830. }
  831. }
  832. function usersTab(){
  833. return {
  834. kind: 'tab',
  835. name: 'users',
  836. label: 'Users',
  837. active: true,
  838. contents: usersTabContents()
  839. }
  840. }
  841. function createUserButton(){
  842. return {
  843. tag: 'button#create-new-user',
  844. element: {
  845. html: 'Create New User',
  846. on: {
  847. click: function(e){
  848. // console.log('clicked');
  849. newUserDialog();
  850. }
  851. }
  852. }
  853. }
  854. }
  855. function reloadUserListButton(){
  856. return {
  857. tag: 'button#reload-user-list',
  858. element: {
  859. html: 'Reload User List',
  860. style: {
  861. marginLeft: '20px'
  862. },
  863. on: {
  864. click: function(e){
  865. // console.log('clicked');
  866. // updateUserData('profiles', 10);
  867. // var $container = $('#user-table-container');
  868. // $container.empty().html('loading...');
  869. // spawnUsersTable().render($container.empty());
  870. // usersGroups.spawnTabs();
  871. setTimeout(function(){
  872. updateUsersTable(true);
  873. }, 10);
  874. }
  875. }
  876. }
  877. }
  878. }
  879. function loadAllUsersButton(){
  880. return {
  881. tag: 'button#load-all-users',
  882. element: {
  883. html: 'Load All Users',
  884. style: {
  885. marginLeft: '10px'
  886. },
  887. on: {
  888. click: function(e){
  889. renderUsersTable($(userTableContainer), '/xapi/users/profiles');
  890. }
  891. }
  892. }
  893. }
  894. }
  895. function allUsersButtonInfoText(){
  896. return {
  897. tag: 'span.tip.shadowed',
  898. element: {
  899. style: {
  900. width: '350px',
  901. left: '-385px',
  902. top: '-75px',
  903. // backgroundColor: '#ffc',
  904. zIndex: 10000
  905. }
  906. },
  907. content: [
  908. 'By default, the table below only shows "current" users: users that are <b>enabled</b>, or who ' +
  909. 'may be <b>disabled</b> but have logged in during the past year, or have an unused account created less ' +
  910. 'than a year ago.' +
  911. '<br><br>' +
  912. 'Click the <b>"Load All Users"</b> button to view <i>all</i> users that have accounts on this system, ' +
  913. 'even if their account is not "current."'
  914. ],
  915. filler: null
  916. }
  917. }
  918. // var alternateInfoText = '' +
  919. //
  920. // 'By default, the table below only shows "current" users: users that are <b>enabled</b>, users that have ' +
  921. // 'logged in during the past year but are <b>disabled</b>, or users with accounts that were ' +
  922. // 'created created in the past year but never used.' +
  923. // '<br><br>' +
  924. // 'Click the <b>"Load All Users"</b> button to view <i>all</i> users that have accounts on this system, ' +
  925. // 'even if their account is not "current."' +
  926. //
  927. // '';
  928. function allUsersButtonInfo(){
  929. return {
  930. tag: 'span.tip_icon',
  931. element: {
  932. style: {
  933. marginRight: '3px',
  934. left: '2px',
  935. top: '3px'
  936. }
  937. },
  938. contents: {
  939. allUsersButtonInfoText: allUsersButtonInfoText()
  940. }
  941. }
  942. }
  943. function allUsersButtonElements(){
  944. return {
  945. tag: 'div',
  946. element: {
  947. style: {
  948. float: 'right'
  949. }
  950. },
  951. contents: {
  952. allUsersButtonInfo: allUsersButtonInfo(),
  953. loadAllUsersButton: loadAllUsersButton()
  954. }
  955. }
  956. }
  957. function selectAllUsers(){
  958. var $this = $(this);
  959. var $table = $this.closest('table');
  960. var $inputs = $table.find('input.select-user');
  961. if ($this.hasClass('selected')) {
  962. $inputs.prop('checked', false);
  963. $this.removeClass('selected');
  964. }
  965. else {
  966. $inputs.prop('checked', true);
  967. $this.addClass('selected');
  968. }
  969. }
  970. function usersTabContents(){
  971. return {
  972. tag: 'div.manage-users',
  973. contents: {
  974. style: {
  975. tag: 'style',
  976. content: '#user-profiles .new-user { background: #ffc }'
  977. },
  978. // actions: {
  979. // kind: 'panel',
  980. // label: 'Actions',
  981. // footer: false,
  982. // contents: {
  983. // viewActiveUsers: {
  984. // tag: 'button#view-active-users.btn1',
  985. // element: {
  986. // type: 'button',
  987. // html: 'View Active Users'
  988. // }
  989. // }//,
  990. // // things: {
  991. // // kind: 'html',
  992. // // content: '' +
  993. // // '<div class="active-users">' +
  994. // // '<button type="button" id="view-active-users" class="btn1">View Active Users</button>' +
  995. // // '</div>'//,
  996. // // element: spawn('div.active-users', {
  997. // // style: { marginBottom: '20px' }
  998. // // }, [['a#view-active-users|href=#!', 'View Active Users']])
  999. // // }
  1000. // }
  1001. // },
  1002. userActions: {
  1003. tag: 'div.user-actions',
  1004. element: {
  1005. style: {
  1006. position: 'relative',
  1007. marginBottom: '30px',
  1008. paddingTop: '30px',
  1009. borderTop: '1px solid #c8c8c8'
  1010. }
  1011. },
  1012. contents: {
  1013. createUserButton: createUserButton(),
  1014. reloadUserListButton: reloadUserListButton(),
  1015. allUsersButtonElements: allUsersButtonElements()
  1016. }
  1017. },
  1018. usersTablePanel: {
  1019. tag: 'div#user-accounts',
  1020. contents: {
  1021. tableContainer: {
  1022. tag: userTableContainer,
  1023. contents: {
  1024. usersTable: spawnUsersTable()
  1025. }
  1026. }
  1027. }
  1028. }
  1029. }
  1030. }
  1031. }
  1032. // kill all active sessions for the specified user
  1033. function killActiveSessions(e){
  1034. e.preventDefault();
  1035. var username = this.title.split(':')[0].trim();
  1036. return xmodal.confirm({
  1037. title: 'Kill Active Sessions?',
  1038. content: 'Kill all active sessions for <b>' + username + '</b>?',
  1039. okClose: false,
  1040. okAction: function(obj){
  1041. XNAT.xhr.delete({
  1042. url: XNAT.url.rootUrl('/xapi/users/active/' + username),
  1043. success: function(){
  1044. obj.close();
  1045. XNAT.ui.banner.top(2000, 'Sessions closed', 'success');
  1046. // wait a few seconds before updating the row
  1047. updateUserData(username, 2000);
  1048. // updateUsersTable(true);
  1049. }
  1050. })
  1051. }
  1052. });
  1053. }
  1054. // TODO: view active sessions
  1055. function viewSessionInfo(e){
  1056. e.preventDefault();
  1057. var username = this.title;
  1058. }
  1059. function goToEmail(){
  1060. var _email = this.title.split(':')[0];
  1061. var _url = XNAT.url.rootUrl('/app/template/XDATScreen_email.vm/emailTo/');
  1062. window.location.href = _url + _email;
  1063. }
  1064. // set up custom filter menus
  1065. function filterMenuElement(prop){
  1066. if (!prop) return false;
  1067. // call this function in context of the table
  1068. var $userProfilesTable = $(this);
  1069. var FILTERCLASS = 'filter-' + prop;
  1070. // var FILTERCLASS = 'hidden';
  1071. return {
  1072. id: 'user-filter-select-' + prop,
  1073. // style: { width: '100%' },
  1074. on: {
  1075. change: function(){
  1076. var selectedValue = $(this).val();
  1077. // console.log(selectedValue);
  1078. $userProfilesTable.find('i.filtering.'+prop).each(function(){
  1079. var $row = $(this).closest('tr');
  1080. if (selectedValue === 'all') {
  1081. $row.removeClass(FILTERCLASS);
  1082. return;
  1083. }
  1084. $row.addClass(FILTERCLASS);
  1085. if (selectedValue == this.textContent) {
  1086. $row.removeClass(FILTERCLASS);
  1087. }
  1088. // if (!this.checked && selectedValue === notProp) {
  1089. // $row.removeClass(FILTERCLASS);
  1090. // }
  1091. })
  1092. }
  1093. }
  1094. };
  1095. }
  1096. // Spawner element config for the users list table
  1097. function spawnUsersTable(url){
  1098. // console.log(spawnUsersTable.name);
  1099. //var _data = XNAT.xapi.users.profiles || XNAT.data['/xapi/users/profiles'];
  1100. var $dataRows = [];
  1101. // load 'current' users initially
  1102. var URL = url || '/xapi/users/current';
  1103. // TODO:
  1104. // TODO: set min-width as well as max-width
  1105. // TODO:
  1106. var styles = {
  1107. id: (60-24)+'px',
  1108. username: (160-24)+'px',
  1109. name: (280-24) + 'px',
  1110. email: (222-24) + 'px',
  1111. verified: (104-24) + 'px',
  1112. enabled: (96-24) +'px',
  1113. active: (92-24) + 'px',
  1114. login: (118-24) + 'px'
  1115. };
  1116. // var altStyles = {};
  1117. // forOwn(styles, function(name, val){
  1118. // altStyles[name] = (val * 0.8)
  1119. // });
  1120. return {
  1121. kind: 'table.dataTable',
  1122. name: 'userProfiles',
  1123. id: 'user-profiles',
  1124. load: URL,
  1125. before: {
  1126. filterCss: {
  1127. tag: 'style|type=text/css',
  1128. content: '\n' +
  1129. '#user-profiles td.user-id { width: ' + styles.id + '; } \n' +
  1130. '#user-profiles td.username .truncate { width: 120px; } \n' +
  1131. '#user-profiles td.fullName .truncate { width: 180px; } \n' +
  1132. '#user-profiles td.email .truncate { width: 220px; } \n' +
  1133. '#user-profiles td.verified { width: ' + styles.verified + '; } \n' +
  1134. '#user-profiles td.enabled { width: ' + styles.enabled + '; } \n' +
  1135. '#user-profiles td.ACTIVE { width: ' + styles.active + '; } \n' +
  1136. '#user-profiles td.lastSuccessfulLogin { width: ' + styles.login + '; } \n' +
  1137. '#user-profiles tr.filter-verified, \n' +
  1138. '#user-profiles tr.filter-enabled, \n' +
  1139. '#user-profiles tr.filter-active, \n' +
  1140. '#user-profiles tr.filter-login { display: none; } \n' +
  1141. '@media screen and (max-width: 1200px) { \n' +
  1142. ' #user-profiles td.username .truncate { width: 90px; } \n' +
  1143. ' #user-profiles td.fullName .truncate { width: 100px; } \n' +
  1144. ' #user-profiles td.email .truncate { width: 140px; } \n' +
  1145. '}'
  1146. }
  1147. },
  1148. onRender: showUsersTable,
  1149. table: {
  1150. classes: 'highlight hidden',
  1151. on: [
  1152. ['click', 'a.user-id', updateUserData],
  1153. ['click', 'a.select-all', selectAllUsers],
  1154. ['click', 'a.edit-user', editUser],
  1155. // ['click', 'a.full-name', userProjectsAndSecurity],
  1156. ['click', 'a.send-email', goToEmail],
  1157. // ['change', 'input.user-verified', setVerified],
  1158. // ['change', 'input.user-enabled', setEnabled],
  1159. ['click', 'a.active-user', killActiveSessions],
  1160. ['click', 'a.session-info', viewSessionInfo]
  1161. ]
  1162. },
  1163. trs: function(tr, data){
  1164. // 'this' is the currently iterating <tr>
  1165. data.userId = setRowId(data);
  1166. tr.id = data.userId;
  1167. addDataAttrs(tr, { filter: '0' });
  1168. },
  1169. sortable: 'username, fullName, email',
  1170. filter: 'fullName, email',
  1171. items: {
  1172. // _id: '~data-id',
  1173. // _username: '~data-username',
  1174. // _select: {
  1175. // label: 'Select',
  1176. // th: { html: '<a href="#!" class="select-all link">Select</a>' },
  1177. // td: { className: 'centered' },
  1178. // apply: function(){
  1179. // return spawn('input.select-user', {
  1180. // type: 'checkbox',
  1181. // checked: false,
  1182. // title: this.username + ':select'
  1183. // });
  1184. // //return '<a href="#!" class="username link">' + this.username + '</a>'
  1185. // }
  1186. // },
  1187. //
  1188. id: {
  1189. label: 'ID',
  1190. sort: true,
  1191. // th: { style: { width: styles.id }},
  1192. td: {
  1193. // style: { width: styles.id },
  1194. className: 'user-id center'
  1195. },
  1196. apply: function(id){
  1197. return spawn('a.user-id.link', {
  1198. href: '#!',
  1199. title: this.username + ': refresh'
  1200. }, [['i.hidden', zeroPad(id, 6)], id]);
  1201. }
  1202. },
  1203. username: {
  1204. label: 'Username',
  1205. filter: true, // add filter: true to individual items to add a filter
  1206. // th: { style: { width: styles.username }},
  1207. // td: { style: { width: styles.username }},
  1208. apply: function(username, tr){
  1209. //console.log(tr);
  1210. // var _username = truncateText(username);
  1211. return spawn('a.username.link.truncate.edit-user', {
  1212. href: '#!',

Large files files are truncated, but you can click here to view the full file