PageRenderTime 57ms CodeModel.GetById 3ms app.highlight 44ms RepoModel.GetById 1ms app.codeStats 0ms

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

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