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
Этот коммит содержится в:
коммит произвёл
Corey Hulen
родитель
9a9729f22f
Коммит
689cac535e
370
webapp/components/admin_console/system_users/system_users.jsx
Обычный файл
370
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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
232
webapp/components/admin_console/system_users/system_users_list.jsx
Обычный файл
232
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>
|
||||
);
|
||||
}
|
||||
}
|
||||
Ссылка в новой задаче
Block a user