diff --git a/public/app/core/components/RolePicker/BuiltinRoleSelector.tsx b/public/app/core/components/RolePicker/BuiltinRoleSelector.tsx new file mode 100644 index 00000000000..8439d994ef3 --- /dev/null +++ b/public/app/core/components/RolePicker/BuiltinRoleSelector.tsx @@ -0,0 +1,46 @@ +import React from 'react'; + +import { SelectableValue } from '@grafana/data'; +import { Icon, RadioButtonGroup, Tooltip, useStyles2, useTheme2 } from '@grafana/ui'; +import { OrgRole } from 'app/types'; + +import { getStyles } from './styles'; + +const BasicRoles = Object.values(OrgRole).filter((r) => r !== OrgRole.None); +const BasicRoleOption: Array> = BasicRoles.map((r) => ({ + label: r, + value: r, +})); + +interface Props { + value?: OrgRole; + onChange: (value: OrgRole) => void; + disabled?: boolean; + disabledMesssage?: string; +} + +export const BuiltinRoleSelector = ({ value, onChange, disabled, disabledMesssage }: Props) => { + const styles = useStyles2(getStyles); + const theme = useTheme2(); + + return ( + <> +
+ Basic roles + {disabled && disabledMesssage && ( + {disabledMesssage}
}> + + + )} + + + + ); +}; diff --git a/public/app/core/components/RolePicker/RolePicker.tsx b/public/app/core/components/RolePicker/RolePicker.tsx index f640b9965b1..21c42c51299 100644 --- a/public/app/core/components/RolePicker/RolePicker.tsx +++ b/public/app/core/components/RolePicker/RolePicker.tsx @@ -14,6 +14,7 @@ export interface Props { isLoading?: boolean; disabled?: boolean; basicRoleDisabled?: boolean; + basicRoleDisabledMessage?: string; showBasicRole?: boolean; onRolesChange: (newRoles: Role[]) => void; onBasicRoleChange?: (newRole: OrgRole) => void; @@ -32,6 +33,7 @@ export const RolePicker = ({ disabled, isLoading, basicRoleDisabled, + basicRoleDisabledMessage, showBasicRole, onRolesChange, onBasicRoleChange, @@ -184,6 +186,7 @@ export const RolePicker = ({ onUpdate={onUpdate} showGroups={query.length === 0 || query.trim() === ''} basicRoleDisabled={basicRoleDisabled} + disabledMessage={basicRoleDisabledMessage} showBasicRole={showBasicRole} updateDisabled={basicRoleDisabled && !canUpdateRoles} apply={apply} diff --git a/public/app/core/components/RolePicker/RolePickerMenu.tsx b/public/app/core/components/RolePicker/RolePickerMenu.tsx index ee73cdb59e3..08573939df4 100644 --- a/public/app/core/components/RolePicker/RolePickerMenu.tsx +++ b/public/app/core/components/RolePicker/RolePickerMenu.tsx @@ -1,11 +1,11 @@ import { css, cx } from '@emotion/css'; import React, { useEffect, useRef, useState } from 'react'; -import { SelectableValue } from '@grafana/data'; -import { Button, CustomScrollbar, HorizontalGroup, RadioButtonGroup, useStyles2, useTheme2 } from '@grafana/ui'; +import { Button, CustomScrollbar, HorizontalGroup, useStyles2, useTheme2 } from '@grafana/ui'; import { getSelectStyles } from '@grafana/ui/src/components/Select/getSelectStyles'; import { OrgRole, Role } from 'app/types'; +import { BuiltinRoleSelector } from './BuiltinRoleSelector'; import { RoleMenuGroupsSection } from './RoleMenuGroupsSection'; import { MENU_MAX_HEIGHT } from './constants'; import { getStyles } from './styles'; @@ -29,12 +29,6 @@ interface RolesCollectionEntry { roles: Role[]; } -const BasicRoles = Object.values(OrgRole).filter((r) => r !== OrgRole.None); -const BasicRoleOption: Array> = BasicRoles.map((r) => ({ - label: r, - value: r, -})); - const fixedRoleGroupNames: Record = { ldap: 'LDAP', current: 'Current org', @@ -46,6 +40,7 @@ interface RolePickerMenuProps { appliedRoles: Role[]; showGroups?: boolean; basicRoleDisabled?: boolean; + disabledMessage?: string; showBasicRole?: boolean; onSelect: (roles: Role[]) => void; onBasicRoleSelect?: (role: OrgRole) => void; @@ -61,6 +56,7 @@ export const RolePickerMenu = ({ appliedRoles, showGroups, basicRoleDisabled, + disabledMessage, showBasicRole, onSelect, onBasicRoleSelect, @@ -206,14 +202,11 @@ export const RolePickerMenu = ({ {showBasicRole && (
-
Basic roles
-
)} diff --git a/public/app/core/components/RolePicker/UserRolePicker.tsx b/public/app/core/components/RolePicker/UserRolePicker.tsx index 573b43ca54c..73bac42b7d8 100644 --- a/public/app/core/components/RolePicker/UserRolePicker.tsx +++ b/public/app/core/components/RolePicker/UserRolePicker.tsx @@ -15,6 +15,7 @@ export interface Props { roleOptions: Role[]; disabled?: boolean; basicRoleDisabled?: boolean; + basicRoleDisabledMessage?: string; /** * Set whether the component should send a request with the new roles to the * backend in UserRolePicker.onRolesChange (apply=false), or call {@link onApplyRoles} @@ -40,6 +41,7 @@ export const UserRolePicker = ({ roleOptions, disabled, basicRoleDisabled, + basicRoleDisabledMessage, apply = false, onApplyRoles, pendingRoles, @@ -91,6 +93,7 @@ export const UserRolePicker = ({ isLoading={loading} disabled={disabled} basicRoleDisabled={basicRoleDisabled} + basicRoleDisabledMessage={basicRoleDisabledMessage} showBasicRole apply={apply} canUpdateRoles={canUpdateRoles} diff --git a/public/app/features/admin/UserOrgs.tsx b/public/app/features/admin/UserOrgs.tsx index 01d334a0b3b..76f348d50a0 100644 --- a/public/app/features/admin/UserOrgs.tsx +++ b/public/app/features/admin/UserOrgs.tsx @@ -209,6 +209,8 @@ class UnThemedOrgRow extends PureComponent { roleOptions={this.state.roleOptions} onBasicRoleChange={this.onBasicRoleChange} basicRoleDisabled={rolePickerDisabled} + basicRoleDisabledMessage="This user's role is not editable because it is synchronized from your auth provider. + Refer to the Grafana authentication docs for details." /> {isExternalUser && } diff --git a/public/app/features/users/UsersTable.tsx b/public/app/features/users/UsersTable.tsx index 438041cabe9..b1bd794bc26 100644 --- a/public/app/features/users/UsersTable.tsx +++ b/public/app/features/users/UsersTable.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; import { OrgRole } from '@grafana/data'; -import { Button, ConfirmModal } from '@grafana/ui'; +import { Button, ConfirmModal, Icon, Tooltip } from '@grafana/ui'; import { UserRolePicker } from 'app/core/components/RolePicker/UserRolePicker'; import { fetchRoleOptions } from 'app/core/components/RolePicker/api'; import { TagBadge } from 'app/core/components/TagFilter/TagBadge'; @@ -11,6 +11,9 @@ import { AccessControlAction, OrgUser, Role } from 'app/types'; import { OrgRolePicker } from '../admin/OrgRolePicker'; +const disabledRoleMessage = `This user's role is not editable because it is synchronized from your auth provider. + Refer to the Grafana authentication docs for details.`; + export interface Props { users: OrgUser[]; orgId?: number; @@ -49,6 +52,7 @@ export const UsersTable = ({ users, orgId, onRoleChange, onRemoveUser }: Props) Name Seen Role + Origin @@ -97,6 +101,7 @@ export const UsersTable = ({ users, orgId, onRoleChange, onRemoveUser }: Props) basicRole={user.role} onBasicRoleChange={(newRole) => onRoleChange(newRole, user)} basicRoleDisabled={basicRoleDisabled} + basicRoleDisabledMessage={disabledRoleMessage} /> ) : ( + + {basicRoleDisabled && ( +
+ + + +
+ )} + + {user.isDisabled && Disabled}