diff options
Diffstat (limited to 'webapp')
-rw-r--r-- | webapp/components/admin_console/admin_controller.jsx | 3 | ||||
-rw-r--r-- | webapp/components/admin_console/admin_sidebar.jsx | 17 | ||||
-rw-r--r-- | webapp/components/admin_console/audits.jsx | 55 | ||||
-rw-r--r-- | webapp/components/admin_console/compliance_reports.jsx | 388 | ||||
-rw-r--r-- | webapp/components/admin_console/compliance_settings.jsx | 260 | ||||
-rw-r--r-- | webapp/components/audit_table.jsx | 9 | ||||
-rw-r--r-- | webapp/i18n/en.json | 35 | ||||
-rw-r--r-- | webapp/sass/routes/_admin-console.scss | 20 | ||||
-rw-r--r-- | webapp/stores/admin_store.jsx | 30 | ||||
-rw-r--r-- | webapp/utils/async_client.jsx | 26 | ||||
-rw-r--r-- | webapp/utils/client.jsx | 29 | ||||
-rw-r--r-- | webapp/utils/constants.jsx | 1 |
12 files changed, 847 insertions, 26 deletions
diff --git a/webapp/components/admin_console/admin_controller.jsx b/webapp/components/admin_console/admin_controller.jsx index e4a4e28fc..aea2a0197 100644 --- a/webapp/components/admin_console/admin_controller.jsx +++ b/webapp/components/admin_console/admin_controller.jsx @@ -23,6 +23,7 @@ import LegalAndSupportSettingsTab from './legal_and_support_settings.jsx'; import TeamUsersTab from './team_users.jsx'; import TeamAnalyticsTab from '../analytics/team_analytics.jsx'; import LdapSettingsTab from './ldap_settings.jsx'; +import ComplianceSettingsTab from './compliance_settings.jsx'; import LicenseSettingsTab from './license_settings.jsx'; import SystemAnalyticsTab from '../analytics/system_analytics.jsx'; @@ -159,6 +160,8 @@ export default class AdminController extends React.Component { tab = <LegalAndSupportSettingsTab config={this.state.config}/>; } else if (this.state.selected === 'ldap_settings') { tab = <LdapSettingsTab config={this.state.config}/>; + } else if (this.state.selected === 'compliance_settings') { + tab = <ComplianceSettingsTab config={this.state.config}/>; } else if (this.state.selected === 'license') { tab = <LicenseSettingsTab config={this.state.config}/>; } else if (this.state.selected === 'team_users') { diff --git a/webapp/components/admin_console/admin_sidebar.jsx b/webapp/components/admin_console/admin_sidebar.jsx index 27d4a4112..8ee75e2ef 100644 --- a/webapp/components/admin_console/admin_sidebar.jsx +++ b/webapp/components/admin_console/admin_sidebar.jsx @@ -171,6 +171,7 @@ export default class AdminSidebar extends React.Component { } let ldapSettings; + let complianceSettings; let licenseSettings; if (global.window.mm_config.BuildEnterpriseReady === 'true') { if (global.window.mm_license.IsLicensed === 'true') { @@ -188,6 +189,21 @@ export default class AdminSidebar extends React.Component { </a> </li> ); + + complianceSettings = ( + <li> + <a + href='#' + className={this.isSelected('compliance_settings')} + onClick={this.handleClick.bind(this, 'compliance_settings', null)} + > + <FormattedMessage + id='admin.sidebar.compliance' + defaultMessage='Compliance Settings' + /> + </a> + </li> + ); } licenseSettings = ( @@ -381,6 +397,7 @@ export default class AdminSidebar extends React.Component { </a> </li> {ldapSettings} + {complianceSettings} <li> <a href='#' diff --git a/webapp/components/admin_console/audits.jsx b/webapp/components/admin_console/audits.jsx index 28503d783..1f94de7da 100644 --- a/webapp/components/admin_console/audits.jsx +++ b/webapp/components/admin_console/audits.jsx @@ -3,6 +3,7 @@ import LoadingScreen from '../loading_screen.jsx'; import AuditTable from '../audit_table.jsx'; +import ComplianceReports from './compliance_reports.jsx'; import AdminStore from 'stores/admin_store.jsx'; @@ -60,36 +61,40 @@ export default class Audits extends React.Component { } else { content = ( <div style={{margin: '10px'}}> - <AuditTable - audits={this.state.audits} - showUserId={true} - showIp={true} - showSession={true} - /> + <AuditTable + audits={this.state.audits} + showUserId={true} + showIp={true} + showSession={true} + /> </div> ); } return ( - <div className='panel'> - <h3> - <FormattedMessage - id='admin.audits.title' - defaultMessage='User Activity' - /> - </h3> - <button - type='submit' - className='btn btn-primary' - onClick={this.reload} - > - <FormattedMessage - id='admin.audits.reload' - defaultMessage='Reload' - /> - </button> - <div className='log__panel'> - {content} + <div> + <ComplianceReports/> + + <div className='panel'> + <h3> + <FormattedMessage + id='admin.audits.title' + defaultMessage='User Activity' + /> + </h3> + <button + type='submit' + className='btn btn-primary' + onClick={this.reload} + > + <FormattedMessage + id='admin.audits.reload' + defaultMessage='Reload' + /> + </button> + <div className='audit__panel'> + {content} + </div> </div> </div> ); diff --git a/webapp/components/admin_console/compliance_reports.jsx b/webapp/components/admin_console/compliance_reports.jsx new file mode 100644 index 000000000..84def2bce --- /dev/null +++ b/webapp/components/admin_console/compliance_reports.jsx @@ -0,0 +1,388 @@ +// Copyright (c) 2016 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 * as Client from '../../utils/client.jsx'; +import * as AsyncClient from '../../utils/async_client.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 = { + reports: AdminStore.getComplianceReports(), + serverError: null + }; + } + + componentDidMount() { + AdminStore.addComplianceReportsChangeListener(this.onComplianceReportsListenerChange); + + if (global.window.mm_license.IsLicensed !== 'true' || global.window.mm_config.EnableCompliance !== 'true') { + 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); + + Client.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 ( + <span style={{whiteSpace: 'nowrap'}}> + <FormattedDate + value={date} + day='2-digit' + month='short' + year='numeric' + /> + {' - '} + <FormattedTime + value={date} + hour='2-digit' + minute='2-digit' + /> + </span> + ); + } + + render() { + var content = null; + + if (global.window.mm_license.IsLicensed !== 'true' || global.window.mm_config.EnableCompliance !== 'true') { + return <div/>; + } + + if (this.state.reports === null) { + content = <LoadingScreen/>; + } 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 = ( + <span> + <FormattedMessage + id='admin.compliance_reports.from' + defaultMessage='From:' + />{' '}{this.getDateTime(report.start_at)} + <br/> + <FormattedMessage + id='admin.compliance_reports.to' + defaultMessage='To:' + />{' '}{this.getDateTime(report.end_at)} + <br/> + <FormattedMessage + id='admin.compliance_reports.emails' + defaultMessage='Emails:' + />{' '}{report.emails} + <br/> + <FormattedMessage + id='admin.compliance_reports.keywords' + defaultMessage='Keywords:' + />{' '}{report.keywords} + </span>); + } + + var download = ''; + if (report.status === 'finished') { + download = ( + <a href={'/api/v1/admin/download_compliance_report/' + report.id}> + <FormattedMessage + id='admin.compliance_table.download' + defaultMessage='Download' + /> + </a> + ); + } + + var status = report.status; + if (report.status === 'finished') { + status = ( + <span style={{color: 'green'}}>{report.status}</span> + ); + } + + if (report.status === 'failed') { + status = ( + <span style={{color: 'red'}}>{report.status}</span> + ); + } + + var user = report.user_id; + var profile = UserStore.getProfile(report.user_id); + if (profile) { + user = profile.email; + } + + list[i] = ( + <tr key={report.id}> + <td style={{whiteSpace: 'nowrap'}}>{download}</td> + <td>{this.getDateTime(report.create_at)}</td> + <td>{status}</td> + <td>{report.count}</td> + <td>{report.type}</td> + <td style={{whiteSpace: 'nowrap'}}>{report.desc}</td> + <td>{user}</td> + <td style={{whiteSpace: 'nowrap'}}>{params}</td> + </tr> + ); + } + + content = ( + <div style={{margin: '10px'}}> + <table className='table'> + <thead> + <tr> + <th></th> + <th> + <FormattedMessage + id='admin.compliance_table.timestamp' + defaultMessage='Timestamp' + /> + </th> + <th> + <FormattedMessage + id='admin.compliance_table.status' + defaultMessage='Status' + /> + </th> + <th> + <FormattedMessage + id='admin.compliance_table.records' + defaultMessage='Records' + /> + </th> + <th> + <FormattedMessage + id='admin.compliance_table.type' + defaultMessage='Type' + /> + </th> + <th> + <FormattedMessage + id='admin.compliance_table.desc' + defaultMessage='Description' + /> + </th> + <th> + <FormattedMessage + id='admin.compliance_table.userId' + defaultMessage='Requested By' + /> + </th> + <th> + <FormattedMessage + id='admin.compliance_table.params' + defaultMessage='Params' + /> + </th> + </tr> + </thead> + <tbody> + {list} + </tbody> + </table> + </div> + ); + } + + let serverError = ''; + if (this.state.serverError) { + serverError = ( + <div + className='form-group has-error' + style={{marginTop: '10px'}} + > + <label className='control-label'>{this.state.serverError}</label> + </div> + ); + } + + return ( + <div className='panel'> + <h3> + <FormattedMessage + id='admin.compliance_reports.title' + defaultMessage='Compliance Reports' + /> + </h3> + + <table> + <tbody> + <tr> + <td colSpan='5' + style={{paddingBottom: '6px'}} + > + <FormattedMessage + id='admin.compliance_reports.desc' + defaultMessage='Job Name:' + /> + <input + style={{width: '425px'}} + type='text' + className='form-control' + id='desc' + ref='desc' + placeholder={Utils.localizeMessage('admin.compliance_reports.desc_placeholder', 'Ex "Audit 445 for HR"')} + /> + </td> + </tr> + <tr> + <td> + <FormattedMessage + id='admin.compliance_reports.from' + defaultMessage='From:' + /> + <input + type='text' + className='form-control' + id='from' + ref='from' + placeholder={Utils.localizeMessage('admin.compliance_reports.from_placeholder', 'Ex "2016-03-11"')} + /> + </td> + <td style={{paddingLeft: '4px'}}> + <FormattedMessage + id='admin.compliance_reports.to' + defaultMessage='To:' + /> + <input + type='text' + className='form-control' + id='to' + ref='to' + placeholder={Utils.localizeMessage('admin.compliance_reports.to_placeholder', 'Ex "2016-03-15"')} + /> + </td> + <td style={{paddingLeft: '4px'}}> + <FormattedMessage + id='admin.compliance_reports.emails' + defaultMessage='Emails:' + /> + <input + style={{width: '325px'}} + type='text' + className='form-control' + id='emails' + ref='emails' + placeholder={Utils.localizeMessage('admin.compliance_reports.emails_placeholder', 'Ex "bill@example.com, bob@example.com"')} + /> + </td> + <td style={{paddingLeft: '4px'}}> + <FormattedMessage + id='admin.compliance_reports.keywords' + defaultMessage='Keywords:' + /> + <input + style={{width: '250px'}} + type='text' + className='form-control' + id='keywords' + ref='keywords' + placeholder={Utils.localizeMessage('admin.compliance_reports.keywords_placeholder', 'Ex "shorting stock"')} + /> + </td> + <td> + <button + id='run-button' + type='submit' + className='btn btn-primary' + onClick={this.runReport} + style={{marginTop: '20px', marginLeft: '20px'}} + > + <FormattedMessage + id='admin.compliance_reports.run' + defaultMessage='Run' + /> + </button> + </td> + </tr> + </tbody> + </table> + {serverError} + <div style={{marginTop: '20px'}}> + <button + type='submit' + className='btn btn-primary' + onClick={this.reload} + > + <FormattedMessage + id='admin.compliance_reports.reload' + defaultMessage='Reload' + /> + </button> + </div> + <div className='compliance__panel'> + {content} + </div> + </div> + ); + } +} diff --git a/webapp/components/admin_console/compliance_settings.jsx b/webapp/components/admin_console/compliance_settings.jsx new file mode 100644 index 000000000..fb2ae26f9 --- /dev/null +++ b/webapp/components/admin_console/compliance_settings.jsx @@ -0,0 +1,260 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import $ from 'jquery'; +import * as Client from '../../utils/client.jsx'; +import * as AsyncClient from '../../utils/async_client.jsx'; +import * as Utils from '../../utils/utils.jsx'; + +import {FormattedMessage, FormattedHTMLMessage} from 'react-intl'; + +import React from 'react'; +import ReactDOM from 'react-dom'; + +export default class ComplianceSettings extends React.Component { + constructor(props) { + super(props); + + this.handleSubmit = this.handleSubmit.bind(this); + this.handleChange = this.handleChange.bind(this); + this.handleEnable = this.handleEnable.bind(this); + this.handleDisable = this.handleDisable.bind(this); + + this.state = { + saveNeeded: false, + serverError: null, + enable: this.props.config.ComplianceSettings.Enable + }; + } + handleChange() { + this.setState({saveNeeded: true}); + } + handleEnable() { + this.setState({saveNeeded: true, enable: true}); + } + handleDisable() { + this.setState({saveNeeded: true, enable: false}); + } + handleSubmit(e) { + e.preventDefault(); + $('#save-button').button('loading'); + + const config = this.props.config; + config.ComplianceSettings.Enable = this.refs.Enable.checked; + config.ComplianceSettings.Directory = ReactDOM.findDOMNode(this.refs.Directory).value; + config.ComplianceSettings.EnableDaily = this.refs.EnableDaily.checked; + + Client.saveConfig( + config, + () => { + AsyncClient.getConfig(); + this.setState({ + serverError: null, + saveNeeded: false + }); + $('#save-button').button('reset'); + }, + (err) => { + this.setState({ + serverError: err.message, + saveNeeded: true + }); + $('#save-button').button('reset'); + } + ); + } + render() { + let serverError = ''; + if (this.state.serverError) { + serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>; + } + + let saveClass = 'btn'; + if (this.state.saveNeeded) { + saveClass = 'btn btn-primary'; + } + + const licenseEnabled = global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.Compliance === 'true'; + + let bannerContent; + if (!licenseEnabled) { + bannerContent = ( + <div className='banner warning'> + <div className='banner__content'> + <FormattedHTMLMessage + id='admin.compliance.noLicense' + defaultMessage='<h4 class="banner__heading">Note:</h4><p>Compliance is an enterprise feature. Your current license does not support Compliance. Click <a href="http://mattermost.com"target="_blank">here</a> for information and pricing on enterprise licenses.</p>' + /> + </div> + </div> + ); + } + + return ( + <div className='wrapper--fixed'> + {bannerContent} + <h3> + <FormattedMessage + id='admin.compliance.title' + defaultMessage='Compliance Settings' + /> + </h3> + <form + className='form-horizontal' + role='form' + > + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='Enable' + > + <FormattedMessage + id='admin.compliance.enableTitle' + defaultMessage='Enable Compliance:' + /> + </label> + <div className='col-sm-8'> + <label className='radio-inline'> + <input + type='radio' + name='Enable' + value='true' + ref='Enable' + defaultChecked={this.props.config.ComplianceSettings.Enable} + onChange={this.handleEnable} + disabled={!licenseEnabled} + /> + <FormattedMessage + id='admin.compliance.true' + defaultMessage='true' + /> + </label> + <label className='radio-inline'> + <input + type='radio' + name='Enable' + value='false' + defaultChecked={!this.props.config.ComplianceSettings.Enable} + onChange={this.handleDisable} + /> + <FormattedMessage + id='admin.compliance.false' + defaultMessage='false' + /> + </label> + <p className='help-text'> + <FormattedMessage + id='admin.compliance.enableDesc' + defaultMessage='When true, Mattermost allows compliance reporting' + /> + </p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='Directory' + > + <FormattedMessage + id='admin.compliance.directoryTitle' + defaultMessage='Compliance Directory Location:' + /> + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='Directory' + ref='Directory' + placeholder={Utils.localizeMessage('admin.compliance.directoryExample', 'Ex "./data/"')} + defaultValue={this.props.config.ComplianceSettings.Directory} + onChange={this.handleChange} + disabled={!this.state.enable} + /> + <p className='help-text'> + <FormattedMessage + id='admin.compliance.directoryDescription' + defaultMessage='Directory to which compliance reports are written. If blank, will be set to ./data/.' + /> + </p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='EnableDaily' + > + <FormattedMessage + id='admin.compliance.enableDailyTitle' + defaultMessage='Enable Daily Report:' + /> + </label> + <div className='col-sm-8'> + <label className='radio-inline'> + <input + type='radio' + name='EnableDaily' + value='true' + ref='EnableDaily' + defaultChecked={this.props.config.ComplianceSettings.EnableDaily} + onChange={this.handleChange} + disabled={!this.state.enable} + /> + <FormattedMessage + id='admin.compliance.true' + defaultMessage='true' + /> + </label> + <label className='radio-inline'> + <input + type='radio' + name='EnableDaily' + value='false' + defaultChecked={!this.props.config.ComplianceSettings.EnableDaily} + disabled={!this.state.enable} + /> + <FormattedMessage + id='admin.compliance.false' + defaultMessage='false' + /> + </label> + <p className='help-text'> + <FormattedMessage + id='admin.compliance.enableDesc' + defaultMessage='When true, Mattermost will generate a daily compliance report.' + /> + </p> + </div> + </div> + + <div className='form-group'> + <div className='col-sm-12'> + {serverError} + <button + disabled={!this.state.saveNeeded} + type='submit' + className={saveClass} + onClick={this.handleSubmit} + id='save-button' + data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + Utils.localizeMessage('admin.compliance.saving', 'Saving Config...')} + > + <FormattedMessage + id='admin.compliance.save' + defaultMessage='Save' + /> + </button> + </div> + </div> + </form> + </div> + ); + } +} + +ComplianceSettings.propTypes = { + config: React.PropTypes.object +}; + diff --git a/webapp/components/audit_table.jsx b/webapp/components/audit_table.jsx index 73dcfccc3..abf09dfaf 100644 --- a/webapp/components/audit_table.jsx +++ b/webapp/components/audit_table.jsx @@ -219,7 +219,12 @@ class AuditTable extends React.Component { let uContent; if (this.props.showUserId) { - uContent = <td>{auditInfo.userId}</td>; + var profile = UserStore.getProfile(auditInfo.userId); + if (profile) { + uContent = <td>{profile.email}</td>; + } else { + uContent = <td>{auditInfo.userId}</td>; + } } let iContent; @@ -562,6 +567,8 @@ export function formatAuditInfo(audit, formatMessage) { default: break; } + } else if (actionURL.indexOf('/admin/download_compliance_report') === 0) { + auditDesc = Utils.toTitleCase(audit.extra_info); } else { switch (actionURL) { case '/logout': diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json index dc43cc019..9a9477557 100644 --- a/webapp/i18n/en.json +++ b/webapp/i18n/en.json @@ -195,6 +195,40 @@ "admin.ldap.uernameAttrDesc": "The attribute in the LDAP server that will be used to populate the username field in Mattermost. This may be the same as the ID Attribute.", "admin.ldap.usernameAttrEx": "Ex \"sAMAccountName\"", "admin.ldap.usernameAttrTitle": "Username Attribute:", + "admin.compliance.saving": "Saving Config...", + "admin.compliance.directoryExample": "Ex \"./data/\"", + "admin.compliance.noLicense": "<h4 class=\"banner__heading\">Note:</h4><p>Compliance is an enterprise feature. Your current license does not support Compliance. Click <a href=\"http://mattermost.com\" target=\"_blank\">here</a> for information and pricing on enterprise licenses.</p>", + "admin.compliance.title": "Compliance Settings", + "admin.compliance.enableTitle": "Enable Compliance:", + "admin.compliance.true": "true", + "admin.compliance.false": "false", + "admin.compliance.enableDesc": "When true, Mattermost allows compliance reporting", + "admin.compliance.directoryTitle": "Compliance Directory Location:", + "admin.compliance.directoryDescription": "Directory to which compliance reports are written. If blank, will be set to ./data/.", + "admin.compliance.enableDailyTitle": "Enable Daily Report:", + "admin.compliance.enableDesc": "When true, Mattermost will generate a daily compliance report.", + "admin.compliance.save": "Save", + "admin.compliance_reports.from": "From:", + "admin.compliance_reports.to": "To:", + "admin.compliance_reports.emails": "Emails:", + "admin.compliance_reports.keywords": "Keywords:", + "admin.compliance_table.download": "Download", + "admin.compliance_table.timestamp": "Timestamp", + "admin.compliance_table.status": "Status", + "admin.compliance_table.records": "Records", + "admin.compliance_table.type": "Type", + "admin.compliance_table.desc": "Description", + "admin.compliance_table.userId": "Requested By", + "admin.compliance_table.params": "Params", + "admin.compliance_reports.title": "Compliance Reports", + "admin.compliance_reports.desc": "Job Name:", + "admin.compliance_reports.desc_placeholder": "Ex \"Audit 445 for HR\"", + "admin.compliance_reports.from_placeholder": "Ex \"2016-03-11\"", + "admin.compliance_reports.to_placeholder": "Ex \"2016-03-15\"", + "admin.compliance_reports.emails_placeholder": "Ex \"bill@example.com, bob@example.com\"", + "admin.compliance_reports.keywords_placeholder": "Ex \"shorting stock\"", + "admin.compliance_reports.run": "Run", + "admin.compliance_reports.reload": "Reload", "admin.licence.keyMigration": "If you’re migrating servers you may need to remove your license key from this server in order to install it on a new server. To start, <a href=\"http://mattermost.com\" target=\"_blank\">disable all Enterprise Edition features on this server</a>. This will enable the ability to remove the license key and downgrade this server from Enterprise Edition to Team Edition.", "admin.license.chooseFile": "Choose File", "admin.license.edition": "Edition: ", @@ -335,6 +369,7 @@ "admin.sidebar.gitlab": "GitLab Settings", "admin.sidebar.ldap": "LDAP Settings", "admin.sidebar.license": "Edition and License", + "admin.sidebar.compliance": "Compliance Settings", "admin.sidebar.loading": "Loading", "admin.sidebar.log": "Log Settings", "admin.sidebar.logs": "Logs", diff --git a/webapp/sass/routes/_admin-console.scss b/webapp/sass/routes/_admin-console.scss index 63cf8eb13..0b47e5ab6 100644 --- a/webapp/sass/routes/_admin-console.scss +++ b/webapp/sass/routes/_admin-console.scss @@ -175,6 +175,26 @@ width: 100%; } + .compliance__panel { + overflow: scroll; + width: 100%; + height: 400px; + border: 1px solid #ddd; + margin-top: 10px; + padding: 5px; + background-color: white; + } + + .audit__panel { + overflow: scroll; + width: 100%; + height: 400px; + border: 1px solid #ddd; + margin-top: 10px; + padding: 5px; + background-color: white; + } + .app__content { color: #333; diff --git a/webapp/stores/admin_store.jsx b/webapp/stores/admin_store.jsx index 0a4c8c442..0f19dd484 100644 --- a/webapp/stores/admin_store.jsx +++ b/webapp/stores/admin_store.jsx @@ -13,6 +13,7 @@ const LOG_CHANGE_EVENT = 'log_change'; const SERVER_AUDIT_CHANGE_EVENT = 'server_audit_change'; const CONFIG_CHANGE_EVENT = 'config_change'; const ALL_TEAMS_EVENT = 'all_team_change'; +const SERVER_COMPLIANCE_REPORT_CHANGE_EVENT = 'server_compliance_reports_change'; class AdminStoreClass extends EventEmitter { constructor() { @@ -22,6 +23,7 @@ class AdminStoreClass extends EventEmitter { this.audits = null; this.config = null; this.teams = null; + this.complianceReports = null; this.emitLogChange = this.emitLogChange.bind(this); this.addLogChangeListener = this.addLogChangeListener.bind(this); @@ -31,6 +33,10 @@ class AdminStoreClass extends EventEmitter { this.addAuditChangeListener = this.addAuditChangeListener.bind(this); this.removeAuditChangeListener = this.removeAuditChangeListener.bind(this); + this.emitComplianceReportsChange = this.emitComplianceReportsChange.bind(this); + this.addComplianceReportsChangeListener = this.addComplianceReportsChangeListener.bind(this); + this.removeComplianceReportsChangeListener = this.removeComplianceReportsChangeListener.bind(this); + this.emitConfigChange = this.emitConfigChange.bind(this); this.addConfigChangeListener = this.addConfigChangeListener.bind(this); this.removeConfigChangeListener = this.removeConfigChangeListener.bind(this); @@ -64,6 +70,18 @@ class AdminStoreClass extends EventEmitter { this.removeListener(SERVER_AUDIT_CHANGE_EVENT, callback); } + emitComplianceReportsChange() { + this.emit(SERVER_COMPLIANCE_REPORT_CHANGE_EVENT); + } + + addComplianceReportsChangeListener(callback) { + this.on(SERVER_COMPLIANCE_REPORT_CHANGE_EVENT, callback); + } + + removeComplianceReportsChangeListener(callback) { + this.removeListener(SERVER_COMPLIANCE_REPORT_CHANGE_EVENT, callback); + } + emitConfigChange() { this.emit(CONFIG_CHANGE_EVENT); } @@ -104,6 +122,14 @@ class AdminStoreClass extends EventEmitter { this.audits = audits; } + getComplianceReports() { + return this.complianceReports; + } + + saveComplianceReports(complianceReports) { + this.complianceReports = complianceReports; + } + getConfig() { return this.config; } @@ -147,6 +173,10 @@ AdminStoreClass.dispatchToken = AppDispatcher.register((payload) => { AdminStore.saveAudits(action.audits); AdminStore.emitAuditChange(); break; + case ActionTypes.RECEIVED_SERVER_COMPLIANCE_REPORTS: + AdminStore.saveComplianceReports(action.complianceReports); + AdminStore.emitComplianceReportsChange(); + break; case ActionTypes.RECEIVED_CONFIG: AdminStore.saveConfig(action.config); AdminStore.emitConfigChange(); diff --git a/webapp/utils/async_client.jsx b/webapp/utils/async_client.jsx index 9a5869f9a..2392b50b9 100644 --- a/webapp/utils/async_client.jsx +++ b/webapp/utils/async_client.jsx @@ -342,6 +342,32 @@ export function getServerAudits() { ); } +export function getComplianceReports() { + if (isCallInProgress('getComplianceReports')) { + return; + } + + callTracker.getComplianceReports = utils.getTimestamp(); + client.getComplianceReports( + (data, textStatus, xhr) => { + callTracker.getComplianceReports = 0; + + if (xhr.status === 304 || !data) { + return; + } + + AppDispatcher.handleServerAction({ + type: ActionTypes.RECEIVED_SERVER_COMPLIANCE_REPORTS, + complianceReports: data + }); + }, + (err) => { + callTracker.getComplianceReports = 0; + dispatchError(err, 'getComplianceReports'); + } + ); +} + export function getConfig() { if (isCallInProgress('getConfig')) { return; diff --git a/webapp/utils/client.jsx b/webapp/utils/client.jsx index ef6d496a2..69bda4303 100644 --- a/webapp/utils/client.jsx +++ b/webapp/utils/client.jsx @@ -413,6 +413,35 @@ export function getAudits(userId, success, error) { }); } +export function getComplianceReports(success, error) { + $.ajax({ + url: '/api/v1/admin/compliance_reports', + dataType: 'json', + contentType: 'application/json', + type: 'GET', + success, + error: function onError(xhr, status, err) { + var e = handleError('getComplianceReports', xhr, status, err); + error(e); + } + }); +} + +export function saveComplianceReports(job, success, error) { + $.ajax({ + url: '/api/v1/admin/save_compliance_report', + dataType: 'json', + contentType: 'application/json', + type: 'POST', + data: JSON.stringify(job), + success, + error: (xhr, status, err) => { + var e = handleError('saveComplianceReports', xhr, status, err); + error(e); + } + }); +} + export function getLogs(success, error) { $.ajax({ url: '/api/v1/admin/logs', diff --git a/webapp/utils/constants.jsx b/webapp/utils/constants.jsx index 29178aca6..4ee934e11 100644 --- a/webapp/utils/constants.jsx +++ b/webapp/utils/constants.jsx @@ -76,6 +76,7 @@ export default { RECEIVED_CONFIG: null, RECEIVED_LOGS: null, RECEIVED_SERVER_AUDITS: null, + RECEIVED_SERVER_COMPLIANCE_REPORTS: null, RECEIVED_ALL_TEAMS: null, RECEIVED_LOCALE: null, |