mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Worked on user administration page, a grafana server admin can now add and edit organization roles for any user, #2014
This commit is contained in:
parent
788e7fd36d
commit
a8aab0cb2b
@ -9,16 +9,6 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/util"
|
"github.com/grafana/grafana/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func AdminSearchUsers(c *middleware.Context) {
|
|
||||||
query := m.SearchUsersQuery{Query: "", Page: 0, Limit: 1000}
|
|
||||||
if err := bus.Dispatch(&query); err != nil {
|
|
||||||
c.JsonApiErr(500, "Failed to fetch users", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(200, query.Result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func AdminCreateUser(c *middleware.Context, form dtos.AdminCreateUserForm) {
|
func AdminCreateUser(c *middleware.Context, form dtos.AdminCreateUserForm) {
|
||||||
cmd := m.CreateUserCommand{
|
cmd := m.CreateUserCommand{
|
||||||
Login: form.Login,
|
Login: form.Login,
|
||||||
|
@ -53,7 +53,8 @@ func Register(r *macaron.Macaron) {
|
|||||||
|
|
||||||
// authed api
|
// authed api
|
||||||
r.Group("/api", func() {
|
r.Group("/api", func() {
|
||||||
// user
|
|
||||||
|
// user (signed in)
|
||||||
r.Group("/user", func() {
|
r.Group("/user", func() {
|
||||||
r.Get("/", wrap(GetSignedInUser))
|
r.Get("/", wrap(GetSignedInUser))
|
||||||
r.Put("/", bind(m.UpdateUserCommand{}), wrap(UpdateSignedInUser))
|
r.Put("/", bind(m.UpdateUserCommand{}), wrap(UpdateSignedInUser))
|
||||||
@ -64,8 +65,9 @@ func Register(r *macaron.Macaron) {
|
|||||||
r.Put("/password", bind(m.ChangeUserPasswordCommand{}), ChangeUserPassword)
|
r.Put("/password", bind(m.ChangeUserPasswordCommand{}), ChangeUserPassword)
|
||||||
})
|
})
|
||||||
|
|
||||||
// users
|
// users (admin permission required)
|
||||||
r.Group("/users", func() {
|
r.Group("/users", func() {
|
||||||
|
r.Get("/", wrap(SearchUsers))
|
||||||
r.Get("/:id", wrap(GetUserById))
|
r.Get("/:id", wrap(GetUserById))
|
||||||
r.Get("/:id/orgs", wrap(GetUserOrgList))
|
r.Get("/:id/orgs", wrap(GetUserOrgList))
|
||||||
r.Put("/:id", bind(m.UpdateUserCommand{}), wrap(UpdateUser))
|
r.Put("/:id", bind(m.UpdateUserCommand{}), wrap(UpdateUser))
|
||||||
@ -84,6 +86,9 @@ func Register(r *macaron.Macaron) {
|
|||||||
// create new org
|
// create new org
|
||||||
r.Post("/orgs", bind(m.CreateOrgCommand{}), wrap(CreateOrg))
|
r.Post("/orgs", bind(m.CreateOrgCommand{}), wrap(CreateOrg))
|
||||||
|
|
||||||
|
// search all orgs
|
||||||
|
r.Get("/orgs", reqGrafanaAdmin, wrap(SearchOrgs))
|
||||||
|
|
||||||
// orgs (admin routes)
|
// orgs (admin routes)
|
||||||
r.Group("/orgs/:orgId", func() {
|
r.Group("/orgs/:orgId", func() {
|
||||||
r.Put("/", bind(m.UpdateOrgCommand{}), wrap(UpdateOrg))
|
r.Put("/", bind(m.UpdateOrgCommand{}), wrap(UpdateOrg))
|
||||||
@ -133,7 +138,6 @@ func Register(r *macaron.Macaron) {
|
|||||||
// admin api
|
// admin api
|
||||||
r.Group("/api/admin", func() {
|
r.Group("/api/admin", func() {
|
||||||
r.Get("/settings", AdminGetSettings)
|
r.Get("/settings", AdminGetSettings)
|
||||||
r.Get("/users", AdminSearchUsers)
|
|
||||||
r.Post("/users", bind(dtos.AdminCreateUserForm{}), AdminCreateUser)
|
r.Post("/users", bind(dtos.AdminCreateUserForm{}), AdminCreateUser)
|
||||||
r.Put("/users/:id/password", bind(dtos.AdminUpdateUserPasswordForm{}), AdminUpdateUserPassword)
|
r.Put("/users/:id/password", bind(dtos.AdminUpdateUserPasswordForm{}), AdminUpdateUserPassword)
|
||||||
r.Put("/users/:id/permissions", bind(dtos.AdminUpdateUserPermissionsForm{}), AdminUpdateUserPermissions)
|
r.Put("/users/:id/permissions", bind(dtos.AdminUpdateUserPermissionsForm{}), AdminUpdateUserPermissions)
|
||||||
|
@ -72,3 +72,18 @@ func updateOrgHelper(cmd m.UpdateOrgCommand) Response {
|
|||||||
|
|
||||||
return ApiSuccess("Organization updated")
|
return ApiSuccess("Organization updated")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SearchOrgs(c *middleware.Context) Response {
|
||||||
|
query := m.SearchOrgsQuery{
|
||||||
|
Query: c.Query("query"),
|
||||||
|
Name: c.Query("name"),
|
||||||
|
Page: 0,
|
||||||
|
Limit: 1000,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := bus.Dispatch(&query); err != nil {
|
||||||
|
return ApiError(500, "Failed to search orgs", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Json(200, query.Result)
|
||||||
|
}
|
||||||
|
@ -84,6 +84,9 @@ func updateOrgUserHelper(cmd m.UpdateOrgUserCommand) Response {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := bus.Dispatch(&cmd); err != nil {
|
if err := bus.Dispatch(&cmd); err != nil {
|
||||||
|
if err == m.ErrLastOrgAdmin {
|
||||||
|
return ApiError(400, "Cannot change role so that there is no organization admin left", nil)
|
||||||
|
}
|
||||||
return ApiError(500, "Failed update org user", err)
|
return ApiError(500, "Failed update org user", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,3 +142,13 @@ func ChangeUserPassword(c *middleware.Context, cmd m.ChangeUserPasswordCommand)
|
|||||||
|
|
||||||
c.JsonOK("User password changed")
|
c.JsonOK("User password changed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GET /api/users
|
||||||
|
func SearchUsers(c *middleware.Context) Response {
|
||||||
|
query := m.SearchUsersQuery{Query: "", Page: 0, Limit: 1000}
|
||||||
|
if err := bus.Dispatch(&query); err != nil {
|
||||||
|
return ApiError(500, "Failed to fetch users", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Json(200, query.Result)
|
||||||
|
}
|
||||||
|
@ -48,8 +48,13 @@ type GetOrgByNameQuery struct {
|
|||||||
Result *Org
|
Result *Org
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetOrgListQuery struct {
|
type SearchOrgsQuery struct {
|
||||||
Result []*Org
|
Query string
|
||||||
|
Name string
|
||||||
|
Limit int
|
||||||
|
Page int
|
||||||
|
|
||||||
|
Result []*OrgDTO
|
||||||
}
|
}
|
||||||
|
|
||||||
type OrgDTO struct {
|
type OrgDTO struct {
|
||||||
|
@ -14,12 +14,23 @@ func init() {
|
|||||||
bus.AddHandler("sql", CreateOrg)
|
bus.AddHandler("sql", CreateOrg)
|
||||||
bus.AddHandler("sql", UpdateOrg)
|
bus.AddHandler("sql", UpdateOrg)
|
||||||
bus.AddHandler("sql", GetOrgByName)
|
bus.AddHandler("sql", GetOrgByName)
|
||||||
bus.AddHandler("sql", GetOrgList)
|
bus.AddHandler("sql", SearchOrgs)
|
||||||
bus.AddHandler("sql", DeleteOrg)
|
bus.AddHandler("sql", DeleteOrg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetOrgList(query *m.GetOrgListQuery) error {
|
func SearchOrgs(query *m.SearchOrgsQuery) error {
|
||||||
return x.Find(&query.Result)
|
query.Result = make([]*m.OrgDTO, 0)
|
||||||
|
sess := x.Table("org")
|
||||||
|
if query.Query != "" {
|
||||||
|
sess.Where("name LIKE ?", query.Query+"%")
|
||||||
|
}
|
||||||
|
if query.Name != "" {
|
||||||
|
sess.Where("name=?", query.Name)
|
||||||
|
}
|
||||||
|
sess.Limit(query.Limit, query.Limit*query.Page)
|
||||||
|
sess.Cols("id", "name")
|
||||||
|
err := sess.Find(&query.Result)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetOrgById(query *m.GetOrgByIdQuery) error {
|
func GetOrgById(query *m.GetOrgByIdQuery) error {
|
||||||
|
@ -142,11 +142,18 @@ func TestAccountDataAccess(t *testing.T) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Cannot delete last admin account user", func() {
|
Convey("Cannot delete last admin org user", func() {
|
||||||
cmd := m.RemoveOrgUserCommand{OrgId: ac1.OrgId, UserId: ac1.Id}
|
cmd := m.RemoveOrgUserCommand{OrgId: ac1.OrgId, UserId: ac1.Id}
|
||||||
err := RemoveOrgUser(&cmd)
|
err := RemoveOrgUser(&cmd)
|
||||||
So(err, ShouldEqual, m.ErrLastOrgAdmin)
|
So(err, ShouldEqual, m.ErrLastOrgAdmin)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Convey("Cannot update role so no one is admin user", func() {
|
||||||
|
cmd := m.UpdateOrgUserCommand{OrgId: ac1.OrgId, UserId: ac1.Id, Role: m.ROLE_VIEWER}
|
||||||
|
err := UpdateOrgUser(&cmd)
|
||||||
|
So(err, ShouldEqual, m.ErrLastOrgAdmin)
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -48,7 +48,11 @@ func UpdateOrgUser(cmd *m.UpdateOrgUserCommand) error {
|
|||||||
orgUser.Role = cmd.Role
|
orgUser.Role = cmd.Role
|
||||||
orgUser.Updated = time.Now()
|
orgUser.Updated = time.Now()
|
||||||
_, err = sess.Id(orgUser.Id).Update(&orgUser)
|
_, err = sess.Id(orgUser.Id).Update(&orgUser)
|
||||||
return err
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return validateOneAdminLeftInOrg(cmd.OrgId, sess)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,16 +76,20 @@ func RemoveOrgUser(cmd *m.RemoveOrgUserCommand) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate that there is an admin user left
|
return validateOneAdminLeftInOrg(cmd.OrgId, sess)
|
||||||
res, err := sess.Query("SELECT 1 from org_user WHERE org_id=? and role='Admin'", cmd.OrgId)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(res) == 0 {
|
|
||||||
return m.ErrLastOrgAdmin
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateOneAdminLeftInOrg(orgId int64, sess *xorm.Session) error {
|
||||||
|
// validate that there is an admin user left
|
||||||
|
res, err := sess.Query("SELECT 1 from org_user WHERE org_id=? and role='Admin'", orgId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(res) == 0 {
|
||||||
|
return m.ErrLastOrgAdmin
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
define([
|
define([
|
||||||
'angular',
|
'angular',
|
||||||
|
'lodash',
|
||||||
],
|
],
|
||||||
function (angular) {
|
function (angular, _) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var module = angular.module('grafana.controllers');
|
var module = angular.module('grafana.controllers');
|
||||||
|
|
||||||
module.controller('AdminEditUserCtrl', function($scope, $routeParams, backendSrv, $location) {
|
module.controller('AdminEditUserCtrl', function($scope, $routeParams, backendSrv, $location) {
|
||||||
$scope.user = {};
|
$scope.user = {};
|
||||||
|
$scope.newOrg = { name: '', role: 'Editor' };
|
||||||
$scope.permissions = {};
|
$scope.permissions = {};
|
||||||
|
|
||||||
$scope.init = function() {
|
$scope.init = function() {
|
||||||
@ -64,6 +66,44 @@ function (angular) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.updateOrgUser= function(orgUser) {
|
||||||
|
backendSrv.patch('/api/orgs/' + orgUser.orgId + '/users/' + $scope.user_id, orgUser).then(function() {
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.removeOrgUser = function(orgUser) {
|
||||||
|
backendSrv.delete('/api/orgs/' + orgUser.orgId + '/users/' + $scope.user_id).then(function() {
|
||||||
|
$scope.getUserOrgs($scope.user_id);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.orgsSearchCache = [];
|
||||||
|
|
||||||
|
$scope.searchOrgs = function(queryStr, callback) {
|
||||||
|
if ($scope.orgsSearchCache.length > 0) {
|
||||||
|
callback(_.pluck($scope.orgsSearchCache, "name"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
backendSrv.get('/api/orgs', {query: ''}).then(function(result) {
|
||||||
|
$scope.orgsSearchCache = result;
|
||||||
|
callback(_.pluck(result, "name"));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.addOrgUser = function() {
|
||||||
|
if (!$scope.addOrgForm.$valid) { return; }
|
||||||
|
|
||||||
|
var orgInfo = _.findWhere($scope.orgsSearchCache, {name: $scope.newOrg.name});
|
||||||
|
if (!orgInfo) { return; }
|
||||||
|
|
||||||
|
$scope.newOrg.loginOrEmail = $scope.user.login;
|
||||||
|
|
||||||
|
backendSrv.post('/api/orgs/' + orgInfo.id + '/users/', $scope.newOrg).then(function() {
|
||||||
|
$scope.getUserOrgs($scope.user_id);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
$scope.init();
|
$scope.init();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -13,7 +13,7 @@ function (angular) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
$scope.getUsers = function() {
|
$scope.getUsers = function() {
|
||||||
backendSrv.get('/api/admin/users').then(function(users) {
|
backendSrv.get('/api/users').then(function(users) {
|
||||||
$scope.users = users;
|
$scope.users = users;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -80,24 +80,52 @@
|
|||||||
Permissions
|
Permissions
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div class="tight-form last">
|
<div>
|
||||||
<ul class="tight-form-list">
|
<div class="tight-form last">
|
||||||
<li class="tight-form-item last">
|
<ul class="tight-form-list">
|
||||||
Grafana Admin
|
<li class="tight-form-item last">
|
||||||
<input class="cr1" id="permissions.isGrafanaAdmin" type="checkbox"
|
Grafana Admin
|
||||||
ng-model="permissions.isGrafanaAdmin" ng-checked="permissions.isGrafanaAdmin">
|
<input class="cr1" id="permissions.isGrafanaAdmin" type="checkbox"
|
||||||
<label for="permissions.isGrafanaAdmin" class="cr1"></label>
|
ng-model="permissions.isGrafanaAdmin" ng-checked="permissions.isGrafanaAdmin">
|
||||||
</li>
|
<label for="permissions.isGrafanaAdmin" class="cr1"></label>
|
||||||
</ul>
|
</li>
|
||||||
<div class="clearfix"></div>
|
</ul>
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<button type="submit" class="pull-right btn btn-success" ng-click="updatePermissions()">Update</button>
|
||||||
|
<br>
|
||||||
</div>
|
</div>
|
||||||
<br>
|
|
||||||
<button type="submit" class="pull-right btn btn-success" ng-click="updatePermissions()">Update</button>
|
|
||||||
|
|
||||||
<h2>
|
<h2>
|
||||||
Organizations
|
Organizations
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
|
<form name="addOrgForm">
|
||||||
|
<div class="tight-form">
|
||||||
|
<ul class="tight-form-list">
|
||||||
|
<li class="tight-form-item" style="width: 160px">
|
||||||
|
Add organization
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<input type="text" ng-model="newOrg.name" bs-typeahead="searchOrgs"
|
||||||
|
required class="input-xlarge tight-form-input" placeholder="organization name">
|
||||||
|
</li>
|
||||||
|
<li class="tight-form-item">
|
||||||
|
Role
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<select type="text" ng-model="newOrg.role" class="input-small tight-form-input" ng-options="f for f in ['Viewer', 'Editor', 'Admin']">
|
||||||
|
</select>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button class="btn btn-success tight-form-btn" ng-click="addOrgUser()">Add</button>
|
||||||
|
</li>
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
<table class="grafana-options-table form-inline">
|
<table class="grafana-options-table form-inline">
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
@ -109,15 +137,16 @@
|
|||||||
{{org.name}} <span class="label label-info" ng-show="org.orgId === user.orgId">Current</span>
|
{{org.name}} <span class="label label-info" ng-show="org.orgId === user.orgId">Current</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<select type="text" ng-model="org.role" class="input-small" ng-options="f for f in ['Viewer', 'Editor', 'Admin']" ng-change="updateOrgRole(org)">
|
<select type="text" ng-model="org.role" class="input-small" ng-options="f for f in ['Viewer', 'Editor', 'Admin']" ng-change="updateOrgUser(org)">
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
<td style="width: 1%">
|
<td style="width: 1%">
|
||||||
<a ng-click="removeUser(user)" class="btn btn-danger btn-mini">
|
<a ng-click="removeOrgUser(org)" class="btn btn-danger btn-mini">
|
||||||
<i class="fa fa-remove"></i>
|
<i class="fa fa-remove"></i>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user