浏览代码

Worked on user administration page, a grafana server admin can now add and edit organization roles for any user, #2014

Torkel Ödegaard 10 年之前
父节点
当前提交
a8aab0cb2b

+ 0 - 10
pkg/api/admin_users.go

@@ -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,

+ 7 - 3
pkg/api/api.go

@@ -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)

+ 15 - 0
pkg/api/org.go

@@ -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)
+}

+ 3 - 0
pkg/api/org_users.go

@@ -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)
 	}
 	}
 
 

+ 10 - 0
pkg/api/user.go

@@ -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)
+}

+ 7 - 2
pkg/models/org.go

@@ -48,8 +48,13 @@ type GetOrgByNameQuery struct {
 	Result *Org
 	Result *Org
 }
 }
 
 
-type GetOrgListQuery struct {
-	Result []*Org
+type SearchOrgsQuery struct {
+	Query string
+	Name  string
+	Limit int
+	Page  int
+
+	Result []*OrgDTO
 }
 }
 
 
 type OrgDTO struct {
 type OrgDTO struct {

+ 14 - 3
pkg/services/sqlstore/org.go

@@ -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 {
-	return x.Find(&query.Result)
+func SearchOrgs(query *m.SearchOrgsQuery) error {
+	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 {

+ 8 - 1
pkg/services/sqlstore/org_test.go

@@ -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)
+				})
+
 			})
 			})
 		})
 		})
 	})
 	})

+ 19 - 11
pkg/services/sqlstore/org_users.go

@@ -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
-		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 validateOneAdminLeftInOrg(cmd.OrgId, sess)
+	})
+}
 
 
+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
 		return err
-	})
+	}
+
+	if len(res) == 0 {
+		return m.ErrLastOrgAdmin
+	}
+
+	return err
 }
 }

+ 41 - 1
public/app/features/admin/adminEditUserCtrl.js

@@ -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();
 
 
   });
   });

+ 1 - 1
public/app/features/admin/adminUsersCtrl.js

@@ -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;
       });
       });
     };
     };

+ 43 - 14
public/app/features/admin/partials/edit_user.html

@@ -80,24 +80,52 @@
 			Permissions
 			Permissions
 		</h2>
 		</h2>
 
 
-		<div class="tight-form last">
-			<ul class="tight-form-list">
-				<li class="tight-form-item last">
-					Grafana Admin&nbsp;
-					<input class="cr1" id="permissions.isGrafanaAdmin" type="checkbox"
-					ng-model="permissions.isGrafanaAdmin" ng-checked="permissions.isGrafanaAdmin">
-					<label for="permissions.isGrafanaAdmin" class="cr1"></label>
-				</li>
-			</ul>
-			<div class="clearfix"></div>
+		<div>
+			<div class="tight-form last">
+				<ul class="tight-form-list">
+					<li class="tight-form-item last">
+						Grafana Admin&nbsp;
+						<input class="cr1" id="permissions.isGrafanaAdmin" type="checkbox"
+						ng-model="permissions.isGrafanaAdmin" ng-checked="permissions.isGrafanaAdmin">
+						<label for="permissions.isGrafanaAdmin" class="cr1"></label>
+					</li>
+				</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>