PLT-2257 Reorganized System Console (#3003)

* Reorganized system console

* Fixed the names of some components

* Fixed timestamp for BrandImageSetting

* Fixed merge issues

* Updated push notification settings to match master branch

* Removed top level setting pages and moved enable Gitlab/LDAP settings onto their respective pages

* Re-added restrictDirectMessage setting to system console

* Re-added email connection test and fixed some margins

* Fixed ESLint errors

* Renamed Authentication > Onboarding to Authentication > Email in the system console

* Renamed Customization > Whitelabeling to Customization > Custom Branding in System Console

* Re-added EnableOpenServer to system console
Этот коммит содержится в:
Harrison Healey
2016-05-17 07:21:39 -04:00
коммит произвёл Christopher Speller
родитель 5f5f813387
Коммит fd53e3b786
57 изменённых файлов: 5754 добавлений и 6631 удалений

Просмотреть файл

@@ -0,0 +1,61 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import $ from 'jquery';
import React from 'react';
import AdminStore from 'stores/admin_store.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import AdminSidebar from './admin_sidebar.jsx';
export default class AdminConsole extends React.Component {
static get propTypes() {
return {
children: React.PropTypes.node.isRequired
};
}
constructor(props) {
super(props);
this.handleConfigChange = this.handleConfigChange.bind(this);
this.state = {
config: AdminStore.getConfig()
};
}
componentWillMount() {
AdminStore.addConfigChangeListener(this.handleConfigChange);
AsyncClient.getConfig();
}
componentWillUnmount() {
AdminStore.removeConfigChangeListener(this.handleConfigChange);
}
handleConfigChange() {
this.setState({
config: AdminStore.getConfig()
});
}
render() {
if ($.isEmptyObject(this.state.config)) {
return <div className='admin-console'/>;
}
// not every page in the system console will need the config, but the vast majority will
const children = React.cloneElement(this.props.children, {
config: this.state.config
});
return (
<div className='admin-console'>
<AdminSidebar/>
{children}
</div>
);
}
}

Просмотреть файл

@@ -1,221 +0,0 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import $ from 'jquery';
import AdminSidebar from './admin_sidebar.jsx';
import AdminStore from 'stores/admin_store.jsx';
import TeamStore from 'stores/team_store.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import LoadingScreen from '../loading_screen.jsx';
import EmailSettingsTab from './email_settings.jsx';
import LogSettingsTab from './log_settings.jsx';
import LogsTab from './logs.jsx';
import AuditsTab from './audits.jsx';
import FileSettingsTab from './image_settings.jsx';
import PrivacySettingsTab from './privacy_settings.jsx';
import RateSettingsTab from './rate_settings.jsx';
import GitLabSettingsTab from './gitlab_settings.jsx';
import SqlSettingsTab from './sql_settings.jsx';
import TeamSettingsTab from './team_settings.jsx';
import ServiceSettingsTab from './service_settings.jsx';
import LegalAndSupportSettingsTab from './legal_and_support_settings.jsx';
import TeamUsersTab from './team_users.jsx';
import TeamAnalyticsTab from '../analytics/team_analytics.jsx';
import LdapSettingsTab from './ldap_settings.jsx';
import ComplianceSettingsTab from './compliance_settings.jsx';
import LicenseSettingsTab from './license_settings.jsx';
import SystemAnalyticsTab from '../analytics/system_analytics.jsx';
import React from 'react';
export default class AdminController extends React.Component {
constructor(props) {
super(props);
this.selectTab = this.selectTab.bind(this);
this.removeSelectedTeam = this.removeSelectedTeam.bind(this);
this.addSelectedTeam = this.addSelectedTeam.bind(this);
this.onConfigListenerChange = this.onConfigListenerChange.bind(this);
this.onAllTeamsListenerChange = this.onAllTeamsListenerChange.bind(this);
var selectedTeams = AdminStore.getSelectedTeams();
if (selectedTeams == null) {
selectedTeams = {};
selectedTeams[TeamStore.getCurrentId()] = 'true';
AdminStore.saveSelectedTeams(selectedTeams);
}
this.state = {
config: AdminStore.getConfig(),
teams: AdminStore.getAllTeams(),
selectedTeams,
selected: props.tab || 'system_analytics',
selectedTeam: props.teamId || null
};
}
componentDidMount() {
AdminStore.addConfigChangeListener(this.onConfigListenerChange);
AsyncClient.getConfig();
AdminStore.addAllTeamsChangeListener(this.onAllTeamsListenerChange);
AsyncClient.getAllTeams();
$('[data-toggle="tooltip"]').tooltip();
$('[data-toggle="popover"]').popover();
}
componentWillUnmount() {
AdminStore.removeConfigChangeListener(this.onConfigListenerChange);
AdminStore.removeAllTeamsChangeListener(this.onAllTeamsListenerChange);
}
onConfigListenerChange() {
this.setState({
config: AdminStore.getConfig(),
teams: AdminStore.getAllTeams(),
selectedTeams: AdminStore.getSelectedTeams(),
selected: this.state.selected,
selectedTeam: this.state.selectedTeam
});
}
onAllTeamsListenerChange() {
this.setState({
config: AdminStore.getConfig(),
teams: AdminStore.getAllTeams(),
selectedTeams: AdminStore.getSelectedTeams(),
selected: this.state.selected,
selectedTeam: this.state.selectedTeam
});
}
selectTab(tab, teamId) {
this.setState({
config: AdminStore.getConfig(),
teams: AdminStore.getAllTeams(),
selectedTeams: AdminStore.getSelectedTeams(),
selected: tab,
selectedTeam: teamId
});
}
removeSelectedTeam(teamId) {
var selectedTeams = AdminStore.getSelectedTeams();
Reflect.deleteProperty(selectedTeams, teamId);
AdminStore.saveSelectedTeams(selectedTeams);
this.setState({
config: AdminStore.getConfig(),
teams: AdminStore.getAllTeams(),
selectedTeams: AdminStore.getSelectedTeams(),
selected: this.state.selected,
selectedTeam: this.state.selectedTeam
});
}
addSelectedTeam(teamId) {
var selectedTeams = AdminStore.getSelectedTeams();
selectedTeams[teamId] = 'true';
AdminStore.saveSelectedTeams(selectedTeams);
this.setState({
config: AdminStore.getConfig(),
teams: AdminStore.getAllTeams(),
selectedTeams: AdminStore.getSelectedTeams(),
selected: this.state.selected,
selectedTeam: this.state.selectedTeam
});
}
render() {
var tab = <LoadingScreen/>;
if (this.state.config != null) {
if (this.state.selected === 'email_settings') {
tab = <EmailSettingsTab config={this.state.config}/>;
} else if (this.state.selected === 'log_settings') {
tab = <LogSettingsTab config={this.state.config}/>;
} else if (this.state.selected === 'logs') {
tab = <LogsTab/>;
} else if (this.state.selected === 'audits') {
tab = <AuditsTab/>;
} else if (this.state.selected === 'image_settings') {
tab = <FileSettingsTab config={this.state.config}/>;
} else if (this.state.selected === 'privacy_settings') {
tab = <PrivacySettingsTab config={this.state.config}/>;
} else if (this.state.selected === 'rate_settings') {
tab = <RateSettingsTab config={this.state.config}/>;
} else if (this.state.selected === 'gitlab_settings') {
tab = <GitLabSettingsTab config={this.state.config}/>;
} else if (this.state.selected === 'sql_settings') {
tab = <SqlSettingsTab config={this.state.config}/>;
} else if (this.state.selected === 'team_settings') {
tab = <TeamSettingsTab config={this.state.config}/>;
} else if (this.state.selected === 'service_settings') {
tab = <ServiceSettingsTab config={this.state.config}/>;
} else if (this.state.selected === 'legal_and_support_settings') {
tab = <LegalAndSupportSettingsTab config={this.state.config}/>;
} else if (this.state.selected === 'ldap_settings') {
tab = <LdapSettingsTab config={this.state.config}/>;
} else if (this.state.selected === 'compliance_settings') {
tab = <ComplianceSettingsTab config={this.state.config}/>;
} else if (this.state.selected === 'license') {
tab = <LicenseSettingsTab config={this.state.config}/>;
} else if (this.state.selected === 'team_users') {
if (this.state.teams) {
tab = <TeamUsersTab team={this.state.teams[this.state.selectedTeam]}/>;
}
} else if (this.state.selected === 'team_analytics') {
if (this.state.teams) {
tab = <TeamAnalyticsTab team={this.state.teams[this.state.selectedTeam]}/>;
}
} else if (this.state.selected === 'system_analytics') {
tab = <SystemAnalyticsTab/>;
}
}
return (
<div
id='admin_controller'
className='admin-controller'
>
<div
className='sidebar--menu'
id='sidebar-menu'
/>
<AdminSidebar
selected={this.state.selected}
selectedTeam={this.state.selectedTeam}
selectTab={this.selectTab}
teams={this.state.teams}
selectedTeams={this.state.selectedTeams}
removeSelectedTeam={this.removeSelectedTeam}
addSelectedTeam={this.addSelectedTeam}
/>
<div className='inner-wrap channel__wrap'>
<div className='row header'>
</div>
<div className='row main'>
<div
id='app-content'
className='app__content admin'
>
{tab}
</div>
</div>
</div>
</div>
);
}
}
AdminController.defaultProps = {
};
AdminController.propTypes = {
tab: React.PropTypes.string,
teamId: React.PropTypes.string
};

Просмотреть файл

@@ -0,0 +1,115 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import * as AsyncClient from 'utils/async_client.jsx';
import Client from 'utils/web_client.jsx';
import FormError from 'components/form_error.jsx';
import SaveButton from 'components/admin_console/save_button.jsx';
export default class AdminSettings extends React.Component {
static get propTypes() {
return {
config: React.PropTypes.object
};
}
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.state = {
saveNeeded: false,
saving: false,
serverError: null
};
}
handleChange(id, value) {
this.setState({
saveNeeded: true,
[id]: value
});
}
handleSubmit(e) {
e.preventDefault();
this.setState({
saving: true,
serverError: null
});
const config = this.getConfigFromState(this.props.config);
Client.saveConfig(
config,
() => {
AsyncClient.getConfig();
this.setState({
saveNeeded: false,
saving: false
});
},
(err) => {
this.setState({
saving: false,
serverError: err.message
});
}
);
}
parseInt(str) {
const n = parseInt(str, 10);
if (isNaN(n)) {
return 0;
}
return n;
}
parseIntNonZero(str) {
const n = parseInt(str, 10);
if (isNaN(n) || n < 1) {
return 1;
}
return n;
}
render() {
let saveClass = 'btn';
if (this.state.saveNeeded) {
saveClass += 'btn-primary';
}
return (
<div className='wrapper--fixed'>
{this.renderTitle()}
<form
className='form-horizontal'
role='form'
>
{this.renderSettings()}
<div className='form-group'>
<div className='col-sm-12'>
<FormError error={this.state.serverError}/>
<SaveButton
saving={this.state.saving}
disabled={!this.state.saveNeeded || (this.canSave && !this.canSave())}
onClick={this.handleSubmit}
/>
</div>
</div>
</form>
</div>
);
}
}

Просмотреть файл

@@ -2,69 +2,80 @@
// See License.txt for license information. // See License.txt for license information.
import $ from 'jquery'; import $ from 'jquery';
import AdminSidebarHeader from './admin_sidebar_header.jsx';
import SelectTeamModal from './select_team_modal.jsx';
import * as Utils from 'utils/utils.jsx';
import {FormattedMessage} from 'react-intl';
import {Tooltip, OverlayTrigger} from 'react-bootstrap';
import React from 'react'; import React from 'react';
import AdminStore from 'stores/admin_store.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import * as Utils from 'utils/utils.jsx';
import AdminSidebarHeader from './admin_sidebar_header.jsx';
import AdminSidebarTeam from './admin_sidebar_team.jsx';
import {FormattedMessage} from 'react-intl';
import {browserHistory} from 'react-router';
import {OverlayTrigger, Tooltip} from 'react-bootstrap';
import SelectTeamModal from './select_team_modal.jsx';
import AdminSidebarCategory from './admin_sidebar_category.jsx';
import AdminSidebarSection from './admin_sidebar_section.jsx';
export default class AdminSidebar extends React.Component { export default class AdminSidebar extends React.Component {
static get contextTypes() {
return {
router: React.PropTypes.object.isRequired
};
}
constructor(props) { constructor(props) {
super(props); super(props);
this.isSelected = this.isSelected.bind(this); this.handleAllTeamsChange = this.handleAllTeamsChange.bind(this);
this.handleClick = this.handleClick.bind(this);
this.removeTeam = this.removeTeam.bind(this); this.removeTeam = this.removeTeam.bind(this);
this.showTeamSelect = this.showTeamSelect.bind(this); this.showTeamSelect = this.showTeamSelect.bind(this);
this.teamSelectedModal = this.teamSelectedModal.bind(this); this.teamSelectedModal = this.teamSelectedModal.bind(this);
this.teamSelectedModalDismissed = this.teamSelectedModalDismissed.bind(this); this.teamSelectedModalDismissed = this.teamSelectedModalDismissed.bind(this);
this.renderAddTeamButton = this.renderAddTeamButton.bind(this);
this.renderTeams = this.renderTeams.bind(this);
this.state = { this.state = {
teams: AdminStore.getAllTeams(),
selectedTeams: AdminStore.getSelectedTeams(),
showSelectModal: false showSelectModal: false
}; };
} }
componentDidMount() {
AdminStore.addAllTeamsChangeListener(this.handleAllTeamsChange);
AsyncClient.getAllTeams();
}
componentDidUpdate() { componentDidUpdate() {
if (!Utils.isMobile()) { if (!Utils.isMobile()) {
$('.sidebar--left .nav-pills__container').perfectScrollbar(); $('.admin-sidebar .nav-pills__container').perfectScrollbar();
} }
} }
handleClick(name, teamId, e) { componentWillUnmount() {
e.preventDefault(); AdminStore.removeAllTeamsChangeListener(this.handleAllTeamsChange);
this.props.selectTab(name, teamId);
} }
isSelected(name, teamId) { handleAllTeamsChange() {
if (this.props.selected === name) { this.setState({
if (name === 'team_users' || name === 'team_analytics') { teams: AdminStore.getAllTeams(),
if (this.props.selectedTeam != null && this.props.selectedTeam === teamId) { selectedTeams: AdminStore.getSelectedTeams()
return 'active'; });
}
} else {
return 'active';
}
}
return '';
} }
removeTeam(teamId, e) { removeTeam(team) {
e.preventDefault(); const selectedTeams = Object.assign({}, this.state.selectedTeams);
e.stopPropagation(); Reflect.deleteProperty(selectedTeams, team.id);
Reflect.deleteProperty(this.props.selectedTeams, teamId); AdminStore.saveSelectedTeams(selectedTeams);
this.props.removeSelectedTeam(teamId);
if (this.props.selected === 'team_users') { this.handleAllTeamsChange();
if (this.props.selectedTeam != null && this.props.selectedTeam === teamId) {
this.props.selectTab('service_settings', null); if (this.context.router.isActive('/admin_console/team/' + team.id)) {
} browserHistory.push('/admin_console');
} }
} }
@@ -74,31 +85,23 @@ export default class AdminSidebar extends React.Component {
} }
teamSelectedModal(teamId) { teamSelectedModal(teamId) {
this.setState({showSelectModal: false}); this.setState({
this.props.addSelectedTeam(teamId); showSelectModal: false
this.forceUpdate(); });
const selectedTeams = Object.assign({}, this.state.selectedTeams);
selectedTeams[teamId] = true;
AdminStore.saveSelectedTeams(selectedTeams);
this.handleAllTeamsChange();
} }
teamSelectedModalDismissed() { teamSelectedModalDismissed() {
this.setState({showSelectModal: false}); this.setState({showSelectModal: false});
} }
render() { renderAddTeamButton() {
var count = '*';
var teams = (
<FormattedMessage
id='admin.sidebar.loading'
defaultMessage='Loading'
/>
);
const removeTooltip = (
<Tooltip id='remove-team-tooltip'>
<FormattedMessage
id='admin.sidebar.rmTeamSidebar'
defaultMessage='Remove team from sidebar menu'
/>
</Tooltip>
);
const addTeamTooltip = ( const addTeamTooltip = (
<Tooltip id='add-team-tooltip'> <Tooltip id='add-team-tooltip'>
<FormattedMessage <FormattedMessage
@@ -108,393 +111,468 @@ export default class AdminSidebar extends React.Component {
</Tooltip> </Tooltip>
); );
if (this.props.teams != null) { return (
count = '' + Object.keys(this.props.teams).length; <span className='menu-icon--right'>
<OverlayTrigger
delayShow={1000}
placement='top'
overlay={addTeamTooltip}
>
<a
href='#'
onClick={this.showTeamSelect}
>
<i
className='fa fa-plus'
></i>
</a>
</OverlayTrigger>
</span>
);
}
teams = []; renderTeams() {
for (var key in this.props.selectedTeams) { const teams = [];
if (this.props.selectedTeams.hasOwnProperty(key)) {
var team = this.props.teams[key];
if (team != null) { for (const key in this.state.selectedTeams) {
teams.push( if (!this.state.selectedTeams.hasOwnProperty(key)) {
<ul continue;
key={'team_' + team.id}
className='nav nav__sub-menu'
>
<li>
<a
href='#'
onClick={this.handleClick.bind(this, 'team_users', team.id)}
className={'nav__sub-menu-item ' + this.isSelected('team_users', team.id) + ' ' + this.isSelected('team_analytics', team.id)}
>
{team.name}
<OverlayTrigger
delayShow={1000}
placement='top'
overlay={removeTooltip}
>
<span
className='menu-icon--right menu__close'
onClick={this.removeTeam.bind(this, team.id)}
style={{cursor: 'pointer'}}
>
{'×'}
</span>
</OverlayTrigger>
</a>
</li>
<li>
<ul className='nav nav__inner-menu'>
<li>
<a
href='#'
className={this.isSelected('team_users', team.id)}
onClick={this.handleClick.bind(this, 'team_users', team.id)}
>
<FormattedMessage
id='admin.sidebar.users'
defaultMessage='- Users'
/>
</a>
</li>
<li>
<a
href='#'
className={this.isSelected('team_analytics', team.id)}
onClick={this.handleClick.bind(this, 'team_analytics', team.id)}
>
<FormattedMessage
id='admin.sidebar.statistics'
defaultMessage='- Statistics'
/>
</a>
</li>
</ul>
</li>
</ul>
);
}
}
} }
const team = this.state.teams[key];
if (!team) {
continue;
}
teams.push(
<AdminSidebarTeam
key={team.id}
team={team}
onRemoveTeam={this.removeTeam}
/>
);
} }
let ldapSettings; return (
let complianceSettings; <AdminSidebarCategory
let licenseSettings; parentLink='/admin_console'
if (global.window.mm_config.BuildEnterpriseReady === 'true') { icon='fa-gear'
if (global.window.mm_license.IsLicensed === 'true') { title={
<FormattedMessage
id='admin.sidebar.teams'
defaultMessage='TEAMS ({count, number})'
values={{
count: Object.keys(this.state.teams).length
}}
/>
}
action={this.renderAddTeamButton()}
>
{teams}
</AdminSidebarCategory>
);
}
render() {
let ldapSettings = null;
let complianceSettings = null;
let license = null;
let audits = null;
if (window.mm_config.BuildEnterpriseReady === 'true') {
if (window.mm_license.IsLicensed === 'true') {
if (global.window.mm_license.LDAP === 'true') { if (global.window.mm_license.LDAP === 'true') {
ldapSettings = ( ldapSettings = (
<li> <AdminSidebarSection
<a name='ldap'
href='#' title={
className={this.isSelected('ldap_settings')}
onClick={this.handleClick.bind(this, 'ldap_settings', null)}
>
<FormattedMessage <FormattedMessage
id='admin.sidebar.ldap' id='admin.sidebar.ldap'
defaultMessage='LDAP Settings' defaultMessage='LDAP'
/> />
</a> }
</li> />
); );
} }
if (global.window.mm_license.Compliance === 'true') { if (global.window.mm_license.Compliance === 'true') {
complianceSettings = ( complianceSettings = (
<li> <AdminSidebarSection
<a name='compliance'
href='#' title={
className={this.isSelected('compliance_settings')}
onClick={this.handleClick.bind(this, 'compliance_settings', null)}
>
<FormattedMessage <FormattedMessage
id='admin.sidebar.compliance' id='admin.sidebar.compliance'
defaultMessage='Compliance Settings' defaultMessage='Compliance'
/> />
</a> }
</li> />
); );
} }
} }
licenseSettings = ( license = (
<li> <AdminSidebarSection
<a name='license'
href='#' title={
className={this.isSelected('license')}
onClick={this.handleClick.bind(this, 'license', null)}
>
<FormattedMessage <FormattedMessage
id='admin.sidebar.license' id='admin.sidebar.license'
defaultMessage='Edition and License' defaultMessage='Edition and License'
/> />
</a> }
</li> />
); );
} }
let audits; if (window.mm_license.IsLicensed === 'true') {
if (global.window.mm_license.IsLicensed === 'true') {
audits = ( audits = (
<li> <AdminSidebarSection
<a name='audits'
href='#' title={
className={this.isSelected('audits')}
onClick={this.handleClick.bind(this, 'audits', null)}
>
<FormattedMessage <FormattedMessage
id='admin.sidebar.audits' id='admin.sidebar.audits'
defaultMessage='Compliance and Auditing' defaultMessage='Complaince and Auditing'
/> />
</a> }
</li> />
); );
} }
return ( return (
<div className='sidebar--left sidebar--collapsable'> <div className='admin-sidebar'>
<AdminSidebarHeader/> <AdminSidebarHeader/>
<div className='nav-pills__container'> <div className='nav-pills__container'>
<ul className='nav nav-pills nav-stacked'> <ul className='nav nav-pills nav-stacked'>
<li> <AdminSidebarCategory
<ul className='nav nav__sub-menu'> parentLink='/admin_console'
<li> icon='fa-gear'
<h4> title={
<span className='icon fa fa-gear'></span> <FormattedMessage
<span> id='admin.sidebar.reports'
<FormattedMessage defaultMessage='SITE REPORTS'
id='admin.sidebar.reports' />
defaultMessage='SITE REPORTS' }
/> >
</span> <AdminSidebarSection
</h4> name='system_analytics'
</li> title={
</ul> <FormattedMessage
<ul className='nav nav__sub-menu padded'> id='admin.sidebar.view_statistics'
<li> defaultMessage='View Statistics'
<a />
href='#' }
className={this.isSelected('system_analytics')} />
onClick={this.handleClick.bind(this, 'system_analytics', null)} </AdminSidebarCategory>
> <AdminSidebarCategory
parentLink='/admin_console'
icon='fa-gear'
title={
<FormattedMessage
id='admin.sidebar.settings'
defaultMessage='SETTINGS'
/>
}
>
<AdminSidebarSection
name='general'
title={
<FormattedMessage
id='admin.sidebar.general'
defaultMessage='General'
/>
}
>
<AdminSidebarSection
name='configuration'
title={
<FormattedMessage <FormattedMessage
id='admin.sidebar.view_statistics' id='admin.sidebar.configuration'
defaultMessage='View Statistics' defaultMessage='Configuration'
/> />
</a> }
</li> />
</ul> <AdminSidebarSection
<ul className='nav nav__sub-menu'> name='users_and_teams'
<li> title={
<h4>
<span className='icon fa fa-gear'></span>
<span>
<FormattedMessage
id='admin.sidebar.settings'
defaultMessage='SETTINGS'
/>
</span>
</h4>
</li>
</ul>
<ul className='nav nav__sub-menu padded'>
<li>
<a
href='#'
className={this.isSelected('service_settings')}
onClick={this.handleClick.bind(this, 'service_settings', null)}
>
<FormattedMessage <FormattedMessage
id='admin.sidebar.service' id='admin.sidebar.usersAndTeams'
defaultMessage='Service Settings' defaultMessage='Users and Teams'
/> />
</a> }
</li> />
<li> <AdminSidebarSection
<a name='privacy'
href='#' title={
className={this.isSelected('team_settings')}
onClick={this.handleClick.bind(this, 'team_settings', null)}
>
<FormattedMessage
id='admin.sidebar.team'
defaultMessage='Team Settings'
/>
</a>
</li>
<li>
<a
href='#'
className={this.isSelected('sql_settings')}
onClick={this.handleClick.bind(this, 'sql_settings', null)}
>
<FormattedMessage
id='admin.sidebar.sql'
defaultMessage='SQL Settings'
/>
</a>
</li>
<li>
<a
href='#'
className={this.isSelected('email_settings')}
onClick={this.handleClick.bind(this, 'email_settings', null)}
>
<FormattedMessage
id='admin.sidebar.email'
defaultMessage='Email Settings'
/>
</a>
</li>
<li>
<a
href='#'
className={this.isSelected('image_settings')}
onClick={this.handleClick.bind(this, 'image_settings', null)}
>
<FormattedMessage
id='admin.sidebar.file'
defaultMessage='File Settings'
/>
</a>
</li>
<li>
<a
href='#'
className={this.isSelected('log_settings')}
onClick={this.handleClick.bind(this, 'log_settings', null)}
>
<FormattedMessage
id='admin.sidebar.log'
defaultMessage='Log Settings'
/>
</a>
</li>
<li>
<a
href='#'
className={this.isSelected('rate_settings')}
onClick={this.handleClick.bind(this, 'rate_settings', null)}
>
<FormattedMessage
id='admin.sidebar.rate_limit'
defaultMessage='Rate Limit Settings'
/>
</a>
</li>
<li>
<a
href='#'
className={this.isSelected('privacy_settings')}
onClick={this.handleClick.bind(this, 'privacy_settings', null)}
>
<FormattedMessage <FormattedMessage
id='admin.sidebar.privacy' id='admin.sidebar.privacy'
defaultMessage='Privacy Settings' defaultMessage='Privacy'
/> />
</a> }
</li> />
<li> <AdminSidebarSection
<a name='logging'
href='#' title={
className={this.isSelected('gitlab_settings')} <FormattedMessage
onClick={this.handleClick.bind(this, 'gitlab_settings', null)} id='admin.sidebar.logging'
> defaultMessage='Logging'
/>
}
/>
</AdminSidebarSection>
<AdminSidebarSection
name='authentication'
title={
<FormattedMessage
id='admin.sidebar.authentication'
defaultMessage='Authentication'
/>
}
>
<AdminSidebarSection
name='email'
title={
<FormattedMessage
id='admin.sidebar.email'
defaultMessage='Email'
/>
}
/>
<AdminSidebarSection
name='gitlab'
title={
<FormattedMessage <FormattedMessage
id='admin.sidebar.gitlab' id='admin.sidebar.gitlab'
defaultMessage='GitLab Settings' defaultMessage='GitLab'
/> />
</a> }
</li> />
{ldapSettings} {ldapSettings}
{complianceSettings} </AdminSidebarSection>
<li> <AdminSidebarSection
<a name='security'
href='#' title={
className={this.isSelected('legal_and_support_settings')} <FormattedMessage
onClick={this.handleClick.bind(this, 'legal_and_support_settings', null)} id='admin.sidebar.security'
> defaultMessage='Security'
/>
}
>
<AdminSidebarSection
name='sign_up'
title={
<FormattedMessage <FormattedMessage
id='admin.sidebar.support' id='admin.sidebar.signUp'
defaultMessage='Legal and Support Settings' defaultMessage='Sign Up'
/> />
</a> }
</li> />
</ul> <AdminSidebarSection
<ul className='nav nav__sub-menu'> name='login'
<li> title={
<h4>
<span className='icon fa fa-gear'></span>
<span>
<FormattedMessage
id='admin.sidebar.teams'
defaultMessage='TEAMS ({count})'
values={{
count: count
}}
/>
</span>
<span className='menu-icon--right'>
<OverlayTrigger
delayShow={1000}
placement='top'
overlay={addTeamTooltip}
>
<a
href='#'
onClick={this.showTeamSelect}
>
<i
className='fa fa-plus'
></i>
</a>
</OverlayTrigger>
</span>
</h4>
</li>
</ul>
<ul className='nav nav__sub-menu padded'>
<li>
{teams}
</li>
</ul>
<ul className='nav nav__sub-menu'>
<li>
<h4>
<span className='icon fa fa-gear'></span>
<span>
<FormattedMessage
id='admin.sidebar.other'
defaultMessage='OTHER'
/>
</span>
</h4>
</li>
</ul>
<ul className='nav nav__sub-menu padded'>
{licenseSettings}
{audits}
<li>
<a
href='#'
className={this.isSelected('logs')}
onClick={this.handleClick.bind(this, 'logs', null)}
>
<FormattedMessage <FormattedMessage
id='admin.sidebar.logs' id='admin.sidebar.login'
defaultMessage='Logs' defaultMessage='Login'
/> />
</a> }
</li> />
</ul> <AdminSidebarSection
</li> name='public_links'
title={
<FormattedMessage
id='admin.sidebar.publicLinks'
defaultMessage='Public Links'
/>
}
/>
<AdminSidebarSection
name='sessions'
title={
<FormattedMessage
id='admin.sidebar.sessions'
defaultMessage='Sessions'
/>
}
/>
<AdminSidebarSection
name='connections'
title={
<FormattedMessage
id='admin.sidebar.connections'
defaultMessage='Connections'
/>
}
/>
</AdminSidebarSection>
<AdminSidebarSection
name='notifications'
title={
<FormattedMessage
id='admin.sidebar.notifications'
defaultMessage='Notifications'
/>
}
>
<AdminSidebarSection
name='email'
title={
<FormattedMessage
id='admin.sidebar.email'
defaultMessage='Email'
/>
}
/>
<AdminSidebarSection
name='push'
title={
<FormattedMessage
id='admin.sidebar.push'
defaultMessage='Mobile Push'
/>
}
/>
</AdminSidebarSection>
<AdminSidebarSection
name='integrations'
title={
<FormattedMessage
id='admin.sidebar.integrations'
defaultMessage='Integrations'
/>
}
>
<AdminSidebarSection
name='webhooks'
title={
<FormattedMessage
id='admin.sidebar.webhooks'
defaultMessage='Webhooks and Commands'
/>
}
/>
<AdminSidebarSection
name='external'
title={
<FormattedMessage
id='admin.sidebar.external'
defaultMessage='External Services'
/>
}
/>
</AdminSidebarSection>
<AdminSidebarSection
name='database'
title={
<FormattedMessage
id='admin.sidebar.database'
defaultMessage='Database'
/>
}
/>
<AdminSidebarSection
name='files'
title={
<FormattedMessage
id='admin.sidebar.files'
defaultMessage='Files'
/>
}
>
<AdminSidebarSection
name='storage'
title={
<FormattedMessage
id='admin.sidebar.storage'
defaultMessage='Storage'
/>
}
/>
<AdminSidebarSection
name='images'
title={
<FormattedMessage
id='admin.sidebar.images'
defaultMessage='Images'
/>
}
/>
</AdminSidebarSection>
<AdminSidebarSection
name='customization'
title={
<FormattedMessage
id='admin.sidebar.customization'
defaultMessage='Customization'
/>
}
>
<AdminSidebarSection
name='custom_brand'
title={
<FormattedMessage
id='admin.sidebar.customBrand'
defaultMessage='Custom Branding'
/>
}
/>
<AdminSidebarSection
name='legal_and_support'
title={
<FormattedMessage
id='admin.sidebar.legalAndSupport'
defaultMessage='Legal and Support'
/>
}
/>
</AdminSidebarSection>
{complianceSettings}
<AdminSidebarSection
name='rate'
title={
<FormattedMessage
id='admin.sidebar.rate'
defaultMessage='Rate Limiting'
/>
}
/>
<AdminSidebarSection
name='developer'
title={
<FormattedMessage
id='admin.sidebar.developer'
defaultMessage='Developer'
/>
}
/>
</AdminSidebarCategory>
{this.renderTeams()}
<AdminSidebarCategory
parentLink='/admin_console'
icon='fa-gear'
title={
<FormattedMessage
id='admin.sidebar.other'
defaultMessage='OTHER'
/>
}
>
{license}
{audits}
<AdminSidebarSection
name='logs'
title={
<FormattedMessage
id='admin.sidebar.logs'
defaultMessage='Logs'
/>
}
/>
</AdminSidebarCategory>
</ul> </ul>
</div> </div>
<SelectTeamModal <SelectTeamModal
teams={this.props.teams} teams={this.state.teams}
show={this.state.showSelectModal} show={this.state.showSelectModal}
onModalSubmit={this.teamSelectedModal} onModalSubmit={this.teamSelectedModal}
onModalDismissed={this.teamSelectedModalDismissed} onModalDismissed={this.teamSelectedModalDismissed}
@@ -503,13 +581,3 @@ export default class AdminSidebar extends React.Component {
); );
} }
} }
AdminSidebar.propTypes = {
teams: React.PropTypes.object,
selectedTeams: React.PropTypes.object,
removeSelectedTeam: React.PropTypes.func,
addSelectedTeam: React.PropTypes.func,
selected: React.PropTypes.string,
selectedTeam: React.PropTypes.string,
selectTab: React.PropTypes.func
};

Просмотреть файл

@@ -0,0 +1,83 @@
// 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 AdminSidebarCategory extends React.Component {
static get propTypes() {
return {
name: React.PropTypes.string,
title: React.PropTypes.node.isRequired,
icon: React.PropTypes.string.isRequired,
parentLink: React.PropTypes.string,
children: React.PropTypes.node,
action: React.PropTypes.node
};
}
static get defaultProps() {
return {
parentLink: ''
};
}
static get contextTypes() {
return {
router: React.PropTypes.object.isRequired
};
}
render() {
let link = this.props.parentLink;
let title = (
<div className='category-title category-title--active'>
<i className={'category-icon fa ' + this.props.icon}/>
<span className='category-title__text'>
{this.props.title}
</span>
{this.props.action}
</div>
);
if (this.props.name) {
link += '/' + name;
title = (
<Link
to={link}
className='category-title'
activeClassName='category-title category-title--active'
>
{title}
</Link>
);
}
let clonedChildren = null;
if (this.props.children && this.context.router.isActive(link)) {
clonedChildren = (
<ul className='sections'>
{
React.Children.map(this.props.children, (child) => {
if (child === null) {
return null;
}
return React.cloneElement(child, {
parentLink: link
});
})
}
</ul>
);
}
return (
<li className='sidebar-category'>
{title}
{clonedChildren}
</li>
);
}
}

Просмотреть файл

@@ -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 AdminSidebarSection 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),
action: React.PropTypes.node,
onlyActiveOnIndex: React.PropTypes.bool
};
}
static get defaultProps() {
return {
parentLink: '',
subsection: false,
children: [],
onlyActiveOnIndex: true
};
}
getLink() {
return this.props.parentLink + '/' + this.props.name;
}
render() {
const link = this.getLink();
let clonedChildren = null;
if (this.props.children.length > 0) {
clonedChildren = (
<ul className='nav nav__sub-menu subsections'>
{
React.Children.map(this.props.children, (child) => {
if (child === null) {
return null;
}
return React.cloneElement(child, {
parentLink: link,
subsection: true
});
})
}
</ul>
);
}
let className = 'sidebar-section';
if (this.props.subsection) {
className += ' sidebar-subsection';
}
return (
<li className={className}>
<Link
className={`${className}-title`}
activeClassName={`${className}-title ${className}-title--active`}
onlyActiveOnIndex={this.props.onlyActiveOnIndex}
onClick={this.handleClick}
to={link}
>
<span className={`${className}-title__text`}>
{this.props.title}
</span>
{this.props.action}
</Link>
{clonedChildren}
</li>
);
}
}

Просмотреть файл

@@ -0,0 +1,87 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import {FormattedMessage} from 'react-intl';
import {OverlayTrigger, Tooltip} from 'react-bootstrap';
import AdminSidebarSection from './admin_sidebar_section.jsx';
export default class AdminSidebarTeam extends React.Component {
static get propTypes() {
return {
team: React.PropTypes.object.isRequired,
onRemoveTeam: React.PropTypes.func.isRequired,
parentLink: React.PropTypes.string
};
}
constructor(props) {
super(props);
this.handleRemoveTeam = this.handleRemoveTeam.bind(this);
}
handleRemoveTeam(e) {
e.preventDefault();
this.props.onRemoveTeam(this.props.team);
}
render() {
const team = this.props.team;
const removeTeamTooltip = (
<Tooltip id='remove-team-tooltip'>
<FormattedMessage
id='admin.sidebar.rmTeamSidebar'
defaultMessage='Remove team from sidebar menu'
/>
</Tooltip>
);
const removeTeamButton = (
<OverlayTrigger
delayShow={1000}
placement='top'
overlay={removeTeamTooltip}
>
<span
className='menu-icon--right menu__close'
onClick={this.handleRemoveTeam}
>
{'×'}
</span>
</OverlayTrigger>
);
return (
<AdminSidebarSection
key={team.id}
name={'team/' + team.id}
parentLink={this.props.parentLink}
title={team.display_name}
action={removeTeamButton}
>
<AdminSidebarSection
name='users'
title={
<FormattedMessage
id='admin.sidebar.users'
defaultMessage='- Users'
/>
}
/>
<AdminSidebarSection
name='analytics'
title={
<FormattedMessage
id='admin.sidebar.statistics'
defaultMessage='- Statistics'
/>
}
/>
</AdminSidebarSection>
);
}
}

Просмотреть файл

@@ -8,16 +8,43 @@ import Setting from './setting.jsx';
import {FormattedMessage} from 'react-intl'; import {FormattedMessage} from 'react-intl';
export default class BooleanSetting extends React.Component { export default class BooleanSetting extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.props.onChange(this.props.id, e.target.value === 'true');
}
render() { render() {
let helpText;
if (this.props.disabled && this.props.disabledText) {
helpText = (
<div>
<span className='admin-console__disabled-text'>
{this.props.disabledText}
</span>
{this.props.helpText}
</div>
);
} else {
helpText = this.props.helpText;
}
return ( return (
<Setting label={this.props.label}> <Setting
label={this.props.label}
helpText={helpText}
>
<label className='radio-inline'> <label className='radio-inline'>
<input <input
type='radio' type='radio'
value='true' value='true'
checked={this.props.currentValue} checked={this.props.value}
onChange={this.props.handleChange} onChange={this.handleChange}
disabled={this.props.isDisabled} disabled={this.props.disabled}
/> />
{this.props.trueText} {this.props.trueText}
</label> </label>
@@ -25,13 +52,12 @@ export default class BooleanSetting extends React.Component {
<input <input
type='radio' type='radio'
value='false' value='false'
checked={!this.props.currentValue} checked={!this.props.value}
onChange={this.props.handleChange} onChange={this.handleChange}
disabled={this.props.isDisabled} disabled={this.props.disabled}
/> />
{this.props.falseText} {this.props.falseText}
</label> </label>
{this.props.helpText}
</Setting> </Setting>
); );
} }
@@ -48,15 +74,18 @@ BooleanSetting.defaultProps = {
id='admin.ldap.false' id='admin.ldap.false'
defaultMessage='false' defaultMessage='false'
/> />
) ),
disabled: false
}; };
BooleanSetting.propTypes = { BooleanSetting.propTypes = {
id: React.PropTypes.string.isRequired,
label: React.PropTypes.node.isRequired, label: React.PropTypes.node.isRequired,
currentValue: React.PropTypes.bool.isRequired, value: React.PropTypes.bool.isRequired,
onChange: React.PropTypes.func.isRequired,
trueText: React.PropTypes.node, trueText: React.PropTypes.node,
falseText: React.PropTypes.node, falseText: React.PropTypes.node,
isDisabled: React.PropTypes.bool.isRequired, disabled: React.PropTypes.bool.isRequired,
handleChange: React.PropTypes.func.isRequired, disabledText: React.PropTypes.node,
helpText: React.PropTypes.node.isRequired helpText: React.PropTypes.node.isRequired
}; };

Просмотреть файл

@@ -0,0 +1,182 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import $ from 'jquery';
import React from 'react';
import ReactDOM from 'react-dom';
import Client from 'utils/web_client.jsx';
import * as Utils from 'utils/utils.jsx';
import FormError from 'components/form_error.jsx';
import {FormattedHTMLMessage, FormattedMessage} from 'react-intl';
export default class BrandImageSetting extends React.Component {
static get propTypes() {
return {
disabled: React.PropTypes.bool.isRequired
};
}
constructor(props) {
super(props);
this.handleImageChange = this.handleImageChange.bind(this);
this.handleImageSubmit = this.handleImageSubmit.bind(this);
this.state = {
brandImage: null,
brandImageExists: false,
brandImageTimestamp: Date.now(),
uploading: false,
uploadCompleted: false,
error: ''
};
}
componentWillMount() {
$.get(Client.getAdminRoute() + '/get_brand_image?t=' + this.state.brandImageTimestamp).done(() => {
this.setState({brandImageExists: true});
});
}
handleImageChange() {
const element = $(this.refs.fileInput);
if (element.prop('files').length > 0) {
this.setState({
brandImage: element.prop('files')[0]
});
}
}
handleImageSubmit(e) {
e.preventDefault();
if (!this.state.brandImage) {
return;
}
if (this.state.uploading) {
return;
}
$(ReactDOM.findDOMNode(this.refs.upload)).button('loading');
this.setState({
uploading: true,
error: ''
});
Client.uploadBrandImage(
this.state.brandImage,
() => {
$(ReactDOM.findDOMNode(this.refs.upload)).button('complete');
this.setState({
brandImageExists: true,
brandImage: null,
brandImageTimestamp: Date.now(),
uploading: false
});
},
(err) => {
$(ReactDOM.findDOMNode(this.refs.upload)).button('reset');
this.setState({
uploading: false,
error: err.message
});
}
);
}
render() {
let btnClass = 'btn';
if (this.state.brandImage) {
btnClass += ' btn-primary';
}
let img = null;
if (this.state.brandImage) {
img = (
<img
ref='image'
className='brand-img'
src=''
/>
);
} else if (this.state.brandImageExists) {
img = (
<img
className='brand-img'
src={Client.getAdminRoute() + '/get_brand_image?t=' + this.state.brandImageTimestamp}
/>
);
} else {
img = (
<p>
<FormattedMessage
id='admin.team.noBrandImage'
defaultMessage='No brand image uploaded'
/>
</p>
);
}
return (
<div className='form-group'>
<label className='control-label col-sm-4'>
<FormattedMessage
id='admin.team.brandImageTitle'
defaultMessage='Custom Brand Image:'
/>
</label>
<div className='col-sm-8'>
{img}
</div>
<div className='col-sm-4'/>
<div className='col-sm-8'>
<div className='file__upload'>
<button
className='btn btn-default'
disabled={this.props.disabled}
>
<FormattedMessage
id='admin.team.chooseImage'
defaultMessage='Choose New Image'
/>
</button>
<input
ref='fileInput'
type='file'
accept='.jpg,.png,.bmp'
onChange={this.handleImageChange}
/>
</div>
<button
className={btnClass}
disabled={this.props.disabled || !this.state.brandImage}
onClick={this.handleImageSubmit}
id='upload-button'
data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + Utils.localizeMessage('admin.team.uploading', 'Uploading..')}
data-complete-text={'<span class=\'glyphicon glyphicon-ok\'></span> ' + Utils.localizeMessage('admin.team.uploaded', 'Uploaded!')}
>
<FormattedMessage
id='admin.team.upload'
defaultMessage='Upload'
/>
</button>
<br/>
<FormError error={this.state.error}/>
<p className='help-text no-margin'>
<FormattedHTMLMessage
id='admin.team.uploadDesc'
defaultMessage='Customize your user experience by adding a custom image to your login screen. See examples at <a href="http://docs.mattermost.com/administration/config-settings.html#custom-branding" target="_blank">docs.mattermost.com/administration/config-settings.html#custom-branding</a>.'
/>
</p>
</div>
</div>
);
}
}

Просмотреть файл

@@ -1,83 +1,51 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
import $ from 'jquery';
import Client from 'utils/web_client.jsx';
import * as AsyncClient from '../../utils/async_client.jsx';
import * as Utils from '../../utils/utils.jsx';
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom';
export default class ComplianceSettings extends React.Component { import * as Utils from 'utils/utils.jsx';
import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import {FormattedHTMLMessage, FormattedMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
export default class ComplianceSettings extends AdminSettings {
constructor(props) { constructor(props) {
super(props); super(props);
this.handleSubmit = this.handleSubmit.bind(this); this.getConfigFromState = this.getConfigFromState.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleEnable = this.handleEnable.bind(this);
this.handleDisable = this.handleDisable.bind(this);
this.state = { this.renderSettings = this.renderSettings.bind(this);
saveNeeded: false,
serverError: null,
enable: this.props.config.ComplianceSettings.Enable
};
}
handleChange() {
this.setState({saveNeeded: true});
}
handleEnable() {
this.setState({saveNeeded: true, enable: true});
}
handleDisable() {
this.setState({saveNeeded: true, enable: false});
}
handleSubmit(e) {
e.preventDefault();
$('#save-button').button('loading');
const config = this.props.config; this.state = Object.assign(this.state, {
const oldEnable = config.ComplianceSettings.Enable; enable: props.config.ComplianceSettings.Enable,
config.ComplianceSettings.Enable = this.refs.Enable.checked; directory: props.config.ComplianceSettings.Directory,
config.ComplianceSettings.Directory = ReactDOM.findDOMNode(this.refs.Directory).value; enableDaily: props.config.ComplianceSettings.EnableDaily
config.ComplianceSettings.EnableDaily = this.refs.EnableDaily.checked; });
}
Client.saveConfig( getConfigFromState(config) {
config, config.ComplianceSettings.Enable = this.state.enable;
() => { config.ComplianceSettings.Directory = this.state.directory;
$('#save-button').button('reset'); config.ComplianceSettings.EnableDaily = this.state.enableDaily;
AsyncClient.getConfig();
this.setState({ return config;
serverError: null, }
saveNeeded: false
}); renderTitle() {
if (oldEnable !== config.ComplianceSettings.Enable) { return (
window.location.reload(); <h3>
} <FormattedMessage
}, id='admin.compliance.title'
(err) => { defaultMessage='Compliance Settings'
this.setState({ />
serverError: err.message, </h3>
saveNeeded: true
});
$('#save-button').button('reset');
}
); );
} }
render() {
let serverError = '';
if (this.state.serverError) {
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
}
let saveClass = 'btn';
if (this.state.saveNeeded) {
saveClass = 'btn btn-primary';
}
renderSettings() {
const licenseEnabled = global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.Compliance === 'true'; const licenseEnabled = global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.Compliance === 'true';
let bannerContent; let bannerContent;
@@ -95,170 +63,64 @@ export default class ComplianceSettings extends React.Component {
} }
return ( return (
<div className='wrapper--fixed'> <SettingsGroup>
{bannerContent} {bannerContent}
<h3> <BooleanSetting
<FormattedMessage id='enable'
id='admin.compliance.title' label={
defaultMessage='Compliance Settings' <FormattedMessage
/> id='admin.compliance.enableTitle'
</h3> defaultMessage='Enable Compliance:'
<form />
className='form-horizontal' }
role='form' helpText={
> <FormattedMessage
id='admin.compliance.enableDesc'
<div className='form-group'> defaultMessage='When true, Mattermost allows compliance reporting'
<label />
className='control-label col-sm-4' }
htmlFor='Enable' value={this.state.enable}
> onChange={this.handleChange}
<FormattedMessage disabled={!licenseEnabled}
id='admin.compliance.enableTitle' />
defaultMessage='Enable Compliance:' <TextSetting
/> id='directory'
</label> label={
<div className='col-sm-8'> <FormattedMessage
<label className='radio-inline'> id='admin.compliance.directoryTitle'
<input defaultMessage='Compliance Directory Location:'
type='radio' />
name='Enable' }
value='true' placeholder={Utils.localizeMessage('admin.sql.maxOpenExample', 'Ex "10"')}
ref='Enable' helpText={
defaultChecked={this.props.config.ComplianceSettings.Enable} <FormattedMessage
onChange={this.handleEnable} id='admin.compliance.directoryDescription'
disabled={!licenseEnabled} defaultMessage='Directory to which compliance reports are written. If blank, will be set to ./data/.'
/> />
<FormattedMessage }
id='admin.compliance.true' value={this.state.directory}
defaultMessage='true' onChange={this.handleChange}
/> disabled={!licenseEnabled || !this.state.enable}
</label> />
<label className='radio-inline'> <BooleanSetting
<input id='enableDaily'
type='radio' label={
name='Enable' <FormattedMessage
value='false' id='admin.compliance.enableDailyTitle'
defaultChecked={!this.props.config.ComplianceSettings.Enable} defaultMessage='Enable Daily Report:'
onChange={this.handleDisable} />
/> }
<FormattedMessage helpText={
id='admin.compliance.false' <FormattedMessage
defaultMessage='false' id='admin.compliance.enableDailyDesc'
/> defaultMessage='When true, Mattermost will generate a daily compliance report.'
</label> />
<p className='help-text'> }
<FormattedMessage value={this.state.enableDaily}
id='admin.compliance.enableDesc' onChange={this.handleChange}
defaultMessage='When true, Mattermost allows compliance reporting' disabled={!licenseEnabled || !this.state.enable}
/> />
</p> </SettingsGroup>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='Directory'
>
<FormattedMessage
id='admin.compliance.directoryTitle'
defaultMessage='Compliance Directory Location:'
/>
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='Directory'
ref='Directory'
placeholder={Utils.localizeMessage('admin.compliance.directoryExample', 'Ex "./data/"')}
defaultValue={this.props.config.ComplianceSettings.Directory}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<p className='help-text'>
<FormattedMessage
id='admin.compliance.directoryDescription'
defaultMessage='Directory to which compliance reports are written. If blank, will be set to ./data/.'
/>
</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='EnableDaily'
>
<FormattedMessage
id='admin.compliance.enableDailyTitle'
defaultMessage='Enable Daily Report:'
/>
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
<input
type='radio'
name='EnableDaily'
value='true'
ref='EnableDaily'
defaultChecked={this.props.config.ComplianceSettings.EnableDaily}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<FormattedMessage
id='admin.compliance.true'
defaultMessage='true'
/>
</label>
<label className='radio-inline'>
<input
type='radio'
name='EnableDaily'
value='false'
defaultChecked={!this.props.config.ComplianceSettings.EnableDaily}
disabled={!this.state.enable}
/>
<FormattedMessage
id='admin.compliance.false'
defaultMessage='false'
/>
</label>
<p className='help-text'>
<FormattedMessage
id='admin.compliance.enableDailyDesc'
defaultMessage='When true, Mattermost will generate a daily compliance report.'
/>
</p>
</div>
</div>
<div className='form-group'>
<div className='col-sm-12'>
{serverError}
<button
disabled={!this.state.saveNeeded}
type='submit'
className={saveClass}
onClick={this.handleSubmit}
id='save-button'
data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + Utils.localizeMessage('admin.compliance.saving', 'Saving Config...')}
>
<FormattedMessage
id='admin.compliance.save'
defaultMessage='Save'
/>
</button>
</div>
</div>
</form>
</div>
); );
} }
} }
ComplianceSettings.propTypes = {
config: React.PropTypes.object
};

Просмотреть файл

@@ -0,0 +1,74 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import * as Utils from 'utils/utils.jsx';
import AdminSettings from './admin_settings.jsx';
import {FormattedMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
export default class ConfigurationSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
this.state = Object.assign(this.state, {
listenAddress: props.config.ServiceSettings.ListenAddress
});
}
getConfigFromState(config) {
config.ServiceSettings.ListenAddress = this.state.listenAddress;
return config;
}
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.general.title'
defaultMessage='General Settings'
/>
</h3>
);
}
renderSettings() {
return (
<SettingsGroup
header={
<FormattedMessage
id='admin.general.configuration'
defaultMessage='Configuration'
/>
}
>
<TextSetting
id='listenAddress'
label={
<FormattedMessage
id='admin.service.listenAddress'
defaultMessage='Listen Address:'
/>
}
placeholder={Utils.localizeMessage('admin.service.listenExample', 'Ex ":8065"')}
helpText={
<FormattedMessage
id='admin.service.listenDescription'
defaultMessage='The address to which to bind and listen. Entering ":8065" will bind to all interfaces or you can choose one like "127.0.0.1:8065". Changing this will require a server restart before taking effect.'
/>
}
value={this.state.listenAddress}
onChange={this.handleChange}
/>
</SettingsGroup>
);
}
}

Просмотреть файл

@@ -8,63 +8,62 @@ import DropdownSetting from './dropdown_setting.jsx';
import {FormattedMessage} from 'react-intl'; import {FormattedMessage} from 'react-intl';
const CONNECTION_SECURITY_HELP_TEXT = ( const CONNECTION_SECURITY_HELP_TEXT = (
<div className='help-text'> <table
<table className='table table-bordered table-margin--none'
className='table table-bordered table-margin--none' cellPadding='5'
cellPadding='5' >
> <tbody>
<tbody> <tr>
<tr> <td className='help-text'>
<td className='help-text'> <FormattedMessage
<FormattedMessage id='admin.connectionSecurityNone'
id='admin.connectionSecurityNone' defaultMessage='None'
defaultMessage='None' />
/> </td>
</td> <td className='help-text'>
<td className='help-text'> <FormattedMessage
<FormattedMessage id='admin.connectionSecurityNoneDescription'
id='admin.connectionSecurityNoneDescription' defaultMessage='Mattermost will connect over an unsecure connection.'
defaultMessage='Mattermost will connect over an unsecure connection.' />
/> </td>
</td> </tr>
</tr> <tr>
<tr> <td className='help-text'>
<td className='help-text'> <FormattedMessage
<FormattedMessage id='admin.connectionSecurityTls'
id='admin.connectionSecurityTls' defaultMessage='TLS'
defaultMessage='TLS' />
/> </td>
</td> <td className='help-text'>
<td className='help-text'> <FormattedMessage
<FormattedMessage id='admin.connectionSecurityTlsDescription'
id='admin.connectionSecurityTlsDescription' defaultMessage='Encrypts the communication between Mattermost and your server.'
defaultMessage='Encrypts the communication between Mattermost and your server.' />
/> </td>
</td> </tr>
</tr> <tr>
<tr> <td className='help-text'>
<td className='help-text'> <FormattedMessage
<FormattedMessage id='admin.connectionSecurityStart'
id='admin.connectionSecurityStart' defaultMessage='STARTTLS'
defaultMessage='STARTTLS' />
/> </td>
</td> <td className='help-text'>
<td className='help-text'> <FormattedMessage
<FormattedMessage id='admin.connectionSecurityStartDescription'
id='admin.connectionSecurityStartDescription' defaultMessage='Takes an existing insecure connection and attempts to upgrade it to a secure connection using TLS.'
defaultMessage='Takes an existing insecure connection and attempts to upgrade it to a secure connection using TLS.' />
/> </td>
</td> </tr>
</tr> </tbody>
</tbody> </table>
</table>
</div>
); );
export default class ConnectionSecurityDropdownSetting extends React.Component { export default class ConnectionSecurityDropdownSetting extends React.Component {
render() { render() {
return ( return (
<DropdownSetting <DropdownSetting
id='connectionSecurity'
values={[ values={[
{value: '', text: Utils.localizeMessage('admin.connectionSecurityNone', 'None')}, {value: '', text: Utils.localizeMessage('admin.connectionSecurityNone', 'None')},
{value: 'TLS', text: Utils.localizeMessage('admin.connectionSecurityTls', 'TLS (Recommended)')}, {value: 'TLS', text: Utils.localizeMessage('admin.connectionSecurityTls', 'TLS (Recommended)')},
@@ -76,11 +75,10 @@ export default class ConnectionSecurityDropdownSetting extends React.Component {
defaultMessage='Connection Security:' defaultMessage='Connection Security:'
/> />
} }
currentValue={this.props.currentValue} value={this.props.value}
handleChange={this.props.handleChange} onChange={this.props.onChange}
isDisabled={this.props.isDisabled} disabled={this.props.disabled}
helpText={CONNECTION_SECURITY_HELP_TEXT} helpText={CONNECTION_SECURITY_HELP_TEXT}
margin='small'
/> />
); );
} }
@@ -89,7 +87,7 @@ ConnectionSecurityDropdownSetting.defaultProps = {
}; };
ConnectionSecurityDropdownSetting.propTypes = { ConnectionSecurityDropdownSetting.propTypes = {
currentValue: React.PropTypes.string.isRequired, value: React.PropTypes.string.isRequired,
handleChange: React.PropTypes.func.isRequired, onChange: React.PropTypes.func.isRequired,
isDisabled: React.PropTypes.bool.isRequired disabled: React.PropTypes.bool.isRequired
}; };

Просмотреть файл

@@ -0,0 +1,94 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import * as Utils from 'utils/utils.jsx';
import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import {FormattedMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
export default class ConnectionSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
this.state = Object.assign(this.state, {
allowCorsFrom: props.config.ServiceSettings.AllowCorsFrom,
enableInsecureOutgoingConnections: props.config.ServiceSettings.EnableInsecureOutgoingConnections
});
}
getConfigFromState(config) {
config.ServiceSettings.AllowCorsFrom = this.state.allowCorsFrom;
config.ServiceSettings.EnableInsecureOutgoingConnections = this.state.enableInsecureOutgoingConnections;
return config;
}
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.security.title'
defaultMessage='Security Settings'
/>
</h3>
);
}
renderSettings() {
return (
<SettingsGroup
header={
<FormattedMessage
id='admin.security.connection'
defaultMessage='Connections'
/>
}
>
<TextSetting
id='allowCorsFrom'
label={
<FormattedMessage
id='admin.service.corsTitle'
defaultMessage='Allow Cross-origin Requests from:'
/>
}
placeholder={Utils.localizeMessage('admin.service.corsEx', 'http://example.com')}
helpText={
<FormattedMessage
id='admin.service.corsDescription'
defaultMessage='Enable HTTP Cross origin request from a specific domain. Use "*" if you want to allow CORS from any domain or leave it blank to disable it.'
/>
}
value={this.state.allowCorsFrom}
onChange={this.handleChange}
/>
<BooleanSetting
id='enableInsecureOutgoingConnections'
label={
<FormattedMessage
id='admin.service.insecureTlsTitle'
defaultMessage='Enable Insecure Outgoing Connections: '
/>
}
helpText={
<FormattedMessage
id='admin.service.insecureTlsDesc'
defaultMessage='When true, any outgoing HTTPS requests will accept unverified, self-signed certificates. For example, outgoing webhooks to a server with a self-signed TLS certificate, using any domain, will be allowed. Note that this makes these connections susceptible to man-in-the-middle attacks.'
/>
}
value={this.state.enableInsecureOutgoingConnections}
onChange={this.handleChange}
/>
</SettingsGroup>
);
}
}

Просмотреть файл

@@ -0,0 +1,137 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import * as Utils from 'utils/utils.jsx';
import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import BrandImageSetting from './brand_image_setting.jsx';
import {FormattedMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
export default class CustomBrandSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
this.state = Object.assign(this.state, {
siteName: props.config.TeamSettings.SiteName,
enableCustomBrand: props.config.TeamSettings.EnableCustomBrand,
customBrandText: props.config.TeamSettings.CustomBrandText
});
}
getConfigFromState(config) {
config.TeamSettings.SiteName = this.state.siteName;
if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.CustomBrand === 'true') {
config.TeamSettings.EnableCustomBrand = this.state.enableCustomBrand;
config.TeamSettings.CustomBrandText = this.state.customBrandText;
}
return config;
}
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.customization.title'
defaultMessage='Customization Settings'
/>
</h3>
);
}
renderSettings() {
const enterpriseSettings = [];
if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.CustomBrand === 'true') {
enterpriseSettings.push(
<BooleanSetting
key='enableCustomBrand'
id='enableCustomBrand'
label={
<FormattedMessage
id='admin.team.brandTitle'
defaultMessage='Enable Custom Branding: '
/>
}
helpText={
<FormattedMessage
id='admin.team.brandDesc'
defaultMessage='Enable custom branding to show an image of your choice, uploaded below, and some help text, written below, on the login page.'
/>
}
value={this.state.enableCustomBrand}
onChange={this.handleChange}
/>
);
enterpriseSettings.push(
<BrandImageSetting
key='customBrandImage'
disabled={!this.state.enableCustomBrand}
/>
);
enterpriseSettings.push(
<TextSetting
key='customBrandText'
id='customBrandText'
type='textarea'
label={
<FormattedMessage
id='admin.team.brandTextTitle'
defaultMessage='Custom Brand Text:'
/>
}
helpText={
<FormattedMessage
id='admin.team.brandTextDescription'
defaultMessage='The custom branding Markdown-formatted text you would like to appear below your custom brand image on your login sreen.'
/>
}
value={this.state.customBrandText}
onChange={this.handleChange}
disabled={!this.state.enableCustomBrand}
/>
);
}
return (
<SettingsGroup
header={
<FormattedMessage
id='admin.customization.customBrand'
defaultMessage='Custom Branding'
/>
}
>
<TextSetting
id='siteName'
label={
<FormattedMessage
id='admin.team.siteNameTitle'
defaultMessage='Site Name:'
/>
}
placeholder={Utils.localizeMessage('admin.team.siteNameExample', 'Ex "Mattermost"')}
helpText={
<FormattedMessage
id='admin.team.siteNameDescription'
defaultMessage='Name of service shown in login screens and UI.'
/>
}
value={this.state.siteName}
onChange={this.handleChange}
/>
{enterpriseSettings}
</SettingsGroup>
);
}
}

Просмотреть файл

@@ -0,0 +1,192 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import * as Utils from 'utils/utils.jsx';
import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import {FormattedMessage} from 'react-intl';
import GeneratedSetting from './generated_setting.jsx';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
export default class DatabaseSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
this.state = Object.assign(this.state, {
driverName: this.props.config.SqlSettings.DriverName,
dataSource: this.props.config.SqlSettings.DataSource,
dataSourceReplicas: this.props.config.SqlSettings.DataSourceReplicas,
maxIdleConns: props.config.SqlSettings.MaxIdleConns,
maxOpenConns: props.config.SqlSettings.MaxOpenConns,
atRestEncryptKey: props.config.SqlSettings.AtRestEncryptKey,
trace: props.config.SqlSettings.Trace
});
}
getConfigFromState(config) {
// driverName, dataSource, and dataSourceReplicas are read-only from the UI
config.SqlSettings.MaxIdleConns = this.parseIntNonZero(this.state.maxIdleConns);
config.SqlSettings.MaxOpenConns = this.parseIntNonZero(this.state.maxOpenConns);
config.SqlSettings.AtRestEncryptKey = this.state.atRestEncryptKey;
config.SqlSettings.Trace = this.state.trace;
return config;
}
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.database.title'
defaultMessage='Database Settings'
/>
</h3>
);
}
renderSettings() {
const dataSource = '**********' + this.state.dataSource.substring(this.state.dataSource.indexOf('@'));
let dataSourceReplicas = '';
this.state.dataSourceReplicas.forEach((replica) => {
dataSourceReplicas += '[**********' + replica.substring(replica.indexOf('@')) + '] ';
});
if (this.state.dataSourceReplicas.length === 0) {
dataSourceReplicas = 'none';
}
return (
<SettingsGroup>
<p>
<FormattedMessage
id='admin.sql.noteDescription'
defaultMessage='Changing properties in this section will require a server restart before taking effect.'
/>
</p>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='DriverName'
>
<FormattedMessage
id='admin.sql.driverName'
defaultMessage='Driver Name:'
/>
</label>
<div className='col-sm-8'>
<p className='help-text'>{this.state.driverName}</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='DataSource'
>
<FormattedMessage
id='admin.sql.dataSource'
defaultMessage='Data Source:'
/>
</label>
<div className='col-sm-8'>
<p className='help-text'>{dataSource}</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='DataSourceReplicas'
>
<FormattedMessage
id='admin.sql.replicas'
defaultMessage='Data Source Replicas:'
/>
</label>
<div className='col-sm-8'>
<p className='help-text'>{dataSourceReplicas}</p>
</div>
</div>
<TextSetting
id='maxIdleConns'
label={
<FormattedMessage
id='admin.sql.maxConnectionsTitle'
defaultMessage='Maximum Idle Connections:'
/>
}
placeholder={Utils.localizeMessage('admin.sql.maxConnectionsExample', 'Ex "10"')}
helpText={
<FormattedMessage
id='admin.sql.maxConnectionsDescription'
defaultMessage='Maximum number of idle connections held open to the database.'
/>
}
value={this.state.maxIdleConns}
onChange={this.handleChange}
/>
<TextSetting
id='maxOpenConns'
label={
<FormattedMessage
id='admin.sql.maxOpenTitle'
defaultMessage='Maximum Open Connections:'
/>
}
placeholder={Utils.localizeMessage('admin.sql.maxOpenExample', 'Ex "10"')}
helpText={
<FormattedMessage
id='admin.sql.maxOpenDescription'
defaultMessage='Maximum number of open connections held open to the database.'
/>
}
value={this.state.maxOpenConns}
onChange={this.handleChange}
/>
<GeneratedSetting
id='atRestEncryptKey'
label={
<FormattedMessage
id='admin.sql.keyTitle'
defaultMessage='At Rest Encrypt Key:'
/>
}
placeholder={Utils.localizeMessage('admin.sql.keyExample', 'Ex "gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6"')}
helpText={
<FormattedMessage
id='admin.sql.keyDescription'
defaultMessage='32-character salt available to encrypt and decrypt sensitive fields in database.'
/>
}
value={this.state.atRestEncryptKey}
onChange={this.handleChange}
/>
<BooleanSetting
id='trace'
label={
<FormattedMessage
id='admin.sql.traceTitle'
defaultMessage='Trace: '
/>
}
helpText={
<FormattedMessage
id='admin.sql.traceDescription'
defaultMessage='(Development Mode) When true, executing SQL statements are written to the log.'
/>
}
value={this.state.trace}
onChange={this.handleChange}
/>
</SettingsGroup>
);
}
}

Просмотреть файл

@@ -0,0 +1,83 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import {FormattedMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
export default class DeveloperSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
this.state = Object.assign(this.state, {
enableTesting: props.config.ServiceSettings.EnableTesting,
enableDeveloper: props.config.ServiceSettings.EnableDeveloper
});
}
getConfigFromState(config) {
config.ServiceSettings.EnableTesting = this.state.enableTesting;
config.ServiceSettings.EnableDeveloper = this.state.enableDeveloper;
return config;
}
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.developer.title'
defaultMessage='Developer Settings'
/>
</h3>
);
}
renderSettings() {
return (
<SettingsGroup>
<BooleanSetting
id='enableTesting'
label={
<FormattedMessage
id='admin.service.testingTitle'
defaultMessage='Enable Testing: '
/>
}
helpText={
<FormattedMessage
id='admin.service.testingDescription'
defaultMessage='(Developer Option) When true, /loadtest slash command is enabled to load test accounts and test data. Changing this will require a server restart before taking effect.'
/>
}
value={this.state.enableTesting}
onChange={this.handleChange}
/>
<BooleanSetting
id='enableDeveloper'
label={
<FormattedMessage
id='admin.service.developerTitle'
defaultMessage='Enable Developer Mode: '
/>
}
helpText={
<FormattedMessage
id='admin.service.developerDesc'
defaultMessage='(Developer Option) When true, extra information around errors will be displayed in the UI.'
/>
}
value={this.state.enableDeveloper}
onChange={this.handleChange}
/>
</SettingsGroup>
);
}
}

Просмотреть файл

@@ -6,6 +6,16 @@ import React from 'react';
import Setting from './setting.jsx'; import Setting from './setting.jsx';
export default class DropdownSetting extends React.Component { export default class DropdownSetting extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.props.onChange(this.props.id, e.target.value);
}
render() { render() {
const options = []; const options = [];
for (const {value, text} of this.props.values) { for (const {value, text} of this.props.values) {
@@ -22,30 +32,33 @@ export default class DropdownSetting extends React.Component {
return ( return (
<Setting <Setting
label={this.props.label} label={this.props.label}
margin={this.props.margin} inputId={this.props.id}
helpText={this.props.helpText}
> >
<select <select
className='form-control' className='form-control'
value={this.props.currentValue} id={this.props.id}
onChange={this.props.handleChange} value={this.props.value}
disabled={this.props.isDisabled} onChange={this.handleChange}
disabled={this.props.disabled}
> >
{options} {options}
</select> </select>
{this.props.helpText}
</Setting> </Setting>
); );
} }
} }
DropdownSetting.defaultProps = { DropdownSetting.defaultProps = {
isDisabled: false
}; };
DropdownSetting.propTypes = { DropdownSetting.propTypes = {
id: React.PropTypes.string.isRequired,
values: React.PropTypes.array.isRequired, values: React.PropTypes.array.isRequired,
label: React.PropTypes.node.isRequired, label: React.PropTypes.node.isRequired,
currentValue: React.PropTypes.string.isRequired, value: React.PropTypes.string.isRequired,
handleChange: React.PropTypes.func.isRequired, onChange: React.PropTypes.func.isRequired,
isDisabled: React.PropTypes.bool.isRequired, disabled: React.PropTypes.bool,
helpText: React.PropTypes.node.isRequired, helpText: React.PropTypes.node
margin: React.PropTypes.oneOf(['', 'small'])
}; };

Просмотреть файл

@@ -0,0 +1,109 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import {FormattedMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
export default class EmailAuthenticationSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
this.state = Object.assign(this.state, {
enableSignUpWithEmail: props.config.EmailSettings.EnableSignUpWithEmail,
enableSignInWithEmail: props.config.EmailSettings.EnableSignInWithEmail,
enableSignInWithUsername: props.config.EmailSettings.EnableSignInWithUsername
});
}
getConfigFromState(config) {
config.EmailSettings.EnableSignUpWithEmail = this.state.enableSignUpWithEmail;
config.EmailSettings.EnableSignInWithEmail = this.state.enableSignInWithEmail;
config.EmailSettings.EnableSignInWithUsername = this.state.enableSignInWithUsername;
return config;
}
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.authentication.title'
defaultMessage='Authentication Settings'
/>
</h3>
);
}
renderSettings() {
return (
<SettingsGroup
header={
<FormattedMessage
id='admin.authentication.email'
defaultMessage='Email'
/>
}
>
<BooleanSetting
id='enableSignUpWithEmail'
label={
<FormattedMessage
id='admin.email.allowSignupTitle'
defaultMessage='Allow Sign Up With Email: '
/>
}
helpText={
<FormattedMessage
id='admin.email.allowSignupDescription'
defaultMessage='When true, Mattermost allows team creation and account signup using email and password. This value should be false only when you want to limit signup to a single-sign-on service like OAuth or LDAP.'
/>
}
value={this.state.enableSignUpWithEmail}
onChange={this.handleChange}
/>
<BooleanSetting
id='enableSignInWithEmail'
label={
<FormattedMessage
id='admin.email.allowEmailSignInTitle'
defaultMessage='Allow Sign In With Email: '
/>
}
helpText={
<FormattedMessage
id='admin.email.allowEmailSignInDescription'
defaultMessage='When true, Mattermost allows users to sign in using their email and password.'
/>
}
value={this.state.enableSignInWithEmail}
onChange={this.handleChange}
/>
<BooleanSetting
id='enableSignInWithUsername'
label={
<FormattedMessage
id='admin.email.allowUsernameSignInTitle'
defaultMessage='Allow Sign In With Username: '
/>
}
helpText={
<FormattedMessage
id='admin.email.allowUsernameSignInDescription'
defaultMessage='When true, Mattermost allows users to sign in using their username and password. This setting is typically only used when email verification is disabled.'
/>
}
value={this.state.enableSignInWithUsername}
onChange={this.handleChange}
/>
</SettingsGroup>
);
}
}

Просмотреть файл

@@ -0,0 +1,118 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import Client from 'utils/web_client.jsx';
import * as Utils from 'utils/utils.jsx';
import {FormattedMessage} from 'react-intl';
export default class EmailConnectionTestButton extends React.Component {
static get propTypes() {
return {
config: React.PropTypes.object.isRequired,
disabled: React.PropTypes.bool.isRequired
};
}
constructor(props) {
super(props);
this.handleTestConnection = this.handleTestConnection.bind(this);
this.state = {
testing: false,
success: false,
fail: null
};
}
handleTestConnection(e) {
e.preventDefault();
this.setState({
testing: true,
success: false,
fail: null
});
Client.testEmail(
this.props.config,
() => {
this.setState({
testing: false,
success: true
});
},
(err) => {
this.setState({
testing: false,
fail: err.message + ' - ' + err.detailed_error
});
}
);
}
render() {
let testMessage = null;
if (this.state.success) {
testMessage = (
<div className='alert alert-success'>
<i className='fa fa-check'></i>
<FormattedMessage
id='admin.email.emailSuccess'
defaultMessage='No errors were reported while sending an email. Please check your inbox to make sure.'
/>
</div>
);
} else if (this.state.fail) {
testMessage = (
<div className='alert alert-warning'>
<i className='fa fa-warning'></i>
<FormattedMessage
id='admin.email.emailFail'
defaultMessage='Connection unsuccessful: {error}'
values={{
error: this.state.fail
}}
/>
</div>
);
}
let contents = null;
if (this.state.testing) {
contents = (
<span>
<span className='glyphicon glyphicon-refresh glyphicon-refresh-animate'/>
{Utils.localizeMessage('admin.email.testing', 'Testing...')}
</span>
);
} else {
contents = (
<FormattedMessage
id='admin.email.connectionSecurityTest'
defaultMessage='Test Connection'
/>
);
}
return (
<div className='form-group email-connection-test'>
<div className='col-sm-offset-4 col-sm-8'>
<div className='help-text'>
<button
className='btn btn-default'
onClick={this.handleTestConnection}
disabled={this.props.disabled}
>
{contents}
</button>
{testMessage}
</div>
</div>
</div>
);
}
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@@ -0,0 +1,94 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import * as Utils from 'utils/utils.jsx';
import AdminSettings from './admin_settings.jsx';
import {FormattedHTMLMessage, FormattedMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
export default class ExternalServiceSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
this.state = Object.assign(this.state, {
segmentDeveloperKey: props.config.ServiceSettings.SegmentDeveloperKey,
googleDeveloperKey: props.config.ServiceSettings.GoogleDeveloperKey
});
}
getConfigFromState(config) {
config.ServiceSettings.SegmentDeveloperKey = this.state.segmentDeveloperKey;
config.ServiceSettings.GoogleDeveloperKey = this.state.googleDeveloperKey;
return config;
}
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.integration.title'
defaultMessage='Integration Settings'
/>
</h3>
);
}
renderSettings() {
return (
<SettingsGroup
header={
<FormattedMessage
id='admin.integrations.external'
defaultMessage='External Services'
/>
}
>
<TextSetting
id='segmentDeveloperKey'
label={
<FormattedMessage
id='admin.service.segmentTitle'
defaultMessage='Segment Developer Key:'
/>
}
placeholder={Utils.localizeMessage('admin.service.segmentExample', 'Ex "g3fgGOXJAQ43QV7rAh6iwQCkV4cA1Gs"')}
helpText={
<FormattedMessage
id='admin.service.segmentDescription'
defaultMessage='For users running a SaaS services, sign up for a key at Segment.com to track metrics.'
/>
}
value={this.state.segmentDeveloperKey}
onChange={this.handleChange}
/>
<TextSetting
id='googleDeveloperKey'
label={
<FormattedMessage
id='admin.service.googleTitle'
defaultMessage='Google Developer Key:'
/>
}
placeholder={Utils.localizeMessage('admin.service.googleExample', 'Ex "7rAh6iwQCkV4cA1Gsg3fgGOXJAQ43QV"')}
helpText={
<FormattedHTMLMessage
id='admin.service.googleDescription'
defaultMessage='Set this key to enable embedding of YouTube video previews based on hyperlinks appearing in messages or comments. Instructions to obtain a key available at <a href="https://www.youtube.com/watch?v=Im69kzhpR3I" target="_blank">https://www.youtube.com/watch?v=Im69kzhpR3I</a>. Leaving the field blank disables the automatic generation of YouTube video previews from links.'
/>
}
value={this.state.googleDeveloperKey}
onChange={this.handleChange}
/>
</SettingsGroup>
);
}
}

Просмотреть файл

@@ -0,0 +1,97 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import crypto from 'crypto';
import {FormattedMessage} from 'react-intl';
export default class GeneratedSetting extends React.Component {
static get propTypes() {
return {
id: React.PropTypes.string.isRequired,
label: React.PropTypes.node.isRequired,
placeholder: React.PropTypes.string,
value: React.PropTypes.string.isRequired,
onChange: React.PropTypes.func.isRequired,
disabled: React.PropTypes.bool.isRequired,
disabledText: React.PropTypes.node,
helpText: React.PropTypes.node.isRequired,
regenerateText: React.PropTypes.node
};
}
static get defaultProps() {
return {
disabled: false,
regenerateText: (
<FormattedMessage
id='admin.email.regenerate'
defaultMessage='Re-Generate'
/>
)
};
}
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.regenerate = this.regenerate.bind(this);
}
handleChange(e) {
this.props.onChange(this.props.id, e.target.value === 'true');
}
regenerate(e) {
e.preventDefault();
this.props.onChange(this.props.id, crypto.randomBytes(256).toString('base64').substring(0, 32));
}
render() {
let disabledText = null;
if (this.props.disabled && this.props.disabledText) {
disabledText = (
<div className='admin-console__disabled-text'>
{this.props.disabledText}
</div>
);
}
return (
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor={this.props.id}
>
{this.props.label}
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id={this.props.id}
placeholder={this.props.placeholder}
value={this.props.value}
onChange={this.handleChange}
disabled={this.props.disabled}
/>
{disabledText}
<div className='help-text'>
{this.props.helpText}
</div>
<button
className='btn btn-default'
onClick={this.regenerate}
disabled={this.props.disabled}
>
{this.props.regenerateText}
</button>
</div>
</div>
);
}
}

Просмотреть файл

@@ -1,382 +1,186 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
import $ from 'jquery';
import ReactDOM from 'react-dom';
import Client from 'utils/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl';
const holders = defineMessages({
clientIdExample: {
id: 'admin.gitlab.clientIdExample',
defaultMessage: 'Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"'
},
clientSecretExample: {
id: 'admin.gitlab.clientSecretExample',
defaultMessage: 'Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"'
},
authExample: {
id: 'admin.gitlab.authExample',
defaultMessage: 'Ex ""'
},
tokenExample: {
id: 'admin.gitlab.tokenExample',
defaultMessage: 'Ex ""'
},
userExample: {
id: 'admin.gitlab.userExample',
defaultMessage: 'Ex ""'
},
saving: {
id: 'admin.gitlab.saving',
defaultMessage: 'Saving Config...'
}
});
import React from 'react'; import React from 'react';
class GitLabSettings extends React.Component { import * as Utils from 'utils/utils.jsx';
import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import {FormattedHTMLMessage, FormattedMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
export default class GitLabSettings extends AdminSettings {
constructor(props) { constructor(props) {
super(props); super(props);
this.handleChange = this.handleChange.bind(this); this.getConfigFromState = this.getConfigFromState.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.state = { this.renderSettings = this.renderSettings.bind(this);
Enable: this.props.config.GitLabSettings.Enable,
saveNeeded: false, this.state = Object.assign(this.state, {
serverError: null enable: props.config.GitLabSettings.Enable,
}; id: props.config.GitLabSettings.Id,
secret: props.config.GitLabSettings.Secret,
userApiEndpoint: props.config.GitLabSettings.UserApiEndpoint,
authEndpoint: props.config.GitLabSettings.AuthEndpoint,
tokenEndpoint: props.config.GitLabSettings.TokenEndpoint
});
} }
handleChange(action) { getConfigFromState(config) {
var s = {saveNeeded: true, serverError: this.state.serverError}; config.GitLabSettings.Enable = this.state.enable;
config.GitLabSettings.Id = this.state.id;
config.GitLabSettings.Secret = this.state.secret;
config.GitLabSettings.UserApiEndpoint = this.state.userApiEndpoint;
config.GitLabSettings.AuthEndpoint = this.state.authEndpoint;
config.GitLabSettings.TokenEndpoint = this.state.tokenEndpoint;
if (action === 'EnableTrue') { return config;
s.Enable = true;
}
if (action === 'EnableFalse') {
s.Enable = false;
}
this.setState(s);
} }
handleSubmit(e) { renderTitle() {
e.preventDefault(); return (
$('#save-button').button('loading'); <h3>
<FormattedMessage
var config = this.props.config; id='admin.authentication.title'
config.GitLabSettings.Enable = ReactDOM.findDOMNode(this.refs.Enable).checked; defaultMessage='Authentication Settings'
config.GitLabSettings.Secret = ReactDOM.findDOMNode(this.refs.Secret).value.trim(); />
config.GitLabSettings.Id = ReactDOM.findDOMNode(this.refs.Id).value.trim(); </h3>
config.GitLabSettings.AuthEndpoint = ReactDOM.findDOMNode(this.refs.AuthEndpoint).value.trim();
config.GitLabSettings.TokenEndpoint = ReactDOM.findDOMNode(this.refs.TokenEndpoint).value.trim();
config.GitLabSettings.UserApiEndpoint = ReactDOM.findDOMNode(this.refs.UserApiEndpoint).value.trim();
Client.saveConfig(
config,
() => {
AsyncClient.getConfig();
this.setState({
serverError: null,
saveNeeded: false
});
$('#save-button').button('reset');
},
(err) => {
this.setState({
serverError: err.message,
saveNeeded: true
});
$('#save-button').button('reset');
}
); );
} }
render() { renderSettings() {
const {formatMessage} = this.props.intl;
var serverError = '';
if (this.state.serverError) {
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
}
var saveClass = 'btn';
if (this.state.saveNeeded) {
saveClass = 'btn btn-primary';
}
return ( return (
<div className='wrapper--fixed'> <SettingsGroup
header={
<h3>
<FormattedMessage <FormattedMessage
id='admin.gitlab.settingsTitle' id='admin.authentication.gitlab'
defaultMessage='GitLab Settings' defaultMessage='GitLab'
/> />
</h3> }
<form >
className='form-horizontal' <BooleanSetting
role='form' id='enable'
> label={
<div className='form-group'> <FormattedMessage
<label id='admin.gitlab.enableTitle'
className='control-label col-sm-4' defaultMessage='Enable Sign Up With GitLab: '
htmlFor='Enable' />
> }
helpText={
<div>
<FormattedMessage <FormattedMessage
id='admin.gitlab.enableTitle' id='admin.gitlab.enableDescription'
defaultMessage='Enable Sign Up With GitLab: ' defaultMessage='When true, Mattermost allows team creation and account signup using GitLab OAuth.'
/>
<br/>
<FormattedHTMLMessage
id='admin.gitlab.EnableHtmlDesc'
defaultMessage='<ol><li>Log in to your GitLab account and go to Profile Settings -> Applications.</li><li>Enter Redirect URIs "<your-mattermost-url>/login/gitlab/complete" (example: http://localhost:8065/login/gitlab/complete) and "<your-mattermost-url>/signup/gitlab/complete". </li><li>Then use "Secret" and "Id" fields from GitLab to complete the options below.</li><li>Complete the Endpoint URLs below. </li></ol>'
/> />
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
<input
type='radio'
name='Enable'
value='true'
ref='Enable'
defaultChecked={this.props.config.GitLabSettings.Enable}
onChange={this.handleChange.bind(this, 'EnableTrue')}
/>
<FormattedMessage
id='admin.gitlab.true'
defaultMessage='true'
/>
</label>
<label className='radio-inline'>
<input
type='radio'
name='Enable'
value='false'
defaultChecked={!this.props.config.GitLabSettings.Enable}
onChange={this.handleChange.bind(this, 'EnableFalse')}
/>
<FormattedMessage
id='admin.gitlab.false'
defaultMessage='false'
/>
</label>
<p className='help-text'>
<FormattedMessage
id='admin.gitlab.enableDescription'
defaultMessage='When true, Mattermost allows team creation and account signup using GitLab OAuth.'
/>
<br/>
</p>
<div className='help-text'>
<FormattedHTMLMessage
id='admin.gitlab.EnableHtmlDesc'
defaultMessage='<ol><li>Log in to your GitLab account and go to Profile Settings -> Applications.</li><li>Enter Redirect URIs "<your-mattermost-url>/login/gitlab/complete" (example: http://localhost:8065/login/gitlab/complete) and "<your-mattermost-url>/signup/gitlab/complete". </li><li>Then use "Secret" and "Id" fields from GitLab to complete the options below.</li><li>Complete the Endpoint URLs below. </li></ol>'
/>
</div>
</div> </div>
</div> }
value={this.state.enable}
<div className='form-group'> onChange={this.handleChange}
<label />
className='control-label col-sm-4' <TextSetting
htmlFor='Id' id='id'
> label={
<FormattedMessage <FormattedMessage
id='admin.gitlab.clientIdTitle' id='admin.gitlab.clientIdTitle'
defaultMessage='Id:' defaultMessage='Id:'
/> />
</label> }
<div className='col-sm-8'> placeholder={Utils.localizeMessage('admin.gitlab.clientIdExample', 'Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"')}
<input helpText={
type='text' <FormattedMessage
className='form-control' id='admin.gitlab.clientIdDescription'
id='Id' defaultMessage='Obtain this value via the instructions above for logging into GitLab'
ref='Id' />
placeholder={formatMessage(holders.clientIdExample)} }
defaultValue={this.props.config.GitLabSettings.Id} value={this.state.id}
onChange={this.handleChange} onChange={this.handleChange}
disabled={!this.state.Enable} disabled={!this.state.enable}
/> />
<p className='help-text'> <TextSetting
<FormattedMessage id='secret'
id='admin.gitlab.clientIdDescription' label={
defaultMessage='Obtain this value via the instructions above for logging into GitLab' <FormattedMessage
/> id='admin.gitlab.clientSecretTitle'
</p> defaultMessage='Secret:'
</div> />
</div> }
placeholder={Utils.localizeMessage('admin.gitlab.clientSecretExample', 'Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"')}
<div className='form-group'> helpText={
<label <FormattedMessage
className='control-label col-sm-4' id='admin.gitab.clientSecretDescription'
htmlFor='Secret' defaultMessage='Obtain this value via the instructions above for logging into GitLab.'
> />
<FormattedMessage }
id='admin.gitlab.clientSecretTitle' value={this.state.secret}
defaultMessage='Secret:' onChange={this.handleChange}
/> disabled={!this.state.enable}
</label> />
<div className='col-sm-8'> <TextSetting
<input id='userApiEndpoint'
type='text' label={
className='form-control' <FormattedMessage
id='Secret' id='admin.gitlab.userTitle'
ref='Secret' defaultMessage='User API Endpoint:'
placeholder={formatMessage(holders.clientSecretExample)} />
defaultValue={this.props.config.GitLabSettings.Secret} }
onChange={this.handleChange} placeholder={Utils.localizeMessage('admin.gitlab.userExample', 'Ex ""')}
disabled={!this.state.Enable} helpText={
/> <FormattedMessage
<p className='help-text'> id='admin.gitlab.userDescription'
<FormattedMessage defaultMessage='Enter https://<your-gitlab-url>/api/v3/user. Make sure you use HTTP or HTTPS in your URL depending on your server configuration.'
id='admin.gitab.clientSecretDescription' />
defaultMessage='Obtain this value via the instructions above for logging into GitLab.' }
/> value={this.state.userApiEndpoint}
</p> onChange={this.handleChange}
</div> disabled={!this.state.enable}
</div> />
<TextSetting
<div className='form-group'> id='authEndpoint'
<label label={
className='control-label col-sm-4' <FormattedMessage
htmlFor='AuthEndpoint' id='admin.gitlab.authTitle'
> defaultMessage='Auth Endpoint:'
<FormattedMessage />
id='admin.gitlab.authTitle' }
defaultMessage='Auth Endpoint:' placeholder={Utils.localizeMessage('admin.gitlab.authExample', 'Ex ""')}
/> helpText={
</label> <FormattedMessage
<div className='col-sm-8'> id='admin.gitlab.authDescription'
<input defaultMessage='Enter https://<your-gitlab-url>/oauth/authorize (example https://example.com:3000/oauth/authorize). Make sure you use HTTP or HTTPS in your URL depending on your server configuration.'
type='text' />
className='form-control' }
id='AuthEndpoint' value={this.state.authEndpoint}
ref='AuthEndpoint' onChange={this.handleChange}
placeholder={formatMessage(holders.authExample)} disabled={!this.state.enable}
defaultValue={this.props.config.GitLabSettings.AuthEndpoint} />
onChange={this.handleChange} <TextSetting
disabled={!this.state.Enable} id='tokenEndpoint'
/> label={
<p className='help-text'> <FormattedMessage
<FormattedMessage id='admin.gitlab.tokenTitle'
id='admin.gitlab.authDescription' defaultMessage='Token Endpoint:'
defaultMessage='Enter https://<your-gitlab-url>/oauth/authorize (example https://example.com:3000/oauth/authorize). Make sure you use HTTP or HTTPS in your URL depending on your server configuration.' />
/> }
</p> placeholder={Utils.localizeMessage('admin.gitlab.tokenExample', 'Ex ""')}
</div> helpText={
</div> <FormattedMessage
id='admin.gitlab.tokenDescription'
<div className='form-group'> defaultMessage='Enter https://<your-gitlab-url>/oauth/token. Make sure you use HTTP or HTTPS in your URL depending on your server configuration.'
<label />
className='control-label col-sm-4' }
htmlFor='TokenEndpoint' value={this.state.tokenEndpoint}
> onChange={this.handleChange}
<FormattedMessage disabled={!this.state.enable}
id='admin.gitlab.tokenTitle' />
defaultMessage='Token Endpoint:' </SettingsGroup>
/>
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='TokenEndpoint'
ref='TokenEndpoint'
placeholder={formatMessage(holders.tokenExample)}
defaultValue={this.props.config.GitLabSettings.TokenEndpoint}
onChange={this.handleChange}
disabled={!this.state.Enable}
/>
<p className='help-text'>
<FormattedMessage
id='admin.gitlab.tokenDescription'
defaultMessage='Enter https://<your-gitlab-url>/oauth/token. Make sure you use HTTP or HTTPS in your URL depending on your server configuration.'
/>
</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='UserApiEndpoint'
>
<FormattedMessage
id='admin.gitlab.userTitle'
defaultMessage='User API Endpoint:'
/>
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='UserApiEndpoint'
ref='UserApiEndpoint'
placeholder={formatMessage(holders.userExample)}
defaultValue={this.props.config.GitLabSettings.UserApiEndpoint}
onChange={this.handleChange}
disabled={!this.state.Enable}
/>
<p className='help-text'>
<FormattedMessage
id='admin.gitlab.userDescription'
defaultMessage='Enter https://<your-gitlab-url>/api/v3/user. Make sure you use HTTP or HTTPS in your URL depending on your server configuration.'
/>
</p>
</div>
</div>
<div className='form-group'>
<div className='col-sm-12'>
{serverError}
<button
disabled={!this.state.saveNeeded}
type='submit'
className={saveClass}
onClick={this.handleSubmit}
id='save-button'
data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + formatMessage(holders.saving)}
>
<FormattedMessage
id='admin.gitlab.save'
defaultMessage='Save'
/>
</button>
</div>
</div>
</form>
</div>
); );
} }
} }
//config.GitLabSettings.Scope = ReactDOM.findDOMNode(this.refs.Scope).value.trim();
// <div className='form-group'>
// <label
// className='control-label col-sm-4'
// htmlFor='Scope'
// >
// {'Scope:'}
// </label>
// <div className='col-sm-8'>
// <input
// type='text'
// className='form-control'
// id='Scope'
// ref='Scope'
// placeholder='Not currently used by GitLab. Please leave blank'
// defaultValue={this.props.config.GitLabSettings.Scope}
// onChange={this.handleChange}
// disabled={!this.state.Allow}
// />
// <p className='help-text'>{'This field is not yet used by GitLab OAuth. Other OAuth providers may use this field to specify the scope of account data from OAuth provider that is sent to Mattermost.'}</p>
// </div>
// </div>
GitLabSettings.propTypes = {
intl: intlShape.isRequired,
config: React.PropTypes.object
};
export default injectIntl(GitLabSettings);

Просмотреть файл

@@ -1,692 +1,174 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
import $ from 'jquery';
import ReactDOM from 'react-dom';
import Client from 'utils/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import crypto from 'crypto';
import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl';
const holders = defineMessages({
storeLocal: {
id: 'admin.image.storeLocal',
defaultMessage: 'Local File System'
},
storeAmazonS3: {
id: 'admin.image.storeAmazonS3',
defaultMessage: 'Amazon S3'
},
localExample: {
id: 'admin.image.localExample',
defaultMessage: 'Ex "./data/"'
},
amazonS3IdExample: {
id: 'admin.image.amazonS3IdExample',
defaultMessage: 'Ex "AKIADTOVBGERKLCBV"'
},
amazonS3SecretExample: {
id: 'admin.image.amazonS3SecretExample',
defaultMessage: 'Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"'
},
amazonS3BucketExample: {
id: 'admin.image.amazonS3BucketExample',
defaultMessage: 'Ex "mattermost-media"'
},
amazonS3RegionExample: {
id: 'admin.image.amazonS3RegionExample',
defaultMessage: 'Ex "us-east-1"'
},
thumbWidthExample: {
id: 'admin.image.thumbWidthExample',
defaultMessage: 'Ex "120"'
},
thumbHeightExample: {
id: 'admin.image.thumbHeightExample',
defaultMessage: 'Ex "100"'
},
previewWidthExample: {
id: 'admin.image.previewWidthExample',
defaultMessage: 'Ex "1024"'
},
previewHeightExample: {
id: 'admin.image.previewHeightExample',
defaultMessage: 'Ex "0"'
},
profileWidthExample: {
id: 'admin.image.profileWidthExample',
defaultMessage: 'Ex "1024"'
},
profileHeightExample: {
id: 'admin.image.profileHeightExample',
defaultMessage: 'Ex "0"'
},
publicLinkExample: {
id: 'admin.image.publicLinkExample',
defaultMessage: 'Ex "gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6"'
},
saving: {
id: 'admin.image.saving',
defaultMessage: 'Saving Config...'
}
});
import React from 'react'; import React from 'react';
class FileSettings extends React.Component { import * as Utils from 'utils/utils.jsx';
import AdminSettings from './admin_settings.jsx';
import {FormattedMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
export default class ImageSettings extends AdminSettings {
constructor(props) { constructor(props) {
super(props); super(props);
this.handleChange = this.handleChange.bind(this); this.getConfigFromState = this.getConfigFromState.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleGenerate = this.handleGenerate.bind(this);
this.state = { this.renderSettings = this.renderSettings.bind(this);
saveNeeded: false,
serverError: null, this.state = Object.assign(this.state, {
DriverName: this.props.config.FileSettings.DriverName thumbnailWidth: props.config.FileSettings.ThumbnailWidth,
}; thumbnailHeight: props.config.FileSettings.ThumbnailHeight,
profileWidth: props.config.FileSettings.ProfileWidth,
profileHeight: props.config.FileSettings.ProfileHeight,
previewWidth: props.config.FileSettings.PreviewWidth,
previewHeight: props.config.FileSettings.PreviewHeight
});
} }
handleChange(action) { getConfigFromState(config) {
var s = {saveNeeded: true, serverError: this.state.serverError}; config.FileSettings.ThumbnailWidth = this.parseInt(this.state.thumbnailWidth);
config.FileSettings.ThumbnailHeight = this.parseInt(this.state.thumbnailHeight);
config.FileSettings.ProfileWidth = this.parseInt(this.state.profileWidth);
config.FileSettings.ProfileHeight = this.parseInt(this.state.profileHeight);
config.FileSettings.PreviewWidth = this.parseInt(this.state.previewWidth);
config.FileSettings.PreviewHeight = this.parseInt(this.state.previewHeight);
if (action === 'DriverName') { return config;
s.DriverName = ReactDOM.findDOMNode(this.refs.DriverName).value;
}
this.setState(s);
} }
handleGenerate(e) { renderTitle() {
e.preventDefault(); return (
ReactDOM.findDOMNode(this.refs.PublicLinkSalt).value = crypto.randomBytes(256).toString('base64').substring(0, 32); <h3>
var s = {saveNeeded: true, serverError: this.state.serverError}; <FormattedMessage
this.setState(s); id='admin.files.title'
} defaultMessage='File Settings'
/>
handleSubmit(e) { </h3>
e.preventDefault();
$('#save-button').button('loading');
var config = this.props.config;
config.FileSettings.DriverName = ReactDOM.findDOMNode(this.refs.DriverName).value;
config.FileSettings.Directory = ReactDOM.findDOMNode(this.refs.Directory).value;
config.FileSettings.AmazonS3AccessKeyId = ReactDOM.findDOMNode(this.refs.AmazonS3AccessKeyId).value;
config.FileSettings.AmazonS3SecretAccessKey = ReactDOM.findDOMNode(this.refs.AmazonS3SecretAccessKey).value;
config.FileSettings.AmazonS3Bucket = ReactDOM.findDOMNode(this.refs.AmazonS3Bucket).value;
config.FileSettings.AmazonS3Region = ReactDOM.findDOMNode(this.refs.AmazonS3Region).value;
config.FileSettings.EnablePublicLink = ReactDOM.findDOMNode(this.refs.EnablePublicLink).checked;
config.FileSettings.PublicLinkSalt = ReactDOM.findDOMNode(this.refs.PublicLinkSalt).value.trim();
if (config.FileSettings.PublicLinkSalt === '') {
config.FileSettings.PublicLinkSalt = crypto.randomBytes(256).toString('base64').substring(0, 32);
ReactDOM.findDOMNode(this.refs.PublicLinkSalt).value = config.FileSettings.PublicLinkSalt;
}
var thumbnailWidth = 120;
if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.ThumbnailWidth).value, 10))) {
thumbnailWidth = parseInt(ReactDOM.findDOMNode(this.refs.ThumbnailWidth).value, 10);
}
config.FileSettings.ThumbnailWidth = thumbnailWidth;
ReactDOM.findDOMNode(this.refs.ThumbnailWidth).value = thumbnailWidth;
var thumbnailHeight = 100;
if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.ThumbnailHeight).value, 10))) {
thumbnailHeight = parseInt(ReactDOM.findDOMNode(this.refs.ThumbnailHeight).value, 10);
}
config.FileSettings.ThumbnailHeight = thumbnailHeight;
ReactDOM.findDOMNode(this.refs.ThumbnailHeight).value = thumbnailHeight;
var previewWidth = 1024;
if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.PreviewWidth).value, 10))) {
previewWidth = parseInt(ReactDOM.findDOMNode(this.refs.PreviewWidth).value, 10);
}
config.FileSettings.PreviewWidth = previewWidth;
ReactDOM.findDOMNode(this.refs.PreviewWidth).value = previewWidth;
var previewHeight = 0;
if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.PreviewHeight).value, 10))) {
previewHeight = parseInt(ReactDOM.findDOMNode(this.refs.PreviewHeight).value, 10);
}
config.FileSettings.PreviewHeight = previewHeight;
ReactDOM.findDOMNode(this.refs.PreviewHeight).value = previewHeight;
var profileWidth = 128;
if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.ProfileWidth).value, 10))) {
profileWidth = parseInt(ReactDOM.findDOMNode(this.refs.ProfileWidth).value, 10);
}
config.FileSettings.ProfileWidth = profileWidth;
ReactDOM.findDOMNode(this.refs.ProfileWidth).value = profileWidth;
var profileHeight = 128;
if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.ProfileHeight).value, 10))) {
profileHeight = parseInt(ReactDOM.findDOMNode(this.refs.ProfileHeight).value, 10);
}
config.FileSettings.ProfileHeight = profileHeight;
ReactDOM.findDOMNode(this.refs.ProfileHeight).value = profileHeight;
Client.saveConfig(
config,
() => {
AsyncClient.getConfig();
this.setState({
serverError: null,
saveNeeded: false
});
$('#save-button').button('reset');
},
(err) => {
this.setState({
serverError: err.message,
saveNeeded: true
});
$('#save-button').button('reset');
}
); );
} }
render() { renderSettings() {
const {formatMessage} = this.props.intl;
var serverError = '';
if (this.state.serverError) {
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
}
var saveClass = 'btn';
if (this.state.saveNeeded) {
saveClass = 'btn btn-primary';
}
var enableFile = false;
var enableS3 = false;
if (this.state.DriverName === 'local') {
enableFile = true;
}
if (this.state.DriverName === 'amazons3') {
enableS3 = true;
}
return ( return (
<div className='wrapper--fixed'> <SettingsGroup
<h3> header={
<FormattedMessage <FormattedMessage
id='admin.image.fileSettings' id='admin.files.images'
defaultMessage='File Settings' defaultMessage='Images'
/> />
</h3> }
<form >
className='form-horizontal' <TextSetting
role='form' id='thumbnailWidth'
> label={
<FormattedMessage
<div className='form-group'> id='admin.image.thumbWidthTitle'
<label defaultMessage='Thumbnail Width:'
className='control-label col-sm-4' />
htmlFor='DriverName' }
> placeholder={Utils.localizeMessage('admin.image.thumbWidthExample', 'Ex "120"')}
<FormattedMessage helpText={
id='admin.image.storeTitle' <FormattedMessage
defaultMessage='Store Files In:' id='admin.image.thumbWidthDescription'
/> defaultMessage='Width of thumbnails generated from uploaded images. Updating this value changes how thumbnail images render in future, but does not change images created in the past.'
</label> />
<div className='col-sm-8'> }
<select value={this.state.thumbnailWidth}
className='form-control' onChange={this.handleChange}
id='DriverName' />
ref='DriverName' <TextSetting
defaultValue={this.props.config.FileSettings.DriverName} id='thumbnailHeight'
onChange={this.handleChange.bind(this, 'DriverName')} label={
> <FormattedMessage
<option value='local'>{formatMessage(holders.storeLocal)}</option> id='admin.image.thumbHeightTitle'
<option value='amazons3'>{formatMessage(holders.storeAmazonS3)}</option> defaultMessage='Thumbnail Height:'
</select> />
</div> }
</div> placeholder={Utils.localizeMessage('admin.image.thumbHeightExample', 'Ex "100"')}
helpText={
<div className='form-group'> <FormattedMessage
<label id='admin.image.thumbHeightDescription'
className='control-label col-sm-4' defaultMessage='Height of thumbnails generated from uploaded images. Updating this value changes how thumbnail images render in future, but does not change images created in the past.'
htmlFor='Directory' />
> }
<FormattedMessage value={this.state.thumbnailHeight}
id='admin.image.localTitle' onChange={this.handleChange}
defaultMessage='Local Directory Location:' />
/> <TextSetting
</label> id='profileWidth'
<div className='col-sm-8'> label={
<input <FormattedMessage
type='text' id='admin.image.profileWidthTitle'
className='form-control' defaultMessage='Profile Width:'
id='Directory' />
ref='Directory' }
placeholder={formatMessage(holders.localExample)} placeholder={Utils.localizeMessage('admin.image.profileWidthExample', 'Ex "1024"')}
defaultValue={this.props.config.FileSettings.Directory} helpText={
onChange={this.handleChange} <FormattedMessage
disabled={!enableFile} id='admin.image.profileWidthDescription'
/> defaultMessage='Width of profile picture.'
<p className='help-text'> />
<FormattedMessage }
id='admin.image.localDescription' value={this.state.profileWidth}
defaultMessage='Directory to which image files are written. If blank, will be set to ./data/.' onChange={this.handleChange}
/> />
</p> <TextSetting
</div> id='profileHeight'
</div> label={
<FormattedMessage
<div className='form-group'> id='admin.image.profileHeightTitle'
<label defaultMessage='Profile Height:'
className='control-label col-sm-4' />
htmlFor='AmazonS3AccessKeyId' }
> placeholder={Utils.localizeMessage('admin.image.profileHeightExample', 'Ex "0"')}
<FormattedMessage helpText={
id='admin.image.amazonS3IdTitle' <FormattedMessage
defaultMessage='Amazon S3 Access Key Id:' id='admin.image.profileHeightDescription'
/> defaultMessage='Height of profile picture.'
</label> />
<div className='col-sm-8'> }
<input value={this.state.profileHeight}
type='text' onChange={this.handleChange}
className='form-control' />
id='AmazonS3AccessKeyId' <TextSetting
ref='AmazonS3AccessKeyId' id='previewWidth'
placeholder={formatMessage(holders.amazonS3IdExample)} label={
defaultValue={this.props.config.FileSettings.AmazonS3AccessKeyId} <FormattedMessage
onChange={this.handleChange} id='admin.image.previewWidthTitle'
disabled={!enableS3} defaultMessage='Preview Width:'
/> />
<p className='help-text'> }
<FormattedMessage placeholder={Utils.localizeMessage('admin.image.previewWidthExample', 'Ex "1024"')}
id='admin.image.amazonS3IdDescription' helpText={
defaultMessage='Obtain this credential from your Amazon EC2 administrator.' <FormattedMessage
/> id='admin.image.previewWidthDescription'
</p> defaultMessage='Maximum width of preview image. Updating this value changes how preview images render in future, but does not change images created in the past.'
</div> />
</div> }
value={this.state.previewWidth}
<div className='form-group'> onChange={this.handleChange}
<label />
className='control-label col-sm-4' <TextSetting
htmlFor='AmazonS3SecretAccessKey' id='previewHeight'
> label={
<FormattedMessage <FormattedMessage
id='admin.image.amazonS3SecretTitle' id='admin.image.previewHeightTitle'
defaultMessage='Amazon S3 Secret Access Key:' defaultMessage='Preview Height:'
/> />
</label> }
<div className='col-sm-8'> placeholder={Utils.localizeMessage('admin.image.previewHeightExample', 'Ex "0"')}
<input helpText={
type='text' <FormattedMessage
className='form-control' id='admin.image.previewHeightDescription'
id='AmazonS3SecretAccessKey' defaultMessage='Maximum height of preview image ("0": Sets to auto-size). Updating this value changes how preview images render in future, but does not change images created in the past.'
ref='AmazonS3SecretAccessKey' />
placeholder={formatMessage(holders.amazonS3SecretExample)} }
defaultValue={this.props.config.FileSettings.AmazonS3SecretAccessKey} value={this.state.previewHeight}
onChange={this.handleChange} onChange={this.handleChange}
disabled={!enableS3} />
/> </SettingsGroup>
<p className='help-text'>
<FormattedMessage
id='admin.image.amazonS3SecretDescription'
defaultMessage='Obtain this credential from your Amazon EC2 administrator.'
/>
</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='AmazonS3Bucket'
>
<FormattedMessage
id='admin.image.amazonS3BucketTitle'
defaultMessage='Amazon S3 Bucket:'
/>
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='AmazonS3Bucket'
ref='AmazonS3Bucket'
placeholder={formatMessage(holders.amazonS3BucketExample)}
defaultValue={this.props.config.FileSettings.AmazonS3Bucket}
onChange={this.handleChange}
disabled={!enableS3}
/>
<p className='help-text'>
<FormattedMessage
id='admin.image.amazonS3BucketDescription'
defaultMessage='Name you selected for your S3 bucket in AWS.'
/>
</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='AmazonS3Region'
>
<FormattedMessage
id='admin.image.amazonS3RegionTitle'
defaultMessage='Amazon S3 Region:'
/>
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='AmazonS3Region'
ref='AmazonS3Region'
placeholder={formatMessage(holders.amazonS3RegionExample)}
defaultValue={this.props.config.FileSettings.AmazonS3Region}
onChange={this.handleChange}
disabled={!enableS3}
/>
<p className='help-text'>
<FormattedMessage
id='admin.image.amazonS3RegionDescription'
defaultMessage='AWS region you selected for creating your S3 bucket.'
/>
</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='ThumbnailWidth'
>
<FormattedMessage
id='admin.image.thumbWidthTitle'
defaultMessage='Thumbnail Width:'
/>
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='ThumbnailWidth'
ref='ThumbnailWidth'
placeholder={formatMessage(holders.thumbWidthExample)}
defaultValue={this.props.config.FileSettings.ThumbnailWidth}
onChange={this.handleChange}
/>
<p className='help-text'>
<FormattedMessage
id='admin.image.thumbWidthDescription'
defaultMessage='Width of thumbnails generated from uploaded images. Updating this value changes how thumbnail images render in future, but does not change images created in the past.'
/>
</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='ThumbnailHeight'
>
<FormattedMessage
id='admin.image.thumbHeightTitle'
defaultMessage='Thumbnail Height:'
/>
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='ThumbnailHeight'
ref='ThumbnailHeight'
placeholder={formatMessage(holders.thumbHeightExample)}
defaultValue={this.props.config.FileSettings.ThumbnailHeight}
onChange={this.handleChange}
/>
<p className='help-text'>
<FormattedMessage
id='admin.image.thumbHeightDescription'
defaultMessage='Height of thumbnails generated from uploaded images. Updating this value changes how thumbnail images render in future, but does not change images created in the past.'
/>
</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='PreviewWidth'
>
<FormattedMessage
id='admin.image.previewWidthTitle'
defaultMessage='Preview Width:'
/>
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='PreviewWidth'
ref='PreviewWidth'
placeholder={formatMessage(holders.previewWidthExample)}
defaultValue={this.props.config.FileSettings.PreviewWidth}
onChange={this.handleChange}
/>
<p className='help-text'>
<FormattedMessage
id='admin.image.previewWidthDescription'
defaultMessage='Maximum width of preview image. Updating this value changes how preview images render in future, but does not change images created in the past.'
/>
</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='PreviewHeight'
>
<FormattedMessage
id='admin.image.previewHeightTitle'
defaultMessage='Preview Height:'
/>
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='PreviewHeight'
ref='PreviewHeight'
placeholder={formatMessage(holders.previewHeightExample)}
defaultValue={this.props.config.FileSettings.PreviewHeight}
onChange={this.handleChange}
/>
<p className='help-text'>
<FormattedMessage
id='admin.image.previewHeightDescription'
defaultMessage='Maximum height of preview image ("0": Sets to auto-size). Updating this value changes how preview images render in future, but does not change images created in the past.'
/>
</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='ProfileWidth'
>
<FormattedMessage
id='admin.image.profileWidthTitle'
defaultMessage='Profile Width:'
/>
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='ProfileWidth'
ref='ProfileWidth'
placeholder={formatMessage(holders.profileWidthExample)}
defaultValue={this.props.config.FileSettings.ProfileWidth}
onChange={this.handleChange}
/>
<p className='help-text'>
<FormattedMessage
id='admin.image.profileWidthDescription'
defaultMessage='Width of profile picture.'
/>
</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='ProfileHeight'
>
<FormattedMessage
id='admin.image.profileHeightTitle'
defaultMessage='Profile Height:'
/>
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='ProfileHeight'
ref='ProfileHeight'
placeholder={formatMessage(holders.profileHeightExample)}
defaultValue={this.props.config.FileSettings.ProfileHeight}
onChange={this.handleChange}
/>
<p className='help-text'>
<FormattedMessage
id='admin.image.profileHeightDescription'
defaultMessage='Height of profile picture.'
/>
</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='EnablePublicLink'
>
<FormattedMessage
id='admin.image.shareTitle'
defaultMessage='Share Public File Link: '
/>
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
<input
type='radio'
name='EnablePublicLink'
value='true'
ref='EnablePublicLink'
defaultChecked={this.props.config.FileSettings.EnablePublicLink}
onChange={this.handleChange}
/>
<FormattedMessage
id='admin.image.true'
defaultMessage='true'
/>
</label>
<label className='radio-inline'>
<input
type='radio'
name='EnablePublicLink'
value='false'
defaultChecked={!this.props.config.FileSettings.EnablePublicLink}
onChange={this.handleChange}
/>
<FormattedMessage
id='admin.image.false'
defaultMessage='false'
/>
</label>
<p className='help-text'>
<FormattedMessage
id='admin.image.shareDescription'
defaultMessage='Allow users to share public links to files and images.'
/>
</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='PublicLinkSalt'
>
<FormattedMessage
id='admin.image.publicLinkTitle'
defaultMessage='Public Link Salt:'
/>
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='PublicLinkSalt'
ref='PublicLinkSalt'
placeholder={formatMessage(holders.publicLinkExample)}
defaultValue={this.props.config.FileSettings.PublicLinkSalt}
onChange={this.handleChange}
/>
<p className='help-text'>
<FormattedMessage
id='admin.image.publicLinkDescription'
defaultMessage='32-character salt added to signing of public image links. Randomly generated on install. Click "Re-Generate" to create new salt.'
/>
</p>
<div className='help-text'>
<button
className='btn btn-default'
onClick={this.handleGenerate}
>
<FormattedMessage
id='admin.image.regenerate'
defaultMessage='Re-Generate'
/>
</button>
</div>
</div>
</div>
<div className='form-group'>
<div className='col-sm-12'>
{serverError}
<button
disabled={!this.state.saveNeeded}
type='submit'
className={saveClass}
onClick={this.handleSubmit}
id='save-button'
data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + formatMessage(holders.saving)}
>
<FormattedMessage
id='admin.image.save'
defaultMessage='Save'
/>
</button>
</div>
</div>
</form>
</div>
); );
} }
} }
FileSettings.propTypes = {
intl: intlShape.isRequired,
config: React.PropTypes.object
};
export default injectIntl(FileSettings);

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@@ -1,309 +1,166 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
import $ from 'jquery';
import ReactDOM from 'react-dom';
import Client from 'utils/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl';
var holders = defineMessages({
saving: {
id: 'admin.support.saving',
defaultMessage: 'Saving Config...'
}
});
import React from 'react'; import React from 'react';
class LegalAndSupportSettings extends React.Component { import AdminSettings from './admin_settings.jsx';
import {FormattedMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
export default class LegalAndSupportSettings extends AdminSettings {
constructor(props) { constructor(props) {
super(props); super(props);
this.handleChange = this.handleChange.bind(this); this.getConfigFromState = this.getConfigFromState.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.state = { this.renderSettings = this.renderSettings.bind(this);
saveNeeded: false,
serverError: null this.state = Object.assign(this.state, {
}; termsOfServiceLink: props.config.SupportSettings.TermsOfServiceLink,
privacyPolicyLink: props.config.SupportSettings.PrivacyPolicyLink,
aboutLink: props.config.SupportSettings.AboutLink,
helpLink: props.config.SupportSettings.HelpLink,
reportAProblemLink: props.config.SupportSettings.ReportAProblemLink,
supportEmail: props.config.SupportSettings.SupportEmail
});
} }
handleChange() { getConfigFromState(config) {
var s = {saveNeeded: true, serverError: this.state.serverError}; config.SupportSettings.TermsOfServiceLink = this.state.termsOfServiceLink;
this.setState(s); config.SupportSettings.PrivacyPolicyLink = this.state.privacyPolicyLink;
config.SupportSettings.AboutLink = this.state.aboutLink;
config.SupportSettings.HelpLink = this.state.helpLink;
config.SupportSettings.ReportAProblemLink = this.state.reportAProblemLink;
config.SupportSettings.SupportEmail = this.state.supportEmail;
return config;
} }
handleSubmit(e) { renderTitle() {
e.preventDefault(); return (
$('#save-button').button('loading'); <h3>
<FormattedMessage
var config = this.props.config; id='admin.customization.title'
defaultMessage='Customization Settings'
config.SupportSettings.TermsOfServiceLink = ReactDOM.findDOMNode(this.refs.TermsOfServiceLink).value.trim(); />
config.SupportSettings.PrivacyPolicyLink = ReactDOM.findDOMNode(this.refs.PrivacyPolicyLink).value.trim(); </h3>
config.SupportSettings.AboutLink = ReactDOM.findDOMNode(this.refs.AboutLink).value.trim();
config.SupportSettings.HelpLink = ReactDOM.findDOMNode(this.refs.HelpLink).value.trim();
config.SupportSettings.ReportAProblemLink = ReactDOM.findDOMNode(this.refs.ReportAProblemLink).value.trim();
config.SupportSettings.SupportEmail = ReactDOM.findDOMNode(this.refs.SupportEmail).value.trim();
Client.saveConfig(
config,
() => {
AsyncClient.getConfig();
this.setState({
serverError: null,
saveNeeded: false
});
$('#save-button').button('reset');
},
(err) => {
this.setState({
serverError: err.message,
saveNeeded: true
});
$('#save-button').button('reset');
}
); );
} }
render() { renderSettings() {
var serverError = '';
if (this.state.serverError) {
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
}
var saveClass = 'btn';
if (this.state.saveNeeded) {
saveClass = 'btn btn-primary';
}
return ( return (
<div className='wrapper--fixed'> <SettingsGroup
<div className='banner'> header={
<div className='banner__content'>
<h4 className='banner__heading'>
<FormattedMessage
id='admin.support.noteTitle'
defaultMessage='Note:'
/>
</h4>
<p>
<FormattedMessage
id='admin.support.noteDescription'
defaultMessage='If linking to an external site, URLs should begin with http:// or https://.'
/>
</p>
</div>
</div>
<h3>
<FormattedMessage <FormattedMessage
id='admin.support.title' id='admin.customization.support'
defaultMessage='Legal and Support Settings' defaultMessage='Legal and Support'
/> />
</h3> }
<form >
className='form-horizontal' <TextSetting
role='form' id='termsOfServiceLink'
> label={
<FormattedMessage
<div className='form-group'> id='admin.support.termsTitle'
<label defaultMessage='Terms of Service link:'
className='control-label col-sm-4' />
htmlFor='TermsOfServiceLink' }
> helpText={
<FormattedMessage <FormattedMessage
id='admin.support.termsTitle' id='admin.support.termsDesc'
defaultMessage='Terms of Service link:' defaultMessage='Link to Terms of Service available to users on desktop and on mobile. Leaving this blank will hide the option to display a notice.'
/> />
</label> }
<div className='col-sm-8'> value={this.state.termsOfServiceLink}
<input onChange={this.handleChange}
type='text' />
className='form-control' <TextSetting
id='TermsOfServiceLink' id='privacyPolicyLink'
ref='TermsOfServiceLink' label={
defaultValue={this.props.config.SupportSettings.TermsOfServiceLink} <FormattedMessage
onChange={this.handleChange} id='admin.support.privacyTitle'
/> defaultMessage='Privacy Policy link:'
<p className='help-text'> />
<FormattedMessage }
id='admin.support.termsDesc' helpText={
defaultMessage='Link to Terms of Service available to users on desktop and on mobile. Leaving this blank will hide the option to display a notice.' <FormattedMessage
/> id='admin.support.privacyDesc'
</p> defaultMessage='Link to Privacy Policy available to users on desktop and on mobile. Leaving this blank will hide the option to display a notice.'
</div> />
</div> }
value={this.state.privacyPolicyLink}
<div className='form-group'> onChange={this.handleChange}
<label />
className='control-label col-sm-4' <TextSetting
htmlFor='PrivacyPolicyLink' id='aboutLink'
> label={
<FormattedMessage <FormattedMessage
id='admin.support.privacyTitle' id='admin.support.aboutTitle'
defaultMessage='Privacy Policy link:' defaultMessage='About link:'
/> />
</label> }
<div className='col-sm-8'> helpText={
<input <FormattedMessage
type='text' id='admin.support.aboutDesc'
className='form-control' defaultMessage='Link to About page for more information on your Mattermost deployment, for example its purpose and audience within your organization. Defaults to Mattermost information page.'
id='PrivacyPolicyLink' />
ref='PrivacyPolicyLink' }
defaultValue={this.props.config.SupportSettings.PrivacyPolicyLink} value={this.state.aboutLink}
onChange={this.handleChange} onChange={this.handleChange}
/> />
<p className='help-text'> <TextSetting
<FormattedMessage id='helpLink'
id='admin.support.privacyDesc' label={
defaultMessage='Link to Privacy Policy available to users on desktop and on mobile. Leaving this blank will hide the option to display a notice.' <FormattedMessage
/> id='admin.support.helpTitle'
</p> defaultMessage='Help link:'
</div> />
</div> }
helpText={
<div className='form-group'> <FormattedMessage
<label id='admin.support.helpDesc'
className='control-label col-sm-4' defaultMessage='Link to help documentation from team site main menu. Typically not changed unless your organization chooses to create custom documentation.'
htmlFor='AboutLink' />
> }
<FormattedMessage value={this.state.helpLink}
id='admin.support.aboutTitle' onChange={this.handleChange}
defaultMessage='About link:' />
/> <TextSetting
</label> id='reportAProblemLink'
<div className='col-sm-8'> label={
<input <FormattedMessage
type='text' id='admin.support.problemTitle'
className='form-control' defaultMessage='Report a Problem link:'
id='AboutLink' />
ref='AboutLink' }
defaultValue={this.props.config.SupportSettings.AboutLink} helpText={
onChange={this.handleChange} <FormattedMessage
/> id='admin.support.problemDesc'
<p className='help-text'> defaultMessage='Link to help documentation from team site main menu. By default this points to the peer-to-peer troubleshooting forum where users can search for, find and request help with technical issues.'
<FormattedMessage />
id='admin.support.aboutDesc' }
defaultMessage='Link to About page for more information on your Mattermost deployment, for example its purpose and audience within your organization. Defaults to Mattermost information page.' value={this.state.reportAProblemLink}
/> onChange={this.handleChange}
</p> />
</div> <TextSetting
</div> id='supportEmail'
label={
<div className='form-group'> <FormattedMessage
<label id='admin.support.emailTitle'
className='control-label col-sm-4' defaultMessage='Support email:'
htmlFor='HelpLink' />
> }
<FormattedMessage helpText={
id='admin.support.helpTitle' <FormattedMessage
defaultMessage='Help link:' id='admin.support.emailHelp'
/> defaultMessage='Email shown during tutorial for end users to ask support questions.'
</label> />
<div className='col-sm-8'> }
<input value={this.state.supportEmail}
type='text' onChange={this.handleChange}
className='form-control' />
id='HelpLink' </SettingsGroup>
ref='HelpLink'
defaultValue={this.props.config.SupportSettings.HelpLink}
onChange={this.handleChange}
/>
<p className='help-text'>
<FormattedMessage
id='admin.support.helpDesc'
defaultMessage='Link to help documentation from team site main menu. Typically not changed unless your organization chooses to create custom documentation.'
/>
</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='ReportAProblemLink'
>
<FormattedMessage
id='admin.support.problemTitle'
defaultMessage='Report a Problem link:'
/>
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='ReportAProblemLink'
ref='ReportAProblemLink'
defaultValue={this.props.config.SupportSettings.ReportAProblemLink}
onChange={this.handleChange}
/>
<p className='help-text'>
<FormattedMessage
id='admin.support.problemDesc'
defaultMessage='Link to help documentation from team site main menu. By default this points to the peer-to-peer troubleshooting forum where users can search for, find and request help with technical issues.'
/>
</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='SupportEmail'
>
<FormattedMessage
id='admin.support.emailTitle'
defaultMessage='Support email:'
/>
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='SupportEmail'
ref='SupportEmail'
defaultValue={this.props.config.SupportSettings.SupportEmail}
onChange={this.handleChange}
/>
<p className='help-text'>
<FormattedMessage
id='admin.support.emailHelp'
defaultMessage='Email shown during tutorial for end users to ask support questions.'
/>
</p>
</div>
</div>
<div className='form-group'>
<div className='col-sm-12'>
{serverError}
<button
disabled={!this.state.saveNeeded}
type='submit'
className={saveClass}
onClick={this.handleSubmit}
id='save-button'
data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + this.props.intl.formatMessage(holders.saving)}
>
<FormattedMessage
id='admin.support.save'
defaultMessage='Save'
/>
</button>
</div>
</div>
</form>
</div>
); );
} }
} }
LegalAndSupportSettings.propTypes = {
intl: intlShape.isRequired,
config: React.PropTypes.object
};
export default injectIntl(LegalAndSupportSettings);

Просмотреть файл

@@ -1,418 +1,246 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
import $ from 'jquery';
import ReactDOM from 'react-dom';
import Client from 'utils/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl';
const holders = defineMessages({
locationPlaceholder: {
id: 'admin.log.locationPlaceholder',
defaultMessage: 'Enter your file location'
},
formatPlaceholder: {
id: 'admin.log.formatPlaceholder',
defaultMessage: 'Enter your file format'
},
saving: {
id: 'admin.log.saving',
defaultMessage: 'Saving Config...'
}
});
import React from 'react'; import React from 'react';
class LogSettings extends React.Component { import * as Utils from 'utils/utils.jsx';
import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import DropdownSetting from './dropdown_setting.jsx';
import {FormattedMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
export default class LogSettings extends AdminSettings {
constructor(props) { constructor(props) {
super(props); super(props);
this.handleChange = this.handleChange.bind(this); this.getConfigFromState = this.getConfigFromState.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.state = { this.renderSettings = this.renderSettings.bind(this);
consoleEnable: this.props.config.LogSettings.EnableConsole,
fileEnable: this.props.config.LogSettings.EnableFile, this.state = Object.assign(this.state, {
saveNeeded: false, enableConsole: props.config.LogSettings.EnableConsole,
serverError: null consoleLevel: props.config.LogSettings.ConsoleLevel,
}; enableFile: props.config.LogSettings.EnableFile,
fileLevel: props.config.LogSettings.FileLevel,
fileLocation: props.config.LogSettings.FileLocation,
fileFormat: props.config.LogSettings.FileFormat
});
} }
handleChange(action) { getConfigFromState(config) {
var s = {saveNeeded: true, serverError: this.state.serverError}; config.LogSettings.EnableConsole = this.state.enableConsole;
config.LogSettings.ConsoleLevel = this.state.consoleLevel;
config.LogSettings.EnableFile = this.state.enableFile;
config.LogSettings.FileLevel = this.state.fileLevel;
config.LogSettings.FileLocation = this.state.fileLocation;
config.LogSettings.FileFormat = this.state.fileFormat;
if (action === 'console_true') { return config;
s.consoleEnable = true;
}
if (action === 'console_false') {
s.consoleEnable = false;
}
if (action === 'file_true') {
s.fileEnable = true;
}
if (action === 'file_false') {
s.fileEnable = false;
}
this.setState(s);
} }
handleSubmit(e) { renderTitle() {
e.preventDefault(); return (
$('#save-button').button('loading'); <h3>
<FormattedMessage
var config = this.props.config; id='admin.general.title'
config.LogSettings.EnableConsole = ReactDOM.findDOMNode(this.refs.consoleEnable).checked; defaultMessage='General Settings'
config.LogSettings.ConsoleLevel = ReactDOM.findDOMNode(this.refs.consoleLevel).value; />
config.LogSettings.EnableFile = ReactDOM.findDOMNode(this.refs.fileEnable).checked; </h3>
config.LogSettings.FileLevel = ReactDOM.findDOMNode(this.refs.fileLevel).value;
config.LogSettings.FileLocation = ReactDOM.findDOMNode(this.refs.fileLocation).value.trim();
config.LogSettings.FileFormat = ReactDOM.findDOMNode(this.refs.fileFormat).value.trim();
Client.saveConfig(
config,
() => {
AsyncClient.getConfig();
this.setState({
consoleEnable: config.LogSettings.EnableConsole,
fileEnable: config.LogSettings.EnableFile,
serverError: null,
saveNeeded: false
});
$('#save-button').button('reset');
},
(err) => {
this.setState({
consoleEnable: config.LogSettings.EnableConsole,
fileEnable: config.LogSettings.EnableFile,
serverError: err.message,
saveNeeded: true
});
$('#save-button').button('reset');
}
); );
} }
render() { renderSettings() {
const {formatMessage} = this.props.intl; const logLevels = [
var serverError = ''; {value: 'DEBUG', text: 'DEBUG'},
if (this.state.serverError) { {value: 'INFO', text: 'INFO'},
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>; {value: 'ERROR', text: 'ERROR'}
} ];
var saveClass = 'btn';
if (this.state.saveNeeded) {
saveClass = 'btn btn-primary';
}
return ( return (
<div className='wrapper--fixed'> <SettingsGroup
<h3> header={
<FormattedMessage <FormattedMessage
id='admin.log.logSettings' id='admin.general.log'
defaultMessage='Log Settings' defaultMessage='Logging'
/> />
</h3>
<form }
className='form-horizontal' >
role='form' <BooleanSetting
id='enableConsole'
label={
<FormattedMessage
id='admin.log.consoleTitle'
defaultMessage='Log To The Console: '
/>
}
helpText={
<FormattedMessage
id='admin.log.consoleDescription'
defaultMessage='Typically set to false in production. Developers may set this field to true to output log messages to console based on the console level option. If true, server writes messages to the standard output stream (stdout).'
/>
}
value={this.state.enableConsole}
onChange={this.handleChange}
/>
<DropdownSetting
id='consoleLevel'
values={logLevels}
label={
<FormattedMessage
id='admin.log.levelTitle'
defaultMessage='Console Log Level:'
/>
}
value={this.state.consoleLevel}
onChange={this.handleChange}
disabled={!this.state.enableConsole}
helpText={
<FormattedMessage
id='admin.log.levelDescription'
defaultMessage='This setting determines the level of detail at which log events are written to the console. ERROR: Outputs only error messages. INFO: Outputs error messages and information around startup and initialization. DEBUG: Prints high detail for developers working on debugging issues.'
/>
}
/>
<BooleanSetting
id='enableFile'
label={
<FormattedMessage
id='admin.log.fileTitle'
defaultMessage='Log To File: '
/>
}
helpText={
<FormattedMessage
id='admin.log.fileDescription'
defaultMessage='Typically set to true in production. When true, log files are written to the log file specified in file location field below.'
/>
}
value={this.state.enableFile}
onChange={this.handleChange}
/>
<DropdownSetting
id='fileLevel'
values={logLevels}
label={
<FormattedMessage
id='admin.log.fileLevelTitle'
defaultMessage='File Log Level:'
/>
}
value={this.state.fileLevel}
onChange={this.handleChange}
disabled={!this.state.enableFile}
helpText={
<FormattedMessage
id='admin.log.fileLevelDescription'
defaultMessage='This setting determines the level of detail at which log events are written to the log file. ERROR: Outputs only error messages. INFO: Outputs error messages and information around startup and initialization. DEBUG: Prints high detail for developers working on debugging issues.'
/>
}
/>
<TextSetting
id='fileLocation'
label={
<FormattedMessage
id='admin.log.locationTitle'
defaultMessage='File Location:'
/>
}
placeholder={Utils.localizeMessage('admin.log.locationPlaceholder', 'Enter your file location')}
helpText={
<FormattedMessage
id='admin.log.locationDescription'
defaultMessage='File to which log files are written. If blank, will be set to ./logs/mattermost, which writes logs to mattermost.log. Log rotation is enabled and every 10,000 lines of log information is written to new files stored in the same directory, for example mattermost.2015-09-23.001, mattermost.2015-09-23.002, and so forth.'
/>
}
value={this.state.fileLocation}
onChange={this.handleChange}
disabled={!this.state.enableFile}
/>
<TextSetting
id='fileFormat'
label={
<FormattedMessage
id='admin.log.formatTitle'
defaultMessage='File Format:'
/>
}
placeholder={Utils.localizeMessage('admin.log.formatPlaceholder', 'Enter your file format')}
helpText={this.renderFileFormatHelpText()}
value={this.state.fileFormat}
onChange={this.handleChange}
disabled={!this.state.enableFile}
/>
</SettingsGroup>
);
}
renderFileFormatHelpText() {
return (
<div>
<FormattedMessage
id='admin.log.formatDescription'
defaultMessage='Format of log message output. If blank will be set to "[%D %T] [%L] %M", where:'
/>
<table
className='table table-bordered'
cellPadding='5'
> >
<tbody>
<div className='form-group'> <tr>
<label <td className='help-text'>{'%T'}</td><td className='help-text'>
className='control-label col-sm-4'
htmlFor='consoleEnable'
>
<FormattedMessage
id='admin.log.consoleTitle'
defaultMessage='Log To The Console: '
/>
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
<input
type='radio'
name='consoleEnable'
value='true'
ref='consoleEnable'
defaultChecked={this.props.config.LogSettings.EnableConsole}
onChange={this.handleChange.bind(this, 'console_true')}
/>
<FormattedMessage <FormattedMessage
id='admin.log.true' id='admin.log.formatTime'
defaultMessage='true' defaultMessage='Time (15:04:05 MST)'
/>
</label>
<label className='radio-inline'>
<input
type='radio'
name='consoleEnable'
value='false'
defaultChecked={!this.props.config.LogSettings.EnableConsole}
onChange={this.handleChange.bind(this, 'console_false')}
/> />
</td>
</tr>
<tr>
<td className='help-text'>{'%D'}</td><td className='help-text'>
<FormattedMessage <FormattedMessage
id='admin.log.false' id='admin.log.formatDateLong'
defaultMessage='false' defaultMessage='Date (2006/01/02)'
/> />
</label> </td>
<p className='help-text'> </tr>
<tr>
<td className='help-text'>{'%d'}</td><td className='help-text'>
<FormattedMessage <FormattedMessage
id='admin.log.consoleDescription' id='admin.log.formatDateShort'
defaultMessage='Typically set to false in production. Developers may set this field to true to output log messages to console based on the console level option. If true, server writes messages to the standard output stream (stdout).' defaultMessage='Date (01/02/06)'
/> />
</p> </td>
</div> </tr>
</div> <tr>
<td className='help-text'>{'%L'}</td><td className='help-text'>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='consoleLevel'
>
<FormattedMessage
id='admin.log.levelTitle'
defaultMessage='Console Log Level:'
/>
</label>
<div className='col-sm-8'>
<select
className='form-control'
id='consoleLevel'
ref='consoleLevel'
defaultValue={this.props.config.LogSettings.consoleLevel}
onChange={this.handleChange}
disabled={!this.state.consoleEnable}
>
<option value='DEBUG'>{'DEBUG'}</option>
<option value='INFO'>{'INFO'}</option>
<option value='ERROR'>{'ERROR'}</option>
</select>
<p className='help-text'>
<FormattedMessage <FormattedMessage
id='admin.log.levelDescription' id='admin.log.formatLevel'
defaultMessage='This setting determines the level of detail at which log events are written to the console. ERROR: Outputs only error messages. INFO: Outputs error messages and information around startup and initialization. DEBUG: Prints high detail for developers working on debugging issues.' defaultMessage='Level (DEBG, INFO, EROR)'
/>
</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
>
<FormattedMessage
id='admin.log.fileTitle'
defaultMessage='Log To File: '
/>
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
<input
type='radio'
name='fileEnable'
ref='fileEnable'
value='true'
defaultChecked={this.props.config.LogSettings.EnableFile}
onChange={this.handleChange.bind(this, 'file_true')}
/> />
</td>
</tr>
<tr>
<td className='help-text'>{'%S'}</td><td className='help-text'>
<FormattedMessage <FormattedMessage
id='admin.log.true' id='admin.log.formatSource'
defaultMessage='true' defaultMessage='Source'
/>
</label>
<label className='radio-inline'>
<input
type='radio'
name='fileEnable'
value='false'
defaultChecked={!this.props.config.LogSettings.EnableFile}
onChange={this.handleChange.bind(this, 'file_false')}
/> />
</td>
</tr>
<tr>
<td className='help-text'>{'%M'}</td><td className='help-text'>
<FormattedMessage <FormattedMessage
id='admin.log.false' id='admin.log.formatMessage'
defaultMessage='false' defaultMessage='Message'
/> />
</label> </td>
<p className='help-text'> </tr>
<FormattedMessage </tbody>
id='admin.log.fileDescription' </table>
defaultMessage='Typically set to true in production. When true, log files are written to the log file specified in file location field below.'
/>
</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='fileLevel'
>
<FormattedMessage
id='admin.log.fileLevelTitle'
defaultMessage='File Log Level:'
/>
</label>
<div className='col-sm-8'>
<select
className='form-control'
id='fileLevel'
ref='fileLevel'
defaultValue={this.props.config.LogSettings.FileLevel}
onChange={this.handleChange}
disabled={!this.state.fileEnable}
>
<option value='DEBUG'>{'DEBUG'}</option>
<option value='INFO'>{'INFO'}</option>
<option value='ERROR'>{'ERROR'}</option>
</select>
<p className='help-text'>
<FormattedMessage
id='admin.log.fileLevelDescription'
defaultMessage='This setting determines the level of detail at which log events are written to the log file. ERROR: Outputs only error messages. INFO: Outputs error messages and information around startup and initialization. DEBUG: Prints high detail for developers working on debugging issues.'
/>
</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='fileLocation'
>
<FormattedMessage
id='admin.log.locationTitle'
defaultMessage='File Location:'
/>
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='fileLocation'
ref='fileLocation'
placeholder={formatMessage(holders.locationPlaceholder)}
defaultValue={this.props.config.LogSettings.FileLocation}
onChange={this.handleChange}
disabled={!this.state.fileEnable}
/>
<p className='help-text'>
<FormattedMessage
id='admin.log.locationDescription'
defaultMessage='File to which log files are written. If blank, will be set to ./logs/mattermost, which writes logs to mattermost.log. Log rotation is enabled and every 10,000 lines of log information is written to new files stored in the same directory, for example mattermost.2015-09-23.001, mattermost.2015-09-23.002, and so forth.'
/>
</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='fileFormat'
>
<FormattedMessage
id='admin.log.formatTitle'
defaultMessage='File Format:'
/>
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='fileFormat'
ref='fileFormat'
placeholder={formatMessage(holders.formatPlaceholder)}
defaultValue={this.props.config.LogSettings.FileFormat}
onChange={this.handleChange}
disabled={!this.state.fileEnable}
/>
<div className='help-text'>
<FormattedMessage
id='admin.log.formatDescription'
defaultMessage='Format of log message output. If blank will be set to "[%D %T] [%L] %M", where:'
/>
<div className='help-text'>
<table
className='table table-bordered'
cellPadding='5'
>
<tbody>
<tr><td className='help-text'>{'%T'}</td><td className='help-text'>
<FormattedMessage
id='admin.log.formatTime'
defaultMessage='Time (15:04:05 MST)'
/>
</td></tr>
<tr><td className='help-text'>{'%D'}</td><td className='help-text'>
<FormattedMessage
id='admin.log.formatDateLong'
defaultMessage='Date (2006/01/02)'
/>
</td></tr>
<tr><td className='help-text'>{'%d'}</td><td className='help-text'>
<FormattedMessage
id='admin.log.formatDateShort'
defaultMessage='Date (01/02/06)'
/>
</td></tr>
<tr><td className='help-text'>{'%L'}</td><td className='help-text'>
<FormattedMessage
id='admin.log.formatLevel'
defaultMessage='Level (DEBG, INFO, EROR)'
/>
</td></tr>
<tr><td className='help-text'>{'%S'}</td><td className='help-text'>
<FormattedMessage
id='admin.log.formatSource'
defaultMessage='Source'
/>
</td></tr>
<tr><td className='help-text'>{'%M'}</td><td className='help-text'>
<FormattedMessage
id='admin.log.formatMessage'
defaultMessage='Message'
/>
</td></tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div className='form-group'>
<div className='col-sm-12'>
{serverError}
<button
disabled={!this.state.saveNeeded}
type='submit'
className={saveClass}
onClick={this.handleSubmit}
id='save-button'
data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + formatMessage(holders.saving)}
>
<FormattedMessage
id='admin.log.save'
defaultMessage='Save'
/>
</button>
</div>
</div>
</form>
</div> </div>
); );
} }
} }
LogSettings.propTypes = {
intl: intlShape.isRequired,
config: React.PropTypes.object
};
export default injectIntl(LogSettings);

Просмотреть файл

@@ -0,0 +1,130 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import * as Utils from 'utils/utils.jsx';
import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import {FormattedMessage} from 'react-intl';
import GeneratedSetting from './generated_setting.jsx';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
export default class LoginSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
this.state = Object.assign(this.state, {
passwordResetSalt: props.config.EmailSettings.PasswordResetSalt,
maximumLoginAttempts: props.config.ServiceSettings.MaximumLoginAttempts,
enableMultifactorAuthentication: props.config.ServiceSettings.EnableMultifactorAuthentication
});
}
getConfigFromState(config) {
config.EmailSettings.PasswordResetSalt = this.state.passwordResetSalt;
config.ServiceSettings.MaximumLoginAttempts = this.parseIntNonZero(this.state.maximumLoginAttempts);
if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.MFA === 'true') {
config.ServiceSettings.EnableMultifactorAuthentication = this.state.enableMultifactorAuthentication;
}
return config;
}
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.security.title'
defaultMessage='Security Settings'
/>
</h3>
);
}
renderSettings() {
let mfaSetting = null;
if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.MFA === 'true') {
mfaSetting = (
<BooleanSetting
id='enableMultifactorAuthentication'
label={
<FormattedMessage
id='admin.service.mfaTitle'
defaultMessage='Enable Multi-factor Authentication:'
/>
}
helpText={
<FormattedMessage
id='admin.service.mfaDesc'
defaultMessage='When true, users will be given the option to add multi-factor authentication to their account. They will need a smartphone and an authenticator app such as Google Authenticator.'
/>
}
value={this.state.enableMultifactorAuthentication}
onChange={this.handleChange}
/>
);
}
return (
<SettingsGroup
header={
<FormattedMessage
id='admin.security.login'
defaultMessage='Login'
/>
}
>
<GeneratedSetting
id='passwordResetSalt'
label={
<FormattedMessage
id='admin.email.passwordSaltTitle'
defaultMessage='Password Reset Salt:'
/>
}
helpText={
<FormattedMessage
id='admin.email.passwordSaltDescription'
defaultMessage='32-character salt added to signing of password reset emails. Randomly generated on install. Click "Re-Generate" to create new salt.'
/>
}
value={this.state.passwordResetSalt}
onChange={this.handleChange}
disabled={this.state.sendEmailNotifications}
disabledText={
<FormattedMessage
id='admin.security.passwordResetSalt.disabled'
defaultMessage='Password reset salt cannot be changed while sending emails is disabled.'
/>
}
/>
<TextSetting
id='maximumLoginAttempts'
label={
<FormattedMessage
id='admin.service.attemptTitle'
defaultMessage='Maximum Login Attempts:'
/>
}
placeholder={Utils.localizeMessage('admin.service.attemptExample', 'Ex "10"')}
helpText={
<FormattedMessage
id='admin.service.attemptDescription'
defaultMessage='Login attempts allowed before user is locked out and required to reset password via email.'
/>
}
value={this.state.maximumLoginAttempts}
onChange={this.handleChange}
/>
{mfaSetting}
</SettingsGroup>
);
}
}

Просмотреть файл

@@ -1,215 +1,90 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
import $ from 'jquery';
import ReactDOM from 'react-dom';
import Client from 'utils/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl';
const holders = defineMessages({
saving: {
id: 'admin.privacy.saving',
defaultMessage: 'Saving Config...'
}
});
import React from 'react'; import React from 'react';
class PrivacySettings extends React.Component { import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import {FormattedMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
export default class PrivacySettings extends AdminSettings {
constructor(props) { constructor(props) {
super(props); super(props);
this.handleChange = this.handleChange.bind(this); this.getConfigFromState = this.getConfigFromState.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.state = { this.renderSettings = this.renderSettings.bind(this);
saveNeeded: false,
serverError: null this.state = Object.assign(this.state, {
}; showEmailAddress: props.config.PrivacySettings.ShowEmailAddress,
showFullName: props.config.PrivacySettings.ShowFullName
});
} }
handleChange() { getConfigFromState(config) {
var s = {saveNeeded: true, serverError: this.state.serverError}; config.PrivacySettings.ShowEmailAddress = this.state.showEmailAddress;
config.PrivacySettings.ShowFullName = this.state.showFullName;
this.setState(s); return config;
} }
handleSubmit(e) { renderTitle() {
e.preventDefault(); return (
$('#save-button').button('loading'); <h3>
<FormattedMessage
var config = this.props.config; id='admin.general.title'
config.PrivacySettings.ShowEmailAddress = ReactDOM.findDOMNode(this.refs.ShowEmailAddress).checked; defaultMessage='General Settings'
config.PrivacySettings.ShowFullName = ReactDOM.findDOMNode(this.refs.ShowFullName).checked; />
</h3>
Client.saveConfig(
config,
() => {
AsyncClient.getConfig();
this.setState({
serverError: null,
saveNeeded: false
});
$('#save-button').button('reset');
},
(err) => {
this.setState({
serverError: err.message,
saveNeeded: true
});
$('#save-button').button('reset');
}
); );
} }
render() { renderSettings() {
var serverError = '';
if (this.state.serverError) {
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
}
var saveClass = 'btn';
if (this.state.saveNeeded) {
saveClass = 'btn btn-primary';
}
return ( return (
<div className='wrapper--fixed'> <SettingsGroup
<h3> header={
<FormattedMessage <FormattedMessage
id='admin.privacy.title' id='admin.general.privacy'
defaultMessage='Privacy Settings' defaultMessage='Privacy'
/> />
</h3> }
<form >
className='form-horizontal' <BooleanSetting
role='form' id='showEmailAddress'
> label={
<FormattedMessage
<div className='form-group'> id='admin.privacy.showEmailTitle'
<label defaultMessage='Show Email Address: '
className='control-label col-sm-4' />
htmlFor='ShowEmailAddress' }
> helpText={
<FormattedMessage <FormattedMessage
id='admin.privacy.showEmailTitle' id='admin.privacy.showEmailDescription'
defaultMessage='Show Email Address: ' defaultMessage='When false, hides email address of users from other users in the user interface, including team owners and team administrators. Used when system is set up for managing teams where some users choose to keep their contact information private.'
/> />
</label> }
<div className='col-sm-8'> value={this.state.showEmailAddress}
<label className='radio-inline'> onChange={this.handleChange}
<input />
type='radio' <BooleanSetting
name='ShowEmailAddress' id='showFullName'
value='true' label={
ref='ShowEmailAddress' <FormattedMessage
defaultChecked={this.props.config.PrivacySettings.ShowEmailAddress} id='admin.privacy.showFullNameTitle'
onChange={this.handleChange} defaultMessage='Show Full Name: '
/> />
<FormattedMessage }
id='admin.privacy.true' helpText={
defaultMessage='true' <FormattedMessage
/> id='admin.privacy.showFullNameDescription'
</label> defaultMessage='When false, hides full name of users from other users, including team owners and team administrators. Username is shown in place of full name.'
<label className='radio-inline'> />
<input }
type='radio' value={this.state.showFullName}
name='ShowEmailAddress' onChange={this.handleChange}
value='false' />
defaultChecked={!this.props.config.PrivacySettings.ShowEmailAddress} </SettingsGroup>
onChange={this.handleChange}
/>
<FormattedMessage
id='admin.privacy.false'
defaultMessage='false'
/>
</label>
<p className='help-text'>
<FormattedMessage
id='admin.privacy.showEmailDescription'
defaultMessage='When false, hides email address of users from other users in the user interface, including team owners and team administrators. Used when system is set up for managing teams where some users choose to keep their contact information private.'
/>
</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='ShowFullName'
>
<FormattedMessage
id='admin.privacy.showFullNameTitle'
defaultMessage='Show Full Name: '
/>
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
<input
type='radio'
name='ShowFullName'
value='true'
ref='ShowFullName'
defaultChecked={this.props.config.PrivacySettings.ShowFullName}
onChange={this.handleChange}
/>
<FormattedMessage
id='admin.privacy.true'
defaultMessage='true'
/>
</label>
<label className='radio-inline'>
<input
type='radio'
name='ShowFullName'
value='false'
defaultChecked={!this.props.config.PrivacySettings.ShowFullName}
onChange={this.handleChange}
/>
<FormattedMessage
id='admin.privacy.false'
defaultMessage='false'
/>
</label>
<p className='help-text'>
<FormattedMessage
id='admin.privacy.showFullNameDescription'
defaultMessage='When false, hides full name of users from other users, including team owners and team administrators. Username is shown in place of full name.'
/>
</p>
</div>
</div>
<div className='form-group'>
<div className='col-sm-12'>
{serverError}
<button
disabled={!this.state.saveNeeded}
type='submit'
className={saveClass}
onClick={this.handleSubmit}
id='save-button'
data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + this.props.intl.formatMessage(holders.saving)}
>
<FormattedMessage
id='admin.privacy.save'
defaultMessage='Save'
/>
</button>
</div>
</div>
</form>
</div>
); );
} }
} }
PrivacySettings.propTypes = {
intl: intlShape.isRequired,
config: React.PropTypes.object
};
export default injectIntl(PrivacySettings);

Просмотреть файл

@@ -0,0 +1,91 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import {FormattedMessage} from 'react-intl';
import GeneratedSetting from './generated_setting.jsx';
import SettingsGroup from './settings_group.jsx';
export default class PublicLinkSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
this.state = Object.assign(this.state, {
enablePublicLink: props.config.FileSettings.EnablePublicLink,
publicLinkSalt: props.config.FileSettings.PublicLinkSalt
});
}
getConfigFromState(config) {
config.FileSettings.EnablePublicLink = this.state.enablePublicLink;
config.FileSettings.PublicLinkSalt = this.state.publicLinkSalt;
return config;
}
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.security.title'
defaultMessage='Security Settings'
/>
</h3>
);
}
renderSettings() {
return (
<SettingsGroup
header={
<FormattedMessage
id='admin.security.public_links'
defaultMessage='Public Links'
/>
}
>
<BooleanSetting
id='enablePublicLink'
label={
<FormattedMessage
id='admin.image.shareTitle'
defaultMessage='Share Public File Link: '
/>
}
helpText={
<FormattedMessage
id='admin.image.shareDescription'
defaultMessage='Allow users to share public links to files and images.'
/>
}
value={this.state.enablePublicLink}
onChange={this.handleChange}
/>
<GeneratedSetting
id='publicLinkSalt'
label={
<FormattedMessage
id='admin.image.publicLinkTitle'
defaultMessage='Public Link Salt:'
/>
}
helpText={
<FormattedMessage
id='admin.image.publicLinkDescription'
defaultMessage='32-character salt added to signing of public image links. Randomly generated on install. Click "Re-Generate" to create new salt.'
/>
}
value={this.state.publicLinkSalt}
onChange={this.handleChange}
/>
</SettingsGroup>
);
}
}

Просмотреть файл

@@ -0,0 +1,235 @@
// 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 * as Utils from 'utils/utils.jsx';
import AdminSettings from './admin_settings.jsx';
import DropdownSetting from './dropdown_setting.jsx';
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
const PUSH_NOTIFICATIONS_OFF = 'off';
const PUSH_NOTIFICATIONS_MHPNS = 'mhpns';
const PUSH_NOTIFICATIONS_MTPNS = 'mtpns';
const PUSH_NOTIFICATIONS_CUSTOM = 'custom';
export default class PushSettings extends AdminSettings {
constructor(props) {
super(props);
this.canSave = this.canSave.bind(this);
this.handleAgreeChange = this.handleAgreeChange.bind(this);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
let pushNotificationServerType = PUSH_NOTIFICATIONS_CUSTOM;
let agree = false;
if (!props.config.EmailSettings.SendPushNotifications) {
pushNotificationServerType = PUSH_NOTIFICATIONS_OFF;
} else if (props.config.EmailSettings.PushNotificationServer === Constants.MHPNS &&
global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.MHPNS === 'true') {
pushNotificationServerType = PUSH_NOTIFICATIONS_MHPNS;
agree = true;
} else if (props.config.EmailSettings.PushNotificationServer === Constants.MTPNS) {
pushNotificationServerType = PUSH_NOTIFICATIONS_MTPNS;
} else {
pushNotificationServerType = PUSH_NOTIFICATIONS_CUSTOM;
}
let pushNotificationServer = this.props.config.EmailSettings.PushNotificationServer;
if (pushNotificationServerType === PUSH_NOTIFICATIONS_MTPNS) {
pushNotificationServer = Constants.MTPNS;
} else if (pushNotificationServerType === PUSH_NOTIFICATIONS_MHPNS) {
pushNotificationServer = Constants.MHPNS;
}
this.state = Object.assign(this.state, {
pushNotificationServerType,
pushNotificationServer,
pushNotificationContents: props.config.EmailSettings.PushNotificationContents,
agree
});
}
canSave() {
return this.state.pushNotificationServerType !== PUSH_NOTIFICATIONS_MHPNS || this.state.agree;
}
handleAgreeChange(e) {
this.setState({
agree: e.target.checked
});
}
handleChange(id, value) {
if (id === 'pushNotificationServerType') {
this.setState({
agree: false
});
if (value === PUSH_NOTIFICATIONS_MHPNS) {
this.setState({
pushNotificationServer: Constants.MHPNS
});
} else if (value === PUSH_NOTIFICATIONS_MTPNS) {
this.setState({
pushNotificationServer: Constants.MTPNS
});
}
}
super.handleChange(id, value);
}
getConfigFromState(config) {
config.EmailSettings.SendPushNotifications = this.state.pushNotificationServerType !== PUSH_NOTIFICATIONS_OFF;
config.EmailSettings.PushNotificationServer = this.state.pushNotificationServer.trim();
config.EmailSettings.PushNotificationContents = this.state.pushNotificationContents;
return config;
}
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.notifications.title'
defaultMessage='Notification Settings'
/>
</h3>
);
}
renderSettings() {
const pushNotificationServerTypes = [];
pushNotificationServerTypes.push({value: PUSH_NOTIFICATIONS_OFF, text: Utils.localizeMessage('admin.email.pushOff', 'Do not send push notifications')});
if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.MHPNS === 'true') {
pushNotificationServerTypes.push({value: PUSH_NOTIFICATIONS_MHPNS, text: Utils.localizeMessage('admin.email.mhpns', 'Use encrypted, production-quality HPNS connection to iOS and Android apps')});
}
pushNotificationServerTypes.push({value: PUSH_NOTIFICATIONS_MTPNS, text: Utils.localizeMessage('admin.email.mtpns', 'Use iOS and Android apps on iTunes and Google Play with TPNS')});
pushNotificationServerTypes.push({value: PUSH_NOTIFICATIONS_CUSTOM, text: Utils.localizeMessage('admin.email.selfPush', 'Manually enter Push Notification Service location')});
let sendHelpText = null;
let pushServerHelpText = null;
if (this.state.pushNotificationServerType === PUSH_NOTIFICATIONS_OFF) {
sendHelpText = (
<FormattedHTMLMessage
id='admin.email.pushOffHelp'
defaultMessage='Please see <a href="http://docs.mattermost.com/deployment/push.html#push-notifications-and-mobile-devices" target="_blank">documentation on push notifications</a> to learn more about setup options.'
/>
);
} else if (this.state.pushNotificationServerType === PUSH_NOTIFICATIONS_MHPNS) {
pushServerHelpText = (
<FormattedHTMLMessage
id='admin.email.mhpnsHelp'
defaultMessage='Download <a href="https://itunes.apple.com/us/app/mattermost/id984966508?mt=8" target="_blank">Mattermost iOS app</a> from iTunes. Download <a href="https://play.google.com/store/apps/details?id=com.mattermost.mattermost&hl=en" target="_blank">Mattermost Android app</a> from Google Play. Learn more about the <a href="http://docs.mattermost.com/deployment/push.html#hosted-push-notifications-service-hpns" target="_blank">Mattermost Hosted Push Notification Service</a>.'
/>
);
} else if (this.state.pushNotificationServerType === PUSH_NOTIFICATIONS_MTPNS) {
pushServerHelpText = (
<FormattedHTMLMessage
id='admin.email.mtpnsHelp'
defaultMessage='Download <a href="https://itunes.apple.com/us/app/mattermost/id984966508?mt=8" target="_blank">Mattermost iOS app</a> from iTunes. Download <a href="https://play.google.com/store/apps/details?id=com.mattermost.mattermost&hl=en" target="_blank">Mattermost Android app</a> from Google Play. Learn more about the <a href="http://docs.mattermost.com/deployment/push.html#test-push-notifications-service-tpns" target="_blank">Mattermost Test Push Notification Service</a>.'
/>
);
} else {
pushServerHelpText = (
<FormattedHTMLMessage
id='admin.email.easHelp'
defaultMessage='Learn more about compiling and deploying your own mobile apps from an <a href="http://docs.mattermost.com/deployment/push.html#enterprise-app-store-eas" target="_blank">Enterprise App Store</a>.'
/>
);
}
let tosCheckbox;
if (this.state.pushNotificationServerType === PUSH_NOTIFICATIONS_MHPNS) {
tosCheckbox = (
<div className='form-group'>
<div className='col-sm-4'/>
<div className='col-sm-8'>
<input
type='checkbox'
ref='agree'
checked={this.state.agree}
onChange={this.handleAgreeChange}
/>
<FormattedHTMLMessage
id='admin.email.agreeHPNS'
defaultMessage=' I understand and accept the Mattermost Hosted Push Notification Service <a href="https://about.mattermost.com/hpns-terms/" target="_blank">Terms of Service</a> and <a href="https://about.mattermost.com/hpns-privacy/" target="_blank">Privacy Policy</a>.'
/>
</div>
</div>
);
}
return (
<SettingsGroup
header={
<FormattedMessage
id='admin.notifications.push'
defaultMessage='Mobile Push'
/>
}
>
<DropdownSetting
id='pushNotificationServerType'
values={pushNotificationServerTypes}
label={
<FormattedMessage
id='admin.email.pushTitle'
defaultMessage='Send Push Notifications: '
/>
}
value={this.state.pushNotificationServerType}
onChange={this.handleChange}
helpText={sendHelpText}
/>
{tosCheckbox}
<TextSetting
id='pushNotificationServer'
label={
<FormattedMessage
id='admin.email.pushServerTitle'
defaultMessage='Push Notification Server:'
/>
}
placeholder={Utils.localizeMessage('admin.email.pushServerEx', 'E.g.: "http://push-test.mattermost.com"')}
helpText={pushServerHelpText}
value={this.state.pushNotificationServer}
onChange={this.handleChange}
disabled={this.state.pushNotificationServerType !== PUSH_NOTIFICATIONS_CUSTOM}
/>
<DropdownSetting
id='pushNotificationContents'
values={[
{value: 'generic', text: Utils.localizeMessage('admin.email.genericPushNotification', 'Send generic description with user and channel names')},
{value: 'full', text: Utils.localizeMessage('admin.email.fullPushNotification', 'Send full message snippet')}
]}
label={
<FormattedMessage
id='admin.email.pushContentTitle'
defaultMessage='Push Notification Contents:'
/>
}
value={this.state.pushNotificationContents}
onChange={this.handleChange}
disabled={this.state.pushNotificationServerType === PUSH_NOTIFICATIONS_OFF}
helpText={
<FormattedHTMLMessage
id='admin.email.pushContentDesc'
defaultMessage='Selecting "Send generic description with user and channel names" provides push notifications with generic messages, including names of users and channels but no specific details from the message text.<br /><br />
Selecting "Send full message snippet" sends excerpts from messages triggering notifications with specifics and may include confidential information sent in messages. If your Push Notification Service is outside your firewall, it is HIGHLY RECOMMENDED this option only be used with an "https" protocol to encrypt the connection.'
/>
}
/>
</SettingsGroup>
);
}
}

Просмотреть файл

@@ -1,371 +1,158 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
import $ from 'jquery';
import ReactDOM from 'react-dom';
import Client from 'utils/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl';
const holders = defineMessages({
queriesExample: {
id: 'admin.rate.queriesExample',
defaultMessage: 'Ex "10"'
},
memoryExample: {
id: 'admin.rate.memoryExample',
defaultMessage: 'Ex "10000"'
},
httpHeaderExample: {
id: 'admin.rate.httpHeaderExample',
defaultMessage: 'Ex "X-Real-IP", "X-Forwarded-For"'
},
saving: {
id: 'admin.rate.saving',
defaultMessage: 'Saving Config...'
}
});
import React from 'react'; import React from 'react';
class RateSettings extends React.Component { import * as Utils from 'utils/utils.jsx';
import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import {FormattedMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
export default class RateSettings extends AdminSettings {
constructor(props) { constructor(props) {
super(props); super(props);
this.handleChange = this.handleChange.bind(this); this.getConfigFromState = this.getConfigFromState.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.state = { this.renderSettings = this.renderSettings.bind(this);
EnableRateLimiter: this.props.config.RateLimitSettings.EnableRateLimiter,
VaryByRemoteAddr: this.props.config.RateLimitSettings.VaryByRemoteAddr, this.state = Object.assign(this.state, {
saveNeeded: false, enableRateLimiter: props.config.RateLimitSettings.EnableRateLimiter,
serverError: null perSec: props.config.RateLimitSettings.PerSec,
}; memoryStoreSize: props.config.RateLimitSettings.MemoryStoreSize,
varyByRemoteAddr: props.config.RateLimitSettings.VaryByRemoteAddr,
varyByHeader: props.config.RateLimitSettings.VaryByHeader
});
} }
handleChange(action) { getConfigFromState(config) {
var s = {saveNeeded: true, serverError: this.state.serverError}; config.RateLimitSettings.EnableRateLimiter = this.state.enableRateLimiter;
config.RateLimitSettings.PerSec = this.parseIntNonZero(this.state.perSec);
config.RateLimitSettings.MemoryStoreSize = this.parseIntNonZero(this.state.memoryStoreSize);
config.RateLimitSettings.VaryByRemoteAddr = this.state.varyByRemoteAddr;
config.RateLimitSettings.VaryByHeader = this.state.varyByHeader;
if (action === 'EnableRateLimiterTrue') { return config;
s.EnableRateLimiter = true;
}
if (action === 'EnableRateLimiterFalse') {
s.EnableRateLimiter = false;
}
if (action === 'VaryByRemoteAddrTrue') {
s.VaryByRemoteAddr = true;
}
if (action === 'VaryByRemoteAddrFalse') {
s.VaryByRemoteAddr = false;
}
this.setState(s);
} }
handleSubmit(e) { renderTitle() {
e.preventDefault(); return (
$('#save-button').button('loading'); <h3>
<FormattedMessage
var config = this.props.config; id='admin.rate.title'
config.RateLimitSettings.EnableRateLimiter = ReactDOM.findDOMNode(this.refs.EnableRateLimiter).checked; defaultMessage='Rate Limit Settings'
config.RateLimitSettings.VaryByRemoteAddr = ReactDOM.findDOMNode(this.refs.VaryByRemoteAddr).checked; />
config.RateLimitSettings.VaryByHeader = ReactDOM.findDOMNode(this.refs.VaryByHeader).value.trim(); </h3>
var PerSec = 10;
if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.PerSec).value, 10))) {
PerSec = parseInt(ReactDOM.findDOMNode(this.refs.PerSec).value, 10);
}
config.RateLimitSettings.PerSec = PerSec;
ReactDOM.findDOMNode(this.refs.PerSec).value = PerSec;
var MemoryStoreSize = 10000;
if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.MemoryStoreSize).value, 10))) {
MemoryStoreSize = parseInt(ReactDOM.findDOMNode(this.refs.MemoryStoreSize).value, 10);
}
config.RateLimitSettings.MemoryStoreSize = MemoryStoreSize;
ReactDOM.findDOMNode(this.refs.MemoryStoreSize).value = MemoryStoreSize;
Client.saveConfig(
config,
() => {
AsyncClient.getConfig();
this.setState({
serverError: null,
saveNeeded: false
});
$('#save-button').button('reset');
},
(err) => {
this.setState({
serverError: err.message,
saveNeeded: true
});
$('#save-button').button('reset');
}
); );
} }
render() { renderSettings() {
const {formatMessage} = this.props.intl;
var serverError = '';
if (this.state.serverError) {
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
}
var saveClass = 'btn';
if (this.state.saveNeeded) {
saveClass = 'btn btn-primary';
}
return ( return (
<div className='wrapper--fixed'> <SettingsGroup>
<div className='banner'> <div className='banner'>
<div className='banner__content'> <div className='banner__content'>
<h4 className='banner__heading'> <FormattedMessage
<FormattedMessage id='admin.rate.noteDescription'
id='admin.rate.noteTitle' defaultMessage='Changing properties in this section will require a server restart before taking effect.'
defaultMessage='Note:' />
/>
</h4>
<p>
<FormattedMessage
id='admin.rate.noteDescription'
defaultMessage='Changing properties in this section will require a server restart before taking effect.'
/>
</p>
</div> </div>
</div> </div>
<BooleanSetting
<h3> id='enableRateLimiter'
<FormattedMessage label={
id='admin.rate.title' <FormattedMessage
defaultMessage='Rate Limit Settings' id='admin.rate.enableLimiterTitle'
/> defaultMessage='Enable Rate Limiter: '
</h3> />
<form }
className='form-horizontal' helpText={
role='form' <FormattedMessage
> id='admin.rate.enableLimiterDescription'
defaultMessage='When true, APIs are throttled at rates specified below.'
<div className='form-group'> />
<label }
className='control-label col-sm-4' value={this.state.enableRateLimiter}
htmlFor='EnableRateLimiter' onChange={this.handleChange}
> />
<FormattedMessage <TextSetting
id='admin.rate.enableLimiterTitle' id='perSec'
defaultMessage='Enable Rate Limiter: ' label={
/> <FormattedMessage
</label> id='admin.rate.queriesTitle'
<div className='col-sm-8'> defaultMessage='Number Of Queries Per Second:'
<label className='radio-inline'> />
<input }
type='radio' placeholder={Utils.localizeMessage('admin.rate.queriesExample', 'Ex "10"')}
name='EnableRateLimiter' helpText={
value='true' <FormattedMessage
ref='EnableRateLimiter' id='admin.rate.queriesDescription'
defaultChecked={this.props.config.RateLimitSettings.EnableRateLimiter} defaultMessage='Throttles API at this number of requests per second.'
onChange={this.handleChange.bind(this, 'EnableRateLimiterTrue')} />
/> }
<FormattedMessage value={this.state.perSec}
id='admin.rate.true' onChange={this.handleChange}
defaultMessage='true' disabled={!this.state.enableRateLimiter}
/> />
</label> <TextSetting
<label className='radio-inline'> id='memoryStoreSize'
<input label={
type='radio' <FormattedMessage
name='EnableRateLimiter' id='admin.rate.memoryTitle'
value='false' defaultMessage='Memory Store Size:'
defaultChecked={!this.props.config.RateLimitSettings.EnableRateLimiter} />
onChange={this.handleChange.bind(this, 'EnableRateLimiterFalse')} }
/> placeholder={Utils.localizeMessage('admin.rate.memoryExample', 'Ex "10000"')}
<FormattedMessage helpText={
id='admin.rate.false' <FormattedMessage
defaultMessage='false' id='admin.rate.memoryDescription'
/> defaultMessage='Maximum number of users sessions connected to the system as determined by "Vary By Remote Address" and "Vary By Header" settings below.'
</label> />
<p className='help-text'> }
<FormattedMessage value={this.state.memoryStoreSize}
id='admin.rate.enableLimiterDescription' onChange={this.handleChange}
defaultMessage='When true, APIs are throttled at rates specified below.' disabled={!this.state.enableRateLimiter}
/> />
</p> <BooleanSetting
</div> id='varyByRemoteAddr'
</div> label={
<FormattedMessage
<div className='form-group'> id='admin.rate.remoteTitle'
<label defaultMessage='Vary By Remote Address: '
className='control-label col-sm-4' />
htmlFor='PerSec' }
> helpText={
<FormattedMessage <FormattedMessage
id='admin.rate.queriesTitle' id='admin.rate.remoteDescription'
defaultMessage='Number Of Queries Per Second:' defaultMessage='When true, rate limit API access by IP address.'
/> />
</label> }
<div className='col-sm-8'> value={this.state.varyByRemoteAddr}
<input onChange={this.handleChange}
type='text' disabled={!this.state.enableRateLimiter}
className='form-control' />
id='PerSec' <TextSetting
ref='PerSec' id='varyByHeader'
placeholder={formatMessage(holders.queriesExample)} label={
defaultValue={this.props.config.RateLimitSettings.PerSec} <FormattedMessage
onChange={this.handleChange} id='admin.rate.httpHeaderTitle'
disabled={!this.state.EnableRateLimiter} defaultMessage='Vary By HTTP Header:'
/> />
<p className='help-text'> }
<FormattedMessage placeholder={Utils.localizeMessage('admin.rate.httpHeaderExample', 'Ex "X-Real-IP", "X-Forwarded-For"')}
id='admin.rate.queriesDescription' helpText={
defaultMessage='Throttles API at this number of requests per second.' <FormattedMessage
/> id='admin.rate.httpHeaderDescription'
</p> defaultMessage='When filled in, vary rate limiting by HTTP header field specified (e.g. when configuring NGINX set to "X-Real-IP", when configuring AmazonELB set to "X-Forwarded-For").'
</div> />
</div> }
value={this.state.varyByHeader}
<div className='form-group'> onChange={this.handleChange}
<label disabled={!this.state.enableRateLimiter || this.state.varyByRemoteAddr}
className='control-label col-sm-4' />
htmlFor='MemoryStoreSize' </SettingsGroup>
>
<FormattedMessage
id='admin.rate.memoryTitle'
defaultMessage='Memory Store Size:'
/>
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='MemoryStoreSize'
ref='MemoryStoreSize'
placeholder={formatMessage(holders.memoryExample)}
defaultValue={this.props.config.RateLimitSettings.MemoryStoreSize}
onChange={this.handleChange}
disabled={!this.state.EnableRateLimiter}
/>
<p className='help-text'>
<FormattedMessage
id='admin.rate.memoryDescription'
defaultMessage='Maximum number of users sessions connected to the system as determined by "Vary By Remote Address" and "Vary By Header" settings below.'
/>
</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='VaryByRemoteAddr'
>
<FormattedMessage
id='admin.rate.remoteTitle'
defaultMessage='Vary By Remote Address: '
/>
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
<input
type='radio'
name='VaryByRemoteAddr'
value='true'
ref='VaryByRemoteAddr'
defaultChecked={this.props.config.RateLimitSettings.VaryByRemoteAddr}
onChange={this.handleChange.bind(this, 'VaryByRemoteAddrTrue')}
disabled={!this.state.EnableRateLimiter}
/>
<FormattedMessage
id='admin.rate.true'
defaultMessage='true'
/>
</label>
<label className='radio-inline'>
<input
type='radio'
name='VaryByRemoteAddr'
value='false'
defaultChecked={!this.props.config.RateLimitSettings.VaryByRemoteAddr}
onChange={this.handleChange.bind(this, 'VaryByRemoteAddrFalse')}
disabled={!this.state.EnableRateLimiter}
/>
<FormattedMessage
id='admin.rate.false'
defaultMessage='false'
/>
</label>
<p className='help-text'>
<FormattedMessage
id='admin.rate.remoteDescription'
defaultMessage='When true, rate limit API access by IP address.'
/>
</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='VaryByHeader'
>
<FormattedMessage
id='admin.rate.httpHeaderTitle'
defaultMessage='Vary By HTTP Header:'
/>
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='VaryByHeader'
ref='VaryByHeader'
placeholder={formatMessage(holders.httpHeaderExample)}
defaultValue={this.props.config.RateLimitSettings.VaryByHeader}
onChange={this.handleChange}
disabled={!this.state.EnableRateLimiter || this.state.VaryByRemoteAddr}
/>
<p className='help-text'>
<FormattedMessage
id='admin.rate.httpHeaderDescription'
defaultMessage='When filled in, vary rate limiting by HTTP header field specified (e.g. when configuring NGINX set to "X-Real-IP", when configuring AmazonELB set to "X-Forwarded-For").'
/>
</p>
</div>
</div>
<div className='form-group'>
<div className='col-sm-12'>
{serverError}
<button
disabled={!this.state.saveNeeded}
type='submit'
className={saveClass}
onClick={this.handleSubmit}
id='save-button'
data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + formatMessage(holders.saving)}
>
<FormattedMessage
id='admin.rate.save'
defaultMessage='Save'
/>
</button>
</div>
</div>
</form>
</div>
); );
} }
} }
RateSettings.propTypes = {
intl: intlShape.isRequired,
config: React.PropTypes.object
};
export default injectIntl(RateSettings);

Просмотреть файл

@@ -0,0 +1,61 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import {FormattedMessage} from 'react-intl';
export default class SaveButton extends React.Component {
static get propTypes() {
return {
saving: React.PropTypes.bool.isRequired,
disabled: React.PropTypes.bool
};
}
static get defaultProps() {
return {
disabled: false
};
}
render() {
const {saving, disabled, ...props} = this.props; // eslint-disable-line no-use-before-define
let contents;
if (saving) {
contents = (
<span>
<span className='glyphicon glyphicon-refresh glyphicon-refresh-animate'/>
<FormattedMessage
id='admin.saving'
defaultMessage='Saving Config...'
/>
</span>
);
} else {
contents = (
<FormattedMessage
id='admin.save'
defaultMessage='Save'
/>
);
}
let className = 'save-button btn';
if (!disabled) {
className += ' btn-primary';
}
return (
<button
type='submit'
className={className}
disabled={disabled}
{...props}
>
{contents}
</button>
);
}
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@@ -0,0 +1,134 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import * as Utils from 'utils/utils.jsx';
import AdminSettings from './admin_settings.jsx';
import {FormattedMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
export default class SessionSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
this.state = Object.assign(this.state, {
sessionLengthWebInDays: props.config.ServiceSettings.SessionLengthWebInDays,
sessionLengthMobileInDays: props.config.ServiceSettings.SessionLengthMobileInDays,
sessionLengthSSOInDays: props.config.ServiceSettings.SessionLengthSSOInDays,
sessionCacheInMinutes: props.config.ServiceSettings.SessionCacheInMinutes
});
}
getConfigFromState(config) {
config.ServiceSettings.SessionLengthWebInDays = this.parseIntNonZero(this.state.sessionLengthWebInDays);
config.ServiceSettings.SessionLengthMobileInDays = this.parseIntNonZero(this.state.sessionLengthMobileInDays);
config.ServiceSettings.SessionLengthSSOInDays = this.parseIntNonZero(this.state.sessionLengthSSOInDays);
config.ServiceSettings.SessionCacheInMinutes = this.parseIntNonZero(this.state.sessionCacheInMinutes);
return config;
}
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.security.title'
defaultMessage='Security Settings'
/>
</h3>
);
}
renderSettings() {
return (
<SettingsGroup
header={
<FormattedMessage
id='admin.security.session'
defaultMessage='Sessions'
/>
}
>
<TextSetting
id='sessionLengthWebInDays'
label={
<FormattedMessage
id='admin.service.webSessionDays'
defaultMessage='Session Length for Web in Days:'
/>
}
placeholder={Utils.localizeMessage('admin.service.sessionDaysEx', 'Ex "30"')}
helpText={
<FormattedMessage
id='admin.service.webSessionDaysDesc'
defaultMessage='The web session will expire after the number of days specified and will require a user to login again.'
/>
}
value={this.state.sessionLengthWebInDays}
onChange={this.handleChange}
/>
<TextSetting
id='sessionLengthMobileInDays'
label={
<FormattedMessage
id='admin.service.mobileSessionDays'
defaultMessage='Session Length for Mobile Device in Days:'
/>
}
placeholder={Utils.localizeMessage('admin.service.sessionDaysEx', 'Ex "30"')}
helpText={
<FormattedMessage
id='admin.service.mobileSessionDaysDesc'
defaultMessage='The native mobile session will expire after the number of days specified and will require a user to login again.'
/>
}
value={this.state.sessionLengthMobileInDays}
onChange={this.handleChange}
/>
<TextSetting
id='sessionLengthSSOInDays'
label={
<FormattedMessage
id='admin.service.ssoSessionDays'
defaultMessage='Session Length for SSO in Days:'
/>
}
placeholder={Utils.localizeMessage('admin.service.sessionDaysEx', 'Ex "30"')}
helpText={
<FormattedMessage
id='admin.service.ssoSessionDaysDesc'
defaultMessage='The SSO session will expire after the number of days specified and will require a user to login again.'
/>
}
value={this.state.sessionLengthSSOInDays}
onChange={this.handleChange}
/>
<TextSetting
id='sessionCacheInMinutes'
label={
<FormattedMessage
id='admin.service.sessionCache'
defaultMessage='Session Cache in Minutes:'
/>
}
placeholder={Utils.localizeMessage('admin.service.sessionDaysEx', 'Ex "30"')}
helpText={
<FormattedMessage
id='admin.service.sessionCacheDesc'
defaultMessage='The number of minutes to cache a session in memory.'
/>
}
value={this.state.sessionCacheInMinutes}
onChange={this.handleChange}
/>
</SettingsGroup>
);
}
}

Просмотреть файл

@@ -5,20 +5,19 @@ import React from 'react';
export default class Setting extends React.Component { export default class Setting extends React.Component {
render() { render() {
let marginClass = '';
if (this.props.margin === 'small') {
marginClass = ' form-group--small';
}
return ( return (
<div className={'form-group' + marginClass}> <div className='form-group'>
<label <label
className='control-label col-sm-4' className='control-label col-sm-4'
htmlFor={this.props.inputId}
> >
{this.props.label} {this.props.label}
</label> </label>
<div className='col-sm-8'> <div className='col-sm-8'>
{this.props.children} {this.props.children}
<div className='help-text'>
{this.props.helpText}
</div>
</div> </div>
</div> </div>
); );
@@ -28,7 +27,8 @@ Setting.defaultProps = {
}; };
Setting.propTypes = { Setting.propTypes = {
inputId: React.PropTypes.string,
label: React.PropTypes.node.isRequired, label: React.PropTypes.node.isRequired,
children: React.PropTypes.node.isRequired, children: React.PropTypes.node.isRequired,
margin: React.PropTypes.oneOf(['', 'small']) helpText: React.PropTypes.node
}; };

Просмотреть файл

@@ -0,0 +1,42 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
export default class SettingsGroup extends React.Component {
static get propTypes() {
return {
show: React.PropTypes.bool.isRequired,
header: React.PropTypes.node,
children: React.PropTypes.node
};
}
static get defaultProps() {
return {
show: true
};
}
render() {
if (!this.props.show) {
return null;
}
let header = null;
if (this.props.header) {
header = (
<h4>
{this.props.header}
</h4>
);
}
return (
<div className='admin-settings__group'>
{header}
{this.props.children}
</div>
);
}
}

Просмотреть файл

@@ -0,0 +1,124 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import {FormattedMessage} from 'react-intl';
import GeneratedSetting from './generated_setting.jsx';
import SettingsGroup from './settings_group.jsx';
export default class SignupSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
this.state = Object.assign(this.state, {
requireEmailVerification: props.config.EmailSettings.RequireEmailVerification,
inviteSalt: props.config.EmailSettings.InviteSalt,
enableOpenServer: props.config.TeamSettings.EnableOpenServer
});
}
getConfigFromState(config) {
config.EmailSettings.RequireEmailVerification = this.state.requireEmailVerification;
config.EmailSettings.InviteSalt = this.state.inviteSalt;
config.TeamSettings.EnableOpenServer = this.state.enableOpenServer;
return config;
}
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.security.title'
defaultMessage='Security Settings'
/>
</h3>
);
}
renderSettings() {
return (
<SettingsGroup
header={
<FormattedMessage
id='admin.security.signup'
defaultMessage='Signup'
/>
}
>
<BooleanSetting
id='requireEmailVerification'
label={
<FormattedMessage
id='admin.email.requireVerificationTitle'
defaultMessage='Require Email Verification: '
/>
}
helpText={
<FormattedMessage
id='admin.email.requireVerificationDescription'
defaultMessage='Typically set to true in production. When true, Mattermost requires email verification after account creation prior to allowing login. Developers may set this field to false so skip sending verification emails for faster development.'
/>
}
value={this.state.requireEmailVerification}
onChange={this.handleChange}
disabled={this.state.sendEmailNotifications}
disabledText={
<FormattedMessage
id='admin.security.requireEmailVerification.disabled'
defaultMessage='Email verification cannot be changed while sending emails is disabled.'
/>
}
/>
<GeneratedSetting
id='inviteSalt'
label={
<FormattedMessage
id='admin.email.inviteSaltTitle'
defaultMessage='Invite Salt:'
/>
}
helpText={
<FormattedMessage
id='admin.email.inviteSaltDescription'
defaultMessage='32-character salt added to signing of email invites. Randomly generated on install. Click "Re-Generate" to create new salt.'
/>
}
value={this.state.inviteSalt}
onChange={this.handleChange}
disabled={this.state.sendEmailNotifications}
disabledText={
<FormattedMessage
id='admin.security.inviteSalt.disabled'
defaultMessage='Invite salt cannot be changed while sending emails is disabled.'
/>
}
/>
<BooleanSetting
id='enableOpenServer'
label={
<FormattedMessage
id='admin.team.openServerTitle'
defaultMessage='Enable Open Server: '
/>
}
helpText={
<FormattedMessage
id='admin.team.openServerDescription'
defaultMessage='When true, anyone can signup for a user account on this server without the need to be invited.'
/>
}
value={this.state.enableOpenServer}
onChange={this.handleChange}
/>
</SettingsGroup>
);
}
}

Просмотреть файл

@@ -1,390 +0,0 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import $ from 'jquery';
import ReactDOM from 'react-dom';
import Client from 'utils/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import crypto from 'crypto';
import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl';
const holders = defineMessages({
warning: {
id: 'admin.sql.warning',
defaultMessage: 'Warning: re-generating this salt may cause some columns in the database to return empty results.'
},
maxConnectionsExample: {
id: 'admin.sql.maxConnectionsExample',
defaultMessage: 'Ex "10"'
},
maxOpenExample: {
id: 'admin.sql.maxOpenExample',
defaultMessage: 'Ex "10"'
},
keyExample: {
id: 'admin.sql.keyExample',
defaultMessage: 'Ex "gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6"'
},
saving: {
id: 'admin.sql.saving',
defaultMessage: 'Saving Config...'
}
});
import React from 'react';
class SqlSettings extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleGenerate = this.handleGenerate.bind(this);
this.state = {
saveNeeded: false,
serverError: null
};
}
handleChange() {
var s = {saveNeeded: true, serverError: this.state.serverError};
this.setState(s);
}
handleSubmit(e) {
e.preventDefault();
$('#save-button').button('loading');
var config = this.props.config;
config.SqlSettings.Trace = ReactDOM.findDOMNode(this.refs.Trace).checked;
config.SqlSettings.AtRestEncryptKey = ReactDOM.findDOMNode(this.refs.AtRestEncryptKey).value.trim();
if (config.SqlSettings.AtRestEncryptKey === '') {
config.SqlSettings.AtRestEncryptKey = crypto.randomBytes(256).toString('base64').substring(0, 32);
ReactDOM.findDOMNode(this.refs.AtRestEncryptKey).value = config.SqlSettings.AtRestEncryptKey;
}
var MaxOpenConns = 10;
if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.MaxOpenConns).value, 10))) {
MaxOpenConns = parseInt(ReactDOM.findDOMNode(this.refs.MaxOpenConns).value, 10);
}
config.SqlSettings.MaxOpenConns = MaxOpenConns;
ReactDOM.findDOMNode(this.refs.MaxOpenConns).value = MaxOpenConns;
var MaxIdleConns = 10;
if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.MaxIdleConns).value, 10))) {
MaxIdleConns = parseInt(ReactDOM.findDOMNode(this.refs.MaxIdleConns).value, 10);
}
config.SqlSettings.MaxIdleConns = MaxIdleConns;
ReactDOM.findDOMNode(this.refs.MaxIdleConns).value = MaxIdleConns;
Client.saveConfig(
config,
() => {
AsyncClient.getConfig();
this.setState({
serverError: null,
saveNeeded: false
});
$('#save-button').button('reset');
},
(err) => {
this.setState({
serverError: err.message,
saveNeeded: true
});
$('#save-button').button('reset');
}
);
}
handleGenerate(e) {
e.preventDefault();
var cfm = global.window.confirm(this.props.intl.formatMessage(holders.warning));
if (cfm === false) {
return;
}
ReactDOM.findDOMNode(this.refs.AtRestEncryptKey).value = crypto.randomBytes(256).toString('base64').substring(0, 32);
var s = {saveNeeded: true, serverError: this.state.serverError};
this.setState(s);
}
render() {
const {formatMessage} = this.props.intl;
var serverError = '';
if (this.state.serverError) {
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
}
var saveClass = 'btn';
if (this.state.saveNeeded) {
saveClass = 'btn btn-primary';
}
var dataSource = '**********' + this.props.config.SqlSettings.DataSource.substring(this.props.config.SqlSettings.DataSource.indexOf('@'));
var dataSourceReplicas = '';
this.props.config.SqlSettings.DataSourceReplicas.forEach((replica) => {
dataSourceReplicas += '[**********' + replica.substring(replica.indexOf('@')) + '] ';
});
if (this.props.config.SqlSettings.DataSourceReplicas.length === 0) {
dataSourceReplicas = 'none';
}
return (
<div className='wrapper--fixed'>
<div className='banner'>
<div className='banner__content'>
<h4 className='banner__heading'>
<FormattedMessage
id='admin.sql.noteTitle'
defaultMessage='Note:'
/>
</h4>
<p>
<FormattedMessage
id='admin.sql.noteDescription'
defaultMessage='Changing properties in this section will require a server restart before taking effect.'
/>
</p>
</div>
</div>
<h3>
<FormattedMessage
id='admin.sql.title'
defaultMessage='SQL Settings'
/>
</h3>
<form
className='form-horizontal'
role='form'
>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='DriverName'
>
<FormattedMessage
id='admin.sql.driverName'
defaultMessage='Driver Name:'
/>
</label>
<div className='col-sm-8'>
<p className='help-text'>{this.props.config.SqlSettings.DriverName}</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='DataSource'
>
<FormattedMessage
id='admin.sql.dataSource'
defaultMessage='Data Source:'
/>
</label>
<div className='col-sm-8'>
<p className='help-text'>{dataSource}</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='DataSourceReplicas'
>
<FormattedMessage
id='admin.sql.replicas'
defaultMessage='Data Source Replicas:'
/>
</label>
<div className='col-sm-8'>
<p className='help-text'>{dataSourceReplicas}</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='MaxIdleConns'
>
<FormattedMessage
id='admin.sql.maxConnectionsTitle'
defaultMessage='Maximum Idle Connections:'
/>
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='MaxIdleConns'
ref='MaxIdleConns'
placeholder={formatMessage(holders.maxConnectionsExample)}
defaultValue={this.props.config.SqlSettings.MaxIdleConns}
onChange={this.handleChange}
/>
<p className='help-text'>
<FormattedMessage
id='admin.sql.maxConnectionsDescription'
defaultMessage='Maximum number of idle connections held open to the database.'
/>
</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='MaxOpenConns'
>
<FormattedMessage
id='admin.sql.maxOpenTitle'
defaultMessage='Maximum Open Connections:'
/>
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='MaxOpenConns'
ref='MaxOpenConns'
placeholder={formatMessage(holders.maxOpenExample)}
defaultValue={this.props.config.SqlSettings.MaxOpenConns}
onChange={this.handleChange}
/>
<p className='help-text'>
<FormattedMessage
id='admin.sql.maxOpenDescription'
defaultMessage='Maximum number of open connections held open to the database.'
/>
</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='AtRestEncryptKey'
>
<FormattedMessage
id='admin.sql.keyTitle'
defaultMessage='At Rest Encrypt Key:'
/>
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='AtRestEncryptKey'
ref='AtRestEncryptKey'
placeholder={formatMessage(holders.keyExample)}
defaultValue={this.props.config.SqlSettings.AtRestEncryptKey}
onChange={this.handleChange}
/>
<p className='help-text'>
<FormattedMessage
id='admin.sql.keyDescription'
defaultMessage='32-character salt available to encrypt and decrypt sensitive fields in database.'
/>
</p>
<div className='help-text'>
<button
className='btn btn-default'
onClick={this.handleGenerate}
>
<FormattedMessage
id='admin.sql.regenerate'
defaultMessage='Re-Generate'
/>
</button>
</div>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='Trace'
>
<FormattedMessage
id='admin.sql.traceTitle'
defaultMessage='Trace: '
/>
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
<input
type='radio'
name='Trace'
value='true'
ref='Trace'
defaultChecked={this.props.config.SqlSettings.Trace}
onChange={this.handleChange}
/>
<FormattedMessage
id='admin.sql.true'
defaultMessage='true'
/>
</label>
<label className='radio-inline'>
<input
type='radio'
name='Trace'
value='false'
defaultChecked={!this.props.config.SqlSettings.Trace}
onChange={this.handleChange}
/>
<FormattedMessage
id='admin.sql.false'
defaultMessage='false'
/>
</label>
<p className='help-text'>
<FormattedMessage
id='admin.sql.traceDescription'
defaultMessage='(Development Mode) When true, executing SQL statements are written to the log.'
/>
</p>
</div>
</div>
<div className='form-group'>
<div className='col-sm-12'>
{serverError}
<button
disabled={!this.state.saveNeeded}
type='submit'
className={saveClass}
onClick={this.handleSubmit}
id='save-button'
data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + formatMessage(holders.saving)}
>
<FormattedMessage
id='admin.sql.save'
defaultMessage='Save'
/>
</button>
</div>
</div>
</form>
</div>
);
}
}
SqlSettings.propTypes = {
intl: intlShape.isRequired,
config: React.PropTypes.object
};
export default injectIntl(SqlSettings);

Просмотреть файл

@@ -0,0 +1,180 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import * as Utils from 'utils/utils.jsx';
import AdminSettings from './admin_settings.jsx';
import DropdownSetting from './dropdown_setting.jsx';
import {FormattedMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
const DRIVER_LOCAL = 'local';
const DRIVER_S3 = 'amazons3';
export default class StorageSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
this.state = Object.assign(this.state, {
driverName: props.config.FileSettings.DriverName,
directory: props.config.FileSettings.Directory,
amazonS3AccessKeyId: props.config.FileSettings.AmazonS3AccessKeyId,
amazonS3SecretAccessKey: props.config.FileSettings.AmazonS3SecretAccessKey,
amazonS3Bucket: props.config.FileSettings.AmazonS3Bucket,
amazonS3Region: props.config.FileSettings.AmazonS3Region
});
}
getConfigFromState(config) {
config.FileSettings.DriverName = this.state.driverName;
config.FileSettings.Directory = this.state.directory;
config.FileSettings.AmazonS3AccessKeyId = this.state.amazonS3AccessKeyId;
config.FileSettings.AmazonS3SecretAccessKey = this.state.amazonS3SecretAccessKey;
config.FileSettings.AmazonS3Bucket = this.state.amazonS3Bucket;
config.FileSettings.AmazonS3Region = this.state.amazonS3Region;
return config;
}
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.files.title'
defaultMessage='File Settings'
/>
</h3>
);
}
renderSettings() {
return (
<SettingsGroup
header={
<FormattedMessage
id='admin.files.storage'
defaultMessage='Storage'
/>
}
>
<DropdownSetting
id='driverName'
values={[
{value: DRIVER_LOCAL, text: Utils.localizeMessage('admin.image.storeLocal', 'Local File System')},
{value: DRIVER_S3, text: Utils.localizeMessage('admin.image.storeAmazonS3', 'Amazon S3')}
]}
label={
<FormattedMessage
id='admin.image.storeTitle'
defaultMessage='Store Files In:'
/>
}
value={this.state.driverName}
onChange={this.handleChange}
/>
<TextSetting
id='directory'
label={
<FormattedMessage
id='admin.image.localTitle'
defaultMessage='Local Directory Location:'
/>
}
placeholder={Utils.localizeMessage('admin.image.localExample', 'Ex "./data/"')}
helpText={
<FormattedMessage
id='admin.image.localDescription'
defaultMessage='Directory to which image files are written. If blank, will be set to ./data/.'
/>
}
value={this.state.directory}
onChange={this.handleChange}
disabled={this.state.driverName !== DRIVER_LOCAL}
/>
<TextSetting
id='amazonS3AccessKeyId'
label={
<FormattedMessage
id='admin.image.amazonS3IdTitle'
defaultMessage='Amazon S3 Access Key Id:'
/>
}
placeholder={Utils.localizeMessage('admin.image.amazonS3IdExample', 'Ex "AKIADTOVBGERKLCBV"')}
helpText={
<FormattedMessage
id='admin.image.amazonS3IdDescription'
defaultMessage='Obtain this credential from your Amazon EC2 administrator.'
/>
}
value={this.state.amazonS3AccessKeyId}
onChange={this.handleChange}
disabled={this.state.driverName !== DRIVER_S3}
/>
<TextSetting
id='amazonS3SecretAccessKey'
label={
<FormattedMessage
id='admin.image.amazonS3SecretTitle'
defaultMessage='Amazon S3 Secret Access Key:'
/>
}
placeholder={Utils.localizeMessage('admin.image.amazonS3SecretExample', 'Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"')}
helpText={
<FormattedMessage
id='admin.image.amazonS3SecretDescription'
defaultMessage='Obtain this credential from your Amazon EC2 administrator.'
/>
}
value={this.state.amazonS3SecretAccessKey}
onChange={this.handleChange}
disabled={this.state.driverName !== DRIVER_S3}
/>
<TextSetting
id='amazonS3Bucket'
label={
<FormattedMessage
id='admin.image.amazonS3BucketTitle'
defaultMessage='Amazon S3 Bucket:'
/>
}
placeholder={Utils.localizeMessage('admin.image.amazonS3BucketExample', 'Ex "mattermost-media"')}
helpText={
<FormattedMessage
id='admin.image.amazonS3BucketDescription'
defaultMessage='Name you selected for your S3 bucket in AWS.'
/>
}
value={this.state.amazonS3Bucket}
onChange={this.handleChange}
disabled={this.state.driverName !== DRIVER_S3}
/>
<TextSetting
id='amazonS3Region'
label={
<FormattedMessage
id='admin.image.amazonS3RegionTitle'
defaultMessage='Amazon S3 Region:'
/>
}
placeholder={Utils.localizeMessage('admin.image.amazonS3RegionExample', 'Ex "us-east-1"')}
helpText={
<FormattedMessage
id='admin.image.amazonS3RegionDescription'
defaultMessage='AWS region you selected for creating your S3 bucket.'
/>
}
value={this.state.amazonS3Region}
onChange={this.handleChange}
disabled={this.state.driverName !== DRIVER_S3}
/>
</SettingsGroup>
);
}
}

Просмотреть файл

@@ -1,7 +1,9 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
import AdminStore from 'stores/admin_store.jsx';
import Client from 'utils/web_client.jsx'; import Client from 'utils/web_client.jsx';
import FormError from 'components/form_error.jsx';
import LoadingScreen from '../loading_screen.jsx'; import LoadingScreen from '../loading_screen.jsx';
import UserItem from './user_item.jsx'; import UserItem from './user_item.jsx';
import ResetPasswordModal from './reset_password_modal.jsx'; import ResetPasswordModal from './reset_password_modal.jsx';
@@ -11,9 +13,17 @@ import {FormattedMessage} from 'react-intl';
import React from 'react'; import React from 'react';
export default class UserList extends React.Component { export default class UserList extends React.Component {
static get propTypes() {
return {
params: React.PropTypes.object.isRequired
};
}
constructor(props) { constructor(props) {
super(props); super(props);
this.onAllTeamsChange = this.onAllTeamsChange.bind(this);
this.getTeamProfiles = this.getTeamProfiles.bind(this); this.getTeamProfiles = this.getTeamProfiles.bind(this);
this.getCurrentTeamProfiles = this.getCurrentTeamProfiles.bind(this); this.getCurrentTeamProfiles = this.getCurrentTeamProfiles.bind(this);
this.doPasswordReset = this.doPasswordReset.bind(this); this.doPasswordReset = this.doPasswordReset.bind(this);
@@ -22,7 +32,7 @@ export default class UserList extends React.Component {
this.getTeamMemberForUser = this.getTeamMemberForUser.bind(this); this.getTeamMemberForUser = this.getTeamMemberForUser.bind(this);
this.state = { this.state = {
teamId: props.team.id, team: AdminStore.getTeam(this.props.params.team),
users: null, users: null,
teamMembers: null, teamMembers: null,
serverError: null, serverError: null,
@@ -35,8 +45,14 @@ export default class UserList extends React.Component {
this.getCurrentTeamProfiles(); this.getCurrentTeamProfiles();
} }
onAllTeamsChange() {
this.setState({
team: AdminStore.getTeam(this.props.params.team)
});
}
getCurrentTeamProfiles() { getCurrentTeamProfiles() {
this.getTeamProfiles(this.props.team.id); this.getTeamProfiles(this.props.params.team);
} }
getTeamProfiles(teamId) { getTeamProfiles(teamId) {
@@ -133,9 +149,8 @@ export default class UserList extends React.Component {
} }
render() { render() {
var serverError = ''; if (!this.state.team) {
if (this.state.serverError) { return null;
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
} }
if (this.state.users == null || this.state.teamMembers == null) { if (this.state.users == null || this.state.teamMembers == null) {
@@ -146,11 +161,11 @@ export default class UserList extends React.Component {
id='admin.userList.title' id='admin.userList.title'
defaultMessage='Users for {team}' defaultMessage='Users for {team}'
values={{ values={{
team: this.props.team.name team: this.state.team.name
}} }}
/> />
</h3> </h3>
{serverError} <FormError error={this.state.serverError}/>
<LoadingScreen/> <LoadingScreen/>
</div> </div>
); );
@@ -161,7 +176,7 @@ export default class UserList extends React.Component {
return ( return (
<UserItem <UserItem
team={this.props.team} team={this.state.team}
key={'user_' + user.id} key={'user_' + user.id}
user={user} user={user}
teamMember={teamMember} teamMember={teamMember}
@@ -177,12 +192,12 @@ export default class UserList extends React.Component {
id='admin.userList.title2' id='admin.userList.title2'
defaultMessage='Users for {team} ({count})' defaultMessage='Users for {team} ({count})'
values={{ values={{
team: this.props.team.name, team: this.state.team.name,
count: this.state.users.length count: this.state.users.length
}} }}
/> />
</h3> </h3>
{serverError} <FormError error={this.state.serverError}/>
<form <form
className='form-horizontal' className='form-horizontal'
role='form' role='form'
@@ -194,7 +209,7 @@ export default class UserList extends React.Component {
<ResetPasswordModal <ResetPasswordModal
user={this.state.user} user={this.state.user}
show={this.state.showPasswordModal} show={this.state.showPasswordModal}
team={this.props.team} team={this.state.team}
onModalSubmit={this.doPasswordResetSubmit} onModalSubmit={this.doPasswordResetSubmit}
onModalDismissed={this.doPasswordResetDismiss} onModalDismissed={this.doPasswordResetDismiss}
/> />
@@ -202,7 +217,3 @@ export default class UserList extends React.Component {
); );
} }
} }
UserList.propTypes = {
team: React.PropTypes.object
};

Просмотреть файл

@@ -0,0 +1,83 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import Setting from './setting.jsx';
export default class TextSetting extends React.Component {
static get propTypes() {
return {
id: React.PropTypes.string.isRequired,
label: React.PropTypes.node.isRequired,
placeholder: React.PropTypes.string,
helpText: React.PropTypes.node,
value: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.number
]).isRequired,
onChange: React.PropTypes.func.isRequired,
disabled: React.PropTypes.bool,
type: React.PropTypes.oneOf([
'input',
'textarea'
])
};
}
static get defaultProps() {
return {
type: 'input'
};
}
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.props.onChange(this.props.id, e.target.value);
}
render() {
let input = null;
if (this.props.type === 'input') {
input = (
<input
id={this.props.id}
className='form-control'
type='text'
placeholder={this.props.placeholder}
value={this.props.value}
onChange={this.handleChange}
disabled={this.props.disabled}
/>
);
} else if (this.props.type === 'textarea') {
input = (
<textarea
id={this.props.id}
className='form-control'
rows='5'
maxLength='1024'
placeholder={this.props.placeholder}
value={this.props.value}
onChange={this.handleChange}
disabled={this.props.disabled}
/>
);
}
return (
<Setting
label={this.props.label}
helpText={this.props.helpText}
inputId={this.props.id}
>
{input}
</Setting>
);
}
}

Просмотреть файл

@@ -0,0 +1,179 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import * as Utils from 'utils/utils.jsx';
import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import DropdownSetting from './dropdown_setting.jsx';
import {FormattedHTMLMessage, FormattedMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
const RESTRICT_DIRECT_MESSAGE_ANY = 'any';
const RESTRICT_DIRECT_MESSAGE_TEAM = 'team';
export default class UsersAndTeamsSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
this.state = Object.assign(this.state, {
enableUserCreation: props.config.TeamSettings.EnableUserCreation,
enableTeamCreation: props.config.TeamSettings.EnableTeamCreation,
maxUsersPerTeam: props.config.TeamSettings.MaxUsersPerTeam,
restrictCreationToDomains: props.config.TeamSettings.RestrictCreationToDomains,
restrictTeamNames: props.config.TeamSettings.RestrictTeamNames,
restrictDirectMessage: props.config.TeamSettings.RestrictDirectMessage
});
}
getConfigFromState(config) {
config.TeamSettings.EnableUserCreation = this.state.enableUserCreation;
config.TeamSettings.EnableTeamCreation = this.state.enableTeamCreation;
config.TeamSettings.MaxUsersPerTeam = this.parseIntNonZero(this.state.maxUsersPerTeam);
config.TeamSettings.RestrictCreationToDomains = this.state.restrictCreationToDomains;
config.TeamSettings.RestrictTeamNames = this.state.restrictTeamNames;
config.TeamSettings.RestrictDirectMessage = this.state.restrictDirectMessage;
return config;
}
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.general.title'
defaultMessage='General Settings'
/>
</h3>
);
}
renderSettings() {
return (
<SettingsGroup
header={
<FormattedMessage
id='admin.general.usersAndTeams'
defaultMessage='Users and Teams'
/>
}
>
<BooleanSetting
id='enableUserCreation'
label={
<FormattedMessage
id='admin.team.userCreationTitle'
defaultMessage='Enable User Creation: '
/>
}
helpText={
<FormattedMessage
id='admin.team.userCreationDescription'
defaultMessage='When false, the ability to create accounts is disabled. The create account button displays error when pressed.'
/>
}
value={this.state.enableUserCreation}
onChange={this.handleChange}
/>
<BooleanSetting
id='enableTeamCreation'
label={
<FormattedMessage
id='admin.team.teamCreationTitle'
defaultMessage='Enable Team Creation: '
/>
}
helpText={
<FormattedMessage
id='admin.team.teamCreationDescription'
defaultMessage='When false, the ability to create teams is disabled. The create team button displays error when pressed.'
/>
}
value={this.state.enableTeamCreation}
onChange={this.handleChange}
/>
<TextSetting
id='maxUsersPerTeam'
label={
<FormattedMessage
id='admin.team.maxUsersTitle'
defaultMessage='Max Users Per Team:'
/>
}
placeholder={Utils.localizeMessage('admin.team.maxUsersExample', 'Ex "25"')}
helpText={
<FormattedMessage
id='admin.team.maxUsersDescription'
defaultMessage='Maximum total number of users per team, including both active and inactive users.'
/>
}
value={this.state.maxUsersPerTeam}
onChange={this.handleChange}
/>
<TextSetting
id='restrictCreationToDomains'
label={
<FormattedMessage
id='admin.team.restrictTitle'
defaultMessage='Restrict Creation To Domains:'
/>
}
placeholder={Utils.localizeMessage('admin.team.restrictExample', 'Ex "corp.mattermost.com, mattermost.org"')}
helpText={
<FormattedMessage
id='admin.team.restrictDescription'
defaultMessage='Teams and user accounts can only be created from a specific domain (e.g. "mattermost.org") or list of comma-separated domains (e.g. "corp.mattermost.com, mattermost.org").'
/>
}
value={this.state.restrictCreationToDomains}
onChange={this.handleChange}
/>
<BooleanSetting
id='restrictTeamNames'
label={
<FormattedMessage
id='admin.team.restrictNameTitle'
defaultMessage='Restrict Team Names: '
/>
}
helpText={
<FormattedMessage
id='admin.team.restrictNameDesc'
defaultMessage='When true, You cannot create a team name with reserved words like www, admin, support, test, channel, etc'
/>
}
value={this.state.restrictTeamNames}
onChange={this.handleChange}
/>
<DropdownSetting
id='restrictDirectMessage'
values={[
{value: RESTRICT_DIRECT_MESSAGE_ANY, text: Utils.localizeMessage('admin.team.restrict_direct_message_any', 'Any user on the Mattermost server')},
{value: RESTRICT_DIRECT_MESSAGE_TEAM, text: Utils.localizeMessage('admin.team.restrict_direct_message_team', 'Any member of the team')}
]}
label={
<FormattedMessage
id='admin.team.restrictDirectMessage'
defaultMessage='Enable users to open Direct Message channels with:'
/>
}
helpText={
<FormattedHTMLMessage
id='admin.team.restrictDirectMessageDesc'
defaultMessage='"Any user on the Mattermost server" enables users to open a Direct Message channel with any user on the server, even if they are not on any teams together. "Any member of the team" limits the ability to open Direct Message channels to only users who are in the same team.'
/>
}
value={this.state.restrictDirectMessage}
onChange={this.handleChange}
/>
</SettingsGroup>
);
}
}

Просмотреть файл

@@ -0,0 +1,166 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import {FormattedMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
export default class WebhookSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
this.state = Object.assign(this.state, {
enableIncomingWebhooks: props.config.ServiceSettings.EnableIncomingWebhooks,
enableOutgoingWebhooks: props.config.ServiceSettings.EnableOutgoingWebhooks,
enableCommands: props.config.ServiceSettings.EnableCommands,
enableOnlyAdminIntegrations: props.config.ServiceSettings.EnableOnlyAdminIntegrations,
enablePostUsernameOverride: props.config.ServiceSettings.EnablePostUsernameOverride,
enablePostIconOverride: props.config.ServiceSettings.EnablePostIconOverride
});
}
getConfigFromState(config) {
config.ServiceSettings.EnableIncomingWebhooks = this.state.enableIncomingWebhooks;
config.ServiceSettings.EnableOutgoingWebhooks = this.state.enableOutgoingWebhooks;
config.ServiceSettings.EnableCommands = this.state.enableCommands;
config.ServiceSettings.EnableOnlyAdminIntegrations = this.state.enableOnlyAdminIntegrations;
config.ServiceSettings.EnablePostUsernameOverride = this.state.enablePostUsernameOverride;
config.ServiceSettings.EnablePostIconOverride = this.state.enablePostIconOverride;
return config;
}
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.integration.title'
defaultMessage='Integration Settings'
/>
</h3>
);
}
renderSettings() {
return (
<SettingsGroup
header={
<FormattedMessage
id='admin.integrations.webhook'
defaultMessage='Webhooks and Commands'
/>
}
>
<BooleanSetting
id='enableIncomingWebhooks'
label={
<FormattedMessage
id='admin.service.webhooksTitle'
defaultMessage='Enable Incoming Webhooks: '
/>
}
helpText={
<FormattedMessage
id='admin.service.webhooksDescription'
defaultMessage='When true, incoming webhooks will be allowed. To help combat phishing attacks, all posts from webhooks will be labelled by a BOT tag.'
/>
}
value={this.state.enableIncomingWebhooks}
onChange={this.handleChange}
/>
<BooleanSetting
id='enableOutgoingWebhooks'
label={
<FormattedMessage
id='admin.service.outWebhooksTitle'
defaultMessage='Enable Outgoing Webhooks: '
/>
}
helpText={
<FormattedMessage
id='admin.service.outWebhooksDesc'
defaultMessage='When true, outgoing webhooks will be allowed.'
/>
}
value={this.state.enableOutgoingWebhooks}
onChange={this.handleChange}
/>
<BooleanSetting
id='enableCommands'
label={
<FormattedMessage
id='admin.service.cmdsTitle'
defaultMessage='Enable Slash Commands: '
/>
}
helpText={
<FormattedMessage
id='admin.service.cmdsDesc'
defaultMessage='When true, user created slash commands will be allowed.'
/>
}
value={this.state.enableCommands}
onChange={this.handleChange}
/>
<BooleanSetting
id='enableOnlyAdminIntegrations'
label={
<FormattedMessage
id='admin.service.integrationAdmin'
defaultMessage='Enable Integrations for Admin Only: '
/>
}
helpText={
<FormattedMessage
id='admin.service.integrationAdminDesc'
defaultMessage='When true, user created integrations can only be created by admins.'
/>
}
value={this.state.enableOnlyAdminIntegrations}
onChange={this.handleChange}
/>
<BooleanSetting
id='enablePostUsernameOverride'
label={
<FormattedMessage
id='admin.service.overrideTitle'
defaultMessage='Enable Overriding Usernames from Webhooks and Slash Commands: '
/>
}
helpText={
<FormattedMessage
id='admin.service.overrideDescription'
defaultMessage='When true, webhooks and slash commands will be allowed to change the username they are posting as. Note, combined with allowing icon overriding, this could open users up to phishing attacks.'
/>
}
value={this.state.enablePostUsernameOverride}
onChange={this.handleChange}
/>
<BooleanSetting
id='enablePostIconOverride'
label={
<FormattedMessage
id='admin.service.iconTitle'
defaultMessage='Enable Overriding Icon from Webhooks and Slash Commands: '
/>
}
helpText={
<FormattedMessage
id='admin.service.iconDescription'
defaultMessage='When true, webhooks and slash commands will be allowed to change the icon they post with. Note, combined with allowing username overriding, this could open users up to phishing attacks.'
/>
}
value={this.state.enablePostIconOverride}
onChange={this.handleChange}
/>
</SettingsGroup>
);
}
}

Просмотреть файл

@@ -245,8 +245,7 @@ class SystemAnalytics extends React.Component {
} }
SystemAnalytics.propTypes = { SystemAnalytics.propTypes = {
intl: intlShape.isRequired, intl: intlShape.isRequired
team: React.PropTypes.object
}; };
export default injectIntl(SystemAnalytics); export default injectIntl(SystemAnalytics);

Просмотреть файл

@@ -5,6 +5,7 @@ import LineChart from './line_chart.jsx';
import StatisticCount from './statistic_count.jsx'; import StatisticCount from './statistic_count.jsx';
import TableChart from './table_chart.jsx'; import TableChart from './table_chart.jsx';
import AdminStore from 'stores/admin_store.jsx';
import AnalyticsStore from 'stores/analytics_store.jsx'; import AnalyticsStore from 'stores/analytics_store.jsx';
import * as Utils from 'utils/utils.jsx'; import * as Utils from 'utils/utils.jsx';
@@ -13,23 +14,34 @@ import Constants from 'utils/constants.jsx';
const StatTypes = Constants.StatTypes; const StatTypes = Constants.StatTypes;
import {formatPostsPerDayData, formatUsersWithPostsPerDayData} from './system_analytics.jsx'; import {formatPostsPerDayData, formatUsersWithPostsPerDayData} from './system_analytics.jsx';
import {injectIntl, intlShape, FormattedMessage, FormattedDate} from 'react-intl'; import {FormattedMessage, FormattedDate} from 'react-intl';
import React from 'react'; import React from 'react';
class TeamAnalytics extends React.Component { export default class TeamAnalytics extends React.Component {
static get propTypes() {
return {
params: React.PropTypes.object.isRequired
};
}
constructor(props) { constructor(props) {
super(props); super(props);
this.onChange = this.onChange.bind(this); this.onChange = this.onChange.bind(this);
this.onAllTeamsChange = this.onAllTeamsChange.bind(this);
this.state = {stats: AnalyticsStore.getAllTeam(this.props.team.id)}; this.state = {
team: AdminStore.getTeam(this.props.params.team),
stats: AnalyticsStore.getAllTeam(this.props.params.team)
};
} }
componentDidMount() { componentDidMount() {
AnalyticsStore.addChangeListener(this.onChange); AnalyticsStore.addChangeListener(this.onChange);
AdminStore.addAllTeamsChangeListener(this.onAllTeamsChange);
this.getData(this.props.team.id); this.getData(this.props.params.team);
} }
getData(id) { getData(id) {
@@ -41,11 +53,14 @@ class TeamAnalytics extends React.Component {
componentWillUnmount() { componentWillUnmount() {
AnalyticsStore.removeChangeListener(this.onChange); AnalyticsStore.removeChangeListener(this.onChange);
AdminStore.removeAllTeamsChangeListener(this.onAllTeamsChange);
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
this.getData(nextProps.team.id); this.getData(nextProps.params.team);
this.setState({stats: AnalyticsStore.getAllTeam(nextProps.team.id)}); this.setState({
stats: AnalyticsStore.getAllTeam(nextProps.params.team)
});
} }
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
@@ -53,7 +68,7 @@ class TeamAnalytics extends React.Component {
return true; return true;
} }
if (!Utils.areObjectsEqual(nextProps.team, this.props.team)) { if (!Utils.areObjectsEqual(nextProps.params.team, this.props.params.team)) {
return true; return true;
} }
@@ -61,10 +76,22 @@ class TeamAnalytics extends React.Component {
} }
onChange() { onChange() {
this.setState({stats: AnalyticsStore.getAllTeam(this.props.team.id)}); this.setState({
stats: AnalyticsStore.getAllTeam(this.props.params.team)
});
}
onAllTeamsChange() {
this.setState({
team: AdminStore.getTeam(this.props.params.team)
});
} }
render() { render() {
if (!this.state.team || !this.state.stats) {
return null;
}
const stats = this.state.stats; const stats = this.state.stats;
const postCountsDay = formatPostsPerDayData(stats[StatTypes.POST_PER_DAY]); const postCountsDay = formatPostsPerDayData(stats[StatTypes.POST_PER_DAY]);
const userCountsWithPostsDay = formatUsersWithPostsPerDayData(stats[StatTypes.USERS_WITH_POSTS_PER_DAY]); const userCountsWithPostsDay = formatUsersWithPostsPerDayData(stats[StatTypes.USERS_WITH_POSTS_PER_DAY]);
@@ -78,7 +105,7 @@ class TeamAnalytics extends React.Component {
id='analytics.team.title' id='analytics.team.title'
defaultMessage='Team Statistics for {team}' defaultMessage='Team Statistics for {team}'
values={{ values={{
team: this.props.team.name team: this.state.team.name
}} }}
/> />
</h3> </h3>
@@ -175,13 +202,6 @@ class TeamAnalytics extends React.Component {
} }
} }
TeamAnalytics.propTypes = {
intl: intlShape.isRequired,
team: React.PropTypes.object.isRequired
};
export default injectIntl(TeamAnalytics);
export function formatRecentUsersData(data) { export function formatRecentUsersData(data) {
if (data == null) { if (data == null) {
return []; return [];

Просмотреть файл

@@ -436,29 +436,46 @@
"admin.service.webhooksTitle": "Enable Incoming Webhooks: ", "admin.service.webhooksTitle": "Enable Incoming Webhooks: ",
"admin.sidebar.addTeamSidebar": "Add team from sidebar menu", "admin.sidebar.addTeamSidebar": "Add team from sidebar menu",
"admin.sidebar.audits": "Compliance and Auditing", "admin.sidebar.audits": "Compliance and Auditing",
"admin.sidebar.compliance": "Compliance Settings", "admin.sidebar.authentication": "Authentication",
"admin.sidebar.email": "Email Settings", "admin.sidebar.compliance": "Compliance",
"admin.sidebar.file": "File Settings", "admin.sidebar.configuration": "Configuration",
"admin.sidebar.gitlab": "GitLab Settings", "admin.sidebar.connections": "Connections",
"admin.sidebar.ldap": "LDAP Settings", "admin.sidebar.customBrand": "Custom Branding",
"admin.sidebar.customization": "Customization",
"admin.sidebar.database": "Database",
"admin.sidebar.developer": "Developer",
"admin.sidebar.email": "Email",
"admin.sidebar.external": "External Services",
"admin.sidebar.files": "Files",
"admin.sidebar.general": "General",
"admin.sidebar.gitlab": "GitLab",
"admin.sidebar.integrations": "Integrations",
"admin.sidebar.images": "Images",
"admin.sidebar.ldap": "LDAP",
"admin.sidebar.license": "Edition and License", "admin.sidebar.license": "Edition and License",
"admin.sidebar.loading": "Loading", "admin.sidebar.logging": "Logging",
"admin.sidebar.log": "Log Settings", "admin.sidebar.login": "Login",
"admin.sidebar.logs": "Logs", "admin.sidebar.logs": "Logs",
"admin.sidebar.notifications": "Notifications",
"admin.sidebar.other": "OTHER", "admin.sidebar.other": "OTHER",
"admin.sidebar.privacy": "Privacy Settings", "admin.sidebar.privacy": "Privacy",
"admin.sidebar.rate_limit": "Rate Limit Settings", "admin.sidebar.publicLinks": "Public Links",
"admin.sidebar.push": "Mobile Push",
"admin.sidebar.rateLimiting": "Rate Limiting",
"admin.sidebar.reports": "SITE REPORTS", "admin.sidebar.reports": "SITE REPORTS",
"admin.sidebar.rmTeamSidebar": "Remove team from sidebar menu", "admin.sidebar.rmTeamSidebar": "Remove team from sidebar menu",
"admin.sidebar.service": "Service Settings", "admin.sidebar.security": "Security",
"admin.sidebar.sign_up": "Sign Up",
"admin.sidebar.sessions": "Sessions",
"admin.sidebar.settings": "SETTINGS", "admin.sidebar.settings": "SETTINGS",
"admin.sidebar.sql": "SQL Settings", "admin.sidebar.statistics": "Statistics",
"admin.sidebar.statistics": "- Statistics", "admin.sidebar.storage": "Storage",
"admin.sidebar.support": "Legal and Support Settings", "admin.sidebar.support": "Legal and Support",
"admin.sidebar.team": "Team Settings", "admin.sidebar.teams": "TEAMS ({count, number})",
"admin.sidebar.teams": "TEAMS ({count})", "admin.sidebar.users": "Users",
"admin.sidebar.users": "- Users", "admin.sidebar.usersAndTeams": "Users and Teams",
"admin.sidebar.view_statistics": "View Statistics", "admin.sidebar.view_statistics": "View Statistics",
"admin.sidebar.webhooks": "Webhooks and Commands",
"admin.sidebarHeader.systemConsole": "System Console", "admin.sidebarHeader.systemConsole": "System Console",
"admin.sql.dataSource": "Data Source:", "admin.sql.dataSource": "Data Source:",
"admin.sql.driverName": "Driver Name:", "admin.sql.driverName": "Driver Name:",

Просмотреть файл

@@ -435,30 +435,25 @@
"admin.service.webhooksDescription": "Cuando es verdadero, la entradas de webhooks será permitida. Para ayudar a combatir ataques phishing, todos los comentarios de webhooks serán marcados con una etiqueta BOT.", "admin.service.webhooksDescription": "Cuando es verdadero, la entradas de webhooks será permitida. Para ayudar a combatir ataques phishing, todos los comentarios de webhooks serán marcados con una etiqueta BOT.",
"admin.service.webhooksTitle": "Habilitar Webhooks de Entrada: ", "admin.service.webhooksTitle": "Habilitar Webhooks de Entrada: ",
"admin.sidebar.addTeamSidebar": "Agregar un equipo el menú lateral", "admin.sidebar.addTeamSidebar": "Agregar un equipo el menú lateral",
"admin.sidebar.audits": "Auditorías", "admin.sidebar.gitlab": "GitLab",
"admin.sidebar.compliance": "Configuración de Cumplimiento", "admin.sidebar.ldap": "LDAP",
"admin.sidebar.email": "Configuración de correo",
"admin.sidebar.file": "Configuracion de archivos",
"admin.sidebar.gitlab": "Configuración de GitLab",
"admin.sidebar.ldap": "Configuración LDAP",
"admin.sidebar.license": "Edición y Licencia", "admin.sidebar.license": "Edición y Licencia",
"admin.sidebar.loading": "Cargando",
"admin.sidebar.log": "Configuracion de log",
"admin.sidebar.logs": "Registros", "admin.sidebar.logs": "Registros",
"admin.sidebar.other": "OTROS", "admin.sidebar.other": "OTROS",
"admin.sidebar.privacy": "Configuración de privacidad",
"admin.sidebar.rate_limit": "Configuración de velocidad",
"admin.sidebar.reports": "REPORTES DEL SITIO", "admin.sidebar.reports": "REPORTES DEL SITIO",
"admin.sidebar.rmTeamSidebar": "Remover un equipo del menú lateral", "admin.sidebar.rmTeamSidebar": "Remover un equipo del menú lateral",
"admin.sidebar.service": "Configuración de servicio",
"admin.sidebar.settings": "CONFIGURACIONES", "admin.sidebar.settings": "CONFIGURACIONES",
"admin.sidebar.sql": "Configuración de SQL", "admin.sidebar.statistics": "Estadísticas",
"admin.sidebar.statistics": "- Estadísticas", "admin.sidebar.teams": "EQUIPOS ({count, number})",
"admin.sidebar.support": "Configuración de Soporte", "admin.sidebar.users": "Usuarios",
"admin.sidebar.team": "Configuración de equipo",
"admin.sidebar.teams": "EQUIPOS ({count})",
"admin.sidebar.users": "- Usuarios",
"admin.sidebar.view_statistics": "Ver Estadísticas", "admin.sidebar.view_statistics": "Ver Estadísticas",
"admin.sidebar.file": "Configuracion de archivos",
"admin.sidebar.loading": "Cargando",
"admin.sidebar.log": "Configuracion de log",
"admin.sidebar.rate_limit": "Configuración de velocidad",
"admin.sidebar.service": "Configuración de servicio",
"admin.sidebar.sql": "Configuración de SQL",
"admin.sidebar.team": "Configuración de equipo",
"admin.sidebarHeader.systemConsole": "Consola de sistema", "admin.sidebarHeader.systemConsole": "Consola de sistema",
"admin.sql.dataSource": "Origen de datos:", "admin.sql.dataSource": "Origen de datos:",
"admin.sql.driverName": "Nombre de controlador:", "admin.sql.driverName": "Nombre de controlador:",

Просмотреть файл

@@ -370,29 +370,27 @@
"admin.service.webhooksTitle": "Activer les webhooks entrants :", "admin.service.webhooksTitle": "Activer les webhooks entrants :",
"admin.sidebar.addTeamSidebar": "Afficher l'équipe dans le menu", "admin.sidebar.addTeamSidebar": "Afficher l'équipe dans le menu",
"admin.sidebar.audits": "Conformité et vérification", "admin.sidebar.audits": "Conformité et vérification",
"admin.sidebar.compliance": "Paramètres de conformité", "admin.sidebar.gitlab": "GitLab",
"admin.sidebar.email": "Configuration messagerie",
"admin.sidebar.file": "Configuration du fichier",
"admin.sidebar.gitlab": "Configuration de GitLab",
"admin.sidebar.ldap": "Paramètres LDAP",
"admin.sidebar.license": "Édition et licence",
"admin.sidebar.loading": "Chargement",
"admin.sidebar.log": "Configuration du journal (log)",
"admin.sidebar.logs": "Journaux",
"admin.sidebar.other": "AUTRES", "admin.sidebar.other": "AUTRES",
"admin.sidebar.privacy": "Paramètres de confidentialité", "admin.sidebar.ldap": "LDAP",
"admin.sidebar.rate_limit": "Configuration des limites de débits", "admin.sidebar.license": "Édition et licence",
"admin.sidebar.logs": "Journaux",
"admin.sidebar.reports": "RAPPORTS DU SITE", "admin.sidebar.reports": "RAPPORTS DU SITE",
"admin.sidebar.rmTeamSidebar": "Ne plus afficher l'équipe dans le menu", "admin.sidebar.rmTeamSidebar": "Ne plus afficher l'équipe dans le menu",
"admin.sidebar.statistics": "Statistiques",
"admin.sidebar.teams": "ÉQUIPES ({count, number})",
"admin.sidebar.users": "Utilisateurs",
"admin.sidebar.view_statistics": "Voir les statistiques",
"admin.sidebar.file": "Configuration du fichier",
"admin.sidebar.ldap": "LDAP",
"admin.sidebar.loading": "Chargement",
"admin.sidebar.log": "Configuration du journal (log)",
"admin.sidebar.rate_limit": "Configuration des limites de débits",
"admin.sidebar.service": "Configuration du service", "admin.sidebar.service": "Configuration du service",
"admin.sidebar.settings": "REGLAGES", "admin.sidebar.settings": "REGLAGES",
"admin.sidebar.sql": "Configuration SQL", "admin.sidebar.sql": "Configuration SQL",
"admin.sidebar.statistics": "- Statistiques",
"admin.sidebar.support": "Paramètres légaux et support", "admin.sidebar.support": "Paramètres légaux et support",
"admin.sidebar.team": "Configuration de l'équipe", "admin.sidebar.team": "Configuration de l'équipe",
"admin.sidebar.teams": "ÉQUIPES ({count})",
"admin.sidebar.users": "- Utilisateurs",
"admin.sidebar.view_statistics": "Voir les statistiques",
"admin.sidebarHeader.systemConsole": "Console système", "admin.sidebarHeader.systemConsole": "Console système",
"admin.sql.dataSource": "Source de données :", "admin.sql.dataSource": "Source de données :",
"admin.sql.driverName": "Nom du pilote :", "admin.sql.driverName": "Nom du pilote :",

Просмотреть файл

@@ -436,28 +436,16 @@
"admin.service.webhooksTitle": "Ativar Webhooks Entrada: ", "admin.service.webhooksTitle": "Ativar Webhooks Entrada: ",
"admin.sidebar.addTeamSidebar": "Adicionar equipe do menu lateral", "admin.sidebar.addTeamSidebar": "Adicionar equipe do menu lateral",
"admin.sidebar.audits": "Conformidade e Auditoria", "admin.sidebar.audits": "Conformidade e Auditoria",
"admin.sidebar.compliance": "Configurações Compliance", "admin.sidebar.gitlab": "GitLab",
"admin.sidebar.email": "Configuração do e-mail",
"admin.sidebar.file": "Configurações do arquivo",
"admin.sidebar.gitlab": "Configurações GitLab",
"admin.sidebar.ldap": "Configurações LDAP",
"admin.sidebar.license": "Edição e Licença",
"admin.sidebar.loading": "Carregando",
"admin.sidebar.log": "Configurações de Log",
"admin.sidebar.logs": "Logs",
"admin.sidebar.other": "OUTROS", "admin.sidebar.other": "OUTROS",
"admin.sidebar.privacy": "Configurações De Privacidade", "admin.sidebar.ldap": "LDAP",
"admin.sidebar.rate_limit": "Configurações Rate Limit", "admin.sidebar.license": "Edição e Licença",
"admin.sidebar.logs": "Logs",
"admin.sidebar.reports": "RELATÓRIOS DO SITE", "admin.sidebar.reports": "RELATÓRIOS DO SITE",
"admin.sidebar.rmTeamSidebar": "Remover equipe do menu lateral", "admin.sidebar.rmTeamSidebar": "Remover equipe do menu lateral",
"admin.sidebar.service": "Configurações do Serviço", "admin.sidebar.statistics": "Estátisticas",
"admin.sidebar.settings": "CONFIGURAÇÕES", "admin.sidebar.teams": "EQUIPES ({count, number})",
"admin.sidebar.sql": "Configurações SQL", "admin.sidebar.users": "Usuários",
"admin.sidebar.statistics": "- Estátisticas",
"admin.sidebar.support": "Configurações jurídico e apoio",
"admin.sidebar.team": "Configurações De Equipe",
"admin.sidebar.teams": "EQUIPES ({count})",
"admin.sidebar.users": "- Usuários",
"admin.sidebar.view_statistics": "Ver Estatísticas", "admin.sidebar.view_statistics": "Ver Estatísticas",
"admin.sidebarHeader.systemConsole": "Console do Sistema", "admin.sidebarHeader.systemConsole": "Console do Sistema",
"admin.sql.dataSource": "Fonte de Dados:", "admin.sql.dataSource": "Fonte de Dados:",

Просмотреть файл

@@ -34,7 +34,6 @@ import * as GlobalActions from 'action_creators/global_actions.jsx';
import SignupUserComplete from 'components/signup_user_complete.jsx'; import SignupUserComplete from 'components/signup_user_complete.jsx';
import ShouldVerifyEmail from 'components/should_verify_email.jsx'; 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 TutorialView from 'components/tutorial/tutorial_view.jsx'; import TutorialView from 'components/tutorial/tutorial_view.jsx';
import BackstageNavbar from 'components/backstage/backstage_navbar.jsx'; import BackstageNavbar from 'components/backstage/backstage_navbar.jsx';
import BackstageSidebar from 'components/backstage/backstage_sidebar.jsx'; import BackstageSidebar from 'components/backstage/backstage_sidebar.jsx';
@@ -51,6 +50,38 @@ import AppDispatcher from './dispatcher/app_dispatcher.jsx';
import Constants from './utils/constants.jsx'; import Constants from './utils/constants.jsx';
const ActionTypes = Constants.ActionTypes; const ActionTypes = Constants.ActionTypes;
import AdminConsole from 'components/admin_console/admin_console.jsx';
import SystemAnalytics from 'components/analytics/system_analytics.jsx';
import ConfigurationSettings from 'components/admin_console/configuration_settings.jsx';
import UsersAndTeamsSettings from 'components/admin_console/users_and_teams_settings.jsx';
import PrivacySettings from 'components/admin_console/privacy_settings.jsx';
import LogSettings from 'components/admin_console/log_settings.jsx';
import EmailAuthenticationSettings from 'components/admin_console/email_authentication_settings.jsx';
import GitLabSettings from 'components/admin_console/gitlab_settings.jsx';
import LdapSettings from 'components/admin_console/ldap_settings.jsx';
import SignupSettings from 'components/admin_console/signup_settings.jsx';
import LoginSettings from 'components/admin_console/login_settings.jsx';
import PublicLinkSettings from 'components/admin_console/public_link_settings.jsx';
import SessionSettings from 'components/admin_console/session_settings.jsx';
import ConnectionSettings from 'components/admin_console/connection_settings.jsx';
import EmailSettings from 'components/admin_console/email_settings.jsx';
import PushSettings from 'components/admin_console/push_settings.jsx';
import WebhookSettings from 'components/admin_console/webhook_settings.jsx';
import ExternalServiceSettings from 'components/admin_console/external_service_settings.jsx';
import DatabaseSettings from 'components/admin_console/database_settings.jsx';
import StorageSettings from 'components/admin_console/storage_settings.jsx';
import ImageSettings from 'components/admin_console/image_settings.jsx';
import CustomBrandSettings from 'components/admin_console/custom_brand_settings.jsx';
import LegalAndSupportSettings from 'components/admin_console/legal_and_support_settings.jsx';
import ComplianceSettings from 'components/admin_console/compliance_settings.jsx';
import RateSettings from 'components/admin_console/rate_settings.jsx';
import DeveloperSettings from 'components/admin_console/developer_settings.jsx';
import TeamUsers from 'components/admin_console/team_users.jsx';
import TeamAnalytics from 'components/analytics/team_analytics.jsx';
import LicenseSettings from 'components/admin_console/license_settings.jsx';
import Audits from 'components/admin_console/audits.jsx';
import Logs from 'components/admin_console/logs.jsx';
import Claim from 'components/claim/claim.jsx'; import Claim from 'components/claim/claim.jsx';
import EmailToOAuth from 'components/claim/components/email_to_oauth.jsx'; import EmailToOAuth from 'components/claim/components/email_to_oauth.jsx';
import OAuthToEmail from 'components/claim/components/oauth_to_email.jsx'; import OAuthToEmail from 'components/claim/components/oauth_to_email.jsx';
@@ -319,7 +350,161 @@ function renderRootComponent() {
<Route <Route
path='admin_console' path='admin_console'
component={AdminConsole} component={AdminConsole}
/> >
<IndexRedirect to='system_analytics'/>
<Route
path='system_analytics'
component={SystemAnalytics}
/>
<Route path='general'>
<IndexRedirect to='configuration'/>
<Route
path='configuration'
component={ConfigurationSettings}
/>
<Route
path='users_and_teams'
component={UsersAndTeamsSettings}
/>
<Route
path='privacy'
component={PrivacySettings}
/>
<Route
path='logging'
component={LogSettings}
/>
</Route>
<Route path='authentication'>
<IndexRedirect to='email'/>
<Route
path='email'
component={EmailAuthenticationSettings}
/>
<Route
path='gitlab'
component={GitLabSettings}
/>
<Route
path='ldap'
component={LdapSettings}
/>
</Route>
<Route path='security'>
<IndexRedirect to='sign_up'/>
<Route
path='sign_up'
component={SignupSettings}
/>
<Route
path='login'
component={LoginSettings}
/>
<Route
path='public_links'
component={PublicLinkSettings}
/>
<Route
path='sessions'
component={SessionSettings}
/>
<Route
path='connections'
component={ConnectionSettings}
/>
</Route>
<Route path='notifications'>
<IndexRedirect to='email'/>
<Route
path='email'
component={EmailSettings}
/>
<Route
path='push'
component={PushSettings}
/>
</Route>
<Route path='integrations'>
<IndexRedirect to='webhooks'/>
<Route
path='webhooks'
component={WebhookSettings}
/>
<Route
path='external'
component={ExternalServiceSettings}
/>
</Route>
<Route
path='database'
component={DatabaseSettings}
/>
<Route path='files'>
<IndexRedirect to='storage'/>
<Route
path='storage'
component={StorageSettings}
/>
<Route
path='images'
component={ImageSettings}
/>
</Route>
<Route path='customization'>
<IndexRedirect to='custom_brand'/>
<Route
path='custom_brand'
component={CustomBrandSettings}
/>
<Route
path='legal_and_support'
component={LegalAndSupportSettings}
/>
</Route>
<Route
path='compliance'
component={ComplianceSettings}
/>
<Route
path='rate'
component={RateSettings}
/>
<Route
path='developer'
component={DeveloperSettings}
/>
<Route path='team'>
<Redirect
from=':team'
to=':team/users'
/>
<Route
path=':team/users'
component={TeamUsers}
/>
<Route
path=':team/analytics'
component={TeamAnalytics}
/>
<Redirect
from='*'
to='/error'
query={notFoundParams}
/>
</Route>
<Route
path='license'
component={LicenseSettings}
/>
<Route
path='audits'
component={Audits}
/>
<Route
path='logs'
component={Logs}
/>
</Route>
<Route <Route
path=':team' path=':team'
component={NeedsTeam} component={NeedsTeam}

Просмотреть файл

@@ -11,6 +11,7 @@
@import 'modal'; @import 'modal';
@import 'oauth'; @import 'oauth';
@import 'popover'; @import 'popover';
@import 'save-button';
@import 'scrollbar'; @import 'scrollbar';
@import 'search'; @import 'search';
@import 'suggestion-list'; @import 'suggestion-list';

7
webapp/sass/components/_save-button.scss Обычный файл
Просмотреть файл

@@ -0,0 +1,7 @@
@charset 'UTF-8';
.save-button {
.glyphicon {
margin-right: 10px;
}
}

Просмотреть файл

@@ -118,7 +118,8 @@
// Team Header in Sidebar // Team Header in Sidebar
.sidebar--left, .sidebar--left,
.sidebar--menu { .sidebar--menu,
.admin-sidebar {
.team__header { .team__header {
@include legacy-pie-clearfix; @include legacy-pie-clearfix;
padding: 9px 10px; padding: 9px 10px;

Просмотреть файл

@@ -1,6 +1,12 @@
@charset 'UTF-8'; @charset 'UTF-8';
.admin-controller { .admin-console {
color: #333;
height: 100%;
margin-left: 220px;
overflow: auto;
padding: 0 20px;
> div { > div {
height: 100%; height: 100%;
} }
@@ -34,122 +40,6 @@
} }
} }
.sidebar--left {
&.sidebar--collapsable {
background: #333;
.team__header {
background: transparent;
margin-bottom: 5px;
}
.nav {
li {
padding: 0;
@include opacity(1);
.icon {
width: 17px;
}
&.divider {
@include alpha-property(background, $black, .1);
}
> a {
&:hover,
&:focus {
@include alpha-property(background, $black, .1);
}
&.active {
background-color: transparent;
}
}
> h4 {
background: alpha-color($white, .15);
color: $white;
margin: 1px 0 0;
padding: 10px;
.menu-icon--right {
right: 12px;
top: 6px;
}
}
}
.menu-icon--right {
font-size: 18px;
font-weight: 600;
height: 20px;
line-height: 20px;
position: absolute;
right: 10px;
text-align: center;
top: 3px;
width: 20px;
.fa {
color: $white;
font-size: 13px;
position: relative;
right: -2px;
}
}
&.nav__sub-menu {
@include font-smoothing(initial);
background: #111;
&.padded {
padding: 5px 0;
}
li {
> a {
background: transparent;
color: #bbb;
font-size: 13px;
padding: 5px 35px 5px 15px;
&:hover {
color: lighten($primary-color, 10);
}
&.active {
color: $white;
font-weight: 600;
}
}
.nav-more {
background: transparent;
color: #bbb;
cursor: pointer;
display: block;
font-size: 13px;
padding: 5px 15px;
&:hover {
color: lighten($primary-color, 10);
}
}
}
}
&.nav__inner-menu {
li {
> a {
padding-left: 20px;
}
}
}
}
}
}
.log__panel { .log__panel {
background-color: white; background-color: white;
border: 1px solid #ddd; border: 1px solid #ddd;
@@ -160,168 +50,160 @@
width: 100%; width: 100%;
} }
.app__content { &.admin {
color: #333; background-color: #f1f1f1;
min-height: 600px;
overflow: auto;
padding: 0 40px 20px;
}
&.admin { .wrapper--fixed {
background-color: #f1f1f1; max-width: 800px;
min-height: 600px; }
overflow: auto;
padding: 0 40px 20px; .form-horizontal {
margin-top: 40px;
.control-label {
font-weight: 600;
padding-right: 0;
text-align: left;
} }
.wrapper--fixed { .form-group {
max-width: 800px; margin-bottom: 25px;
} }
.form-horizontal { .file__upload {
margin-top: 40px; display: inline-block;
margin: 0 10px 10px 0;
position: relative;
.control-label { input {
font-weight: 600; @include opacity(0);
padding-right: 0; height: 100%;
text-align: left; left: 0;
} position: absolute;
top: 0;
.form-group { width: 100%;
margin-bottom: 25px; z-index: 5;
&.form-group--small {
margin-bottom: 10px;
}
}
.file__upload {
display: inline-block;
margin: 0 10px 10px 0;
position: relative;
input {
@include opacity(0);
height: 100%;
left: 0;
position: absolute;
top: 0;
width: 100%;
z-index: 5;
}
}
.help-text {
color: #777;
margin: 10px 0 0 15px;
&.no-margin {
margin: 0;
}
&.no-margin--top {
margin-top: 0;
}
ul,
ol {
padding-left: 23px;
}
.help-link {
margin-right: 5px;
}
.btn {
font-size: 13px;
}
}
.alert {
display: inline-block;
margin: 1em 0 0;
padding: 5px 7px;
position: relative;
top: 1px;
.fa {
margin-right: 5px;
}
} }
} }
.banner { .help-text {
background: $white; color: #777;
border: 1px solid #ddd; margin: 10px 0 0 15px;
font-size: .95em;
margin: 2em 0;
padding: .7em 1.5em;
.banner__heading { &.no-margin {
font-size: 1.5em; margin: 0;
} }
.banner__content { &.no-margin--top {
width: 80%; margin-top: 0;
} }
&.warning { ul,
background: #e60000; ol {
padding-left: 23px;
}
.help-link {
margin-right: 5px;
}
.btn {
font-size: 13px;
} }
} }
.popover { .alert {
border-radius: 3px; display: inline-block;
font-size: .95em; margin: 1em 0 0;
width: 100%; padding: 5px 7px;
position: relative;
top: 1px;
.fa {
margin-right: 5px;
}
}
}
.banner {
background: $white;
border: 1px solid #ddd;
font-size: .95em;
margin: 2em 0;
padding: .7em 1.5em;
.banner__heading {
font-size: 1.5em;
} }
.panel { .banner__content {
width: 80%;
}
&.warning {
background: #e60000;
}
}
.popover {
border-radius: 3px;
font-size: .95em;
width: 100%;
}
.panel {
background-color: transparent;
border: none;
}
.panel-default {
> .panel-heading {
background-color: transparent; background-color: transparent;
border: none; padding: 10px 0;
} }
.panel-default { .panel-body {
> .panel-heading { padding: 30px 0 10px;
background-color: transparent;
padding: 10px 0;
}
.panel-body {
padding: 30px 0 10px;
}
} }
}
.panel-group { .panel-group {
margin-bottom: 50px; margin-bottom: 50px;
} }
.panel-title { .panel-title {
font-size: 24px; font-size: 24px;
line-height: 1.5; line-height: 1.5;
a { a {
@include clearfix; @include clearfix;
display: block; display: block;
text-decoration: none; text-decoration: none;
&.collapsed { &.collapsed {
.fa-minus { .fa-minus {
display: none; display: none;
}
.fa-plus {
display: inline-block;
}
}
.fa {
color: #aaa;
float: right;
font-size: 18px;
margin-top: 8px;
} }
.fa-plus { .fa-plus {
display: none; display: inline-block;
} }
} }
.fa {
color: #aaa;
float: right;
font-size: 18px;
margin-top: 8px;
}
.fa-plus {
display: none;
}
} }
} }
@@ -343,3 +225,108 @@
margin-bottom: 1.5em; margin-bottom: 1.5em;
max-width: 150px; max-width: 150px;
} }
.admin-console__disabled-text {
color: #777;
margin: 10px 0 0 15px;
}
.admin-sidebar {
background: #333;
border-right: 1px solid #ddd;
height: 100%;
left: 0;
position: absolute;
width: 220px;
z-index: 5;
.team__header {
background: transparent;
}
.nav-pills__container {
background: #111;
@include font-smoothing(initial);
height: calc(100% - 80px);
margin-top: 1px;
position: relative;
}
.sidebar-category {
.category-title {
background: alpha-color($white, .15);
color: $white;
line-height: 15.4px;
padding: 10px;
.category-icon {
right: 12px;
top: 6px;
width: 17px;
}
}
.sections {
padding: 5px 0;
}
}
.sidebar-section-title {
padding: 5px 35px 5px 15px;
}
.sidebar-subsection-title {
padding: 5px 35px 5px 30px;
}
.sidebar-section-title,
.sidebar-subsection-title {
color: #bbb;
display: block;
font-size: 13px;
position: relative;
&:focus {
text-decoration: none;
}
&:hover {
color: lighten($primary-color, 10);
text-decoration: none;
}
&--active {
color: $white;
font-weight: 600;
}
}
.menu-icon--right {
font-size: 18px;
font-weight: 600;
height: 20px;
line-height: 20px;
position: absolute;
right: 12px;
text-align: center;
top: 8px;
width: 20px;
.fa {
color: $white;
font-size: 13px;
position: relative;
right: -2px;
}
&.menu__close {
cursor: pointer;
right: 10px;
top: 3px;
}
}
}
.email-connection-test {
margin-top: -15px;
}

Просмотреть файл

@@ -22,7 +22,7 @@ class AdminStoreClass extends EventEmitter {
this.logs = null; this.logs = null;
this.audits = null; this.audits = null;
this.config = null; this.config = null;
this.teams = null; this.teams = {};
this.complianceReports = null; this.complianceReports = null;
} }
@@ -126,6 +126,10 @@ class AdminStoreClass extends EventEmitter {
this.teams = teams; this.teams = teams;
} }
getTeam(id) {
return this.teams[id];
}
getSelectedTeams() { getSelectedTeams() {
const result = BrowserStore.getItem('seleted_teams'); const result = BrowserStore.getItem('seleted_teams');
if (!result) { if (!result) {