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
|
||||
|
||||
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
|
||||
|
||||
- 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**:
|
||||
|
||||
@ -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. Hover your mouse cursor over a folder and click **Go to folder**.
|
||||
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 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. 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. 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 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
|
||||
// can perform against specific resource.
|
||||
type ResourcePermission struct {
|
||||
ID int64
|
||||
RoleName string
|
||||
Actions []string
|
||||
Scope string
|
||||
UserId int64
|
||||
UserLogin string
|
||||
UserEmail string
|
||||
TeamId int64
|
||||
TeamEmail string
|
||||
Team string
|
||||
BuiltInRole string
|
||||
IsManaged bool
|
||||
IsInherited bool
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
ID int64
|
||||
RoleName string
|
||||
Actions []string
|
||||
Scope string
|
||||
UserId int64
|
||||
UserLogin string
|
||||
UserEmail string
|
||||
TeamId int64
|
||||
TeamEmail string
|
||||
Team string
|
||||
BuiltInRole string
|
||||
IsManaged bool
|
||||
IsInherited bool
|
||||
IsServiceAccount bool
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
}
|
||||
|
||||
func (p *ResourcePermission) Contains(targetActions []string) bool {
|
||||
|
@ -165,9 +165,10 @@ func ProvideDashboardPermissions(
|
||||
return []string{}, nil
|
||||
},
|
||||
Assignments: resourcepermissions.Assignments{
|
||||
Users: true,
|
||||
Teams: true,
|
||||
BuiltInRoles: true,
|
||||
Users: true,
|
||||
Teams: true,
|
||||
BuiltInRoles: true,
|
||||
ServiceAccounts: true,
|
||||
},
|
||||
PermissionsToActions: map[string][]string{
|
||||
"View": DashboardViewActions,
|
||||
@ -226,9 +227,10 @@ func ProvideFolderPermissions(
|
||||
return dashboards.GetInheritedScopes(ctx, orgID, resourceID, folderService)
|
||||
},
|
||||
Assignments: resourcepermissions.Assignments{
|
||||
Users: true,
|
||||
Teams: true,
|
||||
BuiltInRoles: true,
|
||||
Users: true,
|
||||
Teams: true,
|
||||
BuiltInRoles: true,
|
||||
ServiceAccounts: true,
|
||||
},
|
||||
PermissionsToActions: map[string][]string{
|
||||
"View": append(DashboardViewActions, FolderViewActions...),
|
||||
|
@ -57,9 +57,10 @@ func (a *api) registerEndpoints() {
|
||||
}
|
||||
|
||||
type Assignments struct {
|
||||
Users bool `json:"users"`
|
||||
Teams bool `json:"teams"`
|
||||
BuiltInRoles bool `json:"builtInRoles"`
|
||||
Users bool `json:"users"`
|
||||
ServiceAccounts bool `json:"serviceAccounts"`
|
||||
Teams bool `json:"teams"`
|
||||
BuiltInRoles bool `json:"builtInRoles"`
|
||||
}
|
||||
|
||||
type Description struct {
|
||||
@ -75,19 +76,20 @@ func (a *api) getDescription(c *contextmodel.ReqContext) response.Response {
|
||||
}
|
||||
|
||||
type resourcePermissionDTO struct {
|
||||
ID int64 `json:"id"`
|
||||
RoleName string `json:"roleName"`
|
||||
IsManaged bool `json:"isManaged"`
|
||||
IsInherited bool `json:"isInherited"`
|
||||
UserID int64 `json:"userId,omitempty"`
|
||||
UserLogin string `json:"userLogin,omitempty"`
|
||||
UserAvatarUrl string `json:"userAvatarUrl,omitempty"`
|
||||
Team string `json:"team,omitempty"`
|
||||
TeamID int64 `json:"teamId,omitempty"`
|
||||
TeamAvatarUrl string `json:"teamAvatarUrl,omitempty"`
|
||||
BuiltInRole string `json:"builtInRole,omitempty"`
|
||||
Actions []string `json:"actions"`
|
||||
Permission string `json:"permission"`
|
||||
ID int64 `json:"id"`
|
||||
RoleName string `json:"roleName"`
|
||||
IsManaged bool `json:"isManaged"`
|
||||
IsInherited bool `json:"isInherited"`
|
||||
IsServiceAccount bool `json:"isServiceAccount"`
|
||||
UserID int64 `json:"userId,omitempty"`
|
||||
UserLogin string `json:"userLogin,omitempty"`
|
||||
UserAvatarUrl string `json:"userAvatarUrl,omitempty"`
|
||||
Team string `json:"team,omitempty"`
|
||||
TeamID int64 `json:"teamId,omitempty"`
|
||||
TeamAvatarUrl string `json:"teamAvatarUrl,omitempty"`
|
||||
BuiltInRole string `json:"builtInRole,omitempty"`
|
||||
Actions []string `json:"actions"`
|
||||
Permission string `json:"permission"`
|
||||
}
|
||||
|
||||
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{
|
||||
ID: p.ID,
|
||||
RoleName: p.RoleName,
|
||||
UserID: p.UserId,
|
||||
UserLogin: p.UserLogin,
|
||||
UserAvatarUrl: dtos.GetGravatarUrl(p.UserEmail),
|
||||
Team: p.Team,
|
||||
TeamID: p.TeamId,
|
||||
TeamAvatarUrl: teamAvatarUrl,
|
||||
BuiltInRole: p.BuiltInRole,
|
||||
Actions: p.Actions,
|
||||
Permission: permission,
|
||||
IsManaged: p.IsManaged,
|
||||
IsInherited: p.IsInherited,
|
||||
ID: p.ID,
|
||||
RoleName: p.RoleName,
|
||||
UserID: p.UserId,
|
||||
UserLogin: p.UserLogin,
|
||||
UserAvatarUrl: dtos.GetGravatarUrl(p.UserEmail),
|
||||
Team: p.Team,
|
||||
TeamID: p.TeamId,
|
||||
TeamAvatarUrl: teamAvatarUrl,
|
||||
BuiltInRole: p.BuiltInRole,
|
||||
Actions: p.Actions,
|
||||
Permission: permission,
|
||||
IsManaged: p.IsManaged,
|
||||
IsInherited: p.IsInherited,
|
||||
IsServiceAccount: p.IsServiceAccount,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -25,19 +25,20 @@ type store struct {
|
||||
}
|
||||
|
||||
type flatResourcePermission struct {
|
||||
ID int64 `xorm:"id"`
|
||||
RoleName string
|
||||
Action string
|
||||
Scope string
|
||||
UserId int64
|
||||
UserLogin string
|
||||
UserEmail string
|
||||
TeamId int64
|
||||
TeamEmail string
|
||||
Team string
|
||||
BuiltInRole string
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
ID int64 `xorm:"id"`
|
||||
RoleName string
|
||||
Action string
|
||||
Scope string
|
||||
UserId int64
|
||||
UserLogin string
|
||||
UserEmail string
|
||||
TeamId int64
|
||||
TeamEmail string
|
||||
Team string
|
||||
BuiltInRole string
|
||||
IsServiceAccount bool `xorm:"is_service_account"`
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
}
|
||||
|
||||
func (p *flatResourcePermission) IsManaged(scope string) bool {
|
||||
@ -307,6 +308,7 @@ func (s *store) getResourcePermissions(sess *db.Session, orgID int64, query GetR
|
||||
userSelect := rawSelect + `
|
||||
ur.user_id AS user_id,
|
||||
u.login AS user_login,
|
||||
u.is_service_account AS is_service_account,
|
||||
u.email AS user_email,
|
||||
0 AS team_id,
|
||||
'' AS team,
|
||||
@ -317,6 +319,7 @@ func (s *store) getResourcePermissions(sess *db.Session, orgID int64, query GetR
|
||||
teamSelect := rawSelect + `
|
||||
0 AS user_id,
|
||||
'' AS user_login,
|
||||
` + s.sql.GetDialect().BooleanStr(false) + ` AS is_service_account,
|
||||
'' AS user_email,
|
||||
tr.team_id AS team_id,
|
||||
t.name AS team,
|
||||
@ -327,6 +330,7 @@ func (s *store) getResourcePermissions(sess *db.Session, orgID int64, query GetR
|
||||
builtinSelect := rawSelect + `
|
||||
0 AS user_id,
|
||||
'' AS user_login,
|
||||
` + s.sql.GetDialect().BooleanStr(false) + ` AS is_service_account,
|
||||
'' AS user_email,
|
||||
0 as team_id,
|
||||
'' AS team,
|
||||
@ -480,21 +484,22 @@ func flatPermissionsToResourcePermission(scope string, permissions []flatResourc
|
||||
|
||||
first := permissions[0]
|
||||
return &accesscontrol.ResourcePermission{
|
||||
ID: first.ID,
|
||||
RoleName: first.RoleName,
|
||||
Actions: actions,
|
||||
Scope: first.Scope,
|
||||
UserId: first.UserId,
|
||||
UserLogin: first.UserLogin,
|
||||
UserEmail: first.UserEmail,
|
||||
TeamId: first.TeamId,
|
||||
TeamEmail: first.TeamEmail,
|
||||
Team: first.Team,
|
||||
BuiltInRole: first.BuiltInRole,
|
||||
Created: first.Created,
|
||||
Updated: first.Updated,
|
||||
IsManaged: first.IsManaged(scope),
|
||||
IsInherited: first.IsInherited(scope),
|
||||
ID: first.ID,
|
||||
RoleName: first.RoleName,
|
||||
Actions: actions,
|
||||
Scope: first.Scope,
|
||||
UserId: first.UserId,
|
||||
UserLogin: first.UserLogin,
|
||||
UserEmail: first.UserEmail,
|
||||
TeamId: first.TeamId,
|
||||
TeamEmail: first.TeamEmail,
|
||||
Team: first.Team,
|
||||
BuiltInRole: first.BuiltInRole,
|
||||
Created: first.Created,
|
||||
Updated: first.Updated,
|
||||
IsManaged: first.IsManaged(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 { Button, Form, Select } from '@grafana/ui';
|
||||
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 { UserPicker } from 'app/core/components/Select/UserPicker';
|
||||
import { Trans, t } from 'app/core/internationalization';
|
||||
@ -36,6 +37,12 @@ export const AddPermission = ({
|
||||
if (assignments.users) {
|
||||
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) {
|
||||
options.push({ value: PermissionTarget.Team, label: t('access-control.add-permission.team-label', 'Team') });
|
||||
}
|
||||
@ -57,6 +64,7 @@ export const AddPermission = ({
|
||||
const isValid = () =>
|
||||
(target === PermissionTarget.Team && teamId > 0) ||
|
||||
(target === PermissionTarget.User && userId > 0) ||
|
||||
(target === PermissionTarget.ServiceAccount && userId > 0) ||
|
||||
(PermissionTarget.BuiltInRole && OrgRole.hasOwnProperty(builtInRole));
|
||||
|
||||
return (
|
||||
@ -82,6 +90,10 @@ export const AddPermission = ({
|
||||
|
||||
{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.BuiltInRole && (
|
||||
|
@ -22,12 +22,13 @@ const INITIAL_DESCRIPTION: Description = {
|
||||
assignments: {
|
||||
teams: false,
|
||||
users: false,
|
||||
serviceAccounts: false,
|
||||
builtInRoles: false,
|
||||
},
|
||||
};
|
||||
|
||||
type ResourceId = string | number;
|
||||
type Type = 'users' | 'teams' | 'builtInRoles';
|
||||
type Type = 'users' | 'teams' | 'serviceAccounts' | 'builtInRoles';
|
||||
|
||||
export type Props = {
|
||||
title?: string;
|
||||
@ -68,6 +69,8 @@ export const Permissions = ({
|
||||
let promise: Promise<void> | null = null;
|
||||
if (state.target === PermissionTarget.User) {
|
||||
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) {
|
||||
promise = setTeamPermission(resource, resourceId, state.teamId!, state.permission);
|
||||
} else if (state.target === PermissionTarget.BuiltInRole) {
|
||||
@ -85,6 +88,8 @@ export const Permissions = ({
|
||||
promise = setUserPermission(resource, resourceId, item.userId, EMPTY_PERMISSION);
|
||||
} else if (item.teamId) {
|
||||
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) {
|
||||
promise = setBuiltInRolePermission(resource, resourceId, item.builtInRole, EMPTY_PERMISSION);
|
||||
}
|
||||
@ -100,6 +105,8 @@ export const Permissions = ({
|
||||
}
|
||||
if (item.userId) {
|
||||
onAdd({ permission, userId: item.userId, target: PermissionTarget.User });
|
||||
} else if (item.isServiceAccount) {
|
||||
onAdd({ permission, userId: item.userId, target: PermissionTarget.User });
|
||||
} else if (item.teamId) {
|
||||
onAdd({ permission, teamId: item.teamId, target: PermissionTarget.Team });
|
||||
} else if (item.builtInRole) {
|
||||
@ -118,7 +125,15 @@ export const Permissions = ({
|
||||
const users = useMemo(
|
||||
() =>
|
||||
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']
|
||||
),
|
||||
[items]
|
||||
@ -134,6 +149,7 @@ export const Permissions = ({
|
||||
|
||||
const titleRole = t('access-control.permissions.role', 'Role');
|
||||
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');
|
||||
|
||||
return (
|
||||
@ -202,6 +218,16 @@ export const Permissions = ({
|
||||
onRemove={onRemove}
|
||||
canSet={canSetPermissions}
|
||||
/>
|
||||
<PermissionList
|
||||
title={titleServiceAccount}
|
||||
items={serviceAccounts}
|
||||
compareKey={'userLogin'}
|
||||
permissionLevels={desc.permissions}
|
||||
onChange={onChange}
|
||||
onRemove={onRemove}
|
||||
canSet={canSetPermissions}
|
||||
/>
|
||||
|
||||
<PermissionList
|
||||
title={titleTeam}
|
||||
items={teams}
|
||||
|
@ -3,6 +3,7 @@ export type ResourcePermission = {
|
||||
resourceId: string;
|
||||
isManaged: boolean;
|
||||
isInherited: boolean;
|
||||
isServiceAccount: boolean;
|
||||
userId?: number;
|
||||
userLogin?: string;
|
||||
userAvatarUrl?: string;
|
||||
@ -26,6 +27,7 @@ export enum PermissionTarget {
|
||||
None = 'None',
|
||||
Team = 'Team',
|
||||
User = 'User',
|
||||
ServiceAccount = 'ServiceAccount',
|
||||
BuiltInRole = 'builtInRole',
|
||||
}
|
||||
export type Description = {
|
||||
@ -35,6 +37,7 @@ export type Description = {
|
||||
|
||||
export type Assignments = {
|
||||
users: boolean;
|
||||
serviceAccounts: boolean;
|
||||
teams: 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 {
|
||||
Team = 'Team',
|
||||
User = 'User',
|
||||
ServiceAccount = 'ServiceAccount',
|
||||
Viewer = 'Viewer',
|
||||
Editor = 'Editor',
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
"access-control": {
|
||||
"add-permission": {
|
||||
"role-label": "",
|
||||
"serviceaccount-label": "",
|
||||
"team-label": "",
|
||||
"title": "",
|
||||
"user-label": ""
|
||||
@ -18,6 +19,7 @@
|
||||
"no-permissions": "",
|
||||
"permissions-change-warning": "",
|
||||
"role": "",
|
||||
"serviceaccount": "",
|
||||
"team": "",
|
||||
"title": "",
|
||||
"user": ""
|
||||
|
@ -3,6 +3,7 @@
|
||||
"access-control": {
|
||||
"add-permission": {
|
||||
"role-label": "Role",
|
||||
"serviceaccount-label": "Service Account",
|
||||
"team-label": "Team",
|
||||
"title": "Add permission for",
|
||||
"user-label": "User"
|
||||
@ -18,6 +19,7 @@
|
||||
"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:",
|
||||
"role": "Role",
|
||||
"serviceaccount": "Service Account",
|
||||
"team": "Team",
|
||||
"title": "Permissions",
|
||||
"user": "User"
|
||||
|
@ -3,6 +3,7 @@
|
||||
"access-control": {
|
||||
"add-permission": {
|
||||
"role-label": "",
|
||||
"serviceaccount-label": "",
|
||||
"team-label": "",
|
||||
"title": "",
|
||||
"user-label": ""
|
||||
@ -18,6 +19,7 @@
|
||||
"no-permissions": "",
|
||||
"permissions-change-warning": "",
|
||||
"role": "",
|
||||
"serviceaccount": "",
|
||||
"team": "",
|
||||
"title": "",
|
||||
"user": ""
|
||||
|
@ -3,6 +3,7 @@
|
||||
"access-control": {
|
||||
"add-permission": {
|
||||
"role-label": "",
|
||||
"serviceaccount-label": "",
|
||||
"team-label": "",
|
||||
"title": "",
|
||||
"user-label": ""
|
||||
@ -18,6 +19,7 @@
|
||||
"no-permissions": "",
|
||||
"permissions-change-warning": "",
|
||||
"role": "",
|
||||
"serviceaccount": "",
|
||||
"team": "",
|
||||
"title": "",
|
||||
"user": ""
|
||||
|
@ -3,6 +3,7 @@
|
||||
"access-control": {
|
||||
"add-permission": {
|
||||
"role-label": "Ŗőľę",
|
||||
"serviceaccount-label": "Ŝęřvįčę Åččőūʼnŧ",
|
||||
"team-label": "Ŧęäm",
|
||||
"title": "Åđđ pęřmįşşįőʼn ƒőř",
|
||||
"user-label": "Ůşęř"
|
||||
@ -18,6 +19,7 @@
|
||||
"no-permissions": "Ŧĥęřę äřę ʼnő pęřmįşşįőʼnş",
|
||||
"permissions-change-warning": "Ŧĥįş ŵįľľ čĥäʼnģę pęřmįşşįőʼnş ƒőř ŧĥįş ƒőľđęř äʼnđ äľľ įŧş đęşčęʼnđäʼnŧş. Ĩʼn ŧőŧäľ, ŧĥįş ŵįľľ 䃃ęčŧ:",
|
||||
"role": "Ŗőľę",
|
||||
"serviceaccount": "Ŝęřvįčę Åččőūʼnŧ",
|
||||
"team": "Ŧęäm",
|
||||
"title": "Pęřmįşşįőʼnş",
|
||||
"user": "Ůşęř"
|
||||
|
@ -3,6 +3,7 @@
|
||||
"access-control": {
|
||||
"add-permission": {
|
||||
"role-label": "",
|
||||
"serviceaccount-label": "",
|
||||
"team-label": "",
|
||||
"title": "",
|
||||
"user-label": ""
|
||||
@ -18,6 +19,7 @@
|
||||
"no-permissions": "",
|
||||
"permissions-change-warning": "",
|
||||
"role": "",
|
||||
"serviceaccount": "",
|
||||
"team": "",
|
||||
"title": "",
|
||||
"user": ""
|
||||
|
Loading…
Reference in New Issue
Block a user