summaryrefslogtreecommitdiffstats
path: root/webapp/components/backstage
diff options
context:
space:
mode:
authorHarrison Healey <harrisonmhealey@gmail.com>2016-07-05 11:58:18 -0400
committerJoram Wilander <jwawilander@gmail.com>2016-07-05 11:58:18 -0400
commitdc2f2a800105b77e665ec2a00c6290f35b1a2ba3 (patch)
tree82f23c2e72a7c785f55c2d6c1c35c10c16994918 /webapp/components/backstage
parenta65f1fc266f15eaa8f79541d4d11440c3d356bb6 (diff)
downloadchat-dc2f2a800105b77e665ec2a00c6290f35b1a2ba3.tar.gz
chat-dc2f2a800105b77e665ec2a00c6290f35b1a2ba3.tar.bz2
chat-dc2f2a800105b77e665ec2a00c6290f35b1a2ba3.zip
PLT-3145 Custom Emojis (#3381)
* Reorganized Backstage code to use a view controller and separated it from integrations code * Renamed InstalledIntegrations component to BackstageList * Added EmojiList page * Added AddEmoji page * Added custom emoji to autocomplete and text formatter * Moved system emoji to EmojiStore * Stopped trying to get emoji before logging in * Rerender posts when emojis change * Fixed submit handler on backstage pages to properly support enter * Removed debugging code * Updated javascript driver * Fixed unit tests * Fixed backstage routes * Added clientside validation to prevent users from creating an emoji with the same name as a system one * Fixed AddEmoji page to properly redirect when an emoji is created successfully * Fixed updating emoji list when an emoji is deleted * Added type prop to BackstageList to properly support using a table for the list * Added help text to EmojiList * Fixed backstage on smaller screen sizes * Disable custom emoji by default * Improved restrictions on creating emojis * Fixed non-admin users seeing the option to delete each other's emojis * Fixing gofmt * Fixed emoji unit tests * Fixed trying to get emoji from the server when it's disabled
Diffstat (limited to 'webapp/components/backstage')
-rw-r--r--webapp/components/backstage/add_command.jsx558
-rw-r--r--webapp/components/backstage/add_incoming_webhook.jsx208
-rw-r--r--webapp/components/backstage/add_outgoing_webhook.jsx341
-rw-r--r--webapp/components/backstage/backstage_controller.jsx71
-rw-r--r--webapp/components/backstage/components/backstage_category.jsx (renamed from webapp/components/backstage/backstage_category.jsx)1
-rw-r--r--webapp/components/backstage/components/backstage_header.jsx (renamed from webapp/components/backstage/backstage_header.jsx)0
-rw-r--r--webapp/components/backstage/components/backstage_list.jsx108
-rw-r--r--webapp/components/backstage/components/backstage_navbar.jsx (renamed from webapp/components/backstage/backstage_navbar.jsx)36
-rw-r--r--webapp/components/backstage/components/backstage_section.jsx (renamed from webapp/components/backstage/backstage_section.jsx)0
-rw-r--r--webapp/components/backstage/components/backstage_sidebar.jsx (renamed from webapp/components/backstage/backstage_sidebar.jsx)79
-rw-r--r--webapp/components/backstage/installed_command.jsx146
-rw-r--r--webapp/components/backstage/installed_commands.jsx100
-rw-r--r--webapp/components/backstage/installed_incoming_webhook.jsx131
-rw-r--r--webapp/components/backstage/installed_incoming_webhooks.jsx94
-rw-r--r--webapp/components/backstage/installed_integrations.jsx101
-rw-r--r--webapp/components/backstage/installed_outgoing_webhook.jsx199
-rw-r--r--webapp/components/backstage/installed_outgoing_webhooks.jsx100
-rw-r--r--webapp/components/backstage/integration_option.jsx39
-rw-r--r--webapp/components/backstage/integrations.jsx99
19 files changed, 248 insertions, 2163 deletions
diff --git a/webapp/components/backstage/add_command.jsx b/webapp/components/backstage/add_command.jsx
deleted file mode 100644
index 91af0416b..000000000
--- a/webapp/components/backstage/add_command.jsx
+++ /dev/null
@@ -1,558 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import * as AsyncClient from 'utils/async_client.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-import BackstageHeader from './backstage_header.jsx';
-import {FormattedMessage} from 'react-intl';
-import FormError from 'components/form_error.jsx';
-import {browserHistory, Link} from 'react-router/es6';
-import SpinnerButton from 'components/spinner_button.jsx';
-import Constants from 'utils/constants.jsx';
-
-const REQUEST_POST = 'P';
-const REQUEST_GET = 'G';
-
-export default class AddCommand extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleSubmit = this.handleSubmit.bind(this);
-
- this.updateDisplayName = this.updateDisplayName.bind(this);
- this.updateDescription = this.updateDescription.bind(this);
- this.updateTrigger = this.updateTrigger.bind(this);
- this.updateUrl = this.updateUrl.bind(this);
- this.updateMethod = this.updateMethod.bind(this);
- this.updateUsername = this.updateUsername.bind(this);
- this.updateIconUrl = this.updateIconUrl.bind(this);
- this.updateAutocomplete = this.updateAutocomplete.bind(this);
- this.updateAutocompleteHint = this.updateAutocompleteHint.bind(this);
- this.updateAutocompleteDescription = this.updateAutocompleteDescription.bind(this);
-
- this.state = {
- displayName: '',
- description: '',
- trigger: '',
- url: '',
- method: REQUEST_POST,
- username: '',
- iconUrl: '',
- autocomplete: false,
- autocompleteHint: '',
- autocompleteDescription: '',
- saving: false,
- serverError: '',
- clientError: null
- };
- }
-
- handleSubmit(e) {
- e.preventDefault();
-
- if (this.state.saving) {
- return;
- }
-
- this.setState({
- saving: true,
- serverError: '',
- clientError: ''
- });
-
- const command = {
- display_name: this.state.displayName,
- description: this.state.description,
- trigger: this.state.trigger.trim(),
- url: this.state.url.trim(),
- method: this.state.method,
- username: this.state.username,
- icon_url: this.state.iconUrl,
- auto_complete: this.state.autocomplete
- };
-
- if (command.auto_complete) {
- command.auto_complete_desc = this.state.autocompleteDescription;
- command.auto_complete_hint = this.state.autocompleteHint;
- }
-
- if (!command.trigger) {
- this.setState({
- saving: false,
- clientError: (
- <FormattedMessage
- id='add_command.triggerRequired'
- defaultMessage='A trigger word is required'
- />
- )
- });
-
- return;
- }
-
- if (command.trigger.indexOf('/') === 0) {
- this.setState({
- saving: false,
- clientError: (
- <FormattedMessage
- id='add_command.triggerInvalidSlash'
- defaultMessage='A trigger word cannot begin with a /'
- />
- )
- });
-
- return;
- }
-
- if (command.trigger.indexOf(' ') !== -1) {
- this.setState({
- saving: false,
- clientError: (
- <FormattedMessage
- id='add_command.triggerInvalidSpace'
- defaultMessage='A trigger word must not contain spaces'
- />
- )
- });
- return;
- }
-
- if (command.trigger.length < Constants.MIN_TRIGGER_LENGTH || command.trigger.length > Constants.MAX_TRIGGER_LENGTH) {
- this.setState({
- saving: false,
- clientError: (
- <FormattedMessage
- id='add_command.triggerInvalidLength'
- defaultMessage='A trigger word must contain between {min} and {max} characters'
- values={{
- min: Constants.MIN_TRIGGER_LENGTH,
- max: Constants.MAX_TRIGGER_LENGTH
- }}
- />
- )
- });
-
- return;
- }
-
- if (!command.url) {
- this.setState({
- saving: false,
- clientError: (
- <FormattedMessage
- id='add_command.urlRequired'
- defaultMessage='A request URL is required'
- />
- )
- });
-
- return;
- }
-
- AsyncClient.addCommand(
- command,
- () => {
- browserHistory.push('/' + Utils.getTeamNameFromUrl() + '/settings/integrations/commands');
- },
- (err) => {
- this.setState({
- saving: false,
- serverError: err.message
- });
- }
- );
- }
-
- updateDisplayName(e) {
- this.setState({
- displayName: e.target.value
- });
- }
-
- updateDescription(e) {
- this.setState({
- description: e.target.value
- });
- }
-
- updateTrigger(e) {
- this.setState({
- trigger: e.target.value
- });
- }
-
- updateUrl(e) {
- this.setState({
- url: e.target.value
- });
- }
-
- updateMethod(e) {
- this.setState({
- method: e.target.value
- });
- }
-
- updateUsername(e) {
- this.setState({
- username: e.target.value
- });
- }
-
- updateIconUrl(e) {
- this.setState({
- iconUrl: e.target.value
- });
- }
-
- updateAutocomplete(e) {
- this.setState({
- autocomplete: e.target.checked
- });
- }
-
- updateAutocompleteHint(e) {
- this.setState({
- autocompleteHint: e.target.value
- });
- }
-
- updateAutocompleteDescription(e) {
- this.setState({
- autocompleteDescription: e.target.value
- });
- }
-
- render() {
- let autocompleteFields = null;
- if (this.state.autocomplete) {
- autocompleteFields = [(
- <div
- key='autocompleteHint'
- className='form-group'
- >
- <label
- className='control-label col-sm-4'
- htmlFor='autocompleteHint'
- >
- <FormattedMessage
- id='add_command.autocompleteHint'
- defaultMessage='Autocomplete Hint'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='autocompleteHint'
- type='text'
- maxLength='1024'
- className='form-control'
- value={this.state.autocompleteHint}
- onChange={this.updateAutocompleteHint}
- placeholder={Utils.localizeMessage('add_command.autocompleteHint.placeholder', 'Example: [Patient Name]')}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_command.autocompleteDescription.help'
- defaultMessage='Optional hint in the autocomplete list about command parameters'
- />
- </div>
- </div>
- </div>
- ),
- (
- <div
- key='autocompleteDescription'
- className='form-group'
- >
- <label
- className='control-label col-sm-4'
- htmlFor='autocompleteDescription'
- >
- <FormattedMessage
- id='add_command.autocompleteDescription'
- defaultMessage='Autocomplete Description'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='description'
- type='text'
- maxLength='128'
- className='form-control'
- value={this.state.autocompleteDescription}
- onChange={this.updateAutocompleteDescription}
- placeholder={Utils.localizeMessage('add_command.autocompleteDescription.placeholder', 'Example: "Returns search results for patient records"')}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_command.autocompleteDescription.help'
- defaultMessage='Optional short description of slash command for the autocomplete list.'
- />
- </div>
- </div>
- </div>
- )];
- }
-
- return (
- <div className='backstage-content row'>
- <BackstageHeader>
- <Link to={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/commands'}>
- <FormattedMessage
- id='installed_command.header'
- defaultMessage='Slash Commands'
- />
- </Link>
- <FormattedMessage
- id='add_command.header'
- defaultMessage='Add'
- />
- </BackstageHeader>
- <div className='backstage-form'>
- <form className='form-horizontal'>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='displayName'
- >
- <FormattedMessage
- id='add_command.displayName'
- defaultMessage='Display Name'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='displayName'
- type='text'
- maxLength='64'
- className='form-control'
- value={this.state.displayName}
- onChange={this.updateDisplayName}
- />
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='description'
- >
- <FormattedMessage
- id='add_command.description'
- defaultMessage='Description'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='description'
- type='text'
- maxLength='128'
- className='form-control'
- value={this.state.description}
- onChange={this.updateDescription}
- />
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='trigger'
- >
- <FormattedMessage
- id='add_command.trigger'
- defaultMessage='Command Trigger Word'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='trigger'
- type='text'
- maxLength={Constants.MAX_TRIGGER_LENGTH}
- className='form-control'
- value={this.state.trigger}
- onChange={this.updateTrigger}
- placeholder={Utils.localizeMessage('add_command.trigger.placeholder', 'Command trigger e.g. "hello" not including the slash')}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_command.trigger.help1'
- defaultMessage='Examples: /patient, /client /employee'
- />
- </div>
- <div className='form__help'>
- <FormattedMessage
- id='add_command.trigger.help2'
- defaultMessage='Reserved: /echo, /join, /logout, /me, /shrug'
- />
- </div>
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='url'
- >
- <FormattedMessage
- id='add_command.url'
- defaultMessage='Request URL'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='url'
- type='text'
- maxLength='1024'
- className='form-control'
- value={this.state.url}
- onChange={this.updateUrl}
- placeholder={Utils.localizeMessage('add_command.url.placeholder', 'Must start with http:// or https://')}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_command.url.help'
- defaultMessage='The callback URL to receive the HTTP POST or GET event request when the slash command is run.'
- />
- </div>
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='method'
- >
- <FormattedMessage
- id='add_command.method'
- defaultMessage='Request Method'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <select
- id='method'
- className='form-control'
- value={this.state.method}
- onChange={this.updateMethod}
- >
- <option value={REQUEST_POST}>
- {Utils.localizeMessage('add_command.method.post', 'POST')}
- </option>
- <option value={REQUEST_GET}>
- {Utils.localizeMessage('add_command.method.get', 'GET')}
- </option>
- </select>
- <div className='form__help'>
- <FormattedMessage
- id='add_command.method.help'
- defaultMessage='The type of command request issued to the Request URL.'
- />
- </div>
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='username'
- >
- <FormattedMessage
- id='add_command.username'
- defaultMessage='Response Username'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='username'
- type='text'
- maxLength='64'
- className='form-control'
- value={this.state.username}
- onChange={this.updateUsername}
- placholder={Utils.localizeMessage('add_command.username.placeholder', 'Username')}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_command.username.help'
- defaultMessage='Choose a username override for responses for this slash command. Usernames can consist of up to 22 characters consisting of lowercase letters, numbers and the symbols "-", "_", and ".".'
- />
- </div>
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='iconUrl'
- >
- <FormattedMessage
- id='add_command.iconUrl'
- defaultMessage='Response Icon'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='iconUrl'
- type='text'
- maxLength='1024'
- className='form-control'
- value={this.state.iconUrl}
- onChange={this.updateIconUrl}
- placeholder={Utils.localizeMessage('add_command.iconUrl.placeholder', 'https://www.example.com/myicon.png')}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_command.iconUrl.help'
- defaultMessage='Choose a profile picture override for the post responses to this slash command. Enter the URL of a .png or .jpg file at least 128 pixels by 128 pixels.'
- />
- </div>
- </div>
- </div>
- <div className='form-group padding-bottom'>
- <div className='col-sm-12'>
- <div className='checkbox'>
- <input
- type='checkbox'
- checked={this.state.autocomplete}
- onChange={this.updateAutocomplete}
- />
- <FormattedMessage
- id='add_command.autocomplete'
- defaultMessage='Autocomplete'
- />
- </div>
- <div className='form__help'>
- <FormattedMessage
- id='add_command.autocomplete.help'
- defaultMessage='Show this command in the autocomplete list'
- />
- </div>
- </div>
- </div>
- {autocompleteFields}
- <div className='backstage-form__footer'>
- <FormError errors={[this.state.serverError, this.state.clientError]}/>
- <Link
- className='btn btn-sm'
- to={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/commands'}
- >
- <FormattedMessage
- id='add_command.cancel'
- defaultMessage='Cancel'
- />
- </Link>
- <SpinnerButton
- className='btn btn-primary'
- type='submit'
- spinning={this.state.saving}
- onClick={this.handleSubmit}
- >
- <FormattedMessage
- id='add_command.save'
- defaultMessage='Save'
- />
- </SpinnerButton>
- </div>
- </form>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/backstage/add_incoming_webhook.jsx b/webapp/components/backstage/add_incoming_webhook.jsx
deleted file mode 100644
index 528f03377..000000000
--- a/webapp/components/backstage/add_incoming_webhook.jsx
+++ /dev/null
@@ -1,208 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import * as AsyncClient from 'utils/async_client.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-import BackstageHeader from './backstage_header.jsx';
-import ChannelSelect from 'components/channel_select.jsx';
-import {FormattedMessage} from 'react-intl';
-import FormError from 'components/form_error.jsx';
-import {browserHistory, Link} from 'react-router/es6';
-import SpinnerButton from 'components/spinner_button.jsx';
-
-export default class AddIncomingWebhook extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleSubmit = this.handleSubmit.bind(this);
-
- this.updateDisplayName = this.updateDisplayName.bind(this);
- this.updateDescription = this.updateDescription.bind(this);
- this.updateChannelId = this.updateChannelId.bind(this);
-
- this.state = {
- displayName: '',
- description: '',
- channelId: '',
- saving: false,
- serverError: '',
- clientError: null
- };
- }
-
- handleSubmit(e) {
- e.preventDefault();
-
- if (this.state.saving) {
- return;
- }
-
- this.setState({
- saving: true,
- serverError: '',
- clientError: ''
- });
-
- if (!this.state.channelId) {
- this.setState({
- saving: false,
- clientError: (
- <FormattedMessage
- id='add_incoming_webhook.channelRequired'
- defaultMessage='A valid channel is required'
- />
- )
- });
-
- return;
- }
-
- const hook = {
- channel_id: this.state.channelId,
- display_name: this.state.displayName,
- description: this.state.description
- };
-
- AsyncClient.addIncomingHook(
- hook,
- () => {
- browserHistory.push('/' + Utils.getTeamNameFromUrl() + '/settings/integrations/incoming_webhooks');
- },
- (err) => {
- this.setState({
- saving: false,
- serverError: err.message
- });
- }
- );
- }
-
- updateDisplayName(e) {
- this.setState({
- displayName: e.target.value
- });
- }
-
- updateDescription(e) {
- this.setState({
- description: e.target.value
- });
- }
-
- updateChannelId(e) {
- this.setState({
- channelId: e.target.value
- });
- }
-
- render() {
- return (
- <div className='backstage-content'>
- <BackstageHeader>
- <Link to={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/incoming_webhooks'}>
- <FormattedMessage
- id='installed_incoming_webhooks.header'
- defaultMessage='Incoming Webhooks'
- />
- </Link>
- <FormattedMessage
- id='add_incoming_webhook.header'
- defaultMessage='Add'
- />
- </BackstageHeader>
- <div className='backstage-form'>
- <form className='form-horizontal'>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='displayName'
- >
- <FormattedMessage
- id='add_incoming_webhook.displayName'
- defaultMessage='Display Name'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='displayName'
- type='text'
- maxLength='64'
- className='form-control'
- value={this.state.displayName}
- onChange={this.updateDisplayName}
- />
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='description'
- >
- <FormattedMessage
- id='add_incoming_webhook.description'
- defaultMessage='Description'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='description'
- type='text'
- maxLength='128'
- className='form-control'
- value={this.state.description}
- onChange={this.updateDescription}
- />
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='channelId'
- >
- <FormattedMessage
- id='add_incoming_webhook.channel'
- defaultMessage='Channel'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <ChannelSelect
- id='channelId'
- value={this.state.channelId}
- onChange={this.updateChannelId}
- selectOpen={true}
- selectPrivate={true}
- />
- </div>
- </div>
- <div className='backstage-form__footer'>
- <FormError errors={[this.state.serverError, this.state.clientError]}/>
- <Link
- className='btn btn-sm'
- to={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/incoming_webhooks'}
- >
- <FormattedMessage
- id='add_incoming_webhook.cancel'
- defaultMessage='Cancel'
- />
- </Link>
- <SpinnerButton
- className='btn btn-primary'
- type='submit'
- spinning={this.state.saving}
- onClick={this.handleSubmit}
- >
- <FormattedMessage
- id='add_incoming_webhook.save'
- defaultMessage='Save'
- />
- </SpinnerButton>
- </div>
- </form>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/backstage/add_outgoing_webhook.jsx b/webapp/components/backstage/add_outgoing_webhook.jsx
deleted file mode 100644
index 5f9d96249..000000000
--- a/webapp/components/backstage/add_outgoing_webhook.jsx
+++ /dev/null
@@ -1,341 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import * as AsyncClient from 'utils/async_client.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-import BackstageHeader from './backstage_header.jsx';
-import ChannelSelect from 'components/channel_select.jsx';
-import {FormattedMessage} from 'react-intl';
-import FormError from 'components/form_error.jsx';
-import {browserHistory, Link} from 'react-router/es6';
-import SpinnerButton from 'components/spinner_button.jsx';
-
-export default class AddOutgoingWebhook extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleSubmit = this.handleSubmit.bind(this);
-
- this.updateDisplayName = this.updateDisplayName.bind(this);
- this.updateDescription = this.updateDescription.bind(this);
- this.updateContentType = this.updateContentType.bind(this);
- this.updateChannelId = this.updateChannelId.bind(this);
- this.updateTriggerWords = this.updateTriggerWords.bind(this);
- this.updateCallbackUrls = this.updateCallbackUrls.bind(this);
-
- this.state = {
- displayName: '',
- description: '',
- contentType: 'application/x-www-form-urlencoded',
- channelId: '',
- triggerWords: '',
- callbackUrls: '',
- saving: false,
- serverError: '',
- clientError: null
- };
- }
-
- handleSubmit(e) {
- e.preventDefault();
-
- if (this.state.saving) {
- return;
- }
-
- this.setState({
- saving: true,
- serverError: '',
- clientError: ''
- });
-
- const triggerWords = [];
- if (this.state.triggerWords) {
- for (let triggerWord of this.state.triggerWords.split('\n')) {
- triggerWord = triggerWord.trim();
-
- if (triggerWord.length > 0) {
- triggerWords.push(triggerWord);
- }
- }
- }
-
- if (!this.state.channelId && triggerWords.length === 0) {
- this.setState({
- saving: false,
- clientError: (
- <FormattedMessage
- id='add_outgoing_webhook.triggerWordsOrChannelRequired'
- defaultMessage='A valid channel or a list of trigger words is required'
- />
- )
- });
-
- return;
- }
-
- const callbackUrls = [];
- for (let callbackUrl of this.state.callbackUrls.split('\n')) {
- callbackUrl = callbackUrl.trim();
-
- if (callbackUrl.length > 0) {
- callbackUrls.push(callbackUrl);
- }
- }
-
- if (callbackUrls.length === 0) {
- this.setState({
- saving: false,
- clientError: (
- <FormattedMessage
- id='add_outgoing_webhook.callbackUrlsRequired'
- defaultMessage='One or more callback URLs are required'
- />
- )
- });
-
- return;
- }
-
- const hook = {
- channel_id: this.state.channelId,
- trigger_words: triggerWords,
- callback_urls: callbackUrls,
- display_name: this.state.displayName,
- content_type: this.state.contentType,
- description: this.state.description
- };
-
- AsyncClient.addOutgoingHook(
- hook,
- () => {
- browserHistory.push('/' + Utils.getTeamNameFromUrl() + '/settings/integrations/outgoing_webhooks');
- },
- (err) => {
- this.setState({
- saving: false,
- serverError: err.message
- });
- }
- );
- }
-
- updateDisplayName(e) {
- this.setState({
- displayName: e.target.value
- });
- }
-
- updateDescription(e) {
- this.setState({
- description: e.target.value
- });
- }
-
- updateContentType(e) {
- this.setState({
- contentType: e.target.value
- });
- }
-
- 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() {
- const contentTypeOption1 = 'application/x-www-form-urlencoded';
- const contentTypeOption2 = 'application/json';
- return (
- <div className='backstage-content'>
- <BackstageHeader>
- <Link to={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/outgoing_webhooks'}>
- <FormattedMessage
- id='installed_outgoing_webhooks.header'
- defaultMessage='Outgoing Webhooks'
- />
- </Link>
- <FormattedMessage
- id='add_outgoing_webhook.header'
- defaultMessage='Add'
- />
- </BackstageHeader>
- <div className='backstage-form'>
- <form className='form-horizontal'>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='displayName'
- >
- <FormattedMessage
- id='add_outgoing_webhook.displayName'
- defaultMessage='Display Name'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='displayName'
- type='text'
- maxLength='64'
- className='form-control'
- value={this.state.displayName}
- onChange={this.updateDisplayName}
- />
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='description'
- >
- <FormattedMessage
- id='add_outgoing_webhook.description'
- defaultMessage='Description'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='description'
- type='text'
- maxLength='128'
- className='form-control'
- value={this.state.description}
- onChange={this.updateDescription}
- />
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='contentType'
- >
- <FormattedMessage
- id='add_outgoing_webhook.content_Type'
- defaultMessage='Content Type'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <select
- className='form-control'
- value={this.state.contentType}
- onChange={this.updateContentType}
- >
- <option
- value={contentTypeOption1}
- >
- {contentTypeOption1}
- </option>
- <option
- value={contentTypeOption2}
- >
- {contentTypeOption2}
- </option>
- </select>
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='channelId'
- >
- <FormattedMessage
- id='add_outgoing_webhook.channel'
- defaultMessage='Channel'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <ChannelSelect
- id='channelId'
- value={this.state.channelId}
- onChange={this.updateChannelId}
- selectOpen={true}
- />
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='triggerWords'
- >
- <FormattedMessage
- id='add_outgoing_webhook.triggerWords'
- defaultMessage='Trigger Words (One Per Line)'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <textarea
- id='triggerWords'
- rows='3'
- maxLength='1000'
- className='form-control'
- value={this.state.triggerWords}
- onChange={this.updateTriggerWords}
- />
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='callbackUrls'
- >
- <FormattedMessage
- id='add_outgoing_webhook.callbackUrls'
- defaultMessage='Callback URLs (One Per Line)'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <textarea
- id='callbackUrls'
- rows='3'
- maxLength='1000'
- className='form-control'
- value={this.state.callbackUrls}
- onChange={this.updateCallbackUrls}
- />
- </div>
- </div>
- <div className='backstage-form__footer'>
- <FormError errors={[this.state.serverError, this.state.clientError]}/>
- <Link
- className='btn btn-sm'
- to={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/outgoing_webhooks'}
- >
- <FormattedMessage
- id='add_outgoing_webhook.cancel'
- defaultMessage='Cancel'
- />
- </Link>
- <SpinnerButton
- className='btn btn-primary'
- type='submit'
- spinning={this.state.saving}
- onClick={this.handleSubmit}
- >
- <FormattedMessage
- id='add_outgoing_webhook.save'
- defaultMessage='Save'
- />
- </SpinnerButton>
- </div>
- </form>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/backstage/backstage_controller.jsx b/webapp/components/backstage/backstage_controller.jsx
new file mode 100644
index 000000000..690880071
--- /dev/null
+++ b/webapp/components/backstage/backstage_controller.jsx
@@ -0,0 +1,71 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+
+import TeamStore from 'stores/team_store.jsx';
+
+import BackstageSidebar from './components/backstage_sidebar.jsx';
+import BackstageNavbar from './components/backstage_navbar.jsx';
+import ErrorBar from 'components/error_bar.jsx';
+
+export default class BackstageController extends React.Component {
+ static get propTypes() {
+ return {
+ children: React.PropTypes.node.isRequired,
+ params: React.PropTypes.object.isRequired,
+ user: React.PropTypes.user.isRequired
+ };
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.onTeamChange = this.onTeamChange.bind(this);
+
+ this.state = {
+ team: props.params.team ? TeamStore.getByName(props.params.team) : TeamStore.getCurrent()
+ };
+ }
+
+ componentDidMount() {
+ TeamStore.addChangeListener(this.onTeamChange);
+ }
+
+ componentWillUnmount() {
+ TeamStore.removeChangeListener(this.onTeamChange);
+ }
+
+ onTeamChange() {
+ this.state = {
+ team: this.props.params.team ? TeamStore.getByName(this.props.params.team) : TeamStore.getCurrent()
+ };
+ }
+
+ render() {
+ return (
+ <div className='backstage'>
+ <ErrorBar/>
+ <BackstageNavbar team={this.state.team}/>
+ <div className='backstage-body'>
+ <BackstageSidebar
+ team={this.state.team}
+ user={this.props.user}
+ />
+ {
+ React.Children.map(this.props.children, (child) => {
+ if (!child) {
+ return child;
+ }
+
+ return React.cloneElement(child, {
+ team: this.state.team,
+ user: this.props.user
+ });
+ })
+ }
+ </div>
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/webapp/components/backstage/backstage_category.jsx b/webapp/components/backstage/components/backstage_category.jsx
index 1d4b11ca3..74dcf3476 100644
--- a/webapp/components/backstage/backstage_category.jsx
+++ b/webapp/components/backstage/components/backstage_category.jsx
@@ -59,6 +59,7 @@ export default class BackstageCategory extends React.Component {
to={link}
className='category-title'
activeClassName='category-title--active'
+ onlyActiveOnIndex={true}
>
<i className={'fa ' + icon}/>
<span className='category-title__text'>
diff --git a/webapp/components/backstage/backstage_header.jsx b/webapp/components/backstage/components/backstage_header.jsx
index 37b4be349..37b4be349 100644
--- a/webapp/components/backstage/backstage_header.jsx
+++ b/webapp/components/backstage/components/backstage_header.jsx
diff --git a/webapp/components/backstage/components/backstage_list.jsx b/webapp/components/backstage/components/backstage_list.jsx
new file mode 100644
index 000000000..81b8ec4d9
--- /dev/null
+++ b/webapp/components/backstage/components/backstage_list.jsx
@@ -0,0 +1,108 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+
+import * as Utils from 'utils/utils.jsx';
+
+import {Link} from 'react-router';
+import LoadingScreen from 'components/loading_screen.jsx';
+
+export default class BackstageList extends React.Component {
+ static propTypes = {
+ children: React.PropTypes.node,
+ header: React.PropTypes.node.isRequired,
+ addLink: React.PropTypes.string,
+ addText: React.PropTypes.node,
+ emptyText: React.PropTypes.node,
+ loading: React.PropTypes.bool.isRequired,
+ searchPlaceholder: React.PropTypes.string
+ }
+
+ static defaultProps = {
+ searchPlaceholder: Utils.localizeMessage('backstage.search', 'Search')
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.updateFilter = this.updateFilter.bind(this);
+
+ this.state = {
+ filter: ''
+ };
+ }
+
+ updateFilter(e) {
+ this.setState({
+ filter: e.target.value
+ });
+ }
+
+ render() {
+ const filter = this.state.filter.toLowerCase();
+
+ let children;
+ if (this.props.loading) {
+ children = <LoadingScreen/>;
+ } else {
+ children = React.Children.map(this.props.children, (child) => {
+ return React.cloneElement(child, {filter});
+ });
+
+ if (children.length === 0 && this.props.emptyText) {
+ children = (
+ <span className='backstage-list__item backstage-list__empty'>
+ {this.props.emptyText}
+ </span>
+ );
+ }
+ }
+
+ let addLink = null;
+ if (this.props.addLink && this.props.addText) {
+ addLink = (
+ <Link
+ className='add-link'
+ to={this.props.addLink}
+ >
+ <button
+ type='button'
+ className='btn btn-primary'
+ >
+ <span>
+ {this.props.addText}
+ </span>
+ </button>
+ </Link>
+ );
+ }
+
+ return (
+ <div className='backstage-content'>
+ <div className='backstage-header'>
+ <h1>
+ {this.props.header}
+ </h1>
+ {addLink}
+ </div>
+ <div className='backstage-filters'>
+ <div className='backstage-filter__search'>
+ <i className='fa fa-search'></i>
+ <input
+ type='search'
+ className='form-control'
+ placeholder={this.props.searchPlaceholder}
+ value={this.state.filter}
+ onChange={this.updateFilter}
+ style={{flexGrow: 0, flexShrink: 0}}
+ />
+ </div>
+ </div>
+ <div className='backstage-list'>
+ {children}
+ </div>
+ </div>
+ );
+ }
+}
diff --git a/webapp/components/backstage/backstage_navbar.jsx b/webapp/components/backstage/components/backstage_navbar.jsx
index 26ab44c87..7bccfc9f7 100644
--- a/webapp/components/backstage/backstage_navbar.jsx
+++ b/webapp/components/backstage/components/backstage_navbar.jsx
@@ -1,52 +1,28 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import $ from 'jquery';
-
import React from 'react';
-import TeamStore from 'stores/team_store.jsx';
-
import {FormattedMessage} from 'react-intl';
import {Link} from 'react-router/es6';
export default class BackstageNavbar extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleChange = this.handleChange.bind(this);
-
- this.state = {
- team: TeamStore.getCurrent()
+ static get propTypes() {
+ return {
+ team: React.propTypes.object.isRequired
};
}
- componentDidMount() {
- TeamStore.addChangeListener(this.handleChange);
- $('body').addClass('backstage');
- }
-
- componentWillUnmount() {
- TeamStore.removeChangeListener(this.handleChange);
- $('body').removeClass('backstage');
- }
-
- handleChange() {
- this.setState({
- team: TeamStore.getCurrent()
- });
- }
-
render() {
- if (!this.state.team) {
+ if (!this.props.team) {
return null;
}
return (
- <div className='backstage-navbar row'>
+ <div className='backstage-navbar'>
<Link
className='backstage-navbar__back'
- to={`/${this.state.team.name}/channels/town-square`}
+ to={`/${this.props.team.name}/channels/town-square`}
>
<i className='fa fa-angle-left'/>
<span>
diff --git a/webapp/components/backstage/backstage_section.jsx b/webapp/components/backstage/components/backstage_section.jsx
index c8b63af18..c8b63af18 100644
--- a/webapp/components/backstage/backstage_section.jsx
+++ b/webapp/components/backstage/components/backstage_section.jsx
diff --git a/webapp/components/backstage/backstage_sidebar.jsx b/webapp/components/backstage/components/backstage_sidebar.jsx
index 4d8d8337d..a17d830b0 100644
--- a/webapp/components/backstage/backstage_sidebar.jsx
+++ b/webapp/components/backstage/components/backstage_sidebar.jsx
@@ -3,13 +3,51 @@
import React from 'react';
-import * as Utils from 'utils/utils.jsx';
+import TeamStore from 'stores/team_store.jsx';
+
import BackstageCategory from './backstage_category.jsx';
import BackstageSection from './backstage_section.jsx';
import {FormattedMessage} from 'react-intl';
export default class BackstageSidebar extends React.Component {
- render() {
+ static get propTypes() {
+ return {
+ team: React.PropTypes.object.isRequired,
+ user: React.PropTypes.object.isRequired
+ };
+ }
+
+ renderCustomEmoji() {
+ if (window.mm_config.EnableCustomEmoji !== 'true') {
+ return null;
+ }
+
+ return (
+ <BackstageCategory
+ name='emoji'
+ parentLink={'/' + this.props.team.name}
+ icon='fa-smile-o'
+ title={
+ <FormattedMessage
+ id='backstage_sidebar.emoji'
+ defaultMessage='Custom Emoji'
+ />
+ }
+ />
+ );
+ }
+
+ renderIntegrations() {
+ if (window.mm_config.EnableIncomingWebhooks !== 'true' &&
+ window.mm_config.EnableOutgoingWebhooks !== 'true' &&
+ window.mm_config.EnableCommands !== 'true') {
+ return null;
+ }
+
+ if (window.mm_config.RestrictCustomEmojiCreation !== 'all' && !TeamStore.isTeamAdmin(this.props.user.id, this.props.team.id)) {
+ return null;
+ }
+
let incomingWebhooks = null;
if (window.mm_config.EnableIncomingWebhooks === 'true') {
incomingWebhooks = (
@@ -56,23 +94,30 @@ export default class BackstageSidebar extends React.Component {
}
return (
+ <BackstageCategory
+ name='integrations'
+ parentLink={'/' + this.props.team.name}
+ icon='fa-link'
+ title={
+ <FormattedMessage
+ id='backstage_sidebar.integrations'
+ defaultMessage='Integrations'
+ />
+ }
+ >
+ {incomingWebhooks}
+ {outgoingWebhooks}
+ {commands}
+ </BackstageCategory>
+ );
+ }
+
+ render() {
+ return (
<div className='backstage-sidebar'>
<ul>
- <BackstageCategory
- name='integrations'
- parentLink={'/' + Utils.getTeamNameFromUrl() + '/settings'}
- icon='fa-link'
- title={
- <FormattedMessage
- id='backstage_sidebar.integrations'
- defaultMessage='Integrations'
- />
- }
- >
- {incomingWebhooks}
- {outgoingWebhooks}
- {commands}
- </BackstageCategory>
+ {this.renderCustomEmoji()}
+ {this.renderIntegrations()}
</ul>
</div>
);
diff --git a/webapp/components/backstage/installed_command.jsx b/webapp/components/backstage/installed_command.jsx
deleted file mode 100644
index 88f43f674..000000000
--- a/webapp/components/backstage/installed_command.jsx
+++ /dev/null
@@ -1,146 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import * as Utils from 'utils/utils.jsx';
-
-import {FormattedMessage} from 'react-intl';
-
-export default class InstalledCommand extends React.Component {
- static get propTypes() {
- return {
- command: React.PropTypes.object.isRequired,
- onRegenToken: React.PropTypes.func.isRequired,
- onDelete: React.PropTypes.func.isRequired,
- filter: React.PropTypes.string
- };
- }
-
- constructor(props) {
- super(props);
-
- this.handleRegenToken = this.handleRegenToken.bind(this);
- this.handleDelete = this.handleDelete.bind(this);
-
- this.matchesFilter = this.matchesFilter.bind(this);
- }
-
- handleRegenToken(e) {
- e.preventDefault();
-
- this.props.onRegenToken(this.props.command);
- }
-
- handleDelete(e) {
- e.preventDefault();
-
- this.props.onDelete(this.props.command);
- }
-
- matchesFilter(command, filter) {
- if (!filter) {
- return true;
- }
-
- return command.display_name.toLowerCase().indexOf(filter) !== -1 ||
- command.description.toLowerCase().indexOf(filter) !== -1 ||
- command.trigger.toLowerCase().indexOf(filter) !== -1;
- }
-
- render() {
- const command = this.props.command;
-
- if (!this.matchesFilter(command, this.props.filter)) {
- return null;
- }
-
- let name;
- if (command.display_name) {
- name = command.display_name;
- } else {
- name = (
- <FormattedMessage
- id='installed_integraions.unnamed_command'
- defaultMessage='Unnamed Slash Command'
- />
- );
- }
-
- let description = null;
- if (command.description) {
- description = (
- <div className='item-details__row'>
- <span className='item-details__description'>
- {command.description}
- </span>
- </div>
- );
- }
-
- let trigger = '- /' + command.trigger;
- if (command.auto_complete && command.auto_complete_hint) {
- trigger += ' ' + command.auto_complete_hint;
- }
-
- return (
- <div className='backstage-list__item'>
- <div className='item-details'>
- <div className='item-details__row'>
- <span className='item-details__name'>
- {name}
- </span>
- <span className='item-details__trigger'>
- {trigger}
- </span>
- </div>
- {description}
- <div className='item-details__row'>
- <span className='item-details__token'>
- <FormattedMessage
- id='installed_integrations.token'
- defaultMessage='Token: {token}'
- values={{
- token: command.token
- }}
- />
- </span>
- </div>
- <div className='item-details__row'>
- <span className='item-details__creation'>
- <FormattedMessage
- id='installed_integrations.creation'
- defaultMessage='Created by {creator} on {createAt, date, full}'
- values={{
- creator: Utils.displayUsername(command.creator_id),
- createAt: command.create_at
- }}
- />
- </span>
- </div>
- </div>
- <div className='item-actions'>
- <a
- href='#'
- onClick={this.handleRegenToken}
- >
- <FormattedMessage
- id='installed_integrations.regenToken'
- defaultMessage='Regenerate Token'
- />
- </a>
- {' - '}
- <a
- href='#'
- onClick={this.handleDelete}
- >
- <FormattedMessage
- id='installed_integrations.delete'
- defaultMessage='Delete'
- />
- </a>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/backstage/installed_commands.jsx b/webapp/components/backstage/installed_commands.jsx
deleted file mode 100644
index df1f56687..000000000
--- a/webapp/components/backstage/installed_commands.jsx
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import * as AsyncClient from 'utils/async_client.jsx';
-import IntegrationStore from 'stores/integration_store.jsx';
-import TeamStore from 'stores/team_store.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-import {FormattedMessage} from 'react-intl';
-import InstalledCommand from './installed_command.jsx';
-import InstalledIntegrations from './installed_integrations.jsx';
-
-export default class InstalledCommands extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleIntegrationChange = this.handleIntegrationChange.bind(this);
-
- this.regenCommandToken = this.regenCommandToken.bind(this);
- this.deleteCommand = this.deleteCommand.bind(this);
-
- const teamId = TeamStore.getCurrentId();
-
- this.state = {
- commands: IntegrationStore.getCommands(teamId),
- loading: !IntegrationStore.hasReceivedCommands(teamId)
- };
- }
-
- componentDidMount() {
- IntegrationStore.addChangeListener(this.handleIntegrationChange);
-
- if (window.mm_config.EnableCommands === 'true') {
- AsyncClient.listTeamCommands();
- }
- }
-
- componentWillUnmount() {
- IntegrationStore.removeChangeListener(this.handleIntegrationChange);
- }
-
- handleIntegrationChange() {
- const teamId = TeamStore.getCurrentId();
-
- this.setState({
- commands: IntegrationStore.getCommands(teamId),
- loading: !IntegrationStore.hasReceivedCommands(teamId)
- });
- }
-
- regenCommandToken(command) {
- AsyncClient.regenCommandToken(command.id);
- }
-
- deleteCommand(command) {
- AsyncClient.deleteCommand(command.id);
- }
-
- render() {
- const commands = this.state.commands.map((command) => {
- return (
- <InstalledCommand
- key={command.id}
- command={command}
- onRegenToken={this.regenCommandToken}
- onDelete={this.deleteCommand}
- />
- );
- });
-
- return (
- <InstalledIntegrations
- header={
- <FormattedMessage
- id='installed_commands.header'
- defaultMessage='Installed Slash Commands'
- />
- }
- addText={
- <FormattedMessage
- id='installed_commands.add'
- defaultMessage='Add Slash Command'
- />
- }
- addLink={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/commands/add'}
- emptyText={
- <FormattedMessage
- id='installed_commands.empty'
- defaultMessage='No slash commands found'
- />
- }
- loading={this.state.loading}
- >
- {commands}
- </InstalledIntegrations>
- );
- }
-}
diff --git a/webapp/components/backstage/installed_incoming_webhook.jsx b/webapp/components/backstage/installed_incoming_webhook.jsx
deleted file mode 100644
index afa6e9958..000000000
--- a/webapp/components/backstage/installed_incoming_webhook.jsx
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import ChannelStore from 'stores/channel_store.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-import {FormattedMessage} from 'react-intl';
-
-export default class InstalledIncomingWebhook extends React.Component {
- static get propTypes() {
- return {
- incomingWebhook: React.PropTypes.object.isRequired,
- onDelete: React.PropTypes.func.isRequired,
- filter: React.PropTypes.string
- };
- }
-
- constructor(props) {
- super(props);
-
- this.handleDelete = this.handleDelete.bind(this);
- }
-
- handleDelete(e) {
- e.preventDefault();
-
- this.props.onDelete(this.props.incomingWebhook);
- }
-
- matchesFilter(incomingWebhook, channel, filter) {
- if (!filter) {
- return true;
- }
-
- if (incomingWebhook.display_name.toLowerCase().indexOf(filter) !== -1 ||
- incomingWebhook.description.toLowerCase().indexOf(filter) !== -1) {
- return true;
- }
-
- if (incomingWebhook.channel_id) {
- if (channel && channel.name.toLowerCase().indexOf(filter) !== -1) {
- return true;
- }
- }
-
- return false;
- }
-
- render() {
- const incomingWebhook = this.props.incomingWebhook;
- const channel = ChannelStore.get(incomingWebhook.channel_id);
-
- if (!this.matchesFilter(incomingWebhook, channel, this.props.filter)) {
- return null;
- }
-
- let displayName;
- if (incomingWebhook.display_name) {
- displayName = incomingWebhook.display_name;
- } else if (channel) {
- displayName = channel.display_name;
- } else {
- displayName = (
- <FormattedMessage
- id='installed_incoming_webhooks.unknown_channel'
- defaultMessage='A Private Webhook'
- />
- );
- }
-
- let description = null;
- if (incomingWebhook.description) {
- description = (
- <div className='item-details__row'>
- <span className='item-details__description'>
- {incomingWebhook.description}
- </span>
- </div>
- );
- }
-
- return (
- <div className='backstage-list__item'>
- <div className='item-details'>
- <div className='item-details__row'>
- <span className='item-details__name'>
- {displayName}
- </span>
- </div>
- {description}
- <div className='item-details__row'>
- <span className='item-details__url'>
- <FormattedMessage
- id='installed_integrations.url'
- defaultMessage='URL: {url}'
- values={{
- url: Utils.getWindowLocationOrigin() + '/hooks/' + incomingWebhook.id
- }}
- />
- </span>
- </div>
- <div className='tem-details__row'>
- <span className='item-details__creation'>
- <FormattedMessage
- id='installed_integrations.creation'
- defaultMessage='Created by {creator} on {createAt, date, full}'
- values={{
- creator: Utils.displayUsername(incomingWebhook.user_id),
- createAt: incomingWebhook.create_at
- }}
- />
- </span>
- </div>
- </div>
- <div className='item-actions'>
- <a
- href='#'
- onClick={this.handleDelete}
- >
- <FormattedMessage
- id='installed_integrations.delete'
- defaultMessage='Delete'
- />
- </a>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/backstage/installed_incoming_webhooks.jsx b/webapp/components/backstage/installed_incoming_webhooks.jsx
deleted file mode 100644
index 0a38a6ab5..000000000
--- a/webapp/components/backstage/installed_incoming_webhooks.jsx
+++ /dev/null
@@ -1,94 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import * as AsyncClient from 'utils/async_client.jsx';
-import IntegrationStore from 'stores/integration_store.jsx';
-import TeamStore from 'stores/team_store.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-import {FormattedMessage} from 'react-intl';
-import InstalledIncomingWebhook from './installed_incoming_webhook.jsx';
-import InstalledIntegrations from './installed_integrations.jsx';
-
-export default class InstalledIncomingWebhooks extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleIntegrationChange = this.handleIntegrationChange.bind(this);
-
- this.deleteIncomingWebhook = this.deleteIncomingWebhook.bind(this);
-
- const teamId = TeamStore.getCurrentId();
-
- this.state = {
- incomingWebhooks: IntegrationStore.getIncomingWebhooks(teamId),
- loading: !IntegrationStore.hasReceivedIncomingWebhooks(teamId)
- };
- }
-
- componentDidMount() {
- IntegrationStore.addChangeListener(this.handleIntegrationChange);
-
- if (window.mm_config.EnableIncomingWebhooks === 'true') {
- AsyncClient.listIncomingHooks();
- }
- }
-
- componentWillUnmount() {
- IntegrationStore.removeChangeListener(this.handleIntegrationChange);
- }
-
- handleIntegrationChange() {
- const teamId = TeamStore.getCurrentId();
-
- this.setState({
- incomingWebhooks: IntegrationStore.getIncomingWebhooks(teamId),
- loading: !IntegrationStore.hasReceivedIncomingWebhooks(teamId)
- });
- }
-
- deleteIncomingWebhook(incomingWebhook) {
- AsyncClient.deleteIncomingHook(incomingWebhook.id);
- }
-
- render() {
- const incomingWebhooks = this.state.incomingWebhooks.map((incomingWebhook) => {
- return (
- <InstalledIncomingWebhook
- key={incomingWebhook.id}
- incomingWebhook={incomingWebhook}
- onDelete={this.deleteIncomingWebhook}
- />
- );
- });
-
- return (
- <InstalledIntegrations
- header={
- <FormattedMessage
- id='installed_incoming_webhooks.header'
- defaultMessage='Installed Incoming Webhooks'
- />
- }
- addText={
- <FormattedMessage
- id='installed_incoming_webhooks.add'
- defaultMessage='Add Incoming Webhook'
- />
- }
- addLink={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/incoming_webhooks/add'}
- emptyText={
- <FormattedMessage
- id='installed_incoming_webhooks.empty'
- defaultMessage='No incoming webhooks found'
- />
- }
- loading={this.state.loading}
- >
- {incomingWebhooks}
- </InstalledIntegrations>
- );
- }
-}
diff --git a/webapp/components/backstage/installed_integrations.jsx b/webapp/components/backstage/installed_integrations.jsx
deleted file mode 100644
index f6de8bc11..000000000
--- a/webapp/components/backstage/installed_integrations.jsx
+++ /dev/null
@@ -1,101 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import * as Utils from 'utils/utils.jsx';
-
-import {Link} from 'react-router/es6';
-import LoadingScreen from 'components/loading_screen.jsx';
-
-export default class InstalledIntegrations extends React.Component {
- static get propTypes() {
- return {
- children: React.PropTypes.node,
- header: React.PropTypes.node.isRequired,
- addLink: React.PropTypes.string.isRequired,
- addText: React.PropTypes.node.isRequired,
- emptyText: React.PropTypes.node.isRequired,
- loading: React.PropTypes.bool.isRequired
- };
- }
-
- constructor(props) {
- super(props);
-
- this.updateFilter = this.updateFilter.bind(this);
-
- this.state = {
- filter: ''
- };
- }
-
- updateFilter(e) {
- this.setState({
- filter: e.target.value
- });
- }
-
- render() {
- const filter = this.state.filter.toLowerCase();
-
- let children;
-
- if (this.props.loading) {
- children = <LoadingScreen/>;
- } else {
- children = React.Children.map(this.props.children, (child) => {
- return React.cloneElement(child, {filter});
- });
-
- if (children.length === 0) {
- children = (
- <span className='backstage-list__item backstage-list_empty'>
- {this.props.emptyText}
- </span>
- );
- }
- }
-
- return (
- <div className='backstage-content'>
- <div className='installed-integrations'>
- <div className='backstage-header'>
- <h1>
- {this.props.header}
- </h1>
- <Link
- className='add-integrations-link'
- to={this.props.addLink}
- >
- <button
- type='button'
- className='btn btn-primary'
- >
- <span>
- {this.props.addText}
- </span>
- </button>
- </Link>
- </div>
- <div className='backstage-filters'>
- <div className='backstage-filter__search'>
- <i className='fa fa-search'></i>
- <input
- type='search'
- className='form-control'
- placeholder={Utils.localizeMessage('installed_integrations.search', 'Search Integrations')}
- value={this.state.filter}
- onChange={this.updateFilter}
- style={{flexGrow: 0, flexShrink: 0}}
- />
- </div>
- </div>
- <div className='backstage-list'>
- {children}
- </div>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/backstage/installed_outgoing_webhook.jsx b/webapp/components/backstage/installed_outgoing_webhook.jsx
deleted file mode 100644
index 99f2439ec..000000000
--- a/webapp/components/backstage/installed_outgoing_webhook.jsx
+++ /dev/null
@@ -1,199 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import ChannelStore from 'stores/channel_store.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-import {FormattedMessage} from 'react-intl';
-
-export default class InstalledOutgoingWebhook extends React.Component {
- static get propTypes() {
- return {
- outgoingWebhook: React.PropTypes.object.isRequired,
- onRegenToken: React.PropTypes.func.isRequired,
- onDelete: React.PropTypes.func.isRequired,
- filter: React.PropTypes.string
- };
- }
-
- constructor(props) {
- super(props);
-
- this.handleRegenToken = this.handleRegenToken.bind(this);
- this.handleDelete = this.handleDelete.bind(this);
- }
-
- handleRegenToken(e) {
- e.preventDefault();
-
- this.props.onRegenToken(this.props.outgoingWebhook);
- }
-
- handleDelete(e) {
- e.preventDefault();
-
- this.props.onDelete(this.props.outgoingWebhook);
- }
-
- matchesFilter(outgoingWebhook, channel, filter) {
- if (!filter) {
- return true;
- }
-
- if (outgoingWebhook.display_name.toLowerCase().indexOf(filter) !== -1 ||
- outgoingWebhook.description.toLowerCase().indexOf(filter) !== -1) {
- return true;
- }
-
- for (const trigger of outgoingWebhook.trigger_words) {
- if (trigger.toLowerCase().indexOf(filter) !== -1) {
- return true;
- }
- }
-
- if (channel) {
- if (channel && channel.name.toLowerCase().indexOf(filter) !== -1) {
- return true;
- }
- }
-
- return false;
- }
-
- render() {
- const outgoingWebhook = this.props.outgoingWebhook;
- const channel = ChannelStore.get(outgoingWebhook.channel_id);
-
- if (!this.matchesFilter(outgoingWebhook, channel, this.props.filter)) {
- return null;
- }
-
- let displayName;
- if (outgoingWebhook.display_name) {
- displayName = outgoingWebhook.display_name;
- } else if (channel) {
- displayName = channel.display_name;
- } else {
- displayName = (
- <FormattedMessage
- id='installed_outgoing_webhooks.unknown_channel'
- defaultMessage='A Private Webhook'
- />
- );
- }
-
- let description = null;
- if (outgoingWebhook.description) {
- description = (
- <div className='item-details__row'>
- <span className='item-details__description'>
- {outgoingWebhook.description}
- </span>
- </div>
- );
- }
-
- let triggerWords = null;
- if (outgoingWebhook.trigger_words && outgoingWebhook.trigger_words.length > 0) {
- triggerWords = (
- <div className='item-details__row'>
- <span className='item-details__trigger-words'>
- <FormattedMessage
- id='installed_integrations.triggerWords'
- defaultMessage='Trigger Words: {triggerWords}'
- values={{
- triggerWords: outgoingWebhook.trigger_words.join(', ')
- }}
- />
- </span>
- </div>
- );
- }
-
- let urls = (
- <div className='item-details__row'>
- <span className='item-details__url'>
- <FormattedMessage
- id='installed_integrations.callback_urls'
- defaultMessage='Callback URLs: {urls}'
- values={{
- urls: outgoingWebhook.callback_urls.join(', ')
- }}
- />
- </span>
- </div>
- );
-
- return (
- <div className='backstage-list__item'>
- <div className='item-details'>
- <div className='item-details__row'>
- <span className='item-details__name'>
- {displayName}
- </span>
- </div>
- {description}
- <div className='item-details__row'>
- <span className='item-details__content_type'>
- <FormattedMessage
- id='installed_integrations.content_type'
- defaultMessage='Content-Type: {contentType}'
- values={{
- contentType: outgoingWebhook.content_type || 'application/x-www-form-urlencoded'
- }}
- />
- </span>
- </div>
- {triggerWords}
- <div className='item-details__row'>
- <span className='item-details__token'>
- <FormattedMessage
- id='installed_integrations.token'
- defaultMessage='Token: {token}'
- values={{
- token: outgoingWebhook.token
- }}
- />
- </span>
- </div>
- <div className='item-details__row'>
- <span className='item-details__creation'>
- <FormattedMessage
- id='installed_integrations.creation'
- defaultMessage='Created by {creator} on {createAt, date, full}'
- values={{
- creator: Utils.displayUsername(outgoingWebhook.creator_id),
- createAt: outgoingWebhook.create_at
- }}
- />
- </span>
- </div>
- {urls}
- </div>
- <div className='item-actions'>
- <a
- href='#'
- onClick={this.handleRegenToken}
- >
- <FormattedMessage
- id='installed_integrations.regenToken'
- defaultMessage='Regen Token'
- />
- </a>
- {' - '}
- <a
- href='#'
- onClick={this.handleDelete}
- >
- <FormattedMessage
- id='installed_integrations.delete'
- defaultMessage='Delete'
- />
- </a>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/backstage/installed_outgoing_webhooks.jsx b/webapp/components/backstage/installed_outgoing_webhooks.jsx
deleted file mode 100644
index b79bc3530..000000000
--- a/webapp/components/backstage/installed_outgoing_webhooks.jsx
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import * as AsyncClient from 'utils/async_client.jsx';
-import IntegrationStore from 'stores/integration_store.jsx';
-import TeamStore from 'stores/team_store.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-import {FormattedMessage} from 'react-intl';
-import InstalledOutgoingWebhook from './installed_outgoing_webhook.jsx';
-import InstalledIntegrations from './installed_integrations.jsx';
-
-export default class InstalledOutgoingWebhooks extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleIntegrationChange = this.handleIntegrationChange.bind(this);
-
- this.regenOutgoingWebhookToken = this.regenOutgoingWebhookToken.bind(this);
- this.deleteOutgoingWebhook = this.deleteOutgoingWebhook.bind(this);
-
- const teamId = TeamStore.getCurrentId();
-
- this.state = {
- outgoingWebhooks: IntegrationStore.getOutgoingWebhooks(teamId),
- loading: !IntegrationStore.hasReceivedOutgoingWebhooks(teamId)
- };
- }
-
- componentDidMount() {
- IntegrationStore.addChangeListener(this.handleIntegrationChange);
-
- if (window.mm_config.EnableOutgoingWebhooks === 'true') {
- AsyncClient.listOutgoingHooks();
- }
- }
-
- componentWillUnmount() {
- IntegrationStore.removeChangeListener(this.handleIntegrationChange);
- }
-
- handleIntegrationChange() {
- const teamId = TeamStore.getCurrentId();
-
- this.setState({
- outgoingWebhooks: IntegrationStore.getOutgoingWebhooks(teamId),
- loading: !IntegrationStore.hasReceivedOutgoingWebhooks(teamId)
- });
- }
-
- regenOutgoingWebhookToken(outgoingWebhook) {
- AsyncClient.regenOutgoingHookToken(outgoingWebhook.id);
- }
-
- deleteOutgoingWebhook(outgoingWebhook) {
- AsyncClient.deleteOutgoingHook(outgoingWebhook.id);
- }
-
- render() {
- const outgoingWebhooks = this.state.outgoingWebhooks.map((outgoingWebhook) => {
- return (
- <InstalledOutgoingWebhook
- key={outgoingWebhook.id}
- outgoingWebhook={outgoingWebhook}
- onRegenToken={this.regenOutgoingWebhookToken}
- onDelete={this.deleteOutgoingWebhook}
- />
- );
- });
-
- return (
- <InstalledIntegrations
- header={
- <FormattedMessage
- id='installed_outgoing_webhooks.header'
- defaultMessage='Installed Outgoing Webhooks'
- />
- }
- addText={
- <FormattedMessage
- id='installed_outgoing_webhooks.add'
- defaultMessage='Add Outgoing Webhook'
- />
- }
- addLink={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/outgoing_webhooks/add'}
- emptyText={
- <FormattedMessage
- id='installed_outgoing_webhooks.empty'
- defaultMessage='No outgoing webhooks found'
- />
- }
- loading={this.state.loading}
- >
- {outgoingWebhooks}
- </InstalledIntegrations>
- );
- }
-}
diff --git a/webapp/components/backstage/integration_option.jsx b/webapp/components/backstage/integration_option.jsx
deleted file mode 100644
index 483e6a888..000000000
--- a/webapp/components/backstage/integration_option.jsx
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import {Link} from 'react-router/es6';
-
-export default class IntegrationOption extends React.Component {
- static get propTypes() {
- return {
- image: React.PropTypes.string.isRequired,
- title: React.PropTypes.node.isRequired,
- description: React.PropTypes.node.isRequired,
- link: React.PropTypes.string.isRequired
- };
- }
-
- render() {
- const {image, title, description, link} = this.props;
-
- return (
- <Link
- to={link}
- className='integration-option'
- >
- <img
- className='integration-option__image'
- src={image}
- />
- <div className='integration-option__title'>
- {title}
- </div>
- <div className='integration-option__description'>
- {description}
- </div>
- </Link>
- );
- }
-}
diff --git a/webapp/components/backstage/integrations.jsx b/webapp/components/backstage/integrations.jsx
deleted file mode 100644
index fdd75026a..000000000
--- a/webapp/components/backstage/integrations.jsx
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import {FormattedMessage} from 'react-intl';
-import IntegrationOption from './integration_option.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-import WebhookIcon from 'images/webhook_icon.jpg';
-
-export default class Integrations extends React.Component {
- render() {
- const options = [];
-
- if (window.mm_config.EnableIncomingWebhooks === 'true') {
- options.push(
- <IntegrationOption
- key='incomingWebhook'
- image={WebhookIcon}
- title={
- <FormattedMessage
- id='integrations.incomingWebhook.title'
- defaultMessage='Incoming Webhook'
- />
- }
- description={
- <FormattedMessage
- id='integrations.incomingWebhook.description'
- defaultMessage='Incoming webhooks allow external integrations to send messages'
- />
- }
- link={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/incoming_webhooks'}
- />
- );
- }
-
- if (window.mm_config.EnableOutgoingWebhooks === 'true') {
- options.push(
- <IntegrationOption
- key='outgoingWebhook'
- image={WebhookIcon}
- title={
- <FormattedMessage
- id='integrations.outgoingWebhook.title'
- defaultMessage='Outgoing Webhook'
- />
- }
- description={
- <FormattedMessage
- id='integrations.outgoingWebhook.description'
- defaultMessage='Outgoing webhooks allow external integrations to receive and respond to messages'
- />
- }
- link={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/outgoing_webhooks'}
- />
- );
- }
-
- if (window.mm_config.EnableCommands === 'true') {
- options.push(
- <IntegrationOption
- key='command'
- image={WebhookIcon}
- title={
- <FormattedMessage
- id='integrations.command.title'
- defaultMessage='Slash Command'
- />
- }
- description={
- <FormattedMessage
- id='integrations.command.description'
- defaultMessage='Slash commands send events to an external integration'
- />
- }
- link={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/commands'}
- />
- );
- }
-
- return (
- <div className='backstage-content row'>
- <div className='backstage-header'>
- <h1>
- <FormattedMessage
- id='integrations.header'
- defaultMessage='Integrations'
- />
- </h1>
- </div>
- <div>
- {options}
- </div>
- </div>
- );
- }
-}
-