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
Этот коммит содержится в:
Harrison Healey
2017-03-30 12:46:47 -04:00
коммит произвёл Corey Hulen
родитель 9a9729f22f
Коммит 689cac535e
74 изменённых файлов: 1445 добавлений и 1298 удалений

Просмотреть файл

@@ -6,7 +6,7 @@ import ReactDOM from 'react-dom';
import TeamStore from 'stores/team_store.jsx';
import Constants from 'utils/constants.jsx';
import {sortTeamsByDisplayName} from 'utils/utils.jsx';
import {sortTeamsByDisplayName} from 'utils/team_utils.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
import {FormattedMessage} from 'react-intl';

Просмотреть файл

@@ -112,7 +112,9 @@ export default class AdminSettings extends React.Component {
render() {
return (
<div className='wrapper--fixed'>
{this.renderTitle()}
<h3 className='admin-console-header'>
{this.renderTitle()}
</h3>
<form
className='form-horizontal'
role='form'

Просмотреть файл

@@ -3,18 +3,12 @@
import $ from 'jquery';
import React from 'react';
import {FormattedMessage} from 'react-intl';
import AdminStore from 'stores/admin_store.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import * as Utils from 'utils/utils.jsx';
import AdminSidebarHeader from './admin_sidebar_header.jsx';
import AdminSidebarTeam from './admin_sidebar_team.jsx';
import {FormattedMessage} from 'react-intl';
import {browserHistory} from 'react-router/es6';
import {OverlayTrigger, Tooltip} from 'react-bootstrap';
import SelectTeamModal from './select_team_modal.jsx';
import AdminSidebarCategory from './admin_sidebar_category.jsx';
import AdminSidebarHeader from './admin_sidebar_header.jsx';
import AdminSidebarSection from './admin_sidebar_section.jsx';
export default class AdminSidebar extends React.Component {
@@ -27,31 +21,15 @@ export default class AdminSidebar extends React.Component {
constructor(props) {
super(props);
this.handleAllTeamsChange = this.handleAllTeamsChange.bind(this);
this.removeTeam = this.removeTeam.bind(this);
this.showTeamSelect = this.showTeamSelect.bind(this);
this.teamSelectedModal = this.teamSelectedModal.bind(this);
this.teamSelectedModalDismissed = this.teamSelectedModalDismissed.bind(this);
this.updateTitle = this.updateTitle.bind(this);
this.renderAddTeamButton = this.renderAddTeamButton.bind(this);
this.renderTeams = this.renderTeams.bind(this);
this.state = {
teams: AdminStore.getAllTeams(),
selectedTeams: AdminStore.getSelectedTeams(),
showSelectModal: false
};
}
componentDidMount() {
AdminStore.addAllTeamsChangeListener(this.handleAllTeamsChange);
AsyncClient.getAllTeams();
this.updateTitle();
if (!Utils.isMobile()) {
$('.admin-sidebar .nav-pills__container').perfectScrollbar();
}
}
componentDidUpdate() {
@@ -60,51 +38,6 @@ export default class AdminSidebar extends React.Component {
}
}
componentWillUnmount() {
AdminStore.removeAllTeamsChangeListener(this.handleAllTeamsChange);
}
handleAllTeamsChange() {
this.setState({
teams: AdminStore.getAllTeams(),
selectedTeams: AdminStore.getSelectedTeams()
});
}
removeTeam(team) {
const selectedTeams = Object.assign({}, this.state.selectedTeams);
Reflect.deleteProperty(selectedTeams, team.id);
AdminStore.saveSelectedTeams(selectedTeams);
this.handleAllTeamsChange();
if (this.context.router.isActive('/admin_console/team/' + team.id)) {
browserHistory.push('/admin_console');
}
}
showTeamSelect(e) {
e.preventDefault();
this.setState({showSelectModal: true});
}
teamSelectedModal(teamId) {
this.setState({
showSelectModal: false
});
const selectedTeams = Object.assign({}, this.state.selectedTeams);
selectedTeams[teamId] = true;
AdminStore.saveSelectedTeams(selectedTeams);
this.handleAllTeamsChange();
}
teamSelectedModalDismissed() {
this.setState({showSelectModal: false});
}
updateTitle() {
let currentSiteName = '';
if (global.window.mm_config.SiteName != null) {
@@ -114,79 +47,6 @@ export default class AdminSidebar extends React.Component {
document.title = Utils.localizeMessage('sidebar_right_menu.console', 'System Console') + ' - ' + currentSiteName;
}
renderAddTeamButton() {
const addTeamTooltip = (
<Tooltip id='add-team-tooltip'>
<FormattedMessage
id='admin.sidebar.addTeamSidebar'
defaultMessage='Add team from sidebar menu'
/>
</Tooltip>
);
return (
<span className='menu-icon--right'>
<OverlayTrigger
delayShow={1000}
placement='top'
overlay={addTeamTooltip}
>
<a
href='#'
onClick={this.showTeamSelect}
>
<i
className='fa fa-plus'
/>
</a>
</OverlayTrigger>
</span>
);
}
renderTeams() {
const teams = [];
let teamsArray = [];
Reflect.ownKeys(this.state.selectedTeams).forEach((key) => {
if (this.state.teams[key]) {
teamsArray.push(this.state.teams[key]);
}
});
teamsArray = teamsArray.sort(Utils.sortTeamsByDisplayName);
for (let i = 0; i < teamsArray.length; i++) {
const team = teamsArray[i];
teams.push(
<AdminSidebarTeam
key={team.id}
team={team}
onRemoveTeam={this.removeTeam}
/>
);
}
return (
<AdminSidebarCategory
parentLink='/admin_console'
icon='fa-user'
title={
<FormattedMessage
id='admin.sidebar.teams'
defaultMessage='TEAMS ({count, number})'
values={{
count: Object.keys(this.state.teams).length
}}
/>
}
action={this.renderAddTeamButton()}
>
{teams}
</AdminSidebarCategory>
);
}
render() {
let oauthSettings = null;
let ldapSettings = null;
@@ -421,6 +281,24 @@ export default class AdminSidebar extends React.Component {
/>
}
/>
<AdminSidebarSection
name='team_analytics'
title={
<FormattedMessage
id='admin.sidebar.statistics'
defaultMessage='Team Statistics'
/>
}
/>
<AdminSidebarSection
name='users'
title={
<FormattedMessage
id='admin.sidebar.users'
defaultMessage='Users'
/>
}
/>
<AdminSidebarSection
name='logs'
title={
@@ -760,16 +638,9 @@ export default class AdminSidebar extends React.Component {
{metricsSettings}
</AdminSidebarSection>
</AdminSidebarCategory>
{this.renderTeams()}
{otherCategory}
</ul>
</div>
<SelectTeamModal
teams={this.state.teams}
show={this.state.showSelectModal}
onModalSubmit={this.teamSelectedModal}
onModalDismissed={this.teamSelectedModalDismissed}
/>
</div>
);
}

Просмотреть файл

@@ -1,87 +0,0 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import {FormattedMessage} from 'react-intl';
import {OverlayTrigger, Tooltip} from 'react-bootstrap';
import AdminSidebarSection from './admin_sidebar_section.jsx';
export default class AdminSidebarTeam extends React.Component {
static get propTypes() {
return {
team: React.PropTypes.object.isRequired,
onRemoveTeam: React.PropTypes.func.isRequired,
parentLink: React.PropTypes.string
};
}
constructor(props) {
super(props);
this.handleRemoveTeam = this.handleRemoveTeam.bind(this);
}
handleRemoveTeam(e) {
e.preventDefault();
this.props.onRemoveTeam(this.props.team);
}
render() {
const team = this.props.team;
const removeTeamTooltip = (
<Tooltip id='remove-team-tooltip'>
<FormattedMessage
id='admin.sidebar.rmTeamSidebar'
defaultMessage='Remove team from sidebar menu'
/>
</Tooltip>
);
const removeTeamButton = (
<OverlayTrigger
delayShow={1000}
placement='top'
overlay={removeTeamTooltip}
>
<span
className='menu-icon--right menu__close'
onClick={this.handleRemoveTeam}
>
{'×'}
</span>
</OverlayTrigger>
);
return (
<AdminSidebarSection
key={team.id}
name={'team/' + team.id}
parentLink={this.props.parentLink}
title={team.display_name}
action={removeTeamButton}
>
<AdminSidebarSection
name='users'
title={
<FormattedMessage
id='admin.sidebar.users'
defaultMessage='- Users'
/>
}
/>
<AdminSidebarSection
name='analytics'
title={
<FormattedMessage
id='admin.sidebar.statistics'
defaultMessage='- Team Statistics'
/>
}
/>
</AdminSidebarSection>
);
}
}

Просмотреть файл

@@ -76,7 +76,7 @@ export default class Audits extends React.Component {
<ComplianceReports/>
<div className='panel audit-panel'>
<h3>
<h3 className='admin-console-header'>
<FormattedMessage
id='admin.audits.title'
defaultMessage='User Activity Logs'

Просмотреть файл

@@ -51,12 +51,10 @@ export default class ClusterSettings extends AdminSettings {
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.advance.cluster'
defaultMessage='High Availability (Beta)'
/>
</h3>
<FormattedMessage
id='admin.advance.cluster'
defaultMessage='High Availability (Beta)'
/>
);
}

Просмотреть файл

@@ -38,12 +38,10 @@ export default class ComplianceSettings extends AdminSettings {
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.compliance.title'
defaultMessage='Compliance Settings'
/>
</h3>
<FormattedMessage
id='admin.compliance.title'
defaultMessage='Compliance Settings'
/>
);
}

Просмотреть файл

@@ -64,12 +64,10 @@ export default class ConfigurationSettings extends AdminSettings {
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.general.configuration'
defaultMessage='Configuration'
/>
</h3>
<FormattedMessage
id='admin.general.configuration'
defaultMessage='Configuration'
/>
);
}

Просмотреть файл

@@ -36,12 +36,10 @@ export default class ConnectionSettings extends AdminSettings {
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.security.connection'
defaultMessage='Connections'
/>
</h3>
<FormattedMessage
id='admin.security.connection'
defaultMessage='Connections'
/>
);
}

Просмотреть файл

@@ -44,12 +44,10 @@ export default class CustomBrandSettings extends AdminSettings {
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.customization.customBrand'
defaultMessage='Custom Branding'
/>
</h3>
<FormattedMessage
id='admin.customization.customBrand'
defaultMessage='Custom Branding'
/>
);
}
@@ -155,4 +153,4 @@ export default class CustomBrandSettings extends AdminSettings {
</SettingsGroup>
);
}
}
}

Просмотреть файл

@@ -39,12 +39,10 @@ export default class CustomEmojiSettings extends AdminSettings {
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.customization.customEmoji'
defaultMessage='Custom Emoji'
/>
</h3>
<FormattedMessage
id='admin.customization.customEmoji'
defaultMessage='Custom Emoji'
/>
);
}

Просмотреть файл

@@ -43,12 +43,10 @@ export default class WebhookSettings extends AdminSettings {
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.integrations.custom'
defaultMessage='Custom Integrations'
/>
</h3>
<FormattedMessage
id='admin.integrations.custom'
defaultMessage='Custom Integrations'
/>
);
}

Просмотреть файл

@@ -46,12 +46,10 @@ export default class DatabaseSettings extends AdminSettings {
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.database.title'
defaultMessage='Database Settings'
/>
</h3>
<FormattedMessage
id='admin.database.title'
defaultMessage='Database Settings'
/>
);
}

Просмотреть файл

@@ -33,12 +33,10 @@ export default class DeveloperSettings extends AdminSettings {
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.developer.title'
defaultMessage='Developer Settings'
/>
</h3>
<FormattedMessage
id='admin.developer.title'
defaultMessage='Developer Settings'
/>
);
}

Просмотреть файл

@@ -35,12 +35,10 @@ export default class EmailAuthenticationSettings extends AdminSettings {
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.authentication.email'
defaultMessage='Email'
/>
</h3>
<FormattedMessage
id='admin.authentication.email'
defaultMessage='Email'
/>
);
}

Просмотреть файл

@@ -56,12 +56,10 @@ export default class EmailSettings extends AdminSettings {
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.notifications.email'
defaultMessage='Email'
/>
</h3>
<FormattedMessage
id='admin.notifications.email'
defaultMessage='Email'
/>
);
}

Просмотреть файл

@@ -32,12 +32,10 @@ export default class ExternalServiceSettings extends AdminSettings {
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.integrations.external'
defaultMessage='External Services'
/>
</h3>
<FormattedMessage
id='admin.integrations.external'
defaultMessage='External Services'
/>
);
}

Просмотреть файл

@@ -44,12 +44,10 @@ export default class GitLabSettings extends AdminSettings {
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.authentication.gitlab'
defaultMessage='GitLab'
/>
</h3>
<FormattedMessage
id='admin.authentication.gitlab'
defaultMessage='GitLab'
/>
);
}

Просмотреть файл

@@ -43,12 +43,10 @@ export default class ImageSettings extends AdminSettings {
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.files.images'
defaultMessage='Images'
/>
</h3>
<FormattedMessage
id='admin.files.images'
defaultMessage='Images'
/>
);
}

Просмотреть файл

@@ -76,12 +76,10 @@ export default class LdapSettings extends AdminSettings {
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.authentication.ldap'
defaultMessage='AD/LDAP'
/>
</h3>
<FormattedMessage
id='admin.authentication.ldap'
defaultMessage='AD/LDAP'
/>
);
}

Просмотреть файл

@@ -41,12 +41,10 @@ export default class LegalAndSupportSettings extends AdminSettings {
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.customization.support'
defaultMessage='Legal and Support'
/>
</h3>
<FormattedMessage
id='admin.customization.support'
defaultMessage='Legal and Support'
/>
);
}

Просмотреть файл

@@ -221,7 +221,7 @@ class LicenseSettings extends React.Component {
return (
<div className='wrapper--fixed'>
<h3>
<h3 className='admin-console-header'>
<FormattedMessage
id='admin.license.title'
defaultMessage='Edition and License'

Просмотреть файл

@@ -31,12 +31,10 @@ export default class LinkPreviewsSettings extends AdminSettings {
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.customization.linkPreviews'
defaultMessage='Link Previews'
/>
</h3>
<FormattedMessage
id='admin.customization.linkPreviews'
defaultMessage='Link Previews'
/>
);
}

Просмотреть файл

@@ -52,12 +52,10 @@ export default class LocalizationSettings extends AdminSettings {
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.general.localization'
defaultMessage='Localization'
/>
</h3>
<FormattedMessage
id='admin.general.localization'
defaultMessage='Localization'
/>
);
}

Просмотреть файл

@@ -49,12 +49,10 @@ export default class LogSettings extends AdminSettings {
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.general.log'
defaultMessage='Logging'
/>
</h3>
<FormattedMessage
id='admin.general.log'
defaultMessage='Logging'
/>
);
}

Просмотреть файл

@@ -85,7 +85,7 @@ export default class Logs extends React.Component {
return (
<div className='panel'>
<h3>
<h3 className='admin-console-header'>
<FormattedMessage
id='admin.logs.title'
defaultMessage='Server Logs'

Просмотреть файл

@@ -38,12 +38,10 @@ export default class MetricsSettings extends AdminSettings {
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.advance.metrics'
defaultMessage='Performance Monitoring'
/>
</h3>
<FormattedMessage
id='admin.advance.metrics'
defaultMessage='Performance Monitoring'
/>
);
}

Просмотреть файл

@@ -38,12 +38,10 @@ export default class MfaSettings extends AdminSettings {
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.mfa.title'
defaultMessage='Multi-factor Authentication'
/>
</h3>
<FormattedMessage
id='admin.mfa.title'
defaultMessage='Multi-factor Authentication'
/>
);
}

Просмотреть файл

@@ -35,12 +35,10 @@ export default class NativeAppLinkSettings extends AdminSettings {
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.customization.nativeAppLinks'
defaultMessage='Mattermost App Links'
/>
</h3>
<FormattedMessage
id='admin.customization.nativeAppLinks'
defaultMessage='Mattermost App Links'
/>
);
}

Просмотреть файл

@@ -111,12 +111,10 @@ export default class OAuthSettings extends AdminSettings {
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.authentication.oauth'
defaultMessage='OAuth 2.0'
/>
</h3>
<FormattedMessage
id='admin.authentication.oauth'
defaultMessage='OAuth 2.0'
/>
);
}

Просмотреть файл

@@ -138,12 +138,10 @@ export default class PasswordSettings extends AdminSettings {
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.security.password'
defaultMessage='Password'
/>
</h3>
<FormattedMessage
id='admin.security.password'
defaultMessage='Password'
/>
);
}

Просмотреть файл

@@ -55,12 +55,10 @@ export default class PolicySettings extends AdminSettings {
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.general.policy'
defaultMessage='Policy'
/>
</h3>
<FormattedMessage
id='admin.general.policy'
defaultMessage='Policy'
/>
);
}

Просмотреть файл

@@ -33,12 +33,10 @@ export default class PrivacySettings extends AdminSettings {
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.general.privacy'
defaultMessage='Privacy'
/>
</h3>
<FormattedMessage
id='admin.general.privacy'
defaultMessage='Privacy'
/>
);
}

Просмотреть файл

@@ -34,12 +34,10 @@ export default class PublicLinkSettings extends AdminSettings {
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.security.public_links'
defaultMessage='Public Links'
/>
</h3>
<FormattedMessage
id='admin.security.public_links'
defaultMessage='Public Links'
/>
);
}

Просмотреть файл

@@ -100,12 +100,10 @@ export default class PushSettings extends AdminSettings {
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.notifications.title'
defaultMessage='Notification Settings'
/>
</h3>
<FormattedMessage
id='admin.notifications.title'
defaultMessage='Notification Settings'
/>
);
}

Просмотреть файл

@@ -44,12 +44,10 @@ export default class RateSettings extends AdminSettings {
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.rate.title'
defaultMessage='Rate Limit Settings'
/>
</h3>
<FormattedMessage
id='admin.rate.title'
defaultMessage='Rate Limit Settings'
/>
);
}

Просмотреть файл

@@ -4,13 +4,24 @@
import * as Utils from 'utils/utils.jsx';
import {Modal} from 'react-bootstrap';
import {injectIntl, intlShape, FormattedMessage} from 'react-intl';
import {FormattedMessage} from 'react-intl';
import {adminResetPassword} from 'actions/admin_actions.jsx';
import React from 'react';
class ResetPasswordModal extends React.Component {
export default class ResetPasswordModal extends React.Component {
static propTypes = {
user: React.PropTypes.object,
show: React.PropTypes.bool.isRequired,
onModalSubmit: React.PropTypes.func,
onModalDismissed: React.PropTypes.func
};
static defaultProps = {
show: false
};
constructor(props) {
super(props);
@@ -150,18 +161,3 @@ class ResetPasswordModal extends React.Component {
);
}
}
ResetPasswordModal.defaultProps = {
show: false
};
ResetPasswordModal.propTypes = {
intl: intlShape.isRequired,
user: React.PropTypes.object,
team: React.PropTypes.object,
show: React.PropTypes.bool.isRequired,
onModalSubmit: React.PropTypes.func,
onModalDismissed: React.PropTypes.func
};
export default injectIntl(ResetPasswordModal);

Просмотреть файл

@@ -130,12 +130,10 @@ export default class SamlSettings extends AdminSettings {
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.authentication.saml'
defaultMessage='SAML'
/>
</h3>
<FormattedMessage
id='admin.authentication.saml'
defaultMessage='SAML'
/>
);
}

Просмотреть файл

@@ -1,120 +0,0 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import ReactDOM from 'react-dom';
import {FormattedMessage} from 'react-intl';
import {Modal} from 'react-bootstrap';
import React from 'react';
import {sortTeamsByDisplayName} from 'utils/utils.jsx';
export default class SelectTeamModal extends React.Component {
constructor(props) {
super(props);
this.doSubmit = this.doSubmit.bind(this);
this.doCancel = this.doCancel.bind(this);
}
doSubmit(e) {
e.preventDefault();
this.props.onModalSubmit(ReactDOM.findDOMNode(this.refs.team).value);
}
doCancel() {
this.props.onModalDismissed();
}
render() {
if (this.props.teams == null) {
return <div/>;
}
const options = [];
let teamsArray = [];
Reflect.ownKeys(this.props.teams).forEach((key) => {
teamsArray.push(this.props.teams[key]);
});
teamsArray = teamsArray.sort(sortTeamsByDisplayName);
for (let i = 0; i < teamsArray.length; i++) {
const team = teamsArray[i];
options.push(
<option
key={'opt_' + team.id}
value={team.id}
>
{team.display_name}
</option>
);
}
return (
<Modal
show={this.props.show}
onHide={this.doCancel}
>
<Modal.Header closeButton={true}>
<Modal.Title>
<FormattedMessage
id='admin.select_team.selectTeam'
defaultMessage='Select Team'
/>
</Modal.Title>
</Modal.Header>
<form
role='form'
className='form-horizontal'
>
<Modal.Body>
<div className='form-group'>
<div className='col-sm-12'>
<select
ref='team'
size='10'
className='form-control'
>
{options}
</select>
</div>
</div>
</Modal.Body>
<Modal.Footer>
<button
type='button'
className='btn btn-default'
onClick={this.doCancel}
>
<FormattedMessage
id='admin.select_team.close'
defaultMessage='Close'
/>
</button>
<button
onClick={this.doSubmit}
type='submit'
className='btn btn-primary'
tabIndex='2'
>
<FormattedMessage
id='admin.select_team.select'
defaultMessage='Select'
/>
</button>
</Modal.Footer>
</form>
</Modal>
);
}
}
SelectTeamModal.defaultProps = {
show: false
};
SelectTeamModal.propTypes = {
teams: React.PropTypes.object,
show: React.PropTypes.bool.isRequired,
onModalSubmit: React.PropTypes.func,
onModalDismissed: React.PropTypes.func
};

Просмотреть файл

@@ -39,12 +39,10 @@ export default class SessionSettings extends AdminSettings {
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.security.session'
defaultMessage='Sessions'
/>
</h3>
<FormattedMessage
id='admin.security.session'
defaultMessage='Sessions'
/>
);
}

Просмотреть файл

@@ -36,12 +36,10 @@ export default class SignupSettings extends AdminSettings {
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.security.signup'
defaultMessage='Signup'
/>
</h3>
<FormattedMessage
id='admin.security.signup'
defaultMessage='Signup'
/>
);
}

Просмотреть файл

@@ -52,12 +52,10 @@ export default class StorageSettings extends AdminSettings {
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.files.storage'
defaultMessage='Storage'
/>
</h3>
<FormattedMessage
id='admin.files.storage'
defaultMessage='Storage'
/>
);
}

Просмотреть файл

@@ -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>
);
}
}

Просмотреть файл

@@ -1,38 +1,38 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import ConfirmModal from '../confirm_modal.jsx';
import ConfirmModal from 'components/confirm_modal.jsx';
import UserStore from 'stores/user_store.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 {updateTeamMemberRoles, removeUserFromTeam} from 'actions/team_actions.jsx';
import {adminResetMfa} from 'actions/admin_actions.jsx';
import {FormattedMessage} from 'react-intl';
import React from 'react';
export default class AdminTeamMembersDropdown extends React.Component {
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.handleRemoveFromTeam = this.handleRemoveFromTeam.bind(this);
this.handleMakeActive = this.handleMakeActive.bind(this);
this.handleMakeNotActive = this.handleMakeNotActive.bind(this);
this.handleMakeTeamAdmin = this.handleMakeTeamAdmin.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.doMakeMember = this.doMakeMember.bind(this);
this.doMakeTeamAdmin = this.doMakeTeamAdmin.bind(this);
this.state = {
serverError: null,
@@ -51,16 +51,6 @@ export default class AdminTeamMembersDropdown extends React.Component {
this.setState({serverError: err.message});
}
);
updateTeamMemberRoles(
this.props.teamMember.team_id,
this.props.user.id,
'team_user',
null,
(err) => {
this.setState({serverError: err.message});
}
);
}
handleMakeMember(e) {
@@ -73,17 +63,6 @@ export default class AdminTeamMembersDropdown extends React.Component {
}
}
handleRemoveFromTeam() {
removeUserFromTeam(
this.props.teamMember.team_id,
this.props.user.id,
null,
(err) => {
this.setState({serverError: err.message});
}
);
}
handleMakeActive(e) {
e.preventDefault();
updateActive(this.props.user.id, true, null,
@@ -102,28 +81,6 @@ export default class AdminTeamMembersDropdown extends React.Component {
);
}
doMakeTeamAdmin() {
updateTeamMemberRoles(
this.props.teamMember.team_id,
this.props.user.id,
'team_user team_admin',
null,
(err) => {
this.setState({serverError: err.message});
}
);
}
handleMakeTeamAdmin(e) {
e.preventDefault();
const me = UserStore.getCurrentUser();
if (this.props.user.id === me.id && me.roles.includes('system_admin')) {
this.handleDemoteSystemAdmin(this.props.user, 'teamadmin');
} else {
this.doMakeTeamAdmin();
}
}
handleMakeSystemAdmin(e) {
e.preventDefault();
@@ -174,8 +131,6 @@ export default class AdminTeamMembersDropdown extends React.Component {
handleDemoteSubmit() {
if (this.state.role === 'member') {
this.doMakeMember();
} else {
this.doMakeTeamAdmin();
}
const teamUrl = TeamStore.getCurrentTeamUrl();
@@ -197,9 +152,8 @@ export default class AdminTeamMembersDropdown extends React.Component {
);
}
const teamMember = this.props.teamMember;
const user = this.props.user;
if (!user || !teamMember) {
if (!user) {
return <div/>;
}
let currentRoles = (
@@ -209,15 +163,6 @@ export default class AdminTeamMembersDropdown extends React.Component {
/>
);
if (teamMember.roles.length > 0 && Utils.isAdmin(teamMember.roles)) {
currentRoles = (
<FormattedMessage
id='team_members_dropdown.teamAdmin'
defaultMessage='Team Admin'
/>
);
}
if (user.roles.length > 0 && Utils.isSystemAdmin(user.roles)) {
currentRoles = (
<FormattedMessage
@@ -228,8 +173,7 @@ export default class AdminTeamMembersDropdown extends React.Component {
}
const me = UserStore.getCurrentUser();
let showMakeMember = Utils.isAdmin(teamMember.roles) || Utils.isSystemAdmin(user.roles);
let showMakeAdmin = !Utils.isAdmin(teamMember.roles) && !Utils.isSystemAdmin(user.roles);
let showMakeMember = Utils.isSystemAdmin(user.roles);
let showMakeSystemAdmin = !Utils.isSystemAdmin(user.roles);
let showMakeActive = false;
let showMakeNotActive = !Utils.isSystemAdmin(user.roles);
@@ -244,7 +188,6 @@ export default class AdminTeamMembersDropdown extends React.Component {
/>
);
showMakeMember = false;
showMakeAdmin = false;
showMakeSystemAdmin = false;
showMakeActive = true;
showMakeNotActive = false;
@@ -273,24 +216,6 @@ export default class AdminTeamMembersDropdown extends React.Component {
);
}
let makeAdmin = null;
if (showMakeAdmin) {
makeAdmin = (
<li role='presentation'>
<a
role='menuitem'
href='#'
onClick={this.handleMakeTeamAdmin}
>
<FormattedMessage
id='admin.user_item.makeTeamAdmin'
defaultMessage='Make Team Admin'
/>
</a>
</li>
);
}
let makeMember = null;
if (showMakeMember) {
makeMember = (
@@ -309,24 +234,6 @@ export default class AdminTeamMembersDropdown extends React.Component {
);
}
let removeFromTeam = null;
if (this.props.user.id !== me.id) {
removeFromTeam = (
<li role='presentation'>
<a
role='menuitem'
href='#'
onClick={this.handleRemoveFromTeam}
>
<FormattedMessage
id='team_members_dropdown.leave_team'
defaultMessage='Remove From Team'
/>
</a>
</li>
);
}
let menuClass = '';
if (disableActivationToggle) {
menuClass = 'disabled';
@@ -493,8 +400,6 @@ export default class AdminTeamMembersDropdown extends React.Component {
className='dropdown-menu member-menu'
role='menu'
>
{removeFromTeam}
{makeAdmin}
{makeMember}
{makeActive}
{makeNotActive}
@@ -508,9 +413,3 @@ export default class AdminTeamMembersDropdown extends React.Component {
);
}
}
AdminTeamMembersDropdown.propTypes = {
user: React.PropTypes.object.isRequired,
teamMember: React.PropTypes.object.isRequired,
doPasswordReset: React.PropTypes.func.isRequired
};

Просмотреть файл

@@ -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>
);
}
}

Просмотреть файл

@@ -1,298 +0,0 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
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 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, getUser} from 'utils/async_client.jsx';
import {Constants, UserSearchOptions} 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() {
return {
params: React.PropTypes.object.isRequired
};
}
constructor(props) {
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.doPasswordReset = this.doPasswordReset.bind(this);
this.doPasswordResetDismiss = this.doPasswordResetDismiss.bind(this);
this.doPasswordResetSubmit = this.doPasswordResetSubmit.bind(this);
this.nextPage = this.nextPage.bind(this);
this.search = this.search.bind(this);
this.loadComplete = this.loadComplete.bind(this);
this.searchTimeoutId = 0;
const stats = TeamStore.getStats(this.props.params.team);
this.state = {
team: AdminStore.getTeam(this.props.params.team),
users: [],
teamMembers: TeamStore.getMembersInTeam(this.props.params.team),
total: stats.total_member_count,
serverError: null,
showPasswordModal: false,
loading: true,
user: null
};
}
componentDidMount() {
AdminStore.addAllTeamsChangeListener(this.onAllTeamsChange);
UserStore.addChangeListener(this.onUsersChange);
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),
users: [],
teamMembers: TeamStore.getMembersInTeam(nextProps.params.team),
total: stats.total_member_count,
serverError: null,
showPasswordModal: false,
loading: true,
user: null
});
loadProfilesAndTeamMembers(0, Constants.PROFILE_CHUNK_SIZE, nextProps.params.team, this.loadComplete);
getTeamStats(nextProps.params.team);
}
}
componentWillUnmount() {
AdminStore.removeAllTeamsChangeListener(this.onAllTeamsChange);
UserStore.removeChangeListener(this.onUsersChange);
UserStore.removeInTeamChangeListener(this.onUsersChange);
TeamStore.removeChangeListener(this.onTeamChange);
TeamStore.removeStatsChangeListener(this.onStatsChange);
}
loadComplete() {
this.setState({loading: false});
}
onAllTeamsChange() {
this.setState({
team: AdminStore.getTeam(this.props.params.team)
});
}
onStatsChange() {
const stats = TeamStore.getStats(this.props.params.team);
this.setState({total: stats.total_member_count});
}
onUsersChange() {
this.setState({users: UserStore.getProfileListInTeam(this.props.params.team)});
}
onTeamChange() {
this.setState({teamMembers: TeamStore.getMembersInTeam(this.props.params.team)});
}
nextPage(page) {
loadProfilesAndTeamMembers((page + 1) * USERS_PER_PAGE, USERS_PER_PAGE, this.props.params.team);
}
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
});
}
search(term) {
clearTimeout(this.searchTimeoutId);
if (term === '') {
this.setState({search: false, users: UserStore.getProfileListInTeam(this.props.params.team)});
this.searchTimeoutId = '';
return;
}
const options = {};
options[UserSearchOptions.ALLOW_INACTIVE] = true;
const searchTimeoutId = setTimeout(
() => {
searchUsers(
term,
this.props.params.team,
options,
(users) => {
if (searchTimeoutId !== this.searchTimeoutId) {
return;
}
this.setState({loading: true, search: true, users});
loadTeamMembersForProfilesList(users, this.props.params.team, this.loadComplete);
}
);
},
Constants.SEARCH_TIMEOUT_MILLISECONDS
);
this.searchTimeoutId = searchTimeoutId;
}
render() {
if (!this.state.team) {
return null;
}
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';
let usersToDisplay;
if (this.state.loading) {
usersToDisplay = null;
} else {
usersToDisplay = [];
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
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'
/>
);
}
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'
/>
);
}
}
extraInfo[user.id] = info;
}
}
}
return (
<div className='wrapper--fixed'>
<h3>
<FormattedMessage
id='admin.userList.title2'
defaultMessage='Users for {team} ({count})'
values={{
team: this.state.team.name,
count: this.state.total
}}
/>
</h3>
<FormError error={this.state.serverError}/>
<div className='more-modal__list member-list-holder'>
<SearchableUserList
users={usersToDisplay}
usersPerPage={USERS_PER_PAGE}
total={this.state.total}
extraInfo={extraInfo}
nextPage={this.nextPage}
search={this.search}
actions={[AdminTeamMembersDropdown]}
actionProps={{
doPasswordReset: this.doPasswordReset
}}
actionUserProps={actionUserProps}
/>
</div>
<ResetPasswordModal
user={this.state.user}
show={this.state.showPasswordModal}
team={this.state.team}
onModalSubmit={this.doPasswordResetSubmit}
onModalDismissed={this.doPasswordResetDismiss}
/>
</div>
);
}
}

Просмотреть файл

@@ -51,12 +51,10 @@ export default class UsersAndTeamsSettings extends AdminSettings {
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.general.usersAndTeams'
defaultMessage='Users and Teams'
/>
</h3>
<FormattedMessage
id='admin.general.usersAndTeams'
defaultMessage='Users and Teams'
/>
);
}

Просмотреть файл

@@ -50,12 +50,10 @@ export default class WebrtcSettings extends AdminSettings {
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.integrations.webrtc'
defaultMessage='Mattermost WebRTC (Beta)'
/>
</h3>
<FormattedMessage
id='admin.integrations.webrtc'
defaultMessage='Mattermost WebRTC (Beta)'
/>
);
}