Added initial backstage components and InstalledIntegrations page
Этот коммит содержится в:
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
62
webapp/components/backstage/backstage_navbar.jsx
Обычный файл
62
webapp/components/backstage/backstage_navbar.jsx
Обычный файл
@@ -0,0 +1,62 @@
|
|||||||
|
// 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.back_to_mattermost'
|
||||||
|
defaultMessage='Back to {siteName}'
|
||||||
|
values={{
|
||||||
|
siteName: global.window.mm_config.SiteName
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
<span style={{float: 'right'}}>{'TODO: Switch Teams'}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
122
webapp/components/backstage/backstage_section.jsx
Обычный файл
122
webapp/components/backstage/backstage_section.jsx
Обычный файл
@@ -0,0 +1,122 @@
|
|||||||
|
// 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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.handleClick = this.handleClick.bind(this);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
expanded: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getLink() {
|
||||||
|
return this.props.parentLink + '/' + this.props.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
isActive() {
|
||||||
|
const link = this.getLink();
|
||||||
|
|
||||||
|
return this.context.router.isActive(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClick(e) {
|
||||||
|
if (this.isActive()) {
|
||||||
|
// we're already on this page so just toggle the link
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
expanded: !this.state.expanded
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, just follow the link
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {title, subsection, children} = this.props;
|
||||||
|
|
||||||
|
const link = this.getLink();
|
||||||
|
const active = this.isActive();
|
||||||
|
|
||||||
|
// act like docs.mattermost.com and only expand if this link is active
|
||||||
|
const expanded = active && this.state.expanded;
|
||||||
|
|
||||||
|
let toggle = null;
|
||||||
|
if (children.length > 0) {
|
||||||
|
if (expanded) {
|
||||||
|
toggle = <i className='fa fa-minus-square-o'/>;
|
||||||
|
} else {
|
||||||
|
toggle = <i className='fa fa-plus-square-o'/>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let clonedChildren = null;
|
||||||
|
if (children.length > 0 && expanded) {
|
||||||
|
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`}
|
||||||
|
onClick={this.handleClick}
|
||||||
|
to={link}
|
||||||
|
>
|
||||||
|
{toggle}
|
||||||
|
<span className={`${className}-title__text`}>
|
||||||
|
{title}
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
{clonedChildren}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
113
webapp/components/backstage/backstage_sidebar.jsx
Обычный файл
113
webapp/components/backstage/backstage_sidebar.jsx
Обычный файл
@@ -0,0 +1,113 @@
|
|||||||
|
// 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 BackstageCategory from './backstage_category.jsx';
|
||||||
|
import BackstageSection from './backstage_section.jsx';
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
|
export default class BackstageSidebar 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() {
|
||||||
|
const team = TeamStore.getCurrent();
|
||||||
|
|
||||||
|
if (!team) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='backstage__sidebar'>
|
||||||
|
<ul>
|
||||||
|
<BackstageCategory
|
||||||
|
name='team_settings'
|
||||||
|
parentLink={`/${team.name}`}
|
||||||
|
icon='fa-users'
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='backstage.team_settings'
|
||||||
|
defaultMessage='Team Settings'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<BackstageCategory
|
||||||
|
name='integrations'
|
||||||
|
parentLink={`/${team.name}`}
|
||||||
|
icon='fa-link'
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='backstage.integrations'
|
||||||
|
defaultMessage='Integrations'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<BackstageSection
|
||||||
|
name='installed'
|
||||||
|
title={(
|
||||||
|
<FormattedMessage
|
||||||
|
id='backstage.integrations.installed'
|
||||||
|
defaultMessage='Installed Integrations'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<BackstageSection
|
||||||
|
name='add'
|
||||||
|
title={(
|
||||||
|
<FormattedMessage
|
||||||
|
id='backstage.integrations.add'
|
||||||
|
defaultMessage='Add Integration'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
collapsible={true}
|
||||||
|
>
|
||||||
|
<BackstageSection
|
||||||
|
name='incoming_webhook'
|
||||||
|
title={(
|
||||||
|
<FormattedMessage
|
||||||
|
id='backstage.integrations.add.incomingWebhook'
|
||||||
|
defaultMessage='Incoming Webhook'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<BackstageSection
|
||||||
|
name='outgoing_webhook'
|
||||||
|
title={(
|
||||||
|
<FormattedMessage
|
||||||
|
id='backstage.integrations.add.outgoingWebhook'
|
||||||
|
defaultMessage='Outgoing Webhook'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</BackstageSection>
|
||||||
|
</BackstageCategory>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
304
webapp/components/backstage/installed_integrations.jsx
Обычный файл
304
webapp/components/backstage/installed_integrations.jsx
Обычный файл
@@ -0,0 +1,304 @@
|
|||||||
|
// 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 {Link} from 'react-router';
|
||||||
|
|
||||||
|
export default class InstalledIntegrations extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.handleChange = this.handleChange.bind(this);
|
||||||
|
this.setFilter = this.setFilter.bind(this);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
incomingWebhooks: [],
|
||||||
|
outgoingWebhooks: [],
|
||||||
|
filter: ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
IntegrationStore.addChangeListener(this.handleChange);
|
||||||
|
|
||||||
|
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.handleChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange() {
|
||||||
|
this.setState({
|
||||||
|
incomingWebhooks: IntegrationStore.getIncomingWebhooks(),
|
||||||
|
outgoingWebhooks: IntegrationStore.getOutgoingWebhooks()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setFilter(e, filter) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
filter
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTypeFilters(incomingWebhooks, outgoingWebhooks) {
|
||||||
|
const fields = [];
|
||||||
|
|
||||||
|
if (incomingWebhooks.length > 0 || outgoingWebhooks.length > 0) {
|
||||||
|
let filterClassName = 'type-filter';
|
||||||
|
if (this.state.filter === '') {
|
||||||
|
filterClassName += ' type-filter--selected';
|
||||||
|
}
|
||||||
|
|
||||||
|
fields.push(
|
||||||
|
<a
|
||||||
|
key='allFilter'
|
||||||
|
className={filterClassName}
|
||||||
|
href='#'
|
||||||
|
onClick={(e) => this.setFilter(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.filter === 'incomingWebhooks') {
|
||||||
|
filterClassName += ' type-filter--selected';
|
||||||
|
}
|
||||||
|
|
||||||
|
fields.push(
|
||||||
|
<a
|
||||||
|
key='incomingWebhooksFilter'
|
||||||
|
className={filterClassName}
|
||||||
|
href='#'
|
||||||
|
onClick={(e) => this.setFilter(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.filter === 'outgoingWebhooks') {
|
||||||
|
filterClassName += ' type-filter--selected';
|
||||||
|
}
|
||||||
|
|
||||||
|
fields.push(
|
||||||
|
<a
|
||||||
|
key='outgoingWebhooksFilter'
|
||||||
|
className={filterClassName}
|
||||||
|
href='#'
|
||||||
|
onClick={(e) => this.setFilter(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 integrations = [];
|
||||||
|
if (!this.state.filter || this.state.filter === 'incomingWebhooks') {
|
||||||
|
for (const incomingWebhook of incomingWebhooks) {
|
||||||
|
integrations.push(
|
||||||
|
<IncomingWebhook
|
||||||
|
key={incomingWebhook.id}
|
||||||
|
incomingWebhook={incomingWebhook}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.state.filter || this.state.filter === 'outgoingWebhooks') {
|
||||||
|
for (const outgoingWebhook of outgoingWebhooks) {
|
||||||
|
integrations.push(
|
||||||
|
<OutgoingWebhook
|
||||||
|
key={outgoingWebhook.id}
|
||||||
|
outgoingWebhook={outgoingWebhook}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='backstage row'>
|
||||||
|
<div className='installed-integrations'>
|
||||||
|
<div className='installed-integrations__header'>
|
||||||
|
<h1 className='text'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='installed_integrations.header'
|
||||||
|
defaultMessage='Installed Integrations'
|
||||||
|
/>
|
||||||
|
</h1>
|
||||||
|
<Link
|
||||||
|
className='add-integrations-link'
|
||||||
|
to={'/yourteamhere/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')}
|
||||||
|
style={{flexGrow: 0, flexShrink: 0}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='installed-integrations__list'>
|
||||||
|
{integrations}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function IncomingWebhook({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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
IncomingWebhook.propTypes = {
|
||||||
|
incomingWebhook: React.PropTypes.object.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
function OutgoingWebhook({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}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
OutgoingWebhook.propTypes = {
|
||||||
|
outgoingWebhook: React.PropTypes.object.isRequired
|
||||||
|
};
|
||||||
@@ -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
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ 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 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 +244,51 @@ function renderRootComponent() {
|
|||||||
path=':team/logout'
|
path=':team/logout'
|
||||||
onEnter={onLoggedOut}
|
onEnter={onLoggedOut}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path=':team/team_settings'
|
||||||
|
components={{
|
||||||
|
navbar: BackstageNavbar,
|
||||||
|
sidebar: BackstageSidebar,
|
||||||
|
center: null
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Route path=':team/integrations'>
|
||||||
|
<IndexRedirect to='installed'/>
|
||||||
|
<Route
|
||||||
|
path='installed'
|
||||||
|
components={{
|
||||||
|
navbar: BackstageNavbar,
|
||||||
|
sidebar: BackstageSidebar,
|
||||||
|
center: InstalledIntegrations
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Route path='add'>
|
||||||
|
<IndexRoute
|
||||||
|
components={{
|
||||||
|
navbar: BackstageNavbar,
|
||||||
|
sidebar: BackstageSidebar,
|
||||||
|
center: null
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path='incoming_webhook'
|
||||||
|
components={{
|
||||||
|
navbar: BackstageNavbar,
|
||||||
|
sidebar: BackstageSidebar,
|
||||||
|
center: null
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path='outgoing_webhook'
|
||||||
|
components={{
|
||||||
|
navbar: BackstageNavbar,
|
||||||
|
sidebar: BackstageSidebar,
|
||||||
|
center: null
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
</Route>
|
||||||
|
>>>>>>> Added initial backstage components and InstalledIntegrations page
|
||||||
<Route
|
<Route
|
||||||
path='admin_console'
|
path='admin_console'
|
||||||
component={AdminConsole}
|
component={AdminConsole}
|
||||||
|
|||||||
182
webapp/sass/routes/_backstage.scss
Обычный файл
182
webapp/sass/routes/_backstage.scss
Обычный файл
@@ -0,0 +1,182 @@
|
|||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.installed-integrations__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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
|||||||
80
webapp/stores/integration_store.jsx
Обычный файл
80
webapp/stores/integration_store.jsx
Обычный файл
@@ -0,0 +1,80 @@
|
|||||||
|
// 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';
|
||||||
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
|
||||||
|
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 = Utils.freezeArray(incomingWebhooks);
|
||||||
|
this.receivedIncomingWebhooks = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasReceivedOutgoingWebhooks() {
|
||||||
|
return this.receivedIncomingWebhooks;
|
||||||
|
}
|
||||||
|
|
||||||
|
getOutgoingWebhooks() {
|
||||||
|
return this.outgoingWebhooks;
|
||||||
|
}
|
||||||
|
|
||||||
|
setOutgoingWebhooks(outgoingWebhooks) {
|
||||||
|
this.outgoingWebhooks = Utils.freezeArray(outgoingWebhooks);
|
||||||
|
this.receivedOutgoingWebhooks = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleEventPayload(payload) {
|
||||||
|
const action = payload.action;
|
||||||
|
|
||||||
|
switch (action.type) {
|
||||||
|
case ActionTypes.RECEIVED_INCOMING_WEBHOOKS:
|
||||||
|
this.setIncomingWebhooks(action.incomingWebhooks);
|
||||||
|
this.emitChange();
|
||||||
|
break;
|
||||||
|
case ActionTypes.RECEIVED_OUTGOING_WEBHOOKS:
|
||||||
|
this.setOutgoingWebhooks(action.outgoingWebhooks);
|
||||||
|
this.emitChange();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new IntegrationStore();
|
||||||
@@ -1121,3 +1121,49 @@ 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');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -68,6 +68,8 @@ export default {
|
|||||||
RECEIVED_PREFERENCE: null,
|
RECEIVED_PREFERENCE: null,
|
||||||
RECEIVED_PREFERENCES: null,
|
RECEIVED_PREFERENCES: null,
|
||||||
RECEIVED_FILE_INFO: null,
|
RECEIVED_FILE_INFO: null,
|
||||||
|
RECEIVED_INCOMING_WEBHOOKS: null,
|
||||||
|
RECEIVED_OUTGOING_WEBHOOKS: null,
|
||||||
|
|
||||||
RECEIVED_MSG: null,
|
RECEIVED_MSG: null,
|
||||||
|
|
||||||
|
|||||||
@@ -1398,3 +1398,13 @@ export function localizeMessage(id, defaultMessage) {
|
|||||||
|
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function freezeArray(arr) {
|
||||||
|
for (const obj of arr) {
|
||||||
|
Object.freeze(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.freeze(arr);
|
||||||
|
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user