mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
Merge branch 'master' into PLT-1429
This commit is contained in:
@@ -11,6 +11,7 @@ import * as Utils from '../../utils/utils.jsx';
|
||||
import EmailSettingsTab from './email_settings.jsx';
|
||||
import LogSettingsTab from './log_settings.jsx';
|
||||
import LogsTab from './logs.jsx';
|
||||
import AuditsTab from './audits.jsx';
|
||||
import FileSettingsTab from './image_settings.jsx';
|
||||
import PrivacySettingsTab from './privacy_settings.jsx';
|
||||
import RateSettingsTab from './rate_settings.jsx';
|
||||
@@ -138,6 +139,8 @@ export default class AdminController extends React.Component {
|
||||
tab = <LogSettingsTab config={this.state.config} />;
|
||||
} else if (this.state.selected === 'logs') {
|
||||
tab = <LogsTab />;
|
||||
} else if (this.state.selected === 'audits') {
|
||||
tab = <AuditsTab />;
|
||||
} else if (this.state.selected === 'image_settings') {
|
||||
tab = <FileSettingsTab config={this.state.config} />;
|
||||
} else if (this.state.selected === 'privacy_settings') {
|
||||
|
||||
@@ -214,6 +214,24 @@ export default class AdminSidebar extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
let audits;
|
||||
if (global.window.mm_license.IsLicensed === 'true') {
|
||||
audits = (
|
||||
<li>
|
||||
<a
|
||||
href='#'
|
||||
className={this.isSelected('audits')}
|
||||
onClick={this.handleClick.bind(this, 'audits', null)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='admin.sidebar.audits'
|
||||
defaultMessage='Audits'
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='sidebar--left sidebar--collapsable'>
|
||||
<div>
|
||||
@@ -448,6 +466,7 @@ export default class AdminSidebar extends React.Component {
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
{audits}
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -4,11 +4,60 @@
|
||||
import * as Utils from '../../utils/utils.jsx';
|
||||
import Constants from '../../utils/constants.jsx';
|
||||
import LineChart from './line_chart.jsx';
|
||||
import DoughnutChart from './doughnut_chart.jsx';
|
||||
import StatisticCount from './statistic_count.jsx';
|
||||
|
||||
var Tooltip = ReactBootstrap.Tooltip;
|
||||
var OverlayTrigger = ReactBootstrap.OverlayTrigger;
|
||||
|
||||
import {FormattedMessage} from 'mm-intl';
|
||||
import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl';
|
||||
|
||||
const holders = defineMessages({
|
||||
analyticsTotalUsers: {
|
||||
id: 'admin.analytics.totalUsers',
|
||||
defaultMessage: 'Total Users'
|
||||
},
|
||||
analyticsPublicChannels: {
|
||||
id: 'admin.analytics.publicChannels',
|
||||
defaultMessage: 'Public Channels'
|
||||
},
|
||||
analyticsPrivateGroups: {
|
||||
id: 'admin.analytics.privateGroups',
|
||||
defaultMessage: 'Private Groups'
|
||||
},
|
||||
analyticsTotalPosts: {
|
||||
id: 'admin.analytics.totalPosts',
|
||||
defaultMessage: 'Total Posts'
|
||||
},
|
||||
analyticsFilePosts: {
|
||||
id: 'admin.analytics.totalFilePosts',
|
||||
defaultMessage: 'Posts with Files'
|
||||
},
|
||||
analyticsHashtagPosts: {
|
||||
id: 'admin.analytics.totalHashtagPosts',
|
||||
defaultMessage: 'Posts with Hashtags'
|
||||
},
|
||||
analyticsIncomingHooks: {
|
||||
id: 'admin.analytics.totalIncomingWebhooks',
|
||||
defaultMessage: 'Incoming Webhooks'
|
||||
},
|
||||
analyticsOutgoingHooks: {
|
||||
id: 'admin.analytics.totalOutgoingWebhooks',
|
||||
defaultMessage: 'Outgoing Webhooks'
|
||||
},
|
||||
analyticsChannelTypes: {
|
||||
id: 'admin.analytics.channelTypes',
|
||||
defaultMessage: 'Channel Types'
|
||||
},
|
||||
analyticsTextPosts: {
|
||||
id: 'admin.analytics.textPosts',
|
||||
defaultMessage: 'Posts with Text-only'
|
||||
},
|
||||
analyticsPostTypes: {
|
||||
id: 'admin.analytics.postTypes',
|
||||
defaultMessage: 'Posts, Files and Hashtags'
|
||||
}
|
||||
});
|
||||
|
||||
export default class Analytics extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -18,6 +67,8 @@ export default class Analytics extends React.Component {
|
||||
}
|
||||
|
||||
render() { // in the future, break down these into smaller components
|
||||
const {formatMessage} = this.props.intl;
|
||||
|
||||
var serverError = '';
|
||||
if (this.props.serverError) {
|
||||
serverError = <div className='form-group has-error'><label className='control-label'>{this.props.serverError}</label></div>;
|
||||
@@ -30,77 +81,129 @@ export default class Analytics extends React.Component {
|
||||
/>
|
||||
);
|
||||
|
||||
var totalCount = (
|
||||
<div className='col-sm-3'>
|
||||
<div className='total-count'>
|
||||
<div className='title'>
|
||||
<FormattedMessage
|
||||
id='admin.analytics.totalUsers'
|
||||
defaultMessage='Total Users'
|
||||
/>
|
||||
<i className='fa fa-users'/></div>
|
||||
<div className='content'>{this.props.uniqueUserCount == null ? loading : this.props.uniqueUserCount}</div>
|
||||
let firstRow;
|
||||
let extraGraphs;
|
||||
if (this.props.showAdvanced) {
|
||||
firstRow = (
|
||||
<div className='row'>
|
||||
<StatisticCount
|
||||
title={formatMessage(holders.analyticsTotalUsers)}
|
||||
icon='fa-users'
|
||||
count={this.props.uniqueUserCount}
|
||||
/>
|
||||
<StatisticCount
|
||||
title={formatMessage(holders.analyticsTotalPosts)}
|
||||
icon='fa-comment'
|
||||
count={this.props.postCount}
|
||||
/>
|
||||
<StatisticCount
|
||||
title={formatMessage(holders.analyticsIncomingHooks)}
|
||||
icon='fa-arrow-down'
|
||||
count={this.props.incomingWebhookCount}
|
||||
/>
|
||||
<StatisticCount
|
||||
title={formatMessage(holders.analyticsOutgoingHooks)}
|
||||
icon='fa-arrow-up'
|
||||
count={this.props.outgoingWebhookCount}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
|
||||
var openChannelCount = (
|
||||
<div className='col-sm-3'>
|
||||
<div className='total-count'>
|
||||
<div className='title'>
|
||||
<FormattedMessage
|
||||
id='admin.analytics.publicChannels'
|
||||
defaultMessage='Public Channels'
|
||||
/>
|
||||
<i className='fa fa-globe'/></div>
|
||||
<div className='content'>{this.props.channelOpenCount == null ? loading : this.props.channelOpenCount}</div>
|
||||
const channelTypeData = [
|
||||
{
|
||||
value: this.props.channelOpenCount,
|
||||
color: '#46BFBD',
|
||||
highlight: '#5AD3D1',
|
||||
label: formatMessage(holders.analyticsPublicChannels)
|
||||
},
|
||||
{
|
||||
value: this.props.channelPrivateCount,
|
||||
color: '#FDB45C',
|
||||
highlight: '#FFC870',
|
||||
label: formatMessage(holders.analyticsPrivateGroups)
|
||||
}
|
||||
];
|
||||
|
||||
const postTypeData = [
|
||||
{
|
||||
value: this.props.filePostCount,
|
||||
color: '#46BFBD',
|
||||
highlight: '#5AD3D1',
|
||||
label: formatMessage(holders.analyticsFilePosts)
|
||||
},
|
||||
{
|
||||
value: this.props.filePostCount,
|
||||
color: '#F7464A',
|
||||
highlight: '#FF5A5E',
|
||||
label: formatMessage(holders.analyticsHashtagPosts)
|
||||
},
|
||||
{
|
||||
value: this.props.postCount - this.props.filePostCount - this.props.hashtagPostCount,
|
||||
color: '#FDB45C',
|
||||
highlight: '#FFC870',
|
||||
label: formatMessage(holders.analyticsTextPosts)
|
||||
}
|
||||
];
|
||||
|
||||
extraGraphs = (
|
||||
<div className='row'>
|
||||
<DoughnutChart
|
||||
title={formatMessage(holders.analyticsChannelTypes)}
|
||||
data={channelTypeData}
|
||||
width='300'
|
||||
height='225'
|
||||
/>
|
||||
<DoughnutChart
|
||||
title={formatMessage(holders.analyticsPostTypes)}
|
||||
data={postTypeData}
|
||||
width='300'
|
||||
height='225'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
var openPrivateCount = (
|
||||
<div className='col-sm-3'>
|
||||
<div className='total-count'>
|
||||
<div className='title'>
|
||||
<FormattedMessage
|
||||
id='admin.analytics.privateGroups'
|
||||
defaultMessage='Private Groups'
|
||||
/>
|
||||
<i className='fa fa-lock'/></div>
|
||||
<div className='content'>{this.props.channelPrivateCount == null ? loading : this.props.channelPrivateCount}</div>
|
||||
);
|
||||
} else {
|
||||
firstRow = (
|
||||
<div className='row'>
|
||||
<StatisticCount
|
||||
title={formatMessage(holders.analyticsTotalUsers)}
|
||||
icon='fa-users'
|
||||
count={this.props.uniqueUserCount}
|
||||
/>
|
||||
<StatisticCount
|
||||
title={formatMessage(holders.analyticsPublicChannels)}
|
||||
icon='fa-globe'
|
||||
count={this.props.channelOpenCount}
|
||||
/>
|
||||
<StatisticCount
|
||||
title={formatMessage(holders.analyticsPrivateGroups)}
|
||||
icon='fa-lock'
|
||||
count={this.props.channelPrivateCount}
|
||||
/>
|
||||
<StatisticCount
|
||||
title={formatMessage(holders.analyticsTotalPosts)}
|
||||
icon='fa-comment'
|
||||
count={this.props.postCount}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
var postCount = (
|
||||
<div className='col-sm-3'>
|
||||
<div className='total-count'>
|
||||
<div className='title'>
|
||||
<FormattedMessage
|
||||
id='admin.analytics.totalPosts'
|
||||
defaultMessage='Total Posts'
|
||||
/>
|
||||
<i className='fa fa-comment'/></div>
|
||||
<div className='content'>{this.props.postCount == null ? loading : this.props.postCount}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
var postCountsByDay = (
|
||||
<div className='col-sm-12'>
|
||||
<div className='total-count by-day'>
|
||||
<div className='title'>
|
||||
<FormattedMessage
|
||||
id='admin.analytics.totalPosts'
|
||||
defaultMessage='Total Posts'
|
||||
/>
|
||||
let postCountsByDay;
|
||||
if (this.props.postCountsDay == null) {
|
||||
postCountsByDay = (
|
||||
<div className='col-sm-12'>
|
||||
<div className='total-count by-day'>
|
||||
<div className='title'>
|
||||
<FormattedMessage
|
||||
id='admin.analytics.totalPosts'
|
||||
defaultMessage='Total Posts'
|
||||
/>
|
||||
</div>
|
||||
<div className='content'>{loading}</div>
|
||||
</div>
|
||||
<div className='content'>{loading}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (this.props.postCountsDay != null) {
|
||||
);
|
||||
} else {
|
||||
let content;
|
||||
if (this.props.postCountsDay.labels.length === 0) {
|
||||
content = (
|
||||
@@ -137,21 +240,22 @@ export default class Analytics extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
var usersWithPostsByDay = (
|
||||
<div className='col-sm-12'>
|
||||
<div className='total-count by-day'>
|
||||
<div className='title'>
|
||||
<FormattedMessage
|
||||
id='admin.analytics.activeUsers'
|
||||
defaultMessage='Active Users With Posts'
|
||||
/>
|
||||
let usersWithPostsByDay;
|
||||
if (this.props.userCountsWithPostsDay == null) {
|
||||
usersWithPostsByDay = (
|
||||
<div className='col-sm-12'>
|
||||
<div className='total-count by-day'>
|
||||
<div className='title'>
|
||||
<FormattedMessage
|
||||
id='admin.analytics.activeUsers'
|
||||
defaultMessage='Active Users With Posts'
|
||||
/>
|
||||
</div>
|
||||
<div className='content'>{loading}</div>
|
||||
</div>
|
||||
<div className='content'>{loading}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (this.props.userCountsWithPostsDay != null) {
|
||||
);
|
||||
} else {
|
||||
let content;
|
||||
if (this.props.userCountsWithPostsDay.labels.length === 0) {
|
||||
content = (
|
||||
@@ -312,12 +416,8 @@ export default class Analytics extends React.Component {
|
||||
/>
|
||||
</h3>
|
||||
{serverError}
|
||||
<div className='row'>
|
||||
{totalCount}
|
||||
{postCount}
|
||||
{openChannelCount}
|
||||
{openPrivateCount}
|
||||
</div>
|
||||
{firstRow}
|
||||
{extraGraphs}
|
||||
<div className='row'>
|
||||
{postCountsByDay}
|
||||
</div>
|
||||
@@ -347,10 +447,16 @@ Analytics.defaultProps = {
|
||||
};
|
||||
|
||||
Analytics.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
title: React.PropTypes.string,
|
||||
channelOpenCount: React.PropTypes.number,
|
||||
channelPrivateCount: React.PropTypes.number,
|
||||
postCount: React.PropTypes.number,
|
||||
showAdvanced: React.PropTypes.bool,
|
||||
filePostCount: React.PropTypes.number,
|
||||
hashtagPostCount: React.PropTypes.number,
|
||||
incomingWebhookCount: React.PropTypes.number,
|
||||
outgoingWebhookCount: React.PropTypes.number,
|
||||
postCountsDay: React.PropTypes.object,
|
||||
userCountsWithPostsDay: React.PropTypes.object,
|
||||
recentActiveUsers: React.PropTypes.array,
|
||||
@@ -358,3 +464,5 @@ Analytics.propTypes = {
|
||||
uniqueUserCount: React.PropTypes.number,
|
||||
serverError: React.PropTypes.string
|
||||
};
|
||||
|
||||
export default injectIntl(Analytics);
|
||||
|
||||
94
web/react/components/admin_console/audits.jsx
Normal file
94
web/react/components/admin_console/audits.jsx
Normal file
@@ -0,0 +1,94 @@
|
||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import LoadingScreen from '../loading_screen.jsx';
|
||||
import AuditTable from '../audit_table.jsx';
|
||||
|
||||
import AdminStore from '../../stores/admin_store.jsx';
|
||||
|
||||
import * as AsyncClient from '../../utils/async_client.jsx';
|
||||
|
||||
import {FormattedMessage} from 'mm-intl';
|
||||
|
||||
export default class Audits extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.onAuditListenerChange = this.onAuditListenerChange.bind(this);
|
||||
this.reload = this.reload.bind(this);
|
||||
|
||||
this.state = {
|
||||
audits: AdminStore.getAudits()
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
AdminStore.addAuditChangeListener(this.onAuditListenerChange);
|
||||
AsyncClient.getServerAudits();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
AdminStore.removeAuditChangeListener(this.onAuditListenerChange);
|
||||
}
|
||||
|
||||
onAuditListenerChange() {
|
||||
this.setState({
|
||||
audits: AdminStore.getAudits()
|
||||
});
|
||||
}
|
||||
|
||||
reload() {
|
||||
AdminStore.saveAudits(null);
|
||||
this.setState({
|
||||
audits: null
|
||||
});
|
||||
|
||||
AsyncClient.getServerAudits();
|
||||
}
|
||||
|
||||
render() {
|
||||
var content = null;
|
||||
|
||||
if (global.window.mm_license.IsLicensed !== 'true') {
|
||||
return <div/>;
|
||||
}
|
||||
|
||||
if (this.state.audits === null) {
|
||||
content = <LoadingScreen />;
|
||||
} else {
|
||||
content = (
|
||||
<div style={{margin: '10px'}}>
|
||||
<AuditTable
|
||||
audits={this.state.audits}
|
||||
oneLine={true}
|
||||
showUserId={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='panel'>
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id='admin.audits.title'
|
||||
defaultMessage='Server Audits'
|
||||
/>
|
||||
</h3>
|
||||
<button
|
||||
type='submit'
|
||||
className='btn btn-primary'
|
||||
onClick={this.reload}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='admin.audits.reload'
|
||||
defaultMessage='Reload'
|
||||
/>
|
||||
</button>
|
||||
<div className='log__panel'>
|
||||
{content}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
77
web/react/components/admin_console/doughnut_chart.jsx
Normal file
77
web/react/components/admin_console/doughnut_chart.jsx
Normal file
@@ -0,0 +1,77 @@
|
||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {FormattedMessage} from 'mm-intl';
|
||||
|
||||
export default class DoughnutChart extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.initChart = this.initChart.bind(this);
|
||||
this.chart = null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.initChart(this.props);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.chart) {
|
||||
this.chart.destroy();
|
||||
this.initChart(nextProps);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.chart) {
|
||||
this.chart.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
initChart(props) {
|
||||
var el = ReactDOM.findDOMNode(this.refs.canvas);
|
||||
var ctx = el.getContext('2d');
|
||||
this.chart = new Chart(ctx).Doughnut(props.data, props.options || {}); //eslint-disable-line new-cap
|
||||
}
|
||||
|
||||
render() {
|
||||
let content;
|
||||
if (this.props.data == null) {
|
||||
content = (
|
||||
<FormattedMessage
|
||||
id='admin.analytics.loading'
|
||||
defaultMessage='Loading...'
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
content = (
|
||||
<canvas
|
||||
ref='canvas'
|
||||
width={this.props.width}
|
||||
height={this.props.height}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='col-sm-6'>
|
||||
<div className='total-count'>
|
||||
<div className='title'>
|
||||
{this.props.title}
|
||||
</div>
|
||||
<div className='content'>
|
||||
{content}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DoughnutChart.propTypes = {
|
||||
title: React.PropTypes.string,
|
||||
width: React.PropTypes.string,
|
||||
height: React.PropTypes.string,
|
||||
data: React.PropTypes.array,
|
||||
options: React.PropTypes.object
|
||||
};
|
||||
@@ -112,6 +112,8 @@ class EmailSettings extends React.Component {
|
||||
buildConfig() {
|
||||
var config = this.props.config;
|
||||
config.EmailSettings.EnableSignUpWithEmail = ReactDOM.findDOMNode(this.refs.allowSignUpWithEmail).checked;
|
||||
config.EmailSettings.EnableSignInWithEmail = ReactDOM.findDOMNode(this.refs.allowSignInWithEmail).checked;
|
||||
config.EmailSettings.EnableSignInWithUsername = ReactDOM.findDOMNode(this.refs.allowSignInWithUsername).checked;
|
||||
config.EmailSettings.SendEmailNotifications = ReactDOM.findDOMNode(this.refs.sendEmailNotifications).checked;
|
||||
config.EmailSettings.SendPushNotifications = ReactDOM.findDOMNode(this.refs.sendPushNotifications).checked;
|
||||
config.EmailSettings.RequireEmailVerification = ReactDOM.findDOMNode(this.refs.requireEmailVerification).checked;
|
||||
@@ -317,6 +319,88 @@ class EmailSettings extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='form-group'>
|
||||
<label
|
||||
className='control-label col-sm-4'
|
||||
htmlFor='allowSignInWithEmail'
|
||||
>
|
||||
<FormattedMessage
|
||||
id='admin.email.allowEmailSignInTitle'
|
||||
defaultMessage='Allow Sign In With Email: '
|
||||
/>
|
||||
</label>
|
||||
<div className='col-sm-8'>
|
||||
<label className='radio-inline'>
|
||||
<input
|
||||
type='radio'
|
||||
name='allowSignInWithEmail'
|
||||
value='true'
|
||||
ref='allowSignInWithEmail'
|
||||
defaultChecked={this.props.config.EmailSettings.EnableSignInWithEmail}
|
||||
onChange={this.handleChange.bind(this, 'allowSignInWithEmail_true')}
|
||||
/>
|
||||
{'true'}
|
||||
</label>
|
||||
<label className='radio-inline'>
|
||||
<input
|
||||
type='radio'
|
||||
name='allowSignInWithEmail'
|
||||
value='false'
|
||||
defaultChecked={!this.props.config.EmailSettings.EnableSignInWithEmail}
|
||||
onChange={this.handleChange.bind(this, 'allowSignInWithEmail_false')}
|
||||
/>
|
||||
{'false'}
|
||||
</label>
|
||||
<p className='help-text'>
|
||||
<FormattedMessage
|
||||
id='admin.email.allowEmailSignInDescription'
|
||||
defaultMessage='When true, Mattermost allows users to sign in using their email and password.'
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='form-group'>
|
||||
<label
|
||||
className='control-label col-sm-4'
|
||||
htmlFor='allowSignInWithUsername'
|
||||
>
|
||||
<FormattedMessage
|
||||
id='admin.email.allowUsernameSignInTitle'
|
||||
defaultMessage='Allow Sign In With Username: '
|
||||
/>
|
||||
</label>
|
||||
<div className='col-sm-8'>
|
||||
<label className='radio-inline'>
|
||||
<input
|
||||
type='radio'
|
||||
name='allowSignInWithUsername'
|
||||
value='true'
|
||||
ref='allowSignInWithUsername'
|
||||
defaultChecked={this.props.config.EmailSettings.EnableSignInWithUsername}
|
||||
onChange={this.handleChange.bind(this, 'allowSignInWithUsername_true')}
|
||||
/>
|
||||
{'true'}
|
||||
</label>
|
||||
<label className='radio-inline'>
|
||||
<input
|
||||
type='radio'
|
||||
name='allowSignInWithUsername'
|
||||
value='false'
|
||||
defaultChecked={!this.props.config.EmailSettings.EnableSignInWithUsername}
|
||||
onChange={this.handleChange.bind(this, 'allowSignInWithUsername_false')}
|
||||
/>
|
||||
{'false'}
|
||||
</label>
|
||||
<p className='help-text'>
|
||||
<FormattedMessage
|
||||
id='admin.email.allowUsernameSignInDescription'
|
||||
defaultMessage='When true, Mattermost allows users to sign in using their username and password. This setting is typically only used when email verification is disabled.'
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='form-group'>
|
||||
<label
|
||||
className='control-label col-sm-4'
|
||||
|
||||
37
web/react/components/admin_console/statistic_count.jsx
Normal file
37
web/react/components/admin_console/statistic_count.jsx
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {FormattedMessage} from 'mm-intl';
|
||||
|
||||
export default class StatisticCount extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
let loading = (
|
||||
<FormattedMessage
|
||||
id='admin.analytics.loading'
|
||||
defaultMessage='Loading...'
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='col-sm-3'>
|
||||
<div className='total-count'>
|
||||
<div className='title'>
|
||||
{this.props.title}
|
||||
<i className={'fa ' + this.props.icon}/>
|
||||
</div>
|
||||
<div className='content'>{this.props.count == null ? loading : this.props.count}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
StatisticCount.propTypes = {
|
||||
title: React.PropTypes.string.isRequired,
|
||||
icon: React.PropTypes.string.isRequired,
|
||||
count: React.PropTypes.number
|
||||
};
|
||||
@@ -140,6 +140,34 @@ class SystemAnalytics extends React.Component {
|
||||
this.setState({serverError: err.message});
|
||||
}
|
||||
);
|
||||
|
||||
if (global.window.mm_license.IsLicensed === 'true') {
|
||||
Client.getSystemAnalytics(
|
||||
'extra_counts',
|
||||
(data) => {
|
||||
for (var index in data) {
|
||||
if (data[index].name === 'file_post_count') {
|
||||
this.setState({file_post_count: data[index].value});
|
||||
}
|
||||
|
||||
if (data[index].name === 'hashtag_post_count') {
|
||||
this.setState({hashtag_post_count: data[index].value});
|
||||
}
|
||||
|
||||
if (data[index].name === 'incoming_webhook_count') {
|
||||
this.setState({incoming_webhook_count: data[index].value});
|
||||
}
|
||||
|
||||
if (data[index].name === 'outgoing_webhook_count') {
|
||||
this.setState({outgoing_webhook_count: data[index].value});
|
||||
}
|
||||
}
|
||||
},
|
||||
(err) => {
|
||||
this.setState({serverError: err.message});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps() {
|
||||
@@ -160,10 +188,16 @@ class SystemAnalytics extends React.Component {
|
||||
return (
|
||||
<div>
|
||||
<Analytics
|
||||
intl={this.props.intl}
|
||||
title={this.props.intl.formatMessage(labels.title)}
|
||||
channelOpenCount={this.state.channel_open_count}
|
||||
channelPrivateCount={this.state.channel_private_count}
|
||||
postCount={this.state.post_count}
|
||||
showAdvanced={global.window.mm_license.IsLicensed === 'true'}
|
||||
filePostCount={this.state.file_post_count}
|
||||
hashtagPostCount={this.state.hashtag_post_count}
|
||||
incomingWebhookCount={this.state.incoming_webhook_count}
|
||||
outgoingWebhookCount={this.state.outgoing_webhook_count}
|
||||
postCountsDay={this.state.post_counts_day}
|
||||
userCountsWithPostsDay={this.state.user_counts_with_posts_day}
|
||||
uniqueUserCount={this.state.unique_user_count}
|
||||
@@ -179,4 +213,4 @@ SystemAnalytics.propTypes = {
|
||||
team: React.PropTypes.object
|
||||
};
|
||||
|
||||
export default injectIntl(SystemAnalytics);
|
||||
export default injectIntl(SystemAnalytics);
|
||||
|
||||
@@ -227,6 +227,7 @@ class TeamAnalytics extends React.Component {
|
||||
return (
|
||||
<div>
|
||||
<Analytics
|
||||
intl={this.props.intl}
|
||||
title={this.props.team.name}
|
||||
users={this.state.users}
|
||||
channelOpenCount={this.state.channel_open_count}
|
||||
@@ -249,4 +250,4 @@ TeamAnalytics.propTypes = {
|
||||
team: React.PropTypes.object
|
||||
};
|
||||
|
||||
export default injectIntl(TeamAnalytics);
|
||||
export default injectIntl(TeamAnalytics);
|
||||
|
||||
Reference in New Issue
Block a user