mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Service Accounts: Enable adding folder, dashboard and data source permissions to service accounts (#76133)
* Add SAs to Datasource permissions Co-authored-by: ievaVasiljeva <ieva.vasiljeva@grafana.com> * add SAs to dashboards/folders managed permissions * Update public/app/core/components/AccessControl/Permissions.tsx Co-authored-by: Ieva <ieva.vasiljeva@grafana.com> * regenerate i18n * add doc --------- Co-authored-by: ievaVasiljeva <ieva.vasiljeva@grafana.com>
This commit is contained in:
parent
8e576911b7
commit
4474f19836
@ -18,12 +18,12 @@ For more information about dashboard permissions, refer to [Dashboard permission
|
|||||||
|
|
||||||
## Grant folder permissions
|
## Grant folder permissions
|
||||||
|
|
||||||
When you grant user permissions for folders, that setting applies to all dashboards and subfolders contained in the folder. Consider using this approach to assigning dashboard and folder permissions when you have users or teams who require access to groups of related dashboards or folders.
|
When you grant user permissions for folders, that setting applies to all dashboards and subfolders contained in the folder. Consider using this approach to assigning dashboard and folder permissions when you have users, service accounts or teams who require access to groups of related dashboards or folders.
|
||||||
|
|
||||||
### Before you begin
|
### Before you begin
|
||||||
|
|
||||||
- Ensure you have organization administrator privileges
|
- Ensure you have organization administrator privileges
|
||||||
- Identify the dashboard folder permissions you want to modify and the users or teams to which you want to grant access. For more information about dashboard permissions, refer to [Dashboard permissions]({{< relref "../../roles-and-permissions/#dashboard-permissions" >}}).
|
- Identify the dashboard folder permissions you want to modify and the users, service accounts or teams to which you want to grant access. For more information about dashboard permissions, refer to [Dashboard permissions]({{< relref "../../roles-and-permissions/#dashboard-permissions" >}}).
|
||||||
|
|
||||||
**To grant dashboard folder permissions**:
|
**To grant dashboard folder permissions**:
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ When you grant user permissions for folders, that setting applies to all dashboa
|
|||||||
1. In the left-side menu, click **Dashboards**.
|
1. In the left-side menu, click **Dashboards**.
|
||||||
1. Hover your mouse cursor over a folder and click **Go to folder**.
|
1. Hover your mouse cursor over a folder and click **Go to folder**.
|
||||||
1. Click the **Permissions** tab, and then click **Add a permission**.
|
1. Click the **Permissions** tab, and then click **Add a permission**.
|
||||||
1. In the **Add Permission For** dropdown menu, select **User**, **Team**, or **Role**.
|
1. In the **Add Permission For** dropdown menu, select **User**, **Service Account**, **Team**, or **Role**.
|
||||||
1. Select the user, team, or role.
|
1. Select the user, team, or role.
|
||||||
1. Select the permission and click **Save**.
|
1. Select the permission and click **Save**.
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ Grant dashboard permissions when you want to restrict or enhance dashboard acces
|
|||||||
1. Open a dashboard.
|
1. Open a dashboard.
|
||||||
1. In the top right corner of the dashboard, click **Dashboard settings** (the cog icon).
|
1. In the top right corner of the dashboard, click **Dashboard settings** (the cog icon).
|
||||||
1. Click **Permissions** in left-side menu, and then **Add a permission**.
|
1. Click **Permissions** in left-side menu, and then **Add a permission**.
|
||||||
1. In the **Add Permission For** dropdown menu, select **User**, **Team**, or **Role**.
|
1. In the **Add Permission For** dropdown menu, select **User**, **Service Account**, **Team**, or **Role**.
|
||||||
1. Select the user, team, or role.
|
1. Select the user, team, or role.
|
||||||
1. Select the permission and click **Save**.
|
1. Select the permission and click **Save**.
|
||||||
|
|
||||||
|
@ -225,21 +225,22 @@ type GetUserPermissionsQuery struct {
|
|||||||
// ResourcePermission is structure that holds all actions that either a team / user / builtin-role
|
// ResourcePermission is structure that holds all actions that either a team / user / builtin-role
|
||||||
// can perform against specific resource.
|
// can perform against specific resource.
|
||||||
type ResourcePermission struct {
|
type ResourcePermission struct {
|
||||||
ID int64
|
ID int64
|
||||||
RoleName string
|
RoleName string
|
||||||
Actions []string
|
Actions []string
|
||||||
Scope string
|
Scope string
|
||||||
UserId int64
|
UserId int64
|
||||||
UserLogin string
|
UserLogin string
|
||||||
UserEmail string
|
UserEmail string
|
||||||
TeamId int64
|
TeamId int64
|
||||||
TeamEmail string
|
TeamEmail string
|
||||||
Team string
|
Team string
|
||||||
BuiltInRole string
|
BuiltInRole string
|
||||||
IsManaged bool
|
IsManaged bool
|
||||||
IsInherited bool
|
IsInherited bool
|
||||||
Created time.Time
|
IsServiceAccount bool
|
||||||
Updated time.Time
|
Created time.Time
|
||||||
|
Updated time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ResourcePermission) Contains(targetActions []string) bool {
|
func (p *ResourcePermission) Contains(targetActions []string) bool {
|
||||||
|
@ -165,9 +165,10 @@ func ProvideDashboardPermissions(
|
|||||||
return []string{}, nil
|
return []string{}, nil
|
||||||
},
|
},
|
||||||
Assignments: resourcepermissions.Assignments{
|
Assignments: resourcepermissions.Assignments{
|
||||||
Users: true,
|
Users: true,
|
||||||
Teams: true,
|
Teams: true,
|
||||||
BuiltInRoles: true,
|
BuiltInRoles: true,
|
||||||
|
ServiceAccounts: true,
|
||||||
},
|
},
|
||||||
PermissionsToActions: map[string][]string{
|
PermissionsToActions: map[string][]string{
|
||||||
"View": DashboardViewActions,
|
"View": DashboardViewActions,
|
||||||
@ -226,9 +227,10 @@ func ProvideFolderPermissions(
|
|||||||
return dashboards.GetInheritedScopes(ctx, orgID, resourceID, folderService)
|
return dashboards.GetInheritedScopes(ctx, orgID, resourceID, folderService)
|
||||||
},
|
},
|
||||||
Assignments: resourcepermissions.Assignments{
|
Assignments: resourcepermissions.Assignments{
|
||||||
Users: true,
|
Users: true,
|
||||||
Teams: true,
|
Teams: true,
|
||||||
BuiltInRoles: true,
|
BuiltInRoles: true,
|
||||||
|
ServiceAccounts: true,
|
||||||
},
|
},
|
||||||
PermissionsToActions: map[string][]string{
|
PermissionsToActions: map[string][]string{
|
||||||
"View": append(DashboardViewActions, FolderViewActions...),
|
"View": append(DashboardViewActions, FolderViewActions...),
|
||||||
|
@ -57,9 +57,10 @@ func (a *api) registerEndpoints() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Assignments struct {
|
type Assignments struct {
|
||||||
Users bool `json:"users"`
|
Users bool `json:"users"`
|
||||||
Teams bool `json:"teams"`
|
ServiceAccounts bool `json:"serviceAccounts"`
|
||||||
BuiltInRoles bool `json:"builtInRoles"`
|
Teams bool `json:"teams"`
|
||||||
|
BuiltInRoles bool `json:"builtInRoles"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Description struct {
|
type Description struct {
|
||||||
@ -75,19 +76,20 @@ func (a *api) getDescription(c *contextmodel.ReqContext) response.Response {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type resourcePermissionDTO struct {
|
type resourcePermissionDTO struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
RoleName string `json:"roleName"`
|
RoleName string `json:"roleName"`
|
||||||
IsManaged bool `json:"isManaged"`
|
IsManaged bool `json:"isManaged"`
|
||||||
IsInherited bool `json:"isInherited"`
|
IsInherited bool `json:"isInherited"`
|
||||||
UserID int64 `json:"userId,omitempty"`
|
IsServiceAccount bool `json:"isServiceAccount"`
|
||||||
UserLogin string `json:"userLogin,omitempty"`
|
UserID int64 `json:"userId,omitempty"`
|
||||||
UserAvatarUrl string `json:"userAvatarUrl,omitempty"`
|
UserLogin string `json:"userLogin,omitempty"`
|
||||||
Team string `json:"team,omitempty"`
|
UserAvatarUrl string `json:"userAvatarUrl,omitempty"`
|
||||||
TeamID int64 `json:"teamId,omitempty"`
|
Team string `json:"team,omitempty"`
|
||||||
TeamAvatarUrl string `json:"teamAvatarUrl,omitempty"`
|
TeamID int64 `json:"teamId,omitempty"`
|
||||||
BuiltInRole string `json:"builtInRole,omitempty"`
|
TeamAvatarUrl string `json:"teamAvatarUrl,omitempty"`
|
||||||
Actions []string `json:"actions"`
|
BuiltInRole string `json:"builtInRole,omitempty"`
|
||||||
Permission string `json:"permission"`
|
Actions []string `json:"actions"`
|
||||||
|
Permission string `json:"permission"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *api) getPermissions(c *contextmodel.ReqContext) response.Response {
|
func (a *api) getPermissions(c *contextmodel.ReqContext) response.Response {
|
||||||
@ -115,19 +117,20 @@ func (a *api) getPermissions(c *contextmodel.ReqContext) response.Response {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dto = append(dto, resourcePermissionDTO{
|
dto = append(dto, resourcePermissionDTO{
|
||||||
ID: p.ID,
|
ID: p.ID,
|
||||||
RoleName: p.RoleName,
|
RoleName: p.RoleName,
|
||||||
UserID: p.UserId,
|
UserID: p.UserId,
|
||||||
UserLogin: p.UserLogin,
|
UserLogin: p.UserLogin,
|
||||||
UserAvatarUrl: dtos.GetGravatarUrl(p.UserEmail),
|
UserAvatarUrl: dtos.GetGravatarUrl(p.UserEmail),
|
||||||
Team: p.Team,
|
Team: p.Team,
|
||||||
TeamID: p.TeamId,
|
TeamID: p.TeamId,
|
||||||
TeamAvatarUrl: teamAvatarUrl,
|
TeamAvatarUrl: teamAvatarUrl,
|
||||||
BuiltInRole: p.BuiltInRole,
|
BuiltInRole: p.BuiltInRole,
|
||||||
Actions: p.Actions,
|
Actions: p.Actions,
|
||||||
Permission: permission,
|
Permission: permission,
|
||||||
IsManaged: p.IsManaged,
|
IsManaged: p.IsManaged,
|
||||||
IsInherited: p.IsInherited,
|
IsInherited: p.IsInherited,
|
||||||
|
IsServiceAccount: p.IsServiceAccount,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,19 +25,20 @@ type store struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type flatResourcePermission struct {
|
type flatResourcePermission struct {
|
||||||
ID int64 `xorm:"id"`
|
ID int64 `xorm:"id"`
|
||||||
RoleName string
|
RoleName string
|
||||||
Action string
|
Action string
|
||||||
Scope string
|
Scope string
|
||||||
UserId int64
|
UserId int64
|
||||||
UserLogin string
|
UserLogin string
|
||||||
UserEmail string
|
UserEmail string
|
||||||
TeamId int64
|
TeamId int64
|
||||||
TeamEmail string
|
TeamEmail string
|
||||||
Team string
|
Team string
|
||||||
BuiltInRole string
|
BuiltInRole string
|
||||||
Created time.Time
|
IsServiceAccount bool `xorm:"is_service_account"`
|
||||||
Updated time.Time
|
Created time.Time
|
||||||
|
Updated time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *flatResourcePermission) IsManaged(scope string) bool {
|
func (p *flatResourcePermission) IsManaged(scope string) bool {
|
||||||
@ -307,6 +308,7 @@ func (s *store) getResourcePermissions(sess *db.Session, orgID int64, query GetR
|
|||||||
userSelect := rawSelect + `
|
userSelect := rawSelect + `
|
||||||
ur.user_id AS user_id,
|
ur.user_id AS user_id,
|
||||||
u.login AS user_login,
|
u.login AS user_login,
|
||||||
|
u.is_service_account AS is_service_account,
|
||||||
u.email AS user_email,
|
u.email AS user_email,
|
||||||
0 AS team_id,
|
0 AS team_id,
|
||||||
'' AS team,
|
'' AS team,
|
||||||
@ -317,6 +319,7 @@ func (s *store) getResourcePermissions(sess *db.Session, orgID int64, query GetR
|
|||||||
teamSelect := rawSelect + `
|
teamSelect := rawSelect + `
|
||||||
0 AS user_id,
|
0 AS user_id,
|
||||||
'' AS user_login,
|
'' AS user_login,
|
||||||
|
` + s.sql.GetDialect().BooleanStr(false) + ` AS is_service_account,
|
||||||
'' AS user_email,
|
'' AS user_email,
|
||||||
tr.team_id AS team_id,
|
tr.team_id AS team_id,
|
||||||
t.name AS team,
|
t.name AS team,
|
||||||
@ -327,6 +330,7 @@ func (s *store) getResourcePermissions(sess *db.Session, orgID int64, query GetR
|
|||||||
builtinSelect := rawSelect + `
|
builtinSelect := rawSelect + `
|
||||||
0 AS user_id,
|
0 AS user_id,
|
||||||
'' AS user_login,
|
'' AS user_login,
|
||||||
|
` + s.sql.GetDialect().BooleanStr(false) + ` AS is_service_account,
|
||||||
'' AS user_email,
|
'' AS user_email,
|
||||||
0 as team_id,
|
0 as team_id,
|
||||||
'' AS team,
|
'' AS team,
|
||||||
@ -480,21 +484,22 @@ func flatPermissionsToResourcePermission(scope string, permissions []flatResourc
|
|||||||
|
|
||||||
first := permissions[0]
|
first := permissions[0]
|
||||||
return &accesscontrol.ResourcePermission{
|
return &accesscontrol.ResourcePermission{
|
||||||
ID: first.ID,
|
ID: first.ID,
|
||||||
RoleName: first.RoleName,
|
RoleName: first.RoleName,
|
||||||
Actions: actions,
|
Actions: actions,
|
||||||
Scope: first.Scope,
|
Scope: first.Scope,
|
||||||
UserId: first.UserId,
|
UserId: first.UserId,
|
||||||
UserLogin: first.UserLogin,
|
UserLogin: first.UserLogin,
|
||||||
UserEmail: first.UserEmail,
|
UserEmail: first.UserEmail,
|
||||||
TeamId: first.TeamId,
|
TeamId: first.TeamId,
|
||||||
TeamEmail: first.TeamEmail,
|
TeamEmail: first.TeamEmail,
|
||||||
Team: first.Team,
|
Team: first.Team,
|
||||||
BuiltInRole: first.BuiltInRole,
|
BuiltInRole: first.BuiltInRole,
|
||||||
Created: first.Created,
|
Created: first.Created,
|
||||||
Updated: first.Updated,
|
Updated: first.Updated,
|
||||||
IsManaged: first.IsManaged(scope),
|
IsManaged: first.IsManaged(scope),
|
||||||
IsInherited: first.IsInherited(scope),
|
IsInherited: first.IsInherited(scope),
|
||||||
|
IsServiceAccount: first.IsServiceAccount,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import React, { useEffect, useMemo, useState } from 'react';
|
|||||||
import { Stack } from '@grafana/experimental';
|
import { Stack } from '@grafana/experimental';
|
||||||
import { Button, Form, Select } from '@grafana/ui';
|
import { Button, Form, Select } from '@grafana/ui';
|
||||||
import { CloseButton } from 'app/core/components/CloseButton/CloseButton';
|
import { CloseButton } from 'app/core/components/CloseButton/CloseButton';
|
||||||
|
import { ServiceAccountPicker } from 'app/core/components/Select/ServiceAccountPicker';
|
||||||
import { TeamPicker } from 'app/core/components/Select/TeamPicker';
|
import { TeamPicker } from 'app/core/components/Select/TeamPicker';
|
||||||
import { UserPicker } from 'app/core/components/Select/UserPicker';
|
import { UserPicker } from 'app/core/components/Select/UserPicker';
|
||||||
import { Trans, t } from 'app/core/internationalization';
|
import { Trans, t } from 'app/core/internationalization';
|
||||||
@ -36,6 +37,12 @@ export const AddPermission = ({
|
|||||||
if (assignments.users) {
|
if (assignments.users) {
|
||||||
options.push({ value: PermissionTarget.User, label: t('access-control.add-permission.user-label', 'User') });
|
options.push({ value: PermissionTarget.User, label: t('access-control.add-permission.user-label', 'User') });
|
||||||
}
|
}
|
||||||
|
if (assignments.serviceAccounts) {
|
||||||
|
options.push({
|
||||||
|
value: PermissionTarget.ServiceAccount,
|
||||||
|
label: t('access-control.add-permission.serviceaccount-label', 'Service Account'),
|
||||||
|
});
|
||||||
|
}
|
||||||
if (assignments.teams) {
|
if (assignments.teams) {
|
||||||
options.push({ value: PermissionTarget.Team, label: t('access-control.add-permission.team-label', 'Team') });
|
options.push({ value: PermissionTarget.Team, label: t('access-control.add-permission.team-label', 'Team') });
|
||||||
}
|
}
|
||||||
@ -57,6 +64,7 @@ export const AddPermission = ({
|
|||||||
const isValid = () =>
|
const isValid = () =>
|
||||||
(target === PermissionTarget.Team && teamId > 0) ||
|
(target === PermissionTarget.Team && teamId > 0) ||
|
||||||
(target === PermissionTarget.User && userId > 0) ||
|
(target === PermissionTarget.User && userId > 0) ||
|
||||||
|
(target === PermissionTarget.ServiceAccount && userId > 0) ||
|
||||||
(PermissionTarget.BuiltInRole && OrgRole.hasOwnProperty(builtInRole));
|
(PermissionTarget.BuiltInRole && OrgRole.hasOwnProperty(builtInRole));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -82,6 +90,10 @@ export const AddPermission = ({
|
|||||||
|
|
||||||
{target === PermissionTarget.User && <UserPicker onSelected={(u) => setUserId(u?.value || 0)} />}
|
{target === PermissionTarget.User && <UserPicker onSelected={(u) => setUserId(u?.value || 0)} />}
|
||||||
|
|
||||||
|
{target === PermissionTarget.ServiceAccount && (
|
||||||
|
<ServiceAccountPicker onSelected={(u) => setUserId(u?.value || 0)} />
|
||||||
|
)}
|
||||||
|
|
||||||
{target === PermissionTarget.Team && <TeamPicker onSelected={(t) => setTeamId(t.value?.id || 0)} />}
|
{target === PermissionTarget.Team && <TeamPicker onSelected={(t) => setTeamId(t.value?.id || 0)} />}
|
||||||
|
|
||||||
{target === PermissionTarget.BuiltInRole && (
|
{target === PermissionTarget.BuiltInRole && (
|
||||||
|
@ -22,12 +22,13 @@ const INITIAL_DESCRIPTION: Description = {
|
|||||||
assignments: {
|
assignments: {
|
||||||
teams: false,
|
teams: false,
|
||||||
users: false,
|
users: false,
|
||||||
|
serviceAccounts: false,
|
||||||
builtInRoles: false,
|
builtInRoles: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
type ResourceId = string | number;
|
type ResourceId = string | number;
|
||||||
type Type = 'users' | 'teams' | 'builtInRoles';
|
type Type = 'users' | 'teams' | 'serviceAccounts' | 'builtInRoles';
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
title?: string;
|
title?: string;
|
||||||
@ -68,6 +69,8 @@ export const Permissions = ({
|
|||||||
let promise: Promise<void> | null = null;
|
let promise: Promise<void> | null = null;
|
||||||
if (state.target === PermissionTarget.User) {
|
if (state.target === PermissionTarget.User) {
|
||||||
promise = setUserPermission(resource, resourceId, state.userId!, state.permission);
|
promise = setUserPermission(resource, resourceId, state.userId!, state.permission);
|
||||||
|
} else if (state.target === PermissionTarget.ServiceAccount) {
|
||||||
|
promise = setUserPermission(resource, resourceId, state.userId!, state.permission);
|
||||||
} else if (state.target === PermissionTarget.Team) {
|
} else if (state.target === PermissionTarget.Team) {
|
||||||
promise = setTeamPermission(resource, resourceId, state.teamId!, state.permission);
|
promise = setTeamPermission(resource, resourceId, state.teamId!, state.permission);
|
||||||
} else if (state.target === PermissionTarget.BuiltInRole) {
|
} else if (state.target === PermissionTarget.BuiltInRole) {
|
||||||
@ -85,6 +88,8 @@ export const Permissions = ({
|
|||||||
promise = setUserPermission(resource, resourceId, item.userId, EMPTY_PERMISSION);
|
promise = setUserPermission(resource, resourceId, item.userId, EMPTY_PERMISSION);
|
||||||
} else if (item.teamId) {
|
} else if (item.teamId) {
|
||||||
promise = setTeamPermission(resource, resourceId, item.teamId, EMPTY_PERMISSION);
|
promise = setTeamPermission(resource, resourceId, item.teamId, EMPTY_PERMISSION);
|
||||||
|
} else if (item.isServiceAccount && item.userId) {
|
||||||
|
promise = setUserPermission(resource, resourceId, item.userId, EMPTY_PERMISSION);
|
||||||
} else if (item.builtInRole) {
|
} else if (item.builtInRole) {
|
||||||
promise = setBuiltInRolePermission(resource, resourceId, item.builtInRole, EMPTY_PERMISSION);
|
promise = setBuiltInRolePermission(resource, resourceId, item.builtInRole, EMPTY_PERMISSION);
|
||||||
}
|
}
|
||||||
@ -100,6 +105,8 @@ export const Permissions = ({
|
|||||||
}
|
}
|
||||||
if (item.userId) {
|
if (item.userId) {
|
||||||
onAdd({ permission, userId: item.userId, target: PermissionTarget.User });
|
onAdd({ permission, userId: item.userId, target: PermissionTarget.User });
|
||||||
|
} else if (item.isServiceAccount) {
|
||||||
|
onAdd({ permission, userId: item.userId, target: PermissionTarget.User });
|
||||||
} else if (item.teamId) {
|
} else if (item.teamId) {
|
||||||
onAdd({ permission, teamId: item.teamId, target: PermissionTarget.Team });
|
onAdd({ permission, teamId: item.teamId, target: PermissionTarget.Team });
|
||||||
} else if (item.builtInRole) {
|
} else if (item.builtInRole) {
|
||||||
@ -118,7 +125,15 @@ export const Permissions = ({
|
|||||||
const users = useMemo(
|
const users = useMemo(
|
||||||
() =>
|
() =>
|
||||||
sortBy(
|
sortBy(
|
||||||
items.filter((i) => i.userId),
|
items.filter((i) => i.userId && !i.isServiceAccount),
|
||||||
|
['userLogin', 'isManaged']
|
||||||
|
),
|
||||||
|
[items]
|
||||||
|
);
|
||||||
|
const serviceAccounts = useMemo(
|
||||||
|
() =>
|
||||||
|
sortBy(
|
||||||
|
items.filter((i) => i.userId && i.isServiceAccount),
|
||||||
['userLogin', 'isManaged']
|
['userLogin', 'isManaged']
|
||||||
),
|
),
|
||||||
[items]
|
[items]
|
||||||
@ -134,6 +149,7 @@ export const Permissions = ({
|
|||||||
|
|
||||||
const titleRole = t('access-control.permissions.role', 'Role');
|
const titleRole = t('access-control.permissions.role', 'Role');
|
||||||
const titleUser = t('access-control.permissions.user', 'User');
|
const titleUser = t('access-control.permissions.user', 'User');
|
||||||
|
const titleServiceAccount = t('access-control.permissions.serviceaccount', 'Service Account');
|
||||||
const titleTeam = t('access-control.permissions.team', 'Team');
|
const titleTeam = t('access-control.permissions.team', 'Team');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -202,6 +218,16 @@ export const Permissions = ({
|
|||||||
onRemove={onRemove}
|
onRemove={onRemove}
|
||||||
canSet={canSetPermissions}
|
canSet={canSetPermissions}
|
||||||
/>
|
/>
|
||||||
|
<PermissionList
|
||||||
|
title={titleServiceAccount}
|
||||||
|
items={serviceAccounts}
|
||||||
|
compareKey={'userLogin'}
|
||||||
|
permissionLevels={desc.permissions}
|
||||||
|
onChange={onChange}
|
||||||
|
onRemove={onRemove}
|
||||||
|
canSet={canSetPermissions}
|
||||||
|
/>
|
||||||
|
|
||||||
<PermissionList
|
<PermissionList
|
||||||
title={titleTeam}
|
title={titleTeam}
|
||||||
items={teams}
|
items={teams}
|
||||||
|
@ -3,6 +3,7 @@ export type ResourcePermission = {
|
|||||||
resourceId: string;
|
resourceId: string;
|
||||||
isManaged: boolean;
|
isManaged: boolean;
|
||||||
isInherited: boolean;
|
isInherited: boolean;
|
||||||
|
isServiceAccount: boolean;
|
||||||
userId?: number;
|
userId?: number;
|
||||||
userLogin?: string;
|
userLogin?: string;
|
||||||
userAvatarUrl?: string;
|
userAvatarUrl?: string;
|
||||||
@ -26,6 +27,7 @@ export enum PermissionTarget {
|
|||||||
None = 'None',
|
None = 'None',
|
||||||
Team = 'Team',
|
Team = 'Team',
|
||||||
User = 'User',
|
User = 'User',
|
||||||
|
ServiceAccount = 'ServiceAccount',
|
||||||
BuiltInRole = 'builtInRole',
|
BuiltInRole = 'builtInRole',
|
||||||
}
|
}
|
||||||
export type Description = {
|
export type Description = {
|
||||||
@ -35,6 +37,7 @@ export type Description = {
|
|||||||
|
|
||||||
export type Assignments = {
|
export type Assignments = {
|
||||||
users: boolean;
|
users: boolean;
|
||||||
|
serviceAccounts: boolean;
|
||||||
teams: boolean;
|
teams: boolean;
|
||||||
builtInRoles: boolean;
|
builtInRoles: boolean;
|
||||||
};
|
};
|
||||||
|
77
public/app/core/components/Select/ServiceAccountPicker.tsx
Normal file
77
public/app/core/components/Select/ServiceAccountPicker.tsx
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import { debounce, DebouncedFuncLeading, isNil } from 'lodash';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
import { SelectableValue } from '@grafana/data';
|
||||||
|
import { getBackendSrv } from '@grafana/runtime';
|
||||||
|
import { AsyncSelect } from '@grafana/ui';
|
||||||
|
import { ServiceAccountDTO, ServiceAccountsState } from 'app/types';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
onSelected: (user: SelectableValue<ServiceAccountDTO['id']>) => void;
|
||||||
|
className?: string;
|
||||||
|
inputId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface State {
|
||||||
|
isLoading: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ServiceAccountPicker extends Component<Props, State> {
|
||||||
|
debouncedSearch: DebouncedFuncLeading<typeof this.search>;
|
||||||
|
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
this.state = { isLoading: false };
|
||||||
|
this.search = this.search.bind(this);
|
||||||
|
|
||||||
|
this.debouncedSearch = debounce(this.search, 300, {
|
||||||
|
leading: true,
|
||||||
|
trailing: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
search(query?: string) {
|
||||||
|
this.setState({ isLoading: true });
|
||||||
|
|
||||||
|
if (isNil(query)) {
|
||||||
|
query = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return getBackendSrv()
|
||||||
|
.get(`/api/serviceaccounts/search`)
|
||||||
|
.then((result: ServiceAccountsState) => {
|
||||||
|
return result.serviceAccounts.map((sa) => ({
|
||||||
|
id: sa.id,
|
||||||
|
value: sa.id,
|
||||||
|
label: sa.login,
|
||||||
|
imgUrl: sa.avatarUrl,
|
||||||
|
login: sa.login,
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.setState({ isLoading: false });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { className, onSelected, inputId } = this.props;
|
||||||
|
const { isLoading } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="service-account-picker" data-testid="serviceAccountPicker">
|
||||||
|
<AsyncSelect
|
||||||
|
isClearable
|
||||||
|
className={className}
|
||||||
|
inputId={inputId}
|
||||||
|
isLoading={isLoading}
|
||||||
|
defaultOptions={true}
|
||||||
|
loadOptions={this.debouncedSearch}
|
||||||
|
onChange={onSelected}
|
||||||
|
placeholder="Start typing to search for service accounts"
|
||||||
|
noOptionsMessage="No service accounts found"
|
||||||
|
aria-label="Service Account picker"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -88,6 +88,7 @@ export enum DataSourcePermissionLevel {
|
|||||||
export enum AclTarget {
|
export enum AclTarget {
|
||||||
Team = 'Team',
|
Team = 'Team',
|
||||||
User = 'User',
|
User = 'User',
|
||||||
|
ServiceAccount = 'ServiceAccount',
|
||||||
Viewer = 'Viewer',
|
Viewer = 'Viewer',
|
||||||
Editor = 'Editor',
|
Editor = 'Editor',
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
"access-control": {
|
"access-control": {
|
||||||
"add-permission": {
|
"add-permission": {
|
||||||
"role-label": "",
|
"role-label": "",
|
||||||
|
"serviceaccount-label": "",
|
||||||
"team-label": "",
|
"team-label": "",
|
||||||
"title": "",
|
"title": "",
|
||||||
"user-label": ""
|
"user-label": ""
|
||||||
@ -18,6 +19,7 @@
|
|||||||
"no-permissions": "",
|
"no-permissions": "",
|
||||||
"permissions-change-warning": "",
|
"permissions-change-warning": "",
|
||||||
"role": "",
|
"role": "",
|
||||||
|
"serviceaccount": "",
|
||||||
"team": "",
|
"team": "",
|
||||||
"title": "",
|
"title": "",
|
||||||
"user": ""
|
"user": ""
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
"access-control": {
|
"access-control": {
|
||||||
"add-permission": {
|
"add-permission": {
|
||||||
"role-label": "Role",
|
"role-label": "Role",
|
||||||
|
"serviceaccount-label": "Service Account",
|
||||||
"team-label": "Team",
|
"team-label": "Team",
|
||||||
"title": "Add permission for",
|
"title": "Add permission for",
|
||||||
"user-label": "User"
|
"user-label": "User"
|
||||||
@ -18,6 +19,7 @@
|
|||||||
"no-permissions": "There are no permissions",
|
"no-permissions": "There are no permissions",
|
||||||
"permissions-change-warning": "This will change permissions for this folder and all its descendants. In total, this will affect:",
|
"permissions-change-warning": "This will change permissions for this folder and all its descendants. In total, this will affect:",
|
||||||
"role": "Role",
|
"role": "Role",
|
||||||
|
"serviceaccount": "Service Account",
|
||||||
"team": "Team",
|
"team": "Team",
|
||||||
"title": "Permissions",
|
"title": "Permissions",
|
||||||
"user": "User"
|
"user": "User"
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
"access-control": {
|
"access-control": {
|
||||||
"add-permission": {
|
"add-permission": {
|
||||||
"role-label": "",
|
"role-label": "",
|
||||||
|
"serviceaccount-label": "",
|
||||||
"team-label": "",
|
"team-label": "",
|
||||||
"title": "",
|
"title": "",
|
||||||
"user-label": ""
|
"user-label": ""
|
||||||
@ -18,6 +19,7 @@
|
|||||||
"no-permissions": "",
|
"no-permissions": "",
|
||||||
"permissions-change-warning": "",
|
"permissions-change-warning": "",
|
||||||
"role": "",
|
"role": "",
|
||||||
|
"serviceaccount": "",
|
||||||
"team": "",
|
"team": "",
|
||||||
"title": "",
|
"title": "",
|
||||||
"user": ""
|
"user": ""
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
"access-control": {
|
"access-control": {
|
||||||
"add-permission": {
|
"add-permission": {
|
||||||
"role-label": "",
|
"role-label": "",
|
||||||
|
"serviceaccount-label": "",
|
||||||
"team-label": "",
|
"team-label": "",
|
||||||
"title": "",
|
"title": "",
|
||||||
"user-label": ""
|
"user-label": ""
|
||||||
@ -18,6 +19,7 @@
|
|||||||
"no-permissions": "",
|
"no-permissions": "",
|
||||||
"permissions-change-warning": "",
|
"permissions-change-warning": "",
|
||||||
"role": "",
|
"role": "",
|
||||||
|
"serviceaccount": "",
|
||||||
"team": "",
|
"team": "",
|
||||||
"title": "",
|
"title": "",
|
||||||
"user": ""
|
"user": ""
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
"access-control": {
|
"access-control": {
|
||||||
"add-permission": {
|
"add-permission": {
|
||||||
"role-label": "Ŗőľę",
|
"role-label": "Ŗőľę",
|
||||||
|
"serviceaccount-label": "Ŝęřvįčę Åččőūʼnŧ",
|
||||||
"team-label": "Ŧęäm",
|
"team-label": "Ŧęäm",
|
||||||
"title": "Åđđ pęřmįşşįőʼn ƒőř",
|
"title": "Åđđ pęřmįşşįőʼn ƒőř",
|
||||||
"user-label": "Ůşęř"
|
"user-label": "Ůşęř"
|
||||||
@ -18,6 +19,7 @@
|
|||||||
"no-permissions": "Ŧĥęřę äřę ʼnő pęřmįşşįőʼnş",
|
"no-permissions": "Ŧĥęřę äřę ʼnő pęřmįşşįőʼnş",
|
||||||
"permissions-change-warning": "Ŧĥįş ŵįľľ čĥäʼnģę pęřmįşşįőʼnş ƒőř ŧĥįş ƒőľđęř äʼnđ äľľ įŧş đęşčęʼnđäʼnŧş. Ĩʼn ŧőŧäľ, ŧĥįş ŵįľľ 䃃ęčŧ:",
|
"permissions-change-warning": "Ŧĥįş ŵįľľ čĥäʼnģę pęřmįşşįőʼnş ƒőř ŧĥįş ƒőľđęř äʼnđ äľľ įŧş đęşčęʼnđäʼnŧş. Ĩʼn ŧőŧäľ, ŧĥįş ŵįľľ 䃃ęčŧ:",
|
||||||
"role": "Ŗőľę",
|
"role": "Ŗőľę",
|
||||||
|
"serviceaccount": "Ŝęřvįčę Åččőūʼnŧ",
|
||||||
"team": "Ŧęäm",
|
"team": "Ŧęäm",
|
||||||
"title": "Pęřmįşşįőʼnş",
|
"title": "Pęřmįşşįőʼnş",
|
||||||
"user": "Ůşęř"
|
"user": "Ůşęř"
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
"access-control": {
|
"access-control": {
|
||||||
"add-permission": {
|
"add-permission": {
|
||||||
"role-label": "",
|
"role-label": "",
|
||||||
|
"serviceaccount-label": "",
|
||||||
"team-label": "",
|
"team-label": "",
|
||||||
"title": "",
|
"title": "",
|
||||||
"user-label": ""
|
"user-label": ""
|
||||||
@ -18,6 +19,7 @@
|
|||||||
"no-permissions": "",
|
"no-permissions": "",
|
||||||
"permissions-change-warning": "",
|
"permissions-change-warning": "",
|
||||||
"role": "",
|
"role": "",
|
||||||
|
"serviceaccount": "",
|
||||||
"team": "",
|
"team": "",
|
||||||
"title": "",
|
"title": "",
|
||||||
"user": ""
|
"user": ""
|
||||||
|
Loading…
Reference in New Issue
Block a user