diff options
Diffstat (limited to 'web/react/components')
4 files changed, 361 insertions, 28 deletions
diff --git a/web/react/components/admin_console/service_settings.jsx b/web/react/components/admin_console/service_settings.jsx index 4105ba6da..53c89a942 100644 --- a/web/react/components/admin_console/service_settings.jsx +++ b/web/react/components/admin_console/service_settings.jsx @@ -36,6 +36,7 @@ export default class ServiceSettings extends React.Component { config.ServiceSettings.SegmentDeveloperKey = ReactDOM.findDOMNode(this.refs.SegmentDeveloperKey).value.trim(); config.ServiceSettings.GoogleDeveloperKey = ReactDOM.findDOMNode(this.refs.GoogleDeveloperKey).value.trim(); config.ServiceSettings.EnableIncomingWebhooks = ReactDOM.findDOMNode(this.refs.EnableIncomingWebhooks).checked; + config.ServiceSettings.EnableOutgoingWebhooks = React.findDOMNode(this.refs.EnableOutgoingWebhooks).checked; config.ServiceSettings.EnablePostUsernameOverride = ReactDOM.findDOMNode(this.refs.EnablePostUsernameOverride).checked; config.ServiceSettings.EnablePostIconOverride = ReactDOM.findDOMNode(this.refs.EnablePostIconOverride).checked; config.ServiceSettings.EnableTesting = ReactDOM.findDOMNode(this.refs.EnableTesting).checked; @@ -207,7 +208,40 @@ export default class ServiceSettings extends React.Component { </div> </div> - <div className='form-group'> + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='EnableOutgoingWebhooks' + > + {'Enable Outgoing Webhooks: '} + </label> + <div className='col-sm-8'> + <label className='radio-inline'> + <input + type='radio' + name='EnableOutgoingWebhooks' + value='true' + ref='EnableOutgoingWebhooks' + defaultChecked={this.props.config.ServiceSettings.EnableOutgoingWebhooks} + onChange={this.handleChange} + /> + {'true'} + </label> + <label className='radio-inline'> + <input + type='radio' + name='EnableOutgoingWebhooks' + value='false' + defaultChecked={!this.props.config.ServiceSettings.EnableOutgoingWebhooks} + onChange={this.handleChange} + /> + {'false'} + </label> + <p className='help-text'>{'When true, outgoing webhooks will be allowed.'}</p> + </div> + </div> + + <div className='form-group'> <label className='control-label col-sm-4' htmlFor='EnablePostUsernameOverride' diff --git a/web/react/components/user_settings/manage_outgoing_hooks.jsx b/web/react/components/user_settings/manage_outgoing_hooks.jsx new file mode 100644 index 000000000..e83ae3bd6 --- /dev/null +++ b/web/react/components/user_settings/manage_outgoing_hooks.jsx @@ -0,0 +1,262 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var Client = require('../../utils/client.jsx'); +var Constants = require('../../utils/constants.jsx'); +var ChannelStore = require('../../stores/channel_store.jsx'); +var LoadingScreen = require('../loading_screen.jsx'); + +export default class ManageOutgoingHooks extends React.Component { + constructor() { + super(); + + this.getHooks = this.getHooks.bind(this); + this.addNewHook = this.addNewHook.bind(this); + this.updateChannelId = this.updateChannelId.bind(this); + this.updateTriggerWords = this.updateTriggerWords.bind(this); + this.updateCallbackURLs = this.updateCallbackURLs.bind(this); + + this.state = {hooks: [], channelId: '', triggerWords: '', callbackURLs: '', getHooksComplete: false}; + } + componentDidMount() { + this.getHooks(); + } + addNewHook(e) { + e.preventDefault(); + + if ((this.state.channelId === '' && this.state.triggerWords === '') || + this.state.callbackURLs === '') { + return; + } + + const hook = {}; + hook.channel_id = this.state.channelId; + if (this.state.triggerWords.length !== 0) { + hook.trigger_words = this.state.triggerWords.trim().split(','); + } + hook.callback_urls = this.state.callbackURLs.split('\n'); + + Client.addOutgoingHook( + hook, + (data) => { + let hooks = Object.assign([], this.state.hooks); + if (!hooks) { + hooks = []; + } + hooks.push(data); + this.setState({hooks, serverError: null, channelId: '', triggerWords: '', callbackURLs: ''}); + }, + (err) => { + this.setState({serverError: err}); + } + ); + } + removeHook(id) { + const data = {}; + data.id = id; + + Client.deleteOutgoingHook( + data, + () => { + const hooks = this.state.hooks; + let index = -1; + for (let i = 0; i < hooks.length; i++) { + if (hooks[i].id === id) { + index = i; + break; + } + } + + if (index !== -1) { + hooks.splice(index, 1); + } + + this.setState({hooks}); + }, + (err) => { + this.setState({serverError: err}); + } + ); + } + regenToken(id) { + const regenData = {}; + regenData.id = id; + + Client.regenOutgoingHookToken( + regenData, + (data) => { + const hooks = Object.assign([], this.state.hooks); + for (let i = 0; i < hooks.length; i++) { + if (hooks[i].id === id) { + hooks[i] = data; + break; + } + } + + this.setState({hooks, serverError: null}); + }, + (err) => { + this.setState({serverError: err}); + } + ); + } + getHooks() { + Client.listOutgoingHooks( + (data) => { + if (data) { + this.setState({hooks: data, getHooksComplete: true, serverError: null}); + } + }, + (err) => { + this.setState({serverError: err}); + } + ); + } + updateChannelId(e) { + this.setState({channelId: e.target.value}); + } + updateTriggerWords(e) { + this.setState({triggerWords: e.target.value}); + } + updateCallbackURLs(e) { + this.setState({callbackURLs: e.target.value}); + } + render() { + let serverError; + if (this.state.serverError) { + serverError = <label className='has-error'>{this.state.serverError}</label>; + } + + const channels = ChannelStore.getAll(); + const options = [<option value=''>{'--- Select a channel ---'}</option>]; + channels.forEach((channel) => { + if (channel.type === Constants.OPEN_CHANNEL) { + options.push(<option value={channel.id}>{channel.name}</option>); + } + }); + + const hooks = []; + this.state.hooks.forEach((hook) => { + const c = ChannelStore.get(hook.channel_id); + let channelDiv; + if (c) { + channelDiv = ( + <div className='padding-top'> + <strong>{'Channel: '}</strong>{c.name} + </div> + ); + } + + let triggerDiv; + if (hook.trigger_words && hook.trigger_words.length !== 0) { + triggerDiv = ( + <div className='padding-top'> + <strong>{'Trigger Words: '}</strong>{hook.trigger_words.join(', ')} + </div> + ); + } + + hooks.push( + <div className='font--small'> + <div className='padding-top x2 divider-light'></div> + <div className='padding-top x2'> + <strong>{'URLs: '}</strong><span className='word-break--all'>{hook.callback_urls.join(', ')}</span> + </div> + {channelDiv} + {triggerDiv} + <div className='padding-top'> + <strong>{'Token: '}</strong>{hook.token} + </div> + <div className='padding-top'> + <a + className='text-danger' + href='#' + onClick={this.regenToken.bind(this, hook.id)} + > + {'Regen Token'} + </a> + <span>{' - '}</span> + <a + className='text-danger' + href='#' + onClick={this.removeHook.bind(this, hook.id)} + > + {'Remove'} + </a> + </div> + </div> + ); + }); + + let displayHooks; + if (!this.state.getHooksComplete) { + displayHooks = <LoadingScreen/>; + } else if (hooks.length > 0) { + displayHooks = hooks; + } else { + displayHooks = <label>{': None'}</label>; + } + + const existingHooks = ( + <div className='padding-top x2'> + <label className='control-label padding-top x2'>{'Existing outgoing webhooks'}</label> + {displayHooks} + </div> + ); + + const disableButton = (this.state.channelId === '' && this.state.triggerWords === '') || this.state.callbackURLs === ''; + + return ( + <div key='addOutgoingHook'> + <label className='control-label'>{'Add a new outgoing webhook'}</label> + <div className='padding-top'> + <strong>{'Channel:'}</strong> + <select + ref='channelName' + className='form-control' + value={this.state.channelId} + onChange={this.updateChannelId} + > + {options} + </select> + <span>{'Only public channels can be used'}</span> + <br/> + <br/> + <strong>{'Trigger Words:'}</strong> + <input + ref='triggerWords' + className='form-control' + value={this.state.triggerWords} + onChange={this.updateTriggerWords} + placeholder='Optional if channel selected' + /> + <span>{'Comma separated words to trigger on'}</span> + <br/> + <br/> + <strong>{'Callback URLs:'}</strong> + <textarea + ref='callbackURLs' + className='form-control no-resize' + value={this.state.callbackURLs} + resize={false} + rows={3} + onChange={this.updateCallbackURLs} + /> + <span>{'New line separated URLs that will receive the HTTP POST event'}</span> + {serverError} + <div className='padding-top'> + <a + className={'btn btn-sm btn-primary'} + href='#' + disabled={disableButton} + onClick={this.addNewHook} + > + {'Add'} + </a> + </div> + </div> + {existingHooks} + </div> + ); + } +} diff --git a/web/react/components/user_settings/user_settings_integrations.jsx b/web/react/components/user_settings/user_settings_integrations.jsx index 3be062ad3..231580cc3 100644 --- a/web/react/components/user_settings/user_settings_integrations.jsx +++ b/web/react/components/user_settings/user_settings_integrations.jsx @@ -4,6 +4,7 @@ var SettingItemMin = require('../setting_item_min.jsx'); var SettingItemMax = require('../setting_item_max.jsx'); var ManageIncomingHooks = require('./manage_incoming_hooks.jsx'); +var ManageOutgoingHooks = require('./manage_outgoing_hooks.jsx'); export default class UserSettingsIntegrationsTab extends React.Component { constructor(props) { @@ -19,6 +20,8 @@ export default class UserSettingsIntegrationsTab extends React.Component { } handleClose() { this.updateSection(''); + $('.ps-container.modal-body').scrollTop(0); + $('.ps-container.modal-body').perfectScrollbar('update'); } componentDidMount() { $('#user_settings').on('hidden.bs.modal', this.handleClose); @@ -28,35 +31,67 @@ export default class UserSettingsIntegrationsTab extends React.Component { } render() { let incomingHooksSection; + let outgoingHooksSection; var inputs = []; - if (this.props.activeSection === 'incoming-hooks') { - inputs.push( - <ManageIncomingHooks /> - ); + if (global.window.config.EnableIncomingWebhooks === 'true') { + if (this.props.activeSection === 'incoming-hooks') { + inputs.push( + <ManageIncomingHooks /> + ); - incomingHooksSection = ( - <SettingItemMax - title='Incoming Webhooks' - width = 'full' - inputs={inputs} - updateSection={function clearSection(e) { - this.updateSection(''); - e.preventDefault(); - }.bind(this)} - /> - ); - } else { - incomingHooksSection = ( - <SettingItemMin - title='Incoming Webhooks' - width = 'full' - describe='Manage your incoming webhooks (Developer feature)' - updateSection={function updateNameSection() { - this.updateSection('incoming-hooks'); - }.bind(this)} - /> - ); + incomingHooksSection = ( + <SettingItemMax + title='Incoming Webhooks' + width = 'full' + inputs={inputs} + updateSection={(e) => { + this.updateSection(''); + e.preventDefault(); + }} + /> + ); + } else { + incomingHooksSection = ( + <SettingItemMin + title='Incoming Webhooks' + width = 'full' + describe='Manage your incoming webhooks (Developer feature)' + updateSection={() => { + this.updateSection('incoming-hooks'); + }} + /> + ); + } + } + + if (global.window.config.EnableOutgoingWebhooks === 'true') { + if (this.props.activeSection === 'outgoing-hooks') { + inputs.push( + <ManageOutgoingHooks /> + ); + + outgoingHooksSection = ( + <SettingItemMax + title='Outgoing Webhooks' + inputs={inputs} + updateSection={(e) => { + this.updateSection(''); + e.preventDefault(); + }} + /> + ); + } else { + outgoingHooksSection = ( + <SettingItemMin + title='Outgoing Webhooks' + describe='Manage your outgoing webhooks' + updateSection={() => { + this.updateSection('outgoing-hooks'); + }} + /> + ); + } } return ( @@ -82,6 +117,8 @@ export default class UserSettingsIntegrationsTab extends React.Component { <h3 className='tab-header'>{'Integration Settings'}</h3> <div className='divider-dark first'/> {incomingHooksSection} + <div className='divider-light'/> + {outgoingHooksSection} <div className='divider-dark'/> </div> </div> diff --git a/web/react/components/user_settings/user_settings_modal.jsx b/web/react/components/user_settings/user_settings_modal.jsx index 692fb26ee..44cd423b5 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.EnableIncomingWebhooks === 'true') { + if (global.window.config.EnableIncomingWebhooks === 'true' || global.window.config.EnableOutgoingWebhooks === 'true') { tabs.push({name: 'integrations', uiName: 'Integrations', icon: 'glyphicon glyphicon-transfer'}); } tabs.push({name: 'display', uiName: 'Display', icon: 'glyphicon glyphicon-eye-open'}); |