1 |
- {"version":3,"file":"UsersListPage.9b63bff5372bebea07b7.js","mappings":"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","sources":["webpack://grafana/./public/app/features/invites/InviteeRow.tsx","webpack://grafana/./public/app/features/invites/InviteesTable.tsx","webpack://grafana/./public/app/features/invites/state/selectors.ts","webpack://grafana/./public/app/features/users/state/selectors.ts","webpack://grafana/./public/app/features/users/UsersActionBar.tsx","webpack://grafana/./public/app/features/users/state/actions.ts","webpack://grafana/./public/app/features/users/UsersListPage.tsx","webpack://grafana/./public/app/features/users/UsersTable.tsx"],"sourcesContent":["import React, { PureComponent } from 'react';\nimport { connect, ConnectedProps } from 'react-redux';\n\nimport { Button, ClipboardButton } from '@grafana/ui';\nimport { Invitee } from 'app/types';\n\nimport { revokeInvite } from './state/actions';\n\nconst mapDispatchToProps = {\n revokeInvite,\n};\n\nconst connector = connect(null, mapDispatchToProps);\n\ninterface OwnProps {\n invitee: Invitee;\n}\n\nexport type Props = OwnProps & ConnectedProps<typeof connector>;\n\nclass InviteeRow extends PureComponent<Props> {\n render() {\n const { invitee, revokeInvite } = this.props;\n return (\n <tr>\n <td>{invitee.email}</td>\n <td>{invitee.name}</td>\n <td className=\"text-right\">\n <ClipboardButton variant=\"secondary\" size=\"sm\" getText={() => invitee.url}>\n Copy Invite\n </ClipboardButton>\n \n </td>\n <td>\n <Button variant=\"destructive\" size=\"sm\" icon=\"times\" onClick={() => revokeInvite(invitee.code)} />\n </td>\n </tr>\n );\n }\n}\n\nexport default connector(InviteeRow);\n","import React, { PureComponent } from 'react';\n\nimport { Invitee } from 'app/types';\n\nimport InviteeRow from './InviteeRow';\n\nexport interface Props {\n invitees: Invitee[];\n}\n\nexport default class InviteesTable extends PureComponent<Props> {\n render() {\n const { invitees } = this.props;\n\n return (\n <table className=\"filter-table form-inline\">\n <thead>\n <tr>\n <th>Email</th>\n <th>Name</th>\n <th />\n <th style={{ width: '34px' }} />\n </tr>\n </thead>\n <tbody>\n {invitees.map((invitee, index) => {\n return <InviteeRow key={`${invitee.id}-${index}`} invitee={invitee} />;\n })}\n </tbody>\n </table>\n );\n }\n}\n","import { createSelector } from '@reduxjs/toolkit';\n\nimport { selectors } from './reducers';\n\nexport const { selectAll, selectById, selectTotal } = selectors;\n\nconst selectQuery = (_: any, query: string) => query;\nexport const selectInvitesMatchingQuery = createSelector([selectAll, selectQuery], (invites, searchQuery) => {\n const regex = new RegExp(searchQuery, 'i');\n const matches = invites.filter((invite) => regex.test(invite.name) || regex.test(invite.email));\n return matches;\n});\n","import { UsersState } from 'app/types';\n\nexport const getUsers = (state: UsersState) => {\n const regex = new RegExp(state.searchQuery, 'i');\n\n return state.users.filter((user) => {\n return regex.test(user.login) || regex.test(user.email) || regex.test(user.name);\n });\n};\n\nexport const getUsersSearchQuery = (state: UsersState) => state.searchQuery;\nexport const getUsersSearchPage = (state: UsersState) => state.searchPage;\n","import React, { PureComponent } from 'react';\nimport { connect } from 'react-redux';\n\nimport { RadioButtonGroup, LinkButton, FilterInput } from '@grafana/ui';\nimport { contextSrv } from 'app/core/core';\nimport { AccessControlAction } from 'app/types';\n\nimport { selectTotal } from '../invites/state/selectors';\n\nimport { setUsersSearchQuery } from './state/reducers';\nimport { getUsersSearchQuery } from './state/selectors';\n\nexport interface Props {\n searchQuery: string;\n setUsersSearchQuery: typeof setUsersSearchQuery;\n onShowInvites: () => void;\n pendingInvitesCount: number;\n canInvite: boolean;\n showInvites: boolean;\n externalUserMngLinkUrl: string;\n externalUserMngLinkName: string;\n}\n\nexport class UsersActionBar extends PureComponent<Props> {\n render() {\n const {\n canInvite,\n externalUserMngLinkName,\n externalUserMngLinkUrl,\n searchQuery,\n pendingInvitesCount,\n setUsersSearchQuery,\n onShowInvites,\n showInvites,\n } = this.props;\n const options = [\n { label: 'Users', value: 'users' },\n { label: `Pending Invites (${pendingInvitesCount})`, value: 'invites' },\n ];\n const canAddToOrg = contextSrv.hasAccess(AccessControlAction.UsersCreate, canInvite);\n\n return (\n <div className=\"page-action-bar\" data-testid=\"users-action-bar\">\n <div className=\"gf-form gf-form--grow\">\n <FilterInput\n value={searchQuery}\n onChange={setUsersSearchQuery}\n placeholder=\"Search user by login, email or name\"\n />\n </div>\n {pendingInvitesCount > 0 && (\n <div style={{ marginLeft: '1rem' }}>\n <RadioButtonGroup value={showInvites ? 'invites' : 'users'} options={options} onChange={onShowInvites} />\n </div>\n )}\n {canAddToOrg && <LinkButton href=\"org/users/invite\">Invite</LinkButton>}\n {externalUserMngLinkUrl && (\n <LinkButton href={externalUserMngLinkUrl} target=\"_blank\" rel=\"noopener\">\n {externalUserMngLinkName}\n </LinkButton>\n )}\n </div>\n );\n }\n}\n\nfunction mapStateToProps(state: any) {\n return {\n searchQuery: getUsersSearchQuery(state.users),\n pendingInvitesCount: selectTotal(state.invites),\n externalUserMngLinkName: state.users.externalUserMngLinkName,\n externalUserMngLinkUrl: state.users.externalUserMngLinkUrl,\n canInvite: state.users.canInvite,\n };\n}\n\nconst mapDispatchToProps = {\n setUsersSearchQuery,\n};\n\nexport default connect(mapStateToProps, mapDispatchToProps)(UsersActionBar);\n","import { getBackendSrv } from '@grafana/runtime';\nimport { accessControlQueryParam } from 'app/core/utils/accessControl';\nimport { OrgUser } from 'app/types';\n\nimport { ThunkResult } from '../../../types';\n\nimport { usersLoaded } from './reducers';\n\nexport function loadUsers(): ThunkResult<void> {\n return async (dispatch) => {\n const users = await getBackendSrv().get('/api/org/users', accessControlQueryParam());\n dispatch(usersLoaded(users));\n };\n}\n\nexport function updateUser(user: OrgUser): ThunkResult<void> {\n return async (dispatch) => {\n await getBackendSrv().patch(`/api/org/users/${user.userId}`, { role: user.role });\n dispatch(loadUsers());\n };\n}\n\nexport function removeUser(userId: number): ThunkResult<void> {\n return async (dispatch) => {\n await getBackendSrv().delete(`/api/org/users/${userId}`);\n dispatch(loadUsers());\n };\n}\n","import React, { PureComponent } from 'react';\nimport { connect, ConnectedProps } from 'react-redux';\n\nimport { renderMarkdown } from '@grafana/data';\nimport { HorizontalGroup, Pagination, VerticalGroup } from '@grafana/ui';\nimport Page from 'app/core/components/Page/Page';\nimport { getNavModel } from 'app/core/selectors/navModel';\nimport { contextSrv } from 'app/core/services/context_srv';\nimport { OrgUser, OrgRole, StoreState } from 'app/types';\n\nimport InviteesTable from '../invites/InviteesTable';\nimport { fetchInvitees } from '../invites/state/actions';\nimport { selectInvitesMatchingQuery } from '../invites/state/selectors';\n\nimport UsersActionBar from './UsersActionBar';\nimport UsersTable from './UsersTable';\nimport { loadUsers, removeUser, updateUser } from './state/actions';\nimport { setUsersSearchQuery, setUsersSearchPage } from './state/reducers';\nimport { getUsers, getUsersSearchQuery, getUsersSearchPage } from './state/selectors';\n\nfunction mapStateToProps(state: StoreState) {\n const searchQuery = getUsersSearchQuery(state.users);\n return {\n navModel: getNavModel(state.navIndex, 'users'),\n users: getUsers(state.users),\n searchQuery: getUsersSearchQuery(state.users),\n searchPage: getUsersSearchPage(state.users),\n invitees: selectInvitesMatchingQuery(state.invites, searchQuery),\n externalUserMngInfo: state.users.externalUserMngInfo,\n hasFetched: state.users.hasFetched,\n };\n}\n\nconst mapDispatchToProps = {\n loadUsers,\n fetchInvitees,\n setUsersSearchQuery,\n setUsersSearchPage,\n updateUser,\n removeUser,\n};\n\nconst connector = connect(mapStateToProps, mapDispatchToProps);\n\nexport type Props = ConnectedProps<typeof connector>;\n\nexport interface State {\n showInvites: boolean;\n}\n\nconst pageLimit = 30;\n\nexport class UsersListPage extends PureComponent<Props, State> {\n declare externalUserMngInfoHtml: string;\n\n constructor(props: Props) {\n super(props);\n\n if (this.props.externalUserMngInfo) {\n this.externalUserMngInfoHtml = renderMarkdown(this.props.externalUserMngInfo);\n }\n\n this.state = {\n showInvites: false,\n };\n }\n\n componentDidMount() {\n this.fetchUsers();\n this.fetchInvitees();\n }\n\n async fetchUsers() {\n return await this.props.loadUsers();\n }\n\n async fetchInvitees() {\n return await this.props.fetchInvitees();\n }\n\n onRoleChange = (role: OrgRole, user: OrgUser) => {\n const updatedUser = { ...user, role: role };\n\n this.props.updateUser(updatedUser);\n };\n\n onShowInvites = () => {\n this.setState((prevState) => ({\n showInvites: !prevState.showInvites,\n }));\n };\n\n getPaginatedUsers = (users: OrgUser[]) => {\n const offset = (this.props.searchPage - 1) * pageLimit;\n return users.slice(offset, offset + pageLimit);\n };\n\n renderTable() {\n const { invitees, users, setUsersSearchPage } = this.props;\n const paginatedUsers = this.getPaginatedUsers(users);\n const totalPages = Math.ceil(users.length / pageLimit);\n\n if (this.state.showInvites) {\n return <InviteesTable invitees={invitees} />;\n } else {\n return (\n <VerticalGroup spacing=\"md\">\n <UsersTable\n users={paginatedUsers}\n orgId={contextSrv.user.orgId}\n onRoleChange={(role, user) => this.onRoleChange(role, user)}\n onRemoveUser={(user) => this.props.removeUser(user.userId)}\n />\n <HorizontalGroup justify=\"flex-end\">\n <Pagination\n onNavigate={setUsersSearchPage}\n currentPage={this.props.searchPage}\n numberOfPages={totalPages}\n hideWhenSinglePage={true}\n />\n </HorizontalGroup>\n </VerticalGroup>\n );\n }\n }\n\n render() {\n const { navModel, hasFetched } = this.props;\n const externalUserMngInfoHtml = this.externalUserMngInfoHtml;\n\n return (\n <Page navModel={navModel}>\n <Page.Contents isLoading={!hasFetched}>\n <>\n <UsersActionBar onShowInvites={this.onShowInvites} showInvites={this.state.showInvites} />\n {externalUserMngInfoHtml && (\n <div className=\"grafana-info-box\" dangerouslySetInnerHTML={{ __html: externalUserMngInfoHtml }} />\n )}\n {hasFetched && this.renderTable()}\n </>\n </Page.Contents>\n </Page>\n );\n }\n}\n\nexport default connector(UsersListPage);\n","import React, { FC, useEffect, useState } from 'react';\n\nimport { OrgRole } from '@grafana/data';\nimport { Button, ConfirmModal } from '@grafana/ui';\nimport { UserRolePicker } from 'app/core/components/RolePicker/UserRolePicker';\nimport { fetchBuiltinRoles, fetchRoleOptions } from 'app/core/components/RolePicker/api';\nimport { contextSrv } from 'app/core/core';\nimport { AccessControlAction, OrgUser, Role } from 'app/types';\n\nimport { OrgRolePicker } from '../admin/OrgRolePicker';\n\nexport interface Props {\n users: OrgUser[];\n orgId?: number;\n onRoleChange: (role: OrgRole, user: OrgUser) => void;\n onRemoveUser: (user: OrgUser) => void;\n}\n\nconst UsersTable: FC<Props> = (props) => {\n const { users, orgId, onRoleChange, onRemoveUser } = props;\n const [userToRemove, setUserToRemove] = useState<OrgUser | null>(null);\n const [roleOptions, setRoleOptions] = useState<Role[]>([]);\n const [builtinRoles, setBuiltinRoles] = useState<{ [key: string]: Role[] }>({});\n\n useEffect(() => {\n async function fetchOptions() {\n try {\n if (contextSrv.hasPermission(AccessControlAction.ActionRolesList)) {\n let options = await fetchRoleOptions(orgId);\n setRoleOptions(options);\n }\n\n if (\n contextSrv.accessControlBuiltInRoleAssignmentEnabled() &&\n contextSrv.hasPermission(AccessControlAction.ActionBuiltinRolesList)\n ) {\n const builtInRoles = await fetchBuiltinRoles(orgId);\n setBuiltinRoles(builtInRoles);\n }\n } catch (e) {\n console.error('Error loading options');\n }\n }\n if (contextSrv.licensedAccessControlEnabled()) {\n fetchOptions();\n }\n }, [orgId]);\n\n return (\n <>\n <table className=\"filter-table form-inline\">\n <thead>\n <tr>\n <th />\n <th>Login</th>\n <th>Email</th>\n <th>Name</th>\n <th>Seen</th>\n <th>Role</th>\n <th style={{ width: '34px' }} />\n </tr>\n </thead>\n <tbody>\n {users.map((user, index) => {\n return (\n <tr key={`${user.userId}-${index}`}>\n <td className=\"width-2 text-center\">\n <img className=\"filter-table__avatar\" src={user.avatarUrl} alt=\"User avatar\" />\n </td>\n <td className=\"max-width-6\">\n <span className=\"ellipsis\" title={user.login}>\n {user.login}\n </span>\n </td>\n\n <td className=\"max-width-5\">\n <span className=\"ellipsis\" title={user.email}>\n {user.email}\n </span>\n </td>\n <td className=\"max-width-5\">\n <span className=\"ellipsis\" title={user.name}>\n {user.name}\n </span>\n </td>\n <td className=\"width-1\">{user.lastSeenAtAge}</td>\n\n <td className=\"width-8\">\n {contextSrv.licensedAccessControlEnabled() ? (\n <UserRolePicker\n userId={user.userId}\n orgId={orgId}\n builtInRole={user.role}\n onBuiltinRoleChange={(newRole) => onRoleChange(newRole, user)}\n roleOptions={roleOptions}\n builtInRoles={builtinRoles}\n disabled={!contextSrv.hasPermissionInMetadata(AccessControlAction.OrgUsersWrite, user)}\n />\n ) : (\n <OrgRolePicker\n aria-label=\"Role\"\n value={user.role}\n disabled={!contextSrv.hasPermissionInMetadata(AccessControlAction.OrgUsersWrite, user)}\n onChange={(newRole) => onRoleChange(newRole, user)}\n />\n )}\n </td>\n\n {contextSrv.hasPermissionInMetadata(AccessControlAction.OrgUsersRemove, user) && (\n <td>\n <Button\n size=\"sm\"\n variant=\"destructive\"\n onClick={() => {\n setUserToRemove(user);\n }}\n icon=\"times\"\n aria-label=\"Delete user\"\n />\n </td>\n )}\n </tr>\n );\n })}\n </tbody>\n </table>\n {Boolean(userToRemove) && (\n <ConfirmModal\n body={`Are you sure you want to delete user ${userToRemove?.login}?`}\n confirmText=\"Delete\"\n title=\"Delete\"\n onDismiss={() => {\n setUserToRemove(null);\n }}\n isOpen={true}\n onConfirm={() => {\n if (!userToRemove) {\n return;\n }\n onRemoveUser(userToRemove);\n setUserToRemove(null);\n }}\n />\n )}\n </>\n );\n};\n\nexport default UsersTable;\n"],"names":["mapDispatchToProps","revokeInvite","connector","connect","InviteeRow","PureComponent","render","invitee","this","props","email","name","className","ClipboardButton","variant","size","getText","url","Button","icon","onClick","code","InviteesTable","invitees","style","width","map","index","id","selectAll","selectById","selectTotal","selectors","selectInvitesMatchingQuery","createSelector","_","query","invites","searchQuery","regex","RegExp","filter","invite","test","getUsers","state","users","user","login","getUsersSearchQuery","getUsersSearchPage","searchPage","UsersActionBar","canInvite","externalUserMngLinkName","externalUserMngLinkUrl","pendingInvitesCount","setUsersSearchQuery","onShowInvites","showInvites","options","label","value","canAddToOrg","contextSrv","AccessControlAction","FilterInput","onChange","placeholder","marginLeft","RadioButtonGroup","LinkButton","href","target","rel","loadUsers","async","getBackendSrv","get","accessControlQueryParam","dispatch","usersLoaded","fetchInvitees","setUsersSearchPage","updateUser","patch","userId","role","removeUser","delete","navModel","getNavModel","navIndex","externalUserMngInfo","hasFetched","UsersListPage","constructor","super","updatedUser","setState","prevState","offset","slice","externalUserMngInfoHtml","renderMarkdown","componentDidMount","fetchUsers","renderTable","paginatedUsers","getPaginatedUsers","totalPages","Math","ceil","length","VerticalGroup","spacing","UsersTable","orgId","onRoleChange","onRemoveUser","HorizontalGroup","justify","Pagination","onNavigate","currentPage","numberOfPages","hideWhenSinglePage","Page","isLoading","dangerouslySetInnerHTML","__html","userToRemove","setUserToRemove","useState","roleOptions","setRoleOptions","builtinRoles","setBuiltinRoles","useEffect","fetchRoleOptions","builtInRoles","fetchBuiltinRoles","e","console","error","fetchOptions","src","avatarUrl","alt","title","lastSeenAtAge","builtInRole","onBuiltinRoleChange","newRole","disabled","Boolean","ConfirmModal","body","confirmText","onDismiss","isOpen","onConfirm"],"sourceRoot":""}
|