Merge pull request #2561 from hmhealey/plt1736
PLT-1736 Initial Backstage Work
Этот коммит содержится в:
188
webapp/components/backstage/add_incoming_webhook.jsx
Обычный файл
188
webapp/components/backstage/add_incoming_webhook.jsx
Обычный файл
@@ -0,0 +1,188 @@
|
|||||||
|
// 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 {browserHistory} from 'react-router';
|
||||||
|
|
||||||
|
import ChannelSelect from 'components/channel_select.jsx';
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
import FormError from 'components/form_error.jsx';
|
||||||
|
import {Link} from 'react-router';
|
||||||
|
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.updateName = this.updateName.bind(this);
|
||||||
|
this.updateDescription = this.updateDescription.bind(this);
|
||||||
|
this.updateChannelId = this.updateChannelId.bind(this);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
name: '',
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
AsyncClient.addIncomingHook(
|
||||||
|
hook,
|
||||||
|
() => {
|
||||||
|
browserHistory.push('/settings/integrations/installed');
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
this.setState({
|
||||||
|
serverError: err.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateName(e) {
|
||||||
|
this.setState({
|
||||||
|
name: e.target.value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDescription(e) {
|
||||||
|
this.setState({
|
||||||
|
description: e.target.value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateChannelId(e) {
|
||||||
|
this.setState({
|
||||||
|
channelId: e.target.value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className='backstage row'>
|
||||||
|
<div className='add-incoming-webhook'>
|
||||||
|
<div className='backstage__header'>
|
||||||
|
<h1 className='text'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='add_incoming_webhook.header'
|
||||||
|
defaultMessage='Add Incoming Webhook'
|
||||||
|
/>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form className='add-incoming-webhook__body'>
|
||||||
|
<div className='add-integration__row'>
|
||||||
|
<label
|
||||||
|
className='add-integration__label'
|
||||||
|
htmlFor='name'
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='add_incoming_webhook.name'
|
||||||
|
defaultMessage='Name'
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id='name'
|
||||||
|
type='text'
|
||||||
|
value={this.state.name}
|
||||||
|
onChange={this.updateName}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='add-integration__row'>
|
||||||
|
<label
|
||||||
|
className='add-integration__label'
|
||||||
|
htmlFor='description'
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='add_incoming_webhook.description'
|
||||||
|
defaultMessage='Description'
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id='description'
|
||||||
|
type='text'
|
||||||
|
value={this.state.description}
|
||||||
|
onChange={this.updateDescription}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='add-integration__row'>
|
||||||
|
<label
|
||||||
|
className='add-integration__label'
|
||||||
|
htmlFor='channelId'
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='add_incoming_webhook.channel'
|
||||||
|
defaultMessage='Channel'
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<ChannelSelect
|
||||||
|
id='channelId'
|
||||||
|
value={this.state.channelId}
|
||||||
|
onChange={this.updateChannelId}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='add-integration__submit-row'>
|
||||||
|
<Link
|
||||||
|
className='btn btn-sm'
|
||||||
|
to={'/settings/integrations/add'}
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
<FormError errors={[this.state.serverError, this.state.clientError]}/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
78
webapp/components/backstage/add_integration.jsx
Обычный файл
78
webapp/components/backstage/add_integration.jsx
Обычный файл
@@ -0,0 +1,78 @@
|
|||||||
|
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
import AddIntegrationOption from './add_integration_option.jsx';
|
||||||
|
|
||||||
|
import WebhookIcon from 'images/webhook_icon.jpg';
|
||||||
|
|
||||||
|
export default class AddIntegration extends React.Component {
|
||||||
|
render() {
|
||||||
|
const options = [];
|
||||||
|
|
||||||
|
if (window.mm_config.EnableIncomingWebhooks === 'true') {
|
||||||
|
options.push(
|
||||||
|
<AddIntegrationOption
|
||||||
|
key='incomingWebhook'
|
||||||
|
image={WebhookIcon}
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='add_integration.incomingWebhook.title'
|
||||||
|
defaultMessage='Incoming Webhook'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
description={
|
||||||
|
<FormattedMessage
|
||||||
|
id='add_integration.incomingWebhook.description'
|
||||||
|
defaultMessage='Create webhook URLs for use in external integrations.'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
link={'/settings/integrations/add/incoming_webhook'}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.mm_config.EnableOutgoingWebhooks === 'true') {
|
||||||
|
options.push(
|
||||||
|
<AddIntegrationOption
|
||||||
|
key='outgoingWebhook'
|
||||||
|
image={WebhookIcon}
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='add_integration.outgoingWebhook.title'
|
||||||
|
defaultMessage='Outgoing Webhook'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
description={
|
||||||
|
<FormattedMessage
|
||||||
|
id='add_integration.outgoingWebhook.description'
|
||||||
|
defaultMessage='Create webhooks to send new message events to an external integration.'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
link={'/settings/integrations/add/outgoing_webhook'}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='backstage row'>
|
||||||
|
<div className='add-integration'>
|
||||||
|
<div className='backstage__header'>
|
||||||
|
<h1 className='text'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='add_integration.header'
|
||||||
|
defaultMessage='Add Integration'
|
||||||
|
/>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='add-integration__options'>
|
||||||
|
{options}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
39
webapp/components/backstage/add_integration_option.jsx
Обычный файл
39
webapp/components/backstage/add_integration_option.jsx
Обычный файл
@@ -0,0 +1,39 @@
|
|||||||
|
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import {Link} from 'react-router';
|
||||||
|
|
||||||
|
export default class AddIntegrationOption 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='add-integration-option'
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
className='add-integration-option__image'
|
||||||
|
src={image}
|
||||||
|
/>
|
||||||
|
<div className='add-integration-option__title'>
|
||||||
|
{title}
|
||||||
|
</div>
|
||||||
|
<div className='add-integration-option__description'>
|
||||||
|
{description}
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
254
webapp/components/backstage/add_outgoing_webhook.jsx
Обычный файл
254
webapp/components/backstage/add_outgoing_webhook.jsx
Обычный файл
@@ -0,0 +1,254 @@
|
|||||||
|
// 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 {browserHistory} from 'react-router';
|
||||||
|
|
||||||
|
import ChannelSelect from 'components/channel_select.jsx';
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
import FormError from 'components/form_error.jsx';
|
||||||
|
import {Link} from 'react-router';
|
||||||
|
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.updateName = this.updateName.bind(this);
|
||||||
|
this.updateDescription = this.updateDescription.bind(this);
|
||||||
|
this.updateChannelId = this.updateChannelId.bind(this);
|
||||||
|
this.updateTriggerWords = this.updateTriggerWords.bind(this);
|
||||||
|
this.updateCallbackUrls = this.updateCallbackUrls.bind(this);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
channelId: '',
|
||||||
|
triggerWords: '',
|
||||||
|
callbackUrls: '',
|
||||||
|
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.state.triggerWords) {
|
||||||
|
this.setState({
|
||||||
|
saving: false,
|
||||||
|
clientError: (
|
||||||
|
<FormattedMessage
|
||||||
|
id='add_outgoing_webhook.triggerWordsOrChannelRequired'
|
||||||
|
defaultMessage='A valid channel or a list of trigger words is required'
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.state.callbackUrls) {
|
||||||
|
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: this.state.triggerWords.split('\n').map((word) => word.trim()),
|
||||||
|
callback_urls: this.state.callbackUrls.split('\n').map((url) => url.trim())
|
||||||
|
};
|
||||||
|
|
||||||
|
AsyncClient.addOutgoingHook(
|
||||||
|
hook,
|
||||||
|
() => {
|
||||||
|
browserHistory.push('/settings/integrations/installed');
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
this.setState({
|
||||||
|
serverError: err.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateName(e) {
|
||||||
|
this.setState({
|
||||||
|
name: e.target.value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDescription(e) {
|
||||||
|
this.setState({
|
||||||
|
description: 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() {
|
||||||
|
return (
|
||||||
|
<div className='backstage row'>
|
||||||
|
<div className='add-outgoing-webhook'>
|
||||||
|
<div className='backstage__header'>
|
||||||
|
<h1 className='text'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='add_outgoing_webhook.header'
|
||||||
|
defaultMessage='Add Outgoing Webhook'
|
||||||
|
/>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form className='add-outgoing-webhook__body'>
|
||||||
|
<div className='add-integration__row'>
|
||||||
|
<label
|
||||||
|
className='add-integration__label'
|
||||||
|
htmlFor='name'
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='add_outgoing_webhook.name'
|
||||||
|
defaultMessage='Name'
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id='name'
|
||||||
|
type='text'
|
||||||
|
value={this.state.name}
|
||||||
|
onChange={this.updateName}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='add-integration__row'>
|
||||||
|
<label
|
||||||
|
className='add-integration__label'
|
||||||
|
htmlFor='description'
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='add_outgoing_webhook.description'
|
||||||
|
defaultMessage='Description'
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id='description'
|
||||||
|
type='text'
|
||||||
|
value={this.state.description}
|
||||||
|
onChange={this.updateDescription}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='add-integration__row'>
|
||||||
|
<label
|
||||||
|
className='add-integration__label'
|
||||||
|
htmlFor='channelId'
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='add_outgoing_webhook.channel'
|
||||||
|
defaultMessage='Channel'
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<ChannelSelect
|
||||||
|
id='channelId'
|
||||||
|
value={this.state.channelId}
|
||||||
|
onChange={this.updateChannelId}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='add-integration__row'>
|
||||||
|
<label
|
||||||
|
className='add-integration__label'
|
||||||
|
htmlFor='triggerWords'
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='add_outgoing_webhook.triggerWords'
|
||||||
|
defaultMessage='Trigger Words (One Per Line)'
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
id='triggerWords'
|
||||||
|
rows='3'
|
||||||
|
value={this.state.triggerWords}
|
||||||
|
onChange={this.updateTriggerWords}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='add-integration__row'>
|
||||||
|
<label
|
||||||
|
className='add-integration__label'
|
||||||
|
htmlFor='callbackUrls'
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='add_outgoing_webhook.callbackUrls'
|
||||||
|
defaultMessage='Callback URLs (One Per Line)'
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
id='callbackUrls'
|
||||||
|
rows='3'
|
||||||
|
value={this.state.callbackUrls}
|
||||||
|
onChange={this.updateCallbackUrls}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='add-integration__submit-row'>
|
||||||
|
<Link
|
||||||
|
className='btn btn-sm'
|
||||||
|
to={'/settings/integrations/add'}
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
<FormError errors={[this.state.serverError, this.state.clientError]}/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
68
webapp/components/backstage/backstage_category.jsx
Обычный файл
68
webapp/components/backstage/backstage_category.jsx
Обычный файл
@@ -0,0 +1,68 @@
|
|||||||
|
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import {Link} from 'react-router';
|
||||||
|
|
||||||
|
export default class BackstageCategory extends React.Component {
|
||||||
|
static get propTypes() {
|
||||||
|
return {
|
||||||
|
name: React.PropTypes.string.isRequired,
|
||||||
|
title: React.PropTypes.node.isRequired,
|
||||||
|
icon: React.PropTypes.string.isRequired,
|
||||||
|
parentLink: React.PropTypes.string,
|
||||||
|
children: React.PropTypes.arrayOf(React.PropTypes.element)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static get defaultProps() {
|
||||||
|
return {
|
||||||
|
parentLink: '',
|
||||||
|
children: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static get contextTypes() {
|
||||||
|
return {
|
||||||
|
router: React.PropTypes.object.isRequired
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {name, title, icon, parentLink, children} = this.props;
|
||||||
|
|
||||||
|
const link = parentLink + '/' + name;
|
||||||
|
|
||||||
|
let clonedChildren = null;
|
||||||
|
if (children.length > 0 && this.context.router.isActive(link)) {
|
||||||
|
clonedChildren = (
|
||||||
|
<ul className='sections'>
|
||||||
|
{
|
||||||
|
React.Children.map(children, (child) => {
|
||||||
|
return React.cloneElement(child, {
|
||||||
|
parentLink: link
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li className='backstage__sidebar__category'>
|
||||||
|
<Link
|
||||||
|
to={link}
|
||||||
|
className='category-title'
|
||||||
|
activeClassName='category-title--active'
|
||||||
|
>
|
||||||
|
<i className={'fa ' + icon}/>
|
||||||
|
<span className='category-title__text'>
|
||||||
|
{title}
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
{clonedChildren}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
61
webapp/components/backstage/backstage_navbar.jsx
Обычный файл
61
webapp/components/backstage/backstage_navbar.jsx
Обычный файл
@@ -0,0 +1,61 @@
|
|||||||
|
// 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 {FormattedMessage} from 'react-intl';
|
||||||
|
import {Link} from 'react-router';
|
||||||
|
|
||||||
|
export default class BackstageNavbar extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.handleChange = this.handleChange.bind(this);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
team: TeamStore.getCurrent()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
TeamStore.addChangeListener(this.handleChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
TeamStore.removeChangeListener(this.handleChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange() {
|
||||||
|
this.setState({
|
||||||
|
team: TeamStore.getCurrent()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (!this.state.team) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='backstage__navbar row'>
|
||||||
|
<Link
|
||||||
|
className='backstage__navbar__back'
|
||||||
|
to={`/${this.state.team.display_name}/channels/town-square`}
|
||||||
|
>
|
||||||
|
<i className='fa fa-angle-left'/>
|
||||||
|
<span>
|
||||||
|
<FormattedMessage
|
||||||
|
id='backstage_navbar.backToMattermost'
|
||||||
|
defaultMessage='Back to {siteName}'
|
||||||
|
values={{
|
||||||
|
siteName: global.window.mm_config.SiteName
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
80
webapp/components/backstage/backstage_section.jsx
Обычный файл
80
webapp/components/backstage/backstage_section.jsx
Обычный файл
@@ -0,0 +1,80 @@
|
|||||||
|
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import {Link} from 'react-router';
|
||||||
|
|
||||||
|
export default class BackstageSection extends React.Component {
|
||||||
|
static get propTypes() {
|
||||||
|
return {
|
||||||
|
name: React.PropTypes.string.isRequired,
|
||||||
|
title: React.PropTypes.node.isRequired,
|
||||||
|
parentLink: React.PropTypes.string,
|
||||||
|
subsection: React.PropTypes.bool,
|
||||||
|
children: React.PropTypes.arrayOf(React.PropTypes.element)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static get defaultProps() {
|
||||||
|
return {
|
||||||
|
parentLink: '',
|
||||||
|
subsection: false,
|
||||||
|
children: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static get contextTypes() {
|
||||||
|
return {
|
||||||
|
router: React.PropTypes.object.isRequired
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getLink() {
|
||||||
|
return this.props.parentLink + '/' + this.props.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {title, subsection, children} = this.props;
|
||||||
|
|
||||||
|
const link = this.getLink();
|
||||||
|
|
||||||
|
let clonedChildren = null;
|
||||||
|
if (children.length > 0) {
|
||||||
|
clonedChildren = (
|
||||||
|
<ul className='subsections'>
|
||||||
|
{
|
||||||
|
React.Children.map(children, (child) => {
|
||||||
|
return React.cloneElement(child, {
|
||||||
|
parentLink: link,
|
||||||
|
subsection: true
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let className = 'section';
|
||||||
|
if (subsection) {
|
||||||
|
className = 'subsection';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li className={className}>
|
||||||
|
<Link
|
||||||
|
className={`${className}-title`}
|
||||||
|
activeClassName={`${className}-title--active`}
|
||||||
|
onlyActiveOnIndex={true}
|
||||||
|
onClick={this.handleClick}
|
||||||
|
to={link}
|
||||||
|
>
|
||||||
|
<span className={`${className}-title__text`}>
|
||||||
|
{title}
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
{clonedChildren}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
68
webapp/components/backstage/backstage_sidebar.jsx
Обычный файл
68
webapp/components/backstage/backstage_sidebar.jsx
Обычный файл
@@ -0,0 +1,68 @@
|
|||||||
|
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
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() {
|
||||||
|
return (
|
||||||
|
<div className='backstage__sidebar'>
|
||||||
|
<ul>
|
||||||
|
<BackstageCategory
|
||||||
|
name='integrations'
|
||||||
|
parentLink={'/settings'}
|
||||||
|
icon='fa-link'
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='backstage_sidebar.integrations'
|
||||||
|
defaultMessage='Integrations'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<BackstageSection
|
||||||
|
name='installed'
|
||||||
|
title={(
|
||||||
|
<FormattedMessage
|
||||||
|
id='backstage_sidebar.integrations.installed'
|
||||||
|
defaultMessage='Installed Integrations'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<BackstageSection
|
||||||
|
name='add'
|
||||||
|
title={(
|
||||||
|
<FormattedMessage
|
||||||
|
id='backstage_sidebar.integrations.add'
|
||||||
|
defaultMessage='Add Integration'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<BackstageSection
|
||||||
|
name='incoming_webhook'
|
||||||
|
title={(
|
||||||
|
<FormattedMessage
|
||||||
|
id='backstage_sidebar.integrations.add.incomingWebhook'
|
||||||
|
defaultMessage='Incoming Webhook'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<BackstageSection
|
||||||
|
name='outgoing_webhook'
|
||||||
|
title={(
|
||||||
|
<FormattedMessage
|
||||||
|
id='backstage_sidebar.integrations.add.outgoingWebhook'
|
||||||
|
defaultMessage='Outgoing Webhook'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</BackstageSection>
|
||||||
|
</BackstageCategory>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
71
webapp/components/backstage/installed_incoming_webhook.jsx
Обычный файл
71
webapp/components/backstage/installed_incoming_webhook.jsx
Обычный файл
@@ -0,0 +1,71 @@
|
|||||||
|
// 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,
|
||||||
|
onDeleteClick: React.PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.handleDeleteClick = this.handleDeleteClick.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDeleteClick(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
this.props.onDeleteClick(this.props.incomingWebhook);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const incomingWebhook = this.props.incomingWebhook;
|
||||||
|
|
||||||
|
const channel = ChannelStore.get(incomingWebhook.channel_id);
|
||||||
|
const channelName = channel ? channel.display_name : 'cannot find channel';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='installed-integrations__item installed-integrations__incoming-webhook'>
|
||||||
|
<div className='details'>
|
||||||
|
<div className='details-row'>
|
||||||
|
<span className='name'>
|
||||||
|
{channelName}
|
||||||
|
</span>
|
||||||
|
<span className='type'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='installed_integrations.incomingWebhookType'
|
||||||
|
defaultMessage='(Incoming Webhook)'
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className='details-row'>
|
||||||
|
<span className='description'>
|
||||||
|
{Utils.getWindowLocationOrigin() + '/hooks/' + incomingWebhook.id}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='actions'>
|
||||||
|
<a
|
||||||
|
href='#'
|
||||||
|
onClick={this.handleDeleteClick}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='installed_integrations.delete'
|
||||||
|
defaultMessage='Delete'
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
289
webapp/components/backstage/installed_integrations.jsx
Обычный файл
289
webapp/components/backstage/installed_integrations.jsx
Обычный файл
@@ -0,0 +1,289 @@
|
|||||||
|
// 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 ChannelStore from 'stores/channel_store.jsx';
|
||||||
|
import IntegrationStore from 'stores/integration_store.jsx';
|
||||||
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
import InstalledIncomingWebhook from './installed_incoming_webhook.jsx';
|
||||||
|
import InstalledOutgoingWebhook from './installed_outgoing_webhook.jsx';
|
||||||
|
import {Link} from 'react-router';
|
||||||
|
|
||||||
|
export default class InstalledIntegrations extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.handleIntegrationChange = this.handleIntegrationChange.bind(this);
|
||||||
|
this.updateFilter = this.updateFilter.bind(this);
|
||||||
|
this.updateTypeFilter = this.updateTypeFilter.bind(this);
|
||||||
|
|
||||||
|
this.deleteIncomingWebhook = this.deleteIncomingWebhook.bind(this);
|
||||||
|
this.regenOutgoingWebhookToken = this.regenOutgoingWebhookToken.bind(this);
|
||||||
|
this.deleteOutgoingWebhook = this.deleteOutgoingWebhook.bind(this);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
incomingWebhooks: [],
|
||||||
|
outgoingWebhooks: [],
|
||||||
|
typeFilter: '',
|
||||||
|
filter: ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
IntegrationStore.addChangeListener(this.handleIntegrationChange);
|
||||||
|
|
||||||
|
if (window.mm_config.EnableIncomingWebhooks === 'true') {
|
||||||
|
if (IntegrationStore.hasReceivedIncomingWebhooks()) {
|
||||||
|
this.setState({
|
||||||
|
incomingWebhooks: IntegrationStore.getIncomingWebhooks()
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
AsyncClient.listIncomingHooks();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.mm_config.EnableOutgoingWebhooks === 'true') {
|
||||||
|
if (IntegrationStore.hasReceivedOutgoingWebhooks()) {
|
||||||
|
this.setState({
|
||||||
|
outgoingWebhooks: IntegrationStore.getOutgoingWebhooks()
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
AsyncClient.listOutgoingHooks();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
IntegrationStore.removeChangeListener(this.handleIntegrationChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleIntegrationChange() {
|
||||||
|
this.setState({
|
||||||
|
incomingWebhooks: IntegrationStore.getIncomingWebhooks(),
|
||||||
|
outgoingWebhooks: IntegrationStore.getOutgoingWebhooks()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTypeFilter(e, typeFilter) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
typeFilter
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFilter(e) {
|
||||||
|
this.setState({
|
||||||
|
filter: e.target.value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteIncomingWebhook(incomingWebhook) {
|
||||||
|
AsyncClient.deleteIncomingHook(incomingWebhook.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
regenOutgoingWebhookToken(outgoingWebhook) {
|
||||||
|
AsyncClient.regenOutgoingHookToken(outgoingWebhook.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteOutgoingWebhook(outgoingWebhook) {
|
||||||
|
AsyncClient.deleteOutgoingHook(outgoingWebhook.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTypeFilters(incomingWebhooks, outgoingWebhooks) {
|
||||||
|
const fields = [];
|
||||||
|
|
||||||
|
if (incomingWebhooks.length > 0 || outgoingWebhooks.length > 0) {
|
||||||
|
let filterClassName = 'type-filter';
|
||||||
|
if (this.state.typeFilter === '') {
|
||||||
|
filterClassName += ' type-filter--selected';
|
||||||
|
}
|
||||||
|
|
||||||
|
fields.push(
|
||||||
|
<a
|
||||||
|
key='allFilter'
|
||||||
|
className={filterClassName}
|
||||||
|
href='#'
|
||||||
|
onClick={(e) => this.updateTypeFilter(e, '')}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='installed_integrations.allFilter'
|
||||||
|
defaultMessage='All ({count})'
|
||||||
|
values={{
|
||||||
|
count: incomingWebhooks.length + outgoingWebhooks.length
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (incomingWebhooks.length > 0) {
|
||||||
|
fields.push(
|
||||||
|
<span
|
||||||
|
key='incomingWebhooksDivider'
|
||||||
|
className='divider'
|
||||||
|
>
|
||||||
|
{'|'}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
|
||||||
|
let filterClassName = 'type-filter';
|
||||||
|
if (this.state.typeFilter === 'incomingWebhooks') {
|
||||||
|
filterClassName += ' type-filter--selected';
|
||||||
|
}
|
||||||
|
|
||||||
|
fields.push(
|
||||||
|
<a
|
||||||
|
key='incomingWebhooksFilter'
|
||||||
|
className={filterClassName}
|
||||||
|
href='#'
|
||||||
|
onClick={(e) => this.updateTypeFilter(e, 'incomingWebhooks')}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='installed_integrations.incomingWebhooksFilter'
|
||||||
|
defaultMessage='Incoming Webhooks ({count})'
|
||||||
|
values={{
|
||||||
|
count: incomingWebhooks.length
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outgoingWebhooks.length > 0) {
|
||||||
|
fields.push(
|
||||||
|
<span
|
||||||
|
key='outgoingWebhooksDivider'
|
||||||
|
className='divider'
|
||||||
|
>
|
||||||
|
{'|'}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
|
||||||
|
let filterClassName = 'type-filter';
|
||||||
|
if (this.state.typeFilter === 'outgoingWebhooks') {
|
||||||
|
filterClassName += ' type-filter--selected';
|
||||||
|
}
|
||||||
|
|
||||||
|
fields.push(
|
||||||
|
<a
|
||||||
|
key='outgoingWebhooksFilter'
|
||||||
|
className={filterClassName}
|
||||||
|
href='#'
|
||||||
|
onClick={(e) => this.updateTypeFilter(e, 'outgoingWebhooks')}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='installed_integrations.outgoingWebhooksFilter'
|
||||||
|
defaultMessage='Outgoing Webhooks ({count})'
|
||||||
|
values={{
|
||||||
|
count: outgoingWebhooks.length
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='type-filters'>
|
||||||
|
{fields}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const incomingWebhooks = this.state.incomingWebhooks;
|
||||||
|
const outgoingWebhooks = this.state.outgoingWebhooks;
|
||||||
|
|
||||||
|
const filter = this.state.filter.toLowerCase();
|
||||||
|
|
||||||
|
const integrations = [];
|
||||||
|
if (!this.state.typeFilter || this.state.typeFilter === 'incomingWebhooks') {
|
||||||
|
for (const incomingWebhook of incomingWebhooks) {
|
||||||
|
if (filter) {
|
||||||
|
const channel = ChannelStore.get(incomingWebhook.channel_id);
|
||||||
|
|
||||||
|
if (!channel || channel.name.toLowerCase().indexOf(filter) === -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
integrations.push(
|
||||||
|
<InstalledIncomingWebhook
|
||||||
|
key={incomingWebhook.id}
|
||||||
|
incomingWebhook={incomingWebhook}
|
||||||
|
onDeleteClick={this.deleteIncomingWebhook}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.state.typeFilter || this.state.typeFilter === 'outgoingWebhooks') {
|
||||||
|
for (const outgoingWebhook of outgoingWebhooks) {
|
||||||
|
if (filter) {
|
||||||
|
const channel = ChannelStore.get(outgoingWebhook.channel_id);
|
||||||
|
|
||||||
|
if (!channel || channel.name.toLowerCase().indexOf(filter) === -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
integrations.push(
|
||||||
|
<InstalledOutgoingWebhook
|
||||||
|
key={outgoingWebhook.id}
|
||||||
|
outgoingWebhook={outgoingWebhook}
|
||||||
|
onRegenToken={this.regenOutgoingWebhookToken}
|
||||||
|
onDelete={this.deleteOutgoingWebhook}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='backstage row'>
|
||||||
|
<div className='installed-integrations'>
|
||||||
|
<div className='backstage__header'>
|
||||||
|
<h1 className='text'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='installed_integrations.header'
|
||||||
|
defaultMessage='Installed Integrations'
|
||||||
|
/>
|
||||||
|
</h1>
|
||||||
|
<Link
|
||||||
|
className='add-integrations-link'
|
||||||
|
to={'/settings/integrations/add'}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
className='btn btn-primary'
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<FormattedMessage
|
||||||
|
id='installed_integrations.add'
|
||||||
|
defaultMessage='Add Integration'
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className='installed-integrations__filters'>
|
||||||
|
{this.renderTypeFilters(this.state.incomingWebhooks, this.state.outgoingWebhooks)}
|
||||||
|
<input
|
||||||
|
type='search'
|
||||||
|
placeholder={Utils.localizeMessage('installed_integrations.search', 'Search Integrations')}
|
||||||
|
value={this.state.filter}
|
||||||
|
onChange={this.updateFilter}
|
||||||
|
style={{flexGrow: 0, flexShrink: 0}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='installed-integrations__list'>
|
||||||
|
{integrations}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
91
webapp/components/backstage/installed_outgoing_webhook.jsx
Обычный файл
91
webapp/components/backstage/installed_outgoing_webhook.jsx
Обычный файл
@@ -0,0 +1,91 @@
|
|||||||
|
// 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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const outgoingWebhook = this.props.outgoingWebhook;
|
||||||
|
|
||||||
|
const channel = ChannelStore.get(outgoingWebhook.channel_id);
|
||||||
|
const channelName = channel ? channel.display_name : 'cannot find channel';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='installed-integrations__item installed-integrations__outgoing-webhook'>
|
||||||
|
<div className='details'>
|
||||||
|
<div className='details-row'>
|
||||||
|
<span className='name'>
|
||||||
|
{channelName}
|
||||||
|
</span>
|
||||||
|
<span className='type'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='installed_integrations.outgoingWebhookType'
|
||||||
|
defaultMessage='(Outgoing Webhook)'
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className='details-row'>
|
||||||
|
<span className='description'>
|
||||||
|
{Utils.getWindowLocationOrigin() + '/hooks/' + outgoingWebhook.id}
|
||||||
|
{' - '}
|
||||||
|
{outgoingWebhook.token}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -65,6 +65,7 @@ export default class ChannelInviteButton extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<SpinnerButton
|
<SpinnerButton
|
||||||
|
className='btn btn-sm btn-primary'
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
spinning={this.state.addingUser}
|
spinning={this.state.addingUser}
|
||||||
>
|
>
|
||||||
|
|||||||
79
webapp/components/channel_select.jsx
Обычный файл
79
webapp/components/channel_select.jsx
Обычный файл
@@ -0,0 +1,79 @@
|
|||||||
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import Constants from 'utils/constants.jsx';
|
||||||
|
import ChannelStore from 'stores/channel_store.jsx';
|
||||||
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
|
||||||
|
export default class ChannelSelect extends React.Component {
|
||||||
|
static get propTypes() {
|
||||||
|
return {
|
||||||
|
onChange: React.PropTypes.func,
|
||||||
|
value: React.PropTypes.string
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.handleChannelChange = this.handleChannelChange.bind(this);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
channels: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
this.setState({
|
||||||
|
channels: ChannelStore.getAll()
|
||||||
|
});
|
||||||
|
|
||||||
|
ChannelStore.addChangeListener(this.handleChannelChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
ChannelStore.removeChangeListener(this.handleChannelChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChannelChange() {
|
||||||
|
this.setState({
|
||||||
|
channels: ChannelStore.getAll()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const options = [
|
||||||
|
<option
|
||||||
|
key=''
|
||||||
|
value=''
|
||||||
|
>
|
||||||
|
{Utils.localizeMessage('channel_select.placeholder', '--- Select a channel ---')}
|
||||||
|
</option>
|
||||||
|
];
|
||||||
|
|
||||||
|
this.state.channels.forEach((channel) => {
|
||||||
|
if (channel.type !== Constants.DM_CHANNEL) {
|
||||||
|
options.push(
|
||||||
|
<option
|
||||||
|
key={channel.id}
|
||||||
|
value={channel.id}
|
||||||
|
>
|
||||||
|
{channel.display_name}
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<select
|
||||||
|
className='form-control'
|
||||||
|
value={this.props.value}
|
||||||
|
onChange={this.props.onChange}
|
||||||
|
>
|
||||||
|
{options}
|
||||||
|
</select>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
50
webapp/components/form_error.jsx
Обычный файл
50
webapp/components/form_error.jsx
Обычный файл
@@ -0,0 +1,50 @@
|
|||||||
|
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default class FormError extends React.Component {
|
||||||
|
static get propTypes() {
|
||||||
|
// accepts either a single error or an array of errors
|
||||||
|
return {
|
||||||
|
error: React.PropTypes.node,
|
||||||
|
errors: React.PropTypes.arrayOf(React.PropTypes.node)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static get defaultProps() {
|
||||||
|
return {
|
||||||
|
error: null,
|
||||||
|
errors: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (!this.props.error && this.props.errors.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// look for the first truthy error to display
|
||||||
|
let message = this.props.error;
|
||||||
|
|
||||||
|
if (!message) {
|
||||||
|
for (const error of this.props.errors) {
|
||||||
|
if (error) {
|
||||||
|
message = error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!message) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='form-group has-error'>
|
||||||
|
<label className='control-label'>
|
||||||
|
{message}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -199,6 +199,9 @@ export default class LoggedIn extends React.Component {
|
|||||||
if (this.props.children) {
|
if (this.props.children) {
|
||||||
content = this.props.children;
|
content = this.props.children;
|
||||||
} else {
|
} else {
|
||||||
|
content.push(
|
||||||
|
this.props.navbar
|
||||||
|
);
|
||||||
content.push(
|
content.push(
|
||||||
this.props.sidebar
|
this.props.sidebar
|
||||||
);
|
);
|
||||||
@@ -247,8 +250,9 @@ LoggedIn.defaultProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
LoggedIn.propTypes = {
|
LoggedIn.propTypes = {
|
||||||
children: React.PropTypes.object,
|
children: React.PropTypes.arrayOf(React.PropTypes.element),
|
||||||
sidebar: React.PropTypes.object,
|
navbar: React.PropTypes.element,
|
||||||
center: React.PropTypes.object,
|
sidebar: React.PropTypes.element,
|
||||||
|
center: React.PropTypes.element,
|
||||||
params: React.PropTypes.object
|
params: React.PropTypes.object
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ export default class MoreDirectChannels extends React.Component {
|
|||||||
createJoinDirectChannelButton({user}) {
|
createJoinDirectChannelButton({user}) {
|
||||||
return (
|
return (
|
||||||
<SpinnerButton
|
<SpinnerButton
|
||||||
|
className='btn btm-sm btn-primary'
|
||||||
spinning={this.state.loadingDMChannel === user.id}
|
spinning={this.state.loadingDMChannel === user.id}
|
||||||
onClick={this.handleShowDirectChannel.bind(this, user)}
|
onClick={this.handleShowDirectChannel.bind(this, user)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ export default class NavbarDropdown extends React.Component {
|
|||||||
var isAdmin = false;
|
var isAdmin = false;
|
||||||
var isSystemAdmin = false;
|
var isSystemAdmin = false;
|
||||||
var teamSettings = null;
|
var teamSettings = null;
|
||||||
|
let integrationsLink = null;
|
||||||
|
|
||||||
if (currentUser != null) {
|
if (currentUser != null) {
|
||||||
isAdmin = Utils.isAdmin(currentUser.roles);
|
isAdmin = Utils.isAdmin(currentUser.roles);
|
||||||
@@ -125,6 +126,21 @@ export default class NavbarDropdown extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (window.mm_config.EnableIncomingWebhooks === 'true' || window.mm_config.EnableOutgoingWebhooks === 'true') {
|
||||||
|
if (isAdmin || window.EnableAdminOnlyIntegrations !== 'true') {
|
||||||
|
integrationsLink = (
|
||||||
|
<li>
|
||||||
|
<Link to={'/settings/integrations'}>
|
||||||
|
<FormattedMessage
|
||||||
|
id='navbar_dropdown.integrations'
|
||||||
|
defaultMessage='Integrations'
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isSystemAdmin) {
|
if (isSystemAdmin) {
|
||||||
sysAdminLink = (
|
sysAdminLink = (
|
||||||
<li>
|
<li>
|
||||||
@@ -238,6 +254,7 @@ export default class NavbarDropdown extends React.Component {
|
|||||||
</li>
|
</li>
|
||||||
{adminDivider}
|
{adminDivider}
|
||||||
{teamSettings}
|
{teamSettings}
|
||||||
|
{integrationsLink}
|
||||||
{manageLink}
|
{manageLink}
|
||||||
{sysAdminLink}
|
{sysAdminLink}
|
||||||
{teams}
|
{teams}
|
||||||
|
|||||||
@@ -14,20 +14,10 @@ export default class SpinnerButton extends React.Component {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.handleClick = this.handleClick.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleClick(e) {
|
|
||||||
if (this.props.onClick) {
|
|
||||||
this.props.onClick(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.props.spinning) {
|
const {spinning, children, ...props} = this.props; // eslint-disable-line no-use-before-define
|
||||||
|
|
||||||
|
if (spinning) {
|
||||||
return (
|
return (
|
||||||
<img
|
<img
|
||||||
className='spinner-button__gif'
|
className='spinner-button__gif'
|
||||||
@@ -38,10 +28,10 @@ export default class SpinnerButton extends React.Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={this.handleClick}
|
className='btn btn-primary'
|
||||||
className='btn btn-sm btn-primary'
|
{...props}
|
||||||
>
|
>
|
||||||
{this.props.children}
|
{children}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,225 +0,0 @@
|
|||||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See License.txt for license information.
|
|
||||||
|
|
||||||
import * as Client from 'utils/client.jsx';
|
|
||||||
import * as Utils from 'utils/utils.jsx';
|
|
||||||
import Constants from 'utils/constants.jsx';
|
|
||||||
import ChannelStore from 'stores/channel_store.jsx';
|
|
||||||
import LoadingScreen from '../loading_screen.jsx';
|
|
||||||
|
|
||||||
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
export default class ManageIncomingHooks extends React.Component {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.getHooks = this.getHooks.bind(this);
|
|
||||||
this.addNewHook = this.addNewHook.bind(this);
|
|
||||||
this.updateChannelId = this.updateChannelId.bind(this);
|
|
||||||
|
|
||||||
this.state = {hooks: [], channelId: ChannelStore.getByName(Constants.DEFAULT_CHANNEL).id, getHooksComplete: false};
|
|
||||||
}
|
|
||||||
componentDidMount() {
|
|
||||||
this.getHooks();
|
|
||||||
}
|
|
||||||
addNewHook() {
|
|
||||||
const hook = {};
|
|
||||||
hook.channel_id = this.state.channelId;
|
|
||||||
|
|
||||||
Client.addIncomingHook(
|
|
||||||
hook,
|
|
||||||
(data) => {
|
|
||||||
let hooks = this.state.hooks;
|
|
||||||
if (!hooks) {
|
|
||||||
hooks = [];
|
|
||||||
}
|
|
||||||
hooks.push(data);
|
|
||||||
this.setState({hooks});
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
this.setState({serverError: err});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
removeHook(id) {
|
|
||||||
const data = {};
|
|
||||||
data.id = id;
|
|
||||||
|
|
||||||
Client.deleteIncomingHook(
|
|
||||||
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});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
getHooks() {
|
|
||||||
Client.listIncomingHooks(
|
|
||||||
(data) => {
|
|
||||||
const state = this.state;
|
|
||||||
|
|
||||||
if (data) {
|
|
||||||
state.hooks = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
state.getHooksComplete = true;
|
|
||||||
this.setState(state);
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
this.setState({serverError: err});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
updateChannelId(e) {
|
|
||||||
this.setState({channelId: 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 = [];
|
|
||||||
channels.forEach((channel) => {
|
|
||||||
if (channel.type !== Constants.DM_CHANNEL) {
|
|
||||||
options.push(
|
|
||||||
<option
|
|
||||||
key={'incoming-hook' + channel.id}
|
|
||||||
value={channel.id}
|
|
||||||
>
|
|
||||||
{channel.display_name}
|
|
||||||
</option>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let disableButton = '';
|
|
||||||
if (this.state.channelId === '') {
|
|
||||||
disableButton = ' disable';
|
|
||||||
}
|
|
||||||
|
|
||||||
const hooks = [];
|
|
||||||
this.state.hooks.forEach((hook) => {
|
|
||||||
const c = ChannelStore.get(hook.channel_id);
|
|
||||||
if (c) {
|
|
||||||
hooks.push(
|
|
||||||
<div
|
|
||||||
key={hook.id}
|
|
||||||
className='webhook__item'
|
|
||||||
>
|
|
||||||
<div className='padding-top x2 webhook__url'>
|
|
||||||
<strong>{'URL: '}</strong>
|
|
||||||
<span className='word-break--all'>{Utils.getWindowLocationOrigin() + '/hooks/' + hook.id}</span>
|
|
||||||
</div>
|
|
||||||
<div className='padding-top'>
|
|
||||||
<strong>
|
|
||||||
<FormattedMessage
|
|
||||||
id='user.settings.hooks_in.channel'
|
|
||||||
defaultMessage='Channel: '
|
|
||||||
/>
|
|
||||||
</strong>{c.display_name}
|
|
||||||
</div>
|
|
||||||
<a
|
|
||||||
className={'webhook__remove'}
|
|
||||||
href='#'
|
|
||||||
onClick={this.removeHook.bind(this, hook.id)}
|
|
||||||
>
|
|
||||||
<span aria-hidden='true'>{'×'}</span>
|
|
||||||
</a>
|
|
||||||
<div className='padding-top x2 divider-light'></div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let displayHooks;
|
|
||||||
if (!this.state.getHooksComplete) {
|
|
||||||
displayHooks = <LoadingScreen/>;
|
|
||||||
} else if (hooks.length > 0) {
|
|
||||||
displayHooks = hooks;
|
|
||||||
} else {
|
|
||||||
displayHooks = (
|
|
||||||
<div className='padding-top x2'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='user.settings.hooks_in.none'
|
|
||||||
defaultMessage='None'
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const existingHooks = (
|
|
||||||
<div className='webhooks__container'>
|
|
||||||
<label className='control-label padding-top x2'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='user.settings.hooks_in.existing'
|
|
||||||
defaultMessage='Existing incoming webhooks'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div className='padding-top divider-light'></div>
|
|
||||||
<div className='webhooks__list'>
|
|
||||||
{displayHooks}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div key='addIncomingHook'>
|
|
||||||
<FormattedHTMLMessage
|
|
||||||
id='user.settings.hooks_in.description'
|
|
||||||
defaultMessage='Create webhook URLs for use in external integrations. Please see <a href="http://docs.mattermost.com/developer/webhooks-incoming.html" target="_blank">incoming webhooks documentation</a> to learn more. View all incoming webhooks configured on this team below.'
|
|
||||||
/>
|
|
||||||
<div><label className='control-label padding-top x2'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='user.settings.hooks_in.addTitle'
|
|
||||||
defaultMessage='Add a new incoming webhook'
|
|
||||||
/>
|
|
||||||
</label></div>
|
|
||||||
<div className='row padding-top'>
|
|
||||||
<div className='col-sm-10 padding-bottom'>
|
|
||||||
<select
|
|
||||||
ref='channelName'
|
|
||||||
className='form-control'
|
|
||||||
value={this.state.channelId}
|
|
||||||
onChange={this.updateChannelId}
|
|
||||||
>
|
|
||||||
{options}
|
|
||||||
</select>
|
|
||||||
{serverError}
|
|
||||||
</div>
|
|
||||||
<div className='col-sm-2 col-xs-4 no-padding--left padding-bottom'>
|
|
||||||
<a
|
|
||||||
className={'btn form-control no-padding btn-sm btn-primary' + disableButton}
|
|
||||||
href='#'
|
|
||||||
onClick={this.addNewHook}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='user.settings.hooks_in.add'
|
|
||||||
defaultMessage='Add'
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{existingHooks}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,397 +0,0 @@
|
|||||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See License.txt for license information.
|
|
||||||
|
|
||||||
import LoadingScreen from '../loading_screen.jsx';
|
|
||||||
|
|
||||||
import ChannelStore from 'stores/channel_store.jsx';
|
|
||||||
|
|
||||||
import * as Client from 'utils/client.jsx';
|
|
||||||
import Constants from 'utils/constants.jsx';
|
|
||||||
|
|
||||||
import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl';
|
|
||||||
|
|
||||||
const holders = defineMessages({
|
|
||||||
optional: {
|
|
||||||
id: 'user.settings.hooks_out.optional',
|
|
||||||
defaultMessage: 'Optional if channel selected'
|
|
||||||
},
|
|
||||||
callbackHolder: {
|
|
||||||
id: 'user.settings.hooks_out.callbackHolder',
|
|
||||||
defaultMessage: 'Each URL must start with http:// or https://'
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: 'user.settings.hooks_out.select',
|
|
||||||
defaultMessage: '--- Select a channel ---'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
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').map((url) => url.trim());
|
|
||||||
|
|
||||||
Client.addOutgoingHook(
|
|
||||||
hook,
|
|
||||||
(data) => {
|
|
||||||
let hooks = Object.assign([], this.state.hooks);
|
|
||||||
if (!hooks) {
|
|
||||||
hooks = [];
|
|
||||||
}
|
|
||||||
hooks.push(data);
|
|
||||||
this.setState({hooks, addError: null, channelId: '', triggerWords: '', callbackURLs: ''});
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
this.setState({addError: err.message});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
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({editError: err.message});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
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, editError: null});
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
this.setState({editError: err.message});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
getHooks() {
|
|
||||||
Client.listOutgoingHooks(
|
|
||||||
(data) => {
|
|
||||||
if (data) {
|
|
||||||
this.setState({hooks: data, getHooksComplete: true, editError: null});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
this.setState({editError: err.message});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
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 addError;
|
|
||||||
if (this.state.addError) {
|
|
||||||
addError = <label className='has-error'>{this.state.addError}</label>;
|
|
||||||
}
|
|
||||||
let editError;
|
|
||||||
if (this.state.editError) {
|
|
||||||
addError = <label className='has-error'>{this.state.editError}</label>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const channels = ChannelStore.getAll();
|
|
||||||
const options = [];
|
|
||||||
options.push(
|
|
||||||
<option
|
|
||||||
key='select-channel'
|
|
||||||
value=''
|
|
||||||
>
|
|
||||||
{this.props.intl.formatMessage(holders.select)}
|
|
||||||
</option>
|
|
||||||
);
|
|
||||||
|
|
||||||
channels.forEach((channel) => {
|
|
||||||
if (channel.type === Constants.OPEN_CHANNEL) {
|
|
||||||
options.push(
|
|
||||||
<option
|
|
||||||
key={'outgoing-hook' + channel.id}
|
|
||||||
value={channel.id}
|
|
||||||
>
|
|
||||||
{channel.display_name}
|
|
||||||
</option>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const hooks = [];
|
|
||||||
this.state.hooks.forEach((hook) => {
|
|
||||||
const c = ChannelStore.get(hook.channel_id);
|
|
||||||
|
|
||||||
if (!c && hook.channel_id && hook.channel_id.length !== 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let channelDiv;
|
|
||||||
if (c) {
|
|
||||||
channelDiv = (
|
|
||||||
<div className='padding-top'>
|
|
||||||
<strong>
|
|
||||||
<FormattedMessage
|
|
||||||
id='user.settings.hooks_out.channel'
|
|
||||||
defaultMessage='Channel: '
|
|
||||||
/>
|
|
||||||
</strong>{c.display_name}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let triggerDiv;
|
|
||||||
if (hook.trigger_words && hook.trigger_words.length !== 0) {
|
|
||||||
triggerDiv = (
|
|
||||||
<div className='padding-top'>
|
|
||||||
<strong>
|
|
||||||
<FormattedMessage
|
|
||||||
id='user.settings.hooks_out.trigger'
|
|
||||||
defaultMessage='Trigger Words: '
|
|
||||||
/>
|
|
||||||
</strong>{hook.trigger_words.join(', ')}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
hooks.push(
|
|
||||||
<div
|
|
||||||
key={hook.id}
|
|
||||||
className='webhook__item'
|
|
||||||
>
|
|
||||||
<div className='padding-top x2 webhook__url'>
|
|
||||||
<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)}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='user.settings.hooks_out.regen'
|
|
||||||
defaultMessage='Regen Token'
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
className='webhook__remove'
|
|
||||||
href='#'
|
|
||||||
onClick={this.removeHook.bind(this, hook.id)}
|
|
||||||
>
|
|
||||||
<span aria-hidden='true'>{'×'}</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div className='padding-top x2 divider-light'></div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
let displayHooks;
|
|
||||||
if (!this.state.getHooksComplete) {
|
|
||||||
displayHooks = <LoadingScreen/>;
|
|
||||||
} else if (hooks.length > 0) {
|
|
||||||
displayHooks = hooks;
|
|
||||||
} else {
|
|
||||||
displayHooks = (
|
|
||||||
<div className='padding-top x2'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='user.settings.hooks_out.none'
|
|
||||||
defaultMessage='None'
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const existingHooks = (
|
|
||||||
<div className='webhooks__container'>
|
|
||||||
<label className='control-label padding-top x2'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='user.settings.hooks_out.existing'
|
|
||||||
defaultMessage='Existing outgoing webhooks'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div className='padding-top divider-light'></div>
|
|
||||||
<div className='webhooks__list'>
|
|
||||||
{displayHooks}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const disableButton = (this.state.channelId === '' && this.state.triggerWords === '') || this.state.callbackURLs === '';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div key='addOutgoingHook'>
|
|
||||||
<FormattedHTMLMessage
|
|
||||||
id='user.settings.hooks_out.addDescription'
|
|
||||||
defaultMessage='Create webhooks to send new message events to an external integration. Please see <a href="http://docs.mattermost.com/developer/webhooks-outgoing.html" target="_blank">outgoing webhooks documentation</a> to learn more. View all outgoing webhooks configured on this team below.'
|
|
||||||
/>
|
|
||||||
<div><label className='control-label padding-top x2'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='user.settings.hooks_out.addTitle'
|
|
||||||
defaultMessage='Add a new outgoing webhook'
|
|
||||||
/>
|
|
||||||
</label></div>
|
|
||||||
<div className='padding-top divider-light'></div>
|
|
||||||
<div className='padding-top'>
|
|
||||||
<div>
|
|
||||||
<label className='control-label'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='user.settings.hooks_out.channel'
|
|
||||||
defaultMessage='Channel: '
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div className='padding-top'>
|
|
||||||
<select
|
|
||||||
ref='channelName'
|
|
||||||
className='form-control'
|
|
||||||
value={this.state.channelId}
|
|
||||||
onChange={this.updateChannelId}
|
|
||||||
>
|
|
||||||
{options}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div className='padding-top'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='user.settings.hooks_out.only'
|
|
||||||
defaultMessage='Only public channels can be used'
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='padding-top x2'>
|
|
||||||
<label className='control-label'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='user.settings.hooks_out.trigger'
|
|
||||||
defaultMessage='Trigger Words: '
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div className='padding-top'>
|
|
||||||
<input
|
|
||||||
ref='triggerWords'
|
|
||||||
className='form-control'
|
|
||||||
value={this.state.triggerWords}
|
|
||||||
onChange={this.updateTriggerWords}
|
|
||||||
placeholder={this.props.intl.formatMessage(holders.optional)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className='padding-top'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='user.settings.hooks_out.comma'
|
|
||||||
defaultMessage='Comma separated words to trigger on'
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='padding-top x2'>
|
|
||||||
<label className='control-label'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='user.settings.hooks_out.callback'
|
|
||||||
defaultMessage='Callback URLs: '
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div className='padding-top'>
|
|
||||||
<textarea
|
|
||||||
ref='callbackURLs'
|
|
||||||
className='form-control no-resize'
|
|
||||||
value={this.state.callbackURLs}
|
|
||||||
resize={false}
|
|
||||||
rows={3}
|
|
||||||
onChange={this.updateCallbackURLs}
|
|
||||||
placeholder={this.props.intl.formatMessage(holders.callbackHolder)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className='padding-top'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='user.settings.hooks_out.callbackDesc'
|
|
||||||
defaultMessage='New line separated URLs that will receive the HTTP POST event'
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{addError}
|
|
||||||
</div>
|
|
||||||
<div className='padding-top padding-bottom'>
|
|
||||||
<a
|
|
||||||
className={'btn btn-sm btn-primary'}
|
|
||||||
href='#'
|
|
||||||
disabled={disableButton}
|
|
||||||
onClick={this.addNewHook}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='user.settings.hooks_out.add'
|
|
||||||
defaultMessage='Add'
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{existingHooks}
|
|
||||||
{editError}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ManageOutgoingHooks.propTypes = {
|
|
||||||
intl: intlShape.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default injectIntl(ManageOutgoingHooks);
|
|
||||||
@@ -4,29 +4,11 @@
|
|||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import SettingItemMin from '../setting_item_min.jsx';
|
import SettingItemMin from '../setting_item_min.jsx';
|
||||||
import SettingItemMax from '../setting_item_max.jsx';
|
import SettingItemMax from '../setting_item_max.jsx';
|
||||||
import ManageIncomingHooks from './manage_incoming_hooks.jsx';
|
|
||||||
import ManageOutgoingHooks from './manage_outgoing_hooks.jsx';
|
|
||||||
import ManageCommandHooks from './manage_command_hooks.jsx';
|
import ManageCommandHooks from './manage_command_hooks.jsx';
|
||||||
|
|
||||||
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl';
|
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
const holders = defineMessages({
|
const holders = defineMessages({
|
||||||
inName: {
|
|
||||||
id: 'user.settings.integrations.incomingWebhooks',
|
|
||||||
defaultMessage: 'Incoming Webhooks'
|
|
||||||
},
|
|
||||||
inDesc: {
|
|
||||||
id: 'user.settings.integrations.incomingWebhooksDescription',
|
|
||||||
defaultMessage: 'Manage your incoming webhooks'
|
|
||||||
},
|
|
||||||
outName: {
|
|
||||||
id: 'user.settings.integrations.outWebhooks',
|
|
||||||
defaultMessage: 'Outgoing Webhooks'
|
|
||||||
},
|
|
||||||
outDesc: {
|
|
||||||
id: 'user.settings.integrations.outWebhooksDescription',
|
|
||||||
defaultMessage: 'Manage your outgoing webhooks'
|
|
||||||
},
|
|
||||||
cmdName: {
|
cmdName: {
|
||||||
id: 'user.settings.integrations.commands',
|
id: 'user.settings.integrations.commands',
|
||||||
defaultMessage: 'Slash Commands'
|
defaultMessage: 'Slash Commands'
|
||||||
@@ -52,74 +34,10 @@ class UserSettingsIntegrationsTab extends React.Component {
|
|||||||
this.props.updateSection(section);
|
this.props.updateSection(section);
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
let incomingHooksSection;
|
|
||||||
let outgoingHooksSection;
|
|
||||||
let commandHooksSection;
|
let commandHooksSection;
|
||||||
var inputs = [];
|
var inputs = [];
|
||||||
const {formatMessage} = this.props.intl;
|
const {formatMessage} = this.props.intl;
|
||||||
|
|
||||||
if (global.window.mm_config.EnableIncomingWebhooks === 'true') {
|
|
||||||
if (this.props.activeSection === 'incoming-hooks') {
|
|
||||||
inputs.push(
|
|
||||||
<ManageIncomingHooks key='incoming-hook-ui'/>
|
|
||||||
);
|
|
||||||
|
|
||||||
incomingHooksSection = (
|
|
||||||
<SettingItemMax
|
|
||||||
title={formatMessage(holders.inName)}
|
|
||||||
width='medium'
|
|
||||||
inputs={inputs}
|
|
||||||
updateSection={(e) => {
|
|
||||||
this.updateSection('');
|
|
||||||
e.preventDefault();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
incomingHooksSection = (
|
|
||||||
<SettingItemMin
|
|
||||||
title={formatMessage(holders.inName)}
|
|
||||||
width='medium'
|
|
||||||
describe={formatMessage(holders.inDesc)}
|
|
||||||
updateSection={() => {
|
|
||||||
this.updateSection('incoming-hooks');
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (global.window.mm_config.EnableOutgoingWebhooks === 'true') {
|
|
||||||
if (this.props.activeSection === 'outgoing-hooks') {
|
|
||||||
inputs.push(
|
|
||||||
<ManageOutgoingHooks key='outgoing-hook-ui'/>
|
|
||||||
);
|
|
||||||
|
|
||||||
outgoingHooksSection = (
|
|
||||||
<SettingItemMax
|
|
||||||
title={formatMessage(holders.outName)}
|
|
||||||
width='medium'
|
|
||||||
inputs={inputs}
|
|
||||||
updateSection={(e) => {
|
|
||||||
this.updateSection('');
|
|
||||||
e.preventDefault();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
outgoingHooksSection = (
|
|
||||||
<SettingItemMin
|
|
||||||
title={formatMessage(holders.outName)}
|
|
||||||
width='medium'
|
|
||||||
describe={formatMessage(holders.outDesc)}
|
|
||||||
updateSection={() => {
|
|
||||||
this.updateSection('outgoing-hooks');
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (global.window.mm_config.EnableCommands === 'true') {
|
if (global.window.mm_config.EnableCommands === 'true') {
|
||||||
if (this.props.activeSection === 'command-hooks') {
|
if (this.props.activeSection === 'command-hooks') {
|
||||||
inputs.push(
|
inputs.push(
|
||||||
@@ -187,10 +105,6 @@ class UserSettingsIntegrationsTab extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</h3>
|
</h3>
|
||||||
<div className='divider-dark first'/>
|
<div className='divider-dark first'/>
|
||||||
{incomingHooksSection}
|
|
||||||
<div className='divider-light'/>
|
|
||||||
{outgoingHooksSection}
|
|
||||||
<div className='divider-dark'/>
|
|
||||||
{commandHooksSection}
|
{commandHooksSection}
|
||||||
<div className='divider-dark'/>
|
<div className='divider-dark'/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -22,6 +22,28 @@
|
|||||||
"activity_log_modal.android": "Android",
|
"activity_log_modal.android": "Android",
|
||||||
"activity_log_modal.androidNativeApp": "Android Native App",
|
"activity_log_modal.androidNativeApp": "Android Native App",
|
||||||
"activity_log_modal.iphoneNativeApp": "iPhone Native App",
|
"activity_log_modal.iphoneNativeApp": "iPhone Native App",
|
||||||
|
"add_incoming_webhook.cancel": "Cancel",
|
||||||
|
"add_incoming_webhook.channel": "Channel",
|
||||||
|
"add_incoming_webhook.channelRequired": "A valid channel is required",
|
||||||
|
"add_incoming_webhook.description": "Description",
|
||||||
|
"add_incoming_webhook.header": "Add Incoming Webhook",
|
||||||
|
"add_incoming_webhook.name": "Name",
|
||||||
|
"add_incoming_webhook.save": "Save",
|
||||||
|
"add_integration.header": "Add Integration",
|
||||||
|
"add_integration.incomingWebhook.title": "Incoming Webhook",
|
||||||
|
"add_integration.incomingWebhook.description": "Create webhook URLs for use in external integrations.",
|
||||||
|
"add_integration.outgoingWebhook.title": "Outgoing Webhook",
|
||||||
|
"add_integration.outgoingWebhook.description": "Create webhooks to send new message events to an external integration.",
|
||||||
|
"add_outgoing_webhook.callbackUrls": "Callback URLs (One Per Line)",
|
||||||
|
"add_outgoing_webhook.callbackUrlsRequired": "One or more callback URLs are required",
|
||||||
|
"add_outgoing_webhook.cancel": "Cancel",
|
||||||
|
"add_outgoing_webhook.channel": "Channel",
|
||||||
|
"add_outgoing_webhook.description": "Description",
|
||||||
|
"add_outgoing_webhook.header": "Add Outgoing Webhook",
|
||||||
|
"add_outgoing_webhook.name": "Name",
|
||||||
|
"add_outgoing_webhook.save": "Save",
|
||||||
|
"add_outgoing_webhook.triggerWOrds": "Trigger Words (One Per Line)",
|
||||||
|
"add_outgoing_webhook.triggerWordsOrChannelRequired": "A valid channel or a list of trigger words is required",
|
||||||
"admin.audits.reload": "Reload",
|
"admin.audits.reload": "Reload",
|
||||||
"admin.audits.title": "User Activity",
|
"admin.audits.title": "User Activity",
|
||||||
"admin.compliance.directoryDescription": "Directory to which compliance reports are written. If blank, will be set to ./data/.",
|
"admin.compliance.directoryDescription": "Directory to which compliance reports are written. If blank, will be set to ./data/.",
|
||||||
@@ -550,6 +572,12 @@
|
|||||||
"authorize.app": "The app <strong>{appName}</strong> would like the ability to access and modify your basic information.",
|
"authorize.app": "The app <strong>{appName}</strong> would like the ability to access and modify your basic information.",
|
||||||
"authorize.deny": "Deny",
|
"authorize.deny": "Deny",
|
||||||
"authorize.title": "An application would like to connect to your {teamName} account",
|
"authorize.title": "An application would like to connect to your {teamName} account",
|
||||||
|
"backstage_navbar.backToMattermost": "Back to {siteName}",
|
||||||
|
"backstage_sidebar.integrations": "Integrations",
|
||||||
|
"backstage_sidebar.integrations.installed": "Installed Integrations",
|
||||||
|
"backstage_sidebar.integrations.add": "Add Integration",
|
||||||
|
"backstage_sidebar.integrations.add.incomingWebhook": "Incoming Webhook",
|
||||||
|
"backstage_sidebar.integrations.add.outgoingWebhook": "Outgoing Webhook",
|
||||||
"center_panel.recent": "Click here to jump to recent messages. ",
|
"center_panel.recent": "Click here to jump to recent messages. ",
|
||||||
"chanel_header.addMembers": "Add Members",
|
"chanel_header.addMembers": "Add Members",
|
||||||
"change_url.close": "Close",
|
"change_url.close": "Close",
|
||||||
@@ -626,6 +654,7 @@
|
|||||||
"channel_notifications.preferences": "Notification Preferences for ",
|
"channel_notifications.preferences": "Notification Preferences for ",
|
||||||
"channel_notifications.sendDesktop": "Send desktop notifications",
|
"channel_notifications.sendDesktop": "Send desktop notifications",
|
||||||
"channel_notifications.unreadInfo": "The channel name is bolded in the sidebar when there are unread messages. Selecting \"Only for mentions\" will bold the channel only when you are mentioned.",
|
"channel_notifications.unreadInfo": "The channel name is bolded in the sidebar when there are unread messages. Selecting \"Only for mentions\" will bold the channel only when you are mentioned.",
|
||||||
|
"channel_select.placeholder": "--- Select a channel ---",
|
||||||
"choose_auth_page.emailCreate": "Create new team with email address",
|
"choose_auth_page.emailCreate": "Create new team with email address",
|
||||||
"choose_auth_page.find": "Find my teams",
|
"choose_auth_page.find": "Find my teams",
|
||||||
"choose_auth_page.gitlabCreate": "Create new team with GitLab Account",
|
"choose_auth_page.gitlabCreate": "Create new team with GitLab Account",
|
||||||
@@ -768,6 +797,16 @@
|
|||||||
"get_team_invite_link_modal.help": "Send teammates the link below for them to sign-up to this team site. The Team Invite Link can be shared with multiple teammates as it does not change unless it's regenerated in Team Settings by a Team Admin.",
|
"get_team_invite_link_modal.help": "Send teammates the link below for them to sign-up to this team site. The Team Invite Link can be shared with multiple teammates as it does not change unless it's regenerated in Team Settings by a Team Admin.",
|
||||||
"get_team_invite_link_modal.helpDisabled": "User creation has been disabled for your team. Please ask your team administrator for details.",
|
"get_team_invite_link_modal.helpDisabled": "User creation has been disabled for your team. Please ask your team administrator for details.",
|
||||||
"get_team_invite_link_modal.title": "Team Invite Link",
|
"get_team_invite_link_modal.title": "Team Invite Link",
|
||||||
|
"installed_integrations.add": "Add Integration",
|
||||||
|
"installed_integrations.allFilter": "All",
|
||||||
|
"installed_integrations.delete": "Delete",
|
||||||
|
"installed_integrations.header": "Installed Integrations",
|
||||||
|
"installed_integrations.incomingWebhooksFilter": "Incoming Webhooks ({count})",
|
||||||
|
"installed_integrations.incomingWebhookType": "(Incoming Webhook)",
|
||||||
|
"installed_integrations.outgoingWebhooksFilter": "Outgoing Webhooks ({count})",
|
||||||
|
"installed_integrations.outgoingWebhookType": "(Outgoing Webhook)",
|
||||||
|
"installed_integrations.regenToken": "Regen Token",
|
||||||
|
"installed_integrations.search": "Search Integrations",
|
||||||
"intro_messages.DM": "This is the start of your direct message history with {teammate}.<br />Direct messages and files shared here are not shown to people outside this area.",
|
"intro_messages.DM": "This is the start of your direct message history with {teammate}.<br />Direct messages and files shared here are not shown to people outside this area.",
|
||||||
"intro_messages.anyMember": " Any member can join and read this channel.",
|
"intro_messages.anyMember": " Any member can join and read this channel.",
|
||||||
"intro_messages.beginning": "Beginning of {name}",
|
"intro_messages.beginning": "Beginning of {name}",
|
||||||
@@ -873,6 +912,7 @@
|
|||||||
"navbar_dropdown.console": "System Console",
|
"navbar_dropdown.console": "System Console",
|
||||||
"navbar_dropdown.create": "Create a New Team",
|
"navbar_dropdown.create": "Create a New Team",
|
||||||
"navbar_dropdown.help": "Help",
|
"navbar_dropdown.help": "Help",
|
||||||
|
"navbar_dropdown.integrations": "Integrations",
|
||||||
"navbar_dropdown.inviteMember": "Invite New Member",
|
"navbar_dropdown.inviteMember": "Invite New Member",
|
||||||
"navbar_dropdown.logout": "Logout",
|
"navbar_dropdown.logout": "Logout",
|
||||||
"navbar_dropdown.manageMembers": "Manage Members",
|
"navbar_dropdown.manageMembers": "Manage Members",
|
||||||
@@ -1274,27 +1314,6 @@
|
|||||||
"user.settings.general.usernameRestrictions": "Username must begin with a letter, and contain between {min} to {max} lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'.",
|
"user.settings.general.usernameRestrictions": "Username must begin with a letter, and contain between {min} to {max} lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'.",
|
||||||
"user.settings.general.validEmail": "Please enter a valid email address",
|
"user.settings.general.validEmail": "Please enter a valid email address",
|
||||||
"user.settings.general.validImage": "Only JPG or PNG images may be used for profile pictures",
|
"user.settings.general.validImage": "Only JPG or PNG images may be used for profile pictures",
|
||||||
"user.settings.hooks_in.add": "Add",
|
|
||||||
"user.settings.hooks_in.addTitle": "Add a new incoming webhook",
|
|
||||||
"user.settings.hooks_in.channel": "Channel: ",
|
|
||||||
"user.settings.hooks_in.description": "Create webhook URLs for use in external integrations. Please see <a href=\"http://docs.mattermost.com/developer/webhooks-incoming.html\" target=\"_blank\">incoming webhooks documentation</a> to learn more. View all incoming webhooks configured on this team below.",
|
|
||||||
"user.settings.hooks_in.existing": "Existing incoming webhooks",
|
|
||||||
"user.settings.hooks_in.none": "None",
|
|
||||||
"user.settings.hooks_out.add": "Add",
|
|
||||||
"user.settings.hooks_out.addDescription": "Create webhooks to send new message events to an external integration. Please see <a href=\"http://docs.mattermost.com/developer/webhooks-outgoing.html\" target=\"_blank\">outgoing webhooks documentation</a> to learn more. View all outgoing webhooks configured on this team below.",
|
|
||||||
"user.settings.hooks_out.addTitle": "Add a new outgoing webhook",
|
|
||||||
"user.settings.hooks_out.callback": "Callback URLs: ",
|
|
||||||
"user.settings.hooks_out.callbackDesc": "New line separated URLs that will receive the HTTP POST event",
|
|
||||||
"user.settings.hooks_out.callbackHolder": "Each URL must start with http:// or https://",
|
|
||||||
"user.settings.hooks_out.channel": "Channel: ",
|
|
||||||
"user.settings.hooks_out.comma": "Comma separated words to trigger on",
|
|
||||||
"user.settings.hooks_out.existing": "Existing outgoing webhooks",
|
|
||||||
"user.settings.hooks_out.none": "None",
|
|
||||||
"user.settings.hooks_out.only": "Only public channels can be used",
|
|
||||||
"user.settings.hooks_out.optional": "Optional if channel selected",
|
|
||||||
"user.settings.hooks_out.regen": "Regen Token",
|
|
||||||
"user.settings.hooks_out.select": "--- Select a channel ---",
|
|
||||||
"user.settings.hooks_out.trigger": "Trigger Words: ",
|
|
||||||
"user.settings.import_theme.cancel": "Cancel",
|
"user.settings.import_theme.cancel": "Cancel",
|
||||||
"user.settings.import_theme.importBody": "To import a theme, go to a Slack team and look for “Preferences -> Sidebar Theme”. Open the custom theme option, copy the theme color values and paste them here:",
|
"user.settings.import_theme.importBody": "To import a theme, go to a Slack team and look for “Preferences -> Sidebar Theme”. Open the custom theme option, copy the theme color values and paste them here:",
|
||||||
"user.settings.import_theme.importHeader": "Import Slack Theme",
|
"user.settings.import_theme.importHeader": "Import Slack Theme",
|
||||||
@@ -1302,10 +1321,6 @@
|
|||||||
"user.settings.import_theme.submitError": "Invalid format, please try copying and pasting in again.",
|
"user.settings.import_theme.submitError": "Invalid format, please try copying and pasting in again.",
|
||||||
"user.settings.integrations.commands": "Slash Commands",
|
"user.settings.integrations.commands": "Slash Commands",
|
||||||
"user.settings.integrations.commandsDescription": "Manage your slash commands",
|
"user.settings.integrations.commandsDescription": "Manage your slash commands",
|
||||||
"user.settings.integrations.incomingWebhooks": "Incoming Webhooks",
|
|
||||||
"user.settings.integrations.incomingWebhooksDescription": "Manage your incoming webhooks",
|
|
||||||
"user.settings.integrations.outWebhooks": "Outgoing Webhooks",
|
|
||||||
"user.settings.integrations.outWebhooksDescription": "Manage your outgoing webhooks",
|
|
||||||
"user.settings.integrations.title": "Integration Settings",
|
"user.settings.integrations.title": "Integration Settings",
|
||||||
"user.settings.languages.change": "Change interface language",
|
"user.settings.languages.change": "Change interface language",
|
||||||
"user.settings.modal.advanced": "Advanced",
|
"user.settings.modal.advanced": "Advanced",
|
||||||
|
|||||||
@@ -1274,27 +1274,6 @@
|
|||||||
"user.settings.general.usernameRestrictions": "El nombre de usuario debe empezar con una letra, y contener entre {min} a {max} caracteres en minúscula con números, lettras, y los símbolos '.', '-' y '_'.",
|
"user.settings.general.usernameRestrictions": "El nombre de usuario debe empezar con una letra, y contener entre {min} a {max} caracteres en minúscula con números, lettras, y los símbolos '.', '-' y '_'.",
|
||||||
"user.settings.general.validEmail": "Por favor ingresa una dirección de correo electrónico válida",
|
"user.settings.general.validEmail": "Por favor ingresa una dirección de correo electrónico válida",
|
||||||
"user.settings.general.validImage": "Sólo pueden ser utilizadas imágenes JPG o PNG en el perfil",
|
"user.settings.general.validImage": "Sólo pueden ser utilizadas imágenes JPG o PNG en el perfil",
|
||||||
"user.settings.hooks_in.add": "Agregar",
|
|
||||||
"user.settings.hooks_in.addTitle": "Agregar un nuevo webhook de entrada",
|
|
||||||
"user.settings.hooks_in.channel": "Canal: ",
|
|
||||||
"user.settings.hooks_in.description": "Crea URLs para webhooks a utilizar con integraciones externas. Revisa la <a href=\"http://docs.mattermost.com/developer/webhooks-incoming.html\" target=\"_blank\">documentación de webhooks de entrada</a> para conocer más. Ver todos los webhooks de entrada configurados para este equipo en la parte de abajo.",
|
|
||||||
"user.settings.hooks_in.existing": "Webhooks de entrada existentes",
|
|
||||||
"user.settings.hooks_in.none": "Ninguno",
|
|
||||||
"user.settings.hooks_out.add": "Agregar",
|
|
||||||
"user.settings.hooks_out.addDescription": "Crea webhooks para enviar mensajes a ingraciones externas. Revisa la <a href=\"http://docs.mattermost.com/developer/webhooks-outgoing.html\" target=\"_blank\">documentación de webhooks de saldida</a> para conocer más. Ver todos los webhooks de salida configurados para este equipo en la parte de abajo.",
|
|
||||||
"user.settings.hooks_out.addTitle": "Agregar un nuevo webhook de salida",
|
|
||||||
"user.settings.hooks_out.callback": "Callback URLs:",
|
|
||||||
"user.settings.hooks_out.callbackDesc": "Separa por una nueva linea cada URL donde quieres recibir el evento de HTTP POST",
|
|
||||||
"user.settings.hooks_out.callbackHolder": "Cada URL debe comenzar con http:// o https://",
|
|
||||||
"user.settings.hooks_out.channel": "Canal: ",
|
|
||||||
"user.settings.hooks_out.comma": "Escribe las palabras de activación que ejecutan el evento separadas por coma",
|
|
||||||
"user.settings.hooks_out.existing": "Webhooks de salida existentes",
|
|
||||||
"user.settings.hooks_out.none": "Ninguno",
|
|
||||||
"user.settings.hooks_out.only": "Sólo se pueden utilizar Canales",
|
|
||||||
"user.settings.hooks_out.optional": "Opcional si se selecciona un canal",
|
|
||||||
"user.settings.hooks_out.regen": "Regenerar Token",
|
|
||||||
"user.settings.hooks_out.select": "--- Selecciona un canal ---",
|
|
||||||
"user.settings.hooks_out.trigger": "Palabras de activación: ",
|
|
||||||
"user.settings.import_theme.cancel": "Cancelar",
|
"user.settings.import_theme.cancel": "Cancelar",
|
||||||
"user.settings.import_theme.importBody": "Para importar un tema, anda al equipo Slack y busca en [Preferences -> Sidebar Theme]. Abre las opciones del tema, copia los valores de color del tema y pégalo aquí:",
|
"user.settings.import_theme.importBody": "Para importar un tema, anda al equipo Slack y busca en [Preferences -> Sidebar Theme]. Abre las opciones del tema, copia los valores de color del tema y pégalo aquí:",
|
||||||
"user.settings.import_theme.importHeader": "Importar Tema de Slack",
|
"user.settings.import_theme.importHeader": "Importar Tema de Slack",
|
||||||
@@ -1302,10 +1281,6 @@
|
|||||||
"user.settings.import_theme.submitError": "Formato inválido, por favor intenta copiando y pegando nuevamente.",
|
"user.settings.import_theme.submitError": "Formato inválido, por favor intenta copiando y pegando nuevamente.",
|
||||||
"user.settings.integrations.commands": "Comandos de Barra",
|
"user.settings.integrations.commands": "Comandos de Barra",
|
||||||
"user.settings.integrations.commandsDescription": "Administra tus comandos de barra",
|
"user.settings.integrations.commandsDescription": "Administra tus comandos de barra",
|
||||||
"user.settings.integrations.incomingWebhooks": "Webhooks de entrada",
|
|
||||||
"user.settings.integrations.incomingWebhooksDescription": "Administra tus webhooks de entrada",
|
|
||||||
"user.settings.integrations.outWebhooks": "Webhooks de salida",
|
|
||||||
"user.settings.integrations.outWebhooksDescription": "Administra tus webhooks de salida",
|
|
||||||
"user.settings.integrations.title": "Configuraciones de Integración",
|
"user.settings.integrations.title": "Configuraciones de Integración",
|
||||||
"user.settings.languages.change": "Cambia el idioma con el que se muestra la intefaz de usuario",
|
"user.settings.languages.change": "Cambia el idioma con el que se muestra la intefaz de usuario",
|
||||||
"user.settings.modal.advanced": "Avanzada",
|
"user.settings.modal.advanced": "Avanzada",
|
||||||
|
|||||||
@@ -1274,27 +1274,6 @@
|
|||||||
"user.settings.general.usernameRestrictions": "Les noms d'utilisateurs doivent commencer par une lettre et contenir entre {min} et {max} caractères composés de chiffres, lettres minuscules et des symboles '.', '-' et '_'",
|
"user.settings.general.usernameRestrictions": "Les noms d'utilisateurs doivent commencer par une lettre et contenir entre {min} et {max} caractères composés de chiffres, lettres minuscules et des symboles '.', '-' et '_'",
|
||||||
"user.settings.general.validEmail": "Veuillez entrer une adresse électronique valide",
|
"user.settings.general.validEmail": "Veuillez entrer une adresse électronique valide",
|
||||||
"user.settings.general.validImage": "Seules les images JPG ou PNG sont autorisées pour les photos de profil",
|
"user.settings.general.validImage": "Seules les images JPG ou PNG sont autorisées pour les photos de profil",
|
||||||
"user.settings.hooks_in.add": "Ajouter",
|
|
||||||
"user.settings.hooks_in.addTitle": "Ajouter un webhook entrant",
|
|
||||||
"user.settings.hooks_in.channel": "Canal\u00a0: ",
|
|
||||||
"user.settings.hooks_in.description": "Crééez des URLs de webhooks pour des intégrations externes. Veuillez consulter <a href=\"http://docs.mattermost.com/developer/webhooks-incoming.html\" target=\"_blank\">la documentation sur les webhooks entrants</a> pour en savoir plus. Examinez tous les webhooks entrants configurés pour cette équipe ci-dessous.",
|
|
||||||
"user.settings.hooks_in.existing": "Webhooks entrants",
|
|
||||||
"user.settings.hooks_in.none": "Aucun",
|
|
||||||
"user.settings.hooks_out.add": "Ajouter",
|
|
||||||
"user.settings.hooks_out.addDescription": "Crééez des webhooks pour envoyer les évènements de nouveaux messages vers des intégrations externes. Veuillez consulter <a href=\"http://docs.mattermost.com/developer/webhooks-outgoing.html\" target=\"_blank\">la documentation sur les webhooks sortants</a> pour en savoir plus. Examinez tous les webhooks sortants configurés pour cette équipe ci-dessous.",
|
|
||||||
"user.settings.hooks_out.addTitle": "Ajouter un webhook sortant",
|
|
||||||
"user.settings.hooks_out.callback": "URLs de callback :",
|
|
||||||
"user.settings.hooks_out.callbackDesc": "URLs séparés par un saut de ligne qui recevront l'événement HTTP POST",
|
|
||||||
"user.settings.hooks_out.callbackHolder": "Chaque URL doit commencer par http:// ou https://",
|
|
||||||
"user.settings.hooks_out.channel": "Canal\u00a0: ",
|
|
||||||
"user.settings.hooks_out.comma": "Liste de mots déclencheurs séparés par une virgule",
|
|
||||||
"user.settings.hooks_out.existing": "Webhooks sortants",
|
|
||||||
"user.settings.hooks_out.none": "Aucun",
|
|
||||||
"user.settings.hooks_out.only": "Seuls les canaux publics peuvent être utilisés",
|
|
||||||
"user.settings.hooks_out.optional": "Facultatif si un canal est sélectionné",
|
|
||||||
"user.settings.hooks_out.regen": "Réinitialiser le jeton",
|
|
||||||
"user.settings.hooks_out.select": "--- Choisissez un canal ---",
|
|
||||||
"user.settings.hooks_out.trigger": "Mots de déclenchement :",
|
|
||||||
"user.settings.import_theme.cancel": "Annuler",
|
"user.settings.import_theme.cancel": "Annuler",
|
||||||
"user.settings.import_theme.importBody": "Pour importer un thème, rendez-vous sur une Slack team et cliquez sur \"Preferences -> Sidebar Theme\". Ouvrez la fenêtre de personnalisation, copiez les couleurs du thèmes et collez-les ici :",
|
"user.settings.import_theme.importBody": "Pour importer un thème, rendez-vous sur une Slack team et cliquez sur \"Preferences -> Sidebar Theme\". Ouvrez la fenêtre de personnalisation, copiez les couleurs du thèmes et collez-les ici :",
|
||||||
"user.settings.import_theme.importHeader": "Importer un thème Slack",
|
"user.settings.import_theme.importHeader": "Importer un thème Slack",
|
||||||
@@ -1302,10 +1281,6 @@
|
|||||||
"user.settings.import_theme.submitError": "Format invalide, veuillez réessayer de copier-coller.",
|
"user.settings.import_theme.submitError": "Format invalide, veuillez réessayer de copier-coller.",
|
||||||
"user.settings.integrations.commands": "Commandes slash",
|
"user.settings.integrations.commands": "Commandes slash",
|
||||||
"user.settings.integrations.commandsDescription": "Gérez vos commandes slash",
|
"user.settings.integrations.commandsDescription": "Gérez vos commandes slash",
|
||||||
"user.settings.integrations.incomingWebhooks": "Webhooks entrants",
|
|
||||||
"user.settings.integrations.incomingWebhooksDescription": "Gérer les webhooks entrants",
|
|
||||||
"user.settings.integrations.outWebhooks": "Webhooks sortants",
|
|
||||||
"user.settings.integrations.outWebhooksDescription": "Gérer les webhooks sortants",
|
|
||||||
"user.settings.integrations.title": "Paramètres d'intégration",
|
"user.settings.integrations.title": "Paramètres d'intégration",
|
||||||
"user.settings.languages.change": "Changer la langue de l'interface",
|
"user.settings.languages.change": "Changer la langue de l'interface",
|
||||||
"user.settings.modal.advanced": "Options avancées",
|
"user.settings.modal.advanced": "Options avancées",
|
||||||
|
|||||||
@@ -1273,27 +1273,6 @@
|
|||||||
"user.settings.general.usernameRestrictions": "O nome de usuário precisa começar com uma letra, e conter entre {min} e {max} caracteres minúsculos contendo números, letras, e os símbolos '.', '-' e '_'.",
|
"user.settings.general.usernameRestrictions": "O nome de usuário precisa começar com uma letra, e conter entre {min} e {max} caracteres minúsculos contendo números, letras, e os símbolos '.', '-' e '_'.",
|
||||||
"user.settings.general.validEmail": "Por favor entre um endereço de e-mail válido",
|
"user.settings.general.validEmail": "Por favor entre um endereço de e-mail válido",
|
||||||
"user.settings.general.validImage": "Somente imagens em JPG ou PNG podem ser usadas como imagem do perfil",
|
"user.settings.general.validImage": "Somente imagens em JPG ou PNG podem ser usadas como imagem do perfil",
|
||||||
"user.settings.hooks_in.add": "Adicionar",
|
|
||||||
"user.settings.hooks_in.addTitle": "Adicionar um novo webhook entrada",
|
|
||||||
"user.settings.hooks_in.channel": "Canal: ",
|
|
||||||
"user.settings.hooks_in.description": "Criar URLs webhook para usar em integrações externas. Por favor veja <a href=\"http://docs.mattermost.com/developer/webhooks-incoming.html\" target=\"_blank\">documentação webhook entrada</a> para saber mais. Ver todos os webhooks de entrada configurados nesta equipe abaixo.",
|
|
||||||
"user.settings.hooks_in.existing": "Webhooks de entrada existentes",
|
|
||||||
"user.settings.hooks_in.none": "Nenhum",
|
|
||||||
"user.settings.hooks_out.add": "Adicionar",
|
|
||||||
"user.settings.hooks_out.addDescription": "Criar webhooks para enviar novos mensagens de eventos para uma integração externa. Por favor veja <a href=\"http://docs.mattermost.com/developer/webhooks-outgoing.html\" target=\"_blank\">documentação webhook saída</a> para saber mais. Ver todos os webhooks de saída desta equipe configurados abaixo.",
|
|
||||||
"user.settings.hooks_out.addTitle": "Adicionar um novo webhook saída",
|
|
||||||
"user.settings.hooks_out.callback": "Callback URLs: ",
|
|
||||||
"user.settings.hooks_out.callbackDesc": "Nova linha separada de URLs que receberá o evento HTTP POST",
|
|
||||||
"user.settings.hooks_out.callbackHolder": "Cada URL deve começar com http:// ou https://",
|
|
||||||
"user.settings.hooks_out.channel": "Canal: ",
|
|
||||||
"user.settings.hooks_out.comma": "Palavras separadas por virgula para gatilho em",
|
|
||||||
"user.settings.hooks_out.existing": "Webhooks de saída existentes",
|
|
||||||
"user.settings.hooks_out.none": "Nenhum",
|
|
||||||
"user.settings.hooks_out.only": "Apenas canais públicos pode ser usado",
|
|
||||||
"user.settings.hooks_out.optional": "Opcional se o canal selecionado",
|
|
||||||
"user.settings.hooks_out.regen": "Re-Gerar Token",
|
|
||||||
"user.settings.hooks_out.select": "--- Selecione um canal ---",
|
|
||||||
"user.settings.hooks_out.trigger": "Palavras de Gatilho: ",
|
|
||||||
"user.settings.import_theme.cancel": "Cancelar",
|
"user.settings.import_theme.cancel": "Cancelar",
|
||||||
"user.settings.import_theme.importBody": "Para importar um tema, vá para uma equipe no Slack e olhe para “Preferences -> Sidebar Theme”. Abra a opção de tema customizado, copie os valores das cores do tema e cole eles aqui:",
|
"user.settings.import_theme.importBody": "Para importar um tema, vá para uma equipe no Slack e olhe para “Preferences -> Sidebar Theme”. Abra a opção de tema customizado, copie os valores das cores do tema e cole eles aqui:",
|
||||||
"user.settings.import_theme.importHeader": "Importar Tema Slack",
|
"user.settings.import_theme.importHeader": "Importar Tema Slack",
|
||||||
@@ -1301,10 +1280,6 @@
|
|||||||
"user.settings.import_theme.submitError": "Formato inválido, por favor tente copiar e colar novamente.",
|
"user.settings.import_theme.submitError": "Formato inválido, por favor tente copiar e colar novamente.",
|
||||||
"user.settings.integrations.commands": "Comandos Slash",
|
"user.settings.integrations.commands": "Comandos Slash",
|
||||||
"user.settings.integrations.commandsDescription": "Gerenciar seus comandos slash",
|
"user.settings.integrations.commandsDescription": "Gerenciar seus comandos slash",
|
||||||
"user.settings.integrations.incomingWebhooks": "Webhooks Entrada",
|
|
||||||
"user.settings.integrations.incomingWebhooksDescription": "Gerencie seus webhooks entrada",
|
|
||||||
"user.settings.integrations.outWebhooks": "Webhooks Saída",
|
|
||||||
"user.settings.integrations.outWebhooksDescription": "Gerencie seus webhooks saída",
|
|
||||||
"user.settings.integrations.title": "Configuração de Integração",
|
"user.settings.integrations.title": "Configuração de Integração",
|
||||||
"user.settings.languages.change": "Alterar o idioma da interface",
|
"user.settings.languages.change": "Alterar o idioma da interface",
|
||||||
"user.settings.modal.advanced": "Avançado",
|
"user.settings.modal.advanced": "Avançado",
|
||||||
|
|||||||
@@ -36,6 +36,12 @@ import ShouldVerifyEmail from 'components/should_verify_email.jsx';
|
|||||||
import DoVerifyEmail from 'components/do_verify_email.jsx';
|
import DoVerifyEmail from 'components/do_verify_email.jsx';
|
||||||
import AdminConsole from 'components/admin_console/admin_controller.jsx';
|
import AdminConsole from 'components/admin_console/admin_controller.jsx';
|
||||||
import TutorialView from 'components/tutorial/tutorial_view.jsx';
|
import TutorialView from 'components/tutorial/tutorial_view.jsx';
|
||||||
|
import BackstageNavbar from 'components/backstage/backstage_navbar.jsx';
|
||||||
|
import BackstageSidebar from 'components/backstage/backstage_sidebar.jsx';
|
||||||
|
import InstalledIntegrations from 'components/backstage/installed_integrations.jsx';
|
||||||
|
import AddIntegration from 'components/backstage/add_integration.jsx';
|
||||||
|
import AddIncomingWebhook from 'components/backstage/add_incoming_webhook.jsx';
|
||||||
|
import AddOutgoingWebhook from 'components/backstage/add_outgoing_webhook.jsx';
|
||||||
|
|
||||||
import SignupTeamComplete from 'components/signup_team_complete/components/signup_team_complete.jsx';
|
import SignupTeamComplete from 'components/signup_team_complete/components/signup_team_complete.jsx';
|
||||||
import WelcomePage from 'components/signup_team_complete/components/team_signup_welcome_page.jsx';
|
import WelcomePage from 'components/signup_team_complete/components/team_signup_welcome_page.jsx';
|
||||||
@@ -241,6 +247,42 @@ function renderRootComponent() {
|
|||||||
path=':team/logout'
|
path=':team/logout'
|
||||||
onEnter={onLoggedOut}
|
onEnter={onLoggedOut}
|
||||||
/>
|
/>
|
||||||
|
<Route path='settings/integrations'>
|
||||||
|
<IndexRedirect to='installed'/>
|
||||||
|
<Route
|
||||||
|
path='installed'
|
||||||
|
components={{
|
||||||
|
navbar: BackstageNavbar,
|
||||||
|
sidebar: BackstageSidebar,
|
||||||
|
center: InstalledIntegrations
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Route path='add'>
|
||||||
|
<IndexRoute
|
||||||
|
components={{
|
||||||
|
navbar: BackstageNavbar,
|
||||||
|
sidebar: BackstageSidebar,
|
||||||
|
center: AddIntegration
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path='incoming_webhook'
|
||||||
|
components={{
|
||||||
|
navbar: BackstageNavbar,
|
||||||
|
sidebar: BackstageSidebar,
|
||||||
|
center: AddIncomingWebhook
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path='outgoing_webhook'
|
||||||
|
components={{
|
||||||
|
navbar: BackstageNavbar,
|
||||||
|
sidebar: BackstageSidebar,
|
||||||
|
center: AddOutgoingWebhook
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
</Route>
|
||||||
<Route
|
<Route
|
||||||
path='admin_console'
|
path='admin_console'
|
||||||
component={AdminConsole}
|
component={AdminConsole}
|
||||||
|
|||||||
212
webapp/sass/routes/_backstage.scss
Обычный файл
212
webapp/sass/routes/_backstage.scss
Обычный файл
@@ -0,0 +1,212 @@
|
|||||||
|
.backstage {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
height: 100%;
|
||||||
|
padding-left: 260px;
|
||||||
|
padding-top: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.backstage__navbar {
|
||||||
|
background: white;
|
||||||
|
border-bottom: lightgray 1px solid;
|
||||||
|
margin: 0 -15px;
|
||||||
|
padding: 10px;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.backstage__navbar__back {
|
||||||
|
color: black;
|
||||||
|
|
||||||
|
.fa {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.backstage__sidebar {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
width: 260px;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-right: 20px;
|
||||||
|
padding-top: 45px;
|
||||||
|
z-index: 5;
|
||||||
|
|
||||||
|
ul {
|
||||||
|
padding: 0px;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.backstage__sidebar__category {
|
||||||
|
border: lightgray 1px solid;
|
||||||
|
|
||||||
|
.category-title {
|
||||||
|
color: gray;
|
||||||
|
display: block;
|
||||||
|
padding: 5px 10px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-title--active {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-title__text {
|
||||||
|
position: absolute;
|
||||||
|
left: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sections {
|
||||||
|
background: white;
|
||||||
|
border-top: lightgray 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
display: block;
|
||||||
|
padding-left: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subsection {
|
||||||
|
}
|
||||||
|
|
||||||
|
.subsection-title {
|
||||||
|
display: block;
|
||||||
|
padding-left: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title--active, .subsection-title--active {
|
||||||
|
background-color:#2388d6;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.backstage__sidebar__category + .backstage__sidebar__category {
|
||||||
|
border-top-width: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.installed-integrations {
|
||||||
|
height: 100%;
|
||||||
|
max-width: 958px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.backstage__header {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.text {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-integrations-link {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.installed-integrations__filters {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.type-filters {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.type-filter {
|
||||||
|
&--selected {
|
||||||
|
color: black;
|
||||||
|
cursor: default;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
margin-left: 8px;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-box {
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.installed-integrations__list {
|
||||||
|
background-color: white;
|
||||||
|
border: lightgray 1px solid;
|
||||||
|
padding-bottom: 30px;
|
||||||
|
padding-left: 30px;
|
||||||
|
padding-right: 30px;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.installed-integrations__item {
|
||||||
|
border-bottom: lightgray 1px solid;
|
||||||
|
display: flex;
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
.details {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
|
.details-row + .details-row {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.type {
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
color: gray;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-integration-option {
|
||||||
|
background-color: white;
|
||||||
|
border: lightgray 1px solid;
|
||||||
|
display: inline-block;
|
||||||
|
height: 210px;
|
||||||
|
margin-right: 30px;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
width: 250px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: default;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__image {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
color: black;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__description {
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
@import 'access-history';
|
@import 'access-history';
|
||||||
@import 'activity-log';
|
@import 'activity-log';
|
||||||
@import 'admin-console';
|
@import 'admin-console';
|
||||||
|
@import 'backstage';
|
||||||
@import 'docs';
|
@import 'docs';
|
||||||
@import 'error-page';
|
@import 'error-page';
|
||||||
@import 'loading';
|
@import 'loading';
|
||||||
|
|||||||
@@ -13,11 +13,6 @@ class FileStore extends EventEmitter {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.addChangeListener = this.addChangeListener.bind(this);
|
|
||||||
this.removeChangeListener = this.removeChangeListener.bind(this);
|
|
||||||
this.emitChange = this.emitChange.bind(this);
|
|
||||||
|
|
||||||
this.handleEventPayload = this.handleEventPayload.bind(this);
|
|
||||||
this.dispatchToken = AppDispatcher.register(this.handleEventPayload);
|
this.dispatchToken = AppDispatcher.register(this.handleEventPayload);
|
||||||
|
|
||||||
this.fileInfo = new Map();
|
this.fileInfo = new Map();
|
||||||
|
|||||||
134
webapp/stores/integration_store.jsx
Обычный файл
134
webapp/stores/integration_store.jsx
Обычный файл
@@ -0,0 +1,134 @@
|
|||||||
|
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
|
||||||
|
import Constants from 'utils/constants.jsx';
|
||||||
|
import EventEmitter from 'events';
|
||||||
|
|
||||||
|
const ActionTypes = Constants.ActionTypes;
|
||||||
|
|
||||||
|
const CHANGE_EVENT = 'changed';
|
||||||
|
|
||||||
|
class IntegrationStore extends EventEmitter {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.dispatchToken = AppDispatcher.register(this.handleEventPayload.bind(this));
|
||||||
|
|
||||||
|
this.incomingWebhooks = [];
|
||||||
|
this.receivedIncomingWebhooks = false;
|
||||||
|
|
||||||
|
this.outgoingWebhooks = [];
|
||||||
|
this.receivedOutgoingWebhooks = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
addChangeListener(callback) {
|
||||||
|
this.on(CHANGE_EVENT, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeChangeListener(callback) {
|
||||||
|
this.removeListener(CHANGE_EVENT, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
emitChange() {
|
||||||
|
this.emit(CHANGE_EVENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasReceivedIncomingWebhooks() {
|
||||||
|
return this.receivedIncomingWebhooks;
|
||||||
|
}
|
||||||
|
|
||||||
|
getIncomingWebhooks() {
|
||||||
|
return this.incomingWebhooks;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIncomingWebhooks(incomingWebhooks) {
|
||||||
|
this.incomingWebhooks = incomingWebhooks;
|
||||||
|
this.receivedIncomingWebhooks = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
addIncomingWebhook(incomingWebhook) {
|
||||||
|
this.incomingWebhooks.push(incomingWebhook);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeIncomingWebhook(id) {
|
||||||
|
for (let i = 0; i < this.incomingWebhooks.length; i++) {
|
||||||
|
if (this.incomingWebhooks[i].id === id) {
|
||||||
|
this.incomingWebhooks.splice(i, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hasReceivedOutgoingWebhooks() {
|
||||||
|
return this.receivedIncomingWebhooks;
|
||||||
|
}
|
||||||
|
|
||||||
|
getOutgoingWebhooks() {
|
||||||
|
return this.outgoingWebhooks;
|
||||||
|
}
|
||||||
|
|
||||||
|
setOutgoingWebhooks(outgoingWebhooks) {
|
||||||
|
this.outgoingWebhooks = outgoingWebhooks;
|
||||||
|
this.receivedOutgoingWebhooks = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
addOutgoingWebhook(outgoingWebhook) {
|
||||||
|
this.outgoingWebhooks.push(outgoingWebhook);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateOutgoingWebhook(outgoingWebhook) {
|
||||||
|
for (let i = 0; i < this.outgoingWebhooks.length; i++) {
|
||||||
|
if (this.outgoingWebhooks[i].id === outgoingWebhook.id) {
|
||||||
|
this.outgoingWebhooks[i] = outgoingWebhook;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeOutgoingWebhook(id) {
|
||||||
|
for (let i = 0; i < this.outgoingWebhooks.length; i++) {
|
||||||
|
if (this.outgoingWebhooks[i].id === id) {
|
||||||
|
this.outgoingWebhooks.splice(i, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleEventPayload(payload) {
|
||||||
|
const action = payload.action;
|
||||||
|
|
||||||
|
switch (action.type) {
|
||||||
|
case ActionTypes.RECEIVED_INCOMING_WEBHOOKS:
|
||||||
|
this.setIncomingWebhooks(action.incomingWebhooks);
|
||||||
|
this.emitChange();
|
||||||
|
break;
|
||||||
|
case ActionTypes.RECEIVED_INCOMING_WEBHOOK:
|
||||||
|
this.addIncomingWebhook(action.incomingWebhook);
|
||||||
|
this.emitChange();
|
||||||
|
break;
|
||||||
|
case ActionTypes.REMOVED_INCOMING_WEBHOOK:
|
||||||
|
this.removeIncomingWebhook(action.id);
|
||||||
|
this.emitChange();
|
||||||
|
break;
|
||||||
|
case ActionTypes.RECEIVED_OUTGOING_WEBHOOKS:
|
||||||
|
this.setOutgoingWebhooks(action.outgoingWebhooks);
|
||||||
|
this.emitChange();
|
||||||
|
break;
|
||||||
|
case ActionTypes.RECEIVED_OUTGOING_WEBHOOK:
|
||||||
|
this.addOutgoingWebhook(action.outgoingWebhook);
|
||||||
|
this.emitChange();
|
||||||
|
break;
|
||||||
|
case ActionTypes.UPDATED_OUTGOING_WEBHOOK:
|
||||||
|
this.updateOutgoingWebhook(action.outgoingWebhook);
|
||||||
|
this.emitChange();
|
||||||
|
break;
|
||||||
|
case ActionTypes.REMOVED_OUTGOING_WEBHOOK:
|
||||||
|
this.removeOutgoingWebhook(action.id);
|
||||||
|
this.emitChange();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new IntegrationStore();
|
||||||
@@ -1121,3 +1121,140 @@ export function getRecentAndNewUsersAnalytics(teamId) {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function listIncomingHooks() {
|
||||||
|
if (isCallInProgress('listIncomingHooks')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callTracker.listIncomingHooks = utils.getTimestamp();
|
||||||
|
|
||||||
|
client.listIncomingHooks(
|
||||||
|
(data) => {
|
||||||
|
callTracker.listIncomingHooks = 0;
|
||||||
|
|
||||||
|
AppDispatcher.handleServerAction({
|
||||||
|
type: ActionTypes.RECEIVED_INCOMING_WEBHOOKS,
|
||||||
|
incomingWebhooks: data
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
callTracker.listIncomingHooks = 0;
|
||||||
|
dispatchError(err, 'getIncomingHooks');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function listOutgoingHooks() {
|
||||||
|
if (isCallInProgress('listOutgoingHooks')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callTracker.listOutgoingHooks = utils.getTimestamp();
|
||||||
|
|
||||||
|
client.listOutgoingHooks(
|
||||||
|
(data) => {
|
||||||
|
callTracker.listOutgoingHooks = 0;
|
||||||
|
|
||||||
|
AppDispatcher.handleServerAction({
|
||||||
|
type: ActionTypes.RECEIVED_OUTGOING_WEBHOOKS,
|
||||||
|
outgoingWebhooks: data
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
callTracker.listOutgoingHooks = 0;
|
||||||
|
dispatchError(err, 'getOutgoingHooks');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addIncomingHook(hook, success, error) {
|
||||||
|
client.addIncomingHook(
|
||||||
|
hook,
|
||||||
|
(data) => {
|
||||||
|
AppDispatcher.handleServerAction({
|
||||||
|
type: ActionTypes.RECEIVED_INCOMING_WEBHOOK,
|
||||||
|
incomingWebhook: data
|
||||||
|
});
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
success();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
dispatchError(err, 'addIncomingHook');
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addOutgoingHook(hook, success, error) {
|
||||||
|
client.addOutgoingHook(
|
||||||
|
hook,
|
||||||
|
(data) => {
|
||||||
|
AppDispatcher.handleServerAction({
|
||||||
|
type: ActionTypes.RECEIVED_OUTGOING_WEBHOOK,
|
||||||
|
outgoingWebhook: data
|
||||||
|
});
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
success();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
dispatchError(err, 'addOutgoingHook');
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteIncomingHook(id) {
|
||||||
|
client.deleteIncomingHook(
|
||||||
|
{id},
|
||||||
|
() => {
|
||||||
|
AppDispatcher.handleServerAction({
|
||||||
|
type: ActionTypes.REMOVED_INCOMING_WEBHOOK,
|
||||||
|
id
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
dispatchError(err, 'deleteIncomingHook');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteOutgoingHook(id) {
|
||||||
|
client.deleteOutgoingHook(
|
||||||
|
{id},
|
||||||
|
() => {
|
||||||
|
AppDispatcher.handleServerAction({
|
||||||
|
type: ActionTypes.REMOVED_OUTGOING_WEBHOOK,
|
||||||
|
id
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
dispatchError(err, 'deleteOutgoingHook');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function regenOutgoingHookToken(id) {
|
||||||
|
client.regenOutgoingHookToken(
|
||||||
|
{id},
|
||||||
|
(data) => {
|
||||||
|
AppDispatcher.handleServerAction({
|
||||||
|
type: ActionTypes.UPDATED_OUTGOING_WEBHOOK,
|
||||||
|
outgoingWebhook: data
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
dispatchError(err, 'regenOutgoingHookToken');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -70,6 +70,14 @@ export default {
|
|||||||
RECEIVED_FILE_INFO: null,
|
RECEIVED_FILE_INFO: null,
|
||||||
RECEIVED_ANALYTICS: null,
|
RECEIVED_ANALYTICS: null,
|
||||||
|
|
||||||
|
RECEIVED_INCOMING_WEBHOOKS: null,
|
||||||
|
RECEIVED_INCOMING_WEBHOOK: null,
|
||||||
|
REMOVED_INCOMING_WEBHOOK: null,
|
||||||
|
RECEIVED_OUTGOING_WEBHOOKS: null,
|
||||||
|
RECEIVED_OUTGOING_WEBHOOK: null,
|
||||||
|
UPDATED_OUTGOING_WEBHOOK: null,
|
||||||
|
REMOVED_OUTGOING_WEBHOOK: null,
|
||||||
|
|
||||||
RECEIVED_MSG: null,
|
RECEIVED_MSG: null,
|
||||||
|
|
||||||
RECEIVED_MY_TEAM: null,
|
RECEIVED_MY_TEAM: null,
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ var config = {
|
|||||||
loaders: ['style', 'css']
|
loaders: ['style', 'css']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.(png|eot|tiff|svg|woff2|woff|ttf|gif|mp3)$/,
|
test: /\.(png|eot|tiff|svg|woff2|woff|ttf|gif|mp3|jpg)$/,
|
||||||
loader: 'file',
|
loader: 'file',
|
||||||
query: {
|
query: {
|
||||||
name: 'files/[hash].[ext]'
|
name: 'files/[hash].[ext]'
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user