diff options
author | Harrison Healey <harrisonmhealey@gmail.com> | 2017-03-30 12:46:47 -0400 |
---|---|---|
committer | Corey Hulen <corey@hulen.com> | 2017-03-30 09:46:47 -0700 |
commit | 689cac535e45c47a4f603b236dc129dd456efcc9 (patch) | |
tree | 767ef80b310d6d073840bd5216da38c439f6e193 /webapp/components/admin_console/system_users | |
parent | 9a9729f22fea7275637eafb4046900c9f372ec56 (diff) | |
download | chat-689cac535e45c47a4f603b236dc129dd456efcc9.tar.gz chat-689cac535e45c47a4f603b236dc129dd456efcc9.tar.bz2 chat-689cac535e45c47a4f603b236dc129dd456efcc9.zip |
PLT-2713/PLT-6028 Added System Users list to System Console (#5882)
* PLT-2713 Added ability for admins to list users not in any team
* Updated style of unit test
* Split SearchableUserList to give better control over its properties
* Added users without any teams to the user store
* Added ManageUsers page
* Renamed ManageUsers to SystemUsers
* Added ability to search by user id in SystemUsers page
* Added SystemUsersDropdown
* Removed unnecessary injectIntl
* Created TeamUtils
* Reduced scope of system console heading CSS
* Added team filter to TeamAnalytics page
* Updated admin console sidebar
* Removed unnecessary TODO
* Removed unused reference to deleted modal
* Fixed system console sidebar not scrolling on first load
* Fixed TeamAnalytics page not rendering on first load
* Fixed chart.js throwing an error when switching between teams
* Changed TeamAnalytics header to show the team's display name
* Fixed appearance of TeamAnalytics and SystemUsers on small screen widths
* Fixed placement of 'No users found' message
* Fixed teams not appearing in SystemUsers on first load
* Updated user count text for SystemUsers
* Changed search by id fallback to trigger less often
* Fixed SystemUsers list items not updating when searching
* Fixed localization strings for SystemUsers page
Diffstat (limited to 'webapp/components/admin_console/system_users')
3 files changed, 1017 insertions, 0 deletions
diff --git a/webapp/components/admin_console/system_users/system_users.jsx b/webapp/components/admin_console/system_users/system_users.jsx new file mode 100644 index 000000000..a311aebb7 --- /dev/null +++ b/webapp/components/admin_console/system_users/system_users.jsx @@ -0,0 +1,370 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; +import {FormattedMessage} from 'react-intl'; + +import { + loadProfiles, + loadProfilesAndTeamMembers, + loadProfilesWithoutTeam, + searchUsers +} from 'actions/user_actions.jsx'; + +import AdminStore from 'stores/admin_store.jsx'; +import AnalyticsStore from 'stores/analytics_store.jsx'; +import TeamStore from 'stores/team_store.jsx'; +import UserStore from 'stores/user_store.jsx'; + +import {getAllTeams, getStandardAnalytics, getTeamStats, getUser} from 'utils/async_client.jsx'; +import {Constants, StatTypes, UserSearchOptions} from 'utils/constants.jsx'; +import {convertTeamMapToList} from 'utils/team_utils.jsx'; +import * as Utils from 'utils/utils.jsx'; + +import SystemUsersList from './system_users_list.jsx'; + +const ALL_USERS = ''; +const NO_TEAM = 'no_team'; + +const USER_ID_LENGTH = 26; +const USERS_PER_PAGE = 50; + +export default class SystemUsers extends React.Component { + constructor(props) { + super(props); + + this.updateTeamsFromStore = this.updateTeamsFromStore.bind(this); + this.updateTotalUsersFromStore = this.updateTotalUsersFromStore.bind(this); + this.updateUsersFromStore = this.updateUsersFromStore.bind(this); + + this.loadDataForTeam = this.loadDataForTeam.bind(this); + this.loadComplete = this.loadComplete.bind(this); + + this.handleTeamChange = this.handleTeamChange.bind(this); + this.handleTermChange = this.handleTermChange.bind(this); + this.nextPage = this.nextPage.bind(this); + + this.doSearch = this.doSearch.bind(this); + this.search = this.search.bind(this); + this.getUserById = this.getUserById.bind(this); + + this.renderFilterRow = this.renderFilterRow.bind(this); + + this.state = { + teams: convertTeamMapToList(AdminStore.getAllTeams()), + totalUsers: AnalyticsStore.getAllSystem()[StatTypes.TOTAL_USERS], + users: UserStore.getProfileList(), + + teamId: ALL_USERS, + term: '', + loading: true, + searching: false + }; + } + + componentDidMount() { + AdminStore.addAllTeamsChangeListener(this.updateTeamsFromStore); + + AnalyticsStore.addChangeListener(this.updateTotalUsersFromStore); + TeamStore.addStatsChangeListener(this.updateTotalUsersFromStore); + + UserStore.addChangeListener(this.updateUsersFromStore); + UserStore.addInTeamChangeListener(this.updateUsersFromStore); + UserStore.addWithoutTeamChangeListener(this.updateUsersFromStore); + + this.loadDataForTeam(this.state.teamId); + getAllTeams(); + } + + componentWillUpdate(nextProps, nextState) { + const nextTeamId = nextState.teamId; + + if (this.state.teamId !== nextTeamId) { + this.updateTotalUsersFromStore(nextTeamId); + this.updateUsersFromStore(nextTeamId, nextState.term); + + this.loadDataForTeam(nextTeamId); + } + } + + componentWillUnmount() { + AdminStore.removeAllTeamsChangeListener(this.updateTeamsFromStore); + + AnalyticsStore.removeChangeListener(this.updateTotalUsersFromStore); + TeamStore.removeStatsChangeListener(this.updateTotalUsersFromStore); + + UserStore.removeChangeListener(this.updateUsersFromStore); + UserStore.removeInTeamChangeListener(this.updateUsersFromStore); + UserStore.removeWithoutTeamChangeListener(this.updateUsersFromStore); + } + + updateTeamsFromStore() { + this.setState({teams: convertTeamMapToList(AdminStore.getAllTeams())}); + } + + updateTotalUsersFromStore(teamId = this.state.teamId) { + if (teamId === ALL_USERS) { + this.setState({ + totalUsers: AnalyticsStore.getAllSystem()[StatTypes.TOTAL_USERS] + }); + } else if (teamId === NO_TEAM) { + this.setState({ + totalUsers: 0 + }); + } else { + this.setState({ + totalUsers: TeamStore.getStats(teamId).total_member_count + }); + } + } + + updateUsersFromStore(teamId = this.state.teamId, term = this.state.term) { + if (term) { + if (teamId === this.state.teamId) { + // Search results aren't in the store, so manually update the users in them + const users = [...this.state.users]; + + for (let i = 0; i < users.length; i++) { + const user = users[i]; + + if (UserStore.hasProfile(user.id)) { + users[i] = UserStore.getProfile(user.id); + } + } + + this.setState({ + users + }); + } else { + this.doSearch(teamId, term, true); + } + + return; + } + + if (teamId === ALL_USERS) { + this.setState({users: UserStore.getProfileList(false, true)}); + } else if (teamId === NO_TEAM) { + this.setState({users: UserStore.getProfileListWithoutTeam()}); + } else { + this.setState({users: UserStore.getProfileListInTeam(this.state.teamId)}); + } + } + + loadDataForTeam(teamId) { + if (teamId === ALL_USERS) { + loadProfiles(0, Constants.PROFILE_CHUNK_SIZE, this.loadComplete); + getStandardAnalytics(); + } else if (teamId === NO_TEAM) { + loadProfilesWithoutTeam(0, Constants.PROFILE_CHUNK_SIZE, this.loadComplete); + } else { + loadProfilesAndTeamMembers(0, Constants.PROFILE_CHUNK_SIZE, teamId, this.loadComplete); + getTeamStats(teamId); + } + } + + loadComplete() { + this.setState({loading: false}); + } + + handleTeamChange(e) { + this.setState({teamId: e.target.value}); + } + + handleTermChange(term) { + this.setState({term}); + } + + nextPage(page) { + // Paging isn't supported while searching + + if (this.state.teamId === ALL_USERS) { + loadProfiles((page + 1) * USERS_PER_PAGE, USERS_PER_PAGE, this.loadComplete); + } else if (this.state.teamId === NO_TEAM) { + loadProfilesWithoutTeam(page + 1, USERS_PER_PAGE, this.loadComplete); + } else { + loadProfilesAndTeamMembers((page + 1) * USERS_PER_PAGE, USERS_PER_PAGE, this.state.teamId, this.loadComplete); + } + } + + search(term) { + if (term === '') { + this.updateUsersFromStore(this.state.teamId, term); + + this.setState({ + loading: false + }); + + this.searchTimeoutId = ''; + return; + } + + this.doSearch(this.state.teamId, term); + } + + doSearch(teamId, term, now = false) { + clearTimeout(this.searchTimeoutId); + + this.setState({ + loading: true, + users: [] + }); + + const options = { + [UserSearchOptions.ALLOW_INACTIVE]: true + }; + if (teamId === NO_TEAM) { + options[UserSearchOptions.WITHOUT_TEAM] = true; + } + + const searchTimeoutId = setTimeout( + () => { + searchUsers( + term, + teamId, + options, + (users) => { + if (searchTimeoutId !== this.searchTimeoutId) { + return; + } + + if (users.length > 0) { + this.setState({ + loading: false, + users + }); + } else if (term.length === USER_ID_LENGTH) { + // This term didn't match any users name, but it does look like it might be a user's ID + this.getUserById(term, searchTimeoutId); + } else { + this.setState({ + loading: false + }); + } + }, + () => { + this.setState({ + loading: false + }); + } + ); + }, + now ? 0 : Constants.SEARCH_TIMEOUT_MILLISECONDS + ); + + this.searchTimeoutId = searchTimeoutId; + } + + getUserById(id, searchTimeoutId) { + if (UserStore.hasProfile(id)) { + this.setState({ + loading: false, + users: [UserStore.getProfile(id)] + }); + + return; + } + + getUser( + id, + (user) => { + if (searchTimeoutId !== this.searchTimeoutId) { + return; + } + + this.setState({ + loading: false, + users: [user] + }); + }, + () => { + if (searchTimeoutId !== this.searchTimeoutId) { + return; + } + + this.setState({ + loading: false, + users: [] + }); + } + ); + } + + renderFilterRow(doSearch) { + const teams = this.state.teams.map((team) => { + return ( + <option + key={team.id} + value={team.id} + > + {team.display_name} + </option> + ); + }); + + return ( + <div className='system-users__filter-row'> + <div className='system-users__filter'> + <input + ref='filter' + className='form-control filter-textbox' + placeholder={Utils.localizeMessage('filtered_user_list.search', 'Search users')} + onInput={doSearch} + /> + </div> + <label> + <span className='system-users__team-filter-label'> + <FormattedMessage + id='filtered_user_list.show' + defaultMessage='Filter:' + /> + </span> + <select + className='form-control system-users__team-filter' + onChange={this.handleTeamChange} + value={this.state.teamId} + > + <option value={ALL_USERS}>{Utils.localizeMessage('admin.system_users.allUsers', 'All Users')}</option> + <option value={NO_TEAM}>{Utils.localizeMessage('admin.system_users.noTeams', 'No Teams')}</option> + {teams} + </select> + </label> + </div> + ); + } + + render() { + let users = null; + if (!this.state.loading) { + users = this.state.users; + } + + return ( + <div className='wrapper--fixed'> + <h3 className='admin-console-header'> + <FormattedMessage + id='admin.system_users.title' + defaultMessage='{siteName} Users' + values={{ + siteName: global.mm_config.SiteName + }} + /> + </h3> + <div className='more-modal__list member-list-holder'> + <SystemUsersList + renderFilterRow={this.renderFilterRow} + search={this.search} + nextPage={this.nextPage} + users={users} + usersPerPage={USERS_PER_PAGE} + total={this.state.totalUsers} + teams={this.state.teams} + teamId={this.state.teamId} + term={this.state.term} + onTermChange={this.handleTermChange} + /> + </div> + </div> + ); + } +} diff --git a/webapp/components/admin_console/system_users/system_users_dropdown.jsx b/webapp/components/admin_console/system_users/system_users_dropdown.jsx new file mode 100644 index 000000000..6f18754a1 --- /dev/null +++ b/webapp/components/admin_console/system_users/system_users_dropdown.jsx @@ -0,0 +1,415 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import ConfirmModal from 'components/confirm_modal.jsx'; + +import TeamStore from 'stores/team_store.jsx'; +import UserStore from 'stores/user_store.jsx'; + +import Constants from 'utils/constants.jsx'; +import * as Utils from 'utils/utils.jsx'; +import {updateUserRoles, updateActive} from 'actions/user_actions.jsx'; +import {adminResetMfa} from 'actions/admin_actions.jsx'; + +import {FormattedMessage} from 'react-intl'; + +import React from 'react'; + +export default class SystemUsersDropdown extends React.Component { + static propTypes = { + user: React.PropTypes.object.isRequired, + doPasswordReset: React.PropTypes.func.isRequired + }; + + constructor(props) { + super(props); + + this.handleMakeMember = this.handleMakeMember.bind(this); + this.handleMakeActive = this.handleMakeActive.bind(this); + this.handleMakeNotActive = this.handleMakeNotActive.bind(this); + this.handleMakeSystemAdmin = this.handleMakeSystemAdmin.bind(this); + this.handleResetPassword = this.handleResetPassword.bind(this); + this.handleResetMfa = this.handleResetMfa.bind(this); + this.handleDemoteSystemAdmin = this.handleDemoteSystemAdmin.bind(this); + this.handleDemoteSubmit = this.handleDemoteSubmit.bind(this); + this.handleDemoteCancel = this.handleDemoteCancel.bind(this); + + this.state = { + serverError: null, + showDemoteModal: false, + user: null, + role: null + }; + } + + doMakeMember() { + updateUserRoles( + this.props.user.id, + 'system_user', + null, + (err) => { + this.setState({serverError: err.message}); + } + ); + } + + handleMakeMember(e) { + e.preventDefault(); + const me = UserStore.getCurrentUser(); + if (this.props.user.id === me.id && me.roles.includes('system_admin')) { + this.handleDemoteSystemAdmin(this.props.user, 'member'); + } else { + this.doMakeMember(); + } + } + + handleMakeActive(e) { + e.preventDefault(); + updateActive(this.props.user.id, true, null, + (err) => { + this.setState({serverError: err.message}); + } + ); + } + + handleMakeNotActive(e) { + e.preventDefault(); + updateActive(this.props.user.id, false, null, + (err) => { + this.setState({serverError: err.message}); + } + ); + } + + handleMakeSystemAdmin(e) { + e.preventDefault(); + + updateUserRoles( + this.props.user.id, + 'system_user system_admin', + null, + (err) => { + this.setState({serverError: err.message}); + } + ); + } + + handleResetPassword(e) { + e.preventDefault(); + this.props.doPasswordReset(this.props.user); + } + + handleResetMfa(e) { + e.preventDefault(); + + adminResetMfa(this.props.user.id, + null, + (err) => { + this.setState({serverError: err.message}); + } + ); + } + + handleDemoteSystemAdmin(user, role) { + this.setState({ + serverError: this.state.serverError, + showDemoteModal: true, + user, + role + }); + } + + handleDemoteCancel() { + this.setState({ + serverError: null, + showDemoteModal: false, + user: null, + role: null + }); + } + + handleDemoteSubmit() { + if (this.state.role === 'member') { + this.doMakeMember(); + } + + const teamUrl = TeamStore.getCurrentTeamUrl(); + if (teamUrl) { + // the channel is added to the URL cause endless loading not being fully fixed + window.location.href = teamUrl + '/channels/town-square'; + } else { + window.location.href = '/'; + } + } + + render() { + let serverError = null; + if (this.state.serverError) { + serverError = ( + <div className='has-error'> + <label className='has-error control-label'>{this.state.serverError}</label> + </div> + ); + } + + const user = this.props.user; + if (!user) { + return <div/>; + } + let currentRoles = ( + <FormattedMessage + id='admin.user_item.member' + defaultMessage='Member' + /> + ); + + if (user.roles.length > 0 && Utils.isSystemAdmin(user.roles)) { + currentRoles = ( + <FormattedMessage + id='team_members_dropdown.systemAdmin' + defaultMessage='System Admin' + /> + ); + } + + const me = UserStore.getCurrentUser(); + let showMakeMember = Utils.isSystemAdmin(user.roles); + let showMakeSystemAdmin = !Utils.isSystemAdmin(user.roles); + let showMakeActive = false; + let showMakeNotActive = !Utils.isSystemAdmin(user.roles); + const mfaEnabled = global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.MFA === 'true' && global.window.mm_config.EnableMultifactorAuthentication === 'true'; + const showMfaReset = mfaEnabled && user.mfa_active; + + if (user.delete_at > 0) { + currentRoles = ( + <FormattedMessage + id='admin.user_item.inactive' + defaultMessage='Inactive' + /> + ); + showMakeMember = false; + showMakeSystemAdmin = false; + showMakeActive = true; + showMakeNotActive = false; + } + + let disableActivationToggle = false; + if (user.auth_service === Constants.LDAP_SERVICE) { + disableActivationToggle = true; + } + + let makeSystemAdmin = null; + if (showMakeSystemAdmin) { + makeSystemAdmin = ( + <li role='presentation'> + <a + role='menuitem' + href='#' + onClick={this.handleMakeSystemAdmin} + > + <FormattedMessage + id='admin.user_item.makeSysAdmin' + defaultMessage='Make System Admin' + /> + </a> + </li> + ); + } + + let makeMember = null; + if (showMakeMember) { + makeMember = ( + <li role='presentation'> + <a + role='menuitem' + href='#' + onClick={this.handleMakeMember} + > + <FormattedMessage + id='admin.user_item.makeMember' + defaultMessage='Make Member' + /> + </a> + </li> + ); + } + + let menuClass = ''; + if (disableActivationToggle) { + menuClass = 'disabled'; + } + + let makeActive = null; + if (showMakeActive) { + makeActive = ( + <li + role='presentation' + className={menuClass} + > + <a + role='menuitem' + href='#' + onClick={this.handleMakeActive} + > + <FormattedMessage + id='admin.user_item.makeActive' + defaultMessage='Make Active' + /> + </a> + </li> + ); + } + + let makeNotActive = null; + if (showMakeNotActive) { + makeNotActive = ( + <li + role='presentation' + className={menuClass} + > + <a + role='menuitem' + href='#' + onClick={this.handleMakeNotActive} + > + <FormattedMessage + id='admin.user_item.makeInactive' + defaultMessage='Make Inactive' + /> + </a> + </li> + ); + } + + let mfaReset = null; + if (showMfaReset) { + mfaReset = ( + <li role='presentation'> + <a + role='menuitem' + href='#' + onClick={this.handleResetMfa} + > + <FormattedMessage + id='admin.user_item.resetMfa' + defaultMessage='Remove MFA' + /> + </a> + </li> + ); + } + + let passwordReset; + if (user.auth_service) { + passwordReset = ( + <li role='presentation'> + <a + role='menuitem' + href='#' + onClick={this.handleResetPassword} + > + <FormattedMessage + id='admin.user_item.switchToEmail' + defaultMessage='Switch to Email/Password' + /> + </a> + </li> + ); + } else { + passwordReset = ( + <li role='presentation'> + <a + role='menuitem' + href='#' + onClick={this.handleResetPassword} + > + <FormattedMessage + id='admin.user_item.resetPwd' + defaultMessage='Reset Password' + /> + </a> + </li> + ); + } + + let makeDemoteModal = null; + if (this.props.user.id === me.id) { + const title = ( + <FormattedMessage + id='admin.user_item.confirmDemoteRoleTitle' + defaultMessage='Confirm demotion from System Admin role' + /> + ); + + const message = ( + <div> + <FormattedMessage + id='admin.user_item.confirmDemoteDescription' + defaultMessage="If you demote yourself from the System Admin role and there is not another user with System Admin privileges, you'll need to re-assign a System Admin by accessing the Mattermost server through a terminal and running the following command." + /> + <br/> + <br/> + <FormattedMessage + id='admin.user_item.confirmDemotionCmd' + defaultMessage='platform roles system_admin {username}' + values={{ + username: me.username + }} + /> + {serverError} + </div> + ); + + const confirmButton = ( + <FormattedMessage + id='admin.user_item.confirmDemotion' + defaultMessage='Confirm Demotion' + /> + ); + + makeDemoteModal = ( + <ConfirmModal + show={this.state.showDemoteModal} + title={title} + message={message} + confirmButton={confirmButton} + onConfirm={this.handleDemoteSubmit} + onCancel={this.handleDemoteCancel} + /> + ); + } + + let displayedName = Utils.getDisplayName(user); + if (displayedName !== user.username) { + displayedName += ' (@' + user.username + ')'; + } + + return ( + <div className='dropdown member-drop'> + <a + href='#' + className='dropdown-toggle theme' + type='button' + data-toggle='dropdown' + aria-expanded='true' + > + <span>{currentRoles} </span> + <span className='caret'/> + </a> + <ul + className='dropdown-menu member-menu' + role='menu' + > + {makeMember} + {makeActive} + {makeNotActive} + {makeSystemAdmin} + {mfaReset} + {passwordReset} + </ul> + {makeDemoteModal} + {serverError} + </div> + ); + } +} diff --git a/webapp/components/admin_console/system_users/system_users_list.jsx b/webapp/components/admin_console/system_users/system_users_list.jsx new file mode 100644 index 000000000..5d8837164 --- /dev/null +++ b/webapp/components/admin_console/system_users/system_users_list.jsx @@ -0,0 +1,232 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; +import {FormattedMessage, FormattedHTMLMessage} from 'react-intl'; + +import ResetPasswordModal from 'components/admin_console/reset_password_modal.jsx'; +import SearchableUserList from 'components/searchable_user_list/searchable_user_list.jsx'; + +import {getUser} from 'utils/async_client.jsx'; +import {Constants} from 'utils/constants.jsx'; +import * as Utils from 'utils/utils.jsx'; + +import SystemUsersDropdown from './system_users_dropdown.jsx'; + +export default class SystemUsersList extends React.Component { + static propTypes = { + users: React.PropTypes.arrayOf(React.PropTypes.object), + usersPerPage: React.PropTypes.number, + total: React.PropTypes.number, + nextPage: React.PropTypes.func, + search: React.PropTypes.func.isRequired, + focusOnMount: React.PropTypes.bool, + renderFilterRow: React.PropTypes.func, + + teamId: React.PropTypes.string.isRequired, + term: React.PropTypes.string.isRequired, + onTermChange: React.PropTypes.func.isRequired + }; + + constructor(props) { + super(props); + + this.nextPage = this.nextPage.bind(this); + this.previousPage = this.previousPage.bind(this); + this.search = this.search.bind(this); + + this.doPasswordReset = this.doPasswordReset.bind(this); + this.doPasswordResetDismiss = this.doPasswordResetDismiss.bind(this); + this.doPasswordResetSubmit = this.doPasswordResetSubmit.bind(this); + + this.state = { + page: 0, + + showPasswordModal: false, + user: null + }; + } + + componentWillReceiveProps(nextProps) { + if (nextProps.teamId !== this.props.teamId) { + this.setState({page: 0}); + } + } + + nextPage() { + this.setState({page: this.state.page + 1}); + + this.props.nextPage(this.state.page + 1); + } + + previousPage() { + this.setState({page: this.state.page - 1}); + } + + search(term) { + this.props.search(term); + + if (term !== '') { + this.setState({page: 0}); + } + } + + doPasswordReset(user) { + this.setState({ + showPasswordModal: true, + user + }); + } + + doPasswordResetDismiss() { + this.setState({ + showPasswordModal: false, + user: null + }); + } + + doPasswordResetSubmit(user) { + getUser(user.id); + + this.setState({ + showPasswordModal: false, + user: null + }); + } + + getInfoForUser(user) { + const info = []; + + if (user.auth_service) { + let service; + if (user.auth_service === Constants.LDAP_SERVICE || user.auth_service === Constants.SAML_SERVICE) { + service = user.auth_service.toUpperCase(); + } else { + service = Utils.toTitleCase(user.auth_service); + } + + info.push( + <FormattedHTMLMessage + key='admin.user_item.authServiceNotEmail' + id='admin.user_item.authServiceNotEmail' + defaultMessage='<strong>Sign-in Method:</strong> {service}' + values={{ + service + }} + /> + ); + } else { + info.push( + <FormattedHTMLMessage + key='admin.user_item.authServiceEmail' + id='admin.user_item.authServiceEmail' + defaultMessage='<strong>Sign-in Method:</strong> Email' + /> + ); + } + + const mfaEnabled = global.window.mm_license.IsLicensed === 'true' && + global.window.mm_license.MFA === 'true' && + global.window.mm_config.EnableMultifactorAuthentication === 'true'; + if (mfaEnabled) { + info.push(', '); + + if (user.mfa_active) { + info.push( + <FormattedHTMLMessage + key='admin.user_item.mfaYes' + id='admin.user_item.mfaYes' + defaultMessage='<strong>MFA</strong>: Yes' + /> + ); + } else { + info.push( + <FormattedHTMLMessage + key='admin.user_item.mfaNo' + id='admin.user_item.mfaNo' + defaultMessage='<strong>MFA</strong>: No' + /> + ); + } + } + + return info; + } + + renderCount(count, total, startCount, endCount, isSearch) { + if (total) { + if (isSearch) { + return ( + <FormattedMessage + id='system_users_list.countSearch' + defaultMessage='{count, number} {count, plural, one {user} other {users}} of {total} total' + values={{ + count, + total + }} + /> + ); + } else if (startCount !== 0 || endCount !== total) { + return ( + <FormattedMessage + id='system_users_list.countPage' + defaultMessage='{startCount, number} - {endCount, number} {count, plural, one {user} other {users}} of {total} total' + values={{ + count, + startCount: startCount + 1, + endCount, + total + }} + /> + ); + } + + return ( + <FormattedMessage + id='system_users_list.count' + defaultMessage='{count, number} {count, plural, one {user} other {users}}' + values={{ + count + }} + /> + ); + } + + return null; + } + + render() { + const extraInfo = {}; + if (this.props.users) { + for (const user of this.props.users) { + extraInfo[user.id] = this.getInfoForUser(user); + } + } + + return ( + <div> + <SearchableUserList + {...this.props} + renderCount={this.renderCount} + extraInfo={extraInfo} + actions={[SystemUsersDropdown]} + actionProps={{ + doPasswordReset: this.doPasswordReset + }} + nextPage={this.nextPage} + previousPage={this.previousPage} + search={this.search} + page={this.state.page} + term={this.props.term} + onTermChange={this.props.onTermChange} + /> + <ResetPasswordModal + user={this.state.user} + show={this.state.showPasswordModal} + onModalSubmit={this.doPasswordResetSubmit} + onModalDismissed={this.doPasswordResetDismiss} + /> + </div> + ); + } +} |