diff options
Diffstat (limited to 'web/react/components')
21 files changed, 2655 insertions, 267 deletions
diff --git a/web/react/components/admin_console/admin_controller.jsx b/web/react/components/admin_console/admin_controller.jsx index e82fe1b76..ce7d61ca9 100644 --- a/web/react/components/admin_console/admin_controller.jsx +++ b/web/react/components/admin_console/admin_controller.jsx @@ -9,6 +9,13 @@ 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'); +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'); +var ServiceSettingsTab = require('./service_settings.jsx'); export default class AdminController extends React.Component { constructor(props) { @@ -19,7 +26,7 @@ export default class AdminController extends React.Component { this.state = { config: null, - selected: 'email_settings' + selected: 'service_settings' }; } @@ -53,6 +60,20 @@ export default class AdminController extends React.Component { tab = <LogSettingsTab config={this.state.config} />; } else if (this.state.selected === 'logs') { tab = <LogsTab />; + } else if (this.state.selected === 'image_settings') { + tab = <ImageSettingsTab config={this.state.config} />; + } else if (this.state.selected === 'privacy_settings') { + tab = <PrivacySettingsTab config={this.state.config} />; + } else if (this.state.selected === 'rate_settings') { + tab = <RateSettingsTab config={this.state.config} />; + } else if (this.state.selected === 'gitlab_settings') { + tab = <GitLabSettingsTab config={this.state.config} />; + } else if (this.state.selected === 'sql_settings') { + tab = <SqlSettingsTab config={this.state.config} />; + } else if (this.state.selected === 'team_settings') { + tab = <TeamSettingsTab config={this.state.config} />; + } else if (this.state.selected === 'service_settings') { + tab = <ServiceSettingsTab config={this.state.config} />; } } diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx index a6e689490..0983c1276 100644 --- a/web/react/components/admin_console/admin_sidebar.jsx +++ b/web/react/components/admin_console/admin_sidebar.jsx @@ -41,6 +41,33 @@ export default class AdminSidebar extends React.Component { <li> <a href='#' + className={this.isSelected('service_settings')} + onClick={this.handleClick.bind(this, 'service_settings')} + > + {'Service Settings'} + </a> + </li> + <li> + <a + href='#' + className={this.isSelected('team_settings')} + onClick={this.handleClick.bind(this, 'team_settings')} + > + {'Team Settings'} + </a> + </li> + <li> + <a + href='#' + className={this.isSelected('sql_settings')} + onClick={this.handleClick.bind(this, 'sql_settings')} + > + {'SQL Settings'} + </a> + </li> + <li> + <a + href='#' className={this.isSelected('email_settings')} onClick={this.handleClick.bind(this, 'email_settings')} > @@ -50,6 +77,15 @@ export default class AdminSidebar extends React.Component { <li> <a href='#' + className={this.isSelected('image_settings')} + onClick={this.handleClick.bind(this, 'image_settings')} + > + {'Image Settings'} + </a> + </li> + <li> + <a + href='#' className={this.isSelected('log_settings')} onClick={this.handleClick.bind(this, 'log_settings')} > @@ -65,6 +101,33 @@ export default class AdminSidebar extends React.Component { {'Logs'} </a> </li> + <li> + <a + href='#' + className={this.isSelected('rate_settings')} + onClick={this.handleClick.bind(this, 'rate_settings')} + > + {'Rate Limit Settings'} + </a> + </li> + <li> + <a + href='#' + className={this.isSelected('privacy_settings')} + onClick={this.handleClick.bind(this, 'privacy_settings')} + > + {'Privacy Settings'} + </a> + </li> + <li> + <a + href='#' + className={this.isSelected('gitlab_settings')} + onClick={this.handleClick.bind(this, 'gitlab_settings')} + > + {'GitLab Settings'} + </a> + </li> </ul> </li> </ul> diff --git a/web/react/components/admin_console/email_settings.jsx b/web/react/components/admin_console/email_settings.jsx index e8fb25858..854988947 100644 --- a/web/react/components/admin_console/email_settings.jsx +++ b/web/react/components/admin_console/email_settings.jsx @@ -1,15 +1,179 @@ // 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 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.handleGenerateInvite = this.handleGenerateInvite.bind(this); + this.handleGenerateReset = this.handleGenerateReset.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.EnableSignUpWithEmail = 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(); + + config.EmailSettings.InviteSalt = React.findDOMNode(this.refs.InviteSalt).value.trim(); + if (config.EmailSettings.InviteSalt === '') { + 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, 32); + React.findDOMNode(this.refs.PasswordResetSalt).value = config.EmailSettings.PasswordResetSalt; + } + + return config; + } + + handleGenerateInvite(e) { + e.preventDefault(); + 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, 32); + var s = {saveNeeded: true, serverError: this.state.serverError}; + this.setState(s); + } + + 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 + }); + $('#connection-button').button('reset'); + }, + (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 = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>; + } + + var saveClass = 'btn'; + if (this.state.saveNeeded) { + saveClass = 'btn btn-primary'; + } + + var emailSuccess = ''; + if (this.state.emailSuccess) { + emailSuccess = ( + <div className='alert alert-success'> + <i className='fa fa-check'></i>{'No errors were reported while sending an email. Please check your inbox to make sure.'} + </div> + ); + } + + var emailFail = ''; + if (this.state.emailFail) { + emailSuccess = ( + <div className='alert alert-warning'> + <i className='fa fa-warning'></i>{'Connection unsuccessful: ' + this.state.emailFail} + </div> + ); + } + return ( <div className='wrapper--fixed'> <h3>{'Email Settings'}</h3> @@ -17,295 +181,370 @@ export default class EmailSettings extends React.Component { className='form-horizontal' role='form' > + <div className='form-group'> <label className='control-label col-sm-4' - htmlFor='email' + htmlFor='allowSignUpWithEmail' > - {'Bypass Email: '} - <a - href='#' - data-trigger='hover click' - data-toggle='popover' - data-position='bottom' - data-content={'Here\'s some more help text inside a popover for the Bypass Email field just to show how popovers look.'} - > - {'(?)'} - </a> + {'Allow Sign Up With Email: '} </label> <div className='col-sm-8'> <label className='radio-inline'> <input type='radio' - name='byPassEmail' - value='option1' + name='allowSignUpWithEmail' + value='true' + ref='allowSignUpWithEmail' + defaultChecked={this.props.config.EmailSettings.EnableSignUpWithEmail} + onChange={this.handleChange.bind(this, 'allowSignUpWithEmail_true')} /> - {'True'} + {'true'} </label> <label className='radio-inline'> <input type='radio' - name='byPassEmail' - value='option2' + name='allowSignUpWithEmail' + value='false' + defaultChecked={!this.props.config.EmailSettings.EnableSignUpWithEmail} + onChange={this.handleChange.bind(this, 'allowSignUpWithEmail_false')} /> - {'False'} + {'false'} </label> - <p className='help-text'>{'This is some sample help text for the Bypass Email field'}</p> + <p className='help-text'>{'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.'}</p> </div> </div> + <div className='form-group'> <label className='control-label col-sm-4' - htmlFor='smtpUsername' + htmlFor='sendEmailNotifications' > - {'SMTP Username:'} + {'Send Email Notifications: '} </label> <div className='col-sm-8'> - <input - type='email' - className='form-control' - id='smtpUsername' - placeholder='Enter your SMTP username' - value='' - /> - <div className='help-text'> - <div className='alert alert-warning'><i className='fa fa-warning'></i>{' This is some error text for the Bypass Email field'}</div> - </div> - <p className='help-text'>{'This is some sample help text for the SMTP username field'}</p> + <label className='radio-inline'> + <input + type='radio' + name='sendEmailNotifications' + value='true' + ref='sendEmailNotifications' + defaultChecked={this.props.config.EmailSettings.SendEmailNotifications} + onChange={this.handleChange.bind(this, 'sendEmailNotifications_true')} + /> + {'true'} + </label> + <label className='radio-inline'> + <input + type='radio' + name='sendEmailNotifications' + value='false' + defaultChecked={!this.props.config.EmailSettings.SendEmailNotifications} + onChange={this.handleChange.bind(this, 'sendEmailNotifications_false')} + /> + {'false'} + </label> + <p className='help-text'>{'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.'}</p> </div> </div> + <div className='form-group'> <label className='control-label col-sm-4' - htmlFor='smtpPassword' + htmlFor='requireEmailVerification' > - {'SMTP Password:'} + {'Require Email Verification: '} + </label> + <div className='col-sm-8'> + <label className='radio-inline'> + <input + type='radio' + name='requireEmailVerification' + value='true' + ref='requireEmailVerification' + defaultChecked={this.props.config.EmailSettings.RequireEmailVerification} + onChange={this.handleChange.bind(this, 'requireEmailVerification_true')} + disabled={!this.state.sendEmailNotifications} + /> + {'true'} + </label> + <label className='radio-inline'> + <input + type='radio' + name='requireEmailVerification' + value='false' + defaultChecked={!this.props.config.EmailSettings.RequireEmailVerification} + onChange={this.handleChange.bind(this, 'requireEmailVerification_false')} + disabled={!this.state.sendEmailNotifications} + /> + {'false'} + </label> + <p className='help-text'>{'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.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='feedbackName' + > + {'Feedback Name:'} </label> <div className='col-sm-8'> <input - type='password' + type='text' className='form-control' - id='smtpPassword' - placeholder='Enter your SMTP password' - value='' + id='feedbackName' + ref='feedbackName' + placeholder='Ex: "Mattermost", "System", "John Smith"' + defaultValue={this.props.config.EmailSettings.FeedbackName} + onChange={this.handleChange} + disabled={!this.state.sendEmailNotifications} /> + <p className='help-text'>{'Name displayed on email account used when sending notification emails from Mattermost.'}</p> </div> </div> + <div className='form-group'> <label className='control-label col-sm-4' - htmlFor='smtpServer' + htmlFor='feedbackEmail' > - {'SMTP Server:'} + {'Feedback Email:'} </label> <div className='col-sm-8'> <input - type='text' + type='email' className='form-control' - id='smtpServer' - placeholder='Enter your SMTP server' - value='' + id='feedbackEmail' + ref='feedbackEmail' + placeholder='Ex: "mattermost@yourcompany.com", "admin@yourcompany.com"' + defaultValue={this.props.config.EmailSettings.FeedbackEmail} + onChange={this.handleChange} + disabled={!this.state.sendEmailNotifications} /> - <div className='help-text'> - <a - href='#' - className='help-link' - > - {'Test Connection'} - </a> - <div className='alert alert-success'><i className='fa fa-check'></i>{' Connection successful'}</div> - <div className='alert alert-warning hide'><i className='fa fa-warning'></i>{' Connection unsuccessful'}</div> - </div> + <p className='help-text'>{'Email displayed on email account used when sending notification emails from Mattermost.'}</p> </div> </div> + <div className='form-group'> - <label className='control-label col-sm-4'>{'Use TLS:'}</label> + <label + className='control-label col-sm-4' + htmlFor='SMTPUsername' + > + {'SMTP Username:'} + </label> <div className='col-sm-8'> - <label className='radio-inline'> - <input - type='radio' - name='tls' - value='option1' - /> - {'True'} - </label> - <label className='radio-inline'> - <input - type='radio' - name='tls' - value='option2' - /> - {'False'} - </label> + <input + type='text' + className='form-control' + id='SMTPUsername' + ref='SMTPUsername' + placeholder='Ex: "admin@yourcompany.com", "AKIADTOVBGERKLCBV"' + defaultValue={this.props.config.EmailSettings.SMTPUsername} + onChange={this.handleChange} + disabled={!this.state.sendEmailNotifications} + /> + <p className='help-text'>{' Obtain this credential from administrator setting up your email server.'}</p> </div> </div> + <div className='form-group'> - <label className='control-label col-sm-4'>{'Use Start TLS:'}</label> + <label + className='control-label col-sm-4' + htmlFor='SMTPPassword' + > + {'SMTP Password:'} + </label> <div className='col-sm-8'> - <label className='radio-inline'> - <input - type='radio' - name='starttls' - value='option1' - /> - {'True'} - </label> - <label className='radio-inline'> - <input - type='radio' - name='starttls' - value='option2' - /> - {'False'} - </label> + <input + type='text' + className='form-control' + id='SMTPPassword' + ref='SMTPPassword' + placeholder='Ex: "yourpassword", "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"' + defaultValue={this.props.config.EmailSettings.SMTPPassword} + onChange={this.handleChange} + disabled={!this.state.sendEmailNotifications} + /> + <p className='help-text'>{' Obtain this credential from administrator setting up your email server.'}</p> </div> </div> + <div className='form-group'> <label className='control-label col-sm-4' - htmlFor='feedbackEmail' + htmlFor='SMTPServer' > - {'Feedback Email:'} + {'SMTP Server:'} </label> <div className='col-sm-8'> <input type='text' className='form-control' - id='feedbackEmail' - placeholder='Enter your feedback email' - value='' + id='SMTPServer' + ref='SMTPServer' + placeholder='Ex: "smtp.yourcompany.com", "email-smtp.us-east-1.amazonaws.com"' + defaultValue={this.props.config.EmailSettings.SMTPServer} + onChange={this.handleChange} + disabled={!this.state.sendEmailNotifications} /> + <p className='help-text'>{'Location of SMTP email server.'}</p> </div> </div> + <div className='form-group'> <label className='control-label col-sm-4' - htmlFor='feedbackUsername' + htmlFor='SMTPPort' > - {'Feedback Username:'} + {'SMTP Port:'} </label> <div className='col-sm-8'> <input type='text' className='form-control' - id='feedbackUsername' - placeholder='Enter your feedback username' - value='' + id='SMTPPort' + ref='SMTPPort' + placeholder='Ex: "25", "465"' + defaultValue={this.props.config.EmailSettings.SMTPPort} + onChange={this.handleChange} + disabled={!this.state.sendEmailNotifications} /> + <p className='help-text'>{'Port of SMTP email server.'}</p> </div> </div> + <div className='form-group'> - <div className='col-sm-offset-4 col-sm-8'> - <div className='checkbox'> - <label><input type='checkbox' />{'Remember me'}</label> + <label + className='control-label col-sm-4' + htmlFor='ConnectionSecurity' + > + {'Connection Security:'} + </label> + <div className='col-sm-8'> + <select + className='form-control' + id='ConnectionSecurity' + ref='ConnectionSecurity' + defaultValue={this.props.config.EmailSettings.ConnectionSecurity} + onChange={this.handleChange} + disabled={!this.state.sendEmailNotifications} + > + <option value=''>{'None'}</option> + <option value='TLS'>{'TLS (Recommended)'}</option> + <option value='STARTTLS'>{'STARTTLS'}</option> + </select> + <div className='help-text'> + <table + className='table-bordered' + cellPadding='5' + > + <tr><td className='help-text'>{'None'}</td><td className='help-text'>{'Mattermost will send email over an unsecure connection.'}</td></tr> + <tr><td className='help-text'>{'TLS'}</td><td className='help-text'>{'Encrypts the communication between Mattermost and your email server.'}</td></tr> + <tr><td className='help-text'>{'STARTTLS'}</td><td className='help-text'>{'Takes an existing insecure connection and attempts to upgrade it to a secure connection using TLS.'}</td></tr> + </table> + </div> + <div className='help-text'> + <button + className='help-link' + onClick={this.handleTestConnection} + disabled={!this.state.sendEmailNotifications} + id='connection-button' + data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Testing...'} + > + {'Test Connection'} + </button> + {emailSuccess} + {emailFail} </div> </div> </div> - <div - className='panel-group' - id='accordion' - role='tablist' - aria-multiselectable='true' - > - <div className='panel panel-default'> - <div - className='panel-heading' - role='tab' - id='headingOne' - > - <h3 className='panel-title'> - <a - className='collapsed' - role='button' - data-toggle='collapse' - data-parent='#accordion' - href='#collapseOne' - aria-expanded='true' - aria-controls='collapseOne' - > - {'Advanced Settings '} - <i className='fa fa-plus'></i> - <i className='fa fa-minus'></i> - </a> - </h3> + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='InviteSalt' + > + {'Invite Salt:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='InviteSalt' + ref='InviteSalt' + placeholder='Ex "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo"' + defaultValue={this.props.config.EmailSettings.InviteSalt} + onChange={this.handleChange} + disabled={!this.state.sendEmailNotifications} + /> + <p className='help-text'>{'32-character salt added to signing of email invites.'}</p> + <div className='help-text'> + <button + className='help-link' + onClick={this.handleGenerateInvite} + disabled={!this.state.sendEmailNotifications} + > + {'Re-Generate'} + </button> </div> - <div - id='collapseOne' - className='panel-collapse collapse' - role='tabpanel' - aria-labelledby='headingOne' - > - <div className='panel-body'> - <div className='form-group'> - <label - className='control-label col-sm-4' - htmlFor='feedbackUsername' - > - {'Apple push server:'} - </label> - <div className='col-sm-8'> - <input - type='text' - className='form-control' - id='feedbackUsername' - placeholder='Enter your Apple push server' - value='' - /> - <p className='help-text'>{'This is some sample help text for the Apple push server field'}</p> - </div> - </div> - <div className='form-group'> - <label - className='control-label col-sm-4' - htmlFor='feedbackUsername' - > - {'Apple push certificate public:'} - </label> - <div className='col-sm-8'> - <input - type='text' - className='form-control' - id='feedbackUsername' - placeholder='Enter your public apple push certificate' - value='' - /> - </div> - </div> - <div className='form-group'> - <label - className='control-label col-sm-4' - htmlFor='feedbackUsername' - > - {'Apple push certificate private:'} - </label> - <div className='col-sm-8'> - <input - type='text' - className='form-control' - id='feedbackUsername' - placeholder='Enter your private apple push certificate' - value='' - /> - </div> - </div> - </div> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='PasswordResetSalt' + > + {'Password Reset Salt:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='PasswordResetSalt' + ref='PasswordResetSalt' + placeholder='Ex "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo"' + defaultValue={this.props.config.EmailSettings.PasswordResetSalt} + onChange={this.handleChange} + disabled={!this.state.sendEmailNotifications} + /> + <p className='help-text'>{'32-character salt added to signing of password reset emails.'}</p> + <div className='help-text'> + <button + className='help-link' + onClick={this.handleGenerateReset} + disabled={!this.state.sendEmailNotifications} + > + {'Re-Generate'} + </button> </div> </div> </div> <div className='form-group'> <div className='col-sm-12'> + {serverError} <button + disabled={!this.state.saveNeeded} type='submit' - className='btn btn-primary' + className={saveClass} + onClick={this.handleSubmit} + id='save-button' + data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Saving Config...'} > {'Save'} </button> </div> </div> + </form> </div> ); } -}
\ 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/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 = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>; + } + + var saveClass = 'btn'; + if (this.state.saveNeeded) { + saveClass = 'btn btn-primary'; + } + + return ( + <div className='wrapper--fixed'> + + <h3>{'GitLab Settings'}</h3> + <form + className='form-horizontal' + role='form' + > + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='Allow' + > + {'Enable Sign Up With GitLab: '} + </label> + <div className='col-sm-8'> + <label className='radio-inline'> + <input + type='radio' + name='Allow' + value='true' + ref='Allow' + defaultChecked={this.props.config.GitLabSettings.Allow} + onChange={this.handleChange.bind(this, 'AllowTrue')} + /> + {'true'} + </label> + <label className='radio-inline'> + <input + type='radio' + name='Allow' + value='false' + defaultChecked={!this.props.config.GitLabSettings.Allow} + onChange={this.handleChange.bind(this, 'AllowFalse')} + /> + {'false'} + </label> + <p className='help-text'>{'When true Mattermost will allow team creation and account signup utilizing GitLab OAuth.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='Secret' + > + {'Secret:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='Secret' + ref='Secret' + placeholder='Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"' + defaultValue={this.props.config.GitLabSettings.Secret} + onChange={this.handleChange} + disabled={!this.state.Allow} + /> + <p className='help-text'>{'Need help text.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='Id' + > + {'Id:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='Id' + ref='Id' + placeholder='Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"' + defaultValue={this.props.config.GitLabSettings.Id} + onChange={this.handleChange} + disabled={!this.state.Allow} + /> + <p className='help-text'>{'Need help text.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='Scope' + > + {'Scope:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='Scope' + ref='Scope' + placeholder='Ex ""' + defaultValue={this.props.config.GitLabSettings.Scope} + onChange={this.handleChange} + disabled={!this.state.Allow} + /> + <p className='help-text'>{'Need help text.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='AuthEndpoint' + > + {'Auth Endpoint:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='AuthEndpoint' + ref='AuthEndpoint' + placeholder='Ex ""' + defaultValue={this.props.config.GitLabSettings.AuthEndpoint} + onChange={this.handleChange} + disabled={!this.state.Allow} + /> + <p className='help-text'>{'Need help text.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='TokenEndpoint' + > + {'Token Endpoint:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='TokenEndpoint' + ref='TokenEndpoint' + placeholder='Ex ""' + defaultValue={this.props.config.GitLabSettings.TokenEndpoint} + onChange={this.handleChange} + disabled={!this.state.Allow} + /> + <p className='help-text'>{'Need help text.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='UserApiEndpoint' + > + {'User API Endpoint:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='UserApiEndpoint' + ref='UserApiEndpoint' + placeholder='Ex ""' + defaultValue={this.props.config.GitLabSettings.UserApiEndpoint} + onChange={this.handleChange} + disabled={!this.state.Allow} + /> + <p className='help-text'>{'Need help text.'}</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> Saving Config...'} + > + {'Save'} + </button> + </div> + </div> + + </form> + </div> + ); + } +} + +GitLabSettings.propTypes = { + config: React.PropTypes.object +}; 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..f84f1c735 --- /dev/null +++ b/web/react/components/admin_console/image_settings.jsx @@ -0,0 +1,496 @@ +// 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 ImageSettings 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, + 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); + } + + handleGenerate(e) { + e.preventDefault(); + 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); + } + + 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; + 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, 32); + 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); + } + 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 = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>; + } + + 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 ( + <div className='wrapper--fixed'> + <h3>{'Image Settings'}</h3> + <form + className='form-horizontal' + role='form' + > + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='DriverName' + > + {'Store Files In:'} + </label> + <div className='col-sm-8'> + <select + className='form-control' + id='DriverName' + ref='DriverName' + defaultValue={this.props.config.ImageSettings.DriverName} + onChange={this.handleChange.bind(this, 'DriverName')} + > + <option value=''>{'Disable File Storage'}</option> + <option value='local'>{'Local File System'}</option> + <option value='amazons3'>{'Amazon S3'}</option> + </select> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='Directory' + > + {'Local Directory Location:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='Directory' + ref='Directory' + placeholder='Ex "./data/"' + defaultValue={this.props.config.ImageSettings.Directory} + onChange={this.handleChange} + disabled={!enableFile} + /> + <p className='help-text'>{'Directory to which image files are written. If blank, will be set to ./data/.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='AmazonS3AccessKeyId' + > + {'Amazon S3 Access Key Id:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='AmazonS3AccessKeyId' + ref='AmazonS3AccessKeyId' + placeholder='Ex "AKIADTOVBGERKLCBV"' + defaultValue={this.props.config.ImageSettings.AmazonS3AccessKeyId} + onChange={this.handleChange} + disabled={!enableS3} + /> + <p className='help-text'>{'Obtain this credential from your Amazon EC2 administrator.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='AmazonS3SecretAccessKey' + > + {'Amazon S3 Secret Access Key:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='AmazonS3SecretAccessKey' + ref='AmazonS3SecretAccessKey' + placeholder='Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"' + defaultValue={this.props.config.ImageSettings.AmazonS3SecretAccessKey} + onChange={this.handleChange} + disabled={!enableS3} + /> + <p className='help-text'>{'Obtain this credential from your Amazon EC2 administrator.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='AmazonS3Bucket' + > + {'Amazon S3 Bucket:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='AmazonS3Bucket' + ref='AmazonS3Bucket' + placeholder='Ex "mattermost-media"' + defaultValue={this.props.config.ImageSettings.AmazonS3Bucket} + onChange={this.handleChange} + disabled={!enableS3} + /> + <p className='help-text'>{'Name you selected for your S3 bucket in AWS.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='AmazonS3Region' + > + {'Amazon S3 Region:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='AmazonS3Region' + ref='AmazonS3Region' + placeholder='Ex "us-east-1"' + defaultValue={this.props.config.ImageSettings.AmazonS3Region} + onChange={this.handleChange} + disabled={!enableS3} + /> + <p className='help-text'>{'AWS region you selected for creating your S3 bucket.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='ThumbnailWidth' + > + {'Thumbnail Width:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='ThumbnailWidth' + ref='ThumbnailWidth' + placeholder='Ex "120"' + defaultValue={this.props.config.ImageSettings.ThumbnailWidth} + onChange={this.handleChange} + /> + <p className='help-text'>{'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.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='ThumbnailHeight' + > + {'Thumbnail Height:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='ThumbnailHeight' + ref='ThumbnailHeight' + placeholder='Ex "100"' + defaultValue={this.props.config.ImageSettings.ThumbnailHeight} + onChange={this.handleChange} + /> + <p className='help-text'>{'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.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='PreviewWidth' + > + {'Preview Width:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='PreviewWidth' + ref='PreviewWidth' + placeholder='Ex "1024"' + defaultValue={this.props.config.ImageSettings.PreviewWidth} + onChange={this.handleChange} + /> + <p className='help-text'>{'Maximum width of preview image. Updating this value changes how preview images render in future, but does not change images created in the past.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='PreviewHeight' + > + {'Preview Height:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='PreviewHeight' + ref='PreviewHeight' + placeholder='Ex "0"' + defaultValue={this.props.config.ImageSettings.PreviewHeight} + onChange={this.handleChange} + /> + <p className='help-text'>{'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.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='ProfileWidth' + > + {'Profile Width:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='ProfileWidth' + ref='ProfileWidth' + placeholder='Ex "1024"' + defaultValue={this.props.config.ImageSettings.ProfileWidth} + onChange={this.handleChange} + /> + <p className='help-text'>{'Width of profile picture.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='ProfileHeight' + > + {'Profile Height:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='ProfileHeight' + ref='ProfileHeight' + placeholder='Ex "0"' + defaultValue={this.props.config.ImageSettings.ProfileHeight} + onChange={this.handleChange} + /> + <p className='help-text'>{'Height of profile picture.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='EnablePublicLink' + > + {'Share Public File Link: '} + </label> + <div className='col-sm-8'> + <label className='radio-inline'> + <input + type='radio' + name='EnablePublicLink' + value='true' + ref='EnablePublicLink' + defaultChecked={this.props.config.ImageSettings.EnablePublicLink} + onChange={this.handleChange} + /> + {'true'} + </label> + <label className='radio-inline'> + <input + type='radio' + name='EnablePublicLink' + value='false' + defaultChecked={!this.props.config.ImageSettings.EnablePublicLink} + onChange={this.handleChange} + /> + {'false'} + </label> + <p className='help-text'>{'Allow users to share public links to files and images.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='PublicLinkSalt' + > + {'Public Link Salt:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='PublicLinkSalt' + ref='PublicLinkSalt' + placeholder='Ex "gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6"' + defaultValue={this.props.config.ImageSettings.PublicLinkSalt} + onChange={this.handleChange} + /> + <p className='help-text'>{'32-character salt added to signing of public image links.'}</p> + <div className='help-text'> + <button + className='help-link' + onClick={this.handleGenerate} + > + {'Re-Generate'} + </button> + </div> + </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> Saving Config...'} + > + {'Save'} + </button> + </div> + </div> + + </form> + </div> + ); + } +} + +ImageSettings.propTypes = { + config: React.PropTypes.object +}; diff --git a/web/react/components/admin_console/log_settings.jsx b/web/react/components/admin_console/log_settings.jsx index 4e3db8f68..d66801431 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.EnableConsole, + fileEnable: this.props.config.LogSettings.EnableFile, 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) { @@ -26,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(); @@ -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.EnableConsole, + fileEnable: config.LogSettings.EnableFile, + serverError: null, + saveNeeded: false + }); $('#save-button').button('reset'); }, (err) => { - this.setState({serverError: err.message, saveNeeded: true}); + this.setState({ + consoleEnable: config.LogSettings.EnableConsole, + fileEnable: config.LogSettings.EnableFile, + serverError: err.message, + saveNeeded: true + }); $('#save-button').button('reset'); } ); @@ -80,8 +110,8 @@ export default class LogSettings extends React.Component { name='consoleEnable' value='true' ref='consoleEnable' - defaultChecked={this.props.config.LogSettings.ConsoleEnable} - onChange={this.handleChange} + defaultChecked={this.props.config.LogSettings.EnableConsole} + onChange={this.handleChange.bind(this, 'console_true')} /> {'true'} </label> @@ -90,8 +120,8 @@ export default class LogSettings extends React.Component { type='radio' name='consoleEnable' value='false' - defaultChecked={!this.props.config.LogSettings.ConsoleEnable} - onChange={this.handleChange} + defaultChecked={!this.props.config.LogSettings.EnableConsole} + onChange={this.handleChange.bind(this, 'console_false')} /> {'false'} </label> @@ -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} > <option value='DEBUG'>{'DEBUG'}</option> <option value='INFO'>{'INFO'}</option> @@ -135,8 +166,8 @@ export default class LogSettings extends React.Component { name='fileEnable' ref='fileEnable' value='true' - defaultChecked={this.props.config.LogSettings.FileEnable} - onChange={this.handleChange} + defaultChecked={this.props.config.LogSettings.EnableFile} + onChange={this.handleChange.bind(this, 'file_true')} /> {'true'} </label> @@ -145,8 +176,8 @@ export default class LogSettings extends React.Component { type='radio' name='fileEnable' value='false' - defaultChecked={!this.props.config.LogSettings.FileEnable} - onChange={this.handleChange} + defaultChecked={!this.props.config.LogSettings.EnableFile} + onChange={this.handleChange.bind(this, 'file_false')} /> {'false'} </label> @@ -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} > <option value='DEBUG'>{'DEBUG'}</option> <option value='INFO'>{'INFO'}</option> @@ -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} /> <p className='help-text'>{'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.'}</p> </div> @@ -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} /> <p className='help-text'> {'Format of log message output. If blank will be set to "[%D %T] [%L] %M", where:'} 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 = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>; + } + + var saveClass = 'btn'; + if (this.state.saveNeeded) { + saveClass = 'btn btn-primary'; + } + + return ( + <div className='wrapper--fixed'> + <h3>{'Privacy Settings'}</h3> + <form + className='form-horizontal' + role='form' + > + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='ShowEmailAddress' + > + {'Show Email Address: '} + </label> + <div className='col-sm-8'> + <label className='radio-inline'> + <input + type='radio' + name='ShowEmailAddress' + value='true' + ref='ShowEmailAddress' + defaultChecked={this.props.config.PrivacySettings.ShowEmailAddress} + onChange={this.handleChange} + /> + {'true'} + </label> + <label className='radio-inline'> + <input + type='radio' + name='ShowEmailAddress' + value='false' + defaultChecked={!this.props.config.PrivacySettings.ShowEmailAddress} + onChange={this.handleChange} + /> + {'false'} + </label> + <p className='help-text'>{'Hides email address of users from other users including team administrator.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='ShowFullName' + > + {'Show Full Name: '} + </label> + <div className='col-sm-8'> + <label className='radio-inline'> + <input + type='radio' + name='ShowFullName' + value='true' + ref='ShowFullName' + defaultChecked={this.props.config.PrivacySettings.ShowFullName} + onChange={this.handleChange} + /> + {'true'} + </label> + <label className='radio-inline'> + <input + type='radio' + name='ShowFullName' + value='false' + defaultChecked={!this.props.config.PrivacySettings.ShowFullName} + onChange={this.handleChange} + /> + {'false'} + </label> + <p className='help-text'>{'Hides full name of users from other users including team administrator.'}</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> Saving Config...'} + > + {'Save'} + </button> + </div> + </div> + + </form> + </div> + ); + } +} + +PrivacySettings.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 new file mode 100644 index 000000000..c05bf4a82 --- /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 = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>; + } + + var saveClass = 'btn'; + if (this.state.saveNeeded) { + saveClass = 'btn btn-primary'; + } + + return ( + <div className='wrapper--fixed'> + + <div className='banner'> + <div className='banner__content'> + <h4 className='banner__heading'>{'Note:'}</h4> + <p>{'Changing properties in this section will require a server restart before taking effect.'}</p> + </div> + </div> + + <h3>{'Rate Limit Settings'}</h3> + <form + className='form-horizontal' + role='form' + > + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='EnableRateLimiter' + > + {'Enable Rate Limiter: '} + </label> + <div className='col-sm-8'> + <label className='radio-inline'> + <input + type='radio' + name='EnableRateLimiter' + value='true' + ref='EnableRateLimiter' + defaultChecked={this.props.config.RateLimitSettings.EnableRateLimiter} + onChange={this.handleChange.bind(this, 'EnableRateLimiterTrue')} + /> + {'true'} + </label> + <label className='radio-inline'> + <input + type='radio' + name='EnableRateLimiter' + value='false' + defaultChecked={!this.props.config.RateLimitSettings.EnableRateLimiter} + onChange={this.handleChange.bind(this, 'EnableRateLimiterFalse')} + /> + {'false'} + </label> + <p className='help-text'>{'When enabled throttles rate at which APIs respond.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='PerSec' + > + {'Number Of Queries Per Second:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='PerSec' + ref='PerSec' + placeholder='Ex "10"' + defaultValue={this.props.config.RateLimitSettings.PerSec} + onChange={this.handleChange} + disabled={!this.state.EnableRateLimiter} + /> + <p className='help-text'>{'Throttles API at this number of requests per second.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='MemoryStoreSize' + > + {'Memory Store Size:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='MemoryStoreSize' + ref='MemoryStoreSize' + placeholder='Ex "10000"' + defaultValue={this.props.config.RateLimitSettings.MemoryStoreSize} + onChange={this.handleChange} + disabled={!this.state.EnableRateLimiter} + /> + <p className='help-text'>{'Maximum number of users sessions connected to the system as determined by VaryByRemoteAddr and VaryByHeader variables.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='VaryByRemoteAddr' + > + {'Limit By Remote Address: '} + </label> + <div className='col-sm-8'> + <label className='radio-inline'> + <input + type='radio' + name='VaryByRemoteAddr' + value='true' + ref='VaryByRemoteAddr' + defaultChecked={this.props.config.RateLimitSettings.VaryByRemoteAddr} + onChange={this.handleChange.bind(this, 'VaryByRemoteAddrTrue')} + disabled={!this.state.EnableRateLimiter} + /> + {'true'} + </label> + <label className='radio-inline'> + <input + type='radio' + name='VaryByRemoteAddr' + value='false' + defaultChecked={!this.props.config.RateLimitSettings.VaryByRemoteAddr} + onChange={this.handleChange.bind(this, 'VaryByRemoteAddrFalse')} + disabled={!this.state.EnableRateLimiter} + /> + {'false'} + </label> + <p className='help-text'>{'Rate limit API access by IP address.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='VaryByHeader' + > + {'Limit By Http Header:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='VaryByHeader' + ref='VaryByHeader' + placeholder='Ex "X-Real-IP", "X-Forwarded-For"' + defaultValue={this.props.config.RateLimitSettings.VaryByHeader} + onChange={this.handleChange} + disabled={!this.state.EnableRateLimiter || this.state.VaryByRemoteAddr} + /> + <p className='help-text'>{'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").'}</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> Saving Config...'} + > + {'Save'} + </button> + </div> + </div> + + </form> + </div> + ); + } +} + +RateSettings.propTypes = { + config: React.PropTypes.object +}; 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..1bb1f053b --- /dev/null +++ b/web/react/components/admin_console/service_settings.jsx @@ -0,0 +1,296 @@ +// 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.EnableIncomingWebhooks = React.findDOMNode(this.refs.EnableIncomingWebhooks).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 = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>; + } + + var saveClass = 'btn'; + if (this.state.saveNeeded) { + saveClass = 'btn btn-primary'; + } + + return ( + <div className='wrapper--fixed'> + + <h3>{'Service Settings'}</h3> + <form + className='form-horizontal' + role='form' + > + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='ListenAddress' + > + {'Listen Address:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='ListenAddress' + ref='ListenAddress' + placeholder='Ex ":8065"' + defaultValue={this.props.config.ServiceSettings.ListenAddress} + onChange={this.handleChange} + /> + <p className='help-text'>{'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.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='MaximumLoginAttempts' + > + {'Maximum Login Attempts:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='MaximumLoginAttempts' + ref='MaximumLoginAttempts' + placeholder='Ex "10"' + defaultValue={this.props.config.ServiceSettings.MaximumLoginAttempts} + onChange={this.handleChange} + /> + <p className='help-text'>{'Login attempts allowed before user is locked out and required to reset password via email.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='SegmentDeveloperKey' + > + {'Segment Developer Key:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='SegmentDeveloperKey' + ref='SegmentDeveloperKey' + placeholder='Ex "g3fgGOXJAQ43QV7rAh6iwQCkV4cA1Gs"' + defaultValue={this.props.config.ServiceSettings.SegmentDeveloperKey} + onChange={this.handleChange} + /> + <p className='help-text'>{'For users running a SaaS services, sign up for a key at Segment.com to track metrics.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='GoogleDeveloperKey' + > + {'Google Developer Key:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='GoogleDeveloperKey' + ref='GoogleDeveloperKey' + placeholder='Ex "7rAh6iwQCkV4cA1Gsg3fgGOXJAQ43QV"' + defaultValue={this.props.config.ServiceSettings.GoogleDeveloperKey} + onChange={this.handleChange} + /> + <p className='help-text'>{'Set this key to enable embedding of YouTube video previews based on hyperlinks appearing in messages or comments. Instructions to obtain a key available at '}<a href='https://www.youtube.com/watch?v=Im69kzhpR3I'>{'https://www.youtube.com/watch?v=Im69kzhpR3I'}</a>{'. Leaving field blank disables the automatic generation of YouTube video previews from links.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='EnableOAuthServiceProvider' + > + {'Enable OAuth Service Provider: '} + </label> + <div className='col-sm-8'> + <label className='radio-inline'> + <input + type='radio' + name='EnableOAuthServiceProvider' + value='true' + ref='EnableOAuthServiceProvider' + defaultChecked={this.props.config.ServiceSettings.EnableOAuthServiceProvider} + onChange={this.handleChange} + /> + {'true'} + </label> + <label className='radio-inline'> + <input + type='radio' + name='EnableOAuthServiceProvider' + value='false' + defaultChecked={!this.props.config.ServiceSettings.EnableOAuthServiceProvider} + onChange={this.handleChange} + /> + {'false'} + </label> + <p className='help-text'>{'When enabled Mattermost will act as an Oauth2 Provider. Changing this will require a server restart before taking effect.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='EnableIncomingWebhooks' + > + {'EnableIncomingWebhooks: '} + </label> + <div className='col-sm-8'> + <label className='radio-inline'> + <input + type='radio' + name='EnableIncomingWebhooks' + value='true' + ref='EnableIncomingWebhooks' + defaultChecked={this.props.config.ServiceSettings.EnableIncomingWebhooks} + onChange={this.handleChange} + /> + {'true'} + </label> + <label className='radio-inline'> + <input + type='radio' + name='EnableIncomingWebhooks' + value='false' + defaultChecked={!this.props.config.ServiceSettings.EnableIncomingWebhooks} + onChange={this.handleChange} + /> + {'false'} + </label> + <p className='help-text'>{'When true incomming web hooks will be allowed.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='EnableTesting' + > + {'Enable Testing: '} + </label> + <div className='col-sm-8'> + <label className='radio-inline'> + <input + type='radio' + name='EnableTesting' + value='true' + ref='EnableTesting' + defaultChecked={this.props.config.ServiceSettings.EnableTesting} + onChange={this.handleChange} + /> + {'true'} + </label> + <label className='radio-inline'> + <input + type='radio' + name='EnableTesting' + value='false' + defaultChecked={!this.props.config.ServiceSettings.EnableTesting} + onChange={this.handleChange} + /> + {'false'} + </label> + <p className='help-text'>{'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.'}</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> Saving Config...'} + > + {'Save'} + </button> + </div> + </div> + + </form> + </div> + ); + } +} + +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 new file mode 100644 index 000000000..ad01b5963 --- /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, 32); + 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, 32); + var s = {saveNeeded: true, serverError: this.state.serverError}; + this.setState(s); + } + + render() { + var serverError = ''; + if (this.state.serverError) { + serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>; + } + + 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 ( + <div className='wrapper--fixed'> + + <div className='banner'> + <div className='banner__content'> + <h4 className='banner__heading'>{'Note:'}</h4> + <p>{'Changing properties in this section will require a server restart before taking effect.'}</p> + </div> + </div> + + <h3>{'SQL Settings'}</h3> + <form + className='form-horizontal' + role='form' + > + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='DriverName' + > + {'Driver Name:'} + </label> + <div className='col-sm-8'> + <p className='help-text'>{this.props.config.SqlSettings.DriverName}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='DataSource' + > + {'Data Source:'} + </label> + <div className='col-sm-8'> + <p className='help-text'>{dataSource}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='DataSourceReplicas' + > + {'Data Source Replicas:'} + </label> + <div className='col-sm-8'> + <p className='help-text'>{dataSourceReplicas}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='MaxIdleConns' + > + {'Maximum Idle Connections:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='MaxIdleConns' + ref='MaxIdleConns' + placeholder='Ex "10"' + defaultValue={this.props.config.SqlSettings.MaxIdleConns} + onChange={this.handleChange} + /> + <p className='help-text'>{'Maximum number of idle connections held open to the database.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='MaxOpenConns' + > + {'Maximum Open Connections:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='MaxOpenConns' + ref='MaxOpenConns' + placeholder='Ex "10"' + defaultValue={this.props.config.SqlSettings.MaxOpenConns} + onChange={this.handleChange} + /> + <p className='help-text'>{'Maximum number of open connections held open to the database.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='AtRestEncryptKey' + > + {'At Rest Encrypt Key:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='AtRestEncryptKey' + ref='AtRestEncryptKey' + placeholder='Ex "gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6"' + defaultValue={this.props.config.SqlSettings.AtRestEncryptKey} + onChange={this.handleChange} + /> + <p className='help-text'>{'32-character salt available to encrypt and decrypt sensitive fields in database.'}</p> + <div className='help-text'> + <button + className='help-link' + onClick={this.handleGenerate} + > + {'Re-Generate'} + </button> + </div> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='Trace' + > + {'Trace: '} + </label> + <div className='col-sm-8'> + <label className='radio-inline'> + <input + type='radio' + name='Trace' + value='true' + ref='Trace' + defaultChecked={this.props.config.SqlSettings.Trace} + onChange={this.handleChange} + /> + {'true'} + </label> + <label className='radio-inline'> + <input + type='radio' + name='Trace' + value='false' + defaultChecked={!this.props.config.SqlSettings.Trace} + onChange={this.handleChange} + /> + {'false'} + </label> + <p className='help-text'>{'Output executing SQL statements to the log. Typically used for development.'}</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> Saving Config...'} + > + {'Save'} + </button> + </div> + </div> + + </form> + </div> + ); + } +} + +SqlSettings.propTypes = { + config: React.PropTypes.object +}; 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 = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>; + } + + var saveClass = 'btn'; + if (this.state.saveNeeded) { + saveClass = 'btn btn-primary'; + } + + return ( + <div className='wrapper--fixed'> + + <h3>{'Team Settings'}</h3> + <form + className='form-horizontal' + role='form' + > + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='SiteName' + > + {'Site Name:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='SiteName' + ref='SiteName' + placeholder='Ex "Mattermost"' + defaultValue={this.props.config.TeamSettings.SiteName} + onChange={this.handleChange} + /> + <p className='help-text'>{'Name of service shown in login screens and UI.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='MaxUsersPerTeam' + > + {'Max Users Per Team:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='MaxUsersPerTeam' + ref='MaxUsersPerTeam' + placeholder='Ex "25"' + defaultValue={this.props.config.TeamSettings.MaxUsersPerTeam} + onChange={this.handleChange} + /> + <p className='help-text'>{'Maximum number of users per team.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='DefaultThemeColor' + > + {'Default Theme Color:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='DefaultThemeColor' + ref='DefaultThemeColor' + placeholder='Ex "#2389D7"' + defaultValue={this.props.config.TeamSettings.DefaultThemeColor} + onChange={this.handleChange} + /> + <p className='help-text'>{'Default theme color for team sites.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='EnableTeamCreation' + > + {'Enable Team Creation: '} + </label> + <div className='col-sm-8'> + <label className='radio-inline'> + <input + type='radio' + name='EnableTeamCreation' + value='true' + ref='EnableTeamCreation' + defaultChecked={this.props.config.TeamSettings.EnableTeamCreation} + onChange={this.handleChange} + /> + {'true'} + </label> + <label className='radio-inline'> + <input + type='radio' + name='EnableTeamCreation' + value='false' + defaultChecked={!this.props.config.TeamSettings.EnableTeamCreation} + onChange={this.handleChange} + /> + {'false'} + </label> + <p className='help-text'>{'When false the ability to create teams is disabled. The create team button displays error when pressed.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='EnableUserCreation' + > + {'Enable User Creation: '} + </label> + <div className='col-sm-8'> + <label className='radio-inline'> + <input + type='radio' + name='EnableUserCreation' + value='true' + ref='EnableUserCreation' + defaultChecked={this.props.config.TeamSettings.EnableUserCreation} + onChange={this.handleChange} + /> + {'true'} + </label> + <label className='radio-inline'> + <input + type='radio' + name='EnableUserCreation' + value='false' + defaultChecked={!this.props.config.TeamSettings.EnableUserCreation} + onChange={this.handleChange} + /> + {'false'} + </label> + <p className='help-text'>{'When false the ability to create accounts is disabled. The create account button displays error when pressed.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='RestrictCreationToDomains' + > + {'Restrict Creation To Domains:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='RestrictCreationToDomains' + ref='RestrictCreationToDomains' + placeholder='Ex "corp.mattermost.com, mattermost.org"' + defaultValue={this.props.config.TeamSettings.RestrictCreationToDomains} + onChange={this.handleChange} + /> + <p className='help-text'>{'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").'}</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> Saving Config...'} + > + {'Save'} + </button> + </div> + </div> + + </form> + </div> + ); + } +} + +TeamSettings.propTypes = { + config: React.PropTypes.object +}; diff --git a/web/react/components/invite_member_modal.jsx b/web/react/components/invite_member_modal.jsx index 650a72516..395b98630 100644 --- a/web/react/components/invite_member_modal.jsx +++ b/web/react/components/invite_member_modal.jsx @@ -21,7 +21,7 @@ export default class InviteMemberModal extends React.Component { emailErrors: {}, firstNameErrors: {}, lastNameErrors: {}, - emailEnabled: !global.window.config.ByPassEmail + emailEnabled: global.window.config.SendEmailNotifications === 'true' }; } diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx index ffc07a4dd..8cc4f1483 100644 --- a/web/react/components/login.jsx +++ b/web/react/components/login.jsx @@ -5,7 +5,6 @@ const Utils = require('../utils/utils.jsx'); const Client = require('../utils/client.jsx'); const UserStore = require('../stores/user_store.jsx'); const BrowserStore = require('../stores/browser_store.jsx'); -const Constants = require('../utils/constants.jsx'); export default class Login extends React.Component { constructor(props) { @@ -95,10 +94,8 @@ export default class Login extends React.Component { focusEmail = true; } - const authServices = JSON.parse(this.props.authServices); - let loginMessage = []; - if (authServices.indexOf(Constants.GITLAB_SERVICE) !== -1) { + if (global.window.config.EnableSignUpWithGitLab === 'true') { loginMessage.push( <a className='btn btn-custom-login gitlab' @@ -116,7 +113,7 @@ export default class Login extends React.Component { } let emailSignup; - if (authServices.indexOf(Constants.EMAIL_SERVICE) !== -1) { + if (global.window.config.EnableSignUpWithEmail === 'true') { emailSignup = ( <div> <div className={'form-group' + errorClass}> @@ -205,11 +202,9 @@ export default class Login extends React.Component { Login.defaultProps = { teamName: '', - teamDisplayName: '', - authServices: '' + teamDisplayName: '' }; Login.propTypes = { teamName: React.PropTypes.string, - teamDisplayName: React.PropTypes.string, - authServices: React.PropTypes.string + teamDisplayName: React.PropTypes.string }; diff --git a/web/react/components/signup_team.jsx b/web/react/components/signup_team.jsx index bf08e6508..7f320e0b2 100644 --- a/web/react/components/signup_team.jsx +++ b/web/react/components/signup_team.jsx @@ -4,7 +4,7 @@ const ChoosePage = require('./team_signup_choose_auth.jsx'); const EmailSignUpPage = require('./team_signup_with_email.jsx'); const SSOSignupPage = require('./team_signup_with_sso.jsx'); -const Constants = require('../utils/constants.jsx'); +var Constants = require('../utils/constants.jsx'); export default class TeamSignUp extends React.Component { constructor(props) { @@ -12,38 +12,42 @@ export default class TeamSignUp extends React.Component { this.updatePage = this.updatePage.bind(this); - if (props.services.length === 1) { - if (props.services[0] === Constants.EMAIL_SERVICE) { - this.state = {page: 'email', service: ''}; - } else { - this.state = {page: 'service', service: props.services[0]}; - } - } else { - this.state = {page: 'choose', service: ''}; + var count = 0; + + if (global.window.config.EnableSignUpWithEmail === 'true') { + count = count + 1; + } + + if (global.window.config.EnableSignUpWithGitLab === 'true') { + count = count + 1; + } + + if (count > 1) { + this.state = {page: 'choose'}; + } else if (global.window.config.EnableSignUpWithEmail === 'true') { + this.state = {page: 'email'}; + } else if (global.window.config.EnableSignUpWithGitLab === 'true') { + this.state = {page: 'gitlab'}; } } - updatePage(page, service) { - this.setState({page: page, service: service}); + + updatePage(page) { + this.setState({page}); } + render() { + if (this.state.page === 'choose') { + return ( + <ChoosePage + updatePage={this.updatePage} + /> + ); + } + if (this.state.page === 'email') { return <EmailSignUpPage />; - } else if (this.state.page === 'service' && this.state.service !== '') { - return <SSOSignupPage service={this.state.service} />; + } else if (this.state.page === 'gitlab') { + return <SSOSignupPage service={Constants.GITLAB_SERVICE} />; } - - return ( - <ChoosePage - services={this.props.services} - updatePage={this.updatePage} - /> - ); } } - -TeamSignUp.defaultProps = { - services: [] -}; -TeamSignUp.propTypes = { - services: React.PropTypes.array -}; diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx index e77bde861..8311747ee 100644 --- a/web/react/components/signup_user_complete.jsx +++ b/web/react/components/signup_user_complete.jsx @@ -5,7 +5,6 @@ var Utils = require('../utils/utils.jsx'); var client = require('../utils/client.jsx'); var UserStore = require('../stores/user_store.jsx'); var BrowserStore = require('../stores/browser_store.jsx'); -var Constants = require('../utils/constants.jsx'); export default class SignupUserComplete extends React.Component { constructor(props) { @@ -168,11 +167,8 @@ export default class SignupUserComplete extends React.Component { </div> ); - // add options to log in using another service - var authServices = JSON.parse(this.props.authServices); - var signupMessage = []; - if (authServices.indexOf(Constants.GITLAB_SERVICE) >= 0) { + if (global.window.config.EnableSignUpWithGitLab === 'true') { signupMessage.push( <a className='btn btn-custom-login gitlab' @@ -185,7 +181,7 @@ export default class SignupUserComplete extends React.Component { } var emailSignup; - if (authServices.indexOf(Constants.EMAIL_SERVICE) !== -1) { + if (global.window.config.EnableSignUpWithEmail === 'true') { emailSignup = ( <div> <div className='inner__content'> @@ -269,7 +265,6 @@ SignupUserComplete.defaultProps = { teamId: '', email: '', data: null, - authServices: '', teamDisplayName: '' }; SignupUserComplete.propTypes = { @@ -278,6 +273,5 @@ SignupUserComplete.propTypes = { teamId: React.PropTypes.string, email: React.PropTypes.string, data: React.PropTypes.string, - authServices: React.PropTypes.string, teamDisplayName: React.PropTypes.string }; diff --git a/web/react/components/team_signup_choose_auth.jsx b/web/react/components/team_signup_choose_auth.jsx index d3107c5c7..b8264b887 100644 --- a/web/react/components/team_signup_choose_auth.jsx +++ b/web/react/components/team_signup_choose_auth.jsx @@ -1,8 +1,6 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var Constants = require('../utils/constants.jsx'); - export default class ChooseAuthPage extends React.Component { constructor(props) { super(props); @@ -10,7 +8,7 @@ export default class ChooseAuthPage extends React.Component { } render() { var buttons = []; - if (this.props.services.indexOf(Constants.GITLAB_SERVICE) !== -1) { + if (global.window.config.EnableSignUpWithGitLab === 'true') { buttons.push( <a className='btn btn-custom-login gitlab btn-full' @@ -18,17 +16,17 @@ export default class ChooseAuthPage extends React.Component { onClick={ function clickGit(e) { e.preventDefault(); - this.props.updatePage('service', Constants.GITLAB_SERVICE); + this.props.updatePage('gitlab'); }.bind(this) } > <span className='icon' /> - <span>Create new team with GitLab Account</span> + <span>{'Create new team with GitLab Account'}</span> </a> ); } - if (this.props.services.indexOf(Constants.EMAIL_SERVICE) !== -1) { + if (global.window.config.EnableSignUpWithEmail === 'true') { buttons.push( <a className='btn btn-custom-login email btn-full' @@ -36,18 +34,18 @@ export default class ChooseAuthPage extends React.Component { onClick={ function clickEmail(e) { e.preventDefault(); - this.props.updatePage('email', ''); + this.props.updatePage('email'); }.bind(this) } > <span className='fa fa-envelope' /> - <span>Create new team with email address</span> + <span>{'Create new team with email address'}</span> </a> ); } if (buttons.length === 0) { - buttons = <span>No sign-up methods configured, please contact your system administrator.</span>; + buttons = <span>{'No sign-up methods configured, please contact your system administrator.'}</span>; } return ( @@ -61,10 +59,6 @@ export default class ChooseAuthPage extends React.Component { } } -ChooseAuthPage.defaultProps = { - services: [] -}; ChooseAuthPage.propTypes = { - services: React.PropTypes.array, updatePage: React.PropTypes.func }; diff --git a/web/react/components/team_signup_send_invites_page.jsx b/web/react/components/team_signup_send_invites_page.jsx index 41ac98303..524bd5b50 100644 --- a/web/react/components/team_signup_send_invites_page.jsx +++ b/web/react/components/team_signup_send_invites_page.jsx @@ -13,7 +13,7 @@ export default class TeamSignupSendInvitesPage extends React.Component { this.submitSkip = this.submitSkip.bind(this); this.keySubmit = this.keySubmit.bind(this); this.state = { - emailEnabled: !global.window.config.ByPassEmail + emailEnabled: global.window.config.SendEmailNotifications === 'true' }; if (!this.state.emailEnabled) { diff --git a/web/react/components/user_profile.jsx b/web/react/components/user_profile.jsx index 7cfac69e7..c5d028d31 100644 --- a/web/react/components/user_profile.jsx +++ b/web/react/components/user_profile.jsx @@ -57,7 +57,7 @@ export default class UserProfile extends React.Component { } var dataContent = '<img class="user-popover__image" src="/api/v1/users/' + this.state.profile.id + '/image?time=' + this.state.profile.update_at + '" height="128" width="128" />'; - if (!global.window.config.ShowEmailAddress) { + if (!global.window.config.ShowEmailAddress === 'true') { dataContent += '<div class="text-nowrap">Email not shared</div>'; } else { dataContent += '<div data-toggle="tooltip" title="' + this.state.profile.email + '"><a href="mailto:' + this.state.profile.email + '" class="text-nowrap text-lowercase user-popover__email">' + this.state.profile.email + '</a></div>'; diff --git a/web/react/components/user_settings/user_settings_general.jsx b/web/react/components/user_settings/user_settings_general.jsx index 5d9d9bfde..c1d4c4ab5 100644 --- a/web/react/components/user_settings/user_settings_general.jsx +++ b/web/react/components/user_settings/user_settings_general.jsx @@ -208,7 +208,7 @@ export default class UserSettingsGeneralTab extends React.Component { } setupInitialState(props) { var user = props.user; - var emailEnabled = !global.window.config.ByPassEmail; + var emailEnabled = global.window.config.SendEmailNotifications === 'true'; return {username: user.username, firstName: user.first_name, lastName: user.last_name, nickname: user.nickname, email: user.email, picture: null, loadingPicture: false, emailEnabled: emailEnabled}; } diff --git a/web/react/components/user_settings/user_settings_modal.jsx b/web/react/components/user_settings/user_settings_modal.jsx index 430a7ec7c..5113d2429 100644 --- a/web/react/components/user_settings/user_settings_modal.jsx +++ b/web/react/components/user_settings/user_settings_modal.jsx @@ -38,7 +38,7 @@ export default class UserSettingsModal extends React.Component { if (global.window.config.EnableOAuthServiceProvider === 'true') { tabs.push({name: 'developer', uiName: 'Developer', icon: 'glyphicon glyphicon-th'}); } - if (global.window.config.AllowIncomingWebhooks === 'true') { + if (global.window.config.EnableIncomingWebhooks === 'true') { tabs.push({name: 'integrations', uiName: 'Integrations', icon: 'glyphicon glyphicon-transfer'}); } diff --git a/web/react/components/view_image.jsx b/web/react/components/view_image.jsx index f7c980396..dafcdd9f9 100644 --- a/web/react/components/view_image.jsx +++ b/web/react/components/view_image.jsx @@ -300,7 +300,7 @@ export default class ViewImageModal extends React.Component { } var publicLink = ''; - if (global.window.config.AllowPublicLink) { + if (global.window.config.EnablePublicLink === 'true') { publicLink = ( <div> <a |