UserAdmin: UI for disabling users (#17333)
* Feat: initial disable user UI * batch disable users * batch revoke users tokens * split batch disable user and revoke token * API: get users with auth info and isExternal flag * fix tests for batch disable users * Disable users: show is user external or not * Users: refactor /api/users/search endpoint * Users: use alias for "user" table * Chore: add BatchDisableUsers() to the bus * Users: order user list by id explicitly * Chore: switch back to /api/users/search endpoint * Users: move disable button to user profile page * Users: return AuthModule from /api/users/:id endpoint * Users: do not return unused fields * Users: mute auth badge for disabled users * Users: move disable button to the user section * Users: fix SearchUsers method after last changes * User: return auth module as array for future purposes * User: tests for SearchUsers() * User: return only latest auth module in SearchUsers() * User: fix JOIN, get only most recent auth module * Users: fix ldap badge after backed changes * Users: show tooltip for inactive disable/enable button * Users: move delete button to edit user view * Users: put deactivated badge on the user list * Users: minor refactor * Users: adjust deactivated badge style * Minor design changes
This commit is contained in:
committed by
Torkel Ödegaard
parent
dad894f1cc
commit
77375f3772
@@ -86,9 +86,7 @@ export default class AdminEditUserCtrl {
|
||||
$scope.updatePermissions = () => {
|
||||
const payload = $scope.permissions;
|
||||
|
||||
backendSrv.put('/api/admin/users/' + $scope.user_id + '/permissions', payload).then(() => {
|
||||
$location.path('/admin/users');
|
||||
});
|
||||
backendSrv.put('/api/admin/users/' + $scope.user_id + '/permissions', payload);
|
||||
};
|
||||
|
||||
$scope.create = () => {
|
||||
@@ -163,6 +161,36 @@ export default class AdminEditUserCtrl {
|
||||
});
|
||||
};
|
||||
|
||||
$scope.deleteUser = (user: any) => {
|
||||
$scope.appEvent('confirm-modal', {
|
||||
title: 'Delete',
|
||||
text: 'Do you want to delete ' + user.login + '?',
|
||||
icon: 'fa-trash',
|
||||
yesText: 'Delete',
|
||||
onConfirm: () => {
|
||||
backendSrv.delete('/api/admin/users/' + user.id).then(() => {
|
||||
$location.path('/admin/users');
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
$scope.disableUser = event => {
|
||||
const user = $scope.user;
|
||||
|
||||
// External user can not be disabled
|
||||
if (user.authModule) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
const actionEndpoint = user.isDisabled ? '/enable' : '/disable';
|
||||
backendSrv.post('/api/admin/users/' + user.id + actionEndpoint).then(() => {
|
||||
$scope.init();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.init();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ export default class AdminListUsersCtrl {
|
||||
navModel: any;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private $scope: any, private backendSrv: BackendSrv, navModelSrv: NavModelSrv) {
|
||||
constructor(private backendSrv: BackendSrv, navModelSrv: NavModelSrv) {
|
||||
this.navModel = navModelSrv.getNav('admin', 'global-users', 0);
|
||||
this.query = '';
|
||||
this.getUsers();
|
||||
@@ -40,17 +40,10 @@ export default class AdminListUsersCtrl {
|
||||
this.getUsers();
|
||||
}
|
||||
|
||||
deleteUser(user: any) {
|
||||
this.$scope.appEvent('confirm-modal', {
|
||||
title: 'Delete',
|
||||
text: 'Do you want to delete ' + user.login + '?',
|
||||
icon: 'fa-trash',
|
||||
yesText: 'Delete',
|
||||
onConfirm: () => {
|
||||
this.backendSrv.delete('/api/admin/users/' + user.id).then(() => {
|
||||
this.getUsers();
|
||||
});
|
||||
},
|
||||
});
|
||||
getAuthModule(user: any) {
|
||||
if (user.authModule && user.authModule.length) {
|
||||
return user.authModule[0];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,135 +1,179 @@
|
||||
<page-header model="navModel"></page-header>
|
||||
|
||||
<div class="page-container page-body">
|
||||
<h3 class="page-sub-heading">Edit User</h3>
|
||||
|
||||
<form name="userForm" class="gf-form-group">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">Name</span>
|
||||
<input type="text" required ng-model="user.name" class="gf-form-input max-width-25" />
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">Email</span>
|
||||
<input type="email" ng-model="user.email" class="gf-form-input max-width-25" />
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">Username</span>
|
||||
<input type="text" ng-model="user.login" class="gf-form-input max-width-25" />
|
||||
</div>
|
||||
|
||||
<h3 class="page-sub-heading">Edit User</h3>
|
||||
<div class="gf-form-button-row">
|
||||
<button type="submit" class="btn btn-primary" ng-click="update()" ng-show="!createMode">Update</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<h3 class="page-heading">Change password</h3>
|
||||
|
||||
<form name="userForm" class="gf-form-group">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">Name</span>
|
||||
<input type="text" required ng-model="user.name" class="gf-form-input max-width-25" >
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">Email</span>
|
||||
<input type="email" ng-model="user.email" class="gf-form-input max-width-25" >
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">Username</span>
|
||||
<input type="text" ng-model="user.login" class="gf-form-input max-width-25" >
|
||||
</div>
|
||||
<form name="passwordForm" class="gf-form-group">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">New password</span>
|
||||
<input type="password" required ng-minlength="4" ng-model="password" class="gf-form-input max-width-25" />
|
||||
</div>
|
||||
|
||||
<div class="gf-form-button-row">
|
||||
<button type="submit" class="btn btn-primary" ng-click="update()" ng-show="!createMode">Update</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="gf-form-button-row">
|
||||
<button type="submit" class="btn btn-primary" ng-click="setPassword()">Update</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<h3 class="page-heading">Change password</h3>
|
||||
<h3 class="page-heading">Permissions</h3>
|
||||
|
||||
<form name="passwordForm" class="gf-form-group">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">New password</span>
|
||||
<input type="password" required ng-minlength="4" ng-model="password" class="gf-form-input max-width-25">
|
||||
</div>
|
||||
<form name="passwordForm" class="gf-form-group">
|
||||
<div class="gf-form">
|
||||
<gf-form-switch
|
||||
class="gf-form"
|
||||
label="Grafana Admin"
|
||||
checked="permissions.isGrafanaAdmin"
|
||||
switch-class="max-width-6"
|
||||
on-change="updatePermissions()"
|
||||
></gf-form-switch>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="gf-form-button-row">
|
||||
<button type="submit" class="btn btn-primary" ng-click="setPassword()">Update</button>
|
||||
</div>
|
||||
</form>
|
||||
<h3 class="page-heading">Organizations</h3>
|
||||
|
||||
<h3 class="page-heading">Permissions</h3>
|
||||
<form name="addOrgForm" class="gf-form-group">
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label">Add</span>
|
||||
<input
|
||||
type="text"
|
||||
ng-model="newOrg.name"
|
||||
bs-typeahead="searchOrgs"
|
||||
required
|
||||
class="gf-form-input max-width-20"
|
||||
placeholder="organization name"
|
||||
/>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label">Role</span>
|
||||
<span class="gf-form-select-wrapper">
|
||||
<select
|
||||
type="text"
|
||||
ng-model="newOrg.role"
|
||||
class="gf-form-input width-10"
|
||||
ng-options="f for f in ['Viewer', 'Editor', 'Admin']"
|
||||
></select>
|
||||
</span>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<button class="btn btn-primary gf-form-btn" ng-click="addOrgUser()">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form name="passwordForm" class="gf-form-group">
|
||||
<div class="gf-form" >
|
||||
<gf-form-switch class="gf-form" label="Grafana Admin" checked="permissions.isGrafanaAdmin" switch-class="max-width-6"></gf-form-switch>
|
||||
</div>
|
||||
<div class="gf-form-group">
|
||||
<table class="filter-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Role</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr ng-repeat="org in orgs">
|
||||
<td>{{org.name}} <span class="label label-info" ng-show="org.orgId === user.orgId">Current</span></td>
|
||||
<td>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-select-wrapper">
|
||||
<select
|
||||
type="text"
|
||||
ng-model="org.role"
|
||||
class="gf-form-input max-width-12"
|
||||
ng-options="f for f in ['Viewer', 'Editor', 'Admin']"
|
||||
ng-change="updateOrgUser(org)"
|
||||
>
|
||||
</select>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td style="width: 1%">
|
||||
<a ng-click="removeOrgUser(org)" class="btn btn-danger btn-small">
|
||||
<i class="fa fa-remove"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-button-row">
|
||||
<button type="submit" class="btn btn-primary" ng-click="updatePermissions()">Update</button>
|
||||
</div>
|
||||
</form>
|
||||
<h3 class="page-heading">Sessions</h3>
|
||||
|
||||
<h3 class="page-heading">Organizations</h3>
|
||||
<div class="gf-form-group">
|
||||
<table class="filter-table form-inline">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Last seen</th>
|
||||
<th>Logged on</th>
|
||||
<th>IP address</th>
|
||||
<th>Browser & OS</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="session in sessions">
|
||||
<td ng-if="session.isActive">Now</td>
|
||||
<td ng-if="!session.isActive">{{session.seenAt}}</td>
|
||||
<td>{{session.createdAt}}</td>
|
||||
<td>{{session.clientIp}}</td>
|
||||
<td>{{session.browser}} on {{session.os}} {{session.osVersion}}</td>
|
||||
<td>
|
||||
<button class="btn btn-danger btn-small" ng-click="revokeUserSession(session.id)">
|
||||
<i class="fa fa-power-off"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<form name="addOrgForm" class="gf-form-group">
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label">Add</span>
|
||||
<input type="text" ng-model="newOrg.name" bs-typeahead="searchOrgs" required class="gf-form-input max-width-20" placeholder="organization name">
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label">Role</span>
|
||||
<span class="gf-form-select-wrapper">
|
||||
<select type="text" ng-model="newOrg.role" class="gf-form-input width-10" ng-options="f for f in ['Viewer', 'Editor', 'Admin']"></select>
|
||||
</span>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<button class="btn btn-primary gf-form-btn" ng-click="addOrgUser()">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<button ng-if="sessions.length" class="btn btn-danger" ng-click="revokeAllUserSessions()">
|
||||
Logout user from all devices
|
||||
</button>
|
||||
|
||||
<div class="gf-form-group">
|
||||
<table class="filter-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Role</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr ng-repeat="org in orgs">
|
||||
<td>
|
||||
{{org.name}} <span class="label label-info" ng-show="org.orgId === user.orgId">Current</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-select-wrapper">
|
||||
<select type="text" ng-model="org.role" class="gf-form-input max-width-12" ng-options="f for f in ['Viewer', 'Editor', 'Admin']" ng-change="updateOrgUser(org)">
|
||||
</select>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td style="width: 1%">
|
||||
<a ng-click="removeOrgUser(org)" class="btn btn-danger btn-small">
|
||||
<i class="fa fa-remove"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h3 class="page-heading">Sessions</h3>
|
||||
|
||||
<div class="gf-form-group">
|
||||
<table class="filter-table form-inline">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Last seen</th>
|
||||
<th>Logged on</th>
|
||||
<th>IP address</th>
|
||||
<th>Browser & OS</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="session in sessions">
|
||||
<td ng-if="session.isActive">Now</td>
|
||||
<td ng-if="!session.isActive">{{session.seenAt}}</td>
|
||||
<td>{{session.createdAt}}</td>
|
||||
<td>{{session.clientIp}}</td>
|
||||
<td>{{session.browser}} on {{session.os}} {{session.osVersion}}</td>
|
||||
<td>
|
||||
<button class="btn btn-danger btn-small" ng-click="revokeUserSession(session.id)">
|
||||
<i class="fa fa-power-off"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<button ng-if="sessions.length" class="btn btn-danger" ng-click="revokeAllUserSessions()">Logout user from all devices</button>
|
||||
<div class="gf-form-group">
|
||||
<h3 class="page-heading">User status</h3>
|
||||
|
||||
<div class="gf-form-button-row">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-danger"
|
||||
ng-if="!user.isDisabled"
|
||||
ng-click="disableUser($event)"
|
||||
bs-tooltip="user.authModule ? 'External user cannot be activated or deactivated' : ''"
|
||||
ng-class="{'disabled': user.authModule}"
|
||||
>
|
||||
Disable
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary"
|
||||
ng-if="user.isDisabled"
|
||||
ng-click="disableUser($event)"
|
||||
bs-tooltip="user.authModule ? 'External user cannot be activated or deactivated' : ''"
|
||||
ng-class="{'disabled': user.authModule}"
|
||||
>
|
||||
Enable
|
||||
</button>
|
||||
<button type="submit" class="btn btn-danger" ng-click="deleteUser(user)" ng-show="!createMode">Delete User</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -55,9 +55,10 @@
|
||||
</a>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<a ng-click="ctrl.deleteUser(user)" class="btn btn-danger btn-small">
|
||||
<i class="fa fa-remove"></i>
|
||||
</a>
|
||||
<span class="label label-tag" ng-class="{'muted': user.isDisabled}" ng-if="ctrl.getAuthModule(user) === 'ldap'">LDAP</span>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span class="label label-tag label-tag--gray" ng-if="user.isDisabled">Disabled</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -29,9 +29,25 @@
|
||||
top: 1px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.label-tag:hover {
|
||||
opacity: 0.85;
|
||||
background-color: darken($purple, 10%);
|
||||
&.muted {
|
||||
opacity: 0.85;
|
||||
background-color: darken($purple, 10%);
|
||||
color: $text-muted;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 0.85;
|
||||
background-color: darken($purple, 10%);
|
||||
}
|
||||
|
||||
&--gray {
|
||||
opacity: 0.85;
|
||||
background-color: $gray-1;
|
||||
border-color: $gray-2;
|
||||
|
||||
&:hover {
|
||||
background-color: $gray-1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user