PageRenderTime 63ms CodeModel.GetById 3ms app.highlight 48ms RepoModel.GetById 1ms app.codeStats 1ms

/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

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

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