From 637c75bca176d3ef7a06e8b4fa2d60fece1c89a7 Mon Sep 17 00:00:00 2001 From: Thomas Pulzer Date: Fri, 8 Jul 2016 13:22:34 +0200 Subject: [PATCH] Implemented visual feedback if a user is disabled in admin user menu. Implemented visuals for enabling/disabling user from admin user list. Added the controller functions for enabling/disabling a user. Added the route for changing user status (enabled/disabled) and added an additional route handler in the user controller. Finished the visuals to reflect current user status and changed user status respectively. Changed the single icon for enabling/disabling a user into a menu where deletion and state toggling of a user is selectable. Added displaying of disabled user count. Improved style of user action menu. Added proper counting of disabled users. Removed visual indicator for disabled users. Moved pseudo-group detection for disabled users from frontend to the controller. Changed units for newly introduced css values from em to px. Removed unnecessary png and optimized svg with scour. Changed the userlist template to display the user action menu with correct width. Style fixes for better readability and coding style conformity. Changed the icons for enabling, disabling and deleting a user in the action menu. --- core/img/actions/user-plus.svg | 12 ++ core/img/actions/user-times.svg | 12 ++ settings/Controller/UsersController.php | 154 ++++++++++++++++++-- settings/css/settings.css | 45 +++++- settings/js/users/groups.js | 17 ++- settings/js/users/users.js | 84 ++++++++--- settings/routes.php | 1 + settings/templates/users/part.grouplist.php | 9 ++ settings/templates/users/part.userlist.php | 18 ++- settings/users.php | 15 ++ 10 files changed, 325 insertions(+), 42 deletions(-) create mode 100644 core/img/actions/user-plus.svg create mode 100644 core/img/actions/user-times.svg diff --git a/core/img/actions/user-plus.svg b/core/img/actions/user-plus.svg new file mode 100644 index 0000000000..c236d9fc1d --- /dev/null +++ b/core/img/actions/user-plus.svg @@ -0,0 +1,12 @@ + + + + + +image/svg+xml + + + + + + diff --git a/core/img/actions/user-times.svg b/core/img/actions/user-times.svg new file mode 100644 index 0000000000..d99463b55c --- /dev/null +++ b/core/img/actions/user-times.svg @@ -0,0 +1,12 @@ + + + + + +image/svg+xml + + + + + + diff --git a/settings/Controller/UsersController.php b/settings/Controller/UsersController.php index 7f6602a510..5949d9c974 100644 --- a/settings/Controller/UsersController.php +++ b/settings/Controller/UsersController.php @@ -225,6 +225,7 @@ class UsersController extends Controller { 'email' => $displayName, 'isRestoreDisabled' => !$restorePossible, 'isAvatarAvailable' => $avatarAvailable, + 'isEnabled' => $user->isEnabled(), ]; } @@ -253,11 +254,6 @@ class UsersController extends Controller { * TODO: Tidy up and write unit tests - code is mainly static method calls */ public function index($offset = 0, $limit = 10, $gid = '', $pattern = '', $backend = '') { - // FIXME: The JS sends the group '_everyone' instead of no GID for the "all users" group. - if($gid === '_everyone') { - $gid = ''; - } - // Remove backends if(!empty($backend)) { $activeBackends = $this->userManager->getBackends(); @@ -272,15 +268,18 @@ class UsersController extends Controller { $users = []; if ($this->isAdmin) { - - if($gid !== '') { + if($gid !== '' && $gid !== 'disabledUsers') { $batch = $this->getUsersForUID($this->groupManager->displayNamesInGroup($gid, $pattern, $limit, $offset)); } else { $batch = $this->userManager->search($pattern, $limit, $offset); } foreach ($batch as $user) { - $users[] = $this->formatUserForIndex($user); + if( ($gid !== 'disabledUsers' && $user->isEnabled()) || + ($gid === 'disabledUsers' && !$user->isEnabled()) + ) { + $users[] = $this->formatUserForIndex($user); + } } } else { @@ -293,7 +292,7 @@ class UsersController extends Controller { $subAdminOfGroups = $gids; // Set the $gid parameter to an empty value if the subadmin has no rights to access a specific group - if($gid !== '' && !in_array($gid, $subAdminOfGroups)) { + if($gid !== '' && $gid !== 'disabledUsers' && !in_array($gid, $subAdminOfGroups)) { $gid = ''; } @@ -318,7 +317,11 @@ class UsersController extends Controller { $this->groupManager->getUserGroupIds($user), $subAdminOfGroups )); - $users[] = $this->formatUserForIndex($user, $userGroups); + if( ($gid !== 'disabledUsers' && $user->isEnabled()) || + ($gid === 'disabledUsers' && !$user->isEnabled()) + ) { + $users[] = $this->formatUserForIndex($user, $userGroups); + } } } @@ -513,6 +516,137 @@ class UsersController extends Controller { } /** + * @NoAdminRequired + * + * @param string $id + * @return DataResponse + */ + public function disable($id) { + $userId = $this->userSession->getUser()->getUID(); + $user = $this->userManager->get($id); + + if($userId === $id) { + return new DataResponse( + array( + 'status' => 'error', + 'data' => array( + 'message' => (string)$this->l10n->t('Unable to disable user.') + ) + ), + Http::STATUS_FORBIDDEN + ); + } + + if(!$this->isAdmin && !$this->groupManager->getSubAdmin()->isUserAccessible($this->userSession->getUser(), $user)) { + return new DataResponse( + array( + 'status' => 'error', + 'data' => array( + 'message' => (string)$this->l10n->t('Authentication error') + ) + ), + Http::STATUS_FORBIDDEN + ); + } + + if($user) { + $user->setEnabled(false); + return new DataResponse( + array( + 'status' => 'success', + 'data' => array( + 'username' => $id, + 'enabled' => 0 + ) + ) + ); + } else { + return new DataResponse( + array( + 'status' => 'error', + 'data' => array( + 'message' => (string)$this->l10n->t('Unable to disable user.') + ) + ) + ); + } + } + + /** + * @NoAdminRequired + * + * @param string $id + * @return DataResponse + */ + public function enable($id) { + $userId = $this->userSession->getUser()->getUID(); + $user = $this->userManager->get($id); + + if($userId === $id) { + return new DataResponse( + array( + 'status' => 'error', + 'data' => array( + 'message' => (string)$this->l10n->t('Unable to enable user.') + ) + ), + Http::STATUS_FORBIDDEN + ); + } + + if(!$this->isAdmin && !$this->groupManager->getSubAdmin()->isUserAccessible($this->userSession->getUser(), $user)) { + return new DataResponse( + array( + 'status' => 'error', + 'data' => array( + 'message' => (string)$this->l10n->t('Authentication error') + ) + ), + Http::STATUS_FORBIDDEN + ); + } + + if($user) { + $user->setEnabled(true); + return new DataResponse( + array( + 'status' => 'success', + 'data' => array( + 'username' => $id, + 'enabled' => 1 + ) + ) + ); + } else { + return new DataResponse( + array( + 'status' => 'error', + 'data' => array( + 'message' => (string)$this->l10n->t('Unable to enable user.') + ) + ) + ); + } + } + + /** + * @NoAdminRequired + * + * @param string $id + * @param int $enabled + * @return DataResponse + */ + public function setEnabled($id, $enabled) { + if((bool)$enabled) { + return $this->enable($id); + } else { + return $this->disable($id); + } + } + + /** + * Set the mail address of a user + * * @NoAdminRequired * @NoSubadminRequired * @PasswordConfirmationRequired diff --git a/settings/css/settings.css b/settings/css/settings.css index 5693224b94..4a245fcf52 100644 --- a/settings/css/settings.css +++ b/settings/css/settings.css @@ -458,15 +458,16 @@ table.grid th, table.grid td { font-weight: normal; } td.name, td.password { padding-left:.8em; } -td.password>img,td.displayName>img, td.remove>a, td.quota>img { visibility:hidden; vertical-align: middle;} +td.password>img,td.displayName>img, td.quota>img { visibility:hidden; } td.password, td.quota, td.displayName { width:12em; cursor:pointer; } -td.password>span, td.quota>span, rd.displayName>span { margin-right: 1.2em; color: #C7C7C7; } +td.password>span, td.quota>span, td.displayName>span { margin-right: 1.2em; color: #C7C7C7; } span.usersLastLoginTooltip { white-space: nowrap; } /* dropdowns will be relative to this element */ #userlist { position: relative; } + #userlist .mailAddress, #userlist .storageLocation, #userlist .userBackend, @@ -501,13 +502,41 @@ span.usersLastLoginTooltip { white-space: nowrap; } display: none; } -tr:hover>td.password>span, tr:hover>td.displayName>span { margin:0; cursor:pointer; } -tr:hover>td.remove>a, tr:hover>td.password>img,tr:hover>td.displayName>img, tr:hover>td.quota>img { visibility:visible; cursor:pointer; } -td.remove { - width: 25px; +.bubble { + z-index:1; + right: -5px; } -tr:hover>td.remove>a { - float: left; +.bubble:after { + right: 5px; +} + +.popovermenu a.menuitem { + padding: 10px; + margin: -5px -10px -5px -15px; + height: 20px; +} + +#userlist .popovermenu { + margin-top: 1px; +} + +#userlist .popovermenu>ul.userActionsMenu { + right: 10px; + padding: 0 10px 10px; +} + +#userlist .popovermenu>ul.userActionsMenu a span { + margin-left: 5px; +} + +#userlist .popovermenu { + display:none; +} + +tr:hover>td.password>span, tr:hover>td.displayName>span { margin:0; cursor:pointer; } +tr:hover>td.password>img,tr:hover>td.displayName>img, tr:hover>td.quota>img { visibility:visible; cursor:pointer; } +td.userActions { + width: 25px; } div.recoveryPassword { left:50em; display:block; position:absolute; top:-1px; } diff --git a/settings/js/users/groups.js b/settings/js/users/groups.js index aac1609bce..b54f26b4cc 100644 --- a/settings/js/users/groups.js +++ b/settings/js/users/groups.js @@ -48,7 +48,8 @@ GroupList = { }, getUserCount: function ($groupLiElement) { - return parseInt($groupLiElement.data('usercount'), 10); + var count = parseInt($groupLiElement.data('usercount'), 10); + return isNaN(count) ? 0 : count; }, modGroupCount: function(gid, diff) { @@ -208,10 +209,17 @@ GroupList = { }, + showDisabledUsers: function () { + UserList.empty(); + UserList.update('disabledUsers'); + $userGroupList.find('li').removeClass('active'); + GroupList.getGroupLI('disabledUsers').addClass('active'); + }, + showGroup: function (gid) { GroupList.activeGID = gid; UserList.empty(); - UserList.update(gid); + UserList.update(gid === '_everyone' ? '' : gid); $userGroupList.find('li').removeClass('active'); if (gid !== undefined) { //TODO: treat Everyone properly @@ -364,6 +372,11 @@ $(document).ready( function () { GroupList.showGroup(GroupList.getElementGID(this)); }); + // show disabled users + $userGroupList.on('click', '.disabledusers', function () { + GroupList.showDisabledUsers(); + }); + $('#newgroupname').on('input', function(){ GroupList.handleAddGroupInput(this.value); }); diff --git a/settings/js/users/users.js b/settings/js/users/users.js index 46f4e5977b..552d28ad50 100644 --- a/settings/js/users/users.js +++ b/settings/js/users/users.js @@ -48,11 +48,12 @@ var UserList = { * 'backend': 'LDAP', * 'email': 'username@example.org' * 'isRestoreDisabled':false + * 'isEnabled': true * } */ add: function (user) { - if (this.currentGid && this.currentGid !== '_everyone' && _.indexOf(user.groups, this.currentGid) < 0) { - return; + if (this.currentGid && this.currentGid !== '_everyone' && this.currentGid !== 'disabledUsers' && _.indexOf(user.groups, this.currentGid) < 0) { + return false; } var $tr = $userListBody.find('tr:first-child').clone(); @@ -77,6 +78,7 @@ var UserList = { $tr.data('displayname', user.displayname); $tr.data('mailAddress', user.email); $tr.data('restoreDisabled', user.isRestoreDisabled); + $tr.data('userEnabled', user.isEnabled); $tr.find('.name').text(user.name); $tr.find('td.displayName > span').text(user.displayname); $tr.find('td.mailAddress > span').text(user.email); @@ -97,18 +99,17 @@ var UserList = { $tdSubadmins.find('.action').tooltip({placement: 'top'}); /** - * remove action + * user actions menu */ - if ($tr.find('td.remove img').length === 0 && OC.currentUser !== user.name) { - var deleteImage = $('').attr({ - src: OC.imagePath('core', 'actions/delete') + if ($tr.find('td.userActions > span > img').length === 0 && OC.currentUser !== user.name) { + var menuImage = $('').attr({ + src: OC.imagePath('core', 'actions/more') }); - var deleteLink = $('') - .attr({ href: '#', 'original-title': t('settings', 'Delete')}) - .append(deleteImage); - $tr.find('td.remove').append(deleteLink); + var menuLink = $('') + .append(menuImage); + $tr.find('td.userActions > span').replaceWith(menuLink); } else if (OC.currentUser === user.name) { - $tr.find('td.remove a').remove(); + $tr.find('td.userActions').empty(); } /** @@ -160,14 +161,6 @@ var UserList = { * append generated row to user list */ $tr.appendTo($userList); - if(UserList.isEmpty === true) { - //when the list was emptied, one row was left, necessary to keep - //add working and the layout unbroken. We need to remove this item - $tr.show(); - $userListBody.find('tr:first').remove(); - UserList.isEmpty = false; - UserList.checkUsersToLoad(); - } $quotaSelect.on('change', UserList.onQuotaSelect); @@ -338,6 +331,9 @@ var UserList = { getRestoreDisabled: function(element) { return ($(element).closest('tr').data('restoreDisabled') || ''); }, + getUserEnabled: function(element) { + return ($(element).closest('tr').data('userEnabled') || ''); + }, initDeleteHandling: function() { //set up handler UserDeleteHandler = new DeleteHandler('/settings/users/users', 'username', @@ -351,7 +347,7 @@ var UserList = { UserList.undoRemove); //when to mark user for delete - $userListBody.on('click', '.delete', function () { + $userListBody.on('click', '.action-remove', function () { // Call function for handling delete/undo var uid = UserList.getUID(this); @@ -908,6 +904,54 @@ $(document).ready(function () { UserList._triggerGroupEdit($td, isSubadminSelect); }); + $userListBody.on('click', '.toggleUserActions', function (event) { + event.stopPropagation(); + var $td = $(this).closest('td'); + var $tr = $($td).closest('tr'); + var menudiv = $td.find('.popovermenu'); + + if(menudiv.is(':visible')) { + menudiv.fadeOut(100); + return; + } + menudiv.find('.action-togglestate').empty(); + if($tr.data('userEnabled')) { + $('.action-togglestate', $td).html(''+t('settings', 'Disable')+''); + } else { + $('.action-togglestate', $td).html(''+t('settings', 'Enable')+''); + } + menudiv.click(function() { menudiv.fadeOut(100); }); + menudiv.hover('', function() { menudiv.fadeOut(100); }); + menudiv.fadeIn(100); + }); + + $userListBody.on('click', '.action-togglestate', function (event) { + event.stopPropagation(); + var $td = $(this).closest('td'); + var $tr = $td.closest('tr'); + var uid = UserList.getUID($td); + var setEnabled = UserList.getUserEnabled($td) ? 0 : 1; + $.post( + OC.generateUrl('/settings/users/{id}/setEnabled', {id: uid}), + {username: uid, enabled: setEnabled}, + function (result) { + if (result && result.status==='success'){ + var count = GroupList.getUserCount(GroupList.getGroupLI('disabledUsers')); + $tr.remove(); + if(result.data.enabled == 1) { + $tr.data('userEnabled', true); + GroupList.setUserCount(GroupList.getGroupLI('disabledUsers'), count-1); + } else { + $tr.data('userEnabled', false); + GroupList.setUserCount(GroupList.getGroupLI('disabledUsers'), count+1); + } + } else { + OC.dialogs.alert(result.data.message, t('settings', 'Unable to change status of {user}', {user: uid})); + } + } + ); + }); + // init the quota field select box after it is shown the first time $('#app-settings').one('show', function() { $(this).find('#default_quota').singleSelect().on('change', UserList.onQuotaSelect); diff --git a/settings/routes.php b/settings/routes.php index ba0761856d..fb85b11f39 100644 --- a/settings/routes.php +++ b/settings/routes.php @@ -53,6 +53,7 @@ $application->registerRoutes($this, [ ['name' => 'Users#setEMailAddress', 'url' => '/settings/users/{id}/mailAddress', 'verb' => 'PUT'], ['name' => 'Users#setUserSettings', 'url' => '/settings/users/{username}/settings', 'verb' => 'PUT'], ['name' => 'Users#getVerificationCode', 'url' => '/settings/users/{account}/verify', 'verb' => 'GET'], + ['name' => 'Users#setEnabled', 'url' => '/settings/users/{id}/setEnabled', 'verb' => 'POST'], ['name' => 'Users#stats', 'url' => '/settings/users/stats', 'verb' => 'GET'], ['name' => 'LogSettings#setLogLevel', 'url' => '/settings/admin/log/level', 'verb' => 'POST'], ['name' => 'LogSettings#getEntries', 'url' => '/settings/admin/log/entries', 'verb' => 'GET'], diff --git a/settings/templates/users/part.grouplist.php b/settings/templates/users/part.grouplist.php index beb0f275a3..0b953c0298 100644 --- a/settings/templates/users/part.grouplist.php +++ b/settings/templates/users/part.grouplist.php @@ -37,6 +37,15 @@ + + +
  • + t('Disabled')); ?> + + 0) { p($disabledUsersGroup['usercount']); } ?> + +
  • +
  • diff --git a/settings/templates/users/part.userlist.php b/settings/templates/users/part.userlist.php index 7e7e1561e2..b908109ad2 100644 --- a/settings/templates/users/part.userlist.php +++ b/settings/templates/users/part.userlist.php @@ -14,7 +14,7 @@ t('Storage location')); ?> t('User backend')); ?> t('Last login')); ?> -   + @@ -63,7 +63,21 @@ - + + + diff --git a/settings/users.php b/settings/users.php index 1986592af7..c3b32d0473 100644 --- a/settings/users.php +++ b/settings/users.php @@ -59,12 +59,15 @@ $config = \OC::$server->getConfig(); $isAdmin = OC_User::isAdminUser(OC_User::getUser()); +$isDisabled = !OC_User::isEnabled(OC_User::getUser()); + $groupsInfo = new \OC\Group\MetaData( OC_User::getUser(), $isAdmin, $groupManager, \OC::$server->getUserSession() ); + $groupsInfo->setSorting($sortGroupsBy); list($adminGroup, $groups) = $groupsInfo->get(); @@ -92,6 +95,17 @@ if($isAdmin) { } $subAdmins = false; } +$disabledUsers = 0; +foreach (OC_User::getUsers() as $uid) { + if(!$userManager->get($uid)->isEnabled()) { + $disabledUsers++; + } +} +$disabledUsersGroup = array( + 'id' => 'disabledUsers', + 'name' => 'disabledUsers', + 'usercount' => $disabledUsers +); // load preset quotas $quotaPreset=$config->getAppValue('files', 'quota_preset', '1 GB, 5 GB, 10 GB'); @@ -111,6 +125,7 @@ $tmpl = new OC_Template("settings", "users/main", "user"); $tmpl->assign('groups', $groups); $tmpl->assign('sortGroups', $sortGroupsBy); $tmpl->assign('adminGroup', $adminGroup); +$tmpl->assign('disabledUsersGroup', $disabledUsersGroup); $tmpl->assign('isAdmin', (int)$isAdmin); $tmpl->assign('subadmins', $subAdmins); $tmpl->assign('numofgroups', count($groups) + count($adminGroup));