@ -1,19 +1,22 @@
import React , { useEffect } from 'react' ;
import React , { useEffect } from 'react' ;
import { css , cx } from '@emotion/css' ;
import { css , cx } from '@emotion/css' ;
import { connect , ConnectedProps } from 'react-redux' ;
import { connect , ConnectedProps } from 'react-redux' ;
import { Pagination , Tooltip , stylesFactory , LinkButton , Icon } from '@grafana/ui' ;
import { Pagination , Tooltip , LinkButton , Icon , RadioButtonGroup , useStyles2 } from '@grafana/ui' ;
import { AccessControlAction , StoreState , UserDTO } from '../../types ';
import { GrafanaTheme2 } from '@grafana/data ';
import Page from 'app/core/components/Page/Page' ;
import Page from 'app/core/components/Page/Page' ;
import { getNavModel } from '../../core/selectors/navModel' ;
import { fetchUsers , changeQuery , changePage } from './state/actions' ;
import { TagBadge } from 'app/core/components/TagFilter/TagBadge' ;
import { TagBadge } from 'app/core/components/TagFilter/TagBadge' ;
import { contextSrv } from 'app/core/core' ;
import { contextSrv } from 'app/core/core' ;
import { FilterInput } from 'app/core/components/FilterInput/FilterInput' ;
import { FilterInput } from 'app/core/components/FilterInput/FilterInput' ;
import { getNavModel } from '../../core/selectors/navModel' ;
import { AccessControlAction , StoreState , UserDTO } from '../../types' ;
import { fetchUsers , changeQuery , changePage , changeFilter } from './state/actions' ;
import PageLoader from '../../core/components/PageLoader/PageLoader' ;
const mapDispatchToProps = {
const mapDispatchToProps = {
fetchUsers ,
fetchUsers ,
changeQuery ,
changeQuery ,
changePage ,
changePage ,
changeFilter ,
} ;
} ;
const mapStateToProps = ( state : StoreState ) = > ( {
const mapStateToProps = ( state : StoreState ) = > ( {
@ -23,6 +26,8 @@ const mapStateToProps = (state: StoreState) => ({
showPaging : state.userListAdmin.showPaging ,
showPaging : state.userListAdmin.showPaging ,
totalPages : state.userListAdmin.totalPages ,
totalPages : state.userListAdmin.totalPages ,
page : state.userListAdmin.page ,
page : state.userListAdmin.page ,
filter : state.userListAdmin.filter ,
isLoading : state.userListAdmin.isLoading ,
} ) ;
} ) ;
const connector = connect ( mapStateToProps , mapDispatchToProps ) ;
const connector = connect ( mapStateToProps , mapDispatchToProps ) ;
@ -31,9 +36,21 @@ interface OwnProps {}
type Props = OwnProps & ConnectedProps < typeof connector > ;
type Props = OwnProps & ConnectedProps < typeof connector > ;
const UserListAdminPageUnConnected : React.FC < Props > = ( props ) = > {
const UserListAdminPageUnConnected : React.FC < Props > = ( {
const styles = getStyles ( ) ;
fetchUsers ,
const { fetchUsers , navModel , query , changeQuery , users , showPaging , totalPages , page , changePage } = props ;
navModel ,
query ,
changeQuery ,
users ,
showPaging ,
totalPages ,
page ,
changePage ,
changeFilter ,
filter ,
isLoading ,
} ) = > {
const styles = useStyles2 ( getStyles ) ;
useEffect ( ( ) = > {
useEffect ( ( ) = > {
fetchUsers ( ) ;
fetchUsers ( ) ;
@ -42,14 +59,22 @@ const UserListAdminPageUnConnected: React.FC<Props> = (props) => {
return (
return (
< Page navModel = { navModel } >
< Page navModel = { navModel } >
< Page.Contents >
< Page.Contents >
< >
< div className = "page-action-bar" >
< div className = "page-action-bar" >
< div className = "gf-form gf-form--grow" >
< div className = "gf-form gf-form--grow" >
< RadioButtonGroup
options = { [
{ label : 'All users' , value : 'all' } ,
{ label : 'Active last 30 days' , value : 'activeLast30Days' } ,
] }
onChange = { changeFilter }
value = { filter }
className = { styles . filter }
/ >
< FilterInput
< FilterInput
placeholder = "Search user by login, email, or name."
placeholder = "Search user by login, email, or name."
autoFocus = { true }
autoFocus = { true }
value = { query }
value = { query }
onChange = { ( value ) = > changeQuery ( value ) }
onChange = { changeQuery }
/ >
/ >
< / div >
< / div >
{ contextSrv . hasPermission ( AccessControlAction . UsersCreate ) && (
{ contextSrv . hasPermission ( AccessControlAction . UsersCreate ) && (
@ -58,6 +83,10 @@ const UserListAdminPageUnConnected: React.FC<Props> = (props) => {
< / LinkButton >
< / LinkButton >
) }
) }
< / div >
< / div >
{ isLoading ? (
< PageLoader / >
) : (
< >
< div className = { cx ( styles . table , 'admin-list-table' ) } >
< div className = { cx ( styles . table , 'admin-list-table' ) } >
< table className = "filter-table form-inline filter-table--hover" >
< table className = "filter-table form-inline filter-table--hover" >
< thead >
< thead >
@ -66,13 +95,13 @@ const UserListAdminPageUnConnected: React.FC<Props> = (props) => {
< th > Login < / th >
< th > Login < / th >
< th > Email < / th >
< th > Email < / th >
< th > Name < / th >
< th > Name < / th >
< th > Server admin < / th >
< th >
< th >
Seen & nbsp ;
Last active & nbsp ;
< Tooltip placement = "top" content = "Time since user was seen using Grafana" >
< Tooltip placement = "top" content = "Time since user was seen using Grafana" >
< Icon name = "question-circle" / >
< Icon name = "question-circle" / >
< / Tooltip >
< / Tooltip >
< / th >
< / th >
< th > < / th >
< th style = { { width : '1%' } } > < / th >
< th style = { { width : '1%' } } > < / th >
< / tr >
< / tr >
< / thead >
< / thead >
@ -81,6 +110,7 @@ const UserListAdminPageUnConnected: React.FC<Props> = (props) => {
< / div >
< / div >
{ showPaging && < Pagination numberOfPages = { totalPages } currentPage = { page } onNavigate = { changePage } / > }
{ showPaging && < Pagination numberOfPages = { totalPages } currentPage = { page } onNavigate = { changePage } / > }
< / >
< / >
) }
< / Page.Contents >
< / Page.Contents >
< / Page >
< / Page >
) ;
) ;
@ -92,35 +122,44 @@ const renderUser = (user: UserDTO) => {
return (
return (
< tr key = { user . id } >
< tr key = { user . id } >
< td className = "width-4 text-center link-td" >
< td className = "width-4 text-center link-td" >
< a href = { editUrl } >
< a href = { editUrl } aria - label = { ` Edit user's ${ user . name } details ` } >
< img className = "filter-table__avatar" src = { user . avatarUrl } / >
< img className = "filter-table__avatar" src = { user . avatarUrl } alt = { ` Avatar for user ${ user . name } ` } / >
< / a >
< / a >
< / td >
< / td >
< td className = "link-td max-width-10" >
< td className = "link-td max-width-10" >
< a className = "ellipsis" href = { editUrl } title = { user . login } >
< a className = "ellipsis" href = { editUrl } title = { user . login } aria - label = { ` Edit user's ${ user . name } details ` } >
{ user . login }
{ user . login }
< / a >
< / a >
< / td >
< / td >
< td className = "link-td max-width-10" >
< td className = "link-td max-width-10" >
< a className = "ellipsis" href = { editUrl } title = { user . email } >
< a className = "ellipsis" href = { editUrl } title = { user . email } aria - label = { ` Edit user's ${ user . name } details ` } >
{ user . email }
{ user . email }
< / a >
< / a >
< / td >
< / td >
< td className = "link-td max-width-10" >
< td className = "link-td max-width-10" >
< a className = "ellipsis" href = { editUrl } title = { user . name } >
< a className = "ellipsis" href = { editUrl } title = { user . name } aria - label = { ` Edit user's ${ user . name } details ` } >
{ user . name }
{ user . name }
< / a >
< / a >
< / td >
< / td >
< td className = "link-td" > { user . lastSeenAtAge && < a href = { editUrl } > { user . lastSeenAtAge } < / a > } < / td >
< td className = "link-td" >
< td className = "link-td" >
{ user . isAdmin && (
{ user . isAdmin && (
< a href = { editUrl } >
< a href = { editUrl } aria - label = { ` Edit user's ${ user . name } details ` } >
< Tooltip placement = "top" content = "Grafana Admin" >
< Tooltip placement = "top" content = "Grafana Admin" >
< Icon name = "shield" / >
< Icon name = "shield" / >
< / Tooltip >
< / Tooltip >
< / a >
< / a >
) }
) }
< / td >
< / td >
< td className = "link-td" >
{ user . lastSeenAtAge && (
< a
href = { editUrl }
aria - label = { ` Last seen at ${ user . lastSeenAtAge } . Follow to edit user's ${ user . name } details. ` }
>
{ user . lastSeenAtAge }
< / a >
) }
< / td >
< td className = "text-right" >
< td className = "text-right" >
{ Array . isArray ( user . authLabels ) && user . authLabels . length > 0 && (
{ Array . isArray ( user . authLabels ) && user . authLabels . length > 0 && (
< TagBadge label = { user . authLabels [ 0 ] } removeIcon = { false } count = { 0 } / >
< TagBadge label = { user . authLabels [ 0 ] } removeIcon = { false } count = { 0 } / >
@ -133,12 +172,15 @@ const renderUser = (user: UserDTO) => {
) ;
) ;
} ;
} ;
const getStyles = stylesFactory ( ( ) = > {
const getStyles = ( theme : GrafanaTheme2 ) = > {
return {
return {
table : css `
table : css `
margin - top : 28px ;
margin - top : $ { theme . spacing ( 3 ) } ;
` ,
filter : css `
margin - right : $ { theme . spacing ( 1 ) } ;
` ,
` ,
} ;
} ;
} ) ;
} ;
export default connector ( UserListAdminPageUnConnected ) ;
export default connector ( UserListAdminPageUnConnected ) ;