Submit
Path:
~
/
/
usr
/
share
/
grafana
/
public
/
app
/
features
/
admin
/
Users
/
File Content:
OrgUsersTable.tsx
import React, { useEffect, useMemo, useState } from 'react'; import { OrgRole } from '@grafana/data'; import { selectors as e2eSelectors } from '@grafana/e2e-selectors'; import { Avatar, Box, Button, CellProps, Column, ConfirmModal, FetchDataFunc, Icon, InteractiveTable, Pagination, Stack, Tag, Text, 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'; import { contextSrv } from 'app/core/core'; import { AccessControlAction, OrgUser, Role } from 'app/types'; import { OrgRolePicker } from '../OrgRolePicker'; type Cell<T extends keyof OrgUser = keyof OrgUser> = CellProps<OrgUser, OrgUser[T]>; 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.`; const getBasicRoleDisabled = (user: OrgUser) => { const isUserSynced = user?.isExternallySynced; return !contextSrv.hasPermissionInMetadata(AccessControlAction.OrgUsersWrite, user) || isUserSynced; }; const selectors = e2eSelectors.pages.UserListPage.UsersListPage; export interface Props { users: OrgUser[]; orgId?: number; onRoleChange: (role: OrgRole, user: OrgUser) => void; onRemoveUser: (user: OrgUser) => void; fetchData?: FetchDataFunc<OrgUser>; changePage: (page: number) => void; page: number; totalPages: number; rolesLoading?: boolean; } export const OrgUsersTable = ({ users, orgId, onRoleChange, onRemoveUser, fetchData, changePage, page, totalPages, rolesLoading, }: Props) => { const [userToRemove, setUserToRemove] = useState<OrgUser | null>(null); const [roleOptions, setRoleOptions] = useState<Role[]>([]); useEffect(() => { async function fetchOptions() { try { if (contextSrv.hasPermission(AccessControlAction.ActionRolesList)) { let options = await fetchRoleOptions(orgId); setRoleOptions(options); } } catch (e) { console.error('Error loading options'); } } if (contextSrv.licensedAccessControlEnabled()) { fetchOptions(); } }, [orgId]); const columns: Array<Column<OrgUser>> = useMemo( () => [ { id: 'avatarUrl', header: '', cell: ({ cell: { value } }: Cell<'avatarUrl'>) => value && <Avatar src={value} alt="User avatar" />, }, { id: 'login', header: 'Login', cell: ({ cell: { value } }: Cell<'login'>) => <div>{value}</div>, sortType: 'string', }, { id: 'email', header: 'Email', cell: ({ cell: { value } }: Cell<'email'>) => value, sortType: 'string', }, { id: 'name', header: 'Name', cell: ({ cell: { value } }: Cell<'name'>) => value, sortType: 'string', }, { id: 'lastSeenAtAge', header: 'Last active', cell: ({ cell: { value } }: Cell<'lastSeenAtAge'>) => { return <>{value && <>{value === '10 years' ? <Text color={'disabled'}>Never</Text> : value}</>}</>; }, sortType: (a, b) => new Date(a.original.lastSeenAt).getTime() - new Date(b.original.lastSeenAt).getTime(), }, { id: 'role', header: 'Role', cell: ({ cell: { value }, row: { original } }: Cell<'role'>) => { const basicRoleDisabled = getBasicRoleDisabled(original); return contextSrv.licensedAccessControlEnabled() ? ( <UserRolePicker userId={original.userId} roles={original.roles || []} isLoading={rolesLoading} orgId={orgId} roleOptions={roleOptions} basicRole={value} onBasicRoleChange={(newRole) => onRoleChange(newRole, original)} basicRoleDisabled={basicRoleDisabled} basicRoleDisabledMessage={disabledRoleMessage} width={40} /> ) : ( <OrgRolePicker aria-label="Role" value={value} disabled={basicRoleDisabled} onChange={(newRole) => onRoleChange(newRole, original)} /> ); }, }, { id: 'info', header: '', cell: ({ row: { original } }: Cell) => { const basicRoleDisabled = getBasicRoleDisabled(original); return ( basicRoleDisabled && ( <Box display={'flex'} alignItems={'center'} marginLeft={1}> <Tooltip interactive={true} content={ <div> This user's role is not editable because it is synchronized from your auth provider. Refer to the <a href={ 'https://grafana.com/docs/grafana/latest/administration/user-management/manage-org-users/#change-a-users-organization-permissions' } rel="noreferrer" target="_blank" > Grafana authentication docs </a> for details. </div> } > <Icon name="question-circle" /> </Tooltip> </Box> ) ); }, }, { id: 'authLabels', header: 'Origin', cell: ({ cell: { value } }: Cell<'authLabels'>) => ( <>{Array.isArray(value) && value.length > 0 && <TagBadge label={value[0]} removeIcon={false} count={0} />}</> ), }, { id: 'isDisabled', header: '', cell: ({ cell: { value } }: Cell<'isDisabled'>) => <>{value && <Tag colorIndex={9} name={'Disabled'} />}</>, }, { id: 'delete', header: '', cell: ({ row: { original } }: Cell) => { return ( contextSrv.hasPermissionInMetadata(AccessControlAction.OrgUsersRemove, original) && ( <Button size="sm" variant="destructive" onClick={() => { setUserToRemove(original); }} icon="times" aria-label={`Delete user ${original.name}`} /> ) ); }, }, ], [rolesLoading, orgId, roleOptions, onRoleChange] ); return ( <Stack direction={'column'} gap={2} data-testid={selectors.container}> <InteractiveTable columns={columns} data={users} getRowId={(user) => String(user.userId)} fetchData={fetchData} /> <Stack justifyContent="flex-end"> <Pagination onNavigate={changePage} currentPage={page} numberOfPages={totalPages} hideWhenSinglePage={true} /> </Stack> {Boolean(userToRemove) && ( <ConfirmModal body={`Are you sure you want to delete user ${userToRemove?.login}?`} confirmText="Delete" title="Delete" onDismiss={() => { setUserToRemove(null); }} isOpen={true} onConfirm={() => { if (!userToRemove) { return; } onRemoveUser(userToRemove); setUserToRemove(null); }} /> )} </Stack> ); };
Edit
Rename
Chmod
Delete
FILE
FOLDER
INFO
Name
Size
Permission
Action
AnonUsersTable.tsx
3480 bytes
0644
OrgUnits.tsx
1061 bytes
0644
OrgUsersTable.test.tsx
1988 bytes
0644
OrgUsersTable.tsx
7570 bytes
0644
UsersTable.tsx
5079 bytes
0644
N4ST4R_ID | Naxtarrr