Add system analytics page
Этот коммит содержится в:
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2015 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 AdminSidebar from './admin_sidebar.jsx';
|
import AdminSidebar from './admin_sidebar.jsx';
|
||||||
@@ -23,6 +23,7 @@ import TeamUsersTab from './team_users.jsx';
|
|||||||
import TeamAnalyticsTab from './team_analytics.jsx';
|
import TeamAnalyticsTab from './team_analytics.jsx';
|
||||||
import LdapSettingsTab from './ldap_settings.jsx';
|
import LdapSettingsTab from './ldap_settings.jsx';
|
||||||
import LicenseSettingsTab from './license_settings.jsx';
|
import LicenseSettingsTab from './license_settings.jsx';
|
||||||
|
import SystemAnalyticsTab from './system_analytics.jsx';
|
||||||
|
|
||||||
export default class AdminController extends React.Component {
|
export default class AdminController extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -165,6 +166,8 @@ export default class AdminController extends React.Component {
|
|||||||
if (this.state.teams) {
|
if (this.state.teams) {
|
||||||
tab = <TeamAnalyticsTab team={this.state.teams[this.state.selectedTeam]} />;
|
tab = <TeamAnalyticsTab team={this.state.teams[this.state.selectedTeam]} />;
|
||||||
}
|
}
|
||||||
|
} else if (this.state.selected === 'system_analytics') {
|
||||||
|
tab = <SystemAnalyticsTab />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -192,6 +192,25 @@ export default class AdminSidebar extends React.Component {
|
|||||||
<div className='nav-pills__container'>
|
<div className='nav-pills__container'>
|
||||||
<ul className='nav nav-pills nav-stacked'>
|
<ul className='nav nav-pills nav-stacked'>
|
||||||
<li>
|
<li>
|
||||||
|
<ul className='nav nav__sub-menu'>
|
||||||
|
<li>
|
||||||
|
<h4>
|
||||||
|
<span className='icon fa fa-gear'></span>
|
||||||
|
<span>{'SITE REPORTS'}</span>
|
||||||
|
</h4>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul className='nav nav__sub-menu padded'>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href='#'
|
||||||
|
className={this.isSelected('system_analytics')}
|
||||||
|
onClick={this.handleClick.bind(this, 'system_analytics', null)}
|
||||||
|
>
|
||||||
|
{'View Statistics'}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
<ul className='nav nav__sub-menu'>
|
<ul className='nav nav__sub-menu'>
|
||||||
<li>
|
<li>
|
||||||
<h4>
|
<h4>
|
||||||
|
|||||||
@@ -94,8 +94,8 @@ export default class Analytics extends React.Component {
|
|||||||
var usersWithPostsByDay = (
|
var usersWithPostsByDay = (
|
||||||
<div className='col-sm-12'>
|
<div className='col-sm-12'>
|
||||||
<div className='total-count by-day'>
|
<div className='total-count by-day'>
|
||||||
<div className='title'>{'Total Posts'}</div>
|
<div className='title'>{'Active Users With Posts'}</div>
|
||||||
<div>{'Loading...'}</div>
|
<div className='content'>{'Loading...'}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -125,98 +125,102 @@ export default class Analytics extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var recentActiveUser = (
|
let recentActiveUser;
|
||||||
<div className='recent-active-users'>
|
|
||||||
<div>{'Recent Active Users'}</div>
|
|
||||||
<div>{'Loading...'}</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (this.props.recentActiveUsers != null) {
|
if (this.props.recentActiveUsers != null) {
|
||||||
|
let content;
|
||||||
|
if (this.props.recentActiveUsers.length === 0) {
|
||||||
|
content = 'Loading...';
|
||||||
|
} else {
|
||||||
|
content = (
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
{
|
||||||
|
this.props.recentActiveUsers.map((user) => {
|
||||||
|
const tooltip = (
|
||||||
|
<Tooltip id={'recent-user-email-tooltip-' + user.id}>
|
||||||
|
{user.email}
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr key={'recent-user-table-entry-' + user.id}>
|
||||||
|
<td>
|
||||||
|
<OverlayTrigger
|
||||||
|
delayShow={Constants.OVERLAY_TIME_DELAY}
|
||||||
|
placement='top'
|
||||||
|
overlay={tooltip}
|
||||||
|
>
|
||||||
|
<time>
|
||||||
|
{user.username}
|
||||||
|
</time>
|
||||||
|
</OverlayTrigger>
|
||||||
|
</td>
|
||||||
|
<td>{Utils.displayDateTime(user.last_activity_at)}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
}
|
||||||
recentActiveUser = (
|
recentActiveUser = (
|
||||||
<div className='col-sm-6'>
|
<div className='col-sm-6'>
|
||||||
<div className='total-count recent-active-users'>
|
<div className='total-count recent-active-users'>
|
||||||
<div className='title'>{'Recent Active Users'}</div>
|
<div className='title'>{'Recent Active Users'}</div>
|
||||||
<div className='content'>
|
<div className='content'>
|
||||||
<table>
|
{content}
|
||||||
<tbody>
|
|
||||||
{
|
|
||||||
this.props.recentActiveUsers.map((user) => {
|
|
||||||
const tooltip = (
|
|
||||||
<Tooltip id={'recent-user-email-tooltip-' + user.id}>
|
|
||||||
{user.email}
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<tr key={'recent-user-table-entry-' + user.id}>
|
|
||||||
<td>
|
|
||||||
<OverlayTrigger
|
|
||||||
delayShow={Constants.OVERLAY_TIME_DELAY}
|
|
||||||
placement='top'
|
|
||||||
overlay={tooltip}
|
|
||||||
>
|
|
||||||
<time>
|
|
||||||
{user.username}
|
|
||||||
</time>
|
|
||||||
</OverlayTrigger>
|
|
||||||
</td>
|
|
||||||
<td>{Utils.displayDateTime(user.last_activity_at)}</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var newUsers = (
|
let newUsers;
|
||||||
<div className='recent-active-users'>
|
|
||||||
<div>{'Newly Created Users'}</div>
|
|
||||||
<div>{'Loading...'}</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (this.props.newlyCreatedUsers != null) {
|
if (this.props.newlyCreatedUsers != null) {
|
||||||
|
let content;
|
||||||
|
if (this.props.newlyCreatedUsers.length === 0) {
|
||||||
|
content = 'Loading...';
|
||||||
|
} else {
|
||||||
|
content = (
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
{
|
||||||
|
this.props.newlyCreatedUsers.map((user) => {
|
||||||
|
const tooltip = (
|
||||||
|
<Tooltip id={'new-user-email-tooltip-' + user.id}>
|
||||||
|
{user.email}
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr key={'new-user-table-entry-' + user.id}>
|
||||||
|
<td>
|
||||||
|
<OverlayTrigger
|
||||||
|
delayShow={Constants.OVERLAY_TIME_DELAY}
|
||||||
|
placement='top'
|
||||||
|
overlay={tooltip}
|
||||||
|
>
|
||||||
|
<time>
|
||||||
|
{user.username}
|
||||||
|
</time>
|
||||||
|
</OverlayTrigger>
|
||||||
|
</td>
|
||||||
|
<td>{Utils.displayDateTime(user.create_at)}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
}
|
||||||
newUsers = (
|
newUsers = (
|
||||||
<div className='col-sm-6'>
|
<div className='col-sm-6'>
|
||||||
<div className='total-count recent-active-users'>
|
<div className='total-count recent-active-users'>
|
||||||
<div className='title'>{'Newly Created Users'}</div>
|
<div className='title'>{'Newly Created Users'}</div>
|
||||||
<div className='content'>
|
<div className='content'>
|
||||||
<table>
|
{content}
|
||||||
<tbody>
|
|
||||||
{
|
|
||||||
this.props.newlyCreatedUsers.map((user) => {
|
|
||||||
const tooltip = (
|
|
||||||
<Tooltip id={'new-user-email-tooltip-' + user.id}>
|
|
||||||
{user.email}
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<tr key={'new-user-table-entry-' + user.id}>
|
|
||||||
<td>
|
|
||||||
<OverlayTrigger
|
|
||||||
delayShow={Constants.OVERLAY_TIME_DELAY}
|
|
||||||
placement='top'
|
|
||||||
overlay={tooltip}
|
|
||||||
>
|
|
||||||
<time>
|
|
||||||
{user.username}
|
|
||||||
</time>
|
|
||||||
</OverlayTrigger>
|
|
||||||
</td>
|
|
||||||
<td>{Utils.displayDateTime(user.create_at)}</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -250,7 +254,6 @@ export default class Analytics extends React.Component {
|
|||||||
|
|
||||||
Analytics.defaultProps = {
|
Analytics.defaultProps = {
|
||||||
title: null,
|
title: null,
|
||||||
users: null,
|
|
||||||
channelOpenCount: null,
|
channelOpenCount: null,
|
||||||
channelPrivateCount: null,
|
channelPrivateCount: null,
|
||||||
postCount: null,
|
postCount: null,
|
||||||
@@ -264,7 +267,6 @@ Analytics.defaultProps = {
|
|||||||
|
|
||||||
Analytics.propTypes = {
|
Analytics.propTypes = {
|
||||||
title: React.PropTypes.string,
|
title: React.PropTypes.string,
|
||||||
users: React.PropTypes.object,
|
|
||||||
channelOpenCount: React.PropTypes.number,
|
channelOpenCount: React.PropTypes.number,
|
||||||
channelPrivateCount: React.PropTypes.number,
|
channelPrivateCount: React.PropTypes.number,
|
||||||
postCount: React.PropTypes.number,
|
postCount: React.PropTypes.number,
|
||||||
|
|||||||
161
web/react/components/admin_console/system_analytics.jsx
Обычный файл
161
web/react/components/admin_console/system_analytics.jsx
Обычный файл
@@ -0,0 +1,161 @@
|
|||||||
|
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
import Analytics from './analytics.jsx';
|
||||||
|
import * as Client from '../../utils/client.jsx';
|
||||||
|
|
||||||
|
export default class SystemAnalytics extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.getData = this.getData.bind(this);
|
||||||
|
|
||||||
|
this.state = { // most of this state should be from a store in the future
|
||||||
|
users: null,
|
||||||
|
serverError: null,
|
||||||
|
channel_open_count: null,
|
||||||
|
channel_private_count: null,
|
||||||
|
post_count: null,
|
||||||
|
post_counts_day: null,
|
||||||
|
user_counts_with_posts_day: null,
|
||||||
|
recent_active_users: null,
|
||||||
|
newly_created_users: null,
|
||||||
|
unique_user_count: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
getData() { // should be moved to an action creator eventually
|
||||||
|
Client.getSystemAnalytics(
|
||||||
|
'standard',
|
||||||
|
(data) => {
|
||||||
|
for (var index in data) {
|
||||||
|
if (data[index].name === 'channel_open_count') {
|
||||||
|
this.setState({channel_open_count: data[index].value});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data[index].name === 'channel_private_count') {
|
||||||
|
this.setState({channel_private_count: data[index].value});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data[index].name === 'post_count') {
|
||||||
|
this.setState({post_count: data[index].value});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data[index].name === 'unique_user_count') {
|
||||||
|
this.setState({unique_user_count: data[index].value});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
this.setState({serverError: err.message});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Client.getSystemAnalytics(
|
||||||
|
'post_counts_day',
|
||||||
|
(data) => {
|
||||||
|
data.reverse();
|
||||||
|
|
||||||
|
var chartData = {
|
||||||
|
labels: [],
|
||||||
|
datasets: [{
|
||||||
|
label: 'Total Posts',
|
||||||
|
fillColor: 'rgba(151,187,205,0.2)',
|
||||||
|
strokeColor: 'rgba(151,187,205,1)',
|
||||||
|
pointColor: 'rgba(151,187,205,1)',
|
||||||
|
pointStrokeColor: '#fff',
|
||||||
|
pointHighlightFill: '#fff',
|
||||||
|
pointHighlightStroke: 'rgba(151,187,205,1)',
|
||||||
|
data: []
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
for (var index in data) {
|
||||||
|
if (data[index]) {
|
||||||
|
var row = data[index];
|
||||||
|
chartData.labels.push(row.name);
|
||||||
|
chartData.datasets[0].data.push(row.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({post_counts_day: chartData});
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
this.setState({serverError: err.message});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Client.getSystemAnalytics(
|
||||||
|
'user_counts_with_posts_day',
|
||||||
|
(data) => {
|
||||||
|
data.reverse();
|
||||||
|
|
||||||
|
var chartData = {
|
||||||
|
labels: [],
|
||||||
|
datasets: [{
|
||||||
|
label: 'Active Users With Posts',
|
||||||
|
fillColor: 'rgba(151,187,205,0.2)',
|
||||||
|
strokeColor: 'rgba(151,187,205,1)',
|
||||||
|
pointColor: 'rgba(151,187,205,1)',
|
||||||
|
pointStrokeColor: '#fff',
|
||||||
|
pointHighlightFill: '#fff',
|
||||||
|
pointHighlightStroke: 'rgba(151,187,205,1)',
|
||||||
|
data: []
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
for (var index in data) {
|
||||||
|
if (data[index]) {
|
||||||
|
var row = data[index];
|
||||||
|
chartData.labels.push(row.name);
|
||||||
|
chartData.datasets[0].data.push(row.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({user_counts_with_posts_day: chartData});
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
this.setState({serverError: err.message});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(newProps) {
|
||||||
|
this.setState({
|
||||||
|
serverError: null,
|
||||||
|
channel_open_count: null,
|
||||||
|
channel_private_count: null,
|
||||||
|
post_count: null,
|
||||||
|
post_counts_day: null,
|
||||||
|
user_counts_with_posts_day: null,
|
||||||
|
unique_user_count: null
|
||||||
|
});
|
||||||
|
|
||||||
|
this.getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Analytics
|
||||||
|
title={'the system'}
|
||||||
|
channelOpenCount={this.state.channel_open_count}
|
||||||
|
channelPrivateCount={this.state.channel_private_count}
|
||||||
|
postCount={this.state.post_count}
|
||||||
|
postCountsDay={this.state.post_counts_day}
|
||||||
|
userCountsWithPostsDay={this.state.user_counts_with_posts_day}
|
||||||
|
uniqueUserCount={this.state.unique_user_count}
|
||||||
|
serverError={this.state.serverError}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SystemAnalytics.propTypes = {
|
||||||
|
team: React.PropTypes.object
|
||||||
|
};
|
||||||
@@ -401,7 +401,7 @@ export function getTeamAnalytics(teamId, name, success, error) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getServerAnalytics(name, success, error) {
|
export function getSystemAnalytics(name, success, error) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/api/v1/admin/analytics/' + name,
|
url: '/api/v1/admin/analytics/' + name,
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
@@ -409,7 +409,7 @@ export function getServerAnalytics(name, success, error) {
|
|||||||
type: 'GET',
|
type: 'GET',
|
||||||
success,
|
success,
|
||||||
error: (xhr, status, err) => {
|
error: (xhr, status, err) => {
|
||||||
var e = handleError('getServerAnalytics', xhr, status, err);
|
var e = handleError('getSystemAnalytics', xhr, status, err);
|
||||||
error(e);
|
error(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user