From 1138dd67705829a6af0d6c610cf3dbe09082187c Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Wed, 14 Jun 2017 08:56:56 -0400 Subject: 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 --- api4/brand.go | 1 + api4/brand_test.go | 24 +- api4/compliance.go | 2 +- api4/system.go | 6 +- api4/user.go | 2 +- webapp/actions/admin_actions.jsx | 302 ++++++++-------- webapp/components/admin_console/admin_console.jsx | 49 ++- webapp/components/admin_console/admin_settings.jsx | 21 +- .../admin_console/admin_sidebar_header.jsx | 8 +- webapp/components/admin_console/audits.jsx | 103 ------ webapp/components/admin_console/audits/audits.jsx | 101 ++++++ webapp/components/admin_console/audits/index.js | 27 ++ .../admin_console/brand_image_setting.jsx | 38 +- .../components/admin_console/cluster_settings.jsx | 7 +- .../admin_console/compliance_reports.jsx | 390 -------------------- .../compliance_reports/compliance_reports.jsx | 394 +++++++++++++++++++++ .../admin_console/compliance_reports/index.js | 42 +++ webapp/components/admin_console/index.js | 27 ++ webapp/components/admin_console/logs.jsx | 122 ------- webapp/components/admin_console/saml_settings.jsx | 70 ++-- .../components/admin_console/server_logs/index.js | 27 ++ .../components/admin_console/server_logs/logs.jsx | 123 +++++++ .../components/admin_console/system_users/index.js | 3 + .../admin_console/system_users/system_users.jsx | 37 +- .../components/analytics/team_analytics/index.js | 10 + .../analytics/team_analytics/team_analytics.jsx | 89 ++--- webapp/components/user_list_row.jsx | 4 +- webapp/package.json | 2 +- webapp/routes/route_admin_console.jsx | 4 +- webapp/routes/route_root.jsx | 2 +- webapp/stores/admin_store.jsx | 177 --------- webapp/yarn.lock | 4 +- 32 files changed, 1085 insertions(+), 1133 deletions(-) delete mode 100644 webapp/components/admin_console/audits.jsx create mode 100644 webapp/components/admin_console/audits/audits.jsx create mode 100644 webapp/components/admin_console/audits/index.js delete mode 100644 webapp/components/admin_console/compliance_reports.jsx create mode 100644 webapp/components/admin_console/compliance_reports/compliance_reports.jsx create mode 100644 webapp/components/admin_console/compliance_reports/index.js create mode 100644 webapp/components/admin_console/index.js delete mode 100644 webapp/components/admin_console/logs.jsx create mode 100644 webapp/components/admin_console/server_logs/index.js create mode 100644 webapp/components/admin_console/server_logs/logs.jsx delete mode 100644 webapp/stores/admin_store.jsx diff --git a/api4/brand.go b/api4/brand.go index ac69f623b..de81b8a3b 100644 --- a/api4/brand.go +++ b/api4/brand.go @@ -23,6 +23,7 @@ func getBrandImage(c *Context, w http.ResponseWriter, r *http.Request) { // No permission check required if img, err := app.GetBrandImage(); err != nil { + w.WriteHeader(http.StatusNotFound) w.Write(nil) } else { w.Header().Set("Content-Type", "image/png") diff --git a/api4/brand_test.go b/api4/brand_test.go index 98a539574..6ecb41a83 100644 --- a/api4/brand_test.go +++ b/api4/brand_test.go @@ -13,27 +13,15 @@ func TestGetBrandImage(t *testing.T) { defer TearDown() Client := th.Client - data, resp := Client.GetBrandImage() - CheckNoError(t, resp) - - if len(data) != 0 { - t.Fatal("no image uploaded - should be empty") - } + _, resp := Client.GetBrandImage() + CheckNotFoundStatus(t, resp) Client.Logout() - data, resp = Client.GetBrandImage() - CheckNoError(t, resp) - - if len(data) != 0 { - t.Fatal("no image uploaded - should be empty") - } - - data, resp = th.SystemAdminClient.GetBrandImage() - CheckNoError(t, resp) + _, resp = Client.GetBrandImage() + CheckNotFoundStatus(t, resp) - if len(data) != 0 { - t.Fatal("no image uploaded - should be empty") - } + _, resp = th.SystemAdminClient.GetBrandImage() + CheckNotFoundStatus(t, resp) } func TestUploadBrandImage(t *testing.T) { diff --git a/api4/compliance.go b/api4/compliance.go index cabac6e21..733abeecf 100644 --- a/api4/compliance.go +++ b/api4/compliance.go @@ -20,7 +20,7 @@ func InitCompliance() { BaseRoutes.Compliance.Handle("/reports", ApiSessionRequired(createComplianceReport)).Methods("POST") 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]+}/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) { diff --git a/api4/system.go b/api4/system.go index 3a077283c..465f4e71d 100644 --- a/api4/system.go +++ b/api4/system.go @@ -52,13 +52,17 @@ func getSystemPing(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) { c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) return } - err := app.TestEmail(c.Session.UserId, utils.Cfg) + err := app.TestEmail(c.Session.UserId, cfg) if err != nil { c.Err = err return diff --git a/api4/user.go b/api4/user.go index d06dd2882..69bea945a 100644 --- a/api4/user.go +++ b/api4/user.go @@ -27,7 +27,7 @@ func InitUser() { BaseRoutes.Users.Handle("/autocomplete", ApiSessionRequired(autocompleteUsers)).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("", ApiSessionRequired(updateUser)).Methods("PUT") BaseRoutes.User.Handle("/patch", ApiSessionRequired(patchUser)).Methods("PUT") diff --git a/webapp/actions/admin_actions.jsx b/webapp/actions/admin_actions.jsx index 9a522caf9..04d0d65bb 100644 --- a/webapp/actions/admin_actions.jsx +++ b/webapp/actions/admin_actions.jsx @@ -2,201 +2,154 @@ // See License.txt for license information. import Client from 'client/web_client.jsx'; -import * as AsyncClient from 'utils/async_client.jsx'; import {browserHistory} from 'react-router/es6'; -// Redux actions import store from 'stores/redux_store.jsx'; const dispatch = store.dispatch; 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) { - Client.saveConfig( - config, - () => { - if (success) { - success(); - } - }, - (err) => { - if (error) { - error(err); + AdminActions.updateConfig(config)(dispatch, getState).then( + (data) => { + if (data && success) { + success(data); + } else if (data == null && error) { + const serverError = getState().requests.admin.updateConfig.error; + error({id: serverError.server_error_id, ...serverError}); } } ); } export function reloadConfig(success, error) { - Client.reloadConfig( - () => { - AsyncClient.getConfig(); - if (success) { - success(); - } - }, - (err) => { - if (error) { - error(err); + AdminActions.reloadConfig()(dispatch, getState).then( + (data) => { + if (data && success) { + AdminActions.getConfig()(dispatch, getState); + success(data); + } else if (data == null && error) { + const serverError = getState().requests.admin.reloadConfig.error; + error({id: serverError.server_error_id, ...serverError}); } } ); } export function adminResetMfa(userId, success, error) { - Client.adminResetMfa( - userId, - () => { - getUser(userId)(dispatch, getState); - - if (success) { - success(); - } - }, - (err) => { - if (error) { - error(err); + updateUserMfa(userId, false)(dispatch, getState).then( + (data) => { + if (data && success) { + success(data); + } else if (data == null && error) { + const serverError = getState().requests.users.updateUser.error; + error({id: serverError.server_error_id, ...serverError}); } } ); } export function getClusterStatus(success, error) { - Client.getClusterStatus( + AdminActions.getClusterStatus()(dispatch, getState).then( (data) => { - if (success) { + if (data && success) { success(data); - } - }, - (err) => { - 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); + } else if (data == null && error) { + const serverError = getState().requests.admin.getClusterStatus.error; + error({id: serverError.server_error_id, ...serverError}); } } ); } export function testEmail(config, success, error) { - Client.testEmail( - config, - () => { - if (success) { - success(); - } - }, - (err) => { - if (error) { - error(err); + AdminActions.testEmail(config)(dispatch, getState).then( + (data) => { + if (data && success) { + success(data); + } else if (data == null && error) { + const serverError = getState().requests.admin.testEmail.error; + error({id: serverError.server_error_id, ...serverError}); } } ); } export function ldapTest(success, error) { - Client.ldapTest( - () => { - if (success) { - success(); - } - }, - (err) => { - if (error) { - error(err); + AdminActions.testLdap()(dispatch, getState).then( + (data) => { + if (data && success) { + success(data); + } else if (data == null && error) { + const serverError = getState().requests.admin.testLdap.error; + error({id: serverError.server_error_id, ...serverError}); } } ); } export function invalidateAllCaches(success, error) { - Client.invalidateAllCaches( - () => { - if (success) { - success(); - } - }, - (err) => { - if (error) { - error(err); + AdminActions.invalidateCaches()(dispatch, getState).then( + (data) => { + if (data && success) { + success(data); + } else if (data == null && error) { + const serverError = getState().requests.admin.invalidateCaches.error; + error({id: serverError.server_error_id, ...serverError}); } } ); } export function recycleDatabaseConnection(success, error) { - Client.recycleDatabaseConnection( - () => { - if (success) { - success(); - } - }, - (err) => { - if (error) { - error(err); + AdminActions.recycleDatabase()(dispatch, getState).then( + (data) => { + if (data && success) { + success(data); + } else if (data == null && error) { + const serverError = getState().requests.admin.recycleDatabase.error; + error({id: serverError.server_error_id, ...serverError}); } } ); } export function adminResetPassword(userId, password, success, error) { - Client.adminResetPassword( - userId, - password, - () => { - if (success) { - success(); - } - }, - (err) => { - if (error) { - error(err); + updateUserPassword(userId, '', password)(dispatch, getState).then( + (data) => { + if (data && success) { + success(data); + } else if (data == null && error) { + const serverError = getState().requests.users.updateUser.error; + error({id: serverError.server_error_id, ...serverError}); } } ); } export function samlCertificateStatus(success, error) { - Client.samlCertificateStatus( + AdminActions.getSamlCertificateStatus()(dispatch, getState).then( (data) => { - if (success) { + if (data && success) { success(data); - } - }, - (err) => { - if (error) { - error(err); + } else if (data == null && error) { + const serverError = getState().requests.admin.getSamlCertificateStatus.error; + error({id: serverError.server_error_id, ...serverError}); } } ); } export function ldapSyncNow(success, error) { - Client.ldapSyncNow( - () => { - if (success) { - success(); - } - }, - (err) => { - if (error) { - error(err); + AdminActions.syncLdap()(dispatch, getState).then( + (data) => { + if (data && success) { + success(data); + } else if (data == null && error) { + const serverError = getState().requests.admin.syncLdap.error; + error({id: serverError.server_error_id, ...serverError}); } } ); @@ -316,16 +269,13 @@ export function regenerateOAuthAppSecret(oauthAppId, success, error) { } export function uploadBrandImage(brandImage, success, error) { - Client.uploadBrandImage( - brandImage, - () => { - if (success) { - success(); - } - }, - (err) => { - if (error) { - error(err); + AdminActions.uploadBrandImage(brandImage)(dispatch, getState).then( + (data) => { + if (data && success) { + success(data); + } else if (data == null && error) { + const serverError = getState().requests.admin.uploadBrandImage.error; + error({id: serverError.server_error_id, ...serverError}); } } ); @@ -362,33 +312,79 @@ export function removeLicenseFile(success, error) { ); } -export function uploadCertificateFile(certificateFile, success, error) { - Client.uploadCertificateFile( - certificateFile, - () => { - if (success) { - success(); +export function uploadPublicSamlCertificate(file, success, error) { + AdminActions.uploadPublicSamlCertificate(file)(dispatch, getState).then( + (data) => { + if (data && success) { + success(data); + } 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 uploadPrivateSamlCertificate(file, success, error) { + AdminActions.uploadPrivateSamlCertificate(file)(dispatch, getState).then( + (data) => { + if (data && success) { + success(data); + } else if (data == null && error) { + const serverError = getState().requests.admin.uploadPrivateSamlCertificate.error; + error({id: serverError.server_error_id, ...serverError}); } } ); } -export function removeCertificateFile(certificateId, success, error) { - Client.removeCertificateFile( - certificateId, - () => { - if (success) { - success(); +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}); } - }, - (err) => { - if (error) { - error(err); + } + ); +} + +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}); } } ); diff --git a/webapp/components/admin_console/admin_console.jsx b/webapp/components/admin_console/admin_console.jsx index b8250bab2..99256d7d4 100644 --- a/webapp/components/admin_console/admin_console.jsx +++ b/webapp/components/admin_console/admin_console.jsx @@ -6,46 +6,37 @@ import PropTypes from 'prop-types'; import 'bootstrap'; 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'; export default class AdminConsole extends React.Component { - static get propTypes() { - return { - children: PropTypes.node.isRequired - }; - } + static propTypes = { - constructor(props) { - super(props); + /* + * Children components to render + */ + children: PropTypes.node.isRequired, - this.handleConfigChange = this.handleConfigChange.bind(this); - - this.state = { - config: AdminStore.getConfig() - }; - } + /* + * Object representing the config file + */ + config: PropTypes.object.isRequired, - componentWillMount() { - AdminStore.addConfigChangeListener(this.handleConfigChange); - AsyncClient.getConfig(); - } + actions: PropTypes.shape({ - componentWillUnmount() { - AdminStore.removeConfigChangeListener(this.handleConfigChange); + /* + * Function to get the config file + */ + getConfig: PropTypes.func.isRequired + }).isRequired } - handleConfigChange() { - this.setState({ - config: AdminStore.getConfig() - }); + componentWillMount() { + this.props.actions.getConfig(); } render() { - const config = this.state.config; - if (!config) { + const config = this.props.config; + if (Object.keys(config).length === 0) { return
; } 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 const children = React.cloneElement(this.props.children, { - config: this.state.config + config }); return (
diff --git a/webapp/components/admin_console/admin_settings.jsx b/webapp/components/admin_console/admin_settings.jsx index 180e6e5b9..2411fbdb8 100644 --- a/webapp/components/admin_console/admin_settings.jsx +++ b/webapp/components/admin_console/admin_settings.jsx @@ -1,11 +1,8 @@ -import PropTypes from 'prop-types'; - // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. import React from 'react'; - -import * as AsyncClient from 'utils/async_client.jsx'; +import PropTypes from 'prop-types'; import FormError from 'components/form_error.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'; export default class AdminSettings extends React.Component { - static get propTypes() { - return { - config: PropTypes.object - }; + static propTypes = { + + /* + * Object representing the config file + */ + config: PropTypes.object } constructor(props) { @@ -58,10 +57,8 @@ export default class AdminSettings extends React.Component { saveConfig( config, - () => { - AsyncClient.getConfig((savedConfig) => { - this.setState(this.getStateFromConfig(savedConfig)); - }); + (savedConfig) => { + this.setState(this.getStateFromConfig(savedConfig)); this.setState({ saveNeeded: false, diff --git a/webapp/components/admin_console/admin_sidebar_header.jsx b/webapp/components/admin_console/admin_sidebar_header.jsx index 87a1170dc..301186917 100644 --- a/webapp/components/admin_console/admin_sidebar_header.jsx +++ b/webapp/components/admin_console/admin_sidebar_header.jsx @@ -4,7 +4,7 @@ import $ from 'jquery'; import AdminNavbarDropdown from './admin_navbar_dropdown.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'; @@ -14,12 +14,10 @@ export default class SidebarHeader extends React.Component { constructor(props) { super(props); - this.toggleDropdown = this.toggleDropdown.bind(this); - this.state = {}; } - toggleDropdown(e) { + toggleDropdown = (e) => { e.preventDefault(); if (this.refs.dropdown.blockToggle) { @@ -42,7 +40,7 @@ export default class SidebarHeader extends React.Component { profilePicture = ( ; - } - - if (this.state.audits === null) { - content = ; - } else { - content = ( -
- -
- ); - } - - return ( -
- - -
-

- - -

-
- {content} -
-
-
- ); - } -} diff --git a/webapp/components/admin_console/audits/audits.jsx b/webapp/components/admin_console/audits/audits.jsx new file mode 100644 index 000000000..0811c216f --- /dev/null +++ b/webapp/components/admin_console/audits/audits.jsx @@ -0,0 +1,101 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import LoadingScreen from 'components/loading_screen.jsx'; +import AuditTable from 'components/audit_table.jsx'; +import ComplianceReports from 'components/admin_console/compliance_reports'; + +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 + } + + constructor(props) { + super(props); + + this.state = { + loadingAudits: true + }; + } + + componentDidMount() { + this.props.actions.getAudits().then( + () => this.setState({loadingAudits: false}) + ); + } + + reload = () => { + this.setState({loadingAudits: true}); + this.props.actions.getAudits().then( + () => this.setState({loadingAudits: false}) + ); + } + + render() { + let content = null; + + if (global.window.mm_license.IsLicensed !== 'true') { + return
; + } + + if (this.state.loadingAudits) { + content = ; + } else { + content = ( +
+ +
+ ); + } + + return ( +
+ + +
+

+ + +

+
+ {content} +
+
+
+ ); + } +} diff --git a/webapp/components/admin_console/audits/index.js b/webapp/components/admin_console/audits/index.js new file mode 100644 index 000000000..a48e33538 --- /dev/null +++ b/webapp/components/admin_console/audits/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 {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); diff --git a/webapp/components/admin_console/brand_image_setting.jsx b/webapp/components/admin_console/brand_image_setting.jsx index eae5ad922..d2eae3f6e 100644 --- a/webapp/components/admin_console/brand_image_setting.jsx +++ b/webapp/components/admin_console/brand_image_setting.jsx @@ -4,20 +4,23 @@ import $ from 'jquery'; import PropTypes from 'prop-types'; 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 {uploadBrandImage} from 'actions/admin_actions.jsx'; import FormError from 'components/form_error.jsx'; import {FormattedHTMLMessage, FormattedMessage} from 'react-intl'; -export default class BrandImageSetting extends React.Component { - static get propTypes() { - return { - disabled: PropTypes.bool.isRequired - }; +const HTTP_STATUS_OK = 200; + +export default class BrandImageSetting extends React.PureComponent { + static propTypes = { + + /* + * Set to disable the setting + */ + disabled: PropTypes.bool.isRequired } constructor(props) { @@ -37,9 +40,15 @@ export default class BrandImageSetting extends React.Component { } componentWillMount() { - $.get(Client.getAdminRoute() + '/get_brand_image?t=' + this.state.brandImageTimestamp).done(() => { - this.setState({brandImageExists: true}); - }); + fetch(Client4.getBrandImageUrl(this.state.brandImageTimestamp)).then( + (resp) => { + if (resp.status === HTTP_STATUS_OK) { + this.setState({brandImageExists: true}); + } else { + this.setState({brandImageExists: false}); + } + } + ); } componentDidUpdate() { @@ -76,7 +85,7 @@ export default class BrandImageSetting extends React.Component { return; } - $(ReactDOM.findDOMNode(this.refs.upload)).button('loading'); + $(this.refs.upload).button('loading'); this.setState({ uploading: true, @@ -86,7 +95,7 @@ export default class BrandImageSetting extends React.Component { uploadBrandImage( this.state.brandImage, () => { - $(ReactDOM.findDOMNode(this.refs.upload)).button('complete'); + $(this.refs.upload).button('complete'); this.setState({ brandImageExists: true, @@ -96,7 +105,7 @@ export default class BrandImageSetting extends React.Component { }); }, (err) => { - $(ReactDOM.findDOMNode(this.refs.upload)).button('reset'); + $(this.refs.upload).button('reset'); this.setState({ uploading: false, @@ -130,7 +139,7 @@ export default class BrandImageSetting extends React.Component { img = ( ' + Utils.localizeMessage('admin.team.uploading', 'Uploading..')} data-complete-text={' ' + Utils.localizeMessage('admin.team.uploaded', 'Uploaded!')} > diff --git a/webapp/components/admin_console/cluster_settings.jsx b/webapp/components/admin_console/cluster_settings.jsx index 895a87ce1..14bc46240 100644 --- a/webapp/components/admin_console/cluster_settings.jsx +++ b/webapp/components/admin_console/cluster_settings.jsx @@ -11,9 +11,10 @@ import {FormattedMessage, FormattedHTMLMessage} from 'react-intl'; import SettingsGroup from './settings_group.jsx'; import ClusterTableContainer from './cluster_table_container.jsx'; -import AdminStore from 'stores/admin_store.jsx'; import * as Utils from 'utils/utils.jsx'; +import {Client4} from 'mattermost-redux/client'; + export default class ClusterSettings extends AdminSettings { constructor(props) { super(props); @@ -74,7 +75,7 @@ export default class ClusterSettings extends AdminSettings { var configLoadedFromCluster = null; - if (AdminStore.getClusterId()) { + if (Client4.clusterId) { configLoadedFromCluster = (
diff --git a/webapp/components/admin_console/compliance_reports.jsx b/webapp/components/admin_console/compliance_reports.jsx deleted file mode 100644 index 567a6ca04..000000000 --- a/webapp/components/admin_console/compliance_reports.jsx +++ /dev/null @@ -1,390 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import $ from 'jquery'; -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 AsyncClient from '../../utils/async_client.jsx'; -import {saveComplianceReports} from 'actions/admin_actions.jsx'; - -import {FormattedMessage, FormattedDate, FormattedTime} from 'react-intl'; - -import React from 'react'; -import ReactDOM from 'react-dom'; - -export default class ComplianceReports extends React.Component { - constructor(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 = { - enabled: AdminStore.getConfig().ComplianceSettings.Enable, - reports: AdminStore.getComplianceReports(), - serverError: null - }; - } - - componentDidMount() { - AdminStore.addComplianceReportsChangeListener(this.onComplianceReportsListenerChange); - - if (global.window.mm_license.IsLicensed !== 'true' || !this.state.enabled) { - return; - } - - AsyncClient.getComplianceReports(); - } - - componentWillUnmount() { - AdminStore.removeComplianceReportsChangeListener(this.onComplianceReportsListenerChange); - } - - onComplianceReportsListenerChange() { - this.setState({ - reports: AdminStore.getComplianceReports() - }); - } - - reload() { - AdminStore.saveComplianceReports(null); - this.setState({ - reports: null, - serverError: null - }); - - AsyncClient.getComplianceReports(); - } - - runReport(e) { - e.preventDefault(); - $('#run-button').button('loading'); - - var job = {}; - 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( - job, - () => { - ReactDOM.findDOMNode(this.refs.emails).value = ''; - ReactDOM.findDOMNode(this.refs.keywords).value = ''; - ReactDOM.findDOMNode(this.refs.desc).value = ''; - ReactDOM.findDOMNode(this.refs.from).value = ''; - ReactDOM.findDOMNode(this.refs.to).value = ''; - this.reload(); - $('#run-button').button('reset'); - }, - (err) => { - this.setState({serverError: err.message}); - $('#run-button').button('reset'); - } - ); - } - - getDateTime(millis) { - const date = new Date(millis); - return ( - - - {' - '} - - - ); - } - - render() { - var content = null; - - if (global.window.mm_license.IsLicensed !== 'true' || !this.state.enabled) { - return
; - } - - if (this.state.reports === null) { - content = ; - } else { - var list = []; - - for (var i = 0; i < this.state.reports.length; i++) { - const report = this.state.reports[i]; - - var params = ''; - if (report.type === 'adhoc') { - params = ( - - {' '}{this.getDateTime(report.start_at)} -
- {' '}{this.getDateTime(report.end_at)} -
- {' '}{report.emails} -
- {' '}{report.keywords} -
); - } - - var download = ''; - if (report.status === 'finished') { - download = ( - - - - ); - } - - var status = report.status; - if (report.status === 'finished') { - status = ( - {report.status} - ); - } - - if (report.status === 'failed') { - status = ( - {report.status} - ); - } - - var user = report.user_id; - var profile = UserStore.getProfile(report.user_id); - if (profile) { - user = profile.email; - } - - list[i] = ( - - {download} - {this.getDateTime(report.create_at)} - {status} - {report.count} - {report.type} - {report.desc} - {user} - {params} - - ); - } - - content = ( -
- - - - - - - - - - - - - - {list} - -
- - - - - - - - - - - - - - -
-
- ); - } - - let serverError = ''; - if (this.state.serverError) { - serverError = ( -
- -
- ); - } - - return ( -
-

- -

-
-
- - -
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
- -
- {serverError} -
- -
-
- {content} -
-
- ); - } -} diff --git a/webapp/components/admin_console/compliance_reports/compliance_reports.jsx b/webapp/components/admin_console/compliance_reports/compliance_reports.jsx new file mode 100644 index 000000000..af361bace --- /dev/null +++ b/webapp/components/admin_console/compliance_reports/compliance_reports.jsx @@ -0,0 +1,394 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import LoadingScreen from 'components/loading_screen.jsx'; + +import * as Utils from 'utils/utils.jsx'; +import UserStore from 'stores/user_store.jsx'; +import {Client4} from 'mattermost-redux/client'; + +import React from 'react'; +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 + } + + constructor(props) { + super(props); + + this.state = { + loadingReports: true + }; + } + + componentDidMount() { + if (global.window.mm_license.IsLicensed !== 'true' || !this.props.enabled) { + return; + } + + this.props.actions.getComplianceReports().then( + () => this.setState({loadingReports: false}) + ); + } + + reload = () => { + this.setState({loadingReports: true}); + + this.props.actions.getComplianceReports().then( + () => this.setState({loadingReports: false}) + ); + } + + runReport = (e) => { + e.preventDefault(); + + this.setState({runningReport: true}); + + const job = {}; + job.desc = this.refs.desc.value; + job.emails = this.refs.emails.value; + job.keywords = this.refs.keywords.value; + job.start_at = Date.parse(this.refs.from.value); + job.end_at = Date.parse(this.refs.to.value); + + this.props.actions.createComplianceReport(job).then( + (data) => { + if (data) { + this.refs.emails.value = ''; + this.refs.keywords.value = ''; + this.refs.desc.value = ''; + this.refs.from.value = ''; + this.refs.to.value = ''; + } + this.setState({runningReport: false}); + } + ); + } + + getDateTime(millis) { + const date = new Date(millis); + return ( + + + {' - '} + + + ); + } + + render() { + if (global.window.mm_license.IsLicensed !== 'true' || !this.props.enabled) { + return
; + } + + let content = null; + if (this.state.loadingReports) { + content = ; + } else { + var list = []; + + for (var i = 0; i < this.props.reports.length; i++) { + const report = this.props.reports[i]; + + let params = ''; + if (report.type === 'adhoc') { + params = ( + + {' '}{this.getDateTime(report.start_at)} +
+ {' '}{this.getDateTime(report.end_at)} +
+ {' '}{report.emails} +
+ {' '}{report.keywords} +
); + } + + let download = ''; + if (report.status === 'finished') { + download = ( + + + + ); + } + + let status = report.status; + if (report.status === 'finished') { + status = ( + {report.status} + ); + } + + if (report.status === 'failed') { + status = ( + {report.status} + ); + } + + let user = report.user_id; + const profile = UserStore.getProfile(report.user_id); + if (profile) { + user = profile.email; + } + + list[i] = ( + + {download} + {this.getDateTime(report.create_at)} + {status} + {report.count} + {report.type} + {report.desc} + {user} + {params} + + ); + } + + content = ( +
+ + + + + + + + + + + + + + {list} + +
+ + + + + + + + + + + + + + +
+
+ ); + } + + let serverError = ''; + if (this.props.serverError) { + serverError = ( +
+ +
+ ); + } + + return ( +
+

+ +

+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ +
+ {serverError} +
+ +
+
+ {content} +
+
+ ); + } +} diff --git a/webapp/components/admin_console/compliance_reports/index.js b/webapp/components/admin_console/compliance_reports/index.js new file mode 100644 index 000000000..8534c1fda --- /dev/null +++ b/webapp/components/admin_console/compliance_reports/index.js @@ -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); diff --git a/webapp/components/admin_console/index.js b/webapp/components/admin_console/index.js new file mode 100644 index 000000000..4b333e65c --- /dev/null +++ b/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); diff --git a/webapp/components/admin_console/logs.jsx b/webapp/components/admin_console/logs.jsx deleted file mode 100644 index d3fa67f55..000000000 --- a/webapp/components/admin_console/logs.jsx +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import AdminStore from 'stores/admin_store.jsx'; -import LoadingScreen from '../loading_screen.jsx'; -import * as AsyncClient from 'utils/async_client.jsx'; - -import {FormattedMessage} from 'react-intl'; - -import React from 'react'; - -export default class Logs extends React.Component { - constructor(props) { - super(props); - - this.onLogListenerChange = this.onLogListenerChange.bind(this); - this.reload = this.reload.bind(this); - - this.state = { - logs: AdminStore.getLogs() - }; - } - - componentDidMount() { - AdminStore.addLogChangeListener(this.onLogListenerChange); - AsyncClient.getLogs(); - this.refs.logPanel.focus(); - } - - componentDidUpdate() { - // Scroll Down to get the latest logs - var node = this.refs.logPanel; - node.scrollTop = node.scrollHeight; - node.focus(); - } - - componentWillUnmount() { - AdminStore.removeLogChangeListener(this.onLogListenerChange); - } - - onLogListenerChange() { - this.setState({ - logs: AdminStore.getLogs() - }); - } - - reload() { - AdminStore.saveLogs(null); - this.setState({ - logs: null - }); - - AsyncClient.getLogs(); - } - - render() { - var content = null; - - if (this.state.logs === null) { - content = ; - } else { - content = []; - - for (var i = 0; i < this.state.logs.length; i++) { - var style = { - whiteSpace: 'nowrap', - fontFamily: 'monospace' - }; - - if (this.state.logs[i].indexOf('[EROR]') > 0) { - style.color = 'red'; - } - - content.push(
); - content.push( - - {this.state.logs[i]} - - ); - } - } - - return ( -
-

- -

-
-
- -
-
- -
- {content} -
-
- ); - } -} diff --git a/webapp/components/admin_console/saml_settings.jsx b/webapp/components/admin_console/saml_settings.jsx index a02ab4a8a..4c0c0c8fd 100644 --- a/webapp/components/admin_console/saml_settings.jsx +++ b/webapp/components/admin_console/saml_settings.jsx @@ -14,7 +14,7 @@ import SettingsGroup from './settings_group.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 { constructor(props) { @@ -74,7 +74,7 @@ export default class SamlSettings extends AdminSettings { } componentWillMount() { - samlCertificateStatus( + AdminActions.samlCertificateStatus( (data) => { const files = {}; if (!data.IdpCertificateFile) { @@ -94,38 +94,50 @@ export default class SamlSettings extends AdminSettings { } uploadCertificate(id, file, callback) { - uploadCertificateFile( - file, - () => { - const fileName = file.name; - this.handleChange(id, fileName); - this.setState({[id]: fileName, [`${id}Error`]: null}); - if (callback && typeof callback === 'function') { - callback(); - } - }, - (error) => { - if (callback && typeof callback === 'function') { - callback(error.message); - } + const complete = () => { + const fileName = file.name; + this.handleChange(id, fileName); + this.setState({[id]: fileName, [`${id}Error`]: null}); + if (callback && typeof callback === 'function') { + callback(); } - ); + }; + + 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) { - removeCertificateFile( - this.state[id], - () => { - this.handleChange(id, ''); - this.setState({[id]: null, [`${id}Error`]: null}); - }, - (error) => { - if (callback && typeof callback === 'function') { - callback(); - } - this.setState({[id]: null, [`${id}Error`]: error.message}); + const complete = () => { + this.handleChange(id, ''); + this.setState({[id]: null, [`${id}Error`]: null}); + }; + + const fail = (error) => { + if (callback && typeof callback === 'function') { + callback(); } - ); + 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() { diff --git a/webapp/components/admin_console/server_logs/index.js b/webapp/components/admin_console/server_logs/index.js new file mode 100644 index 000000000..3adacaf1a --- /dev/null +++ b/webapp/components/admin_console/server_logs/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 {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); diff --git a/webapp/components/admin_console/server_logs/logs.jsx b/webapp/components/admin_console/server_logs/logs.jsx new file mode 100644 index 000000000..b60a66ce6 --- /dev/null +++ b/webapp/components/admin_console/server_logs/logs.jsx @@ -0,0 +1,123 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import LoadingScreen from 'components/loading_screen.jsx'; + +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 + } + + constructor(props) { + super(props); + + this.state = { + loadingLogs: true + }; + } + + componentDidMount() { + this.refs.logPanel.focus(); + + this.props.actions.getLogs().then( + () => this.setState({loadingLogs: false}) + ); + } + + componentDidUpdate() { + // Scroll Down to get the latest logs + var node = this.refs.logPanel; + node.scrollTop = node.scrollHeight; + node.focus(); + } + + reload = () => { + this.setState({loadingLogs: true}); + this.props.actions.getLogs().then( + () => this.setState({loadingLogs: false}) + ); + } + + render() { + let content = null; + + if (this.state.loadingLogs) { + content = ; + } else { + content = []; + + for (let i = 0; i < this.props.logs.length; i++) { + const style = { + whiteSpace: 'nowrap', + fontFamily: 'monospace' + }; + + if (this.props.logs[i].indexOf('[EROR]') > 0) { + style.color = 'red'; + } + + content.push(
); + content.push( + + {this.props.logs[i]} + + ); + } + } + + return ( +
+

+ +

+
+
+ +
+
+ +
+ {content} +
+
+ ); + } +} diff --git a/webapp/components/admin_console/system_users/index.js b/webapp/components/admin_console/system_users/index.js index 24144d701..8f1c0dc35 100644 --- a/webapp/components/admin_console/system_users/index.js +++ b/webapp/components/admin_console/system_users/index.js @@ -6,10 +6,13 @@ import {bindActionCreators} from 'redux'; import {getTeams, getTeamStats} from 'mattermost-redux/actions/teams'; import {getUser} from 'mattermost-redux/actions/users'; +import {getTeamsList} from 'mattermost-redux/selectors/entities/teams'; + import SystemUsers from './system_users.jsx'; function mapStateToProps(state, ownProps) { return { + teams: getTeamsList(state), ...ownProps }; } diff --git a/webapp/components/admin_console/system_users/system_users.jsx b/webapp/components/admin_console/system_users/system_users.jsx index 645d1e9e6..f0b3edac7 100644 --- a/webapp/components/admin_console/system_users/system_users.jsx +++ b/webapp/components/admin_console/system_users/system_users.jsx @@ -1,9 +1,8 @@ -import PropTypes from 'prop-types'; - // Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. import React from 'react'; +import PropTypes from 'prop-types'; import {FormattedMessage} from 'react-intl'; import { @@ -13,14 +12,12 @@ import { searchUsers } from 'actions/user_actions.jsx'; -import AdminStore from 'stores/admin_store.jsx'; import AnalyticsStore from 'stores/analytics_store.jsx'; import TeamStore from 'stores/team_store.jsx'; import UserStore from 'stores/user_store.jsx'; import {getStandardAnalytics} from 'utils/async_client.jsx'; import {Constants, StatTypes, UserSearchOptions} from 'utils/constants.jsx'; -import {convertTeamMapToList} from 'utils/team_utils.jsx'; import * as Utils from 'utils/utils.jsx'; import SystemUsersList from './system_users_list.jsx'; @@ -36,9 +33,27 @@ const USERS_PER_PAGE = 50; export default class SystemUsers extends React.Component { static propTypes = { + + /* + * Array of team objects + */ + teams: PropTypes.arrayOf(PropTypes.object).isRequired, + actions: PropTypes.shape({ + + /* + * Function to get teams + */ getTeams: PropTypes.func.isRequired, + + /* + * Function to get statistics for a team + */ getTeamStats: PropTypes.func.isRequired, + + /* + * Function to get a user + */ getUser: PropTypes.func.isRequired }).isRequired } @@ -46,7 +61,6 @@ export default class SystemUsers extends React.Component { constructor(props) { super(props); - this.updateTeamsFromStore = this.updateTeamsFromStore.bind(this); this.updateTotalUsersFromStore = this.updateTotalUsersFromStore.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.state = { - teams: convertTeamMapToList(AdminStore.getAllTeams()), totalUsers: AnalyticsStore.getAllSystem()[StatTypes.TOTAL_USERS], users: UserStore.getProfileList(), @@ -76,8 +89,6 @@ export default class SystemUsers extends React.Component { } componentDidMount() { - AdminStore.addAllTeamsChangeListener(this.updateTeamsFromStore); - AnalyticsStore.addChangeListener(this.updateTotalUsersFromStore); TeamStore.addStatsChangeListener(this.updateTotalUsersFromStore); @@ -101,8 +112,6 @@ export default class SystemUsers extends React.Component { } componentWillUnmount() { - AdminStore.removeAllTeamsChangeListener(this.updateTeamsFromStore); - AnalyticsStore.removeChangeListener(this.updateTotalUsersFromStore); TeamStore.removeStatsChangeListener(this.updateTotalUsersFromStore); @@ -111,10 +120,6 @@ export default class SystemUsers extends React.Component { UserStore.removeWithoutTeamChangeListener(this.updateUsersFromStore); } - updateTeamsFromStore() { - this.setState({teams: convertTeamMapToList(AdminStore.getAllTeams())}); - } - updateTotalUsersFromStore(teamId = this.state.teamId) { if (teamId === ALL_USERS) { this.setState({ @@ -271,7 +276,7 @@ export default class SystemUsers extends React.Component { } renderFilterRow(doSearch) { - const teams = this.state.teams.map((team) => { + const teams = this.props.teams.map((team) => { return (