mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'dashboard-acl' into dashboard_folders
This commit is contained in:
commit
aab6c98e45
@ -23,7 +23,8 @@ func GetDashboardAclList(c *middleware.Context) Response {
|
||||
return ApiError(500, "Failed to get Dashboard ACL", err)
|
||||
}
|
||||
|
||||
return Json(200, &query.Result)
|
||||
list := query.Result
|
||||
return Json(200, list)
|
||||
}
|
||||
|
||||
func PostDashboardAcl(c *middleware.Context, cmd m.SetDashboardAclCommand) Response {
|
||||
|
@ -13,16 +13,16 @@ import (
|
||||
func TestDashboardAclApiEndpoint(t *testing.T) {
|
||||
Convey("Given a dashboard acl", t, func() {
|
||||
mockResult := []*models.DashboardAcl{
|
||||
{Id: 1, OrgId: 1, DashboardId: 1, UserId: 2, Permissions: models.PERMISSION_EDIT},
|
||||
{Id: 2, OrgId: 1, DashboardId: 1, UserId: 3, Permissions: models.PERMISSION_VIEW},
|
||||
{Id: 3, OrgId: 1, DashboardId: 1, UserGroupId: 1, Permissions: models.PERMISSION_EDIT},
|
||||
{Id: 4, OrgId: 1, DashboardId: 1, UserGroupId: 2, Permissions: models.PERMISSION_READ_ONLY_EDIT},
|
||||
{Id: 1, OrgId: 1, DashboardId: 1, UserId: 2, Permission: models.PERMISSION_EDIT},
|
||||
{Id: 2, OrgId: 1, DashboardId: 1, UserId: 3, Permission: models.PERMISSION_VIEW},
|
||||
{Id: 3, OrgId: 1, DashboardId: 1, UserGroupId: 1, Permission: models.PERMISSION_EDIT},
|
||||
{Id: 4, OrgId: 1, DashboardId: 1, UserGroupId: 2, Permission: models.PERMISSION_READ_ONLY_EDIT},
|
||||
}
|
||||
dtoRes := []*models.DashboardAclInfoDTO{
|
||||
{Id: 1, OrgId: 1, DashboardId: 1, UserId: 2, Permissions: models.PERMISSION_EDIT},
|
||||
{Id: 2, OrgId: 1, DashboardId: 1, UserId: 3, Permissions: models.PERMISSION_VIEW},
|
||||
{Id: 3, OrgId: 1, DashboardId: 1, UserGroupId: 1, Permissions: models.PERMISSION_EDIT},
|
||||
{Id: 4, OrgId: 1, DashboardId: 1, UserGroupId: 2, Permissions: models.PERMISSION_READ_ONLY_EDIT},
|
||||
{Id: 1, OrgId: 1, DashboardId: 1, UserId: 2, Permission: models.PERMISSION_EDIT},
|
||||
{Id: 2, OrgId: 1, DashboardId: 1, UserId: 3, Permission: models.PERMISSION_VIEW},
|
||||
{Id: 3, OrgId: 1, DashboardId: 1, UserGroupId: 1, Permission: models.PERMISSION_EDIT},
|
||||
{Id: 4, OrgId: 1, DashboardId: 1, UserGroupId: 2, Permission: models.PERMISSION_READ_ONLY_EDIT},
|
||||
}
|
||||
|
||||
bus.AddHandler("test", func(query *models.GetDashboardAclInfoListQuery) error {
|
||||
@ -59,7 +59,7 @@ func TestDashboardAclApiEndpoint(t *testing.T) {
|
||||
|
||||
Convey("When user is editor and in the ACL", func() {
|
||||
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/1/acl", "/api/dashboards/id/:dashboardId/acl", models.ROLE_EDITOR, func(sc *scenarioContext) {
|
||||
mockResult = append(mockResult, &models.DashboardAcl{Id: 1, OrgId: 1, DashboardId: 1, UserId: 1, Permissions: models.PERMISSION_EDIT})
|
||||
mockResult = append(mockResult, &models.DashboardAcl{Id: 1, OrgId: 1, DashboardId: 1, UserId: 1, Permission: models.PERMISSION_EDIT})
|
||||
|
||||
Convey("Should be able to access ACL", func() {
|
||||
sc.handlerFunc = GetDashboardAclList
|
||||
@ -70,7 +70,7 @@ func TestDashboardAclApiEndpoint(t *testing.T) {
|
||||
})
|
||||
|
||||
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/id/1/acl/1", "/api/dashboards/id/:dashboardId/acl/:aclId", models.ROLE_EDITOR, func(sc *scenarioContext) {
|
||||
mockResult = append(mockResult, &models.DashboardAcl{Id: 1, OrgId: 1, DashboardId: 1, UserId: 1, Permissions: models.PERMISSION_EDIT})
|
||||
mockResult = append(mockResult, &models.DashboardAcl{Id: 1, OrgId: 1, DashboardId: 1, UserId: 1, Permission: models.PERMISSION_EDIT})
|
||||
|
||||
bus.AddHandler("test3", func(cmd *models.RemoveDashboardAclCommand) error {
|
||||
return nil
|
||||
@ -114,7 +114,7 @@ func TestDashboardAclApiEndpoint(t *testing.T) {
|
||||
})
|
||||
|
||||
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/id/1/acl/user/1", "/api/dashboards/id/:dashboardsId/acl/user/:userId", models.ROLE_EDITOR, func(sc *scenarioContext) {
|
||||
mockResult = append(mockResult, &models.DashboardAcl{Id: 1, OrgId: 1, DashboardId: 1, UserId: 1, Permissions: models.PERMISSION_VIEW})
|
||||
mockResult = append(mockResult, &models.DashboardAcl{Id: 1, OrgId: 1, DashboardId: 1, UserId: 1, Permission: models.PERMISSION_VIEW})
|
||||
bus.AddHandler("test3", func(cmd *models.RemoveDashboardAclCommand) error {
|
||||
return nil
|
||||
})
|
||||
|
@ -174,7 +174,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
aclMockResp := []*models.DashboardAcl{
|
||||
{
|
||||
DashboardId: 1,
|
||||
Permissions: models.PERMISSION_EDIT,
|
||||
Permission: models.PERMISSION_EDIT,
|
||||
UserId: 200,
|
||||
},
|
||||
}
|
||||
@ -273,7 +273,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
role := models.ROLE_VIEWER
|
||||
|
||||
mockResult := []*models.DashboardAcl{
|
||||
{Id: 1, OrgId: 1, DashboardId: 2, UserId: 1, Permissions: models.PERMISSION_EDIT},
|
||||
{Id: 1, OrgId: 1, DashboardId: 2, UserId: 1, Permission: models.PERMISSION_EDIT},
|
||||
}
|
||||
|
||||
bus.AddHandler("test", func(query *models.GetInheritedDashboardAclQuery) error {
|
||||
@ -315,7 +315,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
role := models.ROLE_EDITOR
|
||||
|
||||
mockResult := []*models.DashboardAcl{
|
||||
{Id: 1, OrgId: 1, DashboardId: 2, UserId: 1, Permissions: models.PERMISSION_VIEW},
|
||||
{Id: 1, OrgId: 1, DashboardId: 2, UserId: 1, Permission: models.PERMISSION_VIEW},
|
||||
}
|
||||
|
||||
bus.AddHandler("test", func(query *models.GetInheritedDashboardAclQuery) error {
|
||||
|
@ -9,15 +9,15 @@ type PermissionType int
|
||||
|
||||
const (
|
||||
PERMISSION_VIEW PermissionType = 1 << iota
|
||||
PERMISSION_READ_ONLY_EDIT
|
||||
PERMISSION_EDIT
|
||||
PERMISSION_ADMIN
|
||||
)
|
||||
|
||||
func (p PermissionType) String() string {
|
||||
names := map[int]string{
|
||||
int(PERMISSION_VIEW): "View",
|
||||
int(PERMISSION_READ_ONLY_EDIT): "Read-only Edit",
|
||||
int(PERMISSION_EDIT): "Edit",
|
||||
int(PERMISSION_VIEW): "View",
|
||||
int(PERMISSION_EDIT): "Edit",
|
||||
int(PERMISSION_ADMIN): "Admin",
|
||||
}
|
||||
return names[int(p)]
|
||||
}
|
||||
@ -36,7 +36,7 @@ type DashboardAcl struct {
|
||||
|
||||
UserId int64
|
||||
UserGroupId int64
|
||||
Permissions PermissionType
|
||||
Permission PermissionType
|
||||
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
@ -55,7 +55,8 @@ type DashboardAclInfoDTO struct {
|
||||
UserEmail string `json:"userEmail"`
|
||||
UserGroupId int64 `json:"userGroupId"`
|
||||
UserGroup string `json:"userGroup"`
|
||||
Permissions PermissionType `json:"permissions"`
|
||||
Role RoleType `json:"role"`
|
||||
Permission PermissionType `json:"permission"`
|
||||
PermissionName string `json:"permissionName"`
|
||||
}
|
||||
|
||||
@ -68,7 +69,7 @@ type SetDashboardAclCommand struct {
|
||||
OrgId int64 `json:"-"`
|
||||
UserId int64 `json:"userId"`
|
||||
UserGroupId int64 `json:"userGroupId"`
|
||||
Permissions PermissionType `json:"permissions" binding:"Required"`
|
||||
Permission PermissionType `json:"permission" binding:"Required"`
|
||||
|
||||
Result DashboardAcl `json:"-"`
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ func (g *DashboardGuardian) CanSave() (bool, error) {
|
||||
}
|
||||
|
||||
func (g *DashboardGuardian) CanEdit() (bool, error) {
|
||||
return g.HasPermission(m.PERMISSION_READ_ONLY_EDIT, m.ROLE_READ_ONLY_EDITOR)
|
||||
return g.HasPermission(m.PERMISSION_EDIT, m.ROLE_READ_ONLY_EDITOR)
|
||||
}
|
||||
|
||||
func (g *DashboardGuardian) CanView() (bool, error) {
|
||||
@ -57,12 +57,12 @@ func (g *DashboardGuardian) HasPermission(permission m.PermissionType, fallbackR
|
||||
}
|
||||
|
||||
for _, p := range acl {
|
||||
if p.UserId == g.user.UserId && p.Permissions >= permission {
|
||||
if p.UserId == g.user.UserId && p.Permission >= permission {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
for _, ug := range userGroups {
|
||||
if ug.Id == p.UserGroupId && p.Permissions >= permission {
|
||||
if ug.Id == p.UserGroupId && p.Permission >= permission {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
@ -27,11 +27,13 @@ func SetDashboardAcl(cmd *m.SetDashboardAclCommand) error {
|
||||
if res, err := sess.Query("SELECT 1 from "+dialect.Quote("dashboard_acl")+" WHERE dashboard_id =? and (user_group_id=? or user_id=?)", cmd.DashboardId, cmd.UserGroupId, cmd.UserId); err != nil {
|
||||
return err
|
||||
} else if len(res) == 1 {
|
||||
|
||||
entity := m.DashboardAcl{
|
||||
Permissions: cmd.Permissions,
|
||||
Updated: time.Now(),
|
||||
Permission: cmd.Permission,
|
||||
Updated: time.Now(),
|
||||
}
|
||||
if _, err := sess.Cols("updated", "permissions").Where("dashboard_id =? and (user_group_id=? or user_id=?)", cmd.DashboardId, cmd.UserGroupId, cmd.UserId).Update(&entity); err != nil {
|
||||
|
||||
if _, err := sess.Cols("updated", "permission").Where("dashboard_id =? and (user_group_id=? or user_id=?)", cmd.DashboardId, cmd.UserGroupId, cmd.UserId).Update(&entity); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -45,10 +47,10 @@ func SetDashboardAcl(cmd *m.SetDashboardAclCommand) error {
|
||||
Created: time.Now(),
|
||||
Updated: time.Now(),
|
||||
DashboardId: cmd.DashboardId,
|
||||
Permissions: cmd.Permissions,
|
||||
Permission: cmd.Permission,
|
||||
}
|
||||
|
||||
cols := []string{"org_id", "created", "updated", "dashboard_id", "permissions"}
|
||||
cols := []string{"org_id", "created", "updated", "dashboard_id", "permission"}
|
||||
|
||||
if cmd.UserId != 0 {
|
||||
cols = append(cols, "user_id")
|
||||
@ -58,12 +60,12 @@ func SetDashboardAcl(cmd *m.SetDashboardAclCommand) error {
|
||||
cols = append(cols, "user_group_id")
|
||||
}
|
||||
|
||||
entityId, err := sess.Cols(cols...).Insert(&entity)
|
||||
_, err := sess.Cols(cols...).Insert(&entity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Result = entity
|
||||
cmd.Result.Id = entityId
|
||||
|
||||
// Update dashboard HasAcl flag
|
||||
dashboard := m.Dashboard{
|
||||
@ -97,7 +99,7 @@ func GetInheritedDashboardAcl(query *m.GetInheritedDashboardAclQuery) error {
|
||||
da.dashboard_id,
|
||||
da.user_id,
|
||||
da.user_group_id,
|
||||
da.permissions,
|
||||
da.permission,
|
||||
da.created,
|
||||
da.updated
|
||||
FROM dashboard_acl as da
|
||||
@ -112,29 +114,51 @@ func GetInheritedDashboardAcl(query *m.GetInheritedDashboardAclQuery) error {
|
||||
}
|
||||
|
||||
func GetDashboardAclInfoList(query *m.GetDashboardAclInfoListQuery) error {
|
||||
rawSQL := `SELECT
|
||||
da.id,
|
||||
da.org_id,
|
||||
da.dashboard_id,
|
||||
da.user_id,
|
||||
da.user_group_id,
|
||||
da.permissions,
|
||||
da.created,
|
||||
da.updated,
|
||||
u.login AS user_login,
|
||||
u.email AS user_email,
|
||||
ug.name AS user_group
|
||||
rawSQL := `
|
||||
SELECT
|
||||
da.id,
|
||||
da.org_id,
|
||||
da.dashboard_id,
|
||||
da.user_id,
|
||||
da.user_group_id,
|
||||
da.permission,
|
||||
da.role,
|
||||
da.created,
|
||||
da.updated,
|
||||
u.login AS user_login,
|
||||
u.email AS user_email,
|
||||
ug.name AS user_group
|
||||
FROM` + dialect.Quote("dashboard_acl") + ` as da
|
||||
LEFT OUTER JOIN ` + dialect.Quote("user") + ` AS u ON u.id = da.user_id
|
||||
LEFT OUTER JOIN user_group ug on ug.id = da.user_group_id
|
||||
WHERE dashboard_id=?`
|
||||
LEFT OUTER JOIN ` + dialect.Quote("user") + ` AS u ON u.id = da.user_id
|
||||
LEFT OUTER JOIN user_group ug on ug.id = da.user_group_id
|
||||
WHERE dashboard_id = ?
|
||||
|
||||
-- Also include default permission if has_acl = 0
|
||||
|
||||
UNION
|
||||
SELECT
|
||||
da.id,
|
||||
da.org_id,
|
||||
da.dashboard_id,
|
||||
da.user_id,
|
||||
da.user_group_id,
|
||||
da.permission,
|
||||
da.role,
|
||||
da.created,
|
||||
da.updated,
|
||||
'' as user_login,
|
||||
'' as user_email,
|
||||
'' as user_group
|
||||
FROM dashboard_acl as da, dashboard as dash
|
||||
WHERE dash.id = ? AND dash.has_acl = 0 AND da.dashboard_id = -1
|
||||
`
|
||||
|
||||
query.Result = make([]*m.DashboardAclInfoDTO, 0)
|
||||
|
||||
err := x.SQL(rawSQL, query.DashboardId).Find(&query.Result)
|
||||
err := x.SQL(rawSQL, query.DashboardId, query.DashboardId).Find(&query.Result)
|
||||
|
||||
for _, p := range query.Result {
|
||||
p.PermissionName = p.Permissions.String()
|
||||
p.PermissionName = p.Permission.String()
|
||||
}
|
||||
|
||||
return err
|
||||
|
@ -20,7 +20,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
|
||||
err := SetDashboardAcl(&m.SetDashboardAclCommand{
|
||||
OrgId: 1,
|
||||
DashboardId: savedFolder.Id,
|
||||
Permissions: m.PERMISSION_EDIT,
|
||||
Permission: m.PERMISSION_EDIT,
|
||||
})
|
||||
So(err, ShouldEqual, m.ErrDashboardAclInfoMissing)
|
||||
})
|
||||
@ -30,7 +30,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
|
||||
OrgId: 1,
|
||||
UserId: currentUser.Id,
|
||||
DashboardId: savedFolder.Id,
|
||||
Permissions: m.PERMISSION_EDIT,
|
||||
Permission: m.PERMISSION_EDIT,
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
@ -49,7 +49,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
|
||||
OrgId: 1,
|
||||
UserId: currentUser.Id,
|
||||
DashboardId: childDash.Id,
|
||||
Permissions: m.PERMISSION_EDIT,
|
||||
Permission: m.PERMISSION_EDIT,
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
@ -67,23 +67,29 @@ func TestDashboardAclDataAccess(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Should be able to add dashboard permission", func() {
|
||||
err := SetDashboardAcl(&m.SetDashboardAclCommand{
|
||||
setDashAclCmd := m.SetDashboardAclCommand{
|
||||
OrgId: 1,
|
||||
UserId: currentUser.Id,
|
||||
DashboardId: savedFolder.Id,
|
||||
Permissions: m.PERMISSION_EDIT,
|
||||
})
|
||||
Permission: m.PERMISSION_EDIT,
|
||||
}
|
||||
|
||||
err := SetDashboardAcl(&setDashAclCmd)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(setDashAclCmd.Result.Id, ShouldEqual, 3)
|
||||
|
||||
q1 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id}
|
||||
err = GetDashboardAclInfoList(q1)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(q1.Result[0].DashboardId, ShouldEqual, savedFolder.Id)
|
||||
So(q1.Result[0].Permissions, ShouldEqual, m.PERMISSION_EDIT)
|
||||
So(q1.Result[0].Permission, ShouldEqual, m.PERMISSION_EDIT)
|
||||
So(q1.Result[0].PermissionName, ShouldEqual, "Edit")
|
||||
So(q1.Result[0].UserId, ShouldEqual, currentUser.Id)
|
||||
So(q1.Result[0].UserLogin, ShouldEqual, currentUser.Login)
|
||||
So(q1.Result[0].UserEmail, ShouldEqual, currentUser.Email)
|
||||
So(q1.Result[0].Id, ShouldEqual, setDashAclCmd.Result.Id)
|
||||
|
||||
Convey("Should update hasAcl field to true for dashboard folder and its children", func() {
|
||||
q2 := &m.GetDashboardsQuery{DashboardIds: []int64{savedFolder.Id, childDash.Id}}
|
||||
@ -98,8 +104,9 @@ func TestDashboardAclDataAccess(t *testing.T) {
|
||||
OrgId: 1,
|
||||
UserId: 1,
|
||||
DashboardId: savedFolder.Id,
|
||||
Permissions: m.PERMISSION_READ_ONLY_EDIT,
|
||||
Permission: m.PERMISSION_ADMIN,
|
||||
})
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
q3 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id}
|
||||
@ -107,7 +114,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
So(len(q3.Result), ShouldEqual, 1)
|
||||
So(q3.Result[0].DashboardId, ShouldEqual, savedFolder.Id)
|
||||
So(q3.Result[0].Permissions, ShouldEqual, m.PERMISSION_READ_ONLY_EDIT)
|
||||
So(q3.Result[0].Permission, ShouldEqual, m.PERMISSION_ADMIN)
|
||||
So(q3.Result[0].UserId, ShouldEqual, 1)
|
||||
|
||||
})
|
||||
@ -115,8 +122,9 @@ func TestDashboardAclDataAccess(t *testing.T) {
|
||||
Convey("Should be able to delete an existing permission", func() {
|
||||
err := RemoveDashboardAcl(&m.RemoveDashboardAclCommand{
|
||||
OrgId: 1,
|
||||
AclId: 1,
|
||||
AclId: setDashAclCmd.Result.Id,
|
||||
})
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
q3 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id}
|
||||
@ -132,20 +140,35 @@ func TestDashboardAclDataAccess(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("Should be able to add a user permission for a user group", func() {
|
||||
err := SetDashboardAcl(&m.SetDashboardAclCommand{
|
||||
setDashAclCmd := m.SetDashboardAclCommand{
|
||||
OrgId: 1,
|
||||
UserGroupId: group1.Result.Id,
|
||||
DashboardId: savedFolder.Id,
|
||||
Permissions: m.PERMISSION_EDIT,
|
||||
})
|
||||
Permission: m.PERMISSION_EDIT,
|
||||
}
|
||||
|
||||
err := SetDashboardAcl(&setDashAclCmd)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
q1 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id}
|
||||
err = GetDashboardAclInfoList(q1)
|
||||
So(err, ShouldBeNil)
|
||||
So(q1.Result[0].DashboardId, ShouldEqual, savedFolder.Id)
|
||||
So(q1.Result[0].Permissions, ShouldEqual, m.PERMISSION_EDIT)
|
||||
So(q1.Result[0].Permission, ShouldEqual, m.PERMISSION_EDIT)
|
||||
So(q1.Result[0].UserGroupId, ShouldEqual, group1.Result.Id)
|
||||
|
||||
Convey("Should be able to delete an existing permission for a user group", func() {
|
||||
err := RemoveDashboardAcl(&m.RemoveDashboardAclCommand{
|
||||
OrgId: 1,
|
||||
AclId: setDashAclCmd.Result.Id,
|
||||
})
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
q3 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id}
|
||||
err = GetDashboardAclInfoList(q3)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(q3.Result), ShouldEqual, 0)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Should be able to update an existing permission for a user group", func() {
|
||||
@ -153,7 +176,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
|
||||
OrgId: 1,
|
||||
UserGroupId: group1.Result.Id,
|
||||
DashboardId: savedFolder.Id,
|
||||
Permissions: m.PERMISSION_READ_ONLY_EDIT,
|
||||
Permission: m.PERMISSION_ADMIN,
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
@ -162,23 +185,10 @@ func TestDashboardAclDataAccess(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
So(len(q3.Result), ShouldEqual, 1)
|
||||
So(q3.Result[0].DashboardId, ShouldEqual, savedFolder.Id)
|
||||
So(q3.Result[0].Permissions, ShouldEqual, m.PERMISSION_READ_ONLY_EDIT)
|
||||
So(q3.Result[0].Permission, ShouldEqual, m.PERMISSION_ADMIN)
|
||||
So(q3.Result[0].UserGroupId, ShouldEqual, group1.Result.Id)
|
||||
|
||||
})
|
||||
|
||||
Convey("Should be able to delete an existing permission for a user group", func() {
|
||||
err := RemoveDashboardAcl(&m.RemoveDashboardAclCommand{
|
||||
OrgId: 1,
|
||||
AclId: 1,
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
q3 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id}
|
||||
err = GetDashboardAclInfoList(q3)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(q3.Result), ShouldEqual, 0)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -384,7 +384,7 @@ func updateTestDashboardWithAcl(dashId int64, userId int64, permissions m.Permis
|
||||
OrgId: 1,
|
||||
UserId: userId,
|
||||
DashboardId: dashId,
|
||||
Permissions: permissions,
|
||||
Permission: permissions,
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
}
|
||||
|
@ -11,21 +11,42 @@ func addDashboardAclMigrations(mg *Migrator) {
|
||||
{Name: "dashboard_id", Type: DB_BigInt},
|
||||
{Name: "user_id", Type: DB_BigInt, Nullable: true},
|
||||
{Name: "user_group_id", Type: DB_BigInt, Nullable: true},
|
||||
{Name: "permissions", Type: DB_SmallInt, Default: "4"},
|
||||
{Name: "permission", Type: DB_SmallInt, Default: "4"},
|
||||
{Name: "role", Type: DB_Varchar, Length: 20, Nullable: true},
|
||||
{Name: "created", Type: DB_DateTime, Nullable: false},
|
||||
{Name: "updated", Type: DB_DateTime, Nullable: false},
|
||||
},
|
||||
Indices: []*Index{
|
||||
{Cols: []string{"org_id"}},
|
||||
{Cols: []string{"dashboard_id"}},
|
||||
{Cols: []string{"dashboard_id", "user_id"}, Type: UniqueIndex},
|
||||
{Cols: []string{"dashboard_id", "user_group_id"}, Type: UniqueIndex},
|
||||
},
|
||||
}
|
||||
|
||||
mg.AddMigration("create dashboard acl table", NewAddTableMigration(dashboardAclV1))
|
||||
mg.AddMigration("create dashboard acl table", NewAddTableMigration(dashboardAclV1))
|
||||
|
||||
//------- indexes ------------------
|
||||
mg.AddMigration("add unique index dashboard_acl_org_id", NewAddIndexMigration(dashboardAclV1, dashboardAclV1.Indices[0]))
|
||||
mg.AddMigration("add unique index dashboard_acl_dashboard_id", NewAddIndexMigration(dashboardAclV1, dashboardAclV1.Indices[0]))
|
||||
mg.AddMigration("add unique index dashboard_acl_dashboard_id_user_id", NewAddIndexMigration(dashboardAclV1, dashboardAclV1.Indices[1]))
|
||||
mg.AddMigration("add unique index dashboard_acl_dashboard_id_group_id", NewAddIndexMigration(dashboardAclV1, dashboardAclV1.Indices[2]))
|
||||
|
||||
const rawSQL = `
|
||||
INSERT INTO dashboard_acl
|
||||
(
|
||||
org_id,
|
||||
dashboard_id,
|
||||
permission,
|
||||
role,
|
||||
created,
|
||||
updated
|
||||
)
|
||||
VALUES
|
||||
(-1,-1, 1,'Viewer','2017-06-20','2017-06-20'),
|
||||
(-1,-1, 2,'Editor','2017-06-20','2017-06-20')
|
||||
`
|
||||
|
||||
mg.AddMigration("save default acl rules in dashboard_acl table", new(RawSqlMigration).
|
||||
Sqlite(rawSQL).
|
||||
Postgres(rawSQL).
|
||||
Mysql(rawSQL))
|
||||
}
|
||||
|
@ -174,10 +174,10 @@ func TestAccountDataAccess(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result), ShouldEqual, 3)
|
||||
|
||||
err = SetDashboardAcl(&m.SetDashboardAclCommand{DashboardId: 1, OrgId: ac1.OrgId, UserId: ac3.Id, Permissions: m.PERMISSION_EDIT})
|
||||
err = SetDashboardAcl(&m.SetDashboardAclCommand{DashboardId: 1, OrgId: ac1.OrgId, UserId: ac3.Id, Permission: m.PERMISSION_EDIT})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = SetDashboardAcl(&m.SetDashboardAclCommand{DashboardId: 2, OrgId: ac3.OrgId, UserId: ac3.Id, Permissions: m.PERMISSION_EDIT})
|
||||
err = SetDashboardAcl(&m.SetDashboardAclCommand{DashboardId: 2, OrgId: ac3.OrgId, UserId: ac3.Id, Permission: m.PERMISSION_EDIT})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("When org user is deleted", func() {
|
||||
|
@ -94,7 +94,7 @@ func TestUserGroupCommandsAndQueries(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
err = AddUserGroupMember(&m.AddUserGroupMemberCommand{OrgId: 1, UserGroupId: groupId, UserId: userIds[2]})
|
||||
So(err, ShouldBeNil)
|
||||
err = SetDashboardAcl(&m.SetDashboardAclCommand{DashboardId: 1, OrgId: 1, Permissions: m.PERMISSION_EDIT, UserGroupId: groupId})
|
||||
err = SetDashboardAcl(&m.SetDashboardAclCommand{DashboardId: 1, OrgId: 1, Permission: m.PERMISSION_EDIT, UserGroupId: groupId})
|
||||
|
||||
err = DeleteUserGroup(&m.DeleteUserGroupCommand{Id: groupId})
|
||||
So(err, ShouldBeNil)
|
||||
|
@ -99,7 +99,7 @@ func TestUserDataAccess(t *testing.T) {
|
||||
err = AddOrgUser(&m.AddOrgUserCommand{LoginOrEmail: users[0].Login, Role: m.ROLE_VIEWER, OrgId: users[0].OrgId})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = SetDashboardAcl(&m.SetDashboardAclCommand{DashboardId: 1, OrgId: users[0].OrgId, UserId: users[0].Id, Permissions: m.PERMISSION_EDIT})
|
||||
err = SetDashboardAcl(&m.SetDashboardAclCommand{DashboardId: 1, OrgId: users[0].OrgId, UserId: users[0].Id, Permission: m.PERMISSION_EDIT})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = SavePreferences(&m.SavePreferencesCommand{UserId: users[0].Id, OrgId: users[0].OrgId, HomeDashboardId: 1, Theme: "dark"})
|
||||
|
@ -8,9 +8,9 @@
|
||||
<i class="fa fa-chevron-left"></i>
|
||||
</a>
|
||||
|
||||
<!-- <a class="navbar-page-btn navbar-page-btn--search" ng-click="ctrl.showSearch()"> -->
|
||||
<!-- <i class="fa fa-search"></i> -->
|
||||
<!-- </a> -->
|
||||
<a class="navbar-page-btn navbar-page-btn--search" ng-click="ctrl.showSearch()">
|
||||
<i class="fa fa-search"></i>
|
||||
</a>
|
||||
|
||||
<div ng-if="::!ctrl.hasMenu">
|
||||
<a href="{{::ctrl.section.url}}" class="navbar-page-btn">
|
||||
|
@ -4,66 +4,38 @@ import _ from 'lodash';
|
||||
|
||||
const template = `
|
||||
<div class="dropdown">
|
||||
<metric-segment segment="ctrl.userGroupSegment"
|
||||
get-options="ctrl.debouncedSearchUserGroups($query)"
|
||||
on-change="ctrl.onChange()"></metric-segment>
|
||||
</div>
|
||||
<gf-form-dropdown model="ctrl.group"
|
||||
get-options="ctrl.debouncedSearchGroups($query)"
|
||||
css-class="gf-size-auto"
|
||||
on-change="ctrl.onChange($option)"
|
||||
</gf-form-dropdown>
|
||||
</div>
|
||||
`;
|
||||
export class UserGroupPickerCtrl {
|
||||
userGroupSegment: any;
|
||||
userGroupId: number;
|
||||
debouncedSearchUserGroups: any;
|
||||
group: any;
|
||||
userGroupPicked: any;
|
||||
debouncedSearchGroups: any;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private backendSrv, private $scope, $sce, private uiSegmentSrv) {
|
||||
this.debouncedSearchUserGroups = _.debounce(this.searchUserGroups, 500, {'leading': true, 'trailing': false});
|
||||
this.resetUserGroupSegment();
|
||||
this.debouncedSearchGroups = _.debounce(this.searchGroups, 500, {'leading': true, 'trailing': false});
|
||||
this.reset();
|
||||
}
|
||||
|
||||
resetUserGroupSegment() {
|
||||
this.userGroupId = null;
|
||||
|
||||
const userGroupSegment = this.uiSegmentSrv.newSegment({
|
||||
value: 'Choose',
|
||||
selectMode: true,
|
||||
fake: true,
|
||||
cssClass: 'gf-size-auto'
|
||||
});
|
||||
|
||||
if (!this.userGroupSegment) {
|
||||
this.userGroupSegment = userGroupSegment;
|
||||
} else {
|
||||
this.userGroupSegment.value = userGroupSegment.value;
|
||||
this.userGroupSegment.html = userGroupSegment.html;
|
||||
this.userGroupSegment.value = userGroupSegment.value;
|
||||
}
|
||||
reset() {
|
||||
this.group = {text: 'Choose', value: null};
|
||||
}
|
||||
|
||||
userGroupIdChanged(newVal) {
|
||||
if (!newVal) {
|
||||
this.resetUserGroupSegment();
|
||||
}
|
||||
}
|
||||
|
||||
searchUserGroups(query: string) {
|
||||
searchGroups(query: string) {
|
||||
return Promise.resolve(this.backendSrv.get('/api/user-groups/search?perpage=10&page=1&query=' + query).then(result => {
|
||||
return _.map(result.userGroups, ug => { return this.uiSegmentSrv.newSegment(ug.name); });
|
||||
return _.map(result.userGroups, ug => {
|
||||
return {text: ug.name, value: ug};
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
onChange() {
|
||||
this.backendSrv.get('/api/user-groups/search?perpage=10&page=1&query=' + this.userGroupSegment.value)
|
||||
.then(result => {
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
result.userGroups.forEach(ug => {
|
||||
if (ug.name === this.userGroupSegment.value) {
|
||||
this.userGroupId = ug.id;
|
||||
}
|
||||
});
|
||||
});
|
||||
onChange(option) {
|
||||
this.userGroupPicked({$group: option.value});
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,11 +47,11 @@ export function userGroupPicker() {
|
||||
bindToController: true,
|
||||
controllerAs: 'ctrl',
|
||||
scope: {
|
||||
userGroupId: '=',
|
||||
userGroupPicked: '&',
|
||||
},
|
||||
link: function(scope, elem, attrs, ctrl) {
|
||||
scope.$watch("ctrl.userGroupId", (newVal, oldVal) => {
|
||||
ctrl.userGroupIdChanged(newVal);
|
||||
scope.$on("user-group-picker-reset", () => {
|
||||
ctrl.reset();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -7,37 +7,35 @@ const template = `
|
||||
<gf-form-dropdown model="ctrl.user"
|
||||
get-options="ctrl.debouncedSearchUsers($query)"
|
||||
css-class="gf-size-auto"
|
||||
on-change="ctrl.onChange()"
|
||||
on-change="ctrl.onChange($option)"
|
||||
</gf-form-dropdown>
|
||||
</div>
|
||||
`;
|
||||
export class UserPickerCtrl {
|
||||
user: any;
|
||||
userId: number;
|
||||
debouncedSearchUsers: any;
|
||||
userPicked: any;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private backendSrv, private $scope, $sce) {
|
||||
this.user = {text: 'Choose', value: null};
|
||||
this.reset();
|
||||
this.debouncedSearchUsers = _.debounce(this.searchUsers, 500, {'leading': true, 'trailing': false});
|
||||
}
|
||||
|
||||
searchUsers(query: string) {
|
||||
return Promise.resolve(this.backendSrv.get('/api/users/search?perpage=10&page=1&query=' + query).then(result => {
|
||||
return _.map(result.users, user => {
|
||||
return {text: user.login + ' - ' + user.email, value: user.id};
|
||||
return {text: user.login + ' - ' + user.email, value: user};
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
onChange() {
|
||||
this.userId = this.user.value;
|
||||
onChange(option) {
|
||||
this.userPicked({$user: option.value});
|
||||
}
|
||||
|
||||
userIdChanged() {
|
||||
if (this.userId === null) {
|
||||
this.user = {text: 'Choose', value: null};
|
||||
}
|
||||
reset() {
|
||||
this.user = {text: 'Choose', value: null};
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,11 +54,11 @@ export function userPicker() {
|
||||
bindToController: true,
|
||||
controllerAs: 'ctrl',
|
||||
scope: {
|
||||
userId: '=',
|
||||
userPicked: '&',
|
||||
},
|
||||
link: function(scope, elem, attrs, ctrl) {
|
||||
scope.$watch("ctrl.userId", (newVal, oldVal) => {
|
||||
ctrl.userIdChanged(newVal);
|
||||
scope.$on("user-picker-reset", () => {
|
||||
ctrl.reset();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -2,8 +2,9 @@ define([
|
||||
'jquery',
|
||||
'angular',
|
||||
'../core_module',
|
||||
'lodash',
|
||||
],
|
||||
function ($, angular, coreModule) {
|
||||
function ($, angular, coreModule, _) {
|
||||
'use strict';
|
||||
|
||||
var editViewMap = {
|
||||
@ -12,7 +13,8 @@ function ($, angular, coreModule) {
|
||||
'templating': { src: 'public/app/features/templating/partials/editor.html'},
|
||||
'history': { html: '<gf-dashboard-history dashboard="dashboard"></gf-dashboard-history>'},
|
||||
'timepicker': { src: 'public/app/features/dashboard/timepicker/dropdown.html' },
|
||||
'import': { html: '<dash-import></dash-import>' }
|
||||
'import': { html: '<dash-import dismiss="dismiss()"></dash-import>', isModal: true },
|
||||
'permissions': { html: '<dash-acl-modal dismiss="dismiss()"></dash-acl-modal>', isModal: true }
|
||||
};
|
||||
|
||||
coreModule.default.directive('dashEditorView', function($compile, $location, $rootScope) {
|
||||
@ -20,6 +22,7 @@ function ($, angular, coreModule) {
|
||||
restrict: 'A',
|
||||
link: function(scope, elem) {
|
||||
var editorScope;
|
||||
var modalScope;
|
||||
var lastEditView;
|
||||
|
||||
function hideEditorPane(hideToShowOtherView) {
|
||||
@ -31,8 +34,7 @@ function ($, angular, coreModule) {
|
||||
|
||||
function showEditorPane(evt, options) {
|
||||
if (options.editview) {
|
||||
options.src = editViewMap[options.editview].src;
|
||||
options.html = editViewMap[options.editview].html;
|
||||
_.defaults(options, editViewMap[options.editview]);
|
||||
}
|
||||
|
||||
if (lastEditView && lastEditView === options.editview) {
|
||||
@ -46,6 +48,11 @@ function ($, angular, coreModule) {
|
||||
editorScope = options.scope ? options.scope.$new() : scope.$new();
|
||||
|
||||
editorScope.dismiss = function(hideToShowOtherView) {
|
||||
if (modalScope) {
|
||||
modalScope.dismiss();
|
||||
modalScope = null;
|
||||
}
|
||||
|
||||
editorScope.$destroy();
|
||||
lastEditView = null;
|
||||
editorScope = null;
|
||||
@ -61,19 +68,24 @@ function ($, angular, coreModule) {
|
||||
var urlParams = $location.search();
|
||||
if (options.editview === urlParams.editview) {
|
||||
delete urlParams.editview;
|
||||
$location.search(urlParams);
|
||||
// hack for consistently updating url
|
||||
setTimeout(function() {
|
||||
$rootScope.$apply(function() {
|
||||
$location.search(urlParams);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (options.editview === 'import') {
|
||||
var modalScope = $rootScope.$new();
|
||||
if (options.isModal) {
|
||||
modalScope = $rootScope.$new();
|
||||
modalScope.$on("$destroy", function() {
|
||||
editorScope.dismiss();
|
||||
});
|
||||
|
||||
$rootScope.appEvent('show-modal', {
|
||||
templateHtml: '<dash-import></dash-import>',
|
||||
templateHtml: options.html,
|
||||
scope: modalScope,
|
||||
backdrop: 'static'
|
||||
});
|
||||
|
@ -168,6 +168,12 @@ export class NavModelSrv {
|
||||
clickHandler: () => dashNavCtrl.openEditView('annotations')
|
||||
});
|
||||
|
||||
menu.push({
|
||||
title: 'Permissions...',
|
||||
icon: 'fa fa-fw fa-lock',
|
||||
clickHandler: () => dashNavCtrl.openEditView('permissions')
|
||||
});
|
||||
|
||||
if (!dashboard.meta.isHome) {
|
||||
menu.push({
|
||||
title: 'Version history',
|
||||
@ -199,7 +205,7 @@ export class NavModelSrv {
|
||||
|
||||
if (this.contextSrv.isEditor && !dashboard.meta.isFolder) {
|
||||
menu.push({
|
||||
title: 'Save As ...',
|
||||
title: 'Save As...',
|
||||
icon: 'fa fa-fw fa-save',
|
||||
clickHandler: () => dashNavCtrl.saveDashboardAs()
|
||||
});
|
||||
|
@ -1,74 +1,116 @@
|
||||
<div class="editor-row">
|
||||
<h5 class="section-heading">Add New Permission</h5>
|
||||
<form name="addPermission" class="gf-form-group">
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label">Type</span>
|
||||
<select class="gf-form-input gf-size-auto" ng-model="ctrl.type" ng-options="r for r in ['User Group', 'User']"></select>
|
||||
</div>
|
||||
<div class="gf-form" ng-show="ctrl.type === 'User'">
|
||||
<span class="gf-form-label">User</span>
|
||||
<user-picker user-id="ctrl.userId"></user-picker>
|
||||
</div>
|
||||
<div class="gf-form" ng-show="ctrl.type === 'User Group'">
|
||||
<span class="gf-form-label">User Group</span>
|
||||
<user-group-picker user-group-id="ctrl.userGroupId"></user-group-picker>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label">Permission</span>
|
||||
<select class="gf-form-input gf-size-auto" ng-model="ctrl.permission" ng-options="p.value as p.text for p in ctrl.permissionTypeOptions"></select>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<button class="btn gf-form-btn btn-success" ng-click="ctrl.addPermission()">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div class="modal-body">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-header-title">
|
||||
<i class="fa fa-lock"></i>
|
||||
<span class="p-l-1">Permissions</span>
|
||||
</h2>
|
||||
|
||||
<div class="permissionlist">
|
||||
<div class="permissionlist__section">
|
||||
<div class="permissionlist__section-header">
|
||||
<h6>Permissions</h6>
|
||||
</div>
|
||||
<table class="filter-table form-inline">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 50px;"></th>
|
||||
<th>Name</th>
|
||||
<th style="width: 220px;">Permission</th>
|
||||
<th style="width: 120px"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="permission in ctrl.userPermissions" class="permissionlist__item">
|
||||
<td><i class="fa fa-fw fa-user"></i></td>
|
||||
<td>{{permission.userLogin}}</td>
|
||||
<td><select class="gf-form-input gf-size-auto" ng-model="permission.permissions" ng-options="p.value as p.text for p in ctrl.permissionTypeOptions" ng-change="ctrl.updatePermission(permission)"></select></td>
|
||||
<td class="text-right">
|
||||
<a ng-click="ctrl.removePermission(permission)" class="btn btn-danger btn-small">
|
||||
<i class="fa fa-remove"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-repeat="permission in ctrl.userGroupPermissions" class="permissionlist__item">
|
||||
<td><i class="fa fa-fw fa-users"></i></td>
|
||||
<td>{{permission.userGroup}}</td>
|
||||
<td><select class="gf-form-input gf-size-auto" ng-model="permission.permissions" ng-options="p.value as p.text for p in ctrl.permissionTypeOptions" ng-change="ctrl.updatePermission(permission)"></select></td>
|
||||
<td class="text-right">
|
||||
<a ng-click="ctrl.removePermission(permission)" class="btn btn-danger btn-small">
|
||||
<i class="fa fa-remove"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-repeat="role in ctrl.roles" class="permissionlist__item">
|
||||
<td></td>
|
||||
<td>{{role.name}}</td>
|
||||
<td><select class="gf-form-input gf-size-auto" ng-model="role.permissions" ng-options="p.value as p.text for p in ctrl.roleOptions" ng-change="ctrl.updatePermission(role)"></select></td>
|
||||
<td class="text-right">
|
||||
<a class="modal-header-close" ng-click="ctrl.dismiss();">
|
||||
<i class="fa fa-remove"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="modal-content">
|
||||
<table class="filter-table gf-form-group">
|
||||
<tr ng-repeat="acl in ctrl.aclItems">
|
||||
<td style="width: 100%;">
|
||||
<i class="{{acl.icon}}"></i>
|
||||
<span ng-bind-html="acl.nameHtml"></span>
|
||||
</td>
|
||||
<td class="query-keyword">Can</td>
|
||||
<td>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select class="gf-form-input gf-size-auto" ng-model="acl.permission" ng-options="p.value as p.text for p in ctrl.permissionOptions" ng-change="ctrl.permissionChanged(acl)"></select>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<a class="btn btn-inverse btn-small" ng-click="ctrl.removeItem($index)">
|
||||
<i class="fa fa-remove"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-show="ctrl.aclItems.length === 0">
|
||||
<td colspan="4">
|
||||
<em>No permissions. Will only be accessible by admins.</em>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<form name="addPermission" class="gf-form-group">
|
||||
<h6 class="muted">Add Permission For</h6>
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select class="gf-form-input gf-size-auto" ng-model="ctrl.newType" ng-options="p.value as p.text for p in ctrl.aclTypes" ng-change="ctrl.typeChanged()"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form" ng-show="ctrl.newType === 'User'">
|
||||
<user-picker user-picked="ctrl.userPicked($user)"></user-picker>
|
||||
</div>
|
||||
<div class="gf-form" ng-show="ctrl.newType === 'Group'">
|
||||
<user-group-picker user-group-picked="ctrl.groupPicked($group)"></user-group-picker>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="gf-form-button-row text-center">
|
||||
<button type="button" class="btn btn-danger" ng-disabled="!ctrl.canUpdate" ng-click="ctrl.update()">
|
||||
Update Permissions
|
||||
</button>
|
||||
<a class="btn-text" ng-click="ctrl.dismiss();">Close</a>
|
||||
</div>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <br> -->
|
||||
<!-- <br> -->
|
||||
<!-- <br> -->
|
||||
<!-- -->
|
||||
<!-- <div class="permissionlist"> -->
|
||||
<!-- <div class="permissionlist__section"> -->
|
||||
<!-- <div class="permissionlist__section-header"> -->
|
||||
<!-- <h6>Permissions</h6> -->
|
||||
<!-- </div> -->
|
||||
<!-- <table class="filter-table form-inline"> -->
|
||||
<!-- <thead> -->
|
||||
<!-- <tr> -->
|
||||
<!-- <th style="width: 50px;"></th> -->
|
||||
<!-- <th>Name</th> -->
|
||||
<!-- <th style="width: 220px;">Permission</th> -->
|
||||
<!-- <th style="width: 120px"></th> -->
|
||||
<!-- </tr> -->
|
||||
<!-- </thead> -->
|
||||
<!-- <tbody> -->
|
||||
<!-- <tr ng-repeat="permission in ctrl.userPermissions" class="permissionlist__item"> -->
|
||||
<!-- <td><i class="fa fa-fw fa-user"></i></td> -->
|
||||
<!-- <td>{{permission.userLogin}}</td> -->
|
||||
<!-- <td class="text-right"> -->
|
||||
<!-- <a ng-click="ctrl.removePermission(permission)" class="btn btn-danger btn-small"> -->
|
||||
<!-- <i class="fa fa-remove"></i> -->
|
||||
<!-- </a> -->
|
||||
<!-- </td> -->
|
||||
<!-- </tr> -->
|
||||
<!-- <tr ng-repeat="permission in ctrl.userGroupPermissions" class="permissionlist__item"> -->
|
||||
<!-- <td><i class="fa fa-fw fa-users"></i></td> -->
|
||||
<!-- <td>{{permission.userGroup}}</td> -->
|
||||
<!-- <td><select class="gf-form-input gf-size-auto" ng-model="permission.permissions" ng-options="p.value as p.text for p in ctrl.permissionTypeOptions" ng-change="ctrl.updatePermission(permission)"></select></td> -->
|
||||
<!-- <td class="text-right"> -->
|
||||
<!-- <a ng-click="ctrl.removePermission(permission)" class="btn btn-danger btn-small"> -->
|
||||
<!-- <i class="fa fa-remove"></i> -->
|
||||
<!-- </a> -->
|
||||
<!-- </td> -->
|
||||
<!-- </tr> -->
|
||||
<!-- <tr ng-repeat="role in ctrl.roles" class="permissionlist__item"> -->
|
||||
<!-- <td></td> -->
|
||||
<!-- <td>{{role.name}}</td> -->
|
||||
<!-- <td><select class="gf-form-input gf-size-auto" ng-model="role.permissions" ng-options="p.value as p.text for p in ctrl.roleOptions" ng-change="ctrl.updatePermission(role)"></select></td> -->
|
||||
<!-- <td class="text-right"> -->
|
||||
<!-- -->
|
||||
<!-- </td> -->
|
||||
<!-- </tr> -->
|
||||
<!-- </tbody> -->
|
||||
<!-- </table> -->
|
||||
<!-- </div> -->
|
||||
<!-- </div> -->
|
||||
<!-- </div> -->
|
||||
|
@ -5,117 +5,126 @@ import appEvents from 'app/core/app_events';
|
||||
import _ from 'lodash';
|
||||
|
||||
export class AclCtrl {
|
||||
tabIndex: any;
|
||||
dashboard: any;
|
||||
userPermissions: Permission[];
|
||||
userGroupPermissions: Permission[];
|
||||
permissionTypeOptions = [
|
||||
aclItems: DashboardAcl[];
|
||||
permissionOptions = [
|
||||
{value: 1, text: 'View'},
|
||||
{value: 2, text: 'Read-only Edit'},
|
||||
{value: 4, text: 'Edit'}
|
||||
{value: 2, text: 'Edit'},
|
||||
{value: 4, text: 'Admin'}
|
||||
];
|
||||
aclTypes = [
|
||||
{value: 'Group', text: 'User Group'},
|
||||
{value: 'User', text: 'User'},
|
||||
{value: 'Viewer', text: 'Everyone With Viewer Role'},
|
||||
{value: 'Editor', text: 'Everyone With Editor Role'}
|
||||
];
|
||||
|
||||
roleOptions = [
|
||||
{value: 0, text: 'None'},
|
||||
{value: 1, text: 'View'},
|
||||
{value: 2, text: 'Read-only Edit'},
|
||||
{value: 4, text: 'Edit'}
|
||||
];
|
||||
|
||||
roles = [];
|
||||
|
||||
type = 'User Group';
|
||||
permission = 1;
|
||||
userId: number;
|
||||
userGroupId: number;
|
||||
newType: string;
|
||||
canUpdate: boolean;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private backendSrv, private $scope) {
|
||||
this.tabIndex = 0;
|
||||
this.userPermissions = [];
|
||||
this.userGroupPermissions = [];
|
||||
constructor(private backendSrv, private dashboardSrv, private $sce, private $scope) {
|
||||
this.aclItems = [];
|
||||
this.resetNewType();
|
||||
this.dashboard = dashboardSrv.getCurrent();
|
||||
this.get(this.dashboard.id);
|
||||
}
|
||||
|
||||
resetNewType() {
|
||||
this.newType = 'Group';
|
||||
}
|
||||
|
||||
get(dashboardId: number) {
|
||||
return this.backendSrv.get(`/api/dashboards/id/${dashboardId}/acl`)
|
||||
.then(result => {
|
||||
this.userPermissions = _.filter(result, p => { return p.userId > 0;});
|
||||
this.userGroupPermissions = _.filter(result, p => { return p.userGroupId > 0;});
|
||||
this.roles = this.setRoles(result);
|
||||
this.aclItems = _.map(result, this.prepareViewModel.bind(this));
|
||||
});
|
||||
}
|
||||
|
||||
setRoles(result: any) {
|
||||
return [
|
||||
{name: 'Org Viewer', permissions: 1},
|
||||
{name: 'Org Read Only Editor', permissions: 2},
|
||||
{name: 'Org Editor', permissions: 4},
|
||||
{name: 'Org Admin', permissions: 4}
|
||||
];
|
||||
prepareViewModel(item: DashboardAcl): DashboardAcl {
|
||||
if (item.userId > 0) {
|
||||
item.icon = "fa fa-fw fa-user";
|
||||
item.nameHtml = this.$sce.trustAsHtml(item.userLogin);
|
||||
} else if (item.userGroupId > 0) {
|
||||
item.icon = "fa fa-fw fa-users";
|
||||
item.nameHtml = this.$sce.trustAsHtml(item.userGroup);
|
||||
} else if (item.role) {
|
||||
item.icon = "fa fa-fw fa-street-view";
|
||||
item.nameHtml = this.$sce.trustAsHtml(`Everyone with <span class="query-keyword">${item.role}</span> Role`);
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
addPermission() {
|
||||
if (this.type === 'User') {
|
||||
if (!this.userId) {
|
||||
return;
|
||||
}
|
||||
return this.addOrUpdateUserPermission(this.userId, this.permission).then(() => {
|
||||
this.userId = null;
|
||||
return this.get(this.dashboard.id);
|
||||
});
|
||||
} else {
|
||||
if (!this.userGroupId) {
|
||||
return;
|
||||
}
|
||||
update() {
|
||||
return this.backendSrv.post(`/api/dashboards/id/${this.dashboard.id}/acl`, {
|
||||
acl: this.aclItems.map(item => {
|
||||
return {
|
||||
id: item.id,
|
||||
userId: item.userId,
|
||||
userGroupId: item.userGroupId,
|
||||
role: item.role,
|
||||
permission: item.permission,
|
||||
};
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
return this.addOrUpdateUserGroupPermission(this.userGroupId, this.permission).then(() => {
|
||||
this.userGroupId = null;
|
||||
return this.get(this.dashboard.id);
|
||||
});
|
||||
typeChanged() {
|
||||
if (this.newType === 'Viewer' || this.newType === 'Editor') {
|
||||
this.aclItems.push(this.prepareViewModel({
|
||||
permission: 1,
|
||||
role: this.newType
|
||||
}));
|
||||
|
||||
this.canUpdate = true;
|
||||
this.resetNewType();
|
||||
}
|
||||
}
|
||||
|
||||
addOrUpdateUserPermission(userId: number, permissionType: number) {
|
||||
return this.backendSrv.post(`/api/dashboards/id/${this.dashboard.id}/acl`, {
|
||||
userId: userId,
|
||||
permissions: permissionType
|
||||
});
|
||||
permissionChanged() {
|
||||
this.canUpdate = true;
|
||||
}
|
||||
|
||||
addOrUpdateUserGroupPermission(userGroupId: number, permissionType: number) {
|
||||
return this.backendSrv.post(`/api/dashboards/id/${this.dashboard.id}/acl`, {
|
||||
userGroupId: userGroupId,
|
||||
permissions: permissionType
|
||||
});
|
||||
userPicked(user) {
|
||||
this.aclItems.push(this.prepareViewModel({
|
||||
userId: user.id,
|
||||
userLogin: user.login,
|
||||
permission: 1,
|
||||
}));
|
||||
|
||||
this.canUpdate = true;
|
||||
this.$scope.$broadcast('user-picker-reset');
|
||||
}
|
||||
|
||||
updatePermission(permission: any) {
|
||||
if (permission.userId > 0) {
|
||||
return this.addOrUpdateUserPermission(permission.userId, permission.permissions);
|
||||
} else {
|
||||
if (!permission.userGroupId) {
|
||||
return;
|
||||
}
|
||||
return this.addOrUpdateUserGroupPermission(permission.userGroupId, permission.permissions);
|
||||
}
|
||||
groupPicked(group) {
|
||||
console.log(group);
|
||||
this.aclItems.push(this.prepareViewModel({
|
||||
userGroupId: group.id,
|
||||
userGroup: group.name,
|
||||
permission: 1,
|
||||
}));
|
||||
|
||||
this.canUpdate = true;
|
||||
this.$scope.$broadcast('user-group-picker-reset');
|
||||
}
|
||||
|
||||
removePermission(permission: Permission) {
|
||||
return this.backendSrv.delete(`/api/dashboards/id/${permission.dashboardId}/acl/${permission.id}`).then(() => {
|
||||
return this.get(permission.dashboardId);
|
||||
});
|
||||
removeItem(index) {
|
||||
this.aclItems.splice(index, 1);
|
||||
this.canUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
export function aclSettings() {
|
||||
export function dashAclModal() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
templateUrl: 'public/app/features/dashboard/acl/acl.html',
|
||||
controller: AclCtrl,
|
||||
bindToController: true,
|
||||
controllerAs: 'ctrl',
|
||||
scope: { dashboard: "=" }
|
||||
scope: {
|
||||
dismiss: "&"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -126,19 +135,19 @@ export interface FormModel {
|
||||
PermissionType: number;
|
||||
}
|
||||
|
||||
export interface Permission {
|
||||
id: number;
|
||||
orgId: number;
|
||||
dashboardId: number;
|
||||
created: Date;
|
||||
updated: Date;
|
||||
userId: number;
|
||||
userLogin: number;
|
||||
userEmail: string;
|
||||
userGroupId: number;
|
||||
userGroup: string;
|
||||
permissions: string[];
|
||||
permissionType: number[];
|
||||
export interface DashboardAcl {
|
||||
id?: number;
|
||||
dashboardId?: number;
|
||||
userId?: number;
|
||||
userLogin?: number;
|
||||
userEmail?: string;
|
||||
userGroupId?: number;
|
||||
userGroup?: string;
|
||||
permission?: number;
|
||||
permissionName?: string;
|
||||
role?: string;
|
||||
icon?: string;
|
||||
nameHtml?: string;
|
||||
}
|
||||
|
||||
coreModule.directive('aclSettings', aclSettings);
|
||||
coreModule.directive('dashAclModal', dashAclModal);
|
||||
|
@ -1,95 +1,64 @@
|
||||
<div class="navbar">
|
||||
<div class="navbar-inner">
|
||||
<a class="navbar-brand-btn pointer" ng-click="ctrl.toggleSideMenu()">
|
||||
<span class="navbar-brand-btn-background">
|
||||
<img src="public/img/grafana_icon.svg"></img>
|
||||
</span>
|
||||
<i class="icon-gf icon-gf-grafana_wordmark"></i>
|
||||
<i class="fa fa-caret-down"></i>
|
||||
<i class="fa fa-chevron-left"></i>
|
||||
<navbar model="ctrl.navModel">
|
||||
|
||||
<ul class="nav dash-playlist-actions" ng-if="ctrl.playlistSrv.isPlaying">
|
||||
<li>
|
||||
<a ng-click="ctrl.playlistSrv.prev()"><i class="fa fa-step-backward"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<a ng-click="ctrl.playlistSrv.stop()"><i class="fa fa-stop"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<a ng-click="ctrl.playlistSrv.next()"><i class="fa fa-step-forward"></i></a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="nav pull-left dashnav-action-icons">
|
||||
<li ng-show="::ctrl.dashboard.meta.canStar">
|
||||
<a class="pointer" ng-click="ctrl.starDashboard()">
|
||||
<i class="fa" ng-class="{'fa-star-o': !ctrl.dashboard.meta.isStarred, 'fa-star': ctrl.dashboard.meta.isStarred}" style="color: orange;"></i>
|
||||
</a>
|
||||
|
||||
<div class="navbar-section-wrapper">
|
||||
<a class="navbar-page-btn" ng-click="ctrl.showSearch()">
|
||||
<i class="icon-gf icon-gf-dashboard"></i>
|
||||
{{ctrl.dashboard.title}}
|
||||
<i class="fa fa-caret-down"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<ul class="nav dash-playlist-actions" ng-if="ctrl.playlistSrv.isPlaying">
|
||||
</li>
|
||||
<li ng-show="::ctrl.dashboard.meta.canShare" class="dropdown">
|
||||
<a class="pointer" ng-click="ctrl.hideTooltip($event)" bs-tooltip="'Share dashboard'" data-placement="bottom" data-toggle="dropdown"><i class="fa fa-share-square-o"></i></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<a ng-click="ctrl.playlistSrv.prev()"><i class="fa fa-step-backward"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<a ng-click="ctrl.playlistSrv.stop()"><i class="fa fa-stop"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<a ng-click="ctrl.playlistSrv.next()"><i class="fa fa-step-forward"></i></a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="nav pull-left dashnav-action-icons">
|
||||
<li ng-show="::ctrl.dashboard.meta.canStar">
|
||||
<a class="pointer" ng-click="ctrl.starDashboard()">
|
||||
<i class="fa" ng-class="{'fa-star-o': !ctrl.dashboard.meta.isStarred, 'fa-star': ctrl.dashboard.meta.isStarred}" style="color: orange;"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li ng-show="::ctrl.dashboard.meta.canShare" class="dropdown">
|
||||
<a class="pointer" ng-click="ctrl.hideTooltip($event)" bs-tooltip="'Share dashboard'" data-placement="bottom" data-toggle="dropdown"><i class="fa fa-share-square-o"></i></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<a class="pointer" ng-click="ctrl.shareDashboard(0)">
|
||||
<i class="fa fa-link"></i> Link to Dashboard
|
||||
<div class="dropdown-desc">Share an internal link to the current dashboard. Some configuration options available.</div>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="pointer" ng-click="ctrl.shareDashboard(1)">
|
||||
<i class="icon-gf icon-gf-snapshot"></i>Snapshot
|
||||
<div class="dropdown-desc">Interactive, publically accessible dashboard. Sensitive data is stripped out.</div>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="pointer" ng-click="ctrl.shareDashboard(2)">
|
||||
<i class="fa fa-cloud-upload"></i>Export
|
||||
<div class="dropdown-desc">Export the dashboard to a JSON file for others and to share on Grafana.com</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li ng-show="::ctrl.dashboard.meta.canSave">
|
||||
<a ng-click="ctrl.saveDashboard()" bs-tooltip="'Save dashboard <br> CTRL+S'" data-placement="bottom"><i class="fa fa-save"></i></a>
|
||||
</li>
|
||||
<li ng-if="::ctrl.dashboard.snapshot.originalUrl">
|
||||
<a ng-href="{{ctrl.dashboard.snapshot.originalUrl}}" bs-tooltip="'Open original dashboard'" data-placement="bottom"><i class="fa fa-link"></i></a>
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
<a class="pointer" data-toggle="dropdown">
|
||||
<i class="fa fa-cog"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu--navbar">
|
||||
<li ng-repeat="navItem in ::ctrl.navModel.menu" ng-class="{active: navItem.active}">
|
||||
<a class="pointer" ng-href="{{::navItem.url}}" ng-click="ctrl.navItemClicked(navItem, $event)">
|
||||
<i class="{{::navItem.icon}}" ng-show="::navItem.icon"></i>
|
||||
{{::navItem.title}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="nav pull-right">
|
||||
<li ng-show="ctrl.dashboard.meta.fullscreen" class="dashnav-back-to-dashboard">
|
||||
<a ng-click="ctrl.exitFullscreen()">
|
||||
Back to dashboard
|
||||
<a class="pointer" ng-click="ctrl.shareDashboard(0)">
|
||||
<i class="fa fa-link"></i> Link to Dashboard
|
||||
<div class="dropdown-desc">Share an internal link to the current dashboard. Some configuration options available.</div>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<gf-time-picker dashboard="ctrl.dashboard"></gf-time-picker>
|
||||
<a class="pointer" ng-click="ctrl.shareDashboard(1)">
|
||||
<i class="icon-gf icon-gf-snapshot"></i>Snapshot
|
||||
<div class="dropdown-desc">Interactive, publically accessible dashboard. Sensitive data is stripped out.</div>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="pointer" ng-click="ctrl.shareDashboard(2)">
|
||||
<i class="fa fa-cloud-upload"></i>Export
|
||||
<div class="dropdown-desc">Export the dashboard to a JSON file for others and to share on Grafana.com</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li ng-show="::ctrl.dashboard.meta.canSave">
|
||||
<a ng-click="ctrl.saveDashboard()" bs-tooltip="'Save dashboard <br> CTRL+S'" data-placement="bottom"><i class="fa fa-save"></i></a>
|
||||
</li>
|
||||
<li ng-if="::ctrl.dashboard.snapshot.originalUrl">
|
||||
<a ng-href="{{ctrl.dashboard.snapshot.originalUrl}}" bs-tooltip="'Open original dashboard'" data-placement="bottom"><i class="fa fa-link"></i></a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="nav pull-right">
|
||||
<li ng-show="ctrl.dashboard.meta.fullscreen" class="dashnav-back-to-dashboard">
|
||||
<a ng-click="ctrl.exitFullscreen()">
|
||||
Back to dashboard
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<gf-time-picker dashboard="ctrl.dashboard"></gf-time-picker>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</navbar>
|
||||
|
||||
<dashboard-search></dashboard-search>
|
||||
|
@ -143,17 +143,6 @@ export class DashNavCtrl {
|
||||
onFolderChange(parentId) {
|
||||
this.dashboard.parentId = parentId;
|
||||
}
|
||||
|
||||
showSearch() {
|
||||
this.$rootScope.appEvent('show-dash-search');
|
||||
}
|
||||
|
||||
navItemClicked(navItem, evt) {
|
||||
if (navItem.clickHandler) {
|
||||
navItem.clickHandler();
|
||||
evt.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function dashNavDirective() {
|
||||
|
@ -1,4 +1,3 @@
|
||||
<div class="modal-body">
|
||||
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-header-title">
|
||||
|
@ -275,3 +275,22 @@
|
||||
content: "\f11c";
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu.dropdown-menu--new {
|
||||
li a {
|
||||
padding: $spacer/2 $spacer;
|
||||
border-left: 2px solid $side-menu-bg;
|
||||
background: $side-menu-bg;
|
||||
|
||||
i {
|
||||
display: inline-block;
|
||||
padding-right: 21px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@include left-brand-border-gradient();
|
||||
color: $link-hover-color;
|
||||
background: $input-label-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -149,7 +149,6 @@
|
||||
}
|
||||
|
||||
.dropdown-menu.dropdown-menu--navbar {
|
||||
top: 100%;
|
||||
min-width: 100%;
|
||||
margin-top: 0px;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user