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() {
|
||||
return (
|
||||
<SpinnerButton
|
||||
className='btn btn-sm btn-primary'
|
||||
onClick={this.handleClick}
|
||||
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) {
|
||||
content = this.props.children;
|
||||
} else {
|
||||
content.push(
|
||||
this.props.navbar
|
||||
);
|
||||
content.push(
|
||||
this.props.sidebar
|
||||
);
|
||||
@@ -247,8 +250,9 @@ LoggedIn.defaultProps = {
|
||||
};
|
||||
|
||||
LoggedIn.propTypes = {
|
||||
children: React.PropTypes.object,
|
||||
sidebar: React.PropTypes.object,
|
||||
center: React.PropTypes.object,
|
||||
children: React.PropTypes.arrayOf(React.PropTypes.element),
|
||||
navbar: React.PropTypes.element,
|
||||
sidebar: React.PropTypes.element,
|
||||
center: React.PropTypes.element,
|
||||
params: React.PropTypes.object
|
||||
};
|
||||
|
||||
@@ -86,6 +86,7 @@ export default class MoreDirectChannels extends React.Component {
|
||||
createJoinDirectChannelButton({user}) {
|
||||
return (
|
||||
<SpinnerButton
|
||||
className='btn btm-sm btn-primary'
|
||||
spinning={this.state.loadingDMChannel === user.id}
|
||||
onClick={this.handleShowDirectChannel.bind(this, user)}
|
||||
>
|
||||
|
||||
@@ -59,6 +59,7 @@ export default class NavbarDropdown extends React.Component {
|
||||
var isAdmin = false;
|
||||
var isSystemAdmin = false;
|
||||
var teamSettings = null;
|
||||
let integrationsLink = null;
|
||||
|
||||
if (currentUser != null) {
|
||||
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) {
|
||||
sysAdminLink = (
|
||||
<li>
|
||||
@@ -238,6 +254,7 @@ export default class NavbarDropdown extends React.Component {
|
||||
</li>
|
||||
{adminDivider}
|
||||
{teamSettings}
|
||||
{integrationsLink}
|
||||
{manageLink}
|
||||
{sysAdminLink}
|
||||
{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() {
|
||||
if (this.props.spinning) {
|
||||
const {spinning, children, ...props} = this.props; // eslint-disable-line no-use-before-define
|
||||
|
||||
if (spinning) {
|
||||
return (
|
||||
<img
|
||||
className='spinner-button__gif'
|
||||
@@ -38,10 +28,10 @@ export default class SpinnerButton extends React.Component {
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={this.handleClick}
|
||||
className='btn btn-sm btn-primary'
|
||||
className='btn btn-primary'
|
||||
{...props}
|
||||
>
|
||||
{this.props.children}
|
||||
{children}
|
||||
</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 SettingItemMin from '../setting_item_min.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 {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl';
|
||||
|
||||
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: {
|
||||
id: 'user.settings.integrations.commands',
|
||||
defaultMessage: 'Slash Commands'
|
||||
@@ -52,74 +34,10 @@ class UserSettingsIntegrationsTab extends React.Component {
|
||||
this.props.updateSection(section);
|
||||
}
|
||||
render() {
|
||||
let incomingHooksSection;
|
||||
let outgoingHooksSection;
|
||||
let commandHooksSection;
|
||||
var inputs = [];
|
||||
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 (this.props.activeSection === 'command-hooks') {
|
||||
inputs.push(
|
||||
@@ -187,10 +105,6 @@ class UserSettingsIntegrationsTab extends React.Component {
|
||||
/>
|
||||
</h3>
|
||||
<div className='divider-dark first'/>
|
||||
{incomingHooksSection}
|
||||
<div className='divider-light'/>
|
||||
{outgoingHooksSection}
|
||||
<div className='divider-dark'/>
|
||||
{commandHooksSection}
|
||||
<div className='divider-dark'/>
|
||||
</div>
|
||||
@@ -209,4 +123,4 @@ UserSettingsIntegrationsTab.propTypes = {
|
||||
collapseModal: React.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default injectIntl(UserSettingsIntegrationsTab);
|
||||
export default injectIntl(UserSettingsIntegrationsTab);
|
||||
|
||||
Ссылка в новой задаче
Block a user