summaryrefslogtreecommitdiffstats
path: root/web/react
diff options
context:
space:
mode:
Diffstat (limited to 'web/react')
-rw-r--r--web/react/components/admin_console/admin_controller.jsx7
-rw-r--r--web/react/components/admin_console/admin_sidebar.jsx19
-rw-r--r--web/react/components/admin_console/analytics.jsx279
-rw-r--r--web/react/components/admin_console/system_analytics.jsx161
-rw-r--r--web/react/components/admin_console/team_analytics.jsx260
-rw-r--r--web/react/components/sidebar.jsx4
-rw-r--r--web/react/pages/admin_console.jsx77
-rw-r--r--web/react/pages/authorize.jsx75
-rw-r--r--web/react/pages/claim_account.jsx71
-rw-r--r--web/react/pages/docs.jsx64
-rw-r--r--web/react/pages/password_reset.jsx71
-rw-r--r--web/react/pages/signup_team.jsx63
-rw-r--r--web/react/pages/signup_team_complete.jsx67
-rw-r--r--web/react/pages/signup_user_complete.jsx73
-rw-r--r--web/react/pages/verify.jsx65
-rw-r--r--web/react/utils/client.jsx18
16 files changed, 1048 insertions, 326 deletions
diff --git a/web/react/components/admin_console/admin_controller.jsx b/web/react/components/admin_console/admin_controller.jsx
index 0f85c238d..efd163017 100644
--- a/web/react/components/admin_console/admin_controller.jsx
+++ b/web/react/components/admin_console/admin_controller.jsx
@@ -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.
import AdminSidebar from './admin_sidebar.jsx';
@@ -23,6 +23,7 @@ import TeamUsersTab from './team_users.jsx';
import TeamAnalyticsTab from './team_analytics.jsx';
import LdapSettingsTab from './ldap_settings.jsx';
import LicenseSettingsTab from './license_settings.jsx';
+import SystemAnalyticsTab from './system_analytics.jsx';
export default class AdminController extends React.Component {
constructor(props) {
@@ -45,7 +46,7 @@ export default class AdminController extends React.Component {
config: AdminStore.getConfig(),
teams: AdminStore.getAllTeams(),
selectedTeams,
- selected: props.tab || 'service_settings',
+ selected: props.tab || 'system_analytics',
selectedTeam: props.teamId || null
};
@@ -165,6 +166,8 @@ export default class AdminController extends React.Component {
if (this.state.teams) {
tab = <TeamAnalyticsTab team={this.state.teams[this.state.selectedTeam]} />;
}
+ } else if (this.state.selected === 'system_analytics') {
+ tab = <SystemAnalyticsTab />;
}
}
diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx
index 5a5eaa055..66f82c55b 100644
--- a/web/react/components/admin_console/admin_sidebar.jsx
+++ b/web/react/components/admin_console/admin_sidebar.jsx
@@ -196,6 +196,25 @@ export default class AdminSidebar extends React.Component {
<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'>
+ <li>
+ <h4>
+ <span className='icon fa fa-gear'></span>
<span>{'SETTINGS'}</span>
</h4>
</li>
diff --git a/web/react/components/admin_console/analytics.jsx b/web/react/components/admin_console/analytics.jsx
new file mode 100644
index 000000000..70ef1ecab
--- /dev/null
+++ b/web/react/components/admin_console/analytics.jsx
@@ -0,0 +1,279 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import * as Utils from '../../utils/utils.jsx';
+import Constants from '../../utils/constants.jsx';
+import LineChart from './line_chart.jsx';
+
+var Tooltip = ReactBootstrap.Tooltip;
+var OverlayTrigger = ReactBootstrap.OverlayTrigger;
+
+export default class Analytics extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {};
+ }
+
+ render() { // in the future, break down these into smaller components
+ var serverError = '';
+ if (this.props.serverError) {
+ serverError = <div className='form-group has-error'><label className='control-label'>{this.props.serverError}</label></div>;
+ }
+
+ var totalCount = (
+ <div className='col-sm-3'>
+ <div className='total-count'>
+ <div className='title'>{'Total Users'}<i className='fa fa-users'/></div>
+ <div className='content'>{this.props.uniqueUserCount == null ? 'Loading...' : this.props.uniqueUserCount}</div>
+ </div>
+ </div>
+ );
+
+ var openChannelCount = (
+ <div className='col-sm-3'>
+ <div className='total-count'>
+ <div className='title'>{'Public Channels'}<i className='fa fa-globe'/></div>
+ <div className='content'>{this.props.channelOpenCount == null ? 'Loading...' : this.props.channelOpenCount}</div>
+ </div>
+ </div>
+ );
+
+ var openPrivateCount = (
+ <div className='col-sm-3'>
+ <div className='total-count'>
+ <div className='title'>{'Private Groups'}<i className='fa fa-lock'/></div>
+ <div className='content'>{this.props.channelPrivateCount == null ? 'Loading...' : this.props.channelPrivateCount}</div>
+ </div>
+ </div>
+ );
+
+ var postCount = (
+ <div className='col-sm-3'>
+ <div className='total-count'>
+ <div className='title'>{'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'>{'Total Posts'}</div>
+ <div className='content'>{'Loading...'}</div>
+ </div>
+ </div>
+ );
+
+ if (this.props.postCountsDay != null) {
+ let content;
+ if (this.props.postCountsDay.labels.length === 0) {
+ content = 'Not enough data for a meaningful representation.';
+ } else {
+ content = (
+ <LineChart
+ data={this.props.postCountsDay}
+ width='740'
+ height='225'
+ />
+ );
+ }
+ postCountsByDay = (
+ <div className='col-sm-12'>
+ <div className='total-count by-day'>
+ <div className='title'>{'Total Posts'}</div>
+ <div className='content'>
+ {content}
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+ var usersWithPostsByDay = (
+ <div className='col-sm-12'>
+ <div className='total-count by-day'>
+ <div className='title'>{'Active Users With Posts'}</div>
+ <div className='content'>{'Loading...'}</div>
+ </div>
+ </div>
+ );
+
+ if (this.props.userCountsWithPostsDay != null) {
+ let content;
+ if (this.props.userCountsWithPostsDay.labels.length === 0) {
+ content = 'Not enough data for a meaningful representation.';
+ } else {
+ content = (
+ <LineChart
+ data={this.props.userCountsWithPostsDay}
+ width='740'
+ height='225'
+ />
+ );
+ }
+ usersWithPostsByDay = (
+ <div className='col-sm-12'>
+ <div className='total-count by-day'>
+ <div className='title'>{'Active Users With Posts'}</div>
+ <div className='content'>
+ {content}
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+ let recentActiveUser;
+ 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 = (
+ <div className='col-sm-6'>
+ <div className='total-count recent-active-users'>
+ <div className='title'>{'Recent Active Users'}</div>
+ <div className='content'>
+ {content}
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+ let newUsers;
+ 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 = (
+ <div className='col-sm-6'>
+ <div className='total-count recent-active-users'>
+ <div className='title'>{'Newly Created Users'}</div>
+ <div className='content'>
+ {content}
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+ return (
+ <div className='wrapper--fixed team_statistics'>
+ <h3>{'Statistics for ' + this.props.title}</h3>
+ {serverError}
+ <div className='row'>
+ {totalCount}
+ {postCount}
+ {openChannelCount}
+ {openPrivateCount}
+ </div>
+ <div className='row'>
+ {postCountsByDay}
+ </div>
+ <div className='row'>
+ {usersWithPostsByDay}
+ </div>
+ <div className='row'>
+ {recentActiveUser}
+ {newUsers}
+ </div>
+ </div>
+ );
+ }
+}
+
+Analytics.defaultProps = {
+ title: null,
+ channelOpenCount: null,
+ channelPrivateCount: null,
+ postCount: null,
+ postCountsDay: null,
+ userCountsWithPostsDay: null,
+ recentActiveUsers: null,
+ newlyCreatedUsers: null,
+ uniqueUserCount: null,
+ serverError: null
+};
+
+Analytics.propTypes = {
+ title: React.PropTypes.string,
+ channelOpenCount: React.PropTypes.number,
+ channelPrivateCount: React.PropTypes.number,
+ postCount: React.PropTypes.number,
+ postCountsDay: React.PropTypes.object,
+ userCountsWithPostsDay: React.PropTypes.object,
+ recentActiveUsers: React.PropTypes.array,
+ newlyCreatedUsers: React.PropTypes.array,
+ uniqueUserCount: React.PropTypes.number,
+ serverError: React.PropTypes.string
+};
diff --git a/web/react/components/admin_console/system_analytics.jsx b/web/react/components/admin_console/system_analytics.jsx
new file mode 100644
index 000000000..f54813a94
--- /dev/null
+++ b/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() {
+ 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
+};
diff --git a/web/react/components/admin_console/team_analytics.jsx b/web/react/components/admin_console/team_analytics.jsx
index fe7230946..c164dd98c 100644
--- a/web/react/components/admin_console/team_analytics.jsx
+++ b/web/react/components/admin_console/team_analytics.jsx
@@ -1,13 +1,8 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// 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';
-import * as Utils from '../../utils/utils.jsx';
-import Constants from '../../utils/constants.jsx';
-import LineChart from './line_chart.jsx';
-
-var Tooltip = ReactBootstrap.Tooltip;
-var OverlayTrigger = ReactBootstrap.OverlayTrigger;
export default class TeamAnalytics extends React.Component {
constructor(props) {
@@ -15,7 +10,7 @@ export default class TeamAnalytics extends React.Component {
this.getData = this.getData.bind(this);
- this.state = {
+ this.state = { // most of this state should be from a store in the future
users: null,
serverError: null,
channel_open_count: null,
@@ -24,7 +19,8 @@ export default class TeamAnalytics extends React.Component {
post_counts_day: null,
user_counts_with_posts_day: null,
recent_active_users: null,
- newly_created_users: null
+ newly_created_users: null,
+ unique_user_count: null
};
}
@@ -32,8 +28,8 @@ export default class TeamAnalytics extends React.Component {
this.getData(this.props.team.id);
}
- getData(teamId) {
- Client.getAnalytics(
+ getData(teamId) { // should be moved to an action creator eventually
+ Client.getTeamAnalytics(
teamId,
'standard',
(data) => {
@@ -49,6 +45,10 @@ export default class TeamAnalytics extends React.Component {
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) => {
@@ -56,7 +56,7 @@ export default class TeamAnalytics extends React.Component {
}
);
- Client.getAnalytics(
+ Client.getTeamAnalytics(
teamId,
'post_counts_day',
(data) => {
@@ -91,7 +91,7 @@ export default class TeamAnalytics extends React.Component {
}
);
- Client.getAnalytics(
+ Client.getTeamAnalytics(
teamId,
'user_counts_with_posts_day',
(data) => {
@@ -152,6 +152,10 @@ export default class TeamAnalytics extends React.Component {
var recentActive = [];
for (let i = 0; i < usersList.length; i++) {
+ if (usersList[i].last_activity_at == null) {
+ continue;
+ }
+
recentActive.push(usersList[i]);
if (i > 19) {
break;
@@ -198,227 +202,29 @@ export default class TeamAnalytics extends React.Component {
post_counts_day: null,
user_counts_with_posts_day: null,
recent_active_users: null,
- newly_created_users: null
+ newly_created_users: null,
+ unique_user_count: null
});
this.getData(newProps.team.id);
}
- componentWillUnmount() {
- }
-
render() {
- var serverError = '';
- if (this.state.serverError) {
- serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
- }
-
- var totalCount = (
- <div className='col-sm-3'>
- <div className='total-count'>
- <div className='title'>{'Total Users'}<i className='fa fa-users'/></div>
- <div className='content'>{this.state.users == null ? 'Loading...' : Object.keys(this.state.users).length}</div>
- </div>
- </div>
- );
-
- var openChannelCount = (
- <div className='col-sm-3'>
- <div className='total-count'>
- <div className='title'>{'Public Channels'}<i className='fa fa-globe'/></div>
- <div className='content'>{this.state.channel_open_count == null ? 'Loading...' : this.state.channel_open_count}</div>
- </div>
- </div>
- );
-
- var openPrivateCount = (
- <div className='col-sm-3'>
- <div className='total-count'>
- <div className='title'>{'Private Groups'}<i className='fa fa-lock'/></div>
- <div className='content'>{this.state.channel_private_count == null ? 'Loading...' : this.state.channel_private_count}</div>
- </div>
- </div>
- );
-
- var postCount = (
- <div className='col-sm-3'>
- <div className='total-count'>
- <div className='title'>{'Total Posts'}<i className='fa fa-comment'/></div>
- <div className='content'>{this.state.post_count == null ? 'Loading...' : this.state.post_count}</div>
- </div>
- </div>
- );
-
- var postCountsByDay = (
- <div className='col-sm-12'>
- <div className='total-count by-day'>
- <div className='title'>{'Total Posts'}</div>
- <div className='content'>{'Loading...'}</div>
- </div>
- </div>
- );
-
- if (this.state.post_counts_day != null) {
- postCountsByDay = (
- <div className='col-sm-12'>
- <div className='total-count by-day'>
- <div className='title'>{'Total Posts'}</div>
- <div className='content'>
- <LineChart
- data={this.state.post_counts_day}
- width='740'
- height='225'
- />
- </div>
- </div>
- </div>
- );
- }
-
- var usersWithPostsByDay = (
- <div className='col-sm-12'>
- <div className='total-count by-day'>
- <div className='title'>{'Total Posts'}</div>
- <div>{'Loading...'}</div>
- </div>
- </div>
- );
-
- if (this.state.user_counts_with_posts_day != null) {
- usersWithPostsByDay = (
- <div className='col-sm-12'>
- <div className='total-count by-day'>
- <div className='title'>{'Active Users With Posts'}</div>
- <div className='content'>
- <LineChart
- data={this.state.user_counts_with_posts_day}
- width='740'
- height='225'
- />
- </div>
- </div>
- </div>
- );
- }
-
- var recentActiveUser = (
- <div className='recent-active-users'>
- <div>{'Recent Active Users'}</div>
- <div>{'Loading...'}</div>
- </div>
- );
-
- if (this.state.recent_active_users != null) {
- recentActiveUser = (
- <div className='col-sm-6'>
- <div className='total-count recent-active-users'>
- <div className='title'>{'Recent Active Users'}</div>
- <div className='content'>
- <table>
- <tbody>
- {
- this.state.recent_active_users.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>
- );
- }
-
- var newUsers = (
- <div className='recent-active-users'>
- <div>{'Newly Created Users'}</div>
- <div>{'Loading...'}</div>
- </div>
- );
-
- if (this.state.newly_created_users != null) {
- newUsers = (
- <div className='col-sm-6'>
- <div className='total-count recent-active-users'>
- <div className='title'>{'Newly Created Users'}</div>
- <div className='content'>
- <table>
- <tbody>
- {
- this.state.newly_created_users.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>
- );
- }
-
return (
- <div className='wrapper--fixed team_statistics'>
- <h3>{'Statistics for ' + this.props.team.name}</h3>
- {serverError}
- <div className='row'>
- {totalCount}
- {postCount}
- {openChannelCount}
- {openPrivateCount}
- </div>
- <div className='row'>
- {postCountsByDay}
- </div>
- <div className='row'>
- {usersWithPostsByDay}
- </div>
- <div className='row'>
- {recentActiveUser}
- {newUsers}
- </div>
+ <div>
+ <Analytics
+ title={this.props.team.name}
+ users={this.state.users}
+ 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}
+ recentActiveUsers={this.state.recent_active_users}
+ newlyCreatedUsers={this.state.newly_created_users}
+ uniqueUserCount={this.state.unique_user_count}
+ serverError={this.state.serverError}
+ />
</div>
);
}
diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index eaeb7bb91..c902731c9 100644
--- a/web/react/components/sidebar.jsx
+++ b/web/react/components/sidebar.jsx
@@ -506,9 +506,9 @@ export default class Sidebar extends React.Component {
link.rel = 'shortcut icon';
link.id = 'favicon';
if (this.badgesActive) {
- link.href = '/static/images/redfavicon.ico';
+ link.href = '/static/images/favicon/redfavicon-16x16.png';
} else {
- link.href = '/static/images/favicon.ico';
+ link.href = '/static/images/favicon/favicon-16x16.png';
}
var head = document.getElementsByTagName('head')[0];
var oldLink = document.getElementById('favicon');
diff --git a/web/react/pages/admin_console.jsx b/web/react/pages/admin_console.jsx
index cbd2bd80d..3f4c39934 100644
--- a/web/react/pages/admin_console.jsx
+++ b/web/react/pages/admin_console.jsx
@@ -4,25 +4,68 @@
import ErrorBar from '../components/error_bar.jsx';
import SelectTeamModal from '../components/admin_console/select_team_modal.jsx';
import AdminController from '../components/admin_console/admin_controller.jsx';
+import * as Client from '../utils/client.jsx';
-export function setupAdminConsolePage(props) {
- ReactDOM.render(
- <AdminController
- tab={props.ActiveTab}
- teamId={props.TeamId}
- />,
- document.getElementById('admin_controller')
- );
+var IntlProvider = ReactIntl.IntlProvider;
- ReactDOM.render(
- <SelectTeamModal />,
- document.getElementById('select_team_modal')
- );
+class Root extends React.Component {
+ constructor() {
+ super();
+ this.state = {
+ translations: null,
+ loaded: false
+ };
+ }
- ReactDOM.render(
- <ErrorBar/>,
- document.getElementById('error_bar')
- );
+ static propTypes() {
+ return {
+ map: React.PropTypes.object.isRequired
+ };
+ }
+
+ componentWillMount() {
+ Client.getTranslations(
+ this.props.map.Locale,
+ (data) => {
+ this.setState({
+ translations: data,
+ loaded: true
+ });
+ },
+ () => {
+ this.setState({
+ loaded: true
+ });
+ }
+ );
+ }
+
+ render() {
+ if (!this.state.loaded) {
+ return <div></div>;
+ }
+
+ return (
+ <IntlProvider
+ locale={this.props.map.Locale}
+ messages={this.state.translations}
+ >
+ <div>
+ <ErrorBar/>
+ <AdminController
+ tab={this.props.map.ActiveTab}
+ teamId={this.props.map.TeamId}
+ />
+ <SelectTeamModal />
+ </div>
+ </IntlProvider>
+ );
+ }
}
-global.window.setup_admin_console_page = setupAdminConsolePage;
+global.window.setup_admin_console_page = function setup(props) {
+ ReactDOM.render(
+ <Root map={props} />,
+ document.getElementById('admin_controller')
+ );
+};
diff --git a/web/react/pages/authorize.jsx b/web/react/pages/authorize.jsx
index 71f17d007..7474332ce 100644
--- a/web/react/pages/authorize.jsx
+++ b/web/react/pages/authorize.jsx
@@ -2,20 +2,69 @@
// See License.txt for license information.
import Authorize from '../components/authorize.jsx';
+import * as Client from '../utils/client.jsx';
-function setupAuthorizePage(props) {
+var IntlProvider = ReactIntl.IntlProvider;
+
+class Root extends React.Component {
+ constructor() {
+ super();
+ this.state = {
+ translations: null,
+ loaded: false
+ };
+ }
+
+ static propTypes() {
+ return {
+ map: React.PropTypes.object.isRequired
+ };
+ }
+
+ componentWillMount() {
+ Client.getTranslations(
+ this.props.map.Locale,
+ (data) => {
+ this.setState({
+ translations: data,
+ loaded: true
+ });
+ },
+ () => {
+ this.setState({
+ loaded: true
+ });
+ }
+ );
+ }
+
+ render() {
+ if (!this.state.loaded) {
+ return <div></div>;
+ }
+
+ return (
+ <IntlProvider
+ locale={this.props.map.Locale}
+ messages={this.state.translations}
+ >
+ <Authorize
+ teamName={this.props.map.TeamName}
+ appName={this.props.map.AppName}
+ responseType={this.props.map.ResponseType}
+ clientId={this.props.map.ClientId}
+ redirectUri={this.props.map.RedirectUri}
+ scope={this.props.map.Scope}
+ state={this.props.map.State}
+ />
+ </IntlProvider>
+ );
+ }
+}
+
+global.window.setup_authorize_page = function setup(props) {
ReactDOM.render(
- <Authorize
- teamName={props.TeamName}
- appName={props.AppName}
- responseType={props.ResponseType}
- clientId={props.ClientId}
- redirectUri={props.RedirectUri}
- scope={props.Scope}
- state={props.State}
- />,
+ <Root map={props} />,
document.getElementById('authorize')
);
-}
-
-global.window.setup_authorize_page = setupAuthorizePage;
+};
diff --git a/web/react/pages/claim_account.jsx b/web/react/pages/claim_account.jsx
index bca203d96..7c6af73ca 100644
--- a/web/react/pages/claim_account.jsx
+++ b/web/react/pages/claim_account.jsx
@@ -2,18 +2,67 @@
// See License.txt for license information.
import ClaimAccount from '../components/claim/claim_account.jsx';
+import * as Client from '../utils/client.jsx';
-function setupClaimAccountPage(props) {
+var IntlProvider = ReactIntl.IntlProvider;
+
+class Root extends React.Component {
+ constructor() {
+ super();
+ this.state = {
+ translations: null,
+ loaded: false
+ };
+ }
+
+ static propTypes() {
+ return {
+ map: React.PropTypes.object.isRequired
+ };
+ }
+
+ componentWillMount() {
+ Client.getTranslations(
+ this.props.map.Locale,
+ (data) => {
+ this.setState({
+ translations: data,
+ loaded: true
+ });
+ },
+ () => {
+ this.setState({
+ loaded: true
+ });
+ }
+ );
+ }
+
+ render() {
+ if (!this.state.loaded) {
+ return <div></div>;
+ }
+
+ return (
+ <IntlProvider
+ locale={this.props.map.Locale}
+ messages={this.state.translations}
+ >
+ <ClaimAccount
+ email={this.props.map.Email}
+ currentType={this.props.map.CurrentType}
+ newType={this.props.map.NewType}
+ teamName={this.props.map.TeamName}
+ teamDisplayName={this.props.map.TeamDisplayName}
+ />
+ </IntlProvider>
+ );
+ }
+}
+
+global.window.setup_claim_account_page = function setup(props) {
ReactDOM.render(
- <ClaimAccount
- email={props.Email}
- currentType={props.CurrentType}
- newType={props.NewType}
- teamName={props.TeamName}
- teamDisplayName={props.TeamDisplayName}
- />,
+ <Root map={props} />,
document.getElementById('claim')
);
-}
-
-global.window.setup_claim_account_page = setupClaimAccountPage;
+}; \ No newline at end of file
diff --git a/web/react/pages/docs.jsx b/web/react/pages/docs.jsx
index 74d9c2d19..2f5d4db55 100644
--- a/web/react/pages/docs.jsx
+++ b/web/react/pages/docs.jsx
@@ -2,15 +2,63 @@
// See License.txt for license information.
import Docs from '../components/docs.jsx';
+import * as Client from '../utils/client.jsx';
-function setupDocumentationPage(props) {
- ReactDOM.render(
- <Docs
- site={props.Site}
- />,
- document.getElementById('docs')
- );
+var IntlProvider = ReactIntl.IntlProvider;
+
+class Root extends React.Component {
+ constructor() {
+ super();
+ this.state = {
+ translations: null,
+ loaded: false
+ };
+ }
+
+ static propTypes() {
+ return {
+ map: React.PropTypes.object.isRequired
+ };
+ }
+
+ componentWillMount() {
+ Client.getTranslations(
+ this.props.map.Locale,
+ (data) => {
+ this.setState({
+ translations: data,
+ loaded: true
+ });
+ },
+ () => {
+ this.setState({
+ loaded: true
+ });
+ }
+ );
+ }
+
+ render() {
+ if (!this.state.loaded) {
+ return <div></div>;
+ }
+
+ return (
+ <IntlProvider
+ locale={this.props.map.Locale}
+ messages={this.state.translations}
+ >
+ <Docs site={this.props.map.Site} />
+ </IntlProvider>
+ );
+ }
}
global.window.mm_user = global.window.mm_user || {};
-global.window.setup_documentation_page = setupDocumentationPage;
+
+global.window.setup_documentation_page = function setup(props) {
+ ReactDOM.render(
+ <Root map={props} />,
+ document.getElementById('docs')
+ );
+};
diff --git a/web/react/pages/password_reset.jsx b/web/react/pages/password_reset.jsx
index 4a6f1dcb0..23bbf2691 100644
--- a/web/react/pages/password_reset.jsx
+++ b/web/react/pages/password_reset.jsx
@@ -2,18 +2,67 @@
// See License.txt for license information.
import PasswordReset from '../components/password_reset.jsx';
+import * as Client from '../utils/client.jsx';
-function setupPasswordResetPage(props) {
+var IntlProvider = ReactIntl.IntlProvider;
+
+class Root extends React.Component {
+ constructor() {
+ super();
+ this.state = {
+ translations: null,
+ loaded: false
+ };
+ }
+
+ static propTypes() {
+ return {
+ map: React.PropTypes.object.isRequired
+ };
+ }
+
+ componentWillMount() {
+ Client.getTranslations(
+ this.props.map.Locale,
+ (data) => {
+ this.setState({
+ translations: data,
+ loaded: true
+ });
+ },
+ () => {
+ this.setState({
+ loaded: true
+ });
+ }
+ );
+ }
+
+ render() {
+ if (!this.state.loaded) {
+ return <div></div>;
+ }
+
+ return (
+ <IntlProvider
+ locale={this.props.map.Locale}
+ messages={this.state.translations}
+ >
+ <PasswordReset
+ isReset={this.props.map.IsReset}
+ teamDisplayName={this.props.map.TeamDisplayName}
+ teamName={this.props.map.TeamName}
+ hash={this.props.map.Hash}
+ data={this.props.map.Data}
+ />
+ </IntlProvider>
+ );
+ }
+}
+
+global.window.setup_password_reset_page = function setup(props) {
ReactDOM.render(
- <PasswordReset
- isReset={props.IsReset}
- teamDisplayName={props.TeamDisplayName}
- teamName={props.TeamName}
- hash={props.Hash}
- data={props.Data}
- />,
+ <Root map={props} />,
document.getElementById('reset')
);
-}
-
-global.window.setup_password_reset_page = setupPasswordResetPage;
+};
diff --git a/web/react/pages/signup_team.jsx b/web/react/pages/signup_team.jsx
index 08ea45000..8f4f86a7c 100644
--- a/web/react/pages/signup_team.jsx
+++ b/web/react/pages/signup_team.jsx
@@ -2,8 +2,60 @@
// See License.txt for license information.
import SignupTeam from '../components/signup_team.jsx';
+import * as Client from '../utils/client.jsx';
-function setupSignupTeamPage(props) {
+var IntlProvider = ReactIntl.IntlProvider;
+
+class Root extends React.Component {
+ constructor() {
+ super();
+ this.state = {
+ translations: null,
+ loaded: false
+ };
+ }
+
+ static propTypes() {
+ return {
+ map: React.PropTypes.object.isRequired,
+ teams: React.PropTypes.object.isRequired
+ };
+ }
+
+ componentWillMount() {
+ Client.getTranslations(
+ this.props.map.Locale,
+ (data) => {
+ this.setState({
+ translations: data,
+ loaded: true
+ });
+ },
+ () => {
+ this.setState({
+ loaded: true
+ });
+ }
+ );
+ }
+
+ render() {
+ if (!this.state.loaded) {
+ return <div></div>;
+ }
+
+ return (
+ <IntlProvider
+ locale={this.props.map.Locale}
+ messages={this.state.translations}
+ >
+ <SignupTeam teams={this.props.teams} />
+ </IntlProvider>
+ );
+ }
+}
+
+global.window.setup_signup_team_page = function setup(props) {
var teams = [];
for (var prop in props) {
@@ -15,9 +67,10 @@ function setupSignupTeamPage(props) {
}
ReactDOM.render(
- <SignupTeam teams={teams} />,
+ <Root
+ map={props}
+ teams={teams}
+ />,
document.getElementById('signup-team')
);
-}
-
-global.window.setup_signup_team_page = setupSignupTeamPage;
+}; \ No newline at end of file
diff --git a/web/react/pages/signup_team_complete.jsx b/web/react/pages/signup_team_complete.jsx
index d5ed144a1..1bee4e598 100644
--- a/web/react/pages/signup_team_complete.jsx
+++ b/web/react/pages/signup_team_complete.jsx
@@ -2,16 +2,65 @@
// See License.txt for license information.
import SignupTeamComplete from '../components/signup_team_complete.jsx';
+import * as Client from '../utils/client.jsx';
-function setupSignupTeamCompletePage(props) {
+var IntlProvider = ReactIntl.IntlProvider;
+
+class Root extends React.Component {
+ constructor() {
+ super();
+ this.state = {
+ translations: null,
+ loaded: false
+ };
+ }
+
+ static propTypes() {
+ return {
+ map: React.PropTypes.object.isRequired
+ };
+ }
+
+ componentWillMount() {
+ Client.getTranslations(
+ this.props.map.Locale,
+ (data) => {
+ this.setState({
+ translations: data,
+ loaded: true
+ });
+ },
+ () => {
+ this.setState({
+ loaded: true
+ });
+ }
+ );
+ }
+
+ render() {
+ if (!this.state.loaded) {
+ return <div></div>;
+ }
+
+ return (
+ <IntlProvider
+ locale={this.props.map.Locale}
+ messages={this.state.translations}
+ >
+ <SignupTeamComplete
+ email={this.props.map.Email}
+ hash={this.props.map.Hash}
+ data={this.props.map.Data}
+ />
+ </IntlProvider>
+ );
+ }
+}
+
+global.window.setup_signup_team_complete_page = function setup(props) {
ReactDOM.render(
- <SignupTeamComplete
- email={props.Email}
- hash={props.Hash}
- data={props.Data}
- />,
+ <Root map={props} />,
document.getElementById('signup-team-complete')
);
-}
-
-global.window.setup_signup_team_complete_page = setupSignupTeamCompletePage;
+}; \ No newline at end of file
diff --git a/web/react/pages/signup_user_complete.jsx b/web/react/pages/signup_user_complete.jsx
index de2c48443..6c761c1ee 100644
--- a/web/react/pages/signup_user_complete.jsx
+++ b/web/react/pages/signup_user_complete.jsx
@@ -2,19 +2,68 @@
// See License.txt for license information.
import SignupUserComplete from '../components/signup_user_complete.jsx';
+import * as Client from '../utils/client.jsx';
-function setupSignupUserCompletePage(props) {
+var IntlProvider = ReactIntl.IntlProvider;
+
+class Root extends React.Component {
+ constructor() {
+ super();
+ this.state = {
+ translations: null,
+ loaded: false
+ };
+ }
+
+ static propTypes() {
+ return {
+ map: React.PropTypes.object.isRequired
+ };
+ }
+
+ componentWillMount() {
+ Client.getTranslations(
+ this.props.map.Locale,
+ (data) => {
+ this.setState({
+ translations: data,
+ loaded: true
+ });
+ },
+ () => {
+ this.setState({
+ loaded: true
+ });
+ }
+ );
+ }
+
+ render() {
+ if (!this.state.loaded) {
+ return <div></div>;
+ }
+
+ return (
+ <IntlProvider
+ locale={this.props.map.Locale}
+ messages={this.state.translations}
+ >
+ <SignupUserComplete
+ teamId={this.props.map.TeamId}
+ teamName={this.props.map.TeamName}
+ teamDisplayName={this.props.map.TeamDisplayName}
+ email={this.props.map.Email}
+ hash={this.props.map.Hash}
+ data={this.props.map.Data}
+ />
+ </IntlProvider>
+ );
+ }
+}
+
+global.window.setup_signup_user_complete_page = function setup(props) {
ReactDOM.render(
- <SignupUserComplete
- teamId={props.TeamId}
- teamName={props.TeamName}
- teamDisplayName={props.TeamDisplayName}
- email={props.Email}
- hash={props.Hash}
- data={props.Data}
- />,
+ <Root map={props} />,
document.getElementById('signup-user-complete')
);
-}
-
-global.window.setup_signup_user_complete_page = setupSignupUserCompletePage;
+}; \ No newline at end of file
diff --git a/web/react/pages/verify.jsx b/web/react/pages/verify.jsx
index d4ce4844d..2fc619e58 100644
--- a/web/react/pages/verify.jsx
+++ b/web/react/pages/verify.jsx
@@ -2,15 +2,66 @@
// See License.txt for license information.
import EmailVerify from '../components/email_verify.jsx';
+import * as Client from '../utils/client.jsx';
-global.window.setupVerifyPage = function setupVerifyPage(props) {
+var IntlProvider = ReactIntl.IntlProvider;
+
+class Root extends React.Component {
+ constructor() {
+ super();
+ this.state = {
+ translations: null,
+ loaded: false
+ };
+ }
+
+ static propTypes() {
+ return {
+ map: React.PropTypes.object.isRequired
+ };
+ }
+
+ componentWillMount() {
+ Client.getTranslations(
+ this.props.map.Locale,
+ (data) => {
+ this.setState({
+ translations: data,
+ loaded: true
+ });
+ },
+ () => {
+ this.setState({
+ loaded: true
+ });
+ }
+ );
+ }
+
+ render() {
+ if (!this.state.loaded) {
+ return <div></div>;
+ }
+
+ return (
+ <IntlProvider
+ locale={this.props.map.Locale}
+ messages={this.state.translations}
+ >
+ <EmailVerify
+ isVerified={this.props.map.IsVerified}
+ teamURL={this.props.map.TeamURL}
+ userEmail={this.props.map.UserEmail}
+ resendSuccess={this.props.map.ResendSuccess}
+ />
+ </IntlProvider>
+ );
+ }
+}
+
+global.window.setupVerifyPage = function setup(props) {
ReactDOM.render(
- <EmailVerify
- isVerified={props.IsVerified}
- teamURL={props.TeamURL}
- userEmail={props.UserEmail}
- resendSuccess={props.ResendSuccess}
- />,
+ <Root map={props} />,
document.getElementById('verify')
);
};
diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx
index 65353b70d..09cd4162a 100644
--- a/web/react/utils/client.jsx
+++ b/web/react/utils/client.jsx
@@ -399,7 +399,7 @@ export function getConfig(success, error) {
});
}
-export function getAnalytics(teamId, name, success, error) {
+export function getTeamAnalytics(teamId, name, success, error) {
$.ajax({
url: '/api/v1/admin/analytics/' + teamId + '/' + name,
dataType: 'json',
@@ -407,7 +407,21 @@ export function getAnalytics(teamId, name, success, error) {
type: 'GET',
success,
error: (xhr, status, err) => {
- var e = handleError('getAnalytics', xhr, status, err);
+ var e = handleError('getTeamAnalytics', xhr, status, err);
+ error(e);
+ }
+ });
+}
+
+export function getSystemAnalytics(name, success, error) {
+ $.ajax({
+ url: '/api/v1/admin/analytics/' + name,
+ dataType: 'json',
+ contentType: 'application/json',
+ type: 'GET',
+ success,
+ error: (xhr, status, err) => {
+ var e = handleError('getSystemAnalytics', xhr, status, err);
error(e);
}
});