Commit 5e0aa96b authored by Joram Wilander's avatar Joram Wilander Committed by Harrison Healey

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
parent 75984393
This diff is collapsed.
......@@ -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'>
......
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,
......
......@@ -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)}
/>
);
}
......
// 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}
......
// 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 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!')}
>
......
......@@ -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>
......
// 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'/>
......
// 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);
// 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';