diff options
Diffstat (limited to 'webapp/components/admin_console')
48 files changed, 796 insertions, 1001 deletions
diff --git a/webapp/components/admin_console/admin_navbar_dropdown.jsx b/webapp/components/admin_console/admin_navbar_dropdown.jsx index a1ec2885b..b4fd889bc 100644 --- a/webapp/components/admin_console/admin_navbar_dropdown.jsx +++ b/webapp/components/admin_console/admin_navbar_dropdown.jsx @@ -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'; diff --git a/webapp/components/admin_console/admin_settings.jsx b/webapp/components/admin_console/admin_settings.jsx index b9883d7d8..30b9cbd11 100644 --- a/webapp/components/admin_console/admin_settings.jsx +++ b/webapp/components/admin_console/admin_settings.jsx @@ -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' diff --git a/webapp/components/admin_console/admin_sidebar.jsx b/webapp/components/admin_console/admin_sidebar.jsx index 2f299bdeb..73ec436f4 100644 --- a/webapp/components/admin_console/admin_sidebar.jsx +++ b/webapp/components/admin_console/admin_sidebar.jsx @@ -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,84 +21,23 @@ 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(); - } - componentDidUpdate() { if (!Utils.isMobile()) { $('.admin-sidebar .nav-pills__container').perfectScrollbar(); } } - 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'); + componentDidUpdate() { + if (!Utils.isMobile()) { + $('.admin-sidebar .nav-pills__container').perfectScrollbar(); } } - 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; @@ -422,6 +282,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={ <FormattedMessage @@ -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> ); } diff --git a/webapp/components/admin_console/admin_sidebar_team.jsx b/webapp/components/admin_console/admin_sidebar_team.jsx deleted file mode 100644 index b1df92491..000000000 --- a/webapp/components/admin_console/admin_sidebar_team.jsx +++ /dev/null @@ -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> - ); - } -} diff --git a/webapp/components/admin_console/audits.jsx b/webapp/components/admin_console/audits.jsx index 5e0e03607..47a7e8d89 100644 --- a/webapp/components/admin_console/audits.jsx +++ b/webapp/components/admin_console/audits.jsx @@ -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' diff --git a/webapp/components/admin_console/cluster_settings.jsx b/webapp/components/admin_console/cluster_settings.jsx index bbd135e50..31634d0bd 100644 --- a/webapp/components/admin_console/cluster_settings.jsx +++ b/webapp/components/admin_console/cluster_settings.jsx @@ -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)' + /> ); } diff --git a/webapp/components/admin_console/compliance_settings.jsx b/webapp/components/admin_console/compliance_settings.jsx index f9dc61c1d..e2df967d5 100644 --- a/webapp/components/admin_console/compliance_settings.jsx +++ b/webapp/components/admin_console/compliance_settings.jsx @@ -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' + /> ); } diff --git a/webapp/components/admin_console/configuration_settings.jsx b/webapp/components/admin_console/configuration_settings.jsx index a5e5abe87..ec5606fa1 100644 --- a/webapp/components/admin_console/configuration_settings.jsx +++ b/webapp/components/admin_console/configuration_settings.jsx @@ -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' + /> ); } diff --git a/webapp/components/admin_console/connection_settings.jsx b/webapp/components/admin_console/connection_settings.jsx index 8e030b207..b35f3acf7 100644 --- a/webapp/components/admin_console/connection_settings.jsx +++ b/webapp/components/admin_console/connection_settings.jsx @@ -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' + /> ); } diff --git a/webapp/components/admin_console/custom_brand_settings.jsx b/webapp/components/admin_console/custom_brand_settings.jsx index ee8e464da..48954ef78 100644 --- a/webapp/components/admin_console/custom_brand_settings.jsx +++ b/webapp/components/admin_console/custom_brand_settings.jsx @@ -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> ); } -}
\ No newline at end of file +} diff --git a/webapp/components/admin_console/custom_emoji_settings.jsx b/webapp/components/admin_console/custom_emoji_settings.jsx index 90b70241d..c1457d7e9 100644 --- a/webapp/components/admin_console/custom_emoji_settings.jsx +++ b/webapp/components/admin_console/custom_emoji_settings.jsx @@ -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' + /> ); } diff --git a/webapp/components/admin_console/custom_integrations_settings.jsx b/webapp/components/admin_console/custom_integrations_settings.jsx index 6a4202d00..63015a061 100644 --- a/webapp/components/admin_console/custom_integrations_settings.jsx +++ b/webapp/components/admin_console/custom_integrations_settings.jsx @@ -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' + /> ); } diff --git a/webapp/components/admin_console/database_settings.jsx b/webapp/components/admin_console/database_settings.jsx index 2cd4929ec..84adae29c 100644 --- a/webapp/components/admin_console/database_settings.jsx +++ b/webapp/components/admin_console/database_settings.jsx @@ -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' + /> ); } diff --git a/webapp/components/admin_console/developer_settings.jsx b/webapp/components/admin_console/developer_settings.jsx index 119b92a5a..3bcc2a19b 100644 --- a/webapp/components/admin_console/developer_settings.jsx +++ b/webapp/components/admin_console/developer_settings.jsx @@ -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' + /> ); } diff --git a/webapp/components/admin_console/email_authentication_settings.jsx b/webapp/components/admin_console/email_authentication_settings.jsx index cb7ef3419..177f36d64 100644 --- a/webapp/components/admin_console/email_authentication_settings.jsx +++ b/webapp/components/admin_console/email_authentication_settings.jsx @@ -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' + /> ); } diff --git a/webapp/components/admin_console/email_settings.jsx b/webapp/components/admin_console/email_settings.jsx index 9dc02857b..6cf09f653 100644 --- a/webapp/components/admin_console/email_settings.jsx +++ b/webapp/components/admin_console/email_settings.jsx @@ -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' + /> ); } diff --git a/webapp/components/admin_console/external_service_settings.jsx b/webapp/components/admin_console/external_service_settings.jsx index 53fdbfb53..21fc6c106 100644 --- a/webapp/components/admin_console/external_service_settings.jsx +++ b/webapp/components/admin_console/external_service_settings.jsx @@ -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' + /> ); } diff --git a/webapp/components/admin_console/gitlab_settings.jsx b/webapp/components/admin_console/gitlab_settings.jsx index ec3849b26..6ba2245b8 100644 --- a/webapp/components/admin_console/gitlab_settings.jsx +++ b/webapp/components/admin_console/gitlab_settings.jsx @@ -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' + /> ); } diff --git a/webapp/components/admin_console/image_settings.jsx b/webapp/components/admin_console/image_settings.jsx index 8e8e2868e..0249e3979 100644 --- a/webapp/components/admin_console/image_settings.jsx +++ b/webapp/components/admin_console/image_settings.jsx @@ -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' + /> ); } diff --git a/webapp/components/admin_console/ldap_settings.jsx b/webapp/components/admin_console/ldap_settings.jsx index b774d34f3..50883ac22 100644 --- a/webapp/components/admin_console/ldap_settings.jsx +++ b/webapp/components/admin_console/ldap_settings.jsx @@ -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' + /> ); } diff --git a/webapp/components/admin_console/legal_and_support_settings.jsx b/webapp/components/admin_console/legal_and_support_settings.jsx index 3108dd60b..b0f85f43d 100644 --- a/webapp/components/admin_console/legal_and_support_settings.jsx +++ b/webapp/components/admin_console/legal_and_support_settings.jsx @@ -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' + /> ); } diff --git a/webapp/components/admin_console/license_settings.jsx b/webapp/components/admin_console/license_settings.jsx index 6c14394b7..7e77f44b6 100644 --- a/webapp/components/admin_console/license_settings.jsx +++ b/webapp/components/admin_console/license_settings.jsx @@ -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' diff --git a/webapp/components/admin_console/link_previews_settings.jsx b/webapp/components/admin_console/link_previews_settings.jsx index aea8a56f1..f223ccc3e 100644 --- a/webapp/components/admin_console/link_previews_settings.jsx +++ b/webapp/components/admin_console/link_previews_settings.jsx @@ -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' + /> ); } diff --git a/webapp/components/admin_console/localization_settings.jsx b/webapp/components/admin_console/localization_settings.jsx index 7868ca8eb..b3e8a7b65 100644 --- a/webapp/components/admin_console/localization_settings.jsx +++ b/webapp/components/admin_console/localization_settings.jsx @@ -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' + /> ); } diff --git a/webapp/components/admin_console/log_settings.jsx b/webapp/components/admin_console/log_settings.jsx index 135369942..69dd4eda7 100644 --- a/webapp/components/admin_console/log_settings.jsx +++ b/webapp/components/admin_console/log_settings.jsx @@ -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' + /> ); } diff --git a/webapp/components/admin_console/logs.jsx b/webapp/components/admin_console/logs.jsx index 5846c91db..d2464b37f 100644 --- a/webapp/components/admin_console/logs.jsx +++ b/webapp/components/admin_console/logs.jsx @@ -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' diff --git a/webapp/components/admin_console/metrics_settings.jsx b/webapp/components/admin_console/metrics_settings.jsx index 29fa028ec..607a21fb9 100644 --- a/webapp/components/admin_console/metrics_settings.jsx +++ b/webapp/components/admin_console/metrics_settings.jsx @@ -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' + /> ); } diff --git a/webapp/components/admin_console/mfa_settings.jsx b/webapp/components/admin_console/mfa_settings.jsx index 5a7e0076f..7ae1f2e18 100644 --- a/webapp/components/admin_console/mfa_settings.jsx +++ b/webapp/components/admin_console/mfa_settings.jsx @@ -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' + /> ); } diff --git a/webapp/components/admin_console/native_app_link_settings.jsx b/webapp/components/admin_console/native_app_link_settings.jsx index 05d61a284..d932af645 100644 --- a/webapp/components/admin_console/native_app_link_settings.jsx +++ b/webapp/components/admin_console/native_app_link_settings.jsx @@ -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' + /> ); } diff --git a/webapp/components/admin_console/oauth_settings.jsx b/webapp/components/admin_console/oauth_settings.jsx index 9a86abfa0..f5eac13eb 100644 --- a/webapp/components/admin_console/oauth_settings.jsx +++ b/webapp/components/admin_console/oauth_settings.jsx @@ -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' + /> ); } diff --git a/webapp/components/admin_console/password_settings.jsx b/webapp/components/admin_console/password_settings.jsx index 43ec40904..edb9669e1 100644 --- a/webapp/components/admin_console/password_settings.jsx +++ b/webapp/components/admin_console/password_settings.jsx @@ -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' + /> ); } diff --git a/webapp/components/admin_console/policy_settings.jsx b/webapp/components/admin_console/policy_settings.jsx index 5d82fc69c..c8c145b8d 100644 --- a/webapp/components/admin_console/policy_settings.jsx +++ b/webapp/components/admin_console/policy_settings.jsx @@ -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' + /> ); } diff --git a/webapp/components/admin_console/privacy_settings.jsx b/webapp/components/admin_console/privacy_settings.jsx index 6da9e6c4f..518ec807e 100644 --- a/webapp/components/admin_console/privacy_settings.jsx +++ b/webapp/components/admin_console/privacy_settings.jsx @@ -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' + /> ); } diff --git a/webapp/components/admin_console/public_link_settings.jsx b/webapp/components/admin_console/public_link_settings.jsx index 9b93a6adc..592d607d1 100644 --- a/webapp/components/admin_console/public_link_settings.jsx +++ b/webapp/components/admin_console/public_link_settings.jsx @@ -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' + /> ); } diff --git a/webapp/components/admin_console/push_settings.jsx b/webapp/components/admin_console/push_settings.jsx index 73189cd8f..2fc63afe0 100644 --- a/webapp/components/admin_console/push_settings.jsx +++ b/webapp/components/admin_console/push_settings.jsx @@ -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' + /> ); } diff --git a/webapp/components/admin_console/rate_settings.jsx b/webapp/components/admin_console/rate_settings.jsx index 73e9a4131..9b0a8076f 100644 --- a/webapp/components/admin_console/rate_settings.jsx +++ b/webapp/components/admin_console/rate_settings.jsx @@ -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' + /> ); } diff --git a/webapp/components/admin_console/reset_password_modal.jsx b/webapp/components/admin_console/reset_password_modal.jsx index 1b9e5b37a..d01fc15f3 100644 --- a/webapp/components/admin_console/reset_password_modal.jsx +++ b/webapp/components/admin_console/reset_password_modal.jsx @@ -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); diff --git a/webapp/components/admin_console/saml_settings.jsx b/webapp/components/admin_console/saml_settings.jsx index 7b9ed38b8..6025abe28 100644 --- a/webapp/components/admin_console/saml_settings.jsx +++ b/webapp/components/admin_console/saml_settings.jsx @@ -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' + /> ); } diff --git a/webapp/components/admin_console/select_team_modal.jsx b/webapp/components/admin_console/select_team_modal.jsx deleted file mode 100644 index 68e20f852..000000000 --- a/webapp/components/admin_console/select_team_modal.jsx +++ /dev/null @@ -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 -}; diff --git a/webapp/components/admin_console/session_settings.jsx b/webapp/components/admin_console/session_settings.jsx index 9624dea18..b238da90f 100644 --- a/webapp/components/admin_console/session_settings.jsx +++ b/webapp/components/admin_console/session_settings.jsx @@ -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' + /> ); } diff --git a/webapp/components/admin_console/signup_settings.jsx b/webapp/components/admin_console/signup_settings.jsx index 0c884f486..b75b7591a 100644 --- a/webapp/components/admin_console/signup_settings.jsx +++ b/webapp/components/admin_console/signup_settings.jsx @@ -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' + /> ); } diff --git a/webapp/components/admin_console/storage_settings.jsx b/webapp/components/admin_console/storage_settings.jsx index 381206bf0..41d38d1ca 100644 --- a/webapp/components/admin_console/storage_settings.jsx +++ b/webapp/components/admin_console/storage_settings.jsx @@ -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' + /> ); } diff --git a/webapp/components/admin_console/system_users/system_users.jsx b/webapp/components/admin_console/system_users/system_users.jsx new file mode 100644 index 000000000..a311aebb7 --- /dev/null +++ b/webapp/components/admin_console/system_users/system_users.jsx @@ -0,0 +1,370 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; +import {FormattedMessage} from 'react-intl'; + +import { + loadProfiles, + loadProfilesAndTeamMembers, + loadProfilesWithoutTeam, + searchUsers +} from 'actions/user_actions.jsx'; + +import AdminStore from 'stores/admin_store.jsx'; +import AnalyticsStore from 'stores/analytics_store.jsx'; +import TeamStore from 'stores/team_store.jsx'; +import UserStore from 'stores/user_store.jsx'; + +import {getAllTeams, getStandardAnalytics, getTeamStats, getUser} from 'utils/async_client.jsx'; +import {Constants, StatTypes, UserSearchOptions} from 'utils/constants.jsx'; +import {convertTeamMapToList} from 'utils/team_utils.jsx'; +import * as Utils from 'utils/utils.jsx'; + +import SystemUsersList from './system_users_list.jsx'; + +const ALL_USERS = ''; +const NO_TEAM = 'no_team'; + +const USER_ID_LENGTH = 26; +const USERS_PER_PAGE = 50; + +export default class SystemUsers extends React.Component { + constructor(props) { + super(props); + + this.updateTeamsFromStore = this.updateTeamsFromStore.bind(this); + this.updateTotalUsersFromStore = this.updateTotalUsersFromStore.bind(this); + this.updateUsersFromStore = this.updateUsersFromStore.bind(this); + + this.loadDataForTeam = this.loadDataForTeam.bind(this); + this.loadComplete = this.loadComplete.bind(this); + + this.handleTeamChange = this.handleTeamChange.bind(this); + this.handleTermChange = this.handleTermChange.bind(this); + this.nextPage = this.nextPage.bind(this); + + this.doSearch = this.doSearch.bind(this); + this.search = this.search.bind(this); + this.getUserById = this.getUserById.bind(this); + + this.renderFilterRow = this.renderFilterRow.bind(this); + + this.state = { + teams: convertTeamMapToList(AdminStore.getAllTeams()), + totalUsers: AnalyticsStore.getAllSystem()[StatTypes.TOTAL_USERS], + users: UserStore.getProfileList(), + + teamId: ALL_USERS, + term: '', + loading: true, + searching: false + }; + } + + componentDidMount() { + AdminStore.addAllTeamsChangeListener(this.updateTeamsFromStore); + + AnalyticsStore.addChangeListener(this.updateTotalUsersFromStore); + TeamStore.addStatsChangeListener(this.updateTotalUsersFromStore); + + UserStore.addChangeListener(this.updateUsersFromStore); + UserStore.addInTeamChangeListener(this.updateUsersFromStore); + UserStore.addWithoutTeamChangeListener(this.updateUsersFromStore); + + this.loadDataForTeam(this.state.teamId); + getAllTeams(); + } + + componentWillUpdate(nextProps, nextState) { + const nextTeamId = nextState.teamId; + + if (this.state.teamId !== nextTeamId) { + this.updateTotalUsersFromStore(nextTeamId); + this.updateUsersFromStore(nextTeamId, nextState.term); + + this.loadDataForTeam(nextTeamId); + } + } + + componentWillUnmount() { + AdminStore.removeAllTeamsChangeListener(this.updateTeamsFromStore); + + AnalyticsStore.removeChangeListener(this.updateTotalUsersFromStore); + TeamStore.removeStatsChangeListener(this.updateTotalUsersFromStore); + + UserStore.removeChangeListener(this.updateUsersFromStore); + UserStore.removeInTeamChangeListener(this.updateUsersFromStore); + UserStore.removeWithoutTeamChangeListener(this.updateUsersFromStore); + } + + updateTeamsFromStore() { + this.setState({teams: convertTeamMapToList(AdminStore.getAllTeams())}); + } + + updateTotalUsersFromStore(teamId = this.state.teamId) { + if (teamId === ALL_USERS) { + this.setState({ + totalUsers: AnalyticsStore.getAllSystem()[StatTypes.TOTAL_USERS] + }); + } else if (teamId === NO_TEAM) { + this.setState({ + totalUsers: 0 + }); + } else { + this.setState({ + totalUsers: TeamStore.getStats(teamId).total_member_count + }); + } + } + + updateUsersFromStore(teamId = this.state.teamId, term = this.state.term) { + if (term) { + if (teamId === this.state.teamId) { + // Search results aren't in the store, so manually update the users in them + const users = [...this.state.users]; + + for (let i = 0; i < users.length; i++) { + const user = users[i]; + + if (UserStore.hasProfile(user.id)) { + users[i] = UserStore.getProfile(user.id); + } + } + + this.setState({ + users + }); + } else { + this.doSearch(teamId, term, true); + } + + return; + } + + if (teamId === ALL_USERS) { + this.setState({users: UserStore.getProfileList(false, true)}); + } else if (teamId === NO_TEAM) { + this.setState({users: UserStore.getProfileListWithoutTeam()}); + } else { + this.setState({users: UserStore.getProfileListInTeam(this.state.teamId)}); + } + } + + loadDataForTeam(teamId) { + if (teamId === ALL_USERS) { + loadProfiles(0, Constants.PROFILE_CHUNK_SIZE, this.loadComplete); + getStandardAnalytics(); + } else if (teamId === NO_TEAM) { + loadProfilesWithoutTeam(0, Constants.PROFILE_CHUNK_SIZE, this.loadComplete); + } else { + loadProfilesAndTeamMembers(0, Constants.PROFILE_CHUNK_SIZE, teamId, this.loadComplete); + getTeamStats(teamId); + } + } + + loadComplete() { + this.setState({loading: false}); + } + + handleTeamChange(e) { + this.setState({teamId: e.target.value}); + } + + handleTermChange(term) { + this.setState({term}); + } + + nextPage(page) { + // Paging isn't supported while searching + + if (this.state.teamId === ALL_USERS) { + loadProfiles((page + 1) * USERS_PER_PAGE, USERS_PER_PAGE, this.loadComplete); + } else if (this.state.teamId === NO_TEAM) { + loadProfilesWithoutTeam(page + 1, USERS_PER_PAGE, this.loadComplete); + } else { + loadProfilesAndTeamMembers((page + 1) * USERS_PER_PAGE, USERS_PER_PAGE, this.state.teamId, this.loadComplete); + } + } + + search(term) { + if (term === '') { + this.updateUsersFromStore(this.state.teamId, term); + + this.setState({ + loading: false + }); + + this.searchTimeoutId = ''; + return; + } + + this.doSearch(this.state.teamId, term); + } + + doSearch(teamId, term, now = false) { + clearTimeout(this.searchTimeoutId); + + this.setState({ + loading: true, + users: [] + }); + + const options = { + [UserSearchOptions.ALLOW_INACTIVE]: true + }; + if (teamId === NO_TEAM) { + options[UserSearchOptions.WITHOUT_TEAM] = true; + } + + const searchTimeoutId = setTimeout( + () => { + searchUsers( + term, + teamId, + options, + (users) => { + if (searchTimeoutId !== this.searchTimeoutId) { + return; + } + + if (users.length > 0) { + this.setState({ + loading: false, + users + }); + } else if (term.length === USER_ID_LENGTH) { + // This term didn't match any users name, but it does look like it might be a user's ID + this.getUserById(term, searchTimeoutId); + } else { + this.setState({ + loading: false + }); + } + }, + () => { + this.setState({ + loading: false + }); + } + ); + }, + now ? 0 : Constants.SEARCH_TIMEOUT_MILLISECONDS + ); + + this.searchTimeoutId = searchTimeoutId; + } + + getUserById(id, searchTimeoutId) { + if (UserStore.hasProfile(id)) { + this.setState({ + loading: false, + users: [UserStore.getProfile(id)] + }); + + return; + } + + getUser( + id, + (user) => { + if (searchTimeoutId !== this.searchTimeoutId) { + return; + } + + this.setState({ + loading: false, + users: [user] + }); + }, + () => { + if (searchTimeoutId !== this.searchTimeoutId) { + return; + } + + this.setState({ + loading: false, + users: [] + }); + } + ); + } + + renderFilterRow(doSearch) { + const teams = this.state.teams.map((team) => { + return ( + <option + key={team.id} + value={team.id} + > + {team.display_name} + </option> + ); + }); + + return ( + <div className='system-users__filter-row'> + <div className='system-users__filter'> + <input + ref='filter' + className='form-control filter-textbox' + placeholder={Utils.localizeMessage('filtered_user_list.search', 'Search users')} + onInput={doSearch} + /> + </div> + <label> + <span className='system-users__team-filter-label'> + <FormattedMessage + id='filtered_user_list.show' + defaultMessage='Filter:' + /> + </span> + <select + className='form-control system-users__team-filter' + onChange={this.handleTeamChange} + value={this.state.teamId} + > + <option value={ALL_USERS}>{Utils.localizeMessage('admin.system_users.allUsers', 'All Users')}</option> + <option value={NO_TEAM}>{Utils.localizeMessage('admin.system_users.noTeams', 'No Teams')}</option> + {teams} + </select> + </label> + </div> + ); + } + + render() { + let users = null; + if (!this.state.loading) { + users = this.state.users; + } + + return ( + <div className='wrapper--fixed'> + <h3 className='admin-console-header'> + <FormattedMessage + id='admin.system_users.title' + defaultMessage='{siteName} Users' + values={{ + siteName: global.mm_config.SiteName + }} + /> + </h3> + <div className='more-modal__list member-list-holder'> + <SystemUsersList + renderFilterRow={this.renderFilterRow} + search={this.search} + nextPage={this.nextPage} + users={users} + usersPerPage={USERS_PER_PAGE} + total={this.state.totalUsers} + teams={this.state.teams} + teamId={this.state.teamId} + term={this.state.term} + onTermChange={this.handleTermChange} + /> + </div> + </div> + ); + } +} diff --git a/webapp/components/admin_console/admin_team_members_dropdown.jsx b/webapp/components/admin_console/system_users/system_users_dropdown.jsx index 037d8c73f..6f18754a1 100644 --- a/webapp/components/admin_console/admin_team_members_dropdown.jsx +++ b/webapp/components/admin_console/system_users/system_users_dropdown.jsx @@ -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 -}; diff --git a/webapp/components/admin_console/system_users/system_users_list.jsx b/webapp/components/admin_console/system_users/system_users_list.jsx new file mode 100644 index 000000000..5d8837164 --- /dev/null +++ b/webapp/components/admin_console/system_users/system_users_list.jsx @@ -0,0 +1,232 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; +import {FormattedMessage, FormattedHTMLMessage} from 'react-intl'; + +import ResetPasswordModal from 'components/admin_console/reset_password_modal.jsx'; +import SearchableUserList from 'components/searchable_user_list/searchable_user_list.jsx'; + +import {getUser} from 'utils/async_client.jsx'; +import {Constants} from 'utils/constants.jsx'; +import * as Utils from 'utils/utils.jsx'; + +import SystemUsersDropdown from './system_users_dropdown.jsx'; + +export default class SystemUsersList extends React.Component { + static propTypes = { + users: React.PropTypes.arrayOf(React.PropTypes.object), + usersPerPage: React.PropTypes.number, + total: React.PropTypes.number, + nextPage: React.PropTypes.func, + search: React.PropTypes.func.isRequired, + focusOnMount: React.PropTypes.bool, + renderFilterRow: React.PropTypes.func, + + teamId: React.PropTypes.string.isRequired, + term: React.PropTypes.string.isRequired, + onTermChange: React.PropTypes.func.isRequired + }; + + constructor(props) { + super(props); + + this.nextPage = this.nextPage.bind(this); + this.previousPage = this.previousPage.bind(this); + this.search = this.search.bind(this); + + this.doPasswordReset = this.doPasswordReset.bind(this); + this.doPasswordResetDismiss = this.doPasswordResetDismiss.bind(this); + this.doPasswordResetSubmit = this.doPasswordResetSubmit.bind(this); + + this.state = { + page: 0, + + showPasswordModal: false, + user: null + }; + } + + componentWillReceiveProps(nextProps) { + if (nextProps.teamId !== this.props.teamId) { + this.setState({page: 0}); + } + } + + nextPage() { + this.setState({page: this.state.page + 1}); + + this.props.nextPage(this.state.page + 1); + } + + previousPage() { + this.setState({page: this.state.page - 1}); + } + + search(term) { + this.props.search(term); + + if (term !== '') { + this.setState({page: 0}); + } + } + + doPasswordReset(user) { + this.setState({ + showPasswordModal: true, + user + }); + } + + doPasswordResetDismiss() { + this.setState({ + showPasswordModal: false, + user: null + }); + } + + doPasswordResetSubmit(user) { + getUser(user.id); + + this.setState({ + showPasswordModal: false, + user: null + }); + } + + getInfoForUser(user) { + const info = []; + + if (user.auth_service) { + let service; + if (user.auth_service === Constants.LDAP_SERVICE || user.auth_service === Constants.SAML_SERVICE) { + service = user.auth_service.toUpperCase(); + } else { + service = Utils.toTitleCase(user.auth_service); + } + + info.push( + <FormattedHTMLMessage + key='admin.user_item.authServiceNotEmail' + id='admin.user_item.authServiceNotEmail' + defaultMessage='<strong>Sign-in Method:</strong> {service}' + values={{ + service + }} + /> + ); + } else { + info.push( + <FormattedHTMLMessage + key='admin.user_item.authServiceEmail' + id='admin.user_item.authServiceEmail' + defaultMessage='<strong>Sign-in Method:</strong> Email' + /> + ); + } + + const mfaEnabled = global.window.mm_license.IsLicensed === 'true' && + global.window.mm_license.MFA === 'true' && + global.window.mm_config.EnableMultifactorAuthentication === 'true'; + if (mfaEnabled) { + info.push(', '); + + if (user.mfa_active) { + info.push( + <FormattedHTMLMessage + key='admin.user_item.mfaYes' + id='admin.user_item.mfaYes' + defaultMessage='<strong>MFA</strong>: Yes' + /> + ); + } else { + info.push( + <FormattedHTMLMessage + key='admin.user_item.mfaNo' + id='admin.user_item.mfaNo' + defaultMessage='<strong>MFA</strong>: No' + /> + ); + } + } + + return info; + } + + renderCount(count, total, startCount, endCount, isSearch) { + if (total) { + if (isSearch) { + return ( + <FormattedMessage + id='system_users_list.countSearch' + defaultMessage='{count, number} {count, plural, one {user} other {users}} of {total} total' + values={{ + count, + total + }} + /> + ); + } else if (startCount !== 0 || endCount !== total) { + return ( + <FormattedMessage + id='system_users_list.countPage' + defaultMessage='{startCount, number} - {endCount, number} {count, plural, one {user} other {users}} of {total} total' + values={{ + count, + startCount: startCount + 1, + endCount, + total + }} + /> + ); + } + + return ( + <FormattedMessage + id='system_users_list.count' + defaultMessage='{count, number} {count, plural, one {user} other {users}}' + values={{ + count + }} + /> + ); + } + + return null; + } + + render() { + const extraInfo = {}; + if (this.props.users) { + for (const user of this.props.users) { + extraInfo[user.id] = this.getInfoForUser(user); + } + } + + return ( + <div> + <SearchableUserList + {...this.props} + renderCount={this.renderCount} + extraInfo={extraInfo} + actions={[SystemUsersDropdown]} + actionProps={{ + doPasswordReset: this.doPasswordReset + }} + nextPage={this.nextPage} + previousPage={this.previousPage} + search={this.search} + page={this.state.page} + term={this.props.term} + onTermChange={this.props.onTermChange} + /> + <ResetPasswordModal + user={this.state.user} + show={this.state.showPasswordModal} + onModalSubmit={this.doPasswordResetSubmit} + onModalDismissed={this.doPasswordResetDismiss} + /> + </div> + ); + } +} diff --git a/webapp/components/admin_console/team_users.jsx b/webapp/components/admin_console/team_users.jsx deleted file mode 100644 index 5bdaedf6e..000000000 --- a/webapp/components/admin_console/team_users.jsx +++ /dev/null @@ -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> - ); - } -} diff --git a/webapp/components/admin_console/users_and_teams_settings.jsx b/webapp/components/admin_console/users_and_teams_settings.jsx index 2cb5b4e51..6e83c01e3 100644 --- a/webapp/components/admin_console/users_and_teams_settings.jsx +++ b/webapp/components/admin_console/users_and_teams_settings.jsx @@ -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' + /> ); } diff --git a/webapp/components/admin_console/webrtc_settings.jsx b/webapp/components/admin_console/webrtc_settings.jsx index 63c17f598..e0238e7f3 100644 --- a/webapp/components/admin_console/webrtc_settings.jsx +++ b/webapp/components/admin_console/webrtc_settings.jsx @@ -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)' + /> ); } |