From ed9a2da83b3b77e7dd0314eaa92082ac8a2a9a9c Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Mon, 21 Sep 2015 15:11:56 -0700 Subject: Adding email to admin console --- .../components/admin_console/email_settings.jsx | 528 +++++++++++++-------- .../components/admin_console/log_settings.jsx | 50 +- 2 files changed, 379 insertions(+), 199 deletions(-) (limited to 'web/react/components/admin_console') diff --git a/web/react/components/admin_console/email_settings.jsx b/web/react/components/admin_console/email_settings.jsx index e8fb25858..2e107f1ba 100644 --- a/web/react/components/admin_console/email_settings.jsx +++ b/web/react/components/admin_console/email_settings.jsx @@ -1,15 +1,148 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. +var Client = require('../../utils/client.jsx'); +var AsyncClient = require('../../utils/async_client.jsx'); + export default class EmailSettings extends React.Component { constructor(props) { super(props); + this.handleChange = this.handleChange.bind(this); + this.handleTestConnection = this.handleTestConnection.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + this.buildConfig = this.buildConfig.bind(this); + this.state = { + sendEmailNotifications: this.props.config.EmailSettings.SendEmailNotifications, + saveNeeded: false, + serverError: null, + emailSuccess: null, + emailFail: null }; } + handleChange(action) { + var s = {saveNeeded: true, serverError: this.state.serverError}; + + if (action === 'sendEmailNotifications_true') { + s.sendEmailNotifications = true; + } + + if (action === 'sendEmailNotifications_false') { + s.sendEmailNotifications = false; + } + + this.setState(s); + } + + buildConfig() { + var config = this.props.config; + config.EmailSettings.AllowSignUpWithEmail = React.findDOMNode(this.refs.allowSignUpWithEmail).checked; + config.EmailSettings.SendEmailNotifications = React.findDOMNode(this.refs.sendEmailNotifications).checked; + config.EmailSettings.RequireEmailVerification = React.findDOMNode(this.refs.requireEmailVerification).checked; + config.EmailSettings.SendEmailNotifications = React.findDOMNode(this.refs.sendEmailNotifications).checked; + config.EmailSettings.FeedbackName = React.findDOMNode(this.refs.feedbackName).value.trim(); + config.EmailSettings.FeedbackEmail = React.findDOMNode(this.refs.feedbackEmail).value.trim(); + config.EmailSettings.SMTPServer = React.findDOMNode(this.refs.SMTPServer).value.trim(); + config.EmailSettings.SMTPPort = React.findDOMNode(this.refs.SMTPPort).value.trim(); + config.EmailSettings.SMTPUsername = React.findDOMNode(this.refs.SMTPUsername).value.trim(); + config.EmailSettings.SMTPPassword = React.findDOMNode(this.refs.SMTPPassword).value.trim(); + config.EmailSettings.ConnectionSecurity = React.findDOMNode(this.refs.ConnectionSecurity).value.trim(); + return config; + } + + handleTestConnection(e) { + e.preventDefault(); + $('#connection-button').button('loading'); + + var config = this.buildConfig(); + + Client.testEmail( + config, + () => { + this.setState({ + sendEmailNotifications: config.EmailSettings.SendEmailNotifications, + serverError: null, + saveNeeded: true, + emailSuccess: true, + emailFail: null + }); + }, + (err) => { + this.setState({ + sendEmailNotifications: config.EmailSettings.SendEmailNotifications, + serverError: null, + saveNeeded: true, + emailSuccess: null, + emailFail: err.message + ' - ' + err.detailed_error + }); + $('#connection-button').button('reset'); + } + ); + } + + handleSubmit(e) { + e.preventDefault(); + $('#save-button').button('loading'); + + var config = this.buildConfig(); + + Client.saveConfig( + config, + () => { + AsyncClient.getConfig(); + this.setState({ + sendEmailNotifications: config.EmailSettings.SendEmailNotifications, + serverError: null, + saveNeeded: false, + emailSuccess: null, + emailFail: null + }); + $('#save-button').button('reset'); + }, + (err) => { + this.setState({ + sendEmailNotifications: config.EmailSettings.SendEmailNotifications, + serverError: err.message, + saveNeeded: true, + emailSuccess: null, + emailFail: null + }); + $('#save-button').button('reset'); + } + ); + } + render() { + var serverError = ''; + if (this.state.serverError) { + serverError =
; + } + + var saveClass = 'btn'; + if (this.state.saveNeeded) { + saveClass = 'btn btn-primary'; + } + + var emailSuccess = ''; + if (this.state.emailSuccess) { + emailSuccess = ( +
+ {'No errors were reported while sending an email. Please check your inbox to make sure.'} +
+ ); + } + + var emailFail = ''; + if (this.state.emailFail) { + emailSuccess = ( +
+ {'Connection unsuccessful: ' + this.state.emailFail} +
+ ); + } + return (

{'Email Settings'}

@@ -17,295 +150,308 @@ export default class EmailSettings extends React.Component { className='form-horizontal' role='form' > +
-

{'This is some sample help text for the Bypass Email field'}

+

{'Typically set to true in production. When true Mattermost will allow team creation and account signup utilizing email and password. You would set this to false if you only wanted to allow signup from a service like OAuth or LDAP.'}

+
- -
-
{' This is some error text for the Bypass Email field'}
-
-

{'This is some sample help text for the SMTP username field'}

+ + +

{'Typically set to true in production. When true Mattermost will attempt to send email notifications. Developers may set this field to false skipping sending emails for faster development.'}

+
- + + +

{'Typically set to true in production. When true Mattermost will not allow a user to login without first having recieved an email with a verification link. Developers may set this field to false so skip sending verification emails for faster development.'}

+
-
- - {'Test Connection'} - -
{' Connection successful'}
-
{' Connection unsuccessful'}
-
+

{'Name displayed on email account used when sending notification emails from Mattermost.'}

+
- +
- - + +

{'Email displayed on email account used when sending notification emails from Mattermost.'}

+
- +
- - + +

{' Obtain this credential from administrator setting up your email server.'}

+
+

{' Obtain this credential from administrator setting up your email server.'}

+
+

{'Location of SMTP email server.'}

+
-
-
- -
+ +
+ +

{'Port of SMTP email server.'}

-
-
- ); } -} \ No newline at end of file +} + +EmailSettings.propTypes = { + config: React.PropTypes.object +}; \ No newline at end of file diff --git a/web/react/components/admin_console/log_settings.jsx b/web/react/components/admin_console/log_settings.jsx index 4e3db8f68..2707ce6b6 100644 --- a/web/react/components/admin_console/log_settings.jsx +++ b/web/react/components/admin_console/log_settings.jsx @@ -12,13 +12,33 @@ export default class LogSettings extends React.Component { this.handleSubmit = this.handleSubmit.bind(this); this.state = { + consoleEnable: this.props.config.LogSettings.ConsoleEnable, + fileEnable: this.props.config.LogSettings.FileEnable, saveNeeded: false, serverError: null }; } - handleChange() { - this.setState({saveNeeded: true, serverError: this.state.serverError}); + handleChange(action) { + var s = {saveNeeded: true, serverError: this.state.serverError}; + + if (action === 'console_true') { + s.consoleEnable = true; + } + + if (action === 'console_false') { + s.consoleEnable = false; + } + + if (action === 'file_true') { + s.fileEnable = true; + } + + if (action === 'file_false') { + s.fileEnable = false; + } + + this.setState(s); } handleSubmit(e) { @@ -37,11 +57,21 @@ export default class LogSettings extends React.Component { config, () => { AsyncClient.getConfig(); - this.setState({serverError: null, saveNeeded: false}); + this.setState({ + consoleEnable: config.LogSettings.ConsoleEnable, + fileEnable: config.LogSettings.FileEnable, + serverError: null, + saveNeeded: false + }); $('#save-button').button('reset'); }, (err) => { - this.setState({serverError: err.message, saveNeeded: true}); + this.setState({ + consoleEnable: config.LogSettings.ConsoleEnable, + fileEnable: config.LogSettings.FileEnable, + serverError: err.message, + saveNeeded: true + }); $('#save-button').button('reset'); } ); @@ -81,7 +111,7 @@ export default class LogSettings extends React.Component { value='true' ref='consoleEnable' defaultChecked={this.props.config.LogSettings.ConsoleEnable} - onChange={this.handleChange} + onChange={this.handleChange.bind(this, 'console_true')} /> {'true'} @@ -91,7 +121,7 @@ export default class LogSettings extends React.Component { name='consoleEnable' value='false' defaultChecked={!this.props.config.LogSettings.ConsoleEnable} - onChange={this.handleChange} + onChange={this.handleChange.bind(this, 'console_false')} /> {'false'} @@ -113,6 +143,7 @@ export default class LogSettings extends React.Component { ref='consoleLevel' defaultValue={this.props.config.LogSettings.consoleLevel} onChange={this.handleChange} + disabled={!this.state.consoleEnable} > @@ -136,7 +167,7 @@ export default class LogSettings extends React.Component { ref='fileEnable' value='true' defaultChecked={this.props.config.LogSettings.FileEnable} - onChange={this.handleChange} + onChange={this.handleChange.bind(this, 'file_true')} /> {'true'} @@ -146,7 +177,7 @@ export default class LogSettings extends React.Component { name='fileEnable' value='false' defaultChecked={!this.props.config.LogSettings.FileEnable} - onChange={this.handleChange} + onChange={this.handleChange.bind(this, 'file_false')} /> {'false'} @@ -168,6 +199,7 @@ export default class LogSettings extends React.Component { ref='fileLevel' defaultValue={this.props.config.LogSettings.FileLevel} onChange={this.handleChange} + disabled={!this.state.fileEnable} > @@ -193,6 +225,7 @@ export default class LogSettings extends React.Component { placeholder='Enter your file location' defaultValue={this.props.config.LogSettings.FileLocation} onChange={this.handleChange} + disabled={!this.state.fileEnable} />

{'File to which log files are written. If blank, will be set to ./logs/mattermost.log. Log rotation is enabled and new files may be created in the same directory.'}

@@ -214,6 +247,7 @@ export default class LogSettings extends React.Component { placeholder='Enter your file format' defaultValue={this.props.config.LogSettings.FileFormat} onChange={this.handleChange} + disabled={!this.state.fileEnable} />

{'Format of log message output. If blank will be set to "[%D %T] [%L] %M", where:'} -- cgit v1.2.3-1-g7c22 From 9baef5a6a033fea2ff5499ee9969c6cd65efef0c Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Mon, 21 Sep 2015 15:17:02 -0700 Subject: Fixing button --- web/react/components/admin_console/email_settings.jsx | 1 + 1 file changed, 1 insertion(+) (limited to 'web/react/components/admin_console') diff --git a/web/react/components/admin_console/email_settings.jsx b/web/react/components/admin_console/email_settings.jsx index 2e107f1ba..a87dfc4da 100644 --- a/web/react/components/admin_console/email_settings.jsx +++ b/web/react/components/admin_console/email_settings.jsx @@ -68,6 +68,7 @@ export default class EmailSettings extends React.Component { emailSuccess: true, emailFail: null }); + $('#connection-button').button('reset'); }, (err) => { this.setState({ -- cgit v1.2.3-1-g7c22 From 0529a494ae6b541f917522a20de92e3d36615f65 Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Mon, 21 Sep 2015 17:34:13 -0700 Subject: Adding image properties --- .../components/admin_console/admin_controller.jsx | 3 + .../components/admin_console/admin_sidebar.jsx | 9 + .../components/admin_console/image_settings.jsx | 417 +++++++++++++++++++++ 3 files changed, 429 insertions(+) create mode 100644 web/react/components/admin_console/image_settings.jsx (limited to 'web/react/components/admin_console') diff --git a/web/react/components/admin_console/admin_controller.jsx b/web/react/components/admin_console/admin_controller.jsx index e82fe1b76..0dc0ec45b 100644 --- a/web/react/components/admin_console/admin_controller.jsx +++ b/web/react/components/admin_console/admin_controller.jsx @@ -9,6 +9,7 @@ var LoadingScreen = require('../loading_screen.jsx'); var EmailSettingsTab = require('./email_settings.jsx'); var LogSettingsTab = require('./log_settings.jsx'); var LogsTab = require('./logs.jsx'); +var ImageSettingsTab = require('./image_settings.jsx'); export default class AdminController extends React.Component { constructor(props) { @@ -53,6 +54,8 @@ export default class AdminController extends React.Component { tab = ; } else if (this.state.selected === 'logs') { tab = ; + } else if (this.state.selected === 'image_settings') { + tab = ; } } diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx index a6e689490..5544f9c6d 100644 --- a/web/react/components/admin_console/admin_sidebar.jsx +++ b/web/react/components/admin_console/admin_sidebar.jsx @@ -65,6 +65,15 @@ export default class AdminSidebar extends React.Component { {'Logs'} +

  • + + {'Image Settings'} + +
  • diff --git a/web/react/components/admin_console/image_settings.jsx b/web/react/components/admin_console/image_settings.jsx new file mode 100644 index 000000000..9a7de266d --- /dev/null +++ b/web/react/components/admin_console/image_settings.jsx @@ -0,0 +1,417 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var Client = require('../../utils/client.jsx'); +var AsyncClient = require('../../utils/async_client.jsx'); + +export default class ImageSettings extends React.Component { + constructor(props) { + super(props); + + this.handleChange = this.handleChange.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + + this.state = { + saveNeeded: false, + serverError: null, + DriverName: this.props.config.ImageSettings.DriverName + }; + } + + handleChange(action) { + var s = {saveNeeded: true, serverError: this.state.serverError}; + + if (action === 'DriverName') { + s.DriverName = React.findDOMNode(this.refs.DriverName).value; + } + + this.setState(s); + } + + handleSubmit(e) { + e.preventDefault(); + $('#save-button').button('loading'); + + var config = this.props.config; + config.ImageSettings.DriverName = React.findDOMNode(this.refs.DriverName).value; + config.ImageSettings.Directory = React.findDOMNode(this.refs.Directory).value; + config.ImageSettings.AmazonS3AccessKeyId = React.findDOMNode(this.refs.AmazonS3AccessKeyId).value; + config.ImageSettings.AmazonS3SecretAccessKey = React.findDOMNode(this.refs.AmazonS3SecretAccessKey).value; + config.ImageSettings.AmazonS3Bucket = React.findDOMNode(this.refs.AmazonS3Bucket).value; + config.ImageSettings.AmazonS3Region = React.findDOMNode(this.refs.AmazonS3Region).value; + + var thumbnailWidth = 120; + if (!isNaN(parseInt(React.findDOMNode(this.refs.ThumbnailWidth).value, 10))) { + thumbnailWidth = parseInt(React.findDOMNode(this.refs.ThumbnailWidth).value, 10); + } + config.ImageSettings.ThumbnailWidth = thumbnailWidth; + React.findDOMNode(this.refs.ThumbnailWidth).value = thumbnailWidth; + + var thumbnailHeight = 100; + if (!isNaN(parseInt(React.findDOMNode(this.refs.ThumbnailHeight).value, 10))) { + thumbnailHeight = parseInt(React.findDOMNode(this.refs.ThumbnailHeight).value, 10); + } + config.ImageSettings.ThumbnailHeight = thumbnailHeight; + React.findDOMNode(this.refs.ThumbnailHeight).value = thumbnailHeight; + + var previewWidth = 1024; + if (!isNaN(parseInt(React.findDOMNode(this.refs.PreviewWidth).value, 10))) { + previewWidth = parseInt(React.findDOMNode(this.refs.PreviewWidth).value, 10); + } + config.ImageSettings.PreviewWidth = previewWidth; + React.findDOMNode(this.refs.PreviewWidth).value = previewWidth; + + var previewHeight = 0; + if (!isNaN(parseInt(React.findDOMNode(this.refs.PreviewHeight).value, 10))) { + previewHeight = parseInt(React.findDOMNode(this.refs.PreviewHeight).value, 10); + } + config.ImageSettings.PreviewHeight = previewHeight; + React.findDOMNode(this.refs.PreviewHeight).value = previewHeight; + + var profileWidth = 128; + if (!isNaN(parseInt(React.findDOMNode(this.refs.ProfileWidth).value, 10))) { + profileWidth = parseInt(React.findDOMNode(this.refs.ProfileWidth).value, 10); + } + config.ImageSettings.ProfileWidth = profileWidth; + React.findDOMNode(this.refs.ProfileWidth).value = profileWidth; + + var profileHeight = 128; + if (!isNaN(parseInt(React.findDOMNode(this.refs.ProfileHeight).value, 10))) { + profileHeight = parseInt(React.findDOMNode(this.refs.ProfileHeight).value, 10); + } + config.ImageSettings.ProfileHeight = profileHeight; + React.findDOMNode(this.refs.ProfileHeight).value = profileHeight; + + 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() { + var serverError = ''; + if (this.state.serverError) { + serverError =
    ; + } + + var saveClass = 'btn'; + if (this.state.saveNeeded) { + saveClass = 'btn btn-primary'; + } + + var enableFile = false; + var enableS3 = false; + + if (this.state.DriverName === 'local') { + enableFile = true; + } + + if (this.state.DriverName === 'amazons3') { + enableS3 = true; + } + + return ( +
    +

    {'Image Settings'}

    +
    + +
    + +
    + +
    +
    + +
    + +
    + +

    {'Directory to which image files are written. If blank, will be set to ./data/.'}

    +
    +
    + +
    + +
    + +

    {'Obtain this credential from your Amazon EC2 administrator.'}

    +
    +
    + +
    + +
    + +

    {'Obtain this credential from your Amazon EC2 administrator.'}

    +
    +
    + +
    + +
    + +

    {'Name you selected for your S3 bucket in AWS.'}

    +
    +
    + +
    + +
    + +

    {'AWS region you selected for creating your S3 bucket.'}

    +
    +
    + +
    + +
    + +

    {'Width of thumbnails generated from uploaded images. Updating this value changes how thumbnail images render in future, but does not change images created in the past.'}

    +
    +
    + +
    + +
    + +

    {'Height of thumbnails generated from uploaded images. Updating this value changes how thumbnail images render in future, but does not change images created in the past.'}

    +
    +
    + +
    + +
    + +

    {'Maximum width of preview image. Updating this value changes how preview images render in future, but does not change images created in the past.'}

    +
    +
    + +
    + +
    + +

    {'Maximum height of preview image ("0": Sets to auto-size). Updating this value changes how preview images render in future, but does not change images created in the past.'}

    +
    +
    + +
    + +
    + +

    {'Width of profile picture.'}

    +
    +
    + +
    + +
    + +

    {'Height of profile picture.'}

    +
    +
    + +
    +
    + {serverError} + +
    +
    + +
    +
    + ); + } +} + +ImageSettings.propTypes = { + config: React.PropTypes.object +}; -- cgit v1.2.3-1-g7c22 From cae5dbd245f05e9441156a3918ef796d92ecf331 Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Mon, 21 Sep 2015 18:23:10 -0700 Subject: Adding privacy to admin console --- .../components/admin_console/admin_controller.jsx | 3 + .../components/admin_console/admin_sidebar.jsx | 9 ++ .../components/admin_console/privacy_settings.jsx | 163 +++++++++++++++++++++ 3 files changed, 175 insertions(+) create mode 100644 web/react/components/admin_console/privacy_settings.jsx (limited to 'web/react/components/admin_console') diff --git a/web/react/components/admin_console/admin_controller.jsx b/web/react/components/admin_console/admin_controller.jsx index 0dc0ec45b..9db1c945e 100644 --- a/web/react/components/admin_console/admin_controller.jsx +++ b/web/react/components/admin_console/admin_controller.jsx @@ -10,6 +10,7 @@ var EmailSettingsTab = require('./email_settings.jsx'); var LogSettingsTab = require('./log_settings.jsx'); var LogsTab = require('./logs.jsx'); var ImageSettingsTab = require('./image_settings.jsx'); +var PrivacySettingsTab = require('./privacy_settings.jsx'); export default class AdminController extends React.Component { constructor(props) { @@ -56,6 +57,8 @@ export default class AdminController extends React.Component { tab = ; } else if (this.state.selected === 'image_settings') { tab = ; + } else if (this.state.selected === 'privacy_settings') { + tab = ; } } diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx index 5544f9c6d..5f471ec2c 100644 --- a/web/react/components/admin_console/admin_sidebar.jsx +++ b/web/react/components/admin_console/admin_sidebar.jsx @@ -74,6 +74,15 @@ export default class AdminSidebar extends React.Component { {'Image Settings'} +
  • + + {'Privacy Settings'} + +
  • diff --git a/web/react/components/admin_console/privacy_settings.jsx b/web/react/components/admin_console/privacy_settings.jsx new file mode 100644 index 000000000..8ce693925 --- /dev/null +++ b/web/react/components/admin_console/privacy_settings.jsx @@ -0,0 +1,163 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var Client = require('../../utils/client.jsx'); +var AsyncClient = require('../../utils/async_client.jsx'); + +export default class PrivacySettings extends React.Component { + constructor(props) { + super(props); + + this.handleChange = this.handleChange.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + + this.state = { + saveNeeded: false, + serverError: null + }; + } + + handleChange() { + var s = {saveNeeded: true, serverError: this.state.serverError}; + + this.setState(s); + } + + handleSubmit(e) { + e.preventDefault(); + $('#save-button').button('loading'); + + var config = this.props.config; + config.PrivacySettings.ShowEmailAddress = React.findDOMNode(this.refs.ShowEmailAddress).checked; + config.PrivacySettings.ShowFullName = React.findDOMNode(this.refs.ShowFullName).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() { + var serverError = ''; + if (this.state.serverError) { + serverError =
    ; + } + + var saveClass = 'btn'; + if (this.state.saveNeeded) { + saveClass = 'btn btn-primary'; + } + + return ( +
    +

    {'Privacy Settings'}

    +
    + +
    + +
    + + +

    {'Hides email address of users from other users including team administrator.'}

    +
    +
    + +
    + +
    + + +

    {'Hides full name of users from other users including team administrator.'}

    +
    +
    + +
    +
    + {serverError} + +
    +
    + +
    +
    + ); + } +} + +PrivacySettings.propTypes = { + config: React.PropTypes.object +}; -- cgit v1.2.3-1-g7c22 From e78c79b83213efc40bffc5ef42071fedb85d6061 Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Mon, 21 Sep 2015 19:01:52 -0700 Subject: Adding rate limiter settings to the admin console --- .../components/admin_console/admin_controller.jsx | 3 + .../components/admin_console/admin_sidebar.jsx | 9 + .../components/admin_console/rate_settings.jsx | 272 +++++++++++++++++++++ 3 files changed, 284 insertions(+) create mode 100644 web/react/components/admin_console/rate_settings.jsx (limited to 'web/react/components/admin_console') diff --git a/web/react/components/admin_console/admin_controller.jsx b/web/react/components/admin_console/admin_controller.jsx index 9db1c945e..0cd3500e5 100644 --- a/web/react/components/admin_console/admin_controller.jsx +++ b/web/react/components/admin_console/admin_controller.jsx @@ -11,6 +11,7 @@ var LogSettingsTab = require('./log_settings.jsx'); var LogsTab = require('./logs.jsx'); var ImageSettingsTab = require('./image_settings.jsx'); var PrivacySettingsTab = require('./privacy_settings.jsx'); +var RateSettingsTab = require('./rate_settings.jsx'); export default class AdminController extends React.Component { constructor(props) { @@ -59,6 +60,8 @@ export default class AdminController extends React.Component { tab = ; } else if (this.state.selected === 'privacy_settings') { tab = ; + } else if (this.state.selected === 'rate_settings') { + tab = ; } } diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx index 5f471ec2c..f21511cb9 100644 --- a/web/react/components/admin_console/admin_sidebar.jsx +++ b/web/react/components/admin_console/admin_sidebar.jsx @@ -83,6 +83,15 @@ export default class AdminSidebar extends React.Component { {'Privacy Settings'} +
  • + + {'Rate Limit Settings'} + +
  • diff --git a/web/react/components/admin_console/rate_settings.jsx b/web/react/components/admin_console/rate_settings.jsx new file mode 100644 index 000000000..6c0f070da --- /dev/null +++ b/web/react/components/admin_console/rate_settings.jsx @@ -0,0 +1,272 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var Client = require('../../utils/client.jsx'); +var AsyncClient = require('../../utils/async_client.jsx'); + +export default class RateSettings extends React.Component { + constructor(props) { + super(props); + + this.handleChange = this.handleChange.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + + this.state = { + EnableRateLimiter: this.props.config.RateLimitSettings.EnableRateLimiter, + VaryByRemoteAddr: this.props.config.RateLimitSettings.VaryByRemoteAddr, + saveNeeded: false, + serverError: null + }; + } + + handleChange(action) { + var s = {saveNeeded: true, serverError: this.state.serverError}; + + if (action === 'EnableRateLimiterTrue') { + s.EnableRateLimiter = true; + } + + if (action === 'EnableRateLimiterFalse') { + s.EnableRateLimiter = false; + } + + if (action === 'VaryByRemoteAddrTrue') { + s.VaryByRemoteAddr = true; + } + + if (action === 'VaryByRemoteAddrFalse') { + s.VaryByRemoteAddr = false; + } + + this.setState(s); + } + + handleSubmit(e) { + e.preventDefault(); + $('#save-button').button('loading'); + + var config = this.props.config; + config.RateLimitSettings.EnableRateLimiter = React.findDOMNode(this.refs.EnableRateLimiter).checked; + config.RateLimitSettings.VaryByRemoteAddr = React.findDOMNode(this.refs.VaryByRemoteAddr).checked; + config.RateLimitSettings.VaryByHeader = React.findDOMNode(this.refs.VaryByHeader).value.trim(); + + var PerSec = 10; + if (!isNaN(parseInt(React.findDOMNode(this.refs.PerSec).value, 10))) { + PerSec = parseInt(React.findDOMNode(this.refs.PerSec).value, 10); + } + config.RateLimitSettings.PerSec = PerSec; + React.findDOMNode(this.refs.PerSec).value = PerSec; + + var MemoryStoreSize = 10000; + if (!isNaN(parseInt(React.findDOMNode(this.refs.MemoryStoreSize).value, 10))) { + MemoryStoreSize = parseInt(React.findDOMNode(this.refs.MemoryStoreSize).value, 10); + } + config.RateLimitSettings.MemoryStoreSize = MemoryStoreSize; + React.findDOMNode(this.refs.MemoryStoreSize).value = MemoryStoreSize; + + 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() { + var serverError = ''; + if (this.state.serverError) { + serverError =
    ; + } + + var saveClass = 'btn'; + if (this.state.saveNeeded) { + saveClass = 'btn btn-primary'; + } + + return ( +
    + +
    +
    +

    {'Note:'}

    +

    {'Changing properties in this section will require a server restart before taking effect.'}

    +
    +
    + +

    {'Rate Limit Settings'}

    +
    + +
    + +
    + + +

    {'When enabled throttles rate at which APIs respond.'}

    +
    +
    + +
    + +
    + +

    {'Height of profile picture.'}

    +
    +
    + +
    + +
    + +

    {'Maximum number of users sessions connected to the system as determined by VaryByRemoteAddr and VaryByHeader variables.'}

    +
    +
    + +
    + +
    + + +

    {'Rate limit API access by IP address.'}

    +
    +
    + +
    + +
    + +

    {'When filled in, vary rate limiting by http header field specified (e.g. when configuring ngnix set to "X-Real-IP", when configuring AmazonELB set to "X-Forwarded-For").'}

    +
    +
    + +
    +
    + {serverError} + +
    +
    + +
    +
    + ); + } +} + +RateSettings.propTypes = { + config: React.PropTypes.object +}; -- cgit v1.2.3-1-g7c22 From f05a2c03d5dbf5b0b7d09148a37d2325012b309f Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Tue, 22 Sep 2015 00:00:19 -0700 Subject: Adding SQL settings to admin console --- .../components/admin_console/admin_controller.jsx | 8 +- .../components/admin_console/admin_sidebar.jsx | 30 ++- .../components/admin_console/gitlab_settings.jsx | 277 ++++++++++++++++++++ .../components/admin_console/rate_settings.jsx | 2 +- .../components/admin_console/sql_settings.jsx | 283 +++++++++++++++++++++ 5 files changed, 592 insertions(+), 8 deletions(-) create mode 100644 web/react/components/admin_console/gitlab_settings.jsx create mode 100644 web/react/components/admin_console/sql_settings.jsx (limited to 'web/react/components/admin_console') diff --git a/web/react/components/admin_console/admin_controller.jsx b/web/react/components/admin_console/admin_controller.jsx index 0cd3500e5..491dbd754 100644 --- a/web/react/components/admin_console/admin_controller.jsx +++ b/web/react/components/admin_console/admin_controller.jsx @@ -12,6 +12,8 @@ var LogsTab = require('./logs.jsx'); var ImageSettingsTab = require('./image_settings.jsx'); var PrivacySettingsTab = require('./privacy_settings.jsx'); var RateSettingsTab = require('./rate_settings.jsx'); +var GitLabSettingsTab = require('./gitlab_settings.jsx'); +var SqlSettingsTab = require('./sql_settings.jsx'); export default class AdminController extends React.Component { constructor(props) { @@ -22,7 +24,7 @@ export default class AdminController extends React.Component { this.state = { config: null, - selected: 'email_settings' + selected: 'sql_settings' }; } @@ -62,6 +64,10 @@ export default class AdminController extends React.Component { tab = ; } else if (this.state.selected === 'rate_settings') { tab = ; + } else if (this.state.selected === 'gitlab_settings') { + tab = ; + } else if (this.state.selected === 'sql_settings') { + tab = ; } } diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx index f21511cb9..deb064015 100644 --- a/web/react/components/admin_console/admin_sidebar.jsx +++ b/web/react/components/admin_console/admin_sidebar.jsx @@ -38,6 +38,15 @@ export default class AdminSidebar extends React.Component {
    • diff --git a/web/react/components/admin_console/gitlab_settings.jsx b/web/react/components/admin_console/gitlab_settings.jsx new file mode 100644 index 000000000..f76655b89 --- /dev/null +++ b/web/react/components/admin_console/gitlab_settings.jsx @@ -0,0 +1,277 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var Client = require('../../utils/client.jsx'); +var AsyncClient = require('../../utils/async_client.jsx'); + +export default class GitLabSettings extends React.Component { + constructor(props) { + super(props); + + this.handleChange = this.handleChange.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + + this.state = { + Allow: this.props.config.GitLabSettings.Allow, + saveNeeded: false, + serverError: null + }; + } + + handleChange(action) { + var s = {saveNeeded: true, serverError: this.state.serverError}; + + if (action === 'AllowTrue') { + s.Allow = true; + } + + if (action === 'AllowFalse') { + s.Allow = false; + } + + this.setState(s); + } + + handleSubmit(e) { + e.preventDefault(); + $('#save-button').button('loading'); + + var config = this.props.config; + config.GitLabSettings.Allow = React.findDOMNode(this.refs.Allow).checked; + config.GitLabSettings.Secret = React.findDOMNode(this.refs.Secret).value.trim(); + config.GitLabSettings.Id = React.findDOMNode(this.refs.Id).value.trim(); + config.GitLabSettings.Scope = React.findDOMNode(this.refs.Scope).value.trim(); + config.GitLabSettings.AuthEndpoint = React.findDOMNode(this.refs.AuthEndpoint).value.trim(); + config.GitLabSettings.TokenEndpoint = React.findDOMNode(this.refs.TokenEndpoint).value.trim(); + config.GitLabSettings.UserApiEndpoint = React.findDOMNode(this.refs.UserApiEndpoint).value.trim(); + + 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() { + var serverError = ''; + if (this.state.serverError) { + serverError =
      ; + } + + var saveClass = 'btn'; + if (this.state.saveNeeded) { + saveClass = 'btn btn-primary'; + } + + return ( +
      + +

      {'GitLab Settings'}

      +
      + +
      + +
      + + +

      {'When true Mattermost will allow team creation and account signup utilizing GitLab OAuth.'}

      +
      +
      + +
      + +
      + +

      {'Need help text.'}

      +
      +
      + +
      + +
      + +

      {'Need help text.'}

      +
      +
      + +
      + +
      + +

      {'Need help text.'}

      +
      +
      + +
      + +
      + +

      {'Need help text.'}

      +
      +
      + +
      + +
      + +

      {'Need help text.'}

      +
      +
      + +
      + +
      + +

      {'Need help text.'}

      +
      +
      + +
      +
      + {serverError} + +
      +
      + +
      +
      + ); + } +} + +GitLabSettings.propTypes = { + config: React.PropTypes.object +}; diff --git a/web/react/components/admin_console/rate_settings.jsx b/web/react/components/admin_console/rate_settings.jsx index 6c0f070da..c05bf4a82 100644 --- a/web/react/components/admin_console/rate_settings.jsx +++ b/web/react/components/admin_console/rate_settings.jsx @@ -162,7 +162,7 @@ export default class RateSettings extends React.Component { onChange={this.handleChange} disabled={!this.state.EnableRateLimiter} /> -

      {'Height of profile picture.'}

      +

      {'Throttles API at this number of requests per second.'}

    diff --git a/web/react/components/admin_console/sql_settings.jsx b/web/react/components/admin_console/sql_settings.jsx new file mode 100644 index 000000000..35810f7ee --- /dev/null +++ b/web/react/components/admin_console/sql_settings.jsx @@ -0,0 +1,283 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var Client = require('../../utils/client.jsx'); +var AsyncClient = require('../../utils/async_client.jsx'); +var crypto = require('crypto'); + +export default class SqlSettings extends React.Component { + constructor(props) { + super(props); + + this.handleChange = this.handleChange.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + this.handleGenerate = this.handleGenerate.bind(this); + + this.state = { + saveNeeded: false, + serverError: null + }; + } + + handleChange() { + var s = {saveNeeded: true, serverError: this.state.serverError}; + this.setState(s); + } + + handleSubmit(e) { + e.preventDefault(); + $('#save-button').button('loading'); + + var config = this.props.config; + config.SqlSettings.Trace = React.findDOMNode(this.refs.Trace).checked; + config.SqlSettings.AtRestEncryptKey = React.findDOMNode(this.refs.AtRestEncryptKey).value.trim(); + + if (config.SqlSettings.AtRestEncryptKey === '') { + config.SqlSettings.AtRestEncryptKey = crypto.randomBytes(256).toString('base64').substring(0, 31); + React.findDOMNode(this.refs.AtRestEncryptKey).value = config.SqlSettings.AtRestEncryptKey; + } + + var MaxOpenConns = 10; + if (!isNaN(parseInt(React.findDOMNode(this.refs.MaxOpenConns).value, 10))) { + MaxOpenConns = parseInt(React.findDOMNode(this.refs.MaxOpenConns).value, 10); + } + config.SqlSettings.MaxOpenConns = MaxOpenConns; + React.findDOMNode(this.refs.MaxOpenConns).value = MaxOpenConns; + + var MaxIdleConns = 10; + if (!isNaN(parseInt(React.findDOMNode(this.refs.MaxIdleConns).value, 10))) { + MaxIdleConns = parseInt(React.findDOMNode(this.refs.MaxIdleConns).value, 10); + } + config.SqlSettings.MaxIdleConns = MaxIdleConns; + React.findDOMNode(this.refs.MaxIdleConns).value = MaxIdleConns; + + 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'); + } + ); + } + + handleGenerate(e) { + e.preventDefault(); + React.findDOMNode(this.refs.AtRestEncryptKey).value = crypto.randomBytes(256).toString('base64').substring(0, 31); + var s = {saveNeeded: true, serverError: this.state.serverError}; + this.setState(s); + } + + render() { + var serverError = ''; + if (this.state.serverError) { + serverError =
    ; + } + + var saveClass = 'btn'; + if (this.state.saveNeeded) { + saveClass = 'btn btn-primary'; + } + + var dataSource = '**********' + this.props.config.SqlSettings.DataSource.substring(this.props.config.SqlSettings.DataSource.indexOf('@')); + + var dataSourceReplicas = ''; + this.props.config.SqlSettings.DataSourceReplicas.forEach((replica) => { + dataSourceReplicas += '[**********' + replica.substring(replica.indexOf('@')) + '] '; + }); + + if (this.props.config.SqlSettings.DataSourceReplicas.length === 0) { + dataSourceReplicas = 'none'; + } + + return ( +
    + +
    +
    +

    {'Note:'}

    +

    {'Changing properties in this section will require a server restart before taking effect.'}

    +
    +
    + +

    {'SQL Settings'}

    +
    + +
    + +
    +

    {this.props.config.SqlSettings.DriverName}

    +
    +
    + +
    + +
    +

    {dataSource}

    +
    +
    + +
    + +
    +

    {dataSourceReplicas}

    +
    +
    + +
    + +
    + +

    {'Maximum number of idle connections held open to the database.'}

    +
    +
    + +
    + +
    + +

    {'Maximum number of open connections held open to the database.'}

    +
    +
    + +
    + +
    + +

    {'32-character salt available to encrypt and decrypt sensitive fields in database.'}

    +
    + +
    +
    +
    + +
    + +
    + + +

    {'Output executing SQL statements to the log. Typically used for development.'}

    +
    +
    + +
    +
    + {serverError} + +
    +
    + +
    +
    + ); + } +} + +SqlSettings.propTypes = { + config: React.PropTypes.object +}; -- cgit v1.2.3-1-g7c22 From 08a3acbb44b043b9bb56f9b96e91432352d06d1a Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Tue, 22 Sep 2015 01:15:41 -0700 Subject: Adding team settings to admin console --- .../components/admin_console/admin_controller.jsx | 6 +- .../components/admin_console/admin_sidebar.jsx | 9 + .../components/admin_console/image_settings.jsx | 34 +++ .../components/admin_console/team_settings.jsx | 257 +++++++++++++++++++++ 4 files changed, 305 insertions(+), 1 deletion(-) create mode 100644 web/react/components/admin_console/team_settings.jsx (limited to 'web/react/components/admin_console') diff --git a/web/react/components/admin_console/admin_controller.jsx b/web/react/components/admin_console/admin_controller.jsx index 491dbd754..72b5d5c9d 100644 --- a/web/react/components/admin_console/admin_controller.jsx +++ b/web/react/components/admin_console/admin_controller.jsx @@ -14,6 +14,8 @@ var PrivacySettingsTab = require('./privacy_settings.jsx'); var RateSettingsTab = require('./rate_settings.jsx'); var GitLabSettingsTab = require('./gitlab_settings.jsx'); var SqlSettingsTab = require('./sql_settings.jsx'); +var TeamSettingsTab = require('./team_settings.jsx'); + export default class AdminController extends React.Component { constructor(props) { @@ -24,7 +26,7 @@ export default class AdminController extends React.Component { this.state = { config: null, - selected: 'sql_settings' + selected: 'team_settings' }; } @@ -68,6 +70,8 @@ export default class AdminController extends React.Component { tab = ; } else if (this.state.selected === 'sql_settings') { tab = ; + } else if (this.state.selected === 'team_settings') { + tab = ; } } diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx index deb064015..2b7159e1d 100644 --- a/web/react/components/admin_console/admin_sidebar.jsx +++ b/web/react/components/admin_console/admin_sidebar.jsx @@ -38,6 +38,15 @@ export default class AdminSidebar extends React.Component {
    +
    + +
    + + +

    {'Allow users to share public links to files and images.'}

    +
    +
    +
    {serverError} diff --git a/web/react/components/admin_console/team_settings.jsx b/web/react/components/admin_console/team_settings.jsx new file mode 100644 index 000000000..fefc0e936 --- /dev/null +++ b/web/react/components/admin_console/team_settings.jsx @@ -0,0 +1,257 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var Client = require('../../utils/client.jsx'); +var AsyncClient = require('../../utils/async_client.jsx'); + +export default class TeamSettings extends React.Component { + constructor(props) { + super(props); + + this.handleChange = this.handleChange.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + + this.state = { + saveNeeded: false, + serverError: null + }; + } + + handleChange() { + var s = {saveNeeded: true, serverError: this.state.serverError}; + this.setState(s); + } + + handleSubmit(e) { + e.preventDefault(); + $('#save-button').button('loading'); + + var config = this.props.config; + config.TeamSettings.SiteName = React.findDOMNode(this.refs.SiteName).value.trim(); + config.TeamSettings.DefaultThemeColor = React.findDOMNode(this.refs.DefaultThemeColor).value.trim(); + config.TeamSettings.RestrictCreationToDomains = React.findDOMNode(this.refs.RestrictCreationToDomains).value.trim(); + config.TeamSettings.EnableTeamCreation = React.findDOMNode(this.refs.EnableTeamCreation).checked; + config.TeamSettings.EnableUserCreation = React.findDOMNode(this.refs.EnableUserCreation).checked; + + var MaxUsersPerTeam = 50; + if (!isNaN(parseInt(React.findDOMNode(this.refs.MaxUsersPerTeam).value, 10))) { + MaxUsersPerTeam = parseInt(React.findDOMNode(this.refs.MaxUsersPerTeam).value, 10); + } + config.TeamSettings.MaxUsersPerTeam = MaxUsersPerTeam; + React.findDOMNode(this.refs.MaxUsersPerTeam).value = MaxUsersPerTeam; + + 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() { + var serverError = ''; + if (this.state.serverError) { + serverError =
    ; + } + + var saveClass = 'btn'; + if (this.state.saveNeeded) { + saveClass = 'btn btn-primary'; + } + + return ( +
    + +

    {'Team Settings'}

    +
    + +
    + +
    + +

    {'Name of service shown in login screens and UI.'}

    +
    +
    + +
    + +
    + +

    {'Maximum number of users per team.'}

    +
    +
    + +
    + +
    + +

    {'Default theme color for team sites.'}

    +
    +
    + +
    + +
    + + +

    {'When false the ability to create teams is disabled. The create team button displays error when pressed.'}

    +
    +
    + +
    + +
    + + +

    {'When false the ability to create accounts is disabled. The create account button displays error when pressed.'}

    +
    +
    + +
    + +
    + +

    {'Teams can only be created from a specific domain (e.g. "mattermost.org") or list of comma-separated domains (e.g. "corp.mattermost.com, mattermost.org").'}

    +
    +
    + +
    +
    + {serverError} + +
    +
    + +
    +
    + ); + } +} + +TeamSettings.propTypes = { + config: React.PropTypes.object +}; -- cgit v1.2.3-1-g7c22 From 88e5a71e8c93b495cedaa07931a4f8052d9f12ed Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Tue, 22 Sep 2015 12:12:50 -0700 Subject: Adding service settings to admin console --- .../components/admin_console/admin_controller.jsx | 6 +- .../components/admin_console/admin_sidebar.jsx | 9 + .../components/admin_console/email_settings.jsx | 98 +++++++- .../components/admin_console/image_settings.jsx | 45 ++++ .../components/admin_console/log_settings.jsx | 24 +- .../components/admin_console/service_settings.jsx | 262 +++++++++++++++++++++ .../components/admin_console/sql_settings.jsx | 2 +- 7 files changed, 428 insertions(+), 18 deletions(-) create mode 100644 web/react/components/admin_console/service_settings.jsx (limited to 'web/react/components/admin_console') diff --git a/web/react/components/admin_console/admin_controller.jsx b/web/react/components/admin_console/admin_controller.jsx index 72b5d5c9d..ce7d61ca9 100644 --- a/web/react/components/admin_console/admin_controller.jsx +++ b/web/react/components/admin_console/admin_controller.jsx @@ -15,7 +15,7 @@ var RateSettingsTab = require('./rate_settings.jsx'); var GitLabSettingsTab = require('./gitlab_settings.jsx'); var SqlSettingsTab = require('./sql_settings.jsx'); var TeamSettingsTab = require('./team_settings.jsx'); - +var ServiceSettingsTab = require('./service_settings.jsx'); export default class AdminController extends React.Component { constructor(props) { @@ -26,7 +26,7 @@ export default class AdminController extends React.Component { this.state = { config: null, - selected: 'team_settings' + selected: 'service_settings' }; } @@ -72,6 +72,8 @@ export default class AdminController extends React.Component { tab = ; } else if (this.state.selected === 'team_settings') { tab = ; + } else if (this.state.selected === 'service_settings') { + tab = ; } } diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx index 2b7159e1d..0983c1276 100644 --- a/web/react/components/admin_console/admin_sidebar.jsx +++ b/web/react/components/admin_console/admin_sidebar.jsx @@ -38,6 +38,15 @@ export default class AdminSidebar extends React.Component {
    +
    + +
    + +

    {'32-character salt added to signing of email invites.'}

    +
    + +
    +
    +
    + +
    + +
    + +

    {'32-character salt added to signing of password reset emails.'}

    +
    + +
    +
    +
    +
    {serverError} diff --git a/web/react/components/admin_console/image_settings.jsx b/web/react/components/admin_console/image_settings.jsx index c0cbb5aa6..80da0a47f 100644 --- a/web/react/components/admin_console/image_settings.jsx +++ b/web/react/components/admin_console/image_settings.jsx @@ -3,6 +3,7 @@ var Client = require('../../utils/client.jsx'); var AsyncClient = require('../../utils/async_client.jsx'); +var crypto = require('crypto'); export default class ImageSettings extends React.Component { constructor(props) { @@ -10,6 +11,7 @@ export default class ImageSettings extends React.Component { this.handleChange = this.handleChange.bind(this); this.handleSubmit = this.handleSubmit.bind(this); + this.handleGenerate = this.handleGenerate.bind(this); this.state = { saveNeeded: false, @@ -28,6 +30,13 @@ export default class ImageSettings extends React.Component { this.setState(s); } + handleGenerate(e) { + e.preventDefault(); + React.findDOMNode(this.refs.PublicLinkSalt).value = crypto.randomBytes(256).toString('base64').substring(0, 31); + var s = {saveNeeded: true, serverError: this.state.serverError}; + this.setState(s); + } + handleSubmit(e) { e.preventDefault(); $('#save-button').button('loading'); @@ -41,6 +50,13 @@ export default class ImageSettings extends React.Component { config.ImageSettings.AmazonS3Region = React.findDOMNode(this.refs.AmazonS3Region).value; config.ImageSettings.EnablePublicLink = React.findDOMNode(this.refs.EnablePublicLink).checked; + config.ImageSettings.PublicLinkSalt = React.findDOMNode(this.refs.PublicLinkSalt).value.trim(); + + if (config.ImageSettings.PublicLinkSalt === '') { + config.ImageSettings.PublicLinkSalt = crypto.randomBytes(256).toString('base64').substring(0, 31); + React.findDOMNode(this.refs.PublicLinkSalt).value = config.ImageSettings.PublicLinkSalt; + } + var thumbnailWidth = 120; if (!isNaN(parseInt(React.findDOMNode(this.refs.ThumbnailWidth).value, 10))) { thumbnailWidth = parseInt(React.findDOMNode(this.refs.ThumbnailWidth).value, 10); @@ -424,6 +440,35 @@ export default class ImageSettings extends React.Component {
    +
    + +
    + +

    {'32-character salt added to signing of public image links.'}

    +
    + +
    +
    +
    +
    {serverError} diff --git a/web/react/components/admin_console/log_settings.jsx b/web/react/components/admin_console/log_settings.jsx index 2707ce6b6..d66801431 100644 --- a/web/react/components/admin_console/log_settings.jsx +++ b/web/react/components/admin_console/log_settings.jsx @@ -12,8 +12,8 @@ export default class LogSettings extends React.Component { this.handleSubmit = this.handleSubmit.bind(this); this.state = { - consoleEnable: this.props.config.LogSettings.ConsoleEnable, - fileEnable: this.props.config.LogSettings.FileEnable, + consoleEnable: this.props.config.LogSettings.EnableConsole, + fileEnable: this.props.config.LogSettings.EnableFile, saveNeeded: false, serverError: null }; @@ -46,9 +46,9 @@ export default class LogSettings extends React.Component { $('#save-button').button('loading'); var config = this.props.config; - config.LogSettings.ConsoleEnable = React.findDOMNode(this.refs.consoleEnable).checked; + config.LogSettings.EnableConsole = React.findDOMNode(this.refs.consoleEnable).checked; config.LogSettings.ConsoleLevel = React.findDOMNode(this.refs.consoleLevel).value; - config.LogSettings.FileEnable = React.findDOMNode(this.refs.fileEnable).checked; + config.LogSettings.EnableFile = React.findDOMNode(this.refs.fileEnable).checked; config.LogSettings.FileLevel = React.findDOMNode(this.refs.fileLevel).value; config.LogSettings.FileLocation = React.findDOMNode(this.refs.fileLocation).value.trim(); config.LogSettings.FileFormat = React.findDOMNode(this.refs.fileFormat).value.trim(); @@ -58,8 +58,8 @@ export default class LogSettings extends React.Component { () => { AsyncClient.getConfig(); this.setState({ - consoleEnable: config.LogSettings.ConsoleEnable, - fileEnable: config.LogSettings.FileEnable, + consoleEnable: config.LogSettings.EnableConsole, + fileEnable: config.LogSettings.EnableFile, serverError: null, saveNeeded: false }); @@ -67,8 +67,8 @@ export default class LogSettings extends React.Component { }, (err) => { this.setState({ - consoleEnable: config.LogSettings.ConsoleEnable, - fileEnable: config.LogSettings.FileEnable, + consoleEnable: config.LogSettings.EnableConsole, + fileEnable: config.LogSettings.EnableFile, serverError: err.message, saveNeeded: true }); @@ -110,7 +110,7 @@ export default class LogSettings extends React.Component { name='consoleEnable' value='true' ref='consoleEnable' - defaultChecked={this.props.config.LogSettings.ConsoleEnable} + defaultChecked={this.props.config.LogSettings.EnableConsole} onChange={this.handleChange.bind(this, 'console_true')} /> {'true'} @@ -120,7 +120,7 @@ export default class LogSettings extends React.Component { type='radio' name='consoleEnable' value='false' - defaultChecked={!this.props.config.LogSettings.ConsoleEnable} + defaultChecked={!this.props.config.LogSettings.EnableConsole} onChange={this.handleChange.bind(this, 'console_false')} /> {'false'} @@ -166,7 +166,7 @@ export default class LogSettings extends React.Component { name='fileEnable' ref='fileEnable' value='true' - defaultChecked={this.props.config.LogSettings.FileEnable} + defaultChecked={this.props.config.LogSettings.EnableFile} onChange={this.handleChange.bind(this, 'file_true')} /> {'true'} @@ -176,7 +176,7 @@ export default class LogSettings extends React.Component { type='radio' name='fileEnable' value='false' - defaultChecked={!this.props.config.LogSettings.FileEnable} + defaultChecked={!this.props.config.LogSettings.EnableFile} onChange={this.handleChange.bind(this, 'file_false')} /> {'false'} diff --git a/web/react/components/admin_console/service_settings.jsx b/web/react/components/admin_console/service_settings.jsx new file mode 100644 index 000000000..fcb8f800d --- /dev/null +++ b/web/react/components/admin_console/service_settings.jsx @@ -0,0 +1,262 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var Client = require('../../utils/client.jsx'); +var AsyncClient = require('../../utils/async_client.jsx'); + +export default class ServiceSettings extends React.Component { + constructor(props) { + super(props); + + this.handleChange = this.handleChange.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + + this.state = { + saveNeeded: false, + serverError: null + }; + } + + handleChange() { + var s = {saveNeeded: true, serverError: this.state.serverError}; + this.setState(s); + } + + handleSubmit(e) { + e.preventDefault(); + $('#save-button').button('loading'); + + var config = this.props.config; + config.ServiceSettings.ListenAddress = React.findDOMNode(this.refs.ListenAddress).value.trim(); + if (config.ServiceSettings.ListenAddress === '') { + config.ServiceSettings.ListenAddress = ':8065'; + React.findDOMNode(this.refs.ListenAddress).value = config.ServiceSettings.ListenAddress; + } + + config.ServiceSettings.SegmentDeveloperKey = React.findDOMNode(this.refs.SegmentDeveloperKey).value.trim(); + config.ServiceSettings.GoogleDeveloperKey = React.findDOMNode(this.refs.GoogleDeveloperKey).value.trim(); + config.ServiceSettings.EnableOAuthServiceProvider = React.findDOMNode(this.refs.EnableOAuthServiceProvider).checked; + config.ServiceSettings.EnableTesting = React.findDOMNode(this.refs.EnableTesting).checked; + + var MaximumLoginAttempts = 10; + if (!isNaN(parseInt(React.findDOMNode(this.refs.MaximumLoginAttempts).value, 10))) { + MaximumLoginAttempts = parseInt(React.findDOMNode(this.refs.MaximumLoginAttempts).value, 10); + } + config.ServiceSettings.MaximumLoginAttempts = MaximumLoginAttempts; + React.findDOMNode(this.refs.MaximumLoginAttempts).value = MaximumLoginAttempts; + + 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() { + var serverError = ''; + if (this.state.serverError) { + serverError =
    ; + } + + var saveClass = 'btn'; + if (this.state.saveNeeded) { + saveClass = 'btn btn-primary'; + } + + return ( +
    + +

    {'Service Settings'}

    +
    + +
    + +
    + +

    {'The address to bind to and listen. ":8065" will bind to all interfaces or you can choose one like "127.0.0.1:8065". Changing this will require a server restart before taking effect.'}

    +
    +
    + +
    + +
    + +

    {'Login attempts allowed before user is locked out and required to reset password via email.'}

    +
    +
    + +
    + +
    + +

    {'For users running a SaaS services, sign up for a key at Segment.com to track metrics.'}

    +
    +
    + +
    + +
    + +
    + + +

    {'When enabled Mattermost will act as an Oauth2 Provider. Changing this will require a server restart before taking effect.'}

    +
    +
    + +
    + +
    + + +

    {'When true slash commands like /loadtest are enabled in the add comment box. Changing this will require a server restart before taking effect. Typically used for development.'}

    +
    +
    + +
    +
    + {serverError} + +
    +
    + +
    +
    + ); + } +} + +ServiceSettings.propTypes = { + config: React.PropTypes.object +}; diff --git a/web/react/components/admin_console/sql_settings.jsx b/web/react/components/admin_console/sql_settings.jsx index 35810f7ee..cac017770 100644 --- a/web/react/components/admin_console/sql_settings.jsx +++ b/web/react/components/admin_console/sql_settings.jsx @@ -207,7 +207,7 @@ export default class SqlSettings extends React.Component { className='form-control' id='AtRestEncryptKey' ref='AtRestEncryptKey' - placeholder='Ex "10"' + placeholder='Ex "gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6"' defaultValue={this.props.config.SqlSettings.AtRestEncryptKey} onChange={this.handleChange} /> -- cgit v1.2.3-1-g7c22 From e22e7b8b7b66f342c2df693bbfc06a85980d253e Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Tue, 22 Sep 2015 12:31:01 -0700 Subject: Fixing bug in generating key length --- web/react/components/admin_console/email_settings.jsx | 8 ++++---- web/react/components/admin_console/image_settings.jsx | 4 ++-- web/react/components/admin_console/sql_settings.jsx | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) (limited to 'web/react/components/admin_console') diff --git a/web/react/components/admin_console/email_settings.jsx b/web/react/components/admin_console/email_settings.jsx index d94859cdd..854988947 100644 --- a/web/react/components/admin_console/email_settings.jsx +++ b/web/react/components/admin_console/email_settings.jsx @@ -55,13 +55,13 @@ export default class EmailSettings extends React.Component { config.EmailSettings.InviteSalt = React.findDOMNode(this.refs.InviteSalt).value.trim(); if (config.EmailSettings.InviteSalt === '') { - config.EmailSettings.InviteSalt = crypto.randomBytes(256).toString('base64').substring(0, 31); + config.EmailSettings.InviteSalt = crypto.randomBytes(256).toString('base64').substring(0, 32); React.findDOMNode(this.refs.InviteSalt).value = config.EmailSettings.InviteSalt; } config.EmailSettings.PasswordResetSalt = React.findDOMNode(this.refs.PasswordResetSalt).value.trim(); if (config.EmailSettings.PasswordResetSalt === '') { - config.EmailSettings.PasswordResetSalt = crypto.randomBytes(256).toString('base64').substring(0, 31); + config.EmailSettings.PasswordResetSalt = crypto.randomBytes(256).toString('base64').substring(0, 32); React.findDOMNode(this.refs.PasswordResetSalt).value = config.EmailSettings.PasswordResetSalt; } @@ -70,14 +70,14 @@ export default class EmailSettings extends React.Component { handleGenerateInvite(e) { e.preventDefault(); - React.findDOMNode(this.refs.InviteSalt).value = crypto.randomBytes(256).toString('base64').substring(0, 31); + React.findDOMNode(this.refs.InviteSalt).value = crypto.randomBytes(256).toString('base64').substring(0, 32); var s = {saveNeeded: true, serverError: this.state.serverError}; this.setState(s); } handleGenerateReset(e) { e.preventDefault(); - React.findDOMNode(this.refs.PasswordResetSalt).value = crypto.randomBytes(256).toString('base64').substring(0, 31); + React.findDOMNode(this.refs.PasswordResetSalt).value = crypto.randomBytes(256).toString('base64').substring(0, 32); var s = {saveNeeded: true, serverError: this.state.serverError}; this.setState(s); } diff --git a/web/react/components/admin_console/image_settings.jsx b/web/react/components/admin_console/image_settings.jsx index 80da0a47f..f84f1c735 100644 --- a/web/react/components/admin_console/image_settings.jsx +++ b/web/react/components/admin_console/image_settings.jsx @@ -32,7 +32,7 @@ export default class ImageSettings extends React.Component { handleGenerate(e) { e.preventDefault(); - React.findDOMNode(this.refs.PublicLinkSalt).value = crypto.randomBytes(256).toString('base64').substring(0, 31); + React.findDOMNode(this.refs.PublicLinkSalt).value = crypto.randomBytes(256).toString('base64').substring(0, 32); var s = {saveNeeded: true, serverError: this.state.serverError}; this.setState(s); } @@ -53,7 +53,7 @@ export default class ImageSettings extends React.Component { config.ImageSettings.PublicLinkSalt = React.findDOMNode(this.refs.PublicLinkSalt).value.trim(); if (config.ImageSettings.PublicLinkSalt === '') { - config.ImageSettings.PublicLinkSalt = crypto.randomBytes(256).toString('base64').substring(0, 31); + config.ImageSettings.PublicLinkSalt = crypto.randomBytes(256).toString('base64').substring(0, 32); React.findDOMNode(this.refs.PublicLinkSalt).value = config.ImageSettings.PublicLinkSalt; } diff --git a/web/react/components/admin_console/sql_settings.jsx b/web/react/components/admin_console/sql_settings.jsx index cac017770..ad01b5963 100644 --- a/web/react/components/admin_console/sql_settings.jsx +++ b/web/react/components/admin_console/sql_settings.jsx @@ -33,7 +33,7 @@ export default class SqlSettings extends React.Component { config.SqlSettings.AtRestEncryptKey = React.findDOMNode(this.refs.AtRestEncryptKey).value.trim(); if (config.SqlSettings.AtRestEncryptKey === '') { - config.SqlSettings.AtRestEncryptKey = crypto.randomBytes(256).toString('base64').substring(0, 31); + config.SqlSettings.AtRestEncryptKey = crypto.randomBytes(256).toString('base64').substring(0, 32); React.findDOMNode(this.refs.AtRestEncryptKey).value = config.SqlSettings.AtRestEncryptKey; } @@ -73,7 +73,7 @@ export default class SqlSettings extends React.Component { handleGenerate(e) { e.preventDefault(); - React.findDOMNode(this.refs.AtRestEncryptKey).value = crypto.randomBytes(256).toString('base64').substring(0, 31); + React.findDOMNode(this.refs.AtRestEncryptKey).value = crypto.randomBytes(256).toString('base64').substring(0, 32); var s = {saveNeeded: true, serverError: this.state.serverError}; this.setState(s); } -- cgit v1.2.3-1-g7c22 From 9e04909c0a3672d27c148c931d82b225cc86dfe5 Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Tue, 22 Sep 2015 13:28:03 -0700 Subject: Adding new EnableIncomingWebhooks property to service settings in the admin console. --- .../components/admin_console/service_settings.jsx | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+) (limited to 'web/react/components/admin_console') diff --git a/web/react/components/admin_console/service_settings.jsx b/web/react/components/admin_console/service_settings.jsx index fcb8f800d..1bb1f053b 100644 --- a/web/react/components/admin_console/service_settings.jsx +++ b/web/react/components/admin_console/service_settings.jsx @@ -36,6 +36,7 @@ export default class ServiceSettings extends React.Component { config.ServiceSettings.SegmentDeveloperKey = React.findDOMNode(this.refs.SegmentDeveloperKey).value.trim(); config.ServiceSettings.GoogleDeveloperKey = React.findDOMNode(this.refs.GoogleDeveloperKey).value.trim(); config.ServiceSettings.EnableOAuthServiceProvider = React.findDOMNode(this.refs.EnableOAuthServiceProvider).checked; + config.ServiceSettings.EnableIncomingWebhooks = React.findDOMNode(this.refs.EnableIncomingWebhooks).checked; config.ServiceSettings.EnableTesting = React.findDOMNode(this.refs.EnableTesting).checked; var MaximumLoginAttempts = 10; @@ -202,6 +203,39 @@ export default class ServiceSettings extends React.Component {
    +
    + +
    + + +

    {'When true incomming web hooks will be allowed.'}

    +
    +
    +