PLT-2713/PLT-6028 Added System Users list to System Console (#5882)
* PLT-2713 Added ability for admins to list users not in any team * Updated style of unit test * Split SearchableUserList to give better control over its properties * Added users without any teams to the user store * Added ManageUsers page * Renamed ManageUsers to SystemUsers * Added ability to search by user id in SystemUsers page * Added SystemUsersDropdown * Removed unnecessary injectIntl * Created TeamUtils * Reduced scope of system console heading CSS * Added team filter to TeamAnalytics page * Updated admin console sidebar * Removed unnecessary TODO * Removed unused reference to deleted modal * Fixed system console sidebar not scrolling on first load * Fixed TeamAnalytics page not rendering on first load * Fixed chart.js throwing an error when switching between teams * Changed TeamAnalytics header to show the team's display name * Fixed appearance of TeamAnalytics and SystemUsers on small screen widths * Fixed placement of 'No users found' message * Fixed teams not appearing in SystemUsers on first load * Updated user count text for SystemUsers * Changed search by id fallback to trigger less often * Fixed SystemUsers list items not updating when searching * Fixed localization strings for SystemUsers page
Этот коммит содержится в:
коммит произвёл
Corey Hulen
родитель
9a9729f22f
Коммит
689cac535e
@@ -25,6 +25,7 @@ const ActionTypes = Constants.ActionTypes;
|
|||||||
import Client from 'client/web_client.jsx';
|
import Client from 'client/web_client.jsx';
|
||||||
import * as AsyncClient from 'utils/async_client.jsx';
|
import * as AsyncClient from 'utils/async_client.jsx';
|
||||||
import WebSocketClient from 'client/web_websocket_client.jsx';
|
import WebSocketClient from 'client/web_websocket_client.jsx';
|
||||||
|
import {sortTeamsByDisplayName} from 'utils/team_utils.jsx';
|
||||||
import * as Utils from 'utils/utils.jsx';
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
|
||||||
import en from 'i18n/en.json';
|
import en from 'i18n/en.json';
|
||||||
@@ -594,7 +595,7 @@ export function redirectUserToDefaultTeam() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (myTeams.length > 0) {
|
if (myTeams.length > 0) {
|
||||||
myTeams = myTeams.sort(Utils.sortTeamsByDisplayName);
|
myTeams = myTeams.sort(sortTeamsByDisplayName);
|
||||||
teamId = myTeams[0].id;
|
teamId = myTeams[0].id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,6 +133,29 @@ export function loadTeamMembersForProfilesList(profiles, teamId = TeamStore.getC
|
|||||||
loadTeamMembersForProfiles(list, teamId, success, error);
|
loadTeamMembersForProfiles(list, teamId, success, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function loadProfilesWithoutTeam(page, perPage, success, error) {
|
||||||
|
Client.getProfilesWithoutTeam(
|
||||||
|
page,
|
||||||
|
perPage,
|
||||||
|
(data) => {
|
||||||
|
AppDispatcher.handleServerAction({
|
||||||
|
type: ActionTypes.RECEIVED_PROFILES_WITHOUT_TEAM,
|
||||||
|
profiles: data,
|
||||||
|
page
|
||||||
|
});
|
||||||
|
|
||||||
|
loadStatusesForProfilesMap(data);
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
AsyncClient.dispatchError(err, 'getProfilesWithoutTeam');
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function loadTeamMembersForProfiles(userIds, teamId, success, error) {
|
function loadTeamMembersForProfiles(userIds, teamId, success, error) {
|
||||||
Client.getTeamMembersByIds(
|
Client.getTeamMembersByIds(
|
||||||
teamId,
|
teamId,
|
||||||
@@ -583,17 +606,13 @@ export function updateUserRoles(userId, newRoles, success, error) {
|
|||||||
userId,
|
userId,
|
||||||
newRoles,
|
newRoles,
|
||||||
() => {
|
() => {
|
||||||
AsyncClient.getUser(userId);
|
AsyncClient.getUser(
|
||||||
|
userId,
|
||||||
if (success) {
|
success,
|
||||||
success();
|
error
|
||||||
}
|
);
|
||||||
},
|
},
|
||||||
(err) => {
|
error
|
||||||
if (error) {
|
|
||||||
error(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -658,18 +677,17 @@ export function checkMfa(loginId, success, error) {
|
|||||||
|
|
||||||
export function updateActive(userId, active, success, error) {
|
export function updateActive(userId, active, success, error) {
|
||||||
Client.updateActive(userId, active,
|
Client.updateActive(userId, active,
|
||||||
() => {
|
(data) => {
|
||||||
AsyncClient.getUser(userId);
|
AppDispatcher.handleServerAction({
|
||||||
|
type: ActionTypes.RECEIVED_PROFILE,
|
||||||
|
profile: data
|
||||||
|
});
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
success();
|
success(data);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(err) => {
|
error
|
||||||
if (error) {
|
|
||||||
error(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1126,6 +1126,29 @@ export default class Client {
|
|||||||
this.trackEvent('api', 'api_profiles_get_not_in_channel', {team_id: this.getTeamId(), channel_id: channelId});
|
this.trackEvent('api', 'api_profiles_get_not_in_channel', {team_id: this.getTeamId(), channel_id: channelId});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getProfilesWithoutTeam(page, perPage, success, error) {
|
||||||
|
// Super hacky, but this option only exists in api v4
|
||||||
|
function wrappedSuccess(data, res) {
|
||||||
|
// Convert the profile list provided by api v4 to a map to match similar v3 calls
|
||||||
|
const profiles = {};
|
||||||
|
|
||||||
|
for (const profile of data) {
|
||||||
|
profiles[profile.id] = profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
success(profiles, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
request.
|
||||||
|
get(`${this.url}/api/v4/users?without_team=1&page=${page}&per_page=${perPage}`).
|
||||||
|
set(this.defaultHeaders).
|
||||||
|
type('application/json').
|
||||||
|
accept('application/json').
|
||||||
|
end(this.handleResponse.bind(this, 'getProfilesWithoutTeam', wrappedSuccess, error));
|
||||||
|
|
||||||
|
this.trackEvent('api', 'api_profiles_get_without_team');
|
||||||
|
}
|
||||||
|
|
||||||
getProfilesByIds(userIds, success, error) {
|
getProfilesByIds(userIds, success, error) {
|
||||||
request.
|
request.
|
||||||
post(`${this.getUsersRoute()}/ids`).
|
post(`${this.getUsersRoute()}/ids`).
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import ReactDOM from 'react-dom';
|
|||||||
|
|
||||||
import TeamStore from 'stores/team_store.jsx';
|
import TeamStore from 'stores/team_store.jsx';
|
||||||
import Constants from 'utils/constants.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 * as GlobalActions from 'actions/global_actions.jsx';
|
||||||
|
|
||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|||||||
@@ -112,7 +112,9 @@ export default class AdminSettings extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className='wrapper--fixed'>
|
<div className='wrapper--fixed'>
|
||||||
|
<h3 className='admin-console-header'>
|
||||||
{this.renderTitle()}
|
{this.renderTitle()}
|
||||||
|
</h3>
|
||||||
<form
|
<form
|
||||||
className='form-horizontal'
|
className='form-horizontal'
|
||||||
role='form'
|
role='form'
|
||||||
|
|||||||
@@ -3,18 +3,12 @@
|
|||||||
|
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import React from 'react';
|
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 * 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 AdminSidebarCategory from './admin_sidebar_category.jsx';
|
||||||
|
import AdminSidebarHeader from './admin_sidebar_header.jsx';
|
||||||
import AdminSidebarSection from './admin_sidebar_section.jsx';
|
import AdminSidebarSection from './admin_sidebar_section.jsx';
|
||||||
|
|
||||||
export default class AdminSidebar extends React.Component {
|
export default class AdminSidebar extends React.Component {
|
||||||
@@ -27,31 +21,15 @@ export default class AdminSidebar extends React.Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(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.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() {
|
componentDidMount() {
|
||||||
AdminStore.addAllTeamsChangeListener(this.handleAllTeamsChange);
|
|
||||||
AsyncClient.getAllTeams();
|
|
||||||
|
|
||||||
this.updateTitle();
|
this.updateTitle();
|
||||||
|
|
||||||
|
if (!Utils.isMobile()) {
|
||||||
|
$('.admin-sidebar .nav-pills__container').perfectScrollbar();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
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() {
|
updateTitle() {
|
||||||
let currentSiteName = '';
|
let currentSiteName = '';
|
||||||
if (global.window.mm_config.SiteName != null) {
|
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;
|
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() {
|
render() {
|
||||||
let oauthSettings = null;
|
let oauthSettings = null;
|
||||||
let ldapSettings = 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
|
<AdminSidebarSection
|
||||||
name='logs'
|
name='logs'
|
||||||
title={
|
title={
|
||||||
@@ -760,16 +638,9 @@ export default class AdminSidebar extends React.Component {
|
|||||||
{metricsSettings}
|
{metricsSettings}
|
||||||
</AdminSidebarSection>
|
</AdminSidebarSection>
|
||||||
</AdminSidebarCategory>
|
</AdminSidebarCategory>
|
||||||
{this.renderTeams()}
|
|
||||||
{otherCategory}
|
{otherCategory}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<SelectTeamModal
|
|
||||||
teams={this.state.teams}
|
|
||||||
show={this.state.showSelectModal}
|
|
||||||
onModalSubmit={this.teamSelectedModal}
|
|
||||||
onModalDismissed={this.teamSelectedModalDismissed}
|
|
||||||
/>
|
|
||||||
</div>
|
</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/>
|
<ComplianceReports/>
|
||||||
|
|
||||||
<div className='panel audit-panel'>
|
<div className='panel audit-panel'>
|
||||||
<h3>
|
<h3 className='admin-console-header'>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.audits.title'
|
id='admin.audits.title'
|
||||||
defaultMessage='User Activity Logs'
|
defaultMessage='User Activity Logs'
|
||||||
|
|||||||
@@ -51,12 +51,10 @@ export default class ClusterSettings extends AdminSettings {
|
|||||||
|
|
||||||
renderTitle() {
|
renderTitle() {
|
||||||
return (
|
return (
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.advance.cluster'
|
id='admin.advance.cluster'
|
||||||
defaultMessage='High Availability (Beta)'
|
defaultMessage='High Availability (Beta)'
|
||||||
/>
|
/>
|
||||||
</h3>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,12 +38,10 @@ export default class ComplianceSettings extends AdminSettings {
|
|||||||
|
|
||||||
renderTitle() {
|
renderTitle() {
|
||||||
return (
|
return (
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.compliance.title'
|
id='admin.compliance.title'
|
||||||
defaultMessage='Compliance Settings'
|
defaultMessage='Compliance Settings'
|
||||||
/>
|
/>
|
||||||
</h3>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,12 +64,10 @@ export default class ConfigurationSettings extends AdminSettings {
|
|||||||
|
|
||||||
renderTitle() {
|
renderTitle() {
|
||||||
return (
|
return (
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.general.configuration'
|
id='admin.general.configuration'
|
||||||
defaultMessage='Configuration'
|
defaultMessage='Configuration'
|
||||||
/>
|
/>
|
||||||
</h3>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,12 +36,10 @@ export default class ConnectionSettings extends AdminSettings {
|
|||||||
|
|
||||||
renderTitle() {
|
renderTitle() {
|
||||||
return (
|
return (
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.security.connection'
|
id='admin.security.connection'
|
||||||
defaultMessage='Connections'
|
defaultMessage='Connections'
|
||||||
/>
|
/>
|
||||||
</h3>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,12 +44,10 @@ export default class CustomBrandSettings extends AdminSettings {
|
|||||||
|
|
||||||
renderTitle() {
|
renderTitle() {
|
||||||
return (
|
return (
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.customization.customBrand'
|
id='admin.customization.customBrand'
|
||||||
defaultMessage='Custom Branding'
|
defaultMessage='Custom Branding'
|
||||||
/>
|
/>
|
||||||
</h3>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,12 +39,10 @@ export default class CustomEmojiSettings extends AdminSettings {
|
|||||||
|
|
||||||
renderTitle() {
|
renderTitle() {
|
||||||
return (
|
return (
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.customization.customEmoji'
|
id='admin.customization.customEmoji'
|
||||||
defaultMessage='Custom Emoji'
|
defaultMessage='Custom Emoji'
|
||||||
/>
|
/>
|
||||||
</h3>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,12 +43,10 @@ export default class WebhookSettings extends AdminSettings {
|
|||||||
|
|
||||||
renderTitle() {
|
renderTitle() {
|
||||||
return (
|
return (
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.integrations.custom'
|
id='admin.integrations.custom'
|
||||||
defaultMessage='Custom Integrations'
|
defaultMessage='Custom Integrations'
|
||||||
/>
|
/>
|
||||||
</h3>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,12 +46,10 @@ export default class DatabaseSettings extends AdminSettings {
|
|||||||
|
|
||||||
renderTitle() {
|
renderTitle() {
|
||||||
return (
|
return (
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.database.title'
|
id='admin.database.title'
|
||||||
defaultMessage='Database Settings'
|
defaultMessage='Database Settings'
|
||||||
/>
|
/>
|
||||||
</h3>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,12 +33,10 @@ export default class DeveloperSettings extends AdminSettings {
|
|||||||
|
|
||||||
renderTitle() {
|
renderTitle() {
|
||||||
return (
|
return (
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.developer.title'
|
id='admin.developer.title'
|
||||||
defaultMessage='Developer Settings'
|
defaultMessage='Developer Settings'
|
||||||
/>
|
/>
|
||||||
</h3>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,12 +35,10 @@ export default class EmailAuthenticationSettings extends AdminSettings {
|
|||||||
|
|
||||||
renderTitle() {
|
renderTitle() {
|
||||||
return (
|
return (
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.authentication.email'
|
id='admin.authentication.email'
|
||||||
defaultMessage='Email'
|
defaultMessage='Email'
|
||||||
/>
|
/>
|
||||||
</h3>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,12 +56,10 @@ export default class EmailSettings extends AdminSettings {
|
|||||||
|
|
||||||
renderTitle() {
|
renderTitle() {
|
||||||
return (
|
return (
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.notifications.email'
|
id='admin.notifications.email'
|
||||||
defaultMessage='Email'
|
defaultMessage='Email'
|
||||||
/>
|
/>
|
||||||
</h3>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,12 +32,10 @@ export default class ExternalServiceSettings extends AdminSettings {
|
|||||||
|
|
||||||
renderTitle() {
|
renderTitle() {
|
||||||
return (
|
return (
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.integrations.external'
|
id='admin.integrations.external'
|
||||||
defaultMessage='External Services'
|
defaultMessage='External Services'
|
||||||
/>
|
/>
|
||||||
</h3>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,12 +44,10 @@ export default class GitLabSettings extends AdminSettings {
|
|||||||
|
|
||||||
renderTitle() {
|
renderTitle() {
|
||||||
return (
|
return (
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.authentication.gitlab'
|
id='admin.authentication.gitlab'
|
||||||
defaultMessage='GitLab'
|
defaultMessage='GitLab'
|
||||||
/>
|
/>
|
||||||
</h3>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,12 +43,10 @@ export default class ImageSettings extends AdminSettings {
|
|||||||
|
|
||||||
renderTitle() {
|
renderTitle() {
|
||||||
return (
|
return (
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.files.images'
|
id='admin.files.images'
|
||||||
defaultMessage='Images'
|
defaultMessage='Images'
|
||||||
/>
|
/>
|
||||||
</h3>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,12 +76,10 @@ export default class LdapSettings extends AdminSettings {
|
|||||||
|
|
||||||
renderTitle() {
|
renderTitle() {
|
||||||
return (
|
return (
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.authentication.ldap'
|
id='admin.authentication.ldap'
|
||||||
defaultMessage='AD/LDAP'
|
defaultMessage='AD/LDAP'
|
||||||
/>
|
/>
|
||||||
</h3>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,12 +41,10 @@ export default class LegalAndSupportSettings extends AdminSettings {
|
|||||||
|
|
||||||
renderTitle() {
|
renderTitle() {
|
||||||
return (
|
return (
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.customization.support'
|
id='admin.customization.support'
|
||||||
defaultMessage='Legal and Support'
|
defaultMessage='Legal and Support'
|
||||||
/>
|
/>
|
||||||
</h3>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ class LicenseSettings extends React.Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='wrapper--fixed'>
|
<div className='wrapper--fixed'>
|
||||||
<h3>
|
<h3 className='admin-console-header'>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.license.title'
|
id='admin.license.title'
|
||||||
defaultMessage='Edition and License'
|
defaultMessage='Edition and License'
|
||||||
|
|||||||
@@ -31,12 +31,10 @@ export default class LinkPreviewsSettings extends AdminSettings {
|
|||||||
|
|
||||||
renderTitle() {
|
renderTitle() {
|
||||||
return (
|
return (
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.customization.linkPreviews'
|
id='admin.customization.linkPreviews'
|
||||||
defaultMessage='Link Previews'
|
defaultMessage='Link Previews'
|
||||||
/>
|
/>
|
||||||
</h3>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,12 +52,10 @@ export default class LocalizationSettings extends AdminSettings {
|
|||||||
|
|
||||||
renderTitle() {
|
renderTitle() {
|
||||||
return (
|
return (
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.general.localization'
|
id='admin.general.localization'
|
||||||
defaultMessage='Localization'
|
defaultMessage='Localization'
|
||||||
/>
|
/>
|
||||||
</h3>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,12 +49,10 @@ export default class LogSettings extends AdminSettings {
|
|||||||
|
|
||||||
renderTitle() {
|
renderTitle() {
|
||||||
return (
|
return (
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.general.log'
|
id='admin.general.log'
|
||||||
defaultMessage='Logging'
|
defaultMessage='Logging'
|
||||||
/>
|
/>
|
||||||
</h3>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export default class Logs extends React.Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='panel'>
|
<div className='panel'>
|
||||||
<h3>
|
<h3 className='admin-console-header'>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.logs.title'
|
id='admin.logs.title'
|
||||||
defaultMessage='Server Logs'
|
defaultMessage='Server Logs'
|
||||||
|
|||||||
@@ -38,12 +38,10 @@ export default class MetricsSettings extends AdminSettings {
|
|||||||
|
|
||||||
renderTitle() {
|
renderTitle() {
|
||||||
return (
|
return (
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.advance.metrics'
|
id='admin.advance.metrics'
|
||||||
defaultMessage='Performance Monitoring'
|
defaultMessage='Performance Monitoring'
|
||||||
/>
|
/>
|
||||||
</h3>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,12 +38,10 @@ export default class MfaSettings extends AdminSettings {
|
|||||||
|
|
||||||
renderTitle() {
|
renderTitle() {
|
||||||
return (
|
return (
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.mfa.title'
|
id='admin.mfa.title'
|
||||||
defaultMessage='Multi-factor Authentication'
|
defaultMessage='Multi-factor Authentication'
|
||||||
/>
|
/>
|
||||||
</h3>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,12 +35,10 @@ export default class NativeAppLinkSettings extends AdminSettings {
|
|||||||
|
|
||||||
renderTitle() {
|
renderTitle() {
|
||||||
return (
|
return (
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.customization.nativeAppLinks'
|
id='admin.customization.nativeAppLinks'
|
||||||
defaultMessage='Mattermost App Links'
|
defaultMessage='Mattermost App Links'
|
||||||
/>
|
/>
|
||||||
</h3>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -111,12 +111,10 @@ export default class OAuthSettings extends AdminSettings {
|
|||||||
|
|
||||||
renderTitle() {
|
renderTitle() {
|
||||||
return (
|
return (
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.authentication.oauth'
|
id='admin.authentication.oauth'
|
||||||
defaultMessage='OAuth 2.0'
|
defaultMessage='OAuth 2.0'
|
||||||
/>
|
/>
|
||||||
</h3>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -138,12 +138,10 @@ export default class PasswordSettings extends AdminSettings {
|
|||||||
|
|
||||||
renderTitle() {
|
renderTitle() {
|
||||||
return (
|
return (
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.security.password'
|
id='admin.security.password'
|
||||||
defaultMessage='Password'
|
defaultMessage='Password'
|
||||||
/>
|
/>
|
||||||
</h3>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,12 +55,10 @@ export default class PolicySettings extends AdminSettings {
|
|||||||
|
|
||||||
renderTitle() {
|
renderTitle() {
|
||||||
return (
|
return (
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.general.policy'
|
id='admin.general.policy'
|
||||||
defaultMessage='Policy'
|
defaultMessage='Policy'
|
||||||
/>
|
/>
|
||||||
</h3>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,12 +33,10 @@ export default class PrivacySettings extends AdminSettings {
|
|||||||
|
|
||||||
renderTitle() {
|
renderTitle() {
|
||||||
return (
|
return (
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.general.privacy'
|
id='admin.general.privacy'
|
||||||
defaultMessage='Privacy'
|
defaultMessage='Privacy'
|
||||||
/>
|
/>
|
||||||
</h3>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,12 +34,10 @@ export default class PublicLinkSettings extends AdminSettings {
|
|||||||
|
|
||||||
renderTitle() {
|
renderTitle() {
|
||||||
return (
|
return (
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.security.public_links'
|
id='admin.security.public_links'
|
||||||
defaultMessage='Public Links'
|
defaultMessage='Public Links'
|
||||||
/>
|
/>
|
||||||
</h3>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -100,12 +100,10 @@ export default class PushSettings extends AdminSettings {
|
|||||||
|
|
||||||
renderTitle() {
|
renderTitle() {
|
||||||
return (
|
return (
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.notifications.title'
|
id='admin.notifications.title'
|
||||||
defaultMessage='Notification Settings'
|
defaultMessage='Notification Settings'
|
||||||
/>
|
/>
|
||||||
</h3>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,12 +44,10 @@ export default class RateSettings extends AdminSettings {
|
|||||||
|
|
||||||
renderTitle() {
|
renderTitle() {
|
||||||
return (
|
return (
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.rate.title'
|
id='admin.rate.title'
|
||||||
defaultMessage='Rate Limit Settings'
|
defaultMessage='Rate Limit Settings'
|
||||||
/>
|
/>
|
||||||
</h3>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,24 @@
|
|||||||
import * as Utils from 'utils/utils.jsx';
|
import * as Utils from 'utils/utils.jsx';
|
||||||
import {Modal} from 'react-bootstrap';
|
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 {adminResetPassword} from 'actions/admin_actions.jsx';
|
||||||
|
|
||||||
import React from 'react';
|
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) {
|
constructor(props) {
|
||||||
super(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() {
|
renderTitle() {
|
||||||
return (
|
return (
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.authentication.saml'
|
id='admin.authentication.saml'
|
||||||
defaultMessage='SAML'
|
defaultMessage='SAML'
|
||||||
/>
|
/>
|
||||||
</h3>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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() {
|
renderTitle() {
|
||||||
return (
|
return (
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.security.session'
|
id='admin.security.session'
|
||||||
defaultMessage='Sessions'
|
defaultMessage='Sessions'
|
||||||
/>
|
/>
|
||||||
</h3>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,12 +36,10 @@ export default class SignupSettings extends AdminSettings {
|
|||||||
|
|
||||||
renderTitle() {
|
renderTitle() {
|
||||||
return (
|
return (
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.security.signup'
|
id='admin.security.signup'
|
||||||
defaultMessage='Signup'
|
defaultMessage='Signup'
|
||||||
/>
|
/>
|
||||||
</h3>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,12 +52,10 @@ export default class StorageSettings extends AdminSettings {
|
|||||||
|
|
||||||
renderTitle() {
|
renderTitle() {
|
||||||
return (
|
return (
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.files.storage'
|
id='admin.files.storage'
|
||||||
defaultMessage='Storage'
|
defaultMessage='Storage'
|
||||||
/>
|
/>
|
||||||
</h3>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
370
webapp/components/admin_console/system_users/system_users.jsx
Обычный файл
370
webapp/components/admin_console/system_users/system_users.jsx
Обычный файл
@@ -0,0 +1,370 @@
|
|||||||
|
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
|
import {
|
||||||
|
loadProfiles,
|
||||||
|
loadProfilesAndTeamMembers,
|
||||||
|
loadProfilesWithoutTeam,
|
||||||
|
searchUsers
|
||||||
|
} from 'actions/user_actions.jsx';
|
||||||
|
|
||||||
|
import AdminStore from 'stores/admin_store.jsx';
|
||||||
|
import AnalyticsStore from 'stores/analytics_store.jsx';
|
||||||
|
import TeamStore from 'stores/team_store.jsx';
|
||||||
|
import UserStore from 'stores/user_store.jsx';
|
||||||
|
|
||||||
|
import {getAllTeams, getStandardAnalytics, getTeamStats, getUser} from 'utils/async_client.jsx';
|
||||||
|
import {Constants, StatTypes, UserSearchOptions} from 'utils/constants.jsx';
|
||||||
|
import {convertTeamMapToList} from 'utils/team_utils.jsx';
|
||||||
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
|
||||||
|
import SystemUsersList from './system_users_list.jsx';
|
||||||
|
|
||||||
|
const ALL_USERS = '';
|
||||||
|
const NO_TEAM = 'no_team';
|
||||||
|
|
||||||
|
const USER_ID_LENGTH = 26;
|
||||||
|
const USERS_PER_PAGE = 50;
|
||||||
|
|
||||||
|
export default class SystemUsers extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.updateTeamsFromStore = this.updateTeamsFromStore.bind(this);
|
||||||
|
this.updateTotalUsersFromStore = this.updateTotalUsersFromStore.bind(this);
|
||||||
|
this.updateUsersFromStore = this.updateUsersFromStore.bind(this);
|
||||||
|
|
||||||
|
this.loadDataForTeam = this.loadDataForTeam.bind(this);
|
||||||
|
this.loadComplete = this.loadComplete.bind(this);
|
||||||
|
|
||||||
|
this.handleTeamChange = this.handleTeamChange.bind(this);
|
||||||
|
this.handleTermChange = this.handleTermChange.bind(this);
|
||||||
|
this.nextPage = this.nextPage.bind(this);
|
||||||
|
|
||||||
|
this.doSearch = this.doSearch.bind(this);
|
||||||
|
this.search = this.search.bind(this);
|
||||||
|
this.getUserById = this.getUserById.bind(this);
|
||||||
|
|
||||||
|
this.renderFilterRow = this.renderFilterRow.bind(this);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
teams: convertTeamMapToList(AdminStore.getAllTeams()),
|
||||||
|
totalUsers: AnalyticsStore.getAllSystem()[StatTypes.TOTAL_USERS],
|
||||||
|
users: UserStore.getProfileList(),
|
||||||
|
|
||||||
|
teamId: ALL_USERS,
|
||||||
|
term: '',
|
||||||
|
loading: true,
|
||||||
|
searching: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
AdminStore.addAllTeamsChangeListener(this.updateTeamsFromStore);
|
||||||
|
|
||||||
|
AnalyticsStore.addChangeListener(this.updateTotalUsersFromStore);
|
||||||
|
TeamStore.addStatsChangeListener(this.updateTotalUsersFromStore);
|
||||||
|
|
||||||
|
UserStore.addChangeListener(this.updateUsersFromStore);
|
||||||
|
UserStore.addInTeamChangeListener(this.updateUsersFromStore);
|
||||||
|
UserStore.addWithoutTeamChangeListener(this.updateUsersFromStore);
|
||||||
|
|
||||||
|
this.loadDataForTeam(this.state.teamId);
|
||||||
|
getAllTeams();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUpdate(nextProps, nextState) {
|
||||||
|
const nextTeamId = nextState.teamId;
|
||||||
|
|
||||||
|
if (this.state.teamId !== nextTeamId) {
|
||||||
|
this.updateTotalUsersFromStore(nextTeamId);
|
||||||
|
this.updateUsersFromStore(nextTeamId, nextState.term);
|
||||||
|
|
||||||
|
this.loadDataForTeam(nextTeamId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
AdminStore.removeAllTeamsChangeListener(this.updateTeamsFromStore);
|
||||||
|
|
||||||
|
AnalyticsStore.removeChangeListener(this.updateTotalUsersFromStore);
|
||||||
|
TeamStore.removeStatsChangeListener(this.updateTotalUsersFromStore);
|
||||||
|
|
||||||
|
UserStore.removeChangeListener(this.updateUsersFromStore);
|
||||||
|
UserStore.removeInTeamChangeListener(this.updateUsersFromStore);
|
||||||
|
UserStore.removeWithoutTeamChangeListener(this.updateUsersFromStore);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTeamsFromStore() {
|
||||||
|
this.setState({teams: convertTeamMapToList(AdminStore.getAllTeams())});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTotalUsersFromStore(teamId = this.state.teamId) {
|
||||||
|
if (teamId === ALL_USERS) {
|
||||||
|
this.setState({
|
||||||
|
totalUsers: AnalyticsStore.getAllSystem()[StatTypes.TOTAL_USERS]
|
||||||
|
});
|
||||||
|
} else if (teamId === NO_TEAM) {
|
||||||
|
this.setState({
|
||||||
|
totalUsers: 0
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
totalUsers: TeamStore.getStats(teamId).total_member_count
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateUsersFromStore(teamId = this.state.teamId, term = this.state.term) {
|
||||||
|
if (term) {
|
||||||
|
if (teamId === this.state.teamId) {
|
||||||
|
// Search results aren't in the store, so manually update the users in them
|
||||||
|
const users = [...this.state.users];
|
||||||
|
|
||||||
|
for (let i = 0; i < users.length; i++) {
|
||||||
|
const user = users[i];
|
||||||
|
|
||||||
|
if (UserStore.hasProfile(user.id)) {
|
||||||
|
users[i] = UserStore.getProfile(user.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
users
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.doSearch(teamId, term, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (teamId === ALL_USERS) {
|
||||||
|
this.setState({users: UserStore.getProfileList(false, true)});
|
||||||
|
} else if (teamId === NO_TEAM) {
|
||||||
|
this.setState({users: UserStore.getProfileListWithoutTeam()});
|
||||||
|
} else {
|
||||||
|
this.setState({users: UserStore.getProfileListInTeam(this.state.teamId)});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadDataForTeam(teamId) {
|
||||||
|
if (teamId === ALL_USERS) {
|
||||||
|
loadProfiles(0, Constants.PROFILE_CHUNK_SIZE, this.loadComplete);
|
||||||
|
getStandardAnalytics();
|
||||||
|
} else if (teamId === NO_TEAM) {
|
||||||
|
loadProfilesWithoutTeam(0, Constants.PROFILE_CHUNK_SIZE, this.loadComplete);
|
||||||
|
} else {
|
||||||
|
loadProfilesAndTeamMembers(0, Constants.PROFILE_CHUNK_SIZE, teamId, this.loadComplete);
|
||||||
|
getTeamStats(teamId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadComplete() {
|
||||||
|
this.setState({loading: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTeamChange(e) {
|
||||||
|
this.setState({teamId: e.target.value});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTermChange(term) {
|
||||||
|
this.setState({term});
|
||||||
|
}
|
||||||
|
|
||||||
|
nextPage(page) {
|
||||||
|
// Paging isn't supported while searching
|
||||||
|
|
||||||
|
if (this.state.teamId === ALL_USERS) {
|
||||||
|
loadProfiles((page + 1) * USERS_PER_PAGE, USERS_PER_PAGE, this.loadComplete);
|
||||||
|
} else if (this.state.teamId === NO_TEAM) {
|
||||||
|
loadProfilesWithoutTeam(page + 1, USERS_PER_PAGE, this.loadComplete);
|
||||||
|
} else {
|
||||||
|
loadProfilesAndTeamMembers((page + 1) * USERS_PER_PAGE, USERS_PER_PAGE, this.state.teamId, this.loadComplete);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
search(term) {
|
||||||
|
if (term === '') {
|
||||||
|
this.updateUsersFromStore(this.state.teamId, term);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
loading: false
|
||||||
|
});
|
||||||
|
|
||||||
|
this.searchTimeoutId = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.doSearch(this.state.teamId, term);
|
||||||
|
}
|
||||||
|
|
||||||
|
doSearch(teamId, term, now = false) {
|
||||||
|
clearTimeout(this.searchTimeoutId);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
loading: true,
|
||||||
|
users: []
|
||||||
|
});
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
[UserSearchOptions.ALLOW_INACTIVE]: true
|
||||||
|
};
|
||||||
|
if (teamId === NO_TEAM) {
|
||||||
|
options[UserSearchOptions.WITHOUT_TEAM] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchTimeoutId = setTimeout(
|
||||||
|
() => {
|
||||||
|
searchUsers(
|
||||||
|
term,
|
||||||
|
teamId,
|
||||||
|
options,
|
||||||
|
(users) => {
|
||||||
|
if (searchTimeoutId !== this.searchTimeoutId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (users.length > 0) {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
users
|
||||||
|
});
|
||||||
|
} else if (term.length === USER_ID_LENGTH) {
|
||||||
|
// This term didn't match any users name, but it does look like it might be a user's ID
|
||||||
|
this.getUserById(term, searchTimeoutId);
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
loading: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
this.setState({
|
||||||
|
loading: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
now ? 0 : Constants.SEARCH_TIMEOUT_MILLISECONDS
|
||||||
|
);
|
||||||
|
|
||||||
|
this.searchTimeoutId = searchTimeoutId;
|
||||||
|
}
|
||||||
|
|
||||||
|
getUserById(id, searchTimeoutId) {
|
||||||
|
if (UserStore.hasProfile(id)) {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
users: [UserStore.getProfile(id)]
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
getUser(
|
||||||
|
id,
|
||||||
|
(user) => {
|
||||||
|
if (searchTimeoutId !== this.searchTimeoutId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
users: [user]
|
||||||
|
});
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
if (searchTimeoutId !== this.searchTimeoutId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
users: []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderFilterRow(doSearch) {
|
||||||
|
const teams = this.state.teams.map((team) => {
|
||||||
|
return (
|
||||||
|
<option
|
||||||
|
key={team.id}
|
||||||
|
value={team.id}
|
||||||
|
>
|
||||||
|
{team.display_name}
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='system-users__filter-row'>
|
||||||
|
<div className='system-users__filter'>
|
||||||
|
<input
|
||||||
|
ref='filter'
|
||||||
|
className='form-control filter-textbox'
|
||||||
|
placeholder={Utils.localizeMessage('filtered_user_list.search', 'Search users')}
|
||||||
|
onInput={doSearch}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<label>
|
||||||
|
<span className='system-users__team-filter-label'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='filtered_user_list.show'
|
||||||
|
defaultMessage='Filter:'
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<select
|
||||||
|
className='form-control system-users__team-filter'
|
||||||
|
onChange={this.handleTeamChange}
|
||||||
|
value={this.state.teamId}
|
||||||
|
>
|
||||||
|
<option value={ALL_USERS}>{Utils.localizeMessage('admin.system_users.allUsers', 'All Users')}</option>
|
||||||
|
<option value={NO_TEAM}>{Utils.localizeMessage('admin.system_users.noTeams', 'No Teams')}</option>
|
||||||
|
{teams}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let users = null;
|
||||||
|
if (!this.state.loading) {
|
||||||
|
users = this.state.users;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='wrapper--fixed'>
|
||||||
|
<h3 className='admin-console-header'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.system_users.title'
|
||||||
|
defaultMessage='{siteName} Users'
|
||||||
|
values={{
|
||||||
|
siteName: global.mm_config.SiteName
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</h3>
|
||||||
|
<div className='more-modal__list member-list-holder'>
|
||||||
|
<SystemUsersList
|
||||||
|
renderFilterRow={this.renderFilterRow}
|
||||||
|
search={this.search}
|
||||||
|
nextPage={this.nextPage}
|
||||||
|
users={users}
|
||||||
|
usersPerPage={USERS_PER_PAGE}
|
||||||
|
total={this.state.totalUsers}
|
||||||
|
teams={this.state.teams}
|
||||||
|
teamId={this.state.teamId}
|
||||||
|
term={this.state.term}
|
||||||
|
onTermChange={this.handleTermChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,38 +1,38 @@
|
|||||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// 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 TeamStore from 'stores/team_store.jsx';
|
||||||
|
import UserStore from 'stores/user_store.jsx';
|
||||||
|
|
||||||
import Constants from 'utils/constants.jsx';
|
import Constants from 'utils/constants.jsx';
|
||||||
import * as Utils from 'utils/utils.jsx';
|
import * as Utils from 'utils/utils.jsx';
|
||||||
import {updateUserRoles, updateActive} from 'actions/user_actions.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 {adminResetMfa} from 'actions/admin_actions.jsx';
|
||||||
|
|
||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
import React from 'react';
|
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) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.handleMakeMember = this.handleMakeMember.bind(this);
|
this.handleMakeMember = this.handleMakeMember.bind(this);
|
||||||
this.handleRemoveFromTeam = this.handleRemoveFromTeam.bind(this);
|
|
||||||
this.handleMakeActive = this.handleMakeActive.bind(this);
|
this.handleMakeActive = this.handleMakeActive.bind(this);
|
||||||
this.handleMakeNotActive = this.handleMakeNotActive.bind(this);
|
this.handleMakeNotActive = this.handleMakeNotActive.bind(this);
|
||||||
this.handleMakeTeamAdmin = this.handleMakeTeamAdmin.bind(this);
|
|
||||||
this.handleMakeSystemAdmin = this.handleMakeSystemAdmin.bind(this);
|
this.handleMakeSystemAdmin = this.handleMakeSystemAdmin.bind(this);
|
||||||
this.handleResetPassword = this.handleResetPassword.bind(this);
|
this.handleResetPassword = this.handleResetPassword.bind(this);
|
||||||
this.handleResetMfa = this.handleResetMfa.bind(this);
|
this.handleResetMfa = this.handleResetMfa.bind(this);
|
||||||
this.handleDemoteSystemAdmin = this.handleDemoteSystemAdmin.bind(this);
|
this.handleDemoteSystemAdmin = this.handleDemoteSystemAdmin.bind(this);
|
||||||
this.handleDemoteSubmit = this.handleDemoteSubmit.bind(this);
|
this.handleDemoteSubmit = this.handleDemoteSubmit.bind(this);
|
||||||
this.handleDemoteCancel = this.handleDemoteCancel.bind(this);
|
this.handleDemoteCancel = this.handleDemoteCancel.bind(this);
|
||||||
this.doMakeMember = this.doMakeMember.bind(this);
|
|
||||||
this.doMakeTeamAdmin = this.doMakeTeamAdmin.bind(this);
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
serverError: null,
|
serverError: null,
|
||||||
@@ -51,16 +51,6 @@ export default class AdminTeamMembersDropdown extends React.Component {
|
|||||||
this.setState({serverError: err.message});
|
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) {
|
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) {
|
handleMakeActive(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
updateActive(this.props.user.id, true, null,
|
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) {
|
handleMakeSystemAdmin(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
@@ -174,8 +131,6 @@ export default class AdminTeamMembersDropdown extends React.Component {
|
|||||||
handleDemoteSubmit() {
|
handleDemoteSubmit() {
|
||||||
if (this.state.role === 'member') {
|
if (this.state.role === 'member') {
|
||||||
this.doMakeMember();
|
this.doMakeMember();
|
||||||
} else {
|
|
||||||
this.doMakeTeamAdmin();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const teamUrl = TeamStore.getCurrentTeamUrl();
|
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;
|
const user = this.props.user;
|
||||||
if (!user || !teamMember) {
|
if (!user) {
|
||||||
return <div/>;
|
return <div/>;
|
||||||
}
|
}
|
||||||
let currentRoles = (
|
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)) {
|
if (user.roles.length > 0 && Utils.isSystemAdmin(user.roles)) {
|
||||||
currentRoles = (
|
currentRoles = (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
@@ -228,8 +173,7 @@ export default class AdminTeamMembersDropdown extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const me = UserStore.getCurrentUser();
|
const me = UserStore.getCurrentUser();
|
||||||
let showMakeMember = Utils.isAdmin(teamMember.roles) || Utils.isSystemAdmin(user.roles);
|
let showMakeMember = Utils.isSystemAdmin(user.roles);
|
||||||
let showMakeAdmin = !Utils.isAdmin(teamMember.roles) && !Utils.isSystemAdmin(user.roles);
|
|
||||||
let showMakeSystemAdmin = !Utils.isSystemAdmin(user.roles);
|
let showMakeSystemAdmin = !Utils.isSystemAdmin(user.roles);
|
||||||
let showMakeActive = false;
|
let showMakeActive = false;
|
||||||
let showMakeNotActive = !Utils.isSystemAdmin(user.roles);
|
let showMakeNotActive = !Utils.isSystemAdmin(user.roles);
|
||||||
@@ -244,7 +188,6 @@ export default class AdminTeamMembersDropdown extends React.Component {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
showMakeMember = false;
|
showMakeMember = false;
|
||||||
showMakeAdmin = false;
|
|
||||||
showMakeSystemAdmin = false;
|
showMakeSystemAdmin = false;
|
||||||
showMakeActive = true;
|
showMakeActive = true;
|
||||||
showMakeNotActive = false;
|
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;
|
let makeMember = null;
|
||||||
if (showMakeMember) {
|
if (showMakeMember) {
|
||||||
makeMember = (
|
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 = '';
|
let menuClass = '';
|
||||||
if (disableActivationToggle) {
|
if (disableActivationToggle) {
|
||||||
menuClass = 'disabled';
|
menuClass = 'disabled';
|
||||||
@@ -493,8 +400,6 @@ export default class AdminTeamMembersDropdown extends React.Component {
|
|||||||
className='dropdown-menu member-menu'
|
className='dropdown-menu member-menu'
|
||||||
role='menu'
|
role='menu'
|
||||||
>
|
>
|
||||||
{removeFromTeam}
|
|
||||||
{makeAdmin}
|
|
||||||
{makeMember}
|
{makeMember}
|
||||||
{makeActive}
|
{makeActive}
|
||||||
{makeNotActive}
|
{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
|
|
||||||
};
|
|
||||||
232
webapp/components/admin_console/system_users/system_users_list.jsx
Обычный файл
232
webapp/components/admin_console/system_users/system_users_list.jsx
Обычный файл
@@ -0,0 +1,232 @@
|
|||||||
|
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
|
||||||
|
|
||||||
|
import ResetPasswordModal from 'components/admin_console/reset_password_modal.jsx';
|
||||||
|
import SearchableUserList from 'components/searchable_user_list/searchable_user_list.jsx';
|
||||||
|
|
||||||
|
import {getUser} from 'utils/async_client.jsx';
|
||||||
|
import {Constants} from 'utils/constants.jsx';
|
||||||
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
|
||||||
|
import SystemUsersDropdown from './system_users_dropdown.jsx';
|
||||||
|
|
||||||
|
export default class SystemUsersList extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
users: React.PropTypes.arrayOf(React.PropTypes.object),
|
||||||
|
usersPerPage: React.PropTypes.number,
|
||||||
|
total: React.PropTypes.number,
|
||||||
|
nextPage: React.PropTypes.func,
|
||||||
|
search: React.PropTypes.func.isRequired,
|
||||||
|
focusOnMount: React.PropTypes.bool,
|
||||||
|
renderFilterRow: React.PropTypes.func,
|
||||||
|
|
||||||
|
teamId: React.PropTypes.string.isRequired,
|
||||||
|
term: React.PropTypes.string.isRequired,
|
||||||
|
onTermChange: React.PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.nextPage = this.nextPage.bind(this);
|
||||||
|
this.previousPage = this.previousPage.bind(this);
|
||||||
|
this.search = this.search.bind(this);
|
||||||
|
|
||||||
|
this.doPasswordReset = this.doPasswordReset.bind(this);
|
||||||
|
this.doPasswordResetDismiss = this.doPasswordResetDismiss.bind(this);
|
||||||
|
this.doPasswordResetSubmit = this.doPasswordResetSubmit.bind(this);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
page: 0,
|
||||||
|
|
||||||
|
showPasswordModal: false,
|
||||||
|
user: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
if (nextProps.teamId !== this.props.teamId) {
|
||||||
|
this.setState({page: 0});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nextPage() {
|
||||||
|
this.setState({page: this.state.page + 1});
|
||||||
|
|
||||||
|
this.props.nextPage(this.state.page + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
previousPage() {
|
||||||
|
this.setState({page: this.state.page - 1});
|
||||||
|
}
|
||||||
|
|
||||||
|
search(term) {
|
||||||
|
this.props.search(term);
|
||||||
|
|
||||||
|
if (term !== '') {
|
||||||
|
this.setState({page: 0});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
doPasswordReset(user) {
|
||||||
|
this.setState({
|
||||||
|
showPasswordModal: true,
|
||||||
|
user
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
doPasswordResetDismiss() {
|
||||||
|
this.setState({
|
||||||
|
showPasswordModal: false,
|
||||||
|
user: null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
doPasswordResetSubmit(user) {
|
||||||
|
getUser(user.id);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
showPasswordModal: false,
|
||||||
|
user: null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getInfoForUser(user) {
|
||||||
|
const info = [];
|
||||||
|
|
||||||
|
if (user.auth_service) {
|
||||||
|
let service;
|
||||||
|
if (user.auth_service === Constants.LDAP_SERVICE || user.auth_service === Constants.SAML_SERVICE) {
|
||||||
|
service = user.auth_service.toUpperCase();
|
||||||
|
} else {
|
||||||
|
service = Utils.toTitleCase(user.auth_service);
|
||||||
|
}
|
||||||
|
|
||||||
|
info.push(
|
||||||
|
<FormattedHTMLMessage
|
||||||
|
key='admin.user_item.authServiceNotEmail'
|
||||||
|
id='admin.user_item.authServiceNotEmail'
|
||||||
|
defaultMessage='<strong>Sign-in Method:</strong> {service}'
|
||||||
|
values={{
|
||||||
|
service
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
info.push(
|
||||||
|
<FormattedHTMLMessage
|
||||||
|
key='admin.user_item.authServiceEmail'
|
||||||
|
id='admin.user_item.authServiceEmail'
|
||||||
|
defaultMessage='<strong>Sign-in Method:</strong> Email'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mfaEnabled = global.window.mm_license.IsLicensed === 'true' &&
|
||||||
|
global.window.mm_license.MFA === 'true' &&
|
||||||
|
global.window.mm_config.EnableMultifactorAuthentication === 'true';
|
||||||
|
if (mfaEnabled) {
|
||||||
|
info.push(', ');
|
||||||
|
|
||||||
|
if (user.mfa_active) {
|
||||||
|
info.push(
|
||||||
|
<FormattedHTMLMessage
|
||||||
|
key='admin.user_item.mfaYes'
|
||||||
|
id='admin.user_item.mfaYes'
|
||||||
|
defaultMessage='<strong>MFA</strong>: Yes'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
info.push(
|
||||||
|
<FormattedHTMLMessage
|
||||||
|
key='admin.user_item.mfaNo'
|
||||||
|
id='admin.user_item.mfaNo'
|
||||||
|
defaultMessage='<strong>MFA</strong>: No'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCount(count, total, startCount, endCount, isSearch) {
|
||||||
|
if (total) {
|
||||||
|
if (isSearch) {
|
||||||
|
return (
|
||||||
|
<FormattedMessage
|
||||||
|
id='system_users_list.countSearch'
|
||||||
|
defaultMessage='{count, number} {count, plural, one {user} other {users}} of {total} total'
|
||||||
|
values={{
|
||||||
|
count,
|
||||||
|
total
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (startCount !== 0 || endCount !== total) {
|
||||||
|
return (
|
||||||
|
<FormattedMessage
|
||||||
|
id='system_users_list.countPage'
|
||||||
|
defaultMessage='{startCount, number} - {endCount, number} {count, plural, one {user} other {users}} of {total} total'
|
||||||
|
values={{
|
||||||
|
count,
|
||||||
|
startCount: startCount + 1,
|
||||||
|
endCount,
|
||||||
|
total
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormattedMessage
|
||||||
|
id='system_users_list.count'
|
||||||
|
defaultMessage='{count, number} {count, plural, one {user} other {users}}'
|
||||||
|
values={{
|
||||||
|
count
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const extraInfo = {};
|
||||||
|
if (this.props.users) {
|
||||||
|
for (const user of this.props.users) {
|
||||||
|
extraInfo[user.id] = this.getInfoForUser(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SearchableUserList
|
||||||
|
{...this.props}
|
||||||
|
renderCount={this.renderCount}
|
||||||
|
extraInfo={extraInfo}
|
||||||
|
actions={[SystemUsersDropdown]}
|
||||||
|
actionProps={{
|
||||||
|
doPasswordReset: this.doPasswordReset
|
||||||
|
}}
|
||||||
|
nextPage={this.nextPage}
|
||||||
|
previousPage={this.previousPage}
|
||||||
|
search={this.search}
|
||||||
|
page={this.state.page}
|
||||||
|
term={this.props.term}
|
||||||
|
onTermChange={this.props.onTermChange}
|
||||||
|
/>
|
||||||
|
<ResetPasswordModal
|
||||||
|
user={this.state.user}
|
||||||
|
show={this.state.showPasswordModal}
|
||||||
|
onModalSubmit={this.doPasswordResetSubmit}
|
||||||
|
onModalDismissed={this.doPasswordResetDismiss}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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() {
|
renderTitle() {
|
||||||
return (
|
return (
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.general.usersAndTeams'
|
id='admin.general.usersAndTeams'
|
||||||
defaultMessage='Users and Teams'
|
defaultMessage='Users and Teams'
|
||||||
/>
|
/>
|
||||||
</h3>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,12 +50,10 @@ export default class WebrtcSettings extends AdminSettings {
|
|||||||
|
|
||||||
renderTitle() {
|
renderTitle() {
|
||||||
return (
|
return (
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.integrations.webrtc'
|
id='admin.integrations.webrtc'
|
||||||
defaultMessage='Mattermost WebRTC (Beta)'
|
defaultMessage='Mattermost WebRTC (Beta)'
|
||||||
/>
|
/>
|
||||||
</h3>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,14 +21,33 @@ export default class LineChart extends React.Component {
|
|||||||
this.initChart();
|
this.initChart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUpdate(nextProps) {
|
||||||
|
const willHaveData = nextProps.data && nextProps.data.labels.length > 0;
|
||||||
|
const hasChart = Boolean(this.chart);
|
||||||
|
|
||||||
|
if (!willHaveData && hasChart) {
|
||||||
|
// Clean up the rendered chart before we render and destroy its context
|
||||||
|
this.chart.destroy();
|
||||||
|
this.chart = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (!Utils.areObjectsEqual(prevProps.data, this.props.data) || !Utils.areObjectsEqual(prevProps.options, this.props.options)) {
|
if (Utils.areObjectsEqual(prevProps.data, this.props.data) && Utils.areObjectsEqual(prevProps.options, this.props.options)) {
|
||||||
this.initChart(true);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasData = this.props.data && this.props.data.labels.length > 0;
|
||||||
|
const hasChart = Boolean(this.chart);
|
||||||
|
|
||||||
|
if (hasData) {
|
||||||
|
// Update the rendered chart or initialize it as necessary
|
||||||
|
this.initChart(hasChart);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
if (this.chart && this.refs.canvas) {
|
if (this.chart) {
|
||||||
this.chart.destroy();
|
this.chart.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,9 +56,11 @@ export default class LineChart extends React.Component {
|
|||||||
if (!this.refs.canvas) {
|
if (!this.refs.canvas) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var el = ReactDOM.findDOMNode(this.refs.canvas);
|
var el = ReactDOM.findDOMNode(this.refs.canvas);
|
||||||
var ctx = el.getContext('2d');
|
var ctx = el.getContext('2d');
|
||||||
this.chart = new Chart(ctx, {type: 'line', data: this.props.data, options: this.props.options || {}}); // eslint-disable-line new-cap
|
this.chart = new Chart(ctx, {type: 'line', data: this.props.data, options: this.props.options || {}}); // eslint-disable-line new-cap
|
||||||
|
|
||||||
if (update) {
|
if (update) {
|
||||||
this.chart.update();
|
this.chart.update();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -409,7 +409,7 @@ class SystemAnalytics extends React.Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='wrapper--fixed team_statistics'>
|
<div className='wrapper--fixed team_statistics'>
|
||||||
<h3>
|
<h3 className='admin-console-header'>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='analytics.system.title'
|
id='analytics.system.title'
|
||||||
defaultMessage='System Statistics'
|
defaultMessage='System Statistics'
|
||||||
|
|||||||
@@ -1,40 +1,43 @@
|
|||||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {FormattedDate, FormattedMessage, FormattedHTMLMessage} from 'react-intl';
|
||||||
|
|
||||||
import Banner from 'components/admin_console/banner.jsx';
|
import Banner from 'components/admin_console/banner.jsx';
|
||||||
import LineChart from './line_chart.jsx';
|
import LoadingScreen from 'components/loading_screen.jsx';
|
||||||
import StatisticCount from './statistic_count.jsx';
|
|
||||||
import TableChart from './table_chart.jsx';
|
|
||||||
|
|
||||||
import AdminStore from 'stores/admin_store.jsx';
|
import AdminStore from 'stores/admin_store.jsx';
|
||||||
import AnalyticsStore from 'stores/analytics_store.jsx';
|
import AnalyticsStore from 'stores/analytics_store.jsx';
|
||||||
|
import BrowserStore from 'stores/browser_store.jsx';
|
||||||
|
|
||||||
import * as Utils from 'utils/utils.jsx';
|
|
||||||
import * as AsyncClient from 'utils/async_client.jsx';
|
import * as AsyncClient from 'utils/async_client.jsx';
|
||||||
import Constants from 'utils/constants.jsx';
|
import {StatTypes} from 'utils/constants.jsx';
|
||||||
const StatTypes = Constants.StatTypes;
|
import {convertTeamMapToList} from 'utils/team_utils.jsx';
|
||||||
|
|
||||||
|
import LineChart from './line_chart.jsx';
|
||||||
|
import StatisticCount from './statistic_count.jsx';
|
||||||
|
import TableChart from './table_chart.jsx';
|
||||||
import {formatPostsPerDayData, formatUsersWithPostsPerDayData} from './system_analytics.jsx';
|
import {formatPostsPerDayData, formatUsersWithPostsPerDayData} from './system_analytics.jsx';
|
||||||
import {FormattedMessage, FormattedDate, FormattedHTMLMessage} from 'react-intl';
|
|
||||||
|
|
||||||
import React from 'react';
|
const LAST_ANALYTICS_TEAM = 'last_analytics_team';
|
||||||
|
|
||||||
export default class TeamAnalytics extends React.Component {
|
export default class TeamAnalytics extends React.Component {
|
||||||
static get propTypes() {
|
|
||||||
return {
|
|
||||||
params: React.PropTypes.object.isRequired
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.onChange = this.onChange.bind(this);
|
this.onChange = this.onChange.bind(this);
|
||||||
this.onAllTeamsChange = this.onAllTeamsChange.bind(this);
|
this.onAllTeamsChange = this.onAllTeamsChange.bind(this);
|
||||||
|
this.handleTeamChange = this.handleTeamChange.bind(this);
|
||||||
|
|
||||||
|
const teams = convertTeamMapToList(AdminStore.getAllTeams());
|
||||||
|
const teamId = BrowserStore.getGlobalItem(LAST_ANALYTICS_TEAM, teams.length > 0 ? teams[0].id : '');
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
team: AdminStore.getTeam(this.props.params.team),
|
teams,
|
||||||
stats: AnalyticsStore.getAllTeam(this.props.params.team)
|
teamId,
|
||||||
|
team: AdminStore.getTeam(teamId),
|
||||||
|
stats: AnalyticsStore.getAllTeam(teamId)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +45,19 @@ export default class TeamAnalytics extends React.Component {
|
|||||||
AnalyticsStore.addChangeListener(this.onChange);
|
AnalyticsStore.addChangeListener(this.onChange);
|
||||||
AdminStore.addAllTeamsChangeListener(this.onAllTeamsChange);
|
AdminStore.addAllTeamsChangeListener(this.onAllTeamsChange);
|
||||||
|
|
||||||
this.getData(this.props.params.team);
|
if (this.state.teamId !== '') {
|
||||||
|
this.getData(this.state.teamId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.teams.length === 0) {
|
||||||
|
AsyncClient.getAllTeams();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUpdate(nextProps, nextState) {
|
||||||
|
if (nextState.teamId !== this.state.teamId) {
|
||||||
|
this.getData(nextState.teamId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getData(id) {
|
getData(id) {
|
||||||
@@ -57,40 +72,60 @@ export default class TeamAnalytics extends React.Component {
|
|||||||
AdminStore.removeAllTeamsChangeListener(this.onAllTeamsChange);
|
AdminStore.removeAllTeamsChangeListener(this.onAllTeamsChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
|
||||||
this.getData(nextProps.params.team);
|
|
||||||
this.setState({
|
|
||||||
stats: AnalyticsStore.getAllTeam(nextProps.params.team)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
|
||||||
if (!Utils.areObjectsEqual(nextState.stats, this.state.stats)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Utils.areObjectsEqual(nextProps.params.team, this.props.params.team)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
onChange() {
|
onChange() {
|
||||||
this.setState({
|
this.setState({
|
||||||
stats: AnalyticsStore.getAllTeam(this.props.params.team)
|
stats: AnalyticsStore.getAllTeam(this.state.teamId)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onAllTeamsChange() {
|
onAllTeamsChange() {
|
||||||
|
const teams = convertTeamMapToList(AdminStore.getAllTeams());
|
||||||
|
|
||||||
|
if (teams.length > 0) {
|
||||||
|
if (this.state.teamId) {
|
||||||
this.setState({
|
this.setState({
|
||||||
team: AdminStore.getTeam(this.props.params.team)
|
team: AdminStore.getTeam(this.state.teamId)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
teamId: teams[0].id,
|
||||||
|
team: teams[0]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
teams
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleTeamChange(e) {
|
||||||
|
const teamId = e.target.value;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
teamId,
|
||||||
|
team: AdminStore.getTeam(teamId)
|
||||||
|
});
|
||||||
|
|
||||||
|
BrowserStore.setGlobalItem(LAST_ANALYTICS_TEAM, teamId);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (!this.state.team || !this.state.stats) {
|
if (this.state.teams.length === 0 || !this.state.team || !this.state.stats) {
|
||||||
return null;
|
return <LoadingScreen/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.teamId === '') {
|
||||||
|
return (
|
||||||
|
<Banner
|
||||||
|
description={
|
||||||
|
<FormattedMessage
|
||||||
|
id='analytics.team.noTeams'
|
||||||
|
defaultMessage='There are no teams on this server for which to view statistics.'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const stats = this.state.stats;
|
const stats = this.state.stats;
|
||||||
@@ -129,6 +164,7 @@ export default class TeamAnalytics extends React.Component {
|
|||||||
postTotalGraph = (
|
postTotalGraph = (
|
||||||
<div className='row'>
|
<div className='row'>
|
||||||
<LineChart
|
<LineChart
|
||||||
|
key={this.state.team.id}
|
||||||
title={
|
title={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='analytics.team.totalPosts'
|
id='analytics.team.totalPosts'
|
||||||
@@ -150,6 +186,7 @@ export default class TeamAnalytics extends React.Component {
|
|||||||
userActiveGraph = (
|
userActiveGraph = (
|
||||||
<div className='row'>
|
<div className='row'>
|
||||||
<LineChart
|
<LineChart
|
||||||
|
key={this.state.team.id}
|
||||||
title={
|
title={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='analytics.team.activeUsers'
|
id='analytics.team.activeUsers'
|
||||||
@@ -172,17 +209,41 @@ export default class TeamAnalytics extends React.Component {
|
|||||||
const recentActiveUsers = formatRecentUsersData(stats[StatTypes.RECENTLY_ACTIVE_USERS]);
|
const recentActiveUsers = formatRecentUsersData(stats[StatTypes.RECENTLY_ACTIVE_USERS]);
|
||||||
const newlyCreatedUsers = formatNewUsersData(stats[StatTypes.NEWLY_CREATED_USERS]);
|
const newlyCreatedUsers = formatNewUsersData(stats[StatTypes.NEWLY_CREATED_USERS]);
|
||||||
|
|
||||||
|
const teams = this.state.teams.map((team) => {
|
||||||
|
return (
|
||||||
|
<option
|
||||||
|
key={team.id}
|
||||||
|
value={team.id}
|
||||||
|
>
|
||||||
|
{team.display_name}
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='wrapper--fixed team_statistics'>
|
<div className='wrapper--fixed team_statistics'>
|
||||||
|
<div className='admin-console-header team-statistics__header-row'>
|
||||||
|
<div className='team-statistics__header'>
|
||||||
<h3>
|
<h3>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='analytics.team.title'
|
id='analytics.team.title'
|
||||||
defaultMessage='Team Statistics for {team}'
|
defaultMessage='Team Statistics for {team}'
|
||||||
values={{
|
values={{
|
||||||
team: this.state.team.name
|
team: this.state.team.display_name
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</h3>
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div className='team-statistics__team-filter'>
|
||||||
|
<select
|
||||||
|
className='form-control team-statistics__team-filter__dropdown'
|
||||||
|
onChange={this.handleTeamChange}
|
||||||
|
value={this.state.teamId}
|
||||||
|
>
|
||||||
|
{teams}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{banner}
|
{banner}
|
||||||
<div className='row'>
|
<div className='row'>
|
||||||
<StatisticCount
|
<StatisticCount
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
import ChannelInviteButton from './channel_invite_button.jsx';
|
import ChannelInviteButton from './channel_invite_button.jsx';
|
||||||
import SearchableUserList from './searchable_user_list.jsx';
|
import SearchableUserList from 'components/searchable_user_list/searchable_user_list_container.jsx';
|
||||||
import LoadingScreen from './loading_screen.jsx';
|
import LoadingScreen from './loading_screen.jsx';
|
||||||
|
|
||||||
import ChannelStore from 'stores/channel_store.jsx';
|
import ChannelStore from 'stores/channel_store.jsx';
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
import ChannelMembersDropdown from 'components/channel_members_dropdown.jsx';
|
import ChannelMembersDropdown from 'components/channel_members_dropdown.jsx';
|
||||||
import SearchableUserList from 'components/searchable_user_list.jsx';
|
import SearchableUserList from 'components/searchable_user_list/searchable_user_list_container.jsx';
|
||||||
|
|
||||||
import ChannelStore from 'stores/channel_store.jsx';
|
import ChannelStore from 'stores/channel_store.jsx';
|
||||||
import UserStore from 'stores/user_store.jsx';
|
import UserStore from 'stores/user_store.jsx';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
import SearchableUserList from 'components/searchable_user_list.jsx';
|
import SearchableUserList from 'components/searchable_user_list/searchable_user_list_container.jsx';
|
||||||
import TeamMembersDropdown from 'components/team_members_dropdown.jsx';
|
import TeamMembersDropdown from 'components/team_members_dropdown.jsx';
|
||||||
|
|
||||||
import UserStore from 'stores/user_store.jsx';
|
import UserStore from 'stores/user_store.jsx';
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ import RemovedFromChannelModal from 'components/removed_from_channel_modal.jsx';
|
|||||||
import ImportThemeModal from 'components/user_settings/import_theme_modal.jsx';
|
import ImportThemeModal from 'components/user_settings/import_theme_modal.jsx';
|
||||||
import InviteMemberModal from 'components/invite_member_modal.jsx';
|
import InviteMemberModal from 'components/invite_member_modal.jsx';
|
||||||
import LeaveTeamModal from 'components/leave_team_modal.jsx';
|
import LeaveTeamModal from 'components/leave_team_modal.jsx';
|
||||||
import SelectTeamModal from 'components/admin_console/select_team_modal.jsx';
|
|
||||||
|
|
||||||
import iNoBounce from 'inobounce';
|
import iNoBounce from 'inobounce';
|
||||||
import * as UserAgent from 'utils/user_agent.jsx';
|
import * as UserAgent from 'utils/user_agent.jsx';
|
||||||
@@ -212,7 +211,6 @@ export default class NeedsTeam extends React.Component {
|
|||||||
<EditPostModal/>
|
<EditPostModal/>
|
||||||
<DeletePostModal/>
|
<DeletePostModal/>
|
||||||
<RemovedFromChannelModal/>
|
<RemovedFromChannelModal/>
|
||||||
<SelectTeamModal/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,31 +1,61 @@
|
|||||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
import UserList from 'components/user_list.jsx';
|
import UserList from 'components/user_list.jsx';
|
||||||
|
|
||||||
import * as Utils from 'utils/utils.jsx';
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
|
||||||
import $ from 'jquery';
|
|
||||||
import React from 'react';
|
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import {FormattedMessage} from 'react-intl';
|
|
||||||
|
|
||||||
const NEXT_BUTTON_TIMEOUT = 500;
|
const NEXT_BUTTON_TIMEOUT = 500;
|
||||||
|
|
||||||
export default class SearchableUserList extends React.Component {
|
export default class SearchableUserList extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
users: React.PropTypes.arrayOf(React.PropTypes.object),
|
||||||
|
usersPerPage: React.PropTypes.number,
|
||||||
|
total: React.PropTypes.number,
|
||||||
|
extraInfo: React.PropTypes.object,
|
||||||
|
nextPage: React.PropTypes.func.isRequired,
|
||||||
|
previousPage: React.PropTypes.func.isRequired,
|
||||||
|
search: React.PropTypes.func.isRequired,
|
||||||
|
actions: React.PropTypes.arrayOf(React.PropTypes.func),
|
||||||
|
actionProps: React.PropTypes.object,
|
||||||
|
actionUserProps: React.PropTypes.object,
|
||||||
|
focusOnMount: React.PropTypes.bool,
|
||||||
|
renderCount: React.PropTypes.func,
|
||||||
|
renderFilterRow: React.PropTypes.func,
|
||||||
|
|
||||||
|
page: React.PropTypes.number.isRequired,
|
||||||
|
term: React.PropTypes.string.isRequired,
|
||||||
|
onTermChange: React.PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
users: [],
|
||||||
|
usersPerPage: 50, // eslint-disable-line no-magic-numbers
|
||||||
|
extraInfo: {},
|
||||||
|
actions: [],
|
||||||
|
actionProps: {},
|
||||||
|
actionUserProps: {},
|
||||||
|
showTeamToggle: false,
|
||||||
|
focusOnMount: false
|
||||||
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.nextPage = this.nextPage.bind(this);
|
this.nextPage = this.nextPage.bind(this);
|
||||||
this.previousPage = this.previousPage.bind(this);
|
this.previousPage = this.previousPage.bind(this);
|
||||||
this.doSearch = this.doSearch.bind(this);
|
|
||||||
this.focusSearchBar = this.focusSearchBar.bind(this);
|
this.focusSearchBar = this.focusSearchBar.bind(this);
|
||||||
|
|
||||||
|
this.handleInput = this.handleInput.bind(this);
|
||||||
|
|
||||||
|
this.renderCount = this.renderCount.bind(this);
|
||||||
|
|
||||||
this.nextTimeoutId = 0;
|
this.nextTimeoutId = 0;
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
page: 0,
|
|
||||||
search: false,
|
|
||||||
nextDisabled: false
|
nextDisabled: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -34,10 +64,11 @@ export default class SearchableUserList extends React.Component {
|
|||||||
this.focusSearchBar();
|
this.focusSearchBar();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
componentDidUpdate(prevProps) {
|
||||||
if (this.state.page !== prevState.page) {
|
if (this.props.page !== prevProps.page || this.props.term !== prevProps.term) {
|
||||||
$(ReactDOM.findDOMNode(this.refs.userList)).scrollTop(0);
|
this.refs.userList.scrollToTop();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.focusSearchBar();
|
this.focusSearchBar();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,14 +78,17 @@ export default class SearchableUserList extends React.Component {
|
|||||||
|
|
||||||
nextPage(e) {
|
nextPage(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.setState({page: this.state.page + 1, nextDisabled: true});
|
|
||||||
|
this.setState({nextDisabled: true});
|
||||||
this.nextTimeoutId = setTimeout(() => this.setState({nextDisabled: false}), NEXT_BUTTON_TIMEOUT);
|
this.nextTimeoutId = setTimeout(() => this.setState({nextDisabled: false}), NEXT_BUTTON_TIMEOUT);
|
||||||
this.props.nextPage(this.state.page + 1);
|
|
||||||
|
this.props.nextPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
previousPage(e) {
|
previousPage(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.setState({page: this.state.page - 1});
|
|
||||||
|
this.props.previousPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
focusSearchBar() {
|
focusSearchBar() {
|
||||||
@@ -63,41 +97,74 @@ export default class SearchableUserList extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
doSearch() {
|
handleInput(e) {
|
||||||
const term = this.refs.filter.value;
|
this.props.onTermChange(e.target.value);
|
||||||
this.props.search(term);
|
this.props.search(e.target.value);
|
||||||
if (term === '') {
|
|
||||||
this.setState({page: 0, search: false});
|
|
||||||
} else {
|
|
||||||
this.setState({search: true});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderCount(users) {
|
||||||
|
if (!users) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const count = users.length;
|
||||||
|
const total = this.props.total;
|
||||||
|
const isSearch = Boolean(this.props.term);
|
||||||
|
|
||||||
|
let startCount;
|
||||||
|
let endCount;
|
||||||
|
if (isSearch) {
|
||||||
|
startCount = -1;
|
||||||
|
endCount = -1;
|
||||||
|
} else {
|
||||||
|
startCount = this.props.page * this.props.usersPerPage;
|
||||||
|
endCount = startCount + count;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props.renderCount) {
|
||||||
|
return this.props.renderCount(count, this.props.total, startCount, endCount, isSearch);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props.total) {
|
||||||
|
if (isSearch) {
|
||||||
|
return (
|
||||||
|
<FormattedMessage
|
||||||
|
id='filtered_user_list.countTotal'
|
||||||
|
defaultMessage='{count} {count, plural, =0 {0 members} one {member} other {members}} of {total} total'
|
||||||
|
values={{
|
||||||
|
count,
|
||||||
|
total
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormattedMessage
|
||||||
|
id='filtered_user_list.countTotalPage'
|
||||||
|
defaultMessage='{startCount, number} - {endCount, number} {count, plural, =0 {0 members} one {member} other {members}} of {total} total'
|
||||||
|
values={{
|
||||||
|
count,
|
||||||
|
startCount: startCount + 1,
|
||||||
|
endCount,
|
||||||
|
total
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let nextButton;
|
let nextButton;
|
||||||
let previousButton;
|
let previousButton;
|
||||||
let usersToDisplay;
|
let usersToDisplay;
|
||||||
let count;
|
|
||||||
|
|
||||||
if (this.props.users == null) {
|
if (this.props.term || !this.props.users) {
|
||||||
usersToDisplay = this.props.users;
|
usersToDisplay = this.props.users;
|
||||||
} else if (this.state.search || this.props.users == null) {
|
} else if (!this.props.term) {
|
||||||
usersToDisplay = this.props.users;
|
const pageStart = this.props.page * this.props.usersPerPage;
|
||||||
|
|
||||||
if (this.props.total) {
|
|
||||||
count = (
|
|
||||||
<FormattedMessage
|
|
||||||
id='filtered_user_list.countTotal'
|
|
||||||
defaultMessage='{count} {count, plural, =0 {0 members} one {member} other {members}} of {total} total'
|
|
||||||
values={{
|
|
||||||
count: usersToDisplay.length || 0,
|
|
||||||
total: this.props.total
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const pageStart = this.state.page * this.props.usersPerPage;
|
|
||||||
const pageEnd = pageStart + this.props.usersPerPage;
|
const pageEnd = pageStart + this.props.usersPerPage;
|
||||||
usersToDisplay = this.props.users.slice(pageStart, pageEnd);
|
usersToDisplay = this.props.users.slice(pageStart, pageEnd);
|
||||||
|
|
||||||
@@ -116,7 +183,7 @@ export default class SearchableUserList extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.page > 0) {
|
if (this.props.page > 0) {
|
||||||
previousButton = (
|
previousButton = (
|
||||||
<button
|
<button
|
||||||
className='btn btn-default filter-control filter-control__prev'
|
className='btn btn-default filter-control filter-control__prev'
|
||||||
@@ -129,46 +196,38 @@ export default class SearchableUserList extends React.Component {
|
|||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.total) {
|
|
||||||
const startCount = this.state.page * this.props.usersPerPage;
|
|
||||||
const endCount = startCount + usersToDisplay.length;
|
|
||||||
|
|
||||||
count = (
|
|
||||||
<FormattedMessage
|
|
||||||
id='filtered_user_list.countTotalPage'
|
|
||||||
defaultMessage='{startCount, number} - {endCount, number} {count, plural, =0 {0 members} one {member} other {members}} of {total} total'
|
|
||||||
values={{
|
|
||||||
count: usersToDisplay.length,
|
|
||||||
startCount: startCount + 1,
|
|
||||||
endCount,
|
|
||||||
total: this.props.total
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
let filterRow;
|
||||||
<div className='filtered-user-list'>
|
if (this.props.renderFilterRow) {
|
||||||
<div className='filter-row'>
|
filterRow = this.props.renderFilterRow(this.handleInput);
|
||||||
|
} else {
|
||||||
|
filterRow = (
|
||||||
<div className='col-xs-12'>
|
<div className='col-xs-12'>
|
||||||
<input
|
<input
|
||||||
ref='filter'
|
ref='filter'
|
||||||
className='form-control filter-textbox'
|
className='form-control filter-textbox'
|
||||||
placeholder={Utils.localizeMessage('filtered_user_list.search', 'Search users')}
|
placeholder={Utils.localizeMessage('filtered_user_list.search', 'Search users')}
|
||||||
onInput={this.doSearch}
|
value={this.props.term}
|
||||||
|
onInput={this.handleInput}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='filtered-user-list'>
|
||||||
|
<div className='filter-row'>
|
||||||
|
{filterRow}
|
||||||
<div className='col-sm-12'>
|
<div className='col-sm-12'>
|
||||||
<span className='member-count pull-left'>{count}</span>
|
<span className='member-count pull-left'>{this.renderCount(usersToDisplay)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
ref='userList'
|
|
||||||
className='more-modal__list'
|
className='more-modal__list'
|
||||||
>
|
>
|
||||||
<UserList
|
<UserList
|
||||||
|
ref='userList'
|
||||||
users={usersToDisplay}
|
users={usersToDisplay}
|
||||||
extraInfo={this.props.extraInfo}
|
extraInfo={this.props.extraInfo}
|
||||||
actions={this.props.actions}
|
actions={this.props.actions}
|
||||||
@@ -184,27 +243,3 @@ export default class SearchableUserList extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SearchableUserList.defaultProps = {
|
|
||||||
users: [],
|
|
||||||
usersPerPage: 50, //eslint-disable-line no-magic-numbers
|
|
||||||
extraInfo: {},
|
|
||||||
actions: [],
|
|
||||||
actionProps: {},
|
|
||||||
actionUserProps: {},
|
|
||||||
showTeamToggle: false,
|
|
||||||
focusOnMount: false
|
|
||||||
};
|
|
||||||
|
|
||||||
SearchableUserList.propTypes = {
|
|
||||||
users: React.PropTypes.arrayOf(React.PropTypes.object),
|
|
||||||
usersPerPage: React.PropTypes.number,
|
|
||||||
total: React.PropTypes.number,
|
|
||||||
extraInfo: React.PropTypes.object,
|
|
||||||
nextPage: React.PropTypes.func.isRequired,
|
|
||||||
search: React.PropTypes.func.isRequired,
|
|
||||||
actions: React.PropTypes.arrayOf(React.PropTypes.func),
|
|
||||||
actionProps: React.PropTypes.object,
|
|
||||||
actionUserProps: React.PropTypes.object,
|
|
||||||
focusOnMount: React.PropTypes.bool.isRequired
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import SearchableUserList from './searchable_user_list.jsx';
|
||||||
|
|
||||||
|
export default class SearchableUserListContainer extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
users: React.PropTypes.arrayOf(React.PropTypes.object),
|
||||||
|
usersPerPage: React.PropTypes.number,
|
||||||
|
total: React.PropTypes.number,
|
||||||
|
extraInfo: React.PropTypes.object,
|
||||||
|
nextPage: React.PropTypes.func.isRequired,
|
||||||
|
search: React.PropTypes.func.isRequired,
|
||||||
|
actions: React.PropTypes.arrayOf(React.PropTypes.func),
|
||||||
|
actionProps: React.PropTypes.object,
|
||||||
|
actionUserProps: React.PropTypes.object,
|
||||||
|
focusOnMount: React.PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.handleTermChange = this.handleTermChange.bind(this);
|
||||||
|
|
||||||
|
this.nextPage = this.nextPage.bind(this);
|
||||||
|
this.previousPage = this.previousPage.bind(this);
|
||||||
|
this.search = this.search.bind(this);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
term: '',
|
||||||
|
page: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTermChange(term) {
|
||||||
|
this.setState({term});
|
||||||
|
}
|
||||||
|
|
||||||
|
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});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<SearchableUserList
|
||||||
|
{...this.props}
|
||||||
|
nextPage={this.nextPage}
|
||||||
|
previousPage={this.previousPage}
|
||||||
|
search={this.search}
|
||||||
|
page={this.state.page}
|
||||||
|
term={this.state.term}
|
||||||
|
onTermChange={this.handleTermChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ import PreferenceStore from 'stores/preference_store.jsx';
|
|||||||
import ModalStore from 'stores/modal_store.jsx';
|
import ModalStore from 'stores/modal_store.jsx';
|
||||||
|
|
||||||
import * as AsyncClient from 'utils/async_client.jsx';
|
import * as AsyncClient from 'utils/async_client.jsx';
|
||||||
|
import {sortTeamsByDisplayName} from 'utils/team_utils.jsx';
|
||||||
import * as Utils from 'utils/utils.jsx';
|
import * as Utils from 'utils/utils.jsx';
|
||||||
import * as ChannelUtils from 'utils/channel_utils.jsx';
|
import * as ChannelUtils from 'utils/channel_utils.jsx';
|
||||||
import * as ChannelActions from 'actions/channel_actions.jsx';
|
import * as ChannelActions from 'actions/channel_actions.jsx';
|
||||||
@@ -637,7 +638,7 @@ export default class Sidebar extends React.Component {
|
|||||||
|
|
||||||
// create elements for all 4 types of channels
|
// create elements for all 4 types of channels
|
||||||
const favoriteItems = this.state.favoriteChannels.
|
const favoriteItems = this.state.favoriteChannels.
|
||||||
sort(Utils.sortTeamsByDisplayName).
|
sort(sortTeamsByDisplayName).
|
||||||
map((channel, index, arr) => {
|
map((channel, index, arr) => {
|
||||||
if (channel.type === Constants.DM_CHANNEL) {
|
if (channel.type === Constants.DM_CHANNEL) {
|
||||||
return this.createChannelElement(channel, index, arr, this.handleLeaveDirectChannel);
|
return this.createChannelElement(channel, index, arr, this.handleLeaveDirectChannel);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import TeamStore from 'stores/team_store.jsx';
|
|||||||
import UserStore from 'stores/user_store.jsx';
|
import UserStore from 'stores/user_store.jsx';
|
||||||
|
|
||||||
import * as AsyncClient from 'utils/async_client.jsx';
|
import * as AsyncClient from 'utils/async_client.jsx';
|
||||||
|
import {sortTeamsByDisplayName} from 'utils/team_utils.jsx';
|
||||||
import * as Utils from 'utils/utils.jsx';
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
@@ -118,7 +119,7 @@ export default class TeamSidebar extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const teams = myTeams.
|
const teams = myTeams.
|
||||||
sort(Utils.sortTeamsByDisplayName).
|
sort(sortTeamsByDisplayName).
|
||||||
map((team) => {
|
map((team) => {
|
||||||
return (
|
return (
|
||||||
<TeamButton
|
<TeamButton
|
||||||
|
|||||||
@@ -8,6 +8,18 @@ import React from 'react';
|
|||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
export default class UserList extends React.Component {
|
export default class UserList extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.scrollToTop = this.scrollToTop.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollToTop() {
|
||||||
|
if (this.refs.container) {
|
||||||
|
this.refs.container.scrollTop = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const users = this.props.users;
|
const users = this.props.users;
|
||||||
|
|
||||||
@@ -31,12 +43,12 @@ export default class UserList extends React.Component {
|
|||||||
content = (
|
content = (
|
||||||
<div
|
<div
|
||||||
key='no-users-found'
|
key='no-users-found'
|
||||||
className='no-channel-message'
|
className='more-modal__placeholder-row'
|
||||||
>
|
>
|
||||||
<p className='primary-message'>
|
<p>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='user_list.notFound'
|
id='user_list.notFound'
|
||||||
defaultMessage='No users found :('
|
defaultMessage='No users found'
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -44,7 +56,7 @@ export default class UserList extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div ref='container'>
|
||||||
{content}
|
{content}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -745,7 +745,6 @@
|
|||||||
"admin.service.webhooksTitle": "Enable Incoming Webhooks: ",
|
"admin.service.webhooksTitle": "Enable Incoming Webhooks: ",
|
||||||
"admin.service.writeTimeout": "Write Timeout:",
|
"admin.service.writeTimeout": "Write Timeout:",
|
||||||
"admin.service.writeTimeoutDescription": "If using HTTP (insecure), this is the maximum time allowed from the end of reading the request headers until the response is written. If using HTTPS, it is the total time from when the connection is accepted until the response is written.",
|
"admin.service.writeTimeoutDescription": "If using HTTP (insecure), this is the maximum time allowed from the end of reading the request headers until the response is written. If using HTTPS, it is the total time from when the connection is accepted until the response is written.",
|
||||||
"admin.sidebar.addTeamSidebar": "Add team from sidebar menu",
|
|
||||||
"admin.sidebar.advanced": "Advanced",
|
"admin.sidebar.advanced": "Advanced",
|
||||||
"admin.sidebar.audits": "Compliance and Auditing",
|
"admin.sidebar.audits": "Compliance and Auditing",
|
||||||
"admin.sidebar.authentication": "Authentication",
|
"admin.sidebar.authentication": "Authentication",
|
||||||
@@ -787,7 +786,6 @@
|
|||||||
"admin.sidebar.push": "Mobile Push",
|
"admin.sidebar.push": "Mobile Push",
|
||||||
"admin.sidebar.rateLimiting": "Rate Limiting",
|
"admin.sidebar.rateLimiting": "Rate Limiting",
|
||||||
"admin.sidebar.reports": "REPORTING",
|
"admin.sidebar.reports": "REPORTING",
|
||||||
"admin.sidebar.rmTeamSidebar": "Remove team from sidebar menu",
|
|
||||||
"admin.sidebar.saml": "SAML",
|
"admin.sidebar.saml": "SAML",
|
||||||
"admin.sidebar.security": "Security",
|
"admin.sidebar.security": "Security",
|
||||||
"admin.sidebar.sessions": "Sessions",
|
"admin.sidebar.sessions": "Sessions",
|
||||||
@@ -797,7 +795,6 @@
|
|||||||
"admin.sidebar.statistics": "Team Statistics",
|
"admin.sidebar.statistics": "Team Statistics",
|
||||||
"admin.sidebar.storage": "Storage",
|
"admin.sidebar.storage": "Storage",
|
||||||
"admin.sidebar.support": "Legal and Support",
|
"admin.sidebar.support": "Legal and Support",
|
||||||
"admin.sidebar.teams": "TEAMS ({count, number})",
|
|
||||||
"admin.sidebar.users": "Users",
|
"admin.sidebar.users": "Users",
|
||||||
"admin.sidebar.usersAndTeams": "Users and Teams",
|
"admin.sidebar.usersAndTeams": "Users and Teams",
|
||||||
"admin.sidebar.view_statistics": "Site Statistics",
|
"admin.sidebar.view_statistics": "Site Statistics",
|
||||||
@@ -837,6 +834,9 @@
|
|||||||
"admin.system_analytics.activeUsers": "Active Users With Posts",
|
"admin.system_analytics.activeUsers": "Active Users With Posts",
|
||||||
"admin.system_analytics.title": "the System",
|
"admin.system_analytics.title": "the System",
|
||||||
"admin.system_analytics.totalPosts": "Total Posts",
|
"admin.system_analytics.totalPosts": "Total Posts",
|
||||||
|
"admin.system_users.allUsers": "All Users",
|
||||||
|
"admin.system_users.noTeams": "No Teams",
|
||||||
|
"admin.system_users.title": "{siteName} Users",
|
||||||
"admin.team.brandDesc": "Enable custom branding to show an image of your choice, uploaded below, and some help text, written below, on the login page.",
|
"admin.team.brandDesc": "Enable custom branding to show an image of your choice, uploaded below, and some help text, written below, on the login page.",
|
||||||
"admin.team.brandDescriptionExample": "All team communication in one place, searchable and accessible anywhere",
|
"admin.team.brandDescriptionExample": "All team communication in one place, searchable and accessible anywhere",
|
||||||
"admin.team.brandDescriptionHelp": "Description of service shown in login screens and UI. When not specified, \"All team communication in one place, searchable and accessible anywhere\" is displayed.",
|
"admin.team.brandDescriptionHelp": "Description of service shown in login screens and UI. When not specified, \"All team communication in one place, searchable and accessible anywhere\" is displayed.",
|
||||||
@@ -883,8 +883,6 @@
|
|||||||
"admin.team_analytics.activeUsers": "Active Users With Posts",
|
"admin.team_analytics.activeUsers": "Active Users With Posts",
|
||||||
"admin.team_analytics.totalPosts": "Total Posts",
|
"admin.team_analytics.totalPosts": "Total Posts",
|
||||||
"admin.true": "true",
|
"admin.true": "true",
|
||||||
"admin.userList.title": "Users for {team}",
|
|
||||||
"admin.userList.title2": "Users for {team} ({count})",
|
|
||||||
"admin.user_item.authServiceEmail": "<strong>Sign-in Method:</strong> Email",
|
"admin.user_item.authServiceEmail": "<strong>Sign-in Method:</strong> Email",
|
||||||
"admin.user_item.authServiceNotEmail": "<strong>Sign-in Method:</strong> {service}",
|
"admin.user_item.authServiceNotEmail": "<strong>Sign-in Method:</strong> {service}",
|
||||||
"admin.user_item.confirmDemoteDescription": "If you demote yourself from the System Admin role and there is not another user with System Admin privileges, you'll need to re-assign a System Admin by accessing the Mattermost server through a terminal and running the following command.",
|
"admin.user_item.confirmDemoteDescription": "If you demote yourself from the System Admin role and there is not another user with System Admin privileges, you'll need to re-assign a System Admin by accessing the Mattermost server through a terminal and running the following command.",
|
||||||
@@ -904,7 +902,6 @@
|
|||||||
"admin.user_item.resetMfa": "Remove MFA",
|
"admin.user_item.resetMfa": "Remove MFA",
|
||||||
"admin.user_item.resetPwd": "Reset Password",
|
"admin.user_item.resetPwd": "Reset Password",
|
||||||
"admin.user_item.switchToEmail": "Switch to Email/Password",
|
"admin.user_item.switchToEmail": "Switch to Email/Password",
|
||||||
"admin.user_item.sysAdmin": "System Admin",
|
|
||||||
"admin.user_item.teamAdmin": "Team Admin",
|
"admin.user_item.teamAdmin": "Team Admin",
|
||||||
"admin.webrtc.enableDescription": "When true, Mattermost allows making <strong>one-on-one</strong> video calls. WebRTC calls are available on Chrome, Firefox and Mattermost Desktop Apps.",
|
"admin.webrtc.enableDescription": "When true, Mattermost allows making <strong>one-on-one</strong> video calls. WebRTC calls are available on Chrome, Firefox and Mattermost Desktop Apps.",
|
||||||
"admin.webrtc.enableTitle": "Enable Mattermost WebRTC: ",
|
"admin.webrtc.enableTitle": "Enable Mattermost WebRTC: ",
|
||||||
@@ -964,6 +961,7 @@
|
|||||||
"analytics.system.totalWebsockets": "WebSocket Conns",
|
"analytics.system.totalWebsockets": "WebSocket Conns",
|
||||||
"analytics.team.activeUsers": "Active Users With Posts",
|
"analytics.team.activeUsers": "Active Users With Posts",
|
||||||
"analytics.team.newlyCreated": "Newly Created Users",
|
"analytics.team.newlyCreated": "Newly Created Users",
|
||||||
|
"analytics.team.noTeams": "There are no teams on this server for which to view statistics.",
|
||||||
"analytics.team.privateGroups": "Private Groups",
|
"analytics.team.privateGroups": "Private Groups",
|
||||||
"analytics.team.publicChannels": "Public Channels",
|
"analytics.team.publicChannels": "Public Channels",
|
||||||
"analytics.team.recentActive": "Recent Active Users",
|
"analytics.team.recentActive": "Recent Active Users",
|
||||||
@@ -1323,6 +1321,7 @@
|
|||||||
"filtered_channels_list.search": "Search channels",
|
"filtered_channels_list.search": "Search channels",
|
||||||
"filtered_user_list.any_team": "All Users",
|
"filtered_user_list.any_team": "All Users",
|
||||||
"filtered_user_list.count": "{count} {count, plural, =0 {0 members} one {member} other {members}}",
|
"filtered_user_list.count": "{count} {count, plural, =0 {0 members} one {member} other {members}}",
|
||||||
|
"filtered_user_list.countPage": "{startCount, number} - {endCount, number} {count, plural, =0 {0 members} one {member} other {members}}",
|
||||||
"filtered_user_list.countTotal": "{count} {count, plural, =0 {0 members} one {member} other {members}} of {total} total",
|
"filtered_user_list.countTotal": "{count} {count, plural, =0 {0 members} one {member} other {members}} of {total} total",
|
||||||
"filtered_user_list.countTotalPage": "{startCount, number} - {endCount, number} {count, plural, =0 {0 members} one {member} other {members}} of {total} total",
|
"filtered_user_list.countTotalPage": "{startCount, number} - {endCount, number} {count, plural, =0 {0 members} one {member} other {members}} of {total} total",
|
||||||
"filtered_user_list.member": "Member",
|
"filtered_user_list.member": "Member",
|
||||||
@@ -1916,6 +1915,9 @@
|
|||||||
"suggestion.mention.special": "Special Mentions",
|
"suggestion.mention.special": "Special Mentions",
|
||||||
"suggestion.search.private": "Private Groups",
|
"suggestion.search.private": "Private Groups",
|
||||||
"suggestion.search.public": "Public Channels",
|
"suggestion.search.public": "Public Channels",
|
||||||
|
"system_users_list.count": "{count, number} {count, plural, one {user} other {users}}",
|
||||||
|
"system_users_list.countPage": "{startCount, number} - {endCount, number} {count, plural, one {user} other {users}} of {total} total",
|
||||||
|
"system_users_list.countSearch": "{count, number} {count, plural, one {user} other {users}} of {total} total",
|
||||||
"team_export_tab.download": "download",
|
"team_export_tab.download": "download",
|
||||||
"team_export_tab.export": "Export",
|
"team_export_tab.export": "Export",
|
||||||
"team_export_tab.exportTeam": "Export your team",
|
"team_export_tab.exportTeam": "Export your team",
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ import NativeAppLinkSettings from 'components/admin_console/native_app_link_sett
|
|||||||
import ComplianceSettings from 'components/admin_console/compliance_settings.jsx';
|
import ComplianceSettings from 'components/admin_console/compliance_settings.jsx';
|
||||||
import RateSettings from 'components/admin_console/rate_settings.jsx';
|
import RateSettings from 'components/admin_console/rate_settings.jsx';
|
||||||
import DeveloperSettings from 'components/admin_console/developer_settings.jsx';
|
import DeveloperSettings from 'components/admin_console/developer_settings.jsx';
|
||||||
import TeamUsers from 'components/admin_console/team_users.jsx';
|
import SystemUsers from 'components/admin_console/system_users/system_users.jsx';
|
||||||
import TeamAnalytics from 'components/analytics/team_analytics.jsx';
|
import TeamAnalytics from 'components/analytics/team_analytics.jsx';
|
||||||
import LicenseSettings from 'components/admin_console/license_settings.jsx';
|
import LicenseSettings from 'components/admin_console/license_settings.jsx';
|
||||||
import Audits from 'components/admin_console/audits.jsx';
|
import Audits from 'components/admin_console/audits.jsx';
|
||||||
@@ -217,18 +217,26 @@ export default (
|
|||||||
component={MetricsSettings}
|
component={MetricsSettings}
|
||||||
/>
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
|
<Route
|
||||||
|
path='users'
|
||||||
|
component={SystemUsers}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path='team_analytics'
|
||||||
|
component={TeamAnalytics}
|
||||||
|
/>
|
||||||
<Route path='team'>
|
<Route path='team'>
|
||||||
<Redirect
|
<Redirect
|
||||||
from=':team'
|
from=':team'
|
||||||
to=':team/users'
|
to='../users'
|
||||||
/>
|
/>
|
||||||
<Route
|
<Redirect
|
||||||
path=':team/users'
|
from=':team/users'
|
||||||
component={TeamUsers}
|
to='../users'
|
||||||
/>
|
/>
|
||||||
<Route
|
<Redirect
|
||||||
path=':team/analytics'
|
from=':team/analytics'
|
||||||
component={TeamAnalytics}
|
to='../team_analytics'
|
||||||
/>
|
/>
|
||||||
<Redirect
|
<Redirect
|
||||||
from='*'
|
from='*'
|
||||||
|
|||||||
@@ -602,6 +602,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.more-modal__placeholder-row {
|
||||||
|
padding: 10px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
.more-modal__actions--round {
|
.more-modal__actions--round {
|
||||||
height: 32px;
|
height: 32px;
|
||||||
line-height: 32px;
|
line-height: 32px;
|
||||||
|
|||||||
@@ -28,7 +28,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
border-bottom: 1px solid alpha-color($black, .1);
|
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin: 1em 0;
|
margin: 1em 0;
|
||||||
padding-bottom: .5em;
|
padding-bottom: .5em;
|
||||||
@@ -276,6 +275,10 @@
|
|||||||
@include opacity(.7);
|
@include opacity(.7);
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.admin-console-header {
|
||||||
|
border-bottom: 1px solid alpha-color($black, .1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.brand-img {
|
.brand-img {
|
||||||
@@ -478,3 +481,23 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
width: 130px;
|
width: 130px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.system-users__filter-row {
|
||||||
|
display: flex;
|
||||||
|
margin-left: 15px;
|
||||||
|
margin-right: 15px;
|
||||||
|
|
||||||
|
.system-users__filter {
|
||||||
|
flex: 1;
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.system-users__team-filter-label {
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.system-users__team-filter {
|
||||||
|
display: inline-block;
|
||||||
|
width: 200px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -83,4 +83,30 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.team-statistics__header-row {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 1.5em;
|
||||||
|
margin-top: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.team-statistics__header {
|
||||||
|
display: inline-block;
|
||||||
|
flex: 1;
|
||||||
|
margin-right: 15px;
|
||||||
|
overflow: hidden;
|
||||||
|
padding-bottom: .5em;
|
||||||
|
|
||||||
|
> h3 {
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.team-statistics__team-filter {
|
||||||
|
display: inline-block;
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,6 @@
|
|||||||
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
|
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
|
||||||
import EventEmitter from 'events';
|
import EventEmitter from 'events';
|
||||||
|
|
||||||
import BrowserStore from 'stores/browser_store.jsx';
|
|
||||||
|
|
||||||
import Constants from 'utils/constants.jsx';
|
import Constants from 'utils/constants.jsx';
|
||||||
const ActionTypes = Constants.ActionTypes;
|
const ActionTypes = Constants.ActionTypes;
|
||||||
|
|
||||||
@@ -138,18 +136,6 @@ class AdminStoreClass extends EventEmitter {
|
|||||||
getTeam(id) {
|
getTeam(id) {
|
||||||
return this.teams[id];
|
return this.teams[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
getSelectedTeams() {
|
|
||||||
const result = BrowserStore.getItem('selected_teams');
|
|
||||||
if (!result) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
saveSelectedTeams(teams) {
|
|
||||||
BrowserStore.setItem('selected_teams', teams);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var AdminStore = new AdminStoreClass();
|
var AdminStore = new AdminStoreClass();
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const UserStatuses = Constants.UserStatuses;
|
|||||||
const CHANGE_EVENT_NOT_IN_CHANNEL = 'change_not_in_channel';
|
const CHANGE_EVENT_NOT_IN_CHANNEL = 'change_not_in_channel';
|
||||||
const CHANGE_EVENT_IN_CHANNEL = 'change_in_channel';
|
const CHANGE_EVENT_IN_CHANNEL = 'change_in_channel';
|
||||||
const CHANGE_EVENT_IN_TEAM = 'change_in_team';
|
const CHANGE_EVENT_IN_TEAM = 'change_in_team';
|
||||||
|
const CHANGE_EVENT_WITHOUT_TEAM = 'change_without_team';
|
||||||
const CHANGE_EVENT = 'change';
|
const CHANGE_EVENT = 'change';
|
||||||
const CHANGE_EVENT_SESSIONS = 'change_sessions';
|
const CHANGE_EVENT_SESSIONS = 'change_sessions';
|
||||||
const CHANGE_EVENT_AUDITS = 'change_audits';
|
const CHANGE_EVENT_AUDITS = 'change_audits';
|
||||||
@@ -50,6 +51,9 @@ class UserStoreClass extends EventEmitter {
|
|||||||
this.not_in_channel_offset = {};
|
this.not_in_channel_offset = {};
|
||||||
this.not_in_channel_count = {};
|
this.not_in_channel_count = {};
|
||||||
|
|
||||||
|
// Lists of sorted IDs for users without a team
|
||||||
|
this.profiles_without_team = {};
|
||||||
|
|
||||||
this.statuses = {};
|
this.statuses = {};
|
||||||
this.sessions = {};
|
this.sessions = {};
|
||||||
this.audits = [];
|
this.audits = [];
|
||||||
@@ -105,6 +109,18 @@ class UserStoreClass extends EventEmitter {
|
|||||||
this.removeListener(CHANGE_EVENT_NOT_IN_CHANNEL, callback);
|
this.removeListener(CHANGE_EVENT_NOT_IN_CHANNEL, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
emitWithoutTeamChange() {
|
||||||
|
this.emit(CHANGE_EVENT_WITHOUT_TEAM);
|
||||||
|
}
|
||||||
|
|
||||||
|
addWithoutTeamChangeListener(callback) {
|
||||||
|
this.on(CHANGE_EVENT_WITHOUT_TEAM, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeWithoutTeamChangeListener(callback) {
|
||||||
|
this.removeListener(CHANGE_EVENT_WITHOUT_TEAM, callback);
|
||||||
|
}
|
||||||
|
|
||||||
emitSessionsChange() {
|
emitSessionsChange() {
|
||||||
this.emit(CHANGE_EVENT_SESSIONS);
|
this.emit(CHANGE_EVENT_SESSIONS);
|
||||||
}
|
}
|
||||||
@@ -189,8 +205,33 @@ class UserStoreClass extends EventEmitter {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getProfileListForIds(userIds, skipCurrent = false, skipInactive = false) {
|
||||||
|
const profiles = [];
|
||||||
|
const currentId = this.getCurrentId();
|
||||||
|
|
||||||
|
for (let i = 0; i < userIds.length; i++) {
|
||||||
|
const profile = this.getProfile(userIds[i]);
|
||||||
|
|
||||||
|
if (!profile) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skipCurrent && profile.id === currentId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skipInactive && profile.delete_at > 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
profiles.push(profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
return profiles;
|
||||||
|
}
|
||||||
|
|
||||||
hasProfile(userId) {
|
hasProfile(userId) {
|
||||||
return this.getProfile(userId) != null;
|
return this.getProfiles().hasOwnProperty(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
getProfileByUsername(username) {
|
getProfileByUsername(username) {
|
||||||
@@ -248,7 +289,7 @@ class UserStoreClass extends EventEmitter {
|
|||||||
return profiles;
|
return profiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
getProfileList(skipCurrent) {
|
getProfileList(skipCurrent = false, allowInactive = false) {
|
||||||
const profiles = [];
|
const profiles = [];
|
||||||
const currentId = this.getCurrentId();
|
const currentId = this.getCurrentId();
|
||||||
|
|
||||||
@@ -260,7 +301,7 @@ class UserStoreClass extends EventEmitter {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (profile.delete_at === 0) {
|
if (allowInactive || profile.delete_at === 0) {
|
||||||
profiles.push(profile);
|
profiles.push(profile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -314,28 +355,8 @@ class UserStoreClass extends EventEmitter {
|
|||||||
|
|
||||||
getProfileListInTeam(teamId = TeamStore.getCurrentId(), skipCurrent = false, skipInactive = false) {
|
getProfileListInTeam(teamId = TeamStore.getCurrentId(), skipCurrent = false, skipInactive = false) {
|
||||||
const userIds = this.profiles_in_team[teamId] || [];
|
const userIds = this.profiles_in_team[teamId] || [];
|
||||||
const profiles = [];
|
|
||||||
const currentId = this.getCurrentId();
|
|
||||||
|
|
||||||
for (let i = 0; i < userIds.length; i++) {
|
return this.getProfileListForIds(userIds, skipCurrent, skipInactive);
|
||||||
const profile = this.getProfile(userIds[i]);
|
|
||||||
|
|
||||||
if (!profile) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (skipCurrent && profile.id === currentId) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (skipInactive && profile.delete_at > 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
profiles.push(profile);
|
|
||||||
}
|
|
||||||
|
|
||||||
return profiles;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
removeProfileFromTeam(teamId, userId) {
|
removeProfileFromTeam(teamId, userId) {
|
||||||
@@ -416,21 +437,8 @@ class UserStoreClass extends EventEmitter {
|
|||||||
|
|
||||||
getProfileListInChannel(channelId = ChannelStore.getCurrentId(), skipCurrent = false) {
|
getProfileListInChannel(channelId = ChannelStore.getCurrentId(), skipCurrent = false) {
|
||||||
const userIds = this.profiles_in_channel[channelId] || [];
|
const userIds = this.profiles_in_channel[channelId] || [];
|
||||||
const currentId = this.getCurrentId();
|
|
||||||
const profiles = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < userIds.length; i++) {
|
return this.getProfileListForIds(userIds, skipCurrent, false);
|
||||||
const profile = this.getProfile(userIds[i]);
|
|
||||||
if (profile) {
|
|
||||||
if (skipCurrent && profile.id === currentId) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
profiles.push(profile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return profiles;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
saveProfilesNotInChannel(channelId = ChannelStore.getCurrentId(), profiles) {
|
saveProfilesNotInChannel(channelId = ChannelStore.getCurrentId(), profiles) {
|
||||||
@@ -482,23 +490,43 @@ class UserStoreClass extends EventEmitter {
|
|||||||
|
|
||||||
getProfileListNotInChannel(channelId = ChannelStore.getCurrentId(), skipInactive = false) {
|
getProfileListNotInChannel(channelId = ChannelStore.getCurrentId(), skipInactive = false) {
|
||||||
const userIds = this.profiles_not_in_channel[channelId] || [];
|
const userIds = this.profiles_not_in_channel[channelId] || [];
|
||||||
const profiles = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < userIds.length; i++) {
|
return this.getProfileListForIds(userIds, false, skipInactive);
|
||||||
const profile = this.getProfile(userIds[i]);
|
|
||||||
|
|
||||||
if (!profile) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (skipInactive && profile.delete_at > 0) {
|
// Profiles without any teams
|
||||||
continue;
|
|
||||||
|
saveProfilesWithoutTeam(profiles) {
|
||||||
|
const oldProfileList = this.profiles_without_team;
|
||||||
|
const oldProfileMap = {};
|
||||||
|
for (let i = 0; i < oldProfileList.length; i++) {
|
||||||
|
oldProfileMap[oldProfileList[i]] = this.getProfile(oldProfileList[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
profiles.push(profile);
|
const newProfileMap = Object.assign({}, oldProfileMap, profiles);
|
||||||
|
const newProfileList = Object.keys(newProfileMap);
|
||||||
|
|
||||||
|
newProfileList.sort((a, b) => {
|
||||||
|
const aProfile = newProfileMap[a];
|
||||||
|
const bProfile = newProfileMap[b];
|
||||||
|
|
||||||
|
if (aProfile.username < bProfile.username) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (aProfile.username > bProfile.username) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.profiles_without_team = newProfileList;
|
||||||
|
this.saveProfiles(profiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
return profiles;
|
getProfileListWithoutTeam(skipCurrent = false, skipInactive = false) {
|
||||||
|
const userIds = this.profiles_without_team || [];
|
||||||
|
|
||||||
|
return this.getProfileListForIds(userIds, skipCurrent, skipInactive);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Other
|
// Other
|
||||||
@@ -680,6 +708,10 @@ UserStore.dispatchToken = AppDispatcher.register((payload) => {
|
|||||||
}
|
}
|
||||||
UserStore.emitNotInChannelChange();
|
UserStore.emitNotInChannelChange();
|
||||||
break;
|
break;
|
||||||
|
case ActionTypes.RECEIVED_PROFILES_WITHOUT_TEAM:
|
||||||
|
UserStore.saveProfilesWithoutTeam(action.profiles);
|
||||||
|
UserStore.emitWithoutTeamChange();
|
||||||
|
break;
|
||||||
case ActionTypes.RECEIVED_PROFILE:
|
case ActionTypes.RECEIVED_PROFILE:
|
||||||
UserStore.saveProfile(action.profile);
|
UserStore.saveProfile(action.profile);
|
||||||
UserStore.emitChange();
|
UserStore.emitChange();
|
||||||
|
|||||||
@@ -314,7 +314,7 @@ export function getChannelMember(channelId, userId) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getUser(userId) {
|
export function getUser(userId, success, error) {
|
||||||
const callName = `getUser${userId}`;
|
const callName = `getUser${userId}`;
|
||||||
|
|
||||||
if (isCallInProgress(callName)) {
|
if (isCallInProgress(callName)) {
|
||||||
@@ -331,11 +331,19 @@ export function getUser(userId) {
|
|||||||
type: ActionTypes.RECEIVED_PROFILE,
|
type: ActionTypes.RECEIVED_PROFILE,
|
||||||
profile: data
|
profile: data
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
success(data);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
(err) => {
|
(err) => {
|
||||||
|
if (error) {
|
||||||
|
error(err);
|
||||||
|
} else {
|
||||||
callTracker[callName] = 0;
|
callTracker[callName] = 0;
|
||||||
dispatchError(err, 'getUser');
|
dispatchError(err, 'getUser');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ export const ActionTypes = keyMirror({
|
|||||||
RECEIVED_PROFILE: null,
|
RECEIVED_PROFILE: null,
|
||||||
RECEIVED_PROFILES_IN_CHANNEL: null,
|
RECEIVED_PROFILES_IN_CHANNEL: null,
|
||||||
RECEIVED_PROFILES_NOT_IN_CHANNEL: null,
|
RECEIVED_PROFILES_NOT_IN_CHANNEL: null,
|
||||||
|
RECEIVED_PROFILES_WITHOUT_TEAM: null,
|
||||||
RECEIVED_ME: null,
|
RECEIVED_ME: null,
|
||||||
RECEIVED_SESSIONS: null,
|
RECEIVED_SESSIONS: null,
|
||||||
RECEIVED_AUDITS: null,
|
RECEIVED_AUDITS: null,
|
||||||
@@ -205,7 +206,8 @@ export const UserStatuses = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const UserSearchOptions = {
|
export const UserSearchOptions = {
|
||||||
ALLOW_INACTIVE: 'allow_inactive'
|
ALLOW_INACTIVE: 'allow_inactive',
|
||||||
|
WITHOUT_TEAM: 'without_team'
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SocketEvents = {
|
export const SocketEvents = {
|
||||||
@@ -253,23 +255,7 @@ export const PostTypes = {
|
|||||||
EPHEMERAL: 'system_ephemeral'
|
EPHEMERAL: 'system_ephemeral'
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Constants = {
|
export const StatTypes = keyMirror({
|
||||||
Preferences,
|
|
||||||
SocketEvents,
|
|
||||||
ActionTypes,
|
|
||||||
WebrtcActionTypes,
|
|
||||||
UserStatuses,
|
|
||||||
UserSearchOptions,
|
|
||||||
TutorialSteps,
|
|
||||||
PostTypes,
|
|
||||||
IGNORE_POST_TYPES: [PostTypes.JOIN_LEAVE, PostTypes.JOIN_CHANNEL, PostTypes.LEAVE_CHANNEL, PostTypes.REMOVE_FROM_CHANNEL, PostTypes.ADD_TO_CHANNEL, PostTypes.ADD_REMOVE],
|
|
||||||
|
|
||||||
PayloadSources: keyMirror({
|
|
||||||
SERVER_ACTION: null,
|
|
||||||
VIEW_ACTION: null
|
|
||||||
}),
|
|
||||||
|
|
||||||
StatTypes: keyMirror({
|
|
||||||
TOTAL_USERS: null,
|
TOTAL_USERS: null,
|
||||||
TOTAL_PUBLIC_CHANNELS: null,
|
TOTAL_PUBLIC_CHANNELS: null,
|
||||||
TOTAL_PRIVATE_GROUPS: null,
|
TOTAL_PRIVATE_GROUPS: null,
|
||||||
@@ -290,7 +276,25 @@ export const Constants = {
|
|||||||
TOTAL_READ_DB_CONNECTIONS: null,
|
TOTAL_READ_DB_CONNECTIONS: null,
|
||||||
DAILY_ACTIVE_USERS: null,
|
DAILY_ACTIVE_USERS: null,
|
||||||
MONTHLY_ACTIVE_USERS: null
|
MONTHLY_ACTIVE_USERS: null
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Constants = {
|
||||||
|
Preferences,
|
||||||
|
SocketEvents,
|
||||||
|
ActionTypes,
|
||||||
|
WebrtcActionTypes,
|
||||||
|
UserStatuses,
|
||||||
|
UserSearchOptions,
|
||||||
|
TutorialSteps,
|
||||||
|
PostTypes,
|
||||||
|
IGNORE_POST_TYPES: [PostTypes.JOIN_LEAVE, PostTypes.JOIN_CHANNEL, PostTypes.LEAVE_CHANNEL, PostTypes.REMOVE_FROM_CHANNEL, PostTypes.ADD_TO_CHANNEL, PostTypes.ADD_REMOVE],
|
||||||
|
|
||||||
|
PayloadSources: keyMirror({
|
||||||
|
SERVER_ACTION: null,
|
||||||
|
VIEW_ACTION: null
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
StatTypes,
|
||||||
STAT_MAX_ACTIVE_USERS: 20,
|
STAT_MAX_ACTIVE_USERS: 20,
|
||||||
STAT_MAX_NEW_USERS: 20,
|
STAT_MAX_NEW_USERS: 20,
|
||||||
|
|
||||||
|
|||||||
27
webapp/utils/team_utils.jsx
Обычный файл
27
webapp/utils/team_utils.jsx
Обычный файл
@@ -0,0 +1,27 @@
|
|||||||
|
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
import LocalizationStore from 'stores/localization_store.jsx';
|
||||||
|
|
||||||
|
export function convertTeamMapToList(teamMap) {
|
||||||
|
const teams = [];
|
||||||
|
|
||||||
|
for (const id in teamMap) {
|
||||||
|
if (teamMap.hasOwnProperty(id)) {
|
||||||
|
teams.push(teamMap[id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return teams.sort(sortTeamsByDisplayName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use when sorting multiple teams by their `display_name` field
|
||||||
|
export function sortTeamsByDisplayName(a, b) {
|
||||||
|
const locale = LocalizationStore.getLocale();
|
||||||
|
|
||||||
|
if (a.display_name !== b.display_name) {
|
||||||
|
return a.display_name.localeCompare(b.display_name, locale, {numeric: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.name.localeCompare(b.name, locale, {numeric: true});
|
||||||
|
}
|
||||||
@@ -1117,17 +1117,6 @@ export function windowHeight() {
|
|||||||
return $(window).height();
|
return $(window).height();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use when sorting multiple teams by their `display_name` field
|
|
||||||
export function sortTeamsByDisplayName(a, b) {
|
|
||||||
const locale = LocalizationStore.getLocale();
|
|
||||||
|
|
||||||
if (a.display_name !== b.display_name) {
|
|
||||||
return a.display_name.localeCompare(b.display_name, locale, {numeric: true});
|
|
||||||
}
|
|
||||||
|
|
||||||
return a.name.localeCompare(b.name, locale, {numeric: true});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getChannelTerm(channelType) {
|
export function getChannelTerm(channelType) {
|
||||||
let channelTerm = 'Channel';
|
let channelTerm = 'Channel';
|
||||||
if (channelType === Constants.PRIVATE_CHANNEL) {
|
if (channelType === Constants.PRIVATE_CHANNEL) {
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user