PLT-6657 Move system console to use v4 endpoints and redux (#6572)

* Move system console to use v4 endpoints and redux

* Rename logs dir to get past gitignore

* Fix test email

* Update brand unit test

* Updates per feedback
Этот коммит содержится в:
Joram Wilander
2017-06-14 08:56:56 -04:00
коммит произвёл Harrison Healey
родитель 40efd8367a
Коммит 1138dd6770
29 изменённых файлов: 622 добавлений и 670 удалений

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

@@ -23,6 +23,7 @@ func getBrandImage(c *Context, w http.ResponseWriter, r *http.Request) {
// No permission check required // No permission check required
if img, err := app.GetBrandImage(); err != nil { if img, err := app.GetBrandImage(); err != nil {
w.WriteHeader(http.StatusNotFound)
w.Write(nil) w.Write(nil)
} else { } else {
w.Header().Set("Content-Type", "image/png") w.Header().Set("Content-Type", "image/png")

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

@@ -13,27 +13,15 @@ func TestGetBrandImage(t *testing.T) {
defer TearDown() defer TearDown()
Client := th.Client Client := th.Client
data, resp := Client.GetBrandImage() _, resp := Client.GetBrandImage()
CheckNoError(t, resp) CheckNotFoundStatus(t, resp)
if len(data) != 0 {
t.Fatal("no image uploaded - should be empty")
}
Client.Logout() Client.Logout()
data, resp = Client.GetBrandImage() _, resp = Client.GetBrandImage()
CheckNoError(t, resp) CheckNotFoundStatus(t, resp)
if len(data) != 0 { _, resp = th.SystemAdminClient.GetBrandImage()
t.Fatal("no image uploaded - should be empty") CheckNotFoundStatus(t, resp)
}
data, resp = th.SystemAdminClient.GetBrandImage()
CheckNoError(t, resp)
if len(data) != 0 {
t.Fatal("no image uploaded - should be empty")
}
} }
func TestUploadBrandImage(t *testing.T) { func TestUploadBrandImage(t *testing.T) {

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

@@ -20,7 +20,7 @@ func InitCompliance() {
BaseRoutes.Compliance.Handle("/reports", ApiSessionRequired(createComplianceReport)).Methods("POST") BaseRoutes.Compliance.Handle("/reports", ApiSessionRequired(createComplianceReport)).Methods("POST")
BaseRoutes.Compliance.Handle("/reports", ApiSessionRequired(getComplianceReports)).Methods("GET") BaseRoutes.Compliance.Handle("/reports", ApiSessionRequired(getComplianceReports)).Methods("GET")
BaseRoutes.Compliance.Handle("/reports/{report_id:[A-Za-z0-9]+}", ApiSessionRequired(getComplianceReport)).Methods("GET") BaseRoutes.Compliance.Handle("/reports/{report_id:[A-Za-z0-9]+}", ApiSessionRequired(getComplianceReport)).Methods("GET")
BaseRoutes.Compliance.Handle("/reports/{report_id:[A-Za-z0-9]+}/download", ApiSessionRequired(downloadComplianceReport)).Methods("GET") BaseRoutes.Compliance.Handle("/reports/{report_id:[A-Za-z0-9]+}/download", ApiSessionRequiredTrustRequester(downloadComplianceReport)).Methods("GET")
} }
func createComplianceReport(c *Context, w http.ResponseWriter, r *http.Request) { func createComplianceReport(c *Context, w http.ResponseWriter, r *http.Request) {

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

@@ -52,13 +52,17 @@ func getSystemPing(c *Context, w http.ResponseWriter, r *http.Request) {
} }
func testEmail(c *Context, w http.ResponseWriter, r *http.Request) { func testEmail(c *Context, w http.ResponseWriter, r *http.Request) {
cfg := model.ConfigFromJson(r.Body)
if cfg == nil {
cfg = utils.Cfg
}
if !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { if !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) {
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
return return
} }
err := app.TestEmail(c.Session.UserId, utils.Cfg) err := app.TestEmail(c.Session.UserId, cfg)
if err != nil { if err != nil {
c.Err = err c.Err = err
return return

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

@@ -27,7 +27,7 @@ func InitUser() {
BaseRoutes.Users.Handle("/autocomplete", ApiSessionRequired(autocompleteUsers)).Methods("GET") BaseRoutes.Users.Handle("/autocomplete", ApiSessionRequired(autocompleteUsers)).Methods("GET")
BaseRoutes.User.Handle("", ApiSessionRequired(getUser)).Methods("GET") BaseRoutes.User.Handle("", ApiSessionRequired(getUser)).Methods("GET")
BaseRoutes.User.Handle("/image", ApiSessionRequired(getProfileImage)).Methods("GET") BaseRoutes.User.Handle("/image", ApiSessionRequiredTrustRequester(getProfileImage)).Methods("GET")
BaseRoutes.User.Handle("/image", ApiSessionRequired(setProfileImage)).Methods("POST") BaseRoutes.User.Handle("/image", ApiSessionRequired(setProfileImage)).Methods("POST")
BaseRoutes.User.Handle("", ApiSessionRequired(updateUser)).Methods("PUT") BaseRoutes.User.Handle("", ApiSessionRequired(updateUser)).Methods("PUT")
BaseRoutes.User.Handle("/patch", ApiSessionRequired(patchUser)).Methods("PUT") BaseRoutes.User.Handle("/patch", ApiSessionRequired(patchUser)).Methods("PUT")

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

@@ -2,201 +2,154 @@
// See License.txt for license information. // See License.txt for license information.
import Client from 'client/web_client.jsx'; import Client from 'client/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import {browserHistory} from 'react-router/es6'; import {browserHistory} from 'react-router/es6';
// Redux actions
import store from 'stores/redux_store.jsx'; import store from 'stores/redux_store.jsx';
const dispatch = store.dispatch; const dispatch = store.dispatch;
const getState = store.getState; const getState = store.getState;
import {getUser} from 'mattermost-redux/actions/users'; import {updateUserMfa, updateUserPassword} from 'mattermost-redux/actions/users';
import * as AdminActions from 'mattermost-redux/actions/admin';
export function saveConfig(config, success, error) { export function saveConfig(config, success, error) {
Client.saveConfig( AdminActions.updateConfig(config)(dispatch, getState).then(
config, (data) => {
() => { if (data && success) {
if (success) { success(data);
success(); } else if (data == null && error) {
} const serverError = getState().requests.admin.updateConfig.error;
}, error({id: serverError.server_error_id, ...serverError});
(err) => {
if (error) {
error(err);
} }
} }
); );
} }
export function reloadConfig(success, error) { export function reloadConfig(success, error) {
Client.reloadConfig( AdminActions.reloadConfig()(dispatch, getState).then(
() => { (data) => {
AsyncClient.getConfig(); if (data && success) {
if (success) { AdminActions.getConfig()(dispatch, getState);
success(); success(data);
} } else if (data == null && error) {
}, const serverError = getState().requests.admin.reloadConfig.error;
(err) => { error({id: serverError.server_error_id, ...serverError});
if (error) {
error(err);
} }
} }
); );
} }
export function adminResetMfa(userId, success, error) { export function adminResetMfa(userId, success, error) {
Client.adminResetMfa( updateUserMfa(userId, false)(dispatch, getState).then(
userId, (data) => {
() => { if (data && success) {
getUser(userId)(dispatch, getState); success(data);
} else if (data == null && error) {
if (success) { const serverError = getState().requests.users.updateUser.error;
success(); error({id: serverError.server_error_id, ...serverError});
}
},
(err) => {
if (error) {
error(err);
} }
} }
); );
} }
export function getClusterStatus(success, error) { export function getClusterStatus(success, error) {
Client.getClusterStatus( AdminActions.getClusterStatus()(dispatch, getState).then(
(data) => { (data) => {
if (success) { if (data && success) {
success(data); success(data);
} } else if (data == null && error) {
}, const serverError = getState().requests.admin.getClusterStatus.error;
(err) => { error({id: serverError.server_error_id, ...serverError});
AsyncClient.dispatchError(err, 'getClusterStatus');
if (error) {
error(err);
}
}
);
}
export function saveComplianceReports(job, success, error) {
Client.saveComplianceReports(
job,
() => {
if (success) {
success();
}
},
(err) => {
if (error) {
error(err);
} }
} }
); );
} }
export function testEmail(config, success, error) { export function testEmail(config, success, error) {
Client.testEmail( AdminActions.testEmail(config)(dispatch, getState).then(
config, (data) => {
() => { if (data && success) {
if (success) { success(data);
success(); } else if (data == null && error) {
} const serverError = getState().requests.admin.testEmail.error;
}, error({id: serverError.server_error_id, ...serverError});
(err) => {
if (error) {
error(err);
} }
} }
); );
} }
export function ldapTest(success, error) { export function ldapTest(success, error) {
Client.ldapTest( AdminActions.testLdap()(dispatch, getState).then(
() => { (data) => {
if (success) { if (data && success) {
success(); success(data);
} } else if (data == null && error) {
}, const serverError = getState().requests.admin.testLdap.error;
(err) => { error({id: serverError.server_error_id, ...serverError});
if (error) {
error(err);
} }
} }
); );
} }
export function invalidateAllCaches(success, error) { export function invalidateAllCaches(success, error) {
Client.invalidateAllCaches( AdminActions.invalidateCaches()(dispatch, getState).then(
() => { (data) => {
if (success) { if (data && success) {
success(); success(data);
} } else if (data == null && error) {
}, const serverError = getState().requests.admin.invalidateCaches.error;
(err) => { error({id: serverError.server_error_id, ...serverError});
if (error) {
error(err);
} }
} }
); );
} }
export function recycleDatabaseConnection(success, error) { export function recycleDatabaseConnection(success, error) {
Client.recycleDatabaseConnection( AdminActions.recycleDatabase()(dispatch, getState).then(
() => { (data) => {
if (success) { if (data && success) {
success(); success(data);
} } else if (data == null && error) {
}, const serverError = getState().requests.admin.recycleDatabase.error;
(err) => { error({id: serverError.server_error_id, ...serverError});
if (error) {
error(err);
} }
} }
); );
} }
export function adminResetPassword(userId, password, success, error) { export function adminResetPassword(userId, password, success, error) {
Client.adminResetPassword( updateUserPassword(userId, '', password)(dispatch, getState).then(
userId, (data) => {
password, if (data && success) {
() => { success(data);
if (success) { } else if (data == null && error) {
success(); const serverError = getState().requests.users.updateUser.error;
} error({id: serverError.server_error_id, ...serverError});
},
(err) => {
if (error) {
error(err);
} }
} }
); );
} }
export function samlCertificateStatus(success, error) { export function samlCertificateStatus(success, error) {
Client.samlCertificateStatus( AdminActions.getSamlCertificateStatus()(dispatch, getState).then(
(data) => { (data) => {
if (success) { if (data && success) {
success(data); success(data);
} } else if (data == null && error) {
}, const serverError = getState().requests.admin.getSamlCertificateStatus.error;
(err) => { error({id: serverError.server_error_id, ...serverError});
if (error) {
error(err);
} }
} }
); );
} }
export function ldapSyncNow(success, error) { export function ldapSyncNow(success, error) {
Client.ldapSyncNow( AdminActions.syncLdap()(dispatch, getState).then(
() => { (data) => {
if (success) { if (data && success) {
success(); success(data);
} } else if (data == null && error) {
}, const serverError = getState().requests.admin.syncLdap.error;
(err) => { error({id: serverError.server_error_id, ...serverError});
if (error) {
error(err);
} }
} }
); );
@@ -316,16 +269,13 @@ export function regenerateOAuthAppSecret(oauthAppId, success, error) {
} }
export function uploadBrandImage(brandImage, success, error) { export function uploadBrandImage(brandImage, success, error) {
Client.uploadBrandImage( AdminActions.uploadBrandImage(brandImage)(dispatch, getState).then(
brandImage, (data) => {
() => { if (data && success) {
if (success) { success(data);
success(); } else if (data == null && error) {
} const serverError = getState().requests.admin.uploadBrandImage.error;
}, error({id: serverError.server_error_id, ...serverError});
(err) => {
if (error) {
error(err);
} }
} }
); );
@@ -362,33 +312,79 @@ export function removeLicenseFile(success, error) {
); );
} }
export function uploadCertificateFile(certificateFile, success, error) { export function uploadPublicSamlCertificate(file, success, error) {
Client.uploadCertificateFile( AdminActions.uploadPublicSamlCertificate(file)(dispatch, getState).then(
certificateFile, (data) => {
() => { if (data && success) {
if (success) { success(data);
success(); } else if (data == null && error) {
} const serverError = getState().requests.admin.uploadPublicSamlCertificate.error;
}, error({id: serverError.server_error_id, ...serverError});
(err) => {
if (error) {
error(err);
} }
} }
); );
} }
export function removeCertificateFile(certificateId, success, error) { export function uploadPrivateSamlCertificate(file, success, error) {
Client.removeCertificateFile( AdminActions.uploadPrivateSamlCertificate(file)(dispatch, getState).then(
certificateId, (data) => {
() => { if (data && success) {
if (success) { success(data);
success(); } else if (data == null && error) {
} const serverError = getState().requests.admin.uploadPrivateSamlCertificate.error;
}, error({id: serverError.server_error_id, ...serverError});
(err) => { }
if (error) { }
error(err); );
}
export function uploadIdpSamlCertificate(file, success, error) {
AdminActions.uploadIdpSamlCertificate(file)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.admin.uploadIdpSamlCertificate.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function removePublicSamlCertificate(success, error) {
AdminActions.removePublicSamlCertificate()(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.admin.removePublicSamlCertificate.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function removePrivateSamlCertificate(success, error) {
AdminActions.removePrivateSamlCertificate()(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.admin.removePrivateSamlCertificate.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function removeIdpSamlCertificate(success, error) {
AdminActions.removeIdpSamlCertificate()(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.admin.removeIdpSamlCertificate.error;
error({id: serverError.server_error_id, ...serverError});
} }
} }
); );

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

@@ -6,46 +6,37 @@ import PropTypes from 'prop-types';
import 'bootstrap'; import 'bootstrap';
import AnnouncementBar from 'components/announcement_bar'; import AnnouncementBar from 'components/announcement_bar';
import AdminStore from 'stores/admin_store.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import AdminSidebar from './admin_sidebar.jsx'; import AdminSidebar from './admin_sidebar.jsx';
export default class AdminConsole extends React.Component { export default class AdminConsole extends React.Component {
static get propTypes() { static propTypes = {
return {
children: PropTypes.node.isRequired
};
}
constructor(props) { /*
super(props); * Children components to render
*/
children: PropTypes.node.isRequired,
this.handleConfigChange = this.handleConfigChange.bind(this); /*
* Object representing the config file
*/
config: PropTypes.object.isRequired,
this.state = { actions: PropTypes.shape({
config: AdminStore.getConfig()
}; /*
* Function to get the config file
*/
getConfig: PropTypes.func.isRequired
}).isRequired
} }
componentWillMount() { componentWillMount() {
AdminStore.addConfigChangeListener(this.handleConfigChange); this.props.actions.getConfig();
AsyncClient.getConfig();
}
componentWillUnmount() {
AdminStore.removeConfigChangeListener(this.handleConfigChange);
}
handleConfigChange() {
this.setState({
config: AdminStore.getConfig()
});
} }
render() { render() {
const config = this.state.config; const config = this.props.config;
if (!config) { if (Object.keys(config).length === 0) {
return <div/>; return <div/>;
} }
if (config && Object.keys(config).length === 0 && config.constructor === 'Object') { if (config && Object.keys(config).length === 0 && config.constructor === 'Object') {
@@ -59,7 +50,7 @@ export default class AdminConsole extends React.Component {
// not every page in the system console will need the config, but the vast majority will // not every page in the system console will need the config, but the vast majority will
const children = React.cloneElement(this.props.children, { const children = React.cloneElement(this.props.children, {
config: this.state.config config
}); });
return ( return (
<div className='admin-console__wrapper'> <div className='admin-console__wrapper'>

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

@@ -1,11 +1,8 @@
import PropTypes from 'prop-types';
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import * as AsyncClient from 'utils/async_client.jsx';
import FormError from 'components/form_error.jsx'; import FormError from 'components/form_error.jsx';
import SaveButton from 'components/admin_console/save_button.jsx'; import SaveButton from 'components/admin_console/save_button.jsx';
@@ -13,10 +10,12 @@ import SaveButton from 'components/admin_console/save_button.jsx';
import {saveConfig} from 'actions/admin_actions.jsx'; import {saveConfig} from 'actions/admin_actions.jsx';
export default class AdminSettings extends React.Component { export default class AdminSettings extends React.Component {
static get propTypes() { static propTypes = {
return {
config: PropTypes.object /*
}; * Object representing the config file
*/
config: PropTypes.object
} }
constructor(props) { constructor(props) {
@@ -58,10 +57,8 @@ export default class AdminSettings extends React.Component {
saveConfig( saveConfig(
config, config,
() => { (savedConfig) => {
AsyncClient.getConfig((savedConfig) => { this.setState(this.getStateFromConfig(savedConfig));
this.setState(this.getStateFromConfig(savedConfig));
});
this.setState({ this.setState({
saveNeeded: false, saveNeeded: false,

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

@@ -4,7 +4,7 @@
import $ from 'jquery'; import $ from 'jquery';
import AdminNavbarDropdown from './admin_navbar_dropdown.jsx'; import AdminNavbarDropdown from './admin_navbar_dropdown.jsx';
import UserStore from 'stores/user_store.jsx'; import UserStore from 'stores/user_store.jsx';
import Client from 'client/web_client.jsx'; import {Client4} from 'mattermost-redux/client';
import {FormattedMessage} from 'react-intl'; import {FormattedMessage} from 'react-intl';
@@ -14,12 +14,10 @@ export default class SidebarHeader extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.toggleDropdown = this.toggleDropdown.bind(this);
this.state = {}; this.state = {};
} }
toggleDropdown(e) { toggleDropdown = (e) => {
e.preventDefault(); e.preventDefault();
if (this.refs.dropdown.blockToggle) { if (this.refs.dropdown.blockToggle) {
@@ -42,7 +40,7 @@ export default class SidebarHeader extends React.Component {
profilePicture = ( profilePicture = (
<img <img
className='user__picture' className='user__picture'
src={Client.getUsersRoute() + '/' + me.id + '/image?time=' + me.last_picture_update} src={Client4.getProfilePictureUrl(me.id, me.last_picture_update)}
/> />
); );
} }

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

@@ -1,68 +1,66 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
import LoadingScreen from '../loading_screen.jsx'; import LoadingScreen from 'components/loading_screen.jsx';
import AuditTable from '../audit_table.jsx'; import AuditTable from 'components/audit_table.jsx';
import ComplianceReports from './compliance_reports.jsx'; import ComplianceReports from 'components/admin_console/compliance_reports';
import AdminStore from 'stores/admin_store.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import {FormattedMessage} from 'react-intl';
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import {FormattedMessage} from 'react-intl';
export default class Audits extends React.PureComponent {
static propTypes = {
/*
* Array of audits to render
*/
audits: PropTypes.arrayOf(PropTypes.object).isRequired,
actions: PropTypes.shape({
/*
* Function to fetch audits
*/
getAudits: PropTypes.func.isRequired
}).isRequired
}
export default class Audits extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.onAuditListenerChange = this.onAuditListenerChange.bind(this);
this.reload = this.reload.bind(this);
this.state = { this.state = {
audits: AdminStore.getAudits() loadingAudits: true
}; };
} }
componentDidMount() { componentDidMount() {
AdminStore.addAuditChangeListener(this.onAuditListenerChange); this.props.actions.getAudits().then(
AsyncClient.getServerAudits(); () => this.setState({loadingAudits: false})
);
} }
componentWillUnmount() { reload = () => {
AdminStore.removeAuditChangeListener(this.onAuditListenerChange); this.setState({loadingAudits: true});
} this.props.actions.getAudits().then(
() => this.setState({loadingAudits: false})
onAuditListenerChange() { );
this.setState({
audits: AdminStore.getAudits()
});
}
reload() {
AdminStore.saveAudits(null);
this.setState({
audits: null
});
AsyncClient.getServerAudits();
} }
render() { render() {
var content = null; let content = null;
if (global.window.mm_license.IsLicensed !== 'true') { if (global.window.mm_license.IsLicensed !== 'true') {
return <div/>; return <div/>;
} }
if (this.state.audits === null) { if (this.state.loadingAudits) {
content = <LoadingScreen/>; content = <LoadingScreen/>;
} else { } else {
content = ( content = (
<div style={{margin: '10px'}}> <div style={{margin: '10px'}}>
<AuditTable <AuditTable
audits={this.state.audits} audits={this.props.audits}
showUserId={true} showUserId={true}
showIp={true} showIp={true}
showSession={true} showSession={true}

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

@@ -0,0 +1,27 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {getAudits} from 'mattermost-redux/actions/admin';
import * as Selectors from 'mattermost-redux/selectors/entities/admin';
import Audits from './audits.jsx';
function mapStateToProps(state, ownProps) {
return {
...ownProps,
audits: Object.values(Selectors.getAudits(state))
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
getAudits
}, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Audits);

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

@@ -4,20 +4,23 @@
import $ from 'jquery'; import $ from 'jquery';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom';
import Client from 'client/web_client.jsx'; import {Client4} from 'mattermost-redux/client';
import * as Utils from 'utils/utils.jsx'; import * as Utils from 'utils/utils.jsx';
import {uploadBrandImage} from 'actions/admin_actions.jsx'; import {uploadBrandImage} from 'actions/admin_actions.jsx';
import FormError from 'components/form_error.jsx'; import FormError from 'components/form_error.jsx';
import {FormattedHTMLMessage, FormattedMessage} from 'react-intl'; import {FormattedHTMLMessage, FormattedMessage} from 'react-intl';
export default class BrandImageSetting extends React.Component { const HTTP_STATUS_OK = 200;
static get propTypes() {
return { export default class BrandImageSetting extends React.PureComponent {
disabled: PropTypes.bool.isRequired static propTypes = {
};
/*
* Set to disable the setting
*/
disabled: PropTypes.bool.isRequired
} }
constructor(props) { constructor(props) {
@@ -37,9 +40,15 @@ export default class BrandImageSetting extends React.Component {
} }
componentWillMount() { componentWillMount() {
$.get(Client.getAdminRoute() + '/get_brand_image?t=' + this.state.brandImageTimestamp).done(() => { fetch(Client4.getBrandImageUrl(this.state.brandImageTimestamp)).then(
this.setState({brandImageExists: true}); (resp) => {
}); if (resp.status === HTTP_STATUS_OK) {
this.setState({brandImageExists: true});
} else {
this.setState({brandImageExists: false});
}
}
);
} }
componentDidUpdate() { componentDidUpdate() {
@@ -76,7 +85,7 @@ export default class BrandImageSetting extends React.Component {
return; return;
} }
$(ReactDOM.findDOMNode(this.refs.upload)).button('loading'); $(this.refs.upload).button('loading');
this.setState({ this.setState({
uploading: true, uploading: true,
@@ -86,7 +95,7 @@ export default class BrandImageSetting extends React.Component {
uploadBrandImage( uploadBrandImage(
this.state.brandImage, this.state.brandImage,
() => { () => {
$(ReactDOM.findDOMNode(this.refs.upload)).button('complete'); $(this.refs.upload).button('complete');
this.setState({ this.setState({
brandImageExists: true, brandImageExists: true,
@@ -96,7 +105,7 @@ export default class BrandImageSetting extends React.Component {
}); });
}, },
(err) => { (err) => {
$(ReactDOM.findDOMNode(this.refs.upload)).button('reset'); $(this.refs.upload).button('reset');
this.setState({ this.setState({
uploading: false, uploading: false,
@@ -130,7 +139,7 @@ export default class BrandImageSetting extends React.Component {
img = ( img = (
<img <img
className='brand-img' className='brand-img'
src={Client.getAdminRoute() + '/get_brand_image?t=' + this.state.brandImageTimestamp} src={Client4.getBrandImageUrl(this.state.brandImageTimestamp)}
/> />
); );
} else { } else {
@@ -180,6 +189,7 @@ export default class BrandImageSetting extends React.Component {
disabled={this.props.disabled || !this.state.brandImage} disabled={this.props.disabled || !this.state.brandImage}
onClick={this.handleImageSubmit} onClick={this.handleImageSubmit}
id='upload-button' id='upload-button'
ref='upload'
data-loading-text={'<span class=\'fa fa-refresh fa-rotate\'></span> ' + Utils.localizeMessage('admin.team.uploading', 'Uploading..')} data-loading-text={'<span class=\'fa fa-refresh fa-rotate\'></span> ' + Utils.localizeMessage('admin.team.uploading', 'Uploading..')}
data-complete-text={'<span class=\'fa fa-check\'></span> ' + Utils.localizeMessage('admin.team.uploaded', 'Uploaded!')} data-complete-text={'<span class=\'fa fa-check\'></span> ' + Utils.localizeMessage('admin.team.uploaded', 'Uploaded!')}
> >

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

@@ -11,9 +11,10 @@ import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx'; import SettingsGroup from './settings_group.jsx';
import ClusterTableContainer from './cluster_table_container.jsx'; import ClusterTableContainer from './cluster_table_container.jsx';
import AdminStore from 'stores/admin_store.jsx';
import * as Utils from 'utils/utils.jsx'; import * as Utils from 'utils/utils.jsx';
import {Client4} from 'mattermost-redux/client';
export default class ClusterSettings extends AdminSettings { export default class ClusterSettings extends AdminSettings {
constructor(props) { constructor(props) {
super(props); super(props);
@@ -74,7 +75,7 @@ export default class ClusterSettings extends AdminSettings {
var configLoadedFromCluster = null; var configLoadedFromCluster = null;
if (AdminStore.getClusterId()) { if (Client4.clusterId) {
configLoadedFromCluster = ( configLoadedFromCluster = (
<div <div
style={{marginBottom: '10px'}} style={{marginBottom: '10px'}}
@@ -85,7 +86,7 @@ export default class ClusterSettings extends AdminSettings {
id='admin.cluster.loadedFrom' id='admin.cluster.loadedFrom'
defaultMessage='This configuration file was loaded from Node ID {clusterId}. Please see the Troubleshooting Guide in our <a href="http://docs.mattermost.com/deployment/cluster.html" target="_blank">documentation</a> if you are accessing the System Console through a load balancer and experiencing issues.' defaultMessage='This configuration file was loaded from Node ID {clusterId}. Please see the Troubleshooting Guide in our <a href="http://docs.mattermost.com/deployment/cluster.html" target="_blank">documentation</a> if you are accessing the System Console through a load balancer and experiencing issues.'
values={{ values={{
clusterId: AdminStore.getClusterId() clusterId: Client4.clusterId
}} }}
/> />
</div> </div>

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

@@ -1,92 +1,96 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
import $ from 'jquery'; import LoadingScreen from 'components/loading_screen.jsx';
import LoadingScreen from '../loading_screen.jsx';
import * as Utils from '../../utils/utils.jsx';
import AdminStore from '../../stores/admin_store.jsx';
import UserStore from '../../stores/user_store.jsx';
import Client from 'client/web_client.jsx'; import * as Utils from 'utils/utils.jsx';
import * as AsyncClient from '../../utils/async_client.jsx'; import UserStore from 'stores/user_store.jsx';
import {saveComplianceReports} from 'actions/admin_actions.jsx'; import {Client4} from 'mattermost-redux/client';
import {FormattedMessage, FormattedDate, FormattedTime} from 'react-intl';
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import PropTypes from 'prop-types';
import {FormattedMessage, FormattedDate, FormattedTime} from 'react-intl';
export default class ComplianceReports extends React.PureComponent {
static propTypes = {
/*
* Set if compliance reports are enabled in the config
*/
enabled: PropTypes.bool.isRequired,
/*
* Array of reports to render
*/
reports: PropTypes.arrayOf(PropTypes.object).isRequired,
/*
* Error message to display
*/
serverError: PropTypes.string,
actions: PropTypes.shape({
/*
* Function to get compliance reports
*/
getComplianceReports: PropTypes.func.isRequired,
/*
* Function to save compliance reports
*/
createComplianceReport: PropTypes.func.isRequired
}).isRequired
}
export default class ComplianceReports extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.onComplianceReportsListenerChange = this.onComplianceReportsListenerChange.bind(this);
this.reload = this.reload.bind(this);
this.runReport = this.runReport.bind(this);
this.getDateTime = this.getDateTime.bind(this);
this.state = { this.state = {
enabled: AdminStore.getConfig().ComplianceSettings.Enable, loadingReports: true
reports: AdminStore.getComplianceReports(),
serverError: null
}; };
} }
componentDidMount() { componentDidMount() {
AdminStore.addComplianceReportsChangeListener(this.onComplianceReportsListenerChange); if (global.window.mm_license.IsLicensed !== 'true' || !this.props.enabled) {
if (global.window.mm_license.IsLicensed !== 'true' || !this.state.enabled) {
return; return;
} }
AsyncClient.getComplianceReports(); this.props.actions.getComplianceReports().then(
() => this.setState({loadingReports: false})
);
} }
componentWillUnmount() { reload = () => {
AdminStore.removeComplianceReportsChangeListener(this.onComplianceReportsListenerChange); this.setState({loadingReports: true});
this.props.actions.getComplianceReports().then(
() => this.setState({loadingReports: false})
);
} }
onComplianceReportsListenerChange() { runReport = (e) => {
this.setState({
reports: AdminStore.getComplianceReports()
});
}
reload() {
AdminStore.saveComplianceReports(null);
this.setState({
reports: null,
serverError: null
});
AsyncClient.getComplianceReports();
}
runReport(e) {
e.preventDefault(); e.preventDefault();
$('#run-button').button('loading');
var job = {}; this.setState({runningReport: true});
job.desc = ReactDOM.findDOMNode(this.refs.desc).value;
job.emails = ReactDOM.findDOMNode(this.refs.emails).value;
job.keywords = ReactDOM.findDOMNode(this.refs.keywords).value;
job.start_at = Date.parse(ReactDOM.findDOMNode(this.refs.from).value);
job.end_at = Date.parse(ReactDOM.findDOMNode(this.refs.to).value);
saveComplianceReports( const job = {};
job, job.desc = this.refs.desc.value;
() => { job.emails = this.refs.emails.value;
ReactDOM.findDOMNode(this.refs.emails).value = ''; job.keywords = this.refs.keywords.value;
ReactDOM.findDOMNode(this.refs.keywords).value = ''; job.start_at = Date.parse(this.refs.from.value);
ReactDOM.findDOMNode(this.refs.desc).value = ''; job.end_at = Date.parse(this.refs.to.value);
ReactDOM.findDOMNode(this.refs.from).value = '';
ReactDOM.findDOMNode(this.refs.to).value = ''; this.props.actions.createComplianceReport(job).then(
this.reload(); (data) => {
$('#run-button').button('reset'); if (data) {
}, this.refs.emails.value = '';
(err) => { this.refs.keywords.value = '';
this.setState({serverError: err.message}); this.refs.desc.value = '';
$('#run-button').button('reset'); this.refs.from.value = '';
this.refs.to.value = '';
}
this.setState({runningReport: false});
} }
); );
} }
@@ -112,21 +116,20 @@ export default class ComplianceReports extends React.Component {
} }
render() { render() {
var content = null; if (global.window.mm_license.IsLicensed !== 'true' || !this.props.enabled) {
if (global.window.mm_license.IsLicensed !== 'true' || !this.state.enabled) {
return <div/>; return <div/>;
} }
if (this.state.reports === null) { let content = null;
if (this.state.loadingReports) {
content = <LoadingScreen/>; content = <LoadingScreen/>;
} else { } else {
var list = []; var list = [];
for (var i = 0; i < this.state.reports.length; i++) { for (var i = 0; i < this.props.reports.length; i++) {
const report = this.state.reports[i]; const report = this.props.reports[i];
var params = ''; let params = '';
if (report.type === 'adhoc') { if (report.type === 'adhoc') {
params = ( params = (
<span> <span>
@@ -152,10 +155,10 @@ export default class ComplianceReports extends React.Component {
</span>); </span>);
} }
var download = ''; let download = '';
if (report.status === 'finished') { if (report.status === 'finished') {
download = ( download = (
<a href={Client.getAdminRoute() + '/download_compliance_report/' + report.id}> <a href={`${Client4.getBaseRoute()}/compliance/reports/${report.id}/download`}>
<FormattedMessage <FormattedMessage
id='admin.compliance_table.download' id='admin.compliance_table.download'
defaultMessage='Download' defaultMessage='Download'
@@ -164,7 +167,7 @@ export default class ComplianceReports extends React.Component {
); );
} }
var status = report.status; let status = report.status;
if (report.status === 'finished') { if (report.status === 'finished') {
status = ( status = (
<span style={{color: 'green'}}>{report.status}</span> <span style={{color: 'green'}}>{report.status}</span>
@@ -177,8 +180,8 @@ export default class ComplianceReports extends React.Component {
); );
} }
var user = report.user_id; let user = report.user_id;
var profile = UserStore.getProfile(report.user_id); const profile = UserStore.getProfile(report.user_id);
if (profile) { if (profile) {
user = profile.email; user = profile.email;
} }
@@ -256,13 +259,13 @@ export default class ComplianceReports extends React.Component {
} }
let serverError = ''; let serverError = '';
if (this.state.serverError) { if (this.props.serverError) {
serverError = ( serverError = (
<div <div
className='form-group has-error' className='form-group has-error'
style={{marginTop: '10px'}} style={{marginTop: '10px'}}
> >
<label className='control-label'>{this.state.serverError}</label> <label className='control-label'>{this.props.serverError}</label>
</div> </div>
); );
} }
@@ -372,6 +375,7 @@ export default class ComplianceReports extends React.Component {
<button <button
type='submit' type='submit'
className='btn btn-link' className='btn btn-link'
disabled={this.state.runningReport}
onClick={this.reload} onClick={this.reload}
> >
<i className='fa fa-refresh'/> <i className='fa fa-refresh'/>

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

@@ -0,0 +1,42 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {getComplianceReports, createComplianceReport} from 'mattermost-redux/actions/admin';
import {getComplianceReports as selectComplianceReports, getConfig} from 'mattermost-redux/selectors/entities/admin';
import ComplianceReports from './compliance_reports.jsx';
function mapStateToProps(state, ownProps) {
let enabled = false;
const config = getConfig(state);
if (config && config.ComplianceSettings) {
enabled = config.ComplianceSettings.Enable;
}
let serverError;
const error = state.requests.admin.createCompliance.error;
if (error) {
serverError = error.message;
}
return {
...ownProps,
enabled,
reports: Object.values(selectComplianceReports(state)),
serverError
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
getComplianceReports,
createComplianceReport
}, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(ComplianceReports);

27
webapp/components/admin_console/index.js Обычный файл
Просмотреть файл

@@ -0,0 +1,27 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {getConfig} from 'mattermost-redux/actions/admin';
import * as Selectors from 'mattermost-redux/selectors/entities/admin';
import AdminConsole from './admin_console.jsx';
function mapStateToProps(state, ownProps) {
return {
...ownProps,
config: Selectors.getConfig(state)
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
getConfig
}, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(AdminConsole);

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

@@ -14,7 +14,7 @@ import SettingsGroup from './settings_group.jsx';
import * as Utils from 'utils/utils.jsx'; import * as Utils from 'utils/utils.jsx';
import {samlCertificateStatus, uploadCertificateFile, removeCertificateFile} from 'actions/admin_actions.jsx'; import * as AdminActions from 'actions/admin_actions.jsx';
export default class SamlSettings extends AdminSettings { export default class SamlSettings extends AdminSettings {
constructor(props) { constructor(props) {
@@ -74,7 +74,7 @@ export default class SamlSettings extends AdminSettings {
} }
componentWillMount() { componentWillMount() {
samlCertificateStatus( AdminActions.samlCertificateStatus(
(data) => { (data) => {
const files = {}; const files = {};
if (!data.IdpCertificateFile) { if (!data.IdpCertificateFile) {
@@ -94,38 +94,50 @@ export default class SamlSettings extends AdminSettings {
} }
uploadCertificate(id, file, callback) { uploadCertificate(id, file, callback) {
uploadCertificateFile( const complete = () => {
file, const fileName = file.name;
() => { this.handleChange(id, fileName);
const fileName = file.name; this.setState({[id]: fileName, [`${id}Error`]: null});
this.handleChange(id, fileName); if (callback && typeof callback === 'function') {
this.setState({[id]: fileName, [`${id}Error`]: null}); callback();
if (callback && typeof callback === 'function') {
callback();
}
},
(error) => {
if (callback && typeof callback === 'function') {
callback(error.message);
}
} }
); };
function fail(error) {
if (callback && typeof callback === 'function') {
callback(error.message);
}
}
if (id === 'idpCertificateFile') {
AdminActions.uploadIdpSamlCertificate(file, complete, fail);
} else if (id === 'publicCertificateFile') {
AdminActions.uploadPublicSamlCertificate(file, complete, fail);
} else if (id === 'privateKeyFile') {
AdminActions.uploadPrivateSamlCertificate(file, complete, fail);
}
} }
removeCertificate(id, callback) { removeCertificate(id, callback) {
removeCertificateFile( const complete = () => {
this.state[id], this.handleChange(id, '');
() => { this.setState({[id]: null, [`${id}Error`]: null});
this.handleChange(id, ''); };
this.setState({[id]: null, [`${id}Error`]: null});
}, const fail = (error) => {
(error) => { if (callback && typeof callback === 'function') {
if (callback && typeof callback === 'function') { callback();
callback();
}
this.setState({[id]: null, [`${id}Error`]: error.message});
} }
); this.setState({[id]: null, [`${id}Error`]: error.message});
};
if (id === 'idpCertificateFile') {
AdminActions.removeIdpSamlCertificate(complete, fail);
} else if (id === 'publicCertificateFile') {
AdminActions.removePublicSamlCertificate(complete, fail);
} else if (id === 'privateKeyFile') {
AdminActions.removePrivateSamlCertificate(complete, fail);
}
} }
renderTitle() { renderTitle() {

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

@@ -0,0 +1,27 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {getLogs} from 'mattermost-redux/actions/admin';
import * as Selectors from 'mattermost-redux/selectors/entities/admin';
import Logs from './logs.jsx';
function mapStateToProps(state, ownProps) {
return {
...ownProps,
logs: Selectors.getLogs(state)
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
getLogs
}, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Logs);

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

@@ -1,30 +1,43 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
import AdminStore from 'stores/admin_store.jsx'; import LoadingScreen from 'components/loading_screen.jsx';
import LoadingScreen from '../loading_screen.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import {FormattedMessage} from 'react-intl';
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import {FormattedMessage} from 'react-intl';
export default class Logs extends React.PureComponent {
static propTypes = {
/*
* Array of logs to render
*/
logs: PropTypes.arrayOf(PropTypes.string).isRequired,
actions: PropTypes.shape({
/*
* Function to fetch logs
*/
getLogs: PropTypes.func.isRequired
}).isRequired
}
export default class Logs extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.onLogListenerChange = this.onLogListenerChange.bind(this);
this.reload = this.reload.bind(this);
this.state = { this.state = {
logs: AdminStore.getLogs() loadingLogs: true
}; };
} }
componentDidMount() { componentDidMount() {
AdminStore.addLogChangeListener(this.onLogListenerChange);
AsyncClient.getLogs();
this.refs.logPanel.focus(); this.refs.logPanel.focus();
this.props.actions.getLogs().then(
() => this.setState({loadingLogs: false})
);
} }
componentDidUpdate() { componentDidUpdate() {
@@ -34,40 +47,28 @@ export default class Logs extends React.Component {
node.focus(); node.focus();
} }
componentWillUnmount() { reload = () => {
AdminStore.removeLogChangeListener(this.onLogListenerChange); this.setState({loadingLogs: true});
} this.props.actions.getLogs().then(
() => this.setState({loadingLogs: false})
onLogListenerChange() { );
this.setState({
logs: AdminStore.getLogs()
});
}
reload() {
AdminStore.saveLogs(null);
this.setState({
logs: null
});
AsyncClient.getLogs();
} }
render() { render() {
var content = null; let content = null;
if (this.state.logs === null) { if (this.state.loadingLogs) {
content = <LoadingScreen/>; content = <LoadingScreen/>;
} else { } else {
content = []; content = [];
for (var i = 0; i < this.state.logs.length; i++) { for (let i = 0; i < this.props.logs.length; i++) {
var style = { const style = {
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
fontFamily: 'monospace' fontFamily: 'monospace'
}; };
if (this.state.logs[i].indexOf('[EROR]') > 0) { if (this.props.logs[i].indexOf('[EROR]') > 0) {
style.color = 'red'; style.color = 'red';
} }
@@ -77,7 +78,7 @@ export default class Logs extends React.Component {
key={'log_' + i} key={'log_' + i}
style={style} style={style}
> >
{this.state.logs[i]} {this.props.logs[i]}
</span> </span>
); );
} }

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

@@ -6,10 +6,13 @@ import {bindActionCreators} from 'redux';
import {getTeams, getTeamStats} from 'mattermost-redux/actions/teams'; import {getTeams, getTeamStats} from 'mattermost-redux/actions/teams';
import {getUser} from 'mattermost-redux/actions/users'; import {getUser} from 'mattermost-redux/actions/users';
import {getTeamsList} from 'mattermost-redux/selectors/entities/teams';
import SystemUsers from './system_users.jsx'; import SystemUsers from './system_users.jsx';
function mapStateToProps(state, ownProps) { function mapStateToProps(state, ownProps) {
return { return {
teams: getTeamsList(state),
...ownProps ...ownProps
}; };
} }

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

@@ -1,9 +1,8 @@
import PropTypes from 'prop-types';
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import {FormattedMessage} from 'react-intl'; import {FormattedMessage} from 'react-intl';
import { import {
@@ -13,14 +12,12 @@ import {
searchUsers searchUsers
} from 'actions/user_actions.jsx'; } from 'actions/user_actions.jsx';
import AdminStore from 'stores/admin_store.jsx';
import AnalyticsStore from 'stores/analytics_store.jsx'; import AnalyticsStore from 'stores/analytics_store.jsx';
import TeamStore from 'stores/team_store.jsx'; import TeamStore from 'stores/team_store.jsx';
import UserStore from 'stores/user_store.jsx'; import UserStore from 'stores/user_store.jsx';
import {getStandardAnalytics} from 'utils/async_client.jsx'; import {getStandardAnalytics} from 'utils/async_client.jsx';
import {Constants, StatTypes, UserSearchOptions} from 'utils/constants.jsx'; import {Constants, StatTypes, UserSearchOptions} from 'utils/constants.jsx';
import {convertTeamMapToList} from 'utils/team_utils.jsx';
import * as Utils from 'utils/utils.jsx'; import * as Utils from 'utils/utils.jsx';
import SystemUsersList from './system_users_list.jsx'; import SystemUsersList from './system_users_list.jsx';
@@ -36,9 +33,27 @@ const USERS_PER_PAGE = 50;
export default class SystemUsers extends React.Component { export default class SystemUsers extends React.Component {
static propTypes = { static propTypes = {
/*
* Array of team objects
*/
teams: PropTypes.arrayOf(PropTypes.object).isRequired,
actions: PropTypes.shape({ actions: PropTypes.shape({
/*
* Function to get teams
*/
getTeams: PropTypes.func.isRequired, getTeams: PropTypes.func.isRequired,
/*
* Function to get statistics for a team
*/
getTeamStats: PropTypes.func.isRequired, getTeamStats: PropTypes.func.isRequired,
/*
* Function to get a user
*/
getUser: PropTypes.func.isRequired getUser: PropTypes.func.isRequired
}).isRequired }).isRequired
} }
@@ -46,7 +61,6 @@ export default class SystemUsers extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.updateTeamsFromStore = this.updateTeamsFromStore.bind(this);
this.updateTotalUsersFromStore = this.updateTotalUsersFromStore.bind(this); this.updateTotalUsersFromStore = this.updateTotalUsersFromStore.bind(this);
this.updateUsersFromStore = this.updateUsersFromStore.bind(this); this.updateUsersFromStore = this.updateUsersFromStore.bind(this);
@@ -64,7 +78,6 @@ export default class SystemUsers extends React.Component {
this.renderFilterRow = this.renderFilterRow.bind(this); this.renderFilterRow = this.renderFilterRow.bind(this);
this.state = { this.state = {
teams: convertTeamMapToList(AdminStore.getAllTeams()),
totalUsers: AnalyticsStore.getAllSystem()[StatTypes.TOTAL_USERS], totalUsers: AnalyticsStore.getAllSystem()[StatTypes.TOTAL_USERS],
users: UserStore.getProfileList(), users: UserStore.getProfileList(),
@@ -76,8 +89,6 @@ export default class SystemUsers extends React.Component {
} }
componentDidMount() { componentDidMount() {
AdminStore.addAllTeamsChangeListener(this.updateTeamsFromStore);
AnalyticsStore.addChangeListener(this.updateTotalUsersFromStore); AnalyticsStore.addChangeListener(this.updateTotalUsersFromStore);
TeamStore.addStatsChangeListener(this.updateTotalUsersFromStore); TeamStore.addStatsChangeListener(this.updateTotalUsersFromStore);
@@ -101,8 +112,6 @@ export default class SystemUsers extends React.Component {
} }
componentWillUnmount() { componentWillUnmount() {
AdminStore.removeAllTeamsChangeListener(this.updateTeamsFromStore);
AnalyticsStore.removeChangeListener(this.updateTotalUsersFromStore); AnalyticsStore.removeChangeListener(this.updateTotalUsersFromStore);
TeamStore.removeStatsChangeListener(this.updateTotalUsersFromStore); TeamStore.removeStatsChangeListener(this.updateTotalUsersFromStore);
@@ -111,10 +120,6 @@ export default class SystemUsers extends React.Component {
UserStore.removeWithoutTeamChangeListener(this.updateUsersFromStore); UserStore.removeWithoutTeamChangeListener(this.updateUsersFromStore);
} }
updateTeamsFromStore() {
this.setState({teams: convertTeamMapToList(AdminStore.getAllTeams())});
}
updateTotalUsersFromStore(teamId = this.state.teamId) { updateTotalUsersFromStore(teamId = this.state.teamId) {
if (teamId === ALL_USERS) { if (teamId === ALL_USERS) {
this.setState({ this.setState({
@@ -271,7 +276,7 @@ export default class SystemUsers extends React.Component {
} }
renderFilterRow(doSearch) { renderFilterRow(doSearch) {
const teams = this.state.teams.map((team) => { const teams = this.props.teams.map((team) => {
return ( return (
<option <option
key={team.id} key={team.id}
@@ -339,7 +344,7 @@ export default class SystemUsers extends React.Component {
users={users} users={users}
usersPerPage={USERS_PER_PAGE} usersPerPage={USERS_PER_PAGE}
total={this.state.totalUsers} total={this.state.totalUsers}
teams={this.state.teams} teams={this.props.teams}
teamId={this.state.teamId} teamId={this.state.teamId}
term={this.state.term} term={this.state.term}
onTermChange={this.handleTermChange} onTermChange={this.handleTermChange}

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

@@ -5,10 +5,20 @@ import {connect} from 'react-redux';
import {bindActionCreators} from 'redux'; import {bindActionCreators} from 'redux';
import {getTeams} from 'mattermost-redux/actions/teams'; import {getTeams} from 'mattermost-redux/actions/teams';
import {getTeamsList} from 'mattermost-redux/selectors/entities/teams';
import BrowserStore from 'stores/browser_store.jsx';
import TeamAnalytics from './team_analytics.jsx'; import TeamAnalytics from './team_analytics.jsx';
const LAST_ANALYTICS_TEAM = 'last_analytics_team';
function mapStateToProps(state, ownProps) { function mapStateToProps(state, ownProps) {
const teams = getTeamsList(state);
const teamId = BrowserStore.getGlobalItem(LAST_ANALYTICS_TEAM, teams.length > 0 ? teams[0].id : '');
return { return {
initialTeam: state.entities.teams.teams[teamId],
teams,
...ownProps ...ownProps
}; };
} }

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

@@ -1,21 +1,18 @@
import PropTypes from 'prop-types';
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import {FormattedDate, FormattedMessage, FormattedHTMLMessage} from 'react-intl'; import {FormattedDate, FormattedMessage, FormattedHTMLMessage} from 'react-intl';
import Banner from 'components/admin_console/banner.jsx'; import Banner from 'components/admin_console/banner.jsx';
import LoadingScreen from 'components/loading_screen.jsx'; import LoadingScreen from 'components/loading_screen.jsx';
import AdminStore from 'stores/admin_store.jsx';
import AnalyticsStore from 'stores/analytics_store.jsx'; import AnalyticsStore from 'stores/analytics_store.jsx';
import BrowserStore from 'stores/browser_store.jsx'; import BrowserStore from 'stores/browser_store.jsx';
import * as AsyncClient from 'utils/async_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx';
import {StatTypes} from 'utils/constants.jsx'; import {StatTypes} from 'utils/constants.jsx';
import {convertTeamMapToList} from 'utils/team_utils.jsx';
import LineChart from 'components/analytics/line_chart.jsx'; import LineChart from 'components/analytics/line_chart.jsx';
import StatisticCount from 'components/analytics/statistic_count.jsx'; import StatisticCount from 'components/analytics/statistic_count.jsx';
@@ -26,7 +23,22 @@ const LAST_ANALYTICS_TEAM = 'last_analytics_team';
export default class TeamAnalytics extends React.Component { export default class TeamAnalytics extends React.Component {
static propTypes = { static propTypes = {
/*
* Array of team objects
*/
teams: PropTypes.arrayOf(PropTypes.object).isRequired,
/*
* Initial team to load analytics for
*/
initialTeam: PropTypes.object,
actions: PropTypes.shape({ actions: PropTypes.shape({
/*
* Function to get teams
*/
getTeams: PropTypes.func.isRequired getTeams: PropTypes.func.isRequired
}).isRequired }).isRequired
} }
@@ -34,37 +46,27 @@ export default class TeamAnalytics extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.onChange = this.onChange.bind(this); const teamId = props.initialTeam ? props.initialTeam.id : '';
this.onAllTeamsChange = this.onAllTeamsChange.bind(this);
this.handleTeamChange = this.handleTeamChange.bind(this);
const teams = convertTeamMapToList(AdminStore.getAllTeams());
const teamId = BrowserStore.getGlobalItem(LAST_ANALYTICS_TEAM, teams.length > 0 ? teams[0].id : '');
this.state = { this.state = {
teams, team: props.initialTeam,
teamId,
team: AdminStore.getTeam(teamId),
stats: AnalyticsStore.getAllTeam(teamId) stats: AnalyticsStore.getAllTeam(teamId)
}; };
} }
componentDidMount() { componentDidMount() {
AnalyticsStore.addChangeListener(this.onChange); AnalyticsStore.addChangeListener(this.onChange);
AdminStore.addAllTeamsChangeListener(this.onAllTeamsChange);
if (this.state.teamId !== '') { if (this.state.team) {
this.getData(this.state.teamId); this.getData(this.state.team.id);
} }
if (this.state.teams.length === 0) { this.props.actions.getTeams(0, 1000);
this.props.actions.getTeams(0, 1000);
}
} }
componentWillUpdate(nextProps, nextState) { componentWillUpdate(nextProps, nextState) {
if (nextState.teamId !== this.state.teamId) { if (nextState.team && nextState.team !== this.state.team) {
this.getData(nextState.teamId); this.getData(nextState.team.id);
} }
} }
@@ -77,53 +79,38 @@ export default class TeamAnalytics extends React.Component {
componentWillUnmount() { componentWillUnmount() {
AnalyticsStore.removeChangeListener(this.onChange); AnalyticsStore.removeChangeListener(this.onChange);
AdminStore.removeAllTeamsChangeListener(this.onAllTeamsChange);
} }
onChange() { onChange = () => {
const teamId = this.state.team ? this.state.team.id : '';
this.setState({ this.setState({
stats: AnalyticsStore.getAllTeam(this.state.teamId) stats: AnalyticsStore.getAllTeam(teamId)
}); });
} }
onAllTeamsChange() { handleTeamChange = (e) => {
const teams = convertTeamMapToList(AdminStore.getAllTeams());
if (teams.length > 0) {
if (this.state.teamId) {
this.setState({
team: AdminStore.getTeam(this.state.teamId)
});
} else {
this.setState({
teamId: teams[0].id,
team: teams[0]
});
}
}
this.setState({
teams
});
}
handleTeamChange(e) {
const teamId = e.target.value; const teamId = e.target.value;
let team;
this.props.teams.forEach((t) => {
if (t.id === teamId) {
team = t;
}
});
this.setState({ this.setState({
teamId, team
team: AdminStore.getTeam(teamId)
}); });
BrowserStore.setGlobalItem(LAST_ANALYTICS_TEAM, teamId); BrowserStore.setGlobalItem(LAST_ANALYTICS_TEAM, teamId);
} }
render() { render() {
if (this.state.teams.length === 0 || !this.state.team || !this.state.stats) { if (this.props.teams.length === 0 || !this.state.team || !this.state.stats) {
return <LoadingScreen/>; return <LoadingScreen/>;
} }
if (this.state.teamId === '') { if (this.state.team == null) {
return ( return (
<Banner <Banner
description={ description={
@@ -217,7 +204,7 @@ export default class TeamAnalytics extends React.Component {
const recentActiveUsers = formatRecentUsersData(stats[StatTypes.RECENTLY_ACTIVE_USERS]); const recentActiveUsers = formatRecentUsersData(stats[StatTypes.RECENTLY_ACTIVE_USERS]);
const newlyCreatedUsers = formatNewUsersData(stats[StatTypes.NEWLY_CREATED_USERS]); const newlyCreatedUsers = formatNewUsersData(stats[StatTypes.NEWLY_CREATED_USERS]);
const teams = this.state.teams.map((team) => { const teams = this.props.teams.map((team) => {
return ( return (
<option <option
key={team.id} key={team.id}
@@ -246,7 +233,7 @@ export default class TeamAnalytics extends React.Component {
<select <select
className='form-control team-statistics__team-filter__dropdown' className='form-control team-statistics__team-filter__dropdown'
onChange={this.handleTeamChange} onChange={this.handleTeamChange}
value={this.state.teamId} value={this.state.team.id}
> >
{teams} {teams}
</select> </select>

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

@@ -8,7 +8,7 @@ import PreferenceStore from 'stores/preference_store.jsx';
import Constants from 'utils/constants.jsx'; import Constants from 'utils/constants.jsx';
import * as Utils from 'utils/utils.jsx'; import * as Utils from 'utils/utils.jsx';
import Client from 'client/web_client.jsx'; import {Client4} from 'mattermost-redux/client';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
@@ -73,7 +73,7 @@ export default function UserListRow({user, extraInfo, actions, actionProps, acti
className='more-modal__row' className='more-modal__row'
> >
<ProfilePicture <ProfilePicture
src={`${Client.getUsersRoute()}/${user.id}/image?time=${user.last_picture_update}`} src={Client4.getProfilePictureUrl(user.id, user.last_picture_update)}
status={status} status={status}
width='32' width='32'
height='32' height='32'

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

@@ -22,7 +22,7 @@
"localforage": "1.5.0", "localforage": "1.5.0",
"marked": "mattermost/marked#8f5902fff9bad793cd6c66e0c44002c9e79e1317", "marked": "mattermost/marked#8f5902fff9bad793cd6c66e0c44002c9e79e1317",
"match-at": "0.1.0", "match-at": "0.1.0",
"mattermost-redux": "mattermost/mattermost-redux#webapp-3.10", "mattermost-redux": "mattermost/mattermost-redux#webapp-master",
"object-assign": "4.1.1", "object-assign": "4.1.1",
"pdfjs-dist": "1.8.361", "pdfjs-dist": "1.8.361",
"perfect-scrollbar": "0.7.0", "perfect-scrollbar": "0.7.0",

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

@@ -44,8 +44,8 @@ import DeveloperSettings from 'components/admin_console/developer_settings.jsx';
import SystemUsers from 'components/admin_console/system_users'; import SystemUsers from 'components/admin_console/system_users';
import TeamAnalytics from 'components/analytics/team_analytics'; import TeamAnalytics from 'components/analytics/team_analytics';
import LicenseSettings from 'components/admin_console/license_settings.jsx'; import LicenseSettings from 'components/admin_console/license_settings.jsx';
import Audits from 'components/admin_console/audits.jsx'; import Audits from 'components/admin_console/audits';
import Logs from 'components/admin_console/logs.jsx'; import Logs from 'components/admin_console/server_logs';
export default ( export default (
<Route> <Route>

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

@@ -133,7 +133,7 @@ export default {
{ {
path: 'admin_console', path: 'admin_console',
getComponents: (location, callback) => { getComponents: (location, callback) => {
System.import('components/admin_console/admin_console.jsx').then(RouteUtils.importComponentSuccess(callback)); System.import('components/admin_console').then(RouteUtils.importComponentSuccess(callback));
}, },
indexRoute: {onEnter: (nextState, replace) => replace('/admin_console/system_analytics')}, indexRoute: {onEnter: (nextState, replace) => replace('/admin_console/system_analytics')},
getChildRoutes: (location, callback) => { getChildRoutes: (location, callback) => {

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

@@ -1,177 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
import EventEmitter from 'events';
import Constants from 'utils/constants.jsx';
const ActionTypes = Constants.ActionTypes;
const LOG_CHANGE_EVENT = 'log_change';
const SERVER_AUDIT_CHANGE_EVENT = 'server_audit_change';
const CONFIG_CHANGE_EVENT = 'config_change';
const ALL_TEAMS_EVENT = 'all_team_change';
const SERVER_COMPLIANCE_REPORT_CHANGE_EVENT = 'server_compliance_reports_change';
import store from 'stores/redux_store.jsx';
class AdminStoreClass extends EventEmitter {
constructor() {
super();
this.logs = null;
this.audits = null;
this.config = null;
this.clusterId = null;
this.complianceReports = null;
this.entities = store.getState().entities.teams;
store.subscribe(() => {
const newEntities = store.getState().entities.teams;
if (newEntities.teams !== this.entities.teams) {
this.emitAllTeamsChange();
}
this.entities = newEntities;
});
}
emitLogChange() {
this.emit(LOG_CHANGE_EVENT);
}
addLogChangeListener(callback) {
this.on(LOG_CHANGE_EVENT, callback);
}
removeLogChangeListener(callback) {
this.removeListener(LOG_CHANGE_EVENT, callback);
}
emitAuditChange() {
this.emit(SERVER_AUDIT_CHANGE_EVENT);
}
addAuditChangeListener(callback) {
this.on(SERVER_AUDIT_CHANGE_EVENT, callback);
}
removeAuditChangeListener(callback) {
this.removeListener(SERVER_AUDIT_CHANGE_EVENT, callback);
}
emitComplianceReportsChange() {
this.emit(SERVER_COMPLIANCE_REPORT_CHANGE_EVENT);
}
addComplianceReportsChangeListener(callback) {
this.on(SERVER_COMPLIANCE_REPORT_CHANGE_EVENT, callback);
}
removeComplianceReportsChangeListener(callback) {
this.removeListener(SERVER_COMPLIANCE_REPORT_CHANGE_EVENT, callback);
}
emitConfigChange() {
this.emit(CONFIG_CHANGE_EVENT);
}
addConfigChangeListener(callback) {
this.on(CONFIG_CHANGE_EVENT, callback);
}
removeConfigChangeListener(callback) {
this.removeListener(CONFIG_CHANGE_EVENT, callback);
}
emitAllTeamsChange() {
this.emit(ALL_TEAMS_EVENT);
}
addAllTeamsChangeListener(callback) {
this.on(ALL_TEAMS_EVENT, callback);
}
removeAllTeamsChangeListener(callback) {
this.removeListener(ALL_TEAMS_EVENT, callback);
}
getClusterId() {
return this.clusterId;
}
saveClusterId(clusterId) {
this.clusterId = clusterId;
}
getLogs() {
return this.logs;
}
saveLogs(logs) {
this.logs = logs;
}
getAudits() {
return this.audits;
}
saveAudits(audits) {
this.audits = audits;
}
getComplianceReports() {
return this.complianceReports;
}
saveComplianceReports(complianceReports) {
this.complianceReports = complianceReports;
}
getConfig() {
return this.config;
}
saveConfig(config) {
this.config = config;
}
getAllTeams() {
return store.getState().entities.teams.teams;
}
getTeam(id) {
return this.getAllTeams()[id];
}
}
var AdminStore = new AdminStoreClass();
AdminStoreClass.dispatchToken = AppDispatcher.register((payload) => {
var action = payload.action;
switch (action.type) {
case ActionTypes.RECEIVED_LOGS:
AdminStore.saveLogs(action.logs);
AdminStore.emitLogChange();
break;
case ActionTypes.RECEIVED_SERVER_AUDITS:
AdminStore.saveAudits(action.audits);
AdminStore.emitAuditChange();
break;
case ActionTypes.RECEIVED_SERVER_COMPLIANCE_REPORTS:
AdminStore.saveComplianceReports(action.complianceReports);
AdminStore.emitComplianceReportsChange();
break;
case ActionTypes.RECEIVED_CONFIG:
AdminStore.saveConfig(action.config);
AdminStore.saveClusterId(action.clusterId);
AdminStore.emitConfigChange();
break;
default:
}
});
export default AdminStore;

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

@@ -4882,9 +4882,9 @@ math-expression-evaluator@^1.2.14:
version "1.2.16" version "1.2.16"
resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.16.tgz#b357fa1ca9faefb8e48d10c14ef2bcb2d9f0a7c9" resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.16.tgz#b357fa1ca9faefb8e48d10c14ef2bcb2d9f0a7c9"
mattermost-redux@mattermost/mattermost-redux#webapp-3.10: mattermost-redux@mattermost/mattermost-redux#webapp-master:
version "0.0.1" version "0.0.1"
resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/30af9bdf41aeac4ca5a37817773bd8a9b8372a4a" resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/dc9c164de3153a4c33922a8a3f996490e8949e02"
dependencies: dependencies:
deep-equal "1.0.1" deep-equal "1.0.1"
harmony-reflect "1.5.1" harmony-reflect "1.5.1"