Этот коммит содержится в:
=Corey Hulen
2016-01-25 11:23:55 -05:00
родитель 6b534f1b0d 5478ea34e4
Коммит ea71731f83
91 изменённых файлов: 3645 добавлений и 713 удалений

Просмотреть файл

@@ -100,8 +100,12 @@ export default class ActivityLogModal extends React.Component {
if (currentSession.props.platform === 'Windows') {
devicePicture = 'fa fa-windows';
} else if (currentSession.props.platform === 'Macintosh' || currentSession.props.platform === 'iPhone') {
} else if (currentSession.props.platform === 'Macintosh' ||
currentSession.props.platform === 'iPhone') {
devicePicture = 'fa fa-apple';
} else if (currentSession.props.platform.browser.indexOf('Mattermost/') === 0) {
devicePicture = 'fa fa-apple';
devicePlatform = 'iPhone';
} else if (currentSession.props.platform === 'Linux') {
if (currentSession.props.os.indexOf('Android') >= 0) {
devicePlatform = 'Android';

Просмотреть файл

@@ -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 />;
}
}

Просмотреть файл

@@ -192,6 +192,25 @@ export default class AdminSidebar extends React.Component {
<div className='nav-pills__container'>
<ul className='nav nav-pills nav-stacked'>
<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'>
<li>
<h4>

Просмотреть файл

@@ -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
};

Просмотреть файл

@@ -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
};

Просмотреть файл

@@ -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>
);
}

Просмотреть файл

@@ -23,7 +23,7 @@ export default class DeletePostModal extends React.Component {
this.selectedList = null;
this.state = {
show: true,
show: false,
post: null,
commentCount: 0,
error: ''
@@ -40,6 +40,14 @@ export default class DeletePostModal extends React.Component {
ModalStore.removeModalListener(ActionTypes.TOGGLE_DELETE_POST_MODAL, this.handleToggle);
}
componentDidUpdate(prevProps, prevState) {
if (this.state.show && !prevState.show) {
setTimeout(() => {
$(ReactDOM.findDOMNode(this.refs.deletePostBtn)).focus();
}, 0);
}
}
handleDelete() {
Client.deletePost(
this.state.post.channel_id,
@@ -149,10 +157,10 @@ export default class DeletePostModal extends React.Component {
{'Cancel'}
</button>
<button
ref='deletePostBtn'
type='button'
className='btn btn-danger'
onClick={this.handleDelete}
autoFocus='autofocus'
>
{'Delete'}
</button>

Просмотреть файл

@@ -5,11 +5,16 @@ import * as Utils from '../utils/utils.jsx';
export default function FileInfoPreview({filename, fileUrl, fileInfo}) {
// non-image files include a section providing details about the file
let infoString = 'File type ' + fileInfo.extension.toUpperCase();
if (fileInfo.size > 0) {
infoString += ', Size ' + Utils.fileSizeToString(fileInfo.size);
const infoParts = [];
if (fileInfo.extension !== '') {
infoParts.push('File type ' + fileInfo.extension.toUpperCase());
}
infoParts.push('Size ' + Utils.fileSizeToString(fileInfo.size));
const infoString = infoParts.join(', ');
const name = decodeURIComponent(Utils.getFileName(filename));
return (

Просмотреть файл

@@ -5,6 +5,8 @@ import SuggestionStore from '../../stores/suggestion_store.jsx';
import UserStore from '../../stores/user_store.jsx';
import * as Utils from '../../utils/utils.jsx';
const MaxUserSuggestions = 40;
class AtMentionSuggestion extends React.Component {
render() {
const {item, isSelection, onClick} = this.props;
@@ -78,6 +80,10 @@ export default class AtMentionProvider {
if (user.username.startsWith(usernamePrefix)) {
filtered.push(user);
}
if (filtered.length >= MaxUserSuggestions) {
break;
}
}
// add dummy users to represent the @all and @channel special mentions

Просмотреть файл

@@ -59,7 +59,13 @@ export default class TeamSignupWelcomePage extends React.Component {
}
}.bind(this),
function error(err) {
this.setState({serverError: err.message});
let errorMsg = err.message;
if (err.detailed_error.indexOf('Invalid RCPT TO address provided') >= 0) {
errorMsg = 'Please enter a valid email address';
}
this.setState({emailError: '', serverError: errorMsg});
}.bind(this)
);
}

Просмотреть файл

@@ -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')
);
};

Просмотреть файл

@@ -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) {
ReactDOM.render(
<Authorize
teamName={props.TeamName}
appName={props.AppName}
responseType={props.ResponseType}
clientId={props.ClientId}
redirectUri={props.RedirectUri}
scope={props.Scope}
state={props.State}
/>,
document.getElementById('authorize')
);
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 = setupAuthorizePage;
global.window.setup_authorize_page = function setup(props) {
ReactDOM.render(
<Root map={props} />,
document.getElementById('authorize')
);
};

Просмотреть файл

@@ -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) {
ReactDOM.render(
<ClaimAccount
email={props.Email}
currentType={props.CurrentType}
newType={props.NewType}
teamName={props.TeamName}
teamDisplayName={props.TeamDisplayName}
/>,
document.getElementById('claim')
);
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 = setupClaimAccountPage;
global.window.setup_claim_account_page = function setup(props) {
ReactDOM.render(
<Root map={props} />,
document.getElementById('claim')
);
};

Просмотреть файл

@@ -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')
);
};

Просмотреть файл

@@ -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) {
ReactDOM.render(
<PasswordReset
isReset={props.IsReset}
teamDisplayName={props.TeamDisplayName}
teamName={props.TeamName}
hash={props.Hash}
data={props.Data}
/>,
document.getElementById('reset')
);
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 = setupPasswordResetPage;
global.window.setup_password_reset_page = function setup(props) {
ReactDOM.render(
<Root map={props} />,
document.getElementById('reset')
);
};

Просмотреть файл

@@ -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;
};

Просмотреть файл

@@ -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) {
ReactDOM.render(
<SignupTeamComplete
email={props.Email}
hash={props.Hash}
data={props.Data}
/>,
document.getElementById('signup-team-complete')
);
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 = setupSignupTeamCompletePage;
global.window.setup_signup_team_complete_page = function setup(props) {
ReactDOM.render(
<Root map={props} />,
document.getElementById('signup-team-complete')
);
};

Просмотреть файл

@@ -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) {
ReactDOM.render(
<SignupUserComplete
teamId={props.TeamId}
teamName={props.TeamName}
teamDisplayName={props.TeamDisplayName}
email={props.Email}
hash={props.Hash}
data={props.Data}
/>,
document.getElementById('signup-user-complete')
);
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 = setupSignupUserCompletePage;
global.window.setup_signup_user_complete_page = function setup(props) {
ReactDOM.render(
<Root map={props} />,
document.getElementById('signup-user-complete')
);
};

Просмотреть файл

@@ -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')
);
};

Просмотреть файл

@@ -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);
}
});

Просмотреть файл

@@ -6,11 +6,7 @@
<body>
<script src="/static/js/Chart.min.js"></script>
<div id='error_bar'></div>
<div id='admin_controller' class='container-fluid'></div>
<div id='select_team_modal'></div>
<div id='admin_controller'></div>
<script>
window.setup_admin_console_page({{ .Props }});

Просмотреть файл

@@ -565,9 +565,9 @@ func verifyEmail(c *api.Context, w http.ResponseWriter, r *http.Request) {
user := result.Data.(*model.User)
if user.LastActivityAt > 0 {
api.SendEmailChangeVerifyEmailAndForget(user.Id, user.Email, team.Name, team.DisplayName, c.GetSiteURL(), c.GetTeamURLFromTeam(team))
api.SendEmailChangeVerifyEmailAndForget(c, user.Id, user.Email, team.Name, team.DisplayName, c.GetSiteURL(), c.GetTeamURLFromTeam(team))
} else {
api.SendVerifyEmailAndForget(user.Id, user.Email, team.Name, team.DisplayName, c.GetSiteURL(), c.GetTeamURLFromTeam(team))
api.SendVerifyEmailAndForget(c, user.Id, user.Email, team.Name, team.DisplayName, c.GetSiteURL(), c.GetTeamURLFromTeam(team))
}
newAddress := strings.Replace(r.URL.String(), "&resend=true", "&resend_success=true", -1)