Merging performance branch into master (#4268)
* improve performance on sendNotifications * Fix SQL queries * Remove get direct profiles, not needed anymore * Add raw data to error details if AppError fails to decode * men * Fix decode (#4052) * Fixing json decode * Adding unit test * Initial work for client scaling (#4051) * Begin adding paging to profiles API * Added more paging functionality * Finish hooking up admin console user lists * Add API for searching users and add searching to all user lists * Add lazy loading of profiles * Revert config.json * Fix unit tests and some style issues * Add GetProfilesFromList to Go driver and fix web unit test * Update etag for GetProfiles * Updating ui for filters and pagination (#4044) * Updating UI for pagination * Adjusting margins for filter row * Adjusting margin for specific modals * Adding relative padding to system console * Adjusting responsive view * Update client user tests * Minor fixes for direct messages modal (#4056) * Remove some unneeded initial load calls (#4057) * UX updates to user lists, added smart counts and bug fixes (#4059) * Improved getExplicitMentions and unit tests (#4064) * Refactor getting posts to lazy load profiles correctly (#4062) * Comment out SetActiveChannel test (#4066) * Profiler cpu, block, and memory profiler. (#4081) * Fix TestSetActiveChannel unit test (#4071) * Fixing build failure caused by dependancies updating (#4076) * Adding profiler * Fix admin_team_member_dropdown eslint errors * Bumping session cache size (#4077) * Bumping session cache size * Bumping status cache * Refactor how the client handles channel members to be large team friendly (#4106) * Refactor how the client handles channel members to be large team friendly * Change Id to ChannelId in ChannelStats model * Updated getChannelMember and getProfilesByIds routes to match proposal * Performance improvements (#4100) * Performance improvements * Fixing re-connect issue * Fixing error message * Some other minor perf tweaks * Some other minor perf tweaks * Fixing config file * Fixing buffer size * Fixing web socket send message * adding some error logging * fix getMe to be user required * Fix websocket event for new user * Fixing shutting down * Reverting web socket changes * Fixing logging lvl * Adding caching to GetMember * Adding some logging * Fixing caching * Fixing caching invalidate * Fixing direct message caching * Fixing caching * Fixing caching * Remove GetDirectProfiles from initial load * Adding logging and fixing websocket client * Adding back caching from bad merge. * Explicitly close go driver requests (#4162) * Refactored how the client handles team members to be more large team friendly (#4159) * Refactor getProfilesForDirectMessageList API into getAllProfiles API * Refactored how the client handles team members to be more large team friendly * Fix js error when receiving a notification * Fix JS error caused by current user being overwritten with sanitized version (#4165) * Adding error message to status failure (#4167) * Fix a few bugs caused by client scaling refactoring (#4170) * When there is no read replica, don't open a second set of connections to the master database (#4173) * Adding connection tacking to stats (#4174) * Reduce DB writes for statuses and other status related changes (#4175) * Fix bug preventing opening of DM channels from more modal (#4181) * Fixing socket timing error (#4183) * Fixing ping/pong handler * Fixing socket timing error * Commenting out status broadcasting * Removing user status changes * Removing user status changes * Removing user status changes * Removing user status changes * Adding DoPreComputeJson() * Performance improvements (#4194) * * Fix System Console Analytics queries * Add db.SetConnMaxLifetime to 15 minutes * Add "net/http/pprof" for profiling * Add FreeOSMemory() to manually release memory on reload config * Add flag to enable http profiler * Fix memory leak (#4197) * Fix memory leak * removed unneeded nil assignment * Fixing go routine leak (#4208) * Merge fixes * Merge fix * Refactored statuses to be queried by the client rather than broadcast by the server (#4212) * Refactored server code to reduce status broadcasts and to allow getting statuses by IDs * Refactor client code to periodically fetch statuses * Add store unit test for getting statuses by ids * Fix status unit test * Add getStatusesByIds REST API and move the client over to use that instead of the WebSocket * Adding multiple threads to websocket hub (#4230) * Adding multiple threads to websocket hub * Fixing unit tests * Fixing so websocket connections from the same user end up in the same… (#4240) * Fixing so websocket connections from the same user end up in the same list * Removing old comment * Refactor user autocomplete to query the server (#4239) * Add API for autocompleting users * Converted at mention autocomplete to query server * Converted user search autocomplete to query server * Switch autocomplete API naming to use term instead of username * Split autocomplete API into two, one for channels and for teams * Fix copy/paste error * Some final client scaling fixes (#4246) * Add lazy loading of profiles to integration pages * Add lazy loading of profiles to emoji page * Fix JS error when receiving post in select team menu and also clean up channel store
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
0512bd26ee
Коммит
365b8b465e
@@ -22,7 +22,7 @@ export default class AdminNavbarDropdown extends React.Component {
|
||||
|
||||
this.state = {
|
||||
teams: TeamStore.getAll(),
|
||||
teamMembers: TeamStore.getTeamMembers()
|
||||
teamMembers: TeamStore.getMyTeamMembers()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ export default class AdminNavbarDropdown extends React.Component {
|
||||
onTeamChange() {
|
||||
this.setState({
|
||||
teams: TeamStore.getAll(),
|
||||
teamMembers: TeamStore.getTeamMembers()
|
||||
teamMembers: TeamStore.getMyTeamMembers()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -8,11 +8,11 @@ import UserStore from 'stores/user_store.jsx';
|
||||
import ConfirmModal from '../confirm_modal.jsx';
|
||||
import TeamStore from 'stores/team_store.jsx';
|
||||
|
||||
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export default class UserItem extends React.Component {
|
||||
export default class AdminTeamMembersDropdown extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
@@ -50,7 +50,7 @@ export default class UserItem extends React.Component {
|
||||
}
|
||||
);
|
||||
Client.updateTeamMemberRoles(
|
||||
this.props.team.id,
|
||||
this.props.teamMember.team_id,
|
||||
this.props.user.id,
|
||||
'team_user',
|
||||
() => {
|
||||
@@ -74,7 +74,7 @@ export default class UserItem extends React.Component {
|
||||
|
||||
handleRemoveFromTeam() {
|
||||
Client.removeUserFromTeam(
|
||||
this.props.team.id,
|
||||
this.props.teamMember.team_id,
|
||||
this.props.user.id,
|
||||
() => {
|
||||
this.props.refreshProfiles();
|
||||
@@ -111,7 +111,7 @@ export default class UserItem extends React.Component {
|
||||
|
||||
doMakeTeamAdmin() {
|
||||
Client.updateTeamMemberRoles(
|
||||
this.props.team.id,
|
||||
this.props.teamMember.team_id,
|
||||
this.props.user.id,
|
||||
'team_user team_admin',
|
||||
() => {
|
||||
@@ -241,7 +241,6 @@ export default class UserItem extends React.Component {
|
||||
}
|
||||
|
||||
const me = UserStore.getCurrentUser();
|
||||
const email = user.email;
|
||||
let showMakeMember = Utils.isAdmin(teamMember.roles) || Utils.isSystemAdmin(user.roles);
|
||||
let showMakeAdmin = !Utils.isAdmin(teamMember.roles) && !Utils.isSystemAdmin(user.roles);
|
||||
let showMakeSystemAdmin = !Utils.isSystemAdmin(user.roles);
|
||||
@@ -406,39 +405,8 @@ export default class UserItem extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
let mfaActiveText;
|
||||
if (mfaEnabled) {
|
||||
if (user.mfa_active) {
|
||||
mfaActiveText = (
|
||||
<FormattedHTMLMessage
|
||||
id='admin.user_item.mfaYes'
|
||||
defaultMessage=', <strong>MFA</strong>: Yes'
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
mfaActiveText = (
|
||||
<FormattedHTMLMessage
|
||||
id='admin.user_item.mfaNo'
|
||||
defaultMessage=', <strong>MFA</strong>: No'
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let authServiceText;
|
||||
let passwordReset;
|
||||
if (user.auth_service) {
|
||||
const service = (user.auth_service === Constants.LDAP_SERVICE || user.auth_service === Constants.SAML_SERVICE) ? user.auth_service.toUpperCase() : Utils.toTitleCase(user.auth_service);
|
||||
authServiceText = (
|
||||
<FormattedHTMLMessage
|
||||
id='admin.user_item.authServiceNotEmail'
|
||||
defaultMessage=', <strong>Sign-in Method:</strong> {service}'
|
||||
values={{
|
||||
service
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
passwordReset = (
|
||||
<li role='presentation'>
|
||||
<a
|
||||
@@ -454,13 +422,6 @@ export default class UserItem extends React.Component {
|
||||
</li>
|
||||
);
|
||||
} else {
|
||||
authServiceText = (
|
||||
<FormattedHTMLMessage
|
||||
id='admin.user_item.authServiceEmail'
|
||||
defaultMessage=', <strong>Sign-in Method:</strong> Email'
|
||||
/>
|
||||
);
|
||||
|
||||
passwordReset = (
|
||||
<li role='presentation'>
|
||||
<a
|
||||
@@ -531,63 +492,38 @@ export default class UserItem extends React.Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='more-modal__row'>
|
||||
<img
|
||||
className='more-modal__image pull-left'
|
||||
src={`${Client.getUsersRoute()}/${user.id}/image?time=${user.update_at}`}
|
||||
height='36'
|
||||
width='36'
|
||||
/>
|
||||
<div className='more-modal__details'>
|
||||
<div className='more-modal__name'>{displayedName}</div>
|
||||
<div className='more-modal__description'>
|
||||
<FormattedHTMLMessage
|
||||
id='admin.user_item.emailTitle'
|
||||
defaultMessage='<strong>Email:</strong> {email}'
|
||||
values={{
|
||||
email
|
||||
}}
|
||||
/>
|
||||
{authServiceText}
|
||||
{mfaActiveText}
|
||||
</div>
|
||||
{serverError}
|
||||
</div>
|
||||
<div className='more-modal__actions'>
|
||||
<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'
|
||||
>
|
||||
{removeFromTeam}
|
||||
{makeAdmin}
|
||||
{makeMember}
|
||||
{makeActive}
|
||||
{makeNotActive}
|
||||
{makeSystemAdmin}
|
||||
{mfaReset}
|
||||
{passwordReset}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<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'
|
||||
>
|
||||
{removeFromTeam}
|
||||
{makeAdmin}
|
||||
{makeMember}
|
||||
{makeActive}
|
||||
{makeNotActive}
|
||||
{makeSystemAdmin}
|
||||
{mfaReset}
|
||||
{passwordReset}
|
||||
</ul>
|
||||
{makeDemoteModal}
|
||||
{serverError}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
UserItem.propTypes = {
|
||||
team: React.PropTypes.object.isRequired,
|
||||
AdminTeamMembersDropdown.propTypes = {
|
||||
user: React.PropTypes.object.isRequired,
|
||||
teamMember: React.PropTypes.object.isRequired,
|
||||
refreshProfiles: React.PropTypes.func.isRequired,
|
||||
@@ -1,16 +1,25 @@
|
||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import AdminStore from 'stores/admin_store.jsx';
|
||||
import Client from 'client/web_client.jsx';
|
||||
import FormError from 'components/form_error.jsx';
|
||||
import LoadingScreen from '../loading_screen.jsx';
|
||||
import UserItem from './user_item.jsx';
|
||||
import SearchableUserList from 'components/searchable_user_list.jsx';
|
||||
import AdminTeamMembersDropdown from './admin_team_members_dropdown.jsx';
|
||||
import ResetPasswordModal from './reset_password_modal.jsx';
|
||||
import FormError from 'components/form_error.jsx';
|
||||
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import AdminStore from 'stores/admin_store.jsx';
|
||||
import TeamStore from 'stores/team_store.jsx';
|
||||
import UserStore from 'stores/user_store.jsx';
|
||||
|
||||
import {searchUsers, loadProfilesAndTeamMembers, loadTeamMembersForProfilesList} from 'actions/user_actions.jsx';
|
||||
import {getTeamStats} from 'utils/async_client.jsx';
|
||||
|
||||
import Constants from 'utils/constants.jsx';
|
||||
import * as Utils from 'utils/utils.jsx';
|
||||
|
||||
import React from 'react';
|
||||
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
|
||||
|
||||
const USERS_PER_PAGE = 50;
|
||||
|
||||
export default class UserList extends React.Component {
|
||||
static get propTypes() {
|
||||
@@ -23,34 +32,49 @@ export default class UserList extends React.Component {
|
||||
super(props);
|
||||
|
||||
this.onAllTeamsChange = this.onAllTeamsChange.bind(this);
|
||||
this.onStatsChange = this.onStatsChange.bind(this);
|
||||
this.onUsersChange = this.onUsersChange.bind(this);
|
||||
this.onTeamChange = this.onTeamChange.bind(this);
|
||||
|
||||
this.getTeamProfiles = this.getTeamProfiles.bind(this);
|
||||
this.getCurrentTeamProfiles = this.getCurrentTeamProfiles.bind(this);
|
||||
this.doPasswordReset = this.doPasswordReset.bind(this);
|
||||
this.doPasswordResetDismiss = this.doPasswordResetDismiss.bind(this);
|
||||
this.doPasswordResetSubmit = this.doPasswordResetSubmit.bind(this);
|
||||
this.getTeamMemberForUser = this.getTeamMemberForUser.bind(this);
|
||||
this.nextPage = this.nextPage.bind(this);
|
||||
this.search = this.search.bind(this);
|
||||
this.loadComplete = this.loadComplete.bind(this);
|
||||
|
||||
const stats = TeamStore.getStats(this.props.params.team);
|
||||
|
||||
this.state = {
|
||||
team: AdminStore.getTeam(this.props.params.team),
|
||||
users: null,
|
||||
teamMembers: null,
|
||||
users: [],
|
||||
teamMembers: TeamStore.getMembersInTeam(this.props.params.team),
|
||||
total: stats.member_count,
|
||||
serverError: null,
|
||||
showPasswordModal: false,
|
||||
loading: true,
|
||||
user: null
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getCurrentTeamProfiles();
|
||||
|
||||
AdminStore.addAllTeamsChangeListener(this.onAllTeamsChange);
|
||||
UserStore.addInTeamChangeListener(this.onUsersChange);
|
||||
TeamStore.addChangeListener(this.onTeamChange);
|
||||
TeamStore.addStatsChangeListener(this.onStatsChange);
|
||||
|
||||
loadProfilesAndTeamMembers(0, Constants.PROFILE_CHUNK_SIZE, this.props.params.team, this.loadComplete);
|
||||
getTeamStats(this.props.params.team);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.params.team !== this.props.params.team) {
|
||||
const stats = TeamStore.getStats(nextProps.params.team);
|
||||
this.setState({
|
||||
team: AdminStore.getTeam(nextProps.params.team)
|
||||
team: AdminStore.getTeam(nextProps.params.team),
|
||||
users: [],
|
||||
teamMembers: TeamStore.getMembersInTeam(nextProps.params.team),
|
||||
total: stats.member_count
|
||||
});
|
||||
|
||||
this.getTeamProfiles(nextProps.params.team);
|
||||
@@ -59,6 +83,13 @@ export default class UserList extends React.Component {
|
||||
|
||||
componentWillUnmount() {
|
||||
AdminStore.removeAllTeamsChangeListener(this.onAllTeamsChange);
|
||||
UserStore.removeInTeamChangeListener(this.onUsersChange);
|
||||
TeamStore.removeChangeListener(this.onTeamChange);
|
||||
TeamStore.removeStatsChangeListener(this.onStatsChange);
|
||||
}
|
||||
|
||||
loadComplete() {
|
||||
this.setState({loading: false});
|
||||
}
|
||||
|
||||
onAllTeamsChange() {
|
||||
@@ -67,59 +98,21 @@ export default class UserList extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
getCurrentTeamProfiles() {
|
||||
this.getTeamProfiles(this.props.params.team);
|
||||
onStatsChange() {
|
||||
const stats = TeamStore.getStats(this.props.params.team);
|
||||
this.setState({total: stats.member_count});
|
||||
}
|
||||
|
||||
getTeamProfiles(teamId) {
|
||||
Client.getTeamMembers(
|
||||
teamId,
|
||||
(data) => {
|
||||
this.setState({
|
||||
teamMembers: data
|
||||
});
|
||||
},
|
||||
(err) => {
|
||||
this.setState({
|
||||
teamMembers: null,
|
||||
serverError: err.message
|
||||
});
|
||||
}
|
||||
);
|
||||
onUsersChange() {
|
||||
this.setState({users: UserStore.getProfileListInTeam(this.props.params.team)});
|
||||
}
|
||||
|
||||
Client.getProfilesForTeam(
|
||||
teamId,
|
||||
(users) => {
|
||||
var memberList = [];
|
||||
for (var id in users) {
|
||||
if (users.hasOwnProperty(id)) {
|
||||
memberList.push(users[id]);
|
||||
}
|
||||
}
|
||||
onTeamChange() {
|
||||
this.setState({teamMembers: TeamStore.getMembersInTeam(this.props.params.team)});
|
||||
}
|
||||
|
||||
memberList.sort((a, b) => {
|
||||
if (a.username < b.username) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (a.username > b.username) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
this.setState({
|
||||
users: memberList
|
||||
});
|
||||
},
|
||||
(err) => {
|
||||
this.setState({
|
||||
users: null,
|
||||
serverError: err.message
|
||||
});
|
||||
}
|
||||
);
|
||||
nextPage(page) {
|
||||
loadProfilesAndTeamMembers((page + 1) * USERS_PER_PAGE, USERS_PER_PAGE, this.props.params.team);
|
||||
}
|
||||
|
||||
doPasswordReset(user) {
|
||||
@@ -144,20 +137,21 @@ export default class UserList extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
getTeamMemberForUser(userId) {
|
||||
if (this.state.teamMembers) {
|
||||
for (const index in this.state.teamMembers) {
|
||||
if (this.state.teamMembers.hasOwnProperty(index)) {
|
||||
var teamMember = this.state.teamMembers[index];
|
||||
|
||||
if (teamMember.user_id === userId) {
|
||||
return teamMember;
|
||||
}
|
||||
}
|
||||
}
|
||||
search(term) {
|
||||
if (term === '') {
|
||||
this.setState({search: false, users: UserStore.getProfileListInTeam(this.props.params.team)});
|
||||
return;
|
||||
}
|
||||
|
||||
return null;
|
||||
searchUsers(
|
||||
term,
|
||||
this.props.params.team,
|
||||
{},
|
||||
(users) => {
|
||||
this.setState({loading: true, search: true, users});
|
||||
loadTeamMembersForProfilesList(users, this.props.params.team, this.loadComplete);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -165,41 +159,71 @@ export default class UserList extends React.Component {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.state.users == null || this.state.teamMembers == null) {
|
||||
return (
|
||||
<div className='wrapper--fixed'>
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id='admin.userList.title'
|
||||
defaultMessage='Users for {team}'
|
||||
values={{
|
||||
team: this.state.team.name
|
||||
}}
|
||||
/>
|
||||
</h3>
|
||||
<FormError error={this.state.serverError}/>
|
||||
<LoadingScreen/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const teamMembers = this.state.teamMembers;
|
||||
const users = this.state.users;
|
||||
const actionUserProps = {};
|
||||
const extraInfo = {};
|
||||
const mfaEnabled = global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.MFA === 'true' && global.window.mm_config.EnableMultifactorAuthentication === 'true';
|
||||
|
||||
var memberList = this.state.users.map((user) => {
|
||||
var teamMember = this.getTeamMemberForUser(user.id);
|
||||
let usersToDisplay;
|
||||
if (this.state.loading) {
|
||||
usersToDisplay = null;
|
||||
} else {
|
||||
usersToDisplay = [];
|
||||
|
||||
if (!teamMember || teamMember.delete_at > 0) {
|
||||
return null;
|
||||
for (let i = 0; i < users.length; i++) {
|
||||
const user = users[i];
|
||||
|
||||
if (teamMembers[user.id]) {
|
||||
usersToDisplay.push(user);
|
||||
actionUserProps[user.id] = {
|
||||
teamMember: teamMembers[user.id]
|
||||
};
|
||||
|
||||
const info = [];
|
||||
|
||||
if (user.auth_service) {
|
||||
const service = (user.auth_service === Constants.LDAP_SERVICE || user.auth_service === Constants.SAML_SERVICE) ? user.auth_service.toUpperCase() : Utils.toTitleCase(user.auth_service);
|
||||
info.push(
|
||||
<FormattedHTMLMessage
|
||||
id='admin.user_item.authServiceNotEmail'
|
||||
defaultMessage='<strong>Sign-in Method:</strong> {service}'
|
||||
values={{
|
||||
service
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
info.push(
|
||||
<FormattedHTMLMessage
|
||||
id='admin.user_item.authServiceEmail'
|
||||
defaultMessage='<strong>Sign-in Method:</strong> Email'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (mfaEnabled) {
|
||||
if (user.mfa_active) {
|
||||
info.push(
|
||||
<FormattedHTMLMessage
|
||||
id='admin.user_item.mfaYes'
|
||||
defaultMessage='<strong>MFA</strong>: Yes'
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
info.push(
|
||||
<FormattedHTMLMessage
|
||||
id='admin.user_item.mfaNo'
|
||||
defaultMessage='<strong>MFA</strong>: No'
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extraInfo[user.id] = info;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<UserItem
|
||||
team={this.state.team}
|
||||
key={'user_' + user.id}
|
||||
user={user}
|
||||
teamMember={teamMember}
|
||||
refreshProfiles={this.getCurrentTeamProfiles}
|
||||
doPasswordReset={this.doPasswordReset}
|
||||
/>);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='wrapper--fixed'>
|
||||
@@ -209,7 +233,7 @@ export default class UserList extends React.Component {
|
||||
defaultMessage='Users for {team} ({count})'
|
||||
values={{
|
||||
team: this.state.team.name,
|
||||
count: this.state.users.length
|
||||
count: this.state.total
|
||||
}}
|
||||
/>
|
||||
</h3>
|
||||
@@ -219,7 +243,20 @@ export default class UserList extends React.Component {
|
||||
role='form'
|
||||
>
|
||||
<div className='more-modal__list member-list-holder'>
|
||||
{memberList}
|
||||
<SearchableUserList
|
||||
users={usersToDisplay}
|
||||
usersPerPage={USERS_PER_PAGE}
|
||||
total={this.state.total}
|
||||
extraInfo={extraInfo}
|
||||
nextPage={this.nextPage}
|
||||
search={this.search}
|
||||
actions={[AdminTeamMembersDropdown]}
|
||||
actionProps={{
|
||||
refreshProfiles: this.getCurrentTeamProfiles,
|
||||
doPasswordReset: this.doPasswordReset
|
||||
}}
|
||||
actionUserProps={actionUserProps}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
<ResetPasswordModal
|
||||
|
||||
Ссылка в новой задаче
Block a user