diff options
author | Joram Wilander <jwawilander@gmail.com> | 2017-06-14 08:56:56 -0400 |
---|---|---|
committer | Harrison Healey <harrisonmhealey@gmail.com> | 2017-06-14 08:56:56 -0400 |
commit | 1138dd67705829a6af0d6c610cf3dbe09082187c (patch) | |
tree | 23bdc3db76221bead172be1c51eb52a4987636f1 /webapp/components | |
parent | 40efd8367a85e3333e9b7cc45c390259d412088c (diff) | |
download | chat-1138dd67705829a6af0d6c610cf3dbe09082187c.tar.gz chat-1138dd67705829a6af0d6c610cf3dbe09082187c.tar.bz2 chat-1138dd67705829a6af0d6c610cf3dbe09082187c.zip |
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
Diffstat (limited to 'webapp/components')
18 files changed, 446 insertions, 306 deletions
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 <div/>; } 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 ( <div className='admin-console__wrapper'> 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 = ( <img className='user__picture' - src={Client.getUsersRoute() + '/' + me.id + '/image?time=' + me.last_picture_update} + src={Client4.getProfilePictureUrl(me.id, me.last_picture_update)} /> ); } diff --git a/webapp/components/admin_console/audits.jsx b/webapp/components/admin_console/audits/audits.jsx index 594e55e39..0811c216f 100644 --- a/webapp/components/admin_console/audits.jsx +++ b/webapp/components/admin_console/audits/audits.jsx @@ -1,68 +1,66 @@ // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import LoadingScreen from '../loading_screen.jsx'; -import AuditTable from '../audit_table.jsx'; -import ComplianceReports from './compliance_reports.jsx'; +import LoadingScreen from 'components/loading_screen.jsx'; +import AuditTable from 'components/audit_table.jsx'; +import ComplianceReports from 'components/admin_console/compliance_reports'; -import AdminStore from 'stores/admin_store.jsx'; +import React from 'react'; +import PropTypes from 'prop-types'; +import {FormattedMessage} from 'react-intl'; -import * as AsyncClient from 'utils/async_client.jsx'; +export default class Audits extends React.PureComponent { + static propTypes = { -import {FormattedMessage} from 'react-intl'; + /* + * Array of audits to render + */ + audits: PropTypes.arrayOf(PropTypes.object).isRequired, -import React from 'react'; + actions: PropTypes.shape({ + + /* + * Function to fetch audits + */ + getAudits: PropTypes.func.isRequired + }).isRequired + } -export default class Audits extends React.Component { constructor(props) { super(props); - this.onAuditListenerChange = this.onAuditListenerChange.bind(this); - this.reload = this.reload.bind(this); - this.state = { - audits: AdminStore.getAudits() + loadingAudits: true }; } componentDidMount() { - AdminStore.addAuditChangeListener(this.onAuditListenerChange); - AsyncClient.getServerAudits(); - } - - componentWillUnmount() { - AdminStore.removeAuditChangeListener(this.onAuditListenerChange); - } - - onAuditListenerChange() { - this.setState({ - audits: AdminStore.getAudits() - }); + this.props.actions.getAudits().then( + () => this.setState({loadingAudits: false}) + ); } - reload() { - AdminStore.saveAudits(null); - this.setState({ - audits: null - }); - - AsyncClient.getServerAudits(); + reload = () => { + this.setState({loadingAudits: true}); + this.props.actions.getAudits().then( + () => this.setState({loadingAudits: false}) + ); } render() { - var content = null; + let content = null; if (global.window.mm_license.IsLicensed !== 'true') { return <div/>; } - if (this.state.audits === null) { + if (this.state.loadingAudits) { content = <LoadingScreen/>; } else { content = ( <div style={{margin: '10px'}}> <AuditTable - audits={this.state.audits} + audits={this.props.audits} showUserId={true} showIp={true} showSession={true} 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 = ( <img className='brand-img' - src={Client.getAdminRoute() + '/get_brand_image?t=' + this.state.brandImageTimestamp} + src={Client4.getBrandImageUrl(this.state.brandImageTimestamp)} /> ); } else { @@ -180,6 +189,7 @@ export default class BrandImageSetting extends React.Component { disabled={this.props.disabled || !this.state.brandImage} onClick={this.handleImageSubmit} id='upload-button' + ref='upload' 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!')} > 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 = ( <div style={{marginBottom: '10px'}} @@ -85,7 +86,7 @@ export default class ClusterSettings extends AdminSettings { 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.' values={{ - clusterId: AdminStore.getClusterId() + clusterId: Client4.clusterId }} /> </div> diff --git a/webapp/components/admin_console/compliance_reports.jsx b/webapp/components/admin_console/compliance_reports/compliance_reports.jsx index 567a6ca04..af361bace 100644 --- a/webapp/components/admin_console/compliance_reports.jsx +++ b/webapp/components/admin_console/compliance_reports/compliance_reports.jsx @@ -1,92 +1,96 @@ // 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 LoadingScreen from 'components/loading_screen.jsx'; -import Client from 'client/web_client.jsx'; -import * as AsyncClient from '../../utils/async_client.jsx'; -import {saveComplianceReports} from 'actions/admin_actions.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'; -import React from 'react'; -import ReactDOM from 'react-dom'; +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) { 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 + loadingReports: true }; } componentDidMount() { - AdminStore.addComplianceReportsChangeListener(this.onComplianceReportsListenerChange); - - if (global.window.mm_license.IsLicensed !== 'true' || !this.state.enabled) { + if (global.window.mm_license.IsLicensed !== 'true' || !this.props.enabled) { return; } - AsyncClient.getComplianceReports(); + this.props.actions.getComplianceReports().then( + () => this.setState({loadingReports: false}) + ); } - componentWillUnmount() { - AdminStore.removeComplianceReportsChangeListener(this.onComplianceReportsListenerChange); - } + reload = () => { + this.setState({loadingReports: true}); - onComplianceReportsListenerChange() { - this.setState({ - reports: AdminStore.getComplianceReports() - }); + this.props.actions.getComplianceReports().then( + () => this.setState({loadingReports: false}) + ); } - reload() { - AdminStore.saveComplianceReports(null); - this.setState({ - reports: null, - serverError: null - }); + runReport = (e) => { + e.preventDefault(); + + this.setState({runningReport: true}); - AsyncClient.getComplianceReports(); - } + 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); - 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'); + 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}); } ); } @@ -112,21 +116,20 @@ export default class ComplianceReports extends React.Component { } render() { - var content = null; - - if (global.window.mm_license.IsLicensed !== 'true' || !this.state.enabled) { + if (global.window.mm_license.IsLicensed !== 'true' || !this.props.enabled) { return <div/>; } - if (this.state.reports === null) { + let content = null; + if (this.state.loadingReports) { content = <LoadingScreen/>; } else { var list = []; - for (var i = 0; i < this.state.reports.length; i++) { - const report = this.state.reports[i]; + for (var i = 0; i < this.props.reports.length; i++) { + const report = this.props.reports[i]; - var params = ''; + let params = ''; if (report.type === 'adhoc') { params = ( <span> @@ -152,10 +155,10 @@ export default class ComplianceReports extends React.Component { </span>); } - var download = ''; + let download = ''; if (report.status === 'finished') { download = ( - <a href={Client.getAdminRoute() + '/download_compliance_report/' + report.id}> + <a href={`${Client4.getBaseRoute()}/compliance/reports/${report.id}/download`}> <FormattedMessage id='admin.compliance_table.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') { status = ( <span style={{color: 'green'}}>{report.status}</span> @@ -177,8 +180,8 @@ export default class ComplianceReports extends React.Component { ); } - var user = report.user_id; - var profile = UserStore.getProfile(report.user_id); + let user = report.user_id; + const profile = UserStore.getProfile(report.user_id); if (profile) { user = profile.email; } @@ -256,13 +259,13 @@ export default class ComplianceReports extends React.Component { } let serverError = ''; - if (this.state.serverError) { + if (this.props.serverError) { serverError = ( <div className='form-group has-error' style={{marginTop: '10px'}} > - <label className='control-label'>{this.state.serverError}</label> + <label className='control-label'>{this.props.serverError}</label> </div> ); } @@ -372,6 +375,7 @@ export default class ComplianceReports extends React.Component { <button type='submit' className='btn btn-link' + disabled={this.state.runningReport} onClick={this.reload} > <i className='fa fa-refresh'/> 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/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/logs.jsx b/webapp/components/admin_console/server_logs/logs.jsx index d3fa67f55..b60a66ce6 100644 --- a/webapp/components/admin_console/logs.jsx +++ b/webapp/components/admin_console/server_logs/logs.jsx @@ -1,30 +1,43 @@ // 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 LoadingScreen from 'components/loading_screen.jsx'; +import React from 'react'; +import PropTypes from 'prop-types'; import {FormattedMessage} from 'react-intl'; -import React from 'react'; +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) { super(props); - this.onLogListenerChange = this.onLogListenerChange.bind(this); - this.reload = this.reload.bind(this); - this.state = { - logs: AdminStore.getLogs() + loadingLogs: true }; } componentDidMount() { - AdminStore.addLogChangeListener(this.onLogListenerChange); - AsyncClient.getLogs(); this.refs.logPanel.focus(); + + this.props.actions.getLogs().then( + () => this.setState({loadingLogs: false}) + ); } componentDidUpdate() { @@ -34,40 +47,28 @@ export default class Logs extends React.Component { node.focus(); } - componentWillUnmount() { - AdminStore.removeLogChangeListener(this.onLogListenerChange); - } - - onLogListenerChange() { - this.setState({ - logs: AdminStore.getLogs() - }); - } - - reload() { - AdminStore.saveLogs(null); - this.setState({ - logs: null - }); - - AsyncClient.getLogs(); + reload = () => { + this.setState({loadingLogs: true}); + this.props.actions.getLogs().then( + () => this.setState({loadingLogs: false}) + ); } render() { - var content = null; + let content = null; - if (this.state.logs === null) { + if (this.state.loadingLogs) { content = <LoadingScreen/>; } else { content = []; - for (var i = 0; i < this.state.logs.length; i++) { - var style = { + for (let i = 0; i < this.props.logs.length; i++) { + const style = { whiteSpace: 'nowrap', fontFamily: 'monospace' }; - if (this.state.logs[i].indexOf('[EROR]') > 0) { + if (this.props.logs[i].indexOf('[EROR]') > 0) { style.color = 'red'; } @@ -77,7 +78,7 @@ export default class Logs extends React.Component { key={'log_' + i} style={style} > - {this.state.logs[i]} + {this.props.logs[i]} </span> ); } 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 ( <option key={team.id} @@ -339,7 +344,7 @@ export default class SystemUsers extends React.Component { users={users} usersPerPage={USERS_PER_PAGE} total={this.state.totalUsers} - teams={this.state.teams} + teams={this.props.teams} teamId={this.state.teamId} term={this.state.term} onTermChange={this.handleTermChange} diff --git a/webapp/components/analytics/team_analytics/index.js b/webapp/components/analytics/team_analytics/index.js index 270967a1b..0620a8fdb 100644 --- a/webapp/components/analytics/team_analytics/index.js +++ b/webapp/components/analytics/team_analytics/index.js @@ -5,10 +5,20 @@ import {connect} from 'react-redux'; import {bindActionCreators} from 'redux'; 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'; +const LAST_ANALYTICS_TEAM = 'last_analytics_team'; + function mapStateToProps(state, ownProps) { + const teams = getTeamsList(state); + const teamId = BrowserStore.getGlobalItem(LAST_ANALYTICS_TEAM, teams.length > 0 ? teams[0].id : ''); + return { + initialTeam: state.entities.teams.teams[teamId], + teams, ...ownProps }; } diff --git a/webapp/components/analytics/team_analytics/team_analytics.jsx b/webapp/components/analytics/team_analytics/team_analytics.jsx index eff19a309..6591d293a 100644 --- a/webapp/components/analytics/team_analytics/team_analytics.jsx +++ b/webapp/components/analytics/team_analytics/team_analytics.jsx @@ -1,21 +1,18 @@ -import PropTypes from 'prop-types'; - // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. import React from 'react'; +import PropTypes from 'prop-types'; import {FormattedDate, FormattedMessage, FormattedHTMLMessage} from 'react-intl'; import Banner from 'components/admin_console/banner.jsx'; import LoadingScreen from 'components/loading_screen.jsx'; -import AdminStore from 'stores/admin_store.jsx'; import AnalyticsStore from 'stores/analytics_store.jsx'; import BrowserStore from 'stores/browser_store.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import {StatTypes} from 'utils/constants.jsx'; -import {convertTeamMapToList} from 'utils/team_utils.jsx'; import LineChart from 'components/analytics/line_chart.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 { static propTypes = { + + /* + * Array of team objects + */ + teams: PropTypes.arrayOf(PropTypes.object).isRequired, + + /* + * Initial team to load analytics for + */ + initialTeam: PropTypes.object, + actions: PropTypes.shape({ + + /* + * Function to get teams + */ getTeams: PropTypes.func.isRequired }).isRequired } @@ -34,37 +46,27 @@ export default class TeamAnalytics extends React.Component { constructor(props) { super(props); - this.onChange = this.onChange.bind(this); - 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 : ''); + const teamId = props.initialTeam ? props.initialTeam.id : ''; this.state = { - teams, - teamId, - team: AdminStore.getTeam(teamId), + team: props.initialTeam, stats: AnalyticsStore.getAllTeam(teamId) }; } componentDidMount() { AnalyticsStore.addChangeListener(this.onChange); - AdminStore.addAllTeamsChangeListener(this.onAllTeamsChange); - if (this.state.teamId !== '') { - this.getData(this.state.teamId); + if (this.state.team) { + 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) { - if (nextState.teamId !== this.state.teamId) { - this.getData(nextState.teamId); + if (nextState.team && nextState.team !== this.state.team) { + this.getData(nextState.team.id); } } @@ -77,53 +79,38 @@ export default class TeamAnalytics extends React.Component { componentWillUnmount() { AnalyticsStore.removeChangeListener(this.onChange); - AdminStore.removeAllTeamsChangeListener(this.onAllTeamsChange); } - onChange() { + onChange = () => { + const teamId = this.state.team ? this.state.team.id : ''; this.setState({ - stats: AnalyticsStore.getAllTeam(this.state.teamId) + stats: AnalyticsStore.getAllTeam(teamId) }); } - onAllTeamsChange() { - 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] - }); - } - } + handleTeamChange = (e) => { + const teamId = e.target.value; - this.setState({ - teams + let team; + this.props.teams.forEach((t) => { + if (t.id === teamId) { + team = t; + } }); - } - - handleTeamChange(e) { - const teamId = e.target.value; this.setState({ - teamId, - team: AdminStore.getTeam(teamId) + team }); BrowserStore.setGlobalItem(LAST_ANALYTICS_TEAM, teamId); } 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/>; } - if (this.state.teamId === '') { + if (this.state.team == null) { return ( <Banner description={ @@ -217,7 +204,7 @@ export default class TeamAnalytics extends React.Component { const recentActiveUsers = formatRecentUsersData(stats[StatTypes.RECENTLY_ACTIVE_USERS]); const newlyCreatedUsers = formatNewUsersData(stats[StatTypes.NEWLY_CREATED_USERS]); - const teams = this.state.teams.map((team) => { + const teams = this.props.teams.map((team) => { return ( <option key={team.id} @@ -246,7 +233,7 @@ export default class TeamAnalytics extends React.Component { <select className='form-control team-statistics__team-filter__dropdown' onChange={this.handleTeamChange} - value={this.state.teamId} + value={this.state.team.id} > {teams} </select> diff --git a/webapp/components/user_list_row.jsx b/webapp/components/user_list_row.jsx index 1076752a9..ac26aace7 100644 --- a/webapp/components/user_list_row.jsx +++ b/webapp/components/user_list_row.jsx @@ -8,7 +8,7 @@ import PreferenceStore from 'stores/preference_store.jsx'; import Constants from 'utils/constants.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'; @@ -73,7 +73,7 @@ export default function UserListRow({user, extraInfo, actions, actionProps, acti className='more-modal__row' > <ProfilePicture - src={`${Client.getUsersRoute()}/${user.id}/image?time=${user.last_picture_update}`} + src={Client4.getProfilePictureUrl(user.id, user.last_picture_update)} status={status} width='32' height='32' |