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.
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 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 {
static get contextTypes() {
return {
router: React.PropTypes.object.isRequired
};
}
constructor(props) {
super(props);
this.isSelected = this.isSelected.bind(this);
this.handleClick = this.handleClick.bind(this);
this.handleAllTeamsChange = this.handleAllTeamsChange.bind(this);
this.removeTeam = this.removeTeam.bind(this);
this.showTeamSelect = this.showTeamSelect.bind(this);
this.teamSelectedModal = this.teamSelectedModal.bind(this);
this.teamSelectedModalDismissed = this.teamSelectedModalDismissed.bind(this);
this.renderAddTeamButton = this.renderAddTeamButton.bind(this);
this.renderTeams = this.renderTeams.bind(this);
this.state = {
teams: AdminStore.getAllTeams(),
selectedTeams: AdminStore.getSelectedTeams(),
showSelectModal: false
};
}
componentDidMount() {
AdminStore.addAllTeamsChangeListener(this.handleAllTeamsChange);
AsyncClient.getAllTeams();
}
componentDidUpdate() {
if (!Utils.isMobile()) {
$('.sidebar--left .nav-pills__container').perfectScrollbar();
$('.admin-sidebar .nav-pills__container').perfectScrollbar();
}
}
handleClick(name, teamId, e) {
e.preventDefault();
this.props.selectTab(name, teamId);
componentWillUnmount() {
AdminStore.removeAllTeamsChangeListener(this.handleAllTeamsChange);
}
isSelected(name, teamId) {
if (this.props.selected === name) {
if (name === 'team_users' || name === 'team_analytics') {
if (this.props.selectedTeam != null && this.props.selectedTeam === teamId) {
return 'active';
}
} else {
return 'active';
}
}
return '';
handleAllTeamsChange() {
this.setState({
teams: AdminStore.getAllTeams(),
selectedTeams: AdminStore.getSelectedTeams()
});
}
removeTeam(teamId, e) {
e.preventDefault();
e.stopPropagation();
Reflect.deleteProperty(this.props.selectedTeams, teamId);
this.props.removeSelectedTeam(teamId);
removeTeam(team) {
const selectedTeams = Object.assign({}, this.state.selectedTeams);
Reflect.deleteProperty(selectedTeams, team.id);
AdminStore.saveSelectedTeams(selectedTeams);
if (this.props.selected === 'team_users') {
if (this.props.selectedTeam != null && this.props.selectedTeam === teamId) {
this.props.selectTab('service_settings', null);
}
this.handleAllTeamsChange();
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) {
this.setState({showSelectModal: false});
this.props.addSelectedTeam(teamId);
this.forceUpdate();
this.setState({
showSelectModal: false
});
const selectedTeams = Object.assign({}, this.state.selectedTeams);
selectedTeams[teamId] = true;
AdminStore.saveSelectedTeams(selectedTeams);
this.handleAllTeamsChange();
}
teamSelectedModalDismissed() {
this.setState({showSelectModal: false});
}
render() {
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>
);
renderAddTeamButton() {
const addTeamTooltip = (
<Tooltip id='add-team-tooltip'>
<FormattedMessage
@@ -108,393 +111,468 @@ export default class AdminSidebar extends React.Component {
</Tooltip>
);
if (this.props.teams != null) {
count = '' + Object.keys(this.props.teams).length;
return (
<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 = [];
for (var key in this.props.selectedTeams) {
if (this.props.selectedTeams.hasOwnProperty(key)) {
var team = this.props.teams[key];
renderTeams() {
const teams = [];
if (team != null) {
teams.push(
<ul
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>
);
}
}
for (const key in this.state.selectedTeams) {
if (!this.state.selectedTeams.hasOwnProperty(key)) {
continue;
}
const team = this.state.teams[key];
if (!team) {
continue;
}
teams.push(
<AdminSidebarTeam
key={team.id}
team={team}
onRemoveTeam={this.removeTeam}
/>
);
}
let ldapSettings;
let complianceSettings;
let licenseSettings;
if (global.window.mm_config.BuildEnterpriseReady === 'true') {
if (global.window.mm_license.IsLicensed === 'true') {
return (
<AdminSidebarCategory
parentLink='/admin_console'
icon='fa-gear'
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') {
ldapSettings = (
<li>
<a
href='#'
className={this.isSelected('ldap_settings')}
onClick={this.handleClick.bind(this, 'ldap_settings', null)}
>
<AdminSidebarSection
name='ldap'
title={
<FormattedMessage
id='admin.sidebar.ldap'
defaultMessage='LDAP Settings'
defaultMessage='LDAP'
/>
</a>
</li>
}
/>
);
}
if (global.window.mm_license.Compliance === 'true') {
complianceSettings = (
<li>
<a
href='#'
className={this.isSelected('compliance_settings')}
onClick={this.handleClick.bind(this, 'compliance_settings', null)}
>
<AdminSidebarSection
name='compliance'
title={
<FormattedMessage
id='admin.sidebar.compliance'
defaultMessage='Compliance Settings'
defaultMessage='Compliance'
/>
</a>
</li>
}
/>
);
}
}
licenseSettings = (
<li>
<a
href='#'
className={this.isSelected('license')}
onClick={this.handleClick.bind(this, 'license', null)}
>
license = (
<AdminSidebarSection
name='license'
title={
<FormattedMessage
id='admin.sidebar.license'
defaultMessage='Edition and License'
/>
</a>
</li>
}
/>
);
}
let audits;
if (global.window.mm_license.IsLicensed === 'true') {
if (window.mm_license.IsLicensed === 'true') {
audits = (
<li>
<a
href='#'
className={this.isSelected('audits')}
onClick={this.handleClick.bind(this, 'audits', null)}
>
<AdminSidebarSection
name='audits'
title={
<FormattedMessage
id='admin.sidebar.audits'
defaultMessage='Compliance and Auditing'
defaultMessage='Complaince and Auditing'
/>
</a>
</li>
}
/>
);
}
return (
<div className='sidebar--left sidebar--collapsable'>
<div className='admin-sidebar'>
<AdminSidebarHeader/>
<div className='nav-pills__container'>
<ul className='nav nav-pills nav-stacked'>
<li>
<ul className='nav nav__sub-menu'>
<li>
<h4>
<span className='icon fa fa-gear'></span>
<span>
<FormattedMessage
id='admin.sidebar.reports'
defaultMessage='SITE REPORTS'
/>
</span>
</h4>
</li>
</ul>
<ul className='nav nav__sub-menu padded'>
<li>
<a
href='#'
className={this.isSelected('system_analytics')}
onClick={this.handleClick.bind(this, 'system_analytics', null)}
>
<AdminSidebarCategory
parentLink='/admin_console'
icon='fa-gear'
title={
<FormattedMessage
id='admin.sidebar.reports'
defaultMessage='SITE REPORTS'
/>
}
>
<AdminSidebarSection
name='system_analytics'
title={
<FormattedMessage
id='admin.sidebar.view_statistics'
defaultMessage='View Statistics'
/>
}
/>
</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
id='admin.sidebar.view_statistics'
defaultMessage='View Statistics'
id='admin.sidebar.configuration'
defaultMessage='Configuration'
/>
</a>
</li>
</ul>
<ul className='nav nav__sub-menu'>
<li>
<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)}
>
}
/>
<AdminSidebarSection
name='users_and_teams'
title={
<FormattedMessage
id='admin.sidebar.service'
defaultMessage='Service Settings'
id='admin.sidebar.usersAndTeams'
defaultMessage='Users and Teams'
/>
</a>
</li>
<li>
<a
href='#'
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)}
>
}
/>
<AdminSidebarSection
name='privacy'
title={
<FormattedMessage
id='admin.sidebar.privacy'
defaultMessage='Privacy Settings'
defaultMessage='Privacy'
/>
</a>
</li>
<li>
<a
href='#'
className={this.isSelected('gitlab_settings')}
onClick={this.handleClick.bind(this, 'gitlab_settings', null)}
>
}
/>
<AdminSidebarSection
name='logging'
title={
<FormattedMessage
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
id='admin.sidebar.gitlab'
defaultMessage='GitLab Settings'
defaultMessage='GitLab'
/>
</a>
</li>
}
/>
{ldapSettings}
{complianceSettings}
<li>
<a
href='#'
className={this.isSelected('legal_and_support_settings')}
onClick={this.handleClick.bind(this, 'legal_and_support_settings', null)}
>
</AdminSidebarSection>
<AdminSidebarSection
name='security'
title={
<FormattedMessage
id='admin.sidebar.security'
defaultMessage='Security'
/>
}
>
<AdminSidebarSection
name='sign_up'
title={
<FormattedMessage
id='admin.sidebar.support'
defaultMessage='Legal and Support Settings'
id='admin.sidebar.signUp'
defaultMessage='Sign Up'
/>
</a>
</li>
</ul>
<ul className='nav nav__sub-menu'>
<li>
<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)}
>
}
/>
<AdminSidebarSection
name='login'
title={
<FormattedMessage
id='admin.sidebar.logs'
defaultMessage='Logs'
id='admin.sidebar.login'
defaultMessage='Login'
/>
</a>
</li>
</ul>
</li>
}
/>
<AdminSidebarSection
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>
</div>
<SelectTeamModal
teams={this.props.teams}
teams={this.state.teams}
show={this.state.showSelectModal}
onModalSubmit={this.teamSelectedModal}
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';
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() {
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 (
<Setting label={this.props.label}>
<Setting
label={this.props.label}
helpText={helpText}
>
<label className='radio-inline'>
<input
type='radio'
value='true'
checked={this.props.currentValue}
onChange={this.props.handleChange}
disabled={this.props.isDisabled}
checked={this.props.value}
onChange={this.handleChange}
disabled={this.props.disabled}
/>
{this.props.trueText}
</label>
@@ -25,13 +52,12 @@ export default class BooleanSetting extends React.Component {
<input
type='radio'
value='false'
checked={!this.props.currentValue}
onChange={this.props.handleChange}
disabled={this.props.isDisabled}
checked={!this.props.value}
onChange={this.handleChange}
disabled={this.props.disabled}
/>
{this.props.falseText}
</label>
{this.props.helpText}
</Setting>
);
}
@@ -48,15 +74,18 @@ BooleanSetting.defaultProps = {
id='admin.ldap.false'
defaultMessage='false'
/>
)
),
disabled: false
};
BooleanSetting.propTypes = {
id: React.PropTypes.string.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,
falseText: React.PropTypes.node,
isDisabled: React.PropTypes.bool.isRequired,
handleChange: React.PropTypes.func.isRequired,
disabled: React.PropTypes.bool.isRequired,
disabledText: React.PropTypes.node,
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.
// 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 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) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleEnable = this.handleEnable.bind(this);
this.handleDisable = this.handleDisable.bind(this);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.state = {
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');
this.renderSettings = this.renderSettings.bind(this);
const config = this.props.config;
const oldEnable = config.ComplianceSettings.Enable;
config.ComplianceSettings.Enable = this.refs.Enable.checked;
config.ComplianceSettings.Directory = ReactDOM.findDOMNode(this.refs.Directory).value;
config.ComplianceSettings.EnableDaily = this.refs.EnableDaily.checked;
this.state = Object.assign(this.state, {
enable: props.config.ComplianceSettings.Enable,
directory: props.config.ComplianceSettings.Directory,
enableDaily: props.config.ComplianceSettings.EnableDaily
});
}
Client.saveConfig(
config,
() => {
$('#save-button').button('reset');
AsyncClient.getConfig();
this.setState({
serverError: null,
saveNeeded: false
});
if (oldEnable !== config.ComplianceSettings.Enable) {
window.location.reload();
}
},
(err) => {
this.setState({
serverError: err.message,
saveNeeded: true
});
$('#save-button').button('reset');
}
getConfigFromState(config) {
config.ComplianceSettings.Enable = this.state.enable;
config.ComplianceSettings.Directory = this.state.directory;
config.ComplianceSettings.EnableDaily = this.state.enableDaily;
return config;
}
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.compliance.title'
defaultMessage='Compliance Settings'
/>
</h3>
);
}
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';
let bannerContent;
@@ -95,170 +63,64 @@ export default class ComplianceSettings extends React.Component {
}
return (
<div className='wrapper--fixed'>
<SettingsGroup>
{bannerContent}
<h3>
<FormattedMessage
id='admin.compliance.title'
defaultMessage='Compliance Settings'
/>
</h3>
<form
className='form-horizontal'
role='form'
>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='Enable'
>
<FormattedMessage
id='admin.compliance.enableTitle'
defaultMessage='Enable Compliance:'
/>
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
<input
type='radio'
name='Enable'
value='true'
ref='Enable'
defaultChecked={this.props.config.ComplianceSettings.Enable}
onChange={this.handleEnable}
disabled={!licenseEnabled}
/>
<FormattedMessage
id='admin.compliance.true'
defaultMessage='true'
/>
</label>
<label className='radio-inline'>
<input
type='radio'
name='Enable'
value='false'
defaultChecked={!this.props.config.ComplianceSettings.Enable}
onChange={this.handleDisable}
/>
<FormattedMessage
id='admin.compliance.false'
defaultMessage='false'
/>
</label>
<p className='help-text'>
<FormattedMessage
id='admin.compliance.enableDesc'
defaultMessage='When true, Mattermost allows compliance reporting'
/>
</p>
</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>
<BooleanSetting
id='enable'
label={
<FormattedMessage
id='admin.compliance.enableTitle'
defaultMessage='Enable Compliance:'
/>
}
helpText={
<FormattedMessage
id='admin.compliance.enableDesc'
defaultMessage='When true, Mattermost allows compliance reporting'
/>
}
value={this.state.enable}
onChange={this.handleChange}
disabled={!licenseEnabled}
/>
<TextSetting
id='directory'
label={
<FormattedMessage
id='admin.compliance.directoryTitle'
defaultMessage='Compliance Directory Location:'
/>
}
placeholder={Utils.localizeMessage('admin.sql.maxOpenExample', 'Ex "10"')}
helpText={
<FormattedMessage
id='admin.compliance.directoryDescription'
defaultMessage='Directory to which compliance reports are written. If blank, will be set to ./data/.'
/>
}
value={this.state.directory}
onChange={this.handleChange}
disabled={!licenseEnabled || !this.state.enable}
/>
<BooleanSetting
id='enableDaily'
label={
<FormattedMessage
id='admin.compliance.enableDailyTitle'
defaultMessage='Enable Daily Report:'
/>
}
helpText={
<FormattedMessage
id='admin.compliance.enableDailyDesc'
defaultMessage='When true, Mattermost will generate a daily compliance report.'
/>
}
value={this.state.enableDaily}
onChange={this.handleChange}
disabled={!licenseEnabled || !this.state.enable}
/>
</SettingsGroup>
);
}
}
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';
const CONNECTION_SECURITY_HELP_TEXT = (
<div className='help-text'>
<table
className='table table-bordered table-margin--none'
cellPadding='5'
>
<tbody>
<tr>
<td className='help-text'>
<FormattedMessage
id='admin.connectionSecurityNone'
defaultMessage='None'
/>
</td>
<td className='help-text'>
<FormattedMessage
id='admin.connectionSecurityNoneDescription'
defaultMessage='Mattermost will connect over an unsecure connection.'
/>
</td>
</tr>
<tr>
<td className='help-text'>
<FormattedMessage
id='admin.connectionSecurityTls'
defaultMessage='TLS'
/>
</td>
<td className='help-text'>
<FormattedMessage
id='admin.connectionSecurityTlsDescription'
defaultMessage='Encrypts the communication between Mattermost and your server.'
/>
</td>
</tr>
<tr>
<td className='help-text'>
<FormattedMessage
id='admin.connectionSecurityStart'
defaultMessage='STARTTLS'
/>
</td>
<td className='help-text'>
<FormattedMessage
id='admin.connectionSecurityStartDescription'
defaultMessage='Takes an existing insecure connection and attempts to upgrade it to a secure connection using TLS.'
/>
</td>
</tr>
</tbody>
</table>
</div>
<table
className='table table-bordered table-margin--none'
cellPadding='5'
>
<tbody>
<tr>
<td className='help-text'>
<FormattedMessage
id='admin.connectionSecurityNone'
defaultMessage='None'
/>
</td>
<td className='help-text'>
<FormattedMessage
id='admin.connectionSecurityNoneDescription'
defaultMessage='Mattermost will connect over an unsecure connection.'
/>
</td>
</tr>
<tr>
<td className='help-text'>
<FormattedMessage
id='admin.connectionSecurityTls'
defaultMessage='TLS'
/>
</td>
<td className='help-text'>
<FormattedMessage
id='admin.connectionSecurityTlsDescription'
defaultMessage='Encrypts the communication between Mattermost and your server.'
/>
</td>
</tr>
<tr>
<td className='help-text'>
<FormattedMessage
id='admin.connectionSecurityStart'
defaultMessage='STARTTLS'
/>
</td>
<td className='help-text'>
<FormattedMessage
id='admin.connectionSecurityStartDescription'
defaultMessage='Takes an existing insecure connection and attempts to upgrade it to a secure connection using TLS.'
/>
</td>
</tr>
</tbody>
</table>
);
export default class ConnectionSecurityDropdownSetting extends React.Component {
render() {
return (
<DropdownSetting
id='connectionSecurity'
values={[
{value: '', text: Utils.localizeMessage('admin.connectionSecurityNone', 'None')},
{value: 'TLS', text: Utils.localizeMessage('admin.connectionSecurityTls', 'TLS (Recommended)')},
@@ -76,11 +75,10 @@ export default class ConnectionSecurityDropdownSetting extends React.Component {
defaultMessage='Connection Security:'
/>
}
currentValue={this.props.currentValue}
handleChange={this.props.handleChange}
isDisabled={this.props.isDisabled}
value={this.props.value}
onChange={this.props.onChange}
disabled={this.props.disabled}
helpText={CONNECTION_SECURITY_HELP_TEXT}
margin='small'
/>
);
}
@@ -89,7 +87,7 @@ ConnectionSecurityDropdownSetting.defaultProps = {
};
ConnectionSecurityDropdownSetting.propTypes = {
currentValue: React.PropTypes.string.isRequired,
handleChange: React.PropTypes.func.isRequired,
isDisabled: React.PropTypes.bool.isRequired
value: React.PropTypes.string.isRequired,
onChange: React.PropTypes.func.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';
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() {
const options = [];
for (const {value, text} of this.props.values) {
@@ -22,30 +32,33 @@ export default class DropdownSetting extends React.Component {
return (
<Setting
label={this.props.label}
margin={this.props.margin}
inputId={this.props.id}
helpText={this.props.helpText}
>
<select
className='form-control'
value={this.props.currentValue}
onChange={this.props.handleChange}
disabled={this.props.isDisabled}
id={this.props.id}
value={this.props.value}
onChange={this.handleChange}
disabled={this.props.disabled}
>
{options}
</select>
{this.props.helpText}
</Setting>
);
}
}
DropdownSetting.defaultProps = {
isDisabled: false
};
DropdownSetting.propTypes = {
id: React.PropTypes.string.isRequired,
values: React.PropTypes.array.isRequired,
label: React.PropTypes.node.isRequired,
currentValue: React.PropTypes.string.isRequired,
handleChange: React.PropTypes.func.isRequired,
isDisabled: React.PropTypes.bool.isRequired,
helpText: React.PropTypes.node.isRequired,
margin: React.PropTypes.oneOf(['', 'small'])
value: React.PropTypes.string.isRequired,
onChange: React.PropTypes.func.isRequired,
disabled: React.PropTypes.bool,
helpText: React.PropTypes.node
};

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

@@ -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.
// 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';
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) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.state = {
Enable: this.props.config.GitLabSettings.Enable,
saveNeeded: false,
serverError: null
};
this.renderSettings = this.renderSettings.bind(this);
this.state = Object.assign(this.state, {
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) {
var s = {saveNeeded: true, serverError: this.state.serverError};
getConfigFromState(config) {
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') {
s.Enable = true;
}
if (action === 'EnableFalse') {
s.Enable = false;
}
this.setState(s);
return config;
}
handleSubmit(e) {
e.preventDefault();
$('#save-button').button('loading');
var config = this.props.config;
config.GitLabSettings.Enable = ReactDOM.findDOMNode(this.refs.Enable).checked;
config.GitLabSettings.Secret = ReactDOM.findDOMNode(this.refs.Secret).value.trim();
config.GitLabSettings.Id = ReactDOM.findDOMNode(this.refs.Id).value.trim();
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() {
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';
}
renderTitle() {
return (
<div className='wrapper--fixed'>
<h3>
<FormattedMessage
id='admin.gitlab.settingsTitle'
defaultMessage='GitLab Settings'
/>
</h3>
<form
className='form-horizontal'
role='form'
>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='Enable'
>
<FormattedMessage
id='admin.gitlab.enableTitle'
defaultMessage='Enable Sign Up With GitLab: '
/>
</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 className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='Id'
>
<FormattedMessage
id='admin.gitlab.clientIdTitle'
defaultMessage='Id:'
/>
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='Id'
ref='Id'
placeholder={formatMessage(holders.clientIdExample)}
defaultValue={this.props.config.GitLabSettings.Id}
onChange={this.handleChange}
disabled={!this.state.Enable}
/>
<p className='help-text'>
<FormattedMessage
id='admin.gitlab.clientIdDescription'
defaultMessage='Obtain this value via the instructions above for logging into GitLab'
/>
</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='Secret'
>
<FormattedMessage
id='admin.gitlab.clientSecretTitle'
defaultMessage='Secret:'
/>
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='Secret'
ref='Secret'
placeholder={formatMessage(holders.clientSecretExample)}
defaultValue={this.props.config.GitLabSettings.Secret}
onChange={this.handleChange}
disabled={!this.state.Enable}
/>
<p className='help-text'>
<FormattedMessage
id='admin.gitab.clientSecretDescription'
defaultMessage='Obtain this value via the instructions above for logging into GitLab.'
/>
</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='AuthEndpoint'
>
<FormattedMessage
id='admin.gitlab.authTitle'
defaultMessage='Auth Endpoint:'
/>
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='AuthEndpoint'
ref='AuthEndpoint'
placeholder={formatMessage(holders.authExample)}
defaultValue={this.props.config.GitLabSettings.AuthEndpoint}
onChange={this.handleChange}
disabled={!this.state.Enable}
/>
<p className='help-text'>
<FormattedMessage
id='admin.gitlab.authDescription'
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>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='TokenEndpoint'
>
<FormattedMessage
id='admin.gitlab.tokenTitle'
defaultMessage='Token Endpoint:'
/>
</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>
<h3>
<FormattedMessage
id='admin.authentication.title'
defaultMessage='Authentication Settings'
/>
</h3>
);
}
}
//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);
renderSettings() {
return (
<SettingsGroup
header={
<FormattedMessage
id='admin.authentication.gitlab'
defaultMessage='GitLab'
/>
}
>
<BooleanSetting
id='enable'
label={
<FormattedMessage
id='admin.gitlab.enableTitle'
defaultMessage='Enable Sign Up With GitLab: '
/>
}
helpText={
<div>
<FormattedMessage
id='admin.gitlab.enableDescription'
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>'
/>
</div>
}
value={this.state.enable}
onChange={this.handleChange}
/>
<TextSetting
id='id'
label={
<FormattedMessage
id='admin.gitlab.clientIdTitle'
defaultMessage='Id:'
/>
}
placeholder={Utils.localizeMessage('admin.gitlab.clientIdExample', 'Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"')}
helpText={
<FormattedMessage
id='admin.gitlab.clientIdDescription'
defaultMessage='Obtain this value via the instructions above for logging into GitLab'
/>
}
value={this.state.id}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<TextSetting
id='secret'
label={
<FormattedMessage
id='admin.gitlab.clientSecretTitle'
defaultMessage='Secret:'
/>
}
placeholder={Utils.localizeMessage('admin.gitlab.clientSecretExample', 'Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"')}
helpText={
<FormattedMessage
id='admin.gitab.clientSecretDescription'
defaultMessage='Obtain this value via the instructions above for logging into GitLab.'
/>
}
value={this.state.secret}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<TextSetting
id='userApiEndpoint'
label={
<FormattedMessage
id='admin.gitlab.userTitle'
defaultMessage='User API Endpoint:'
/>
}
placeholder={Utils.localizeMessage('admin.gitlab.userExample', 'Ex ""')}
helpText={
<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.'
/>
}
value={this.state.userApiEndpoint}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<TextSetting
id='authEndpoint'
label={
<FormattedMessage
id='admin.gitlab.authTitle'
defaultMessage='Auth Endpoint:'
/>
}
placeholder={Utils.localizeMessage('admin.gitlab.authExample', 'Ex ""')}
helpText={
<FormattedMessage
id='admin.gitlab.authDescription'
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.'
/>
}
value={this.state.authEndpoint}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
<TextSetting
id='tokenEndpoint'
label={
<FormattedMessage
id='admin.gitlab.tokenTitle'
defaultMessage='Token Endpoint:'
/>
}
placeholder={Utils.localizeMessage('admin.gitlab.tokenExample', 'Ex ""')}
helpText={
<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.'
/>
}
value={this.state.tokenEndpoint}
onChange={this.handleChange}
disabled={!this.state.enable}
/>
</SettingsGroup>
);
}
}

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

@@ -1,692 +1,174 @@
// 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({
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';
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) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleGenerate = this.handleGenerate.bind(this);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.state = {
saveNeeded: false,
serverError: null,
DriverName: this.props.config.FileSettings.DriverName
};
this.renderSettings = this.renderSettings.bind(this);
this.state = Object.assign(this.state, {
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) {
var s = {saveNeeded: true, serverError: this.state.serverError};
getConfigFromState(config) {
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') {
s.DriverName = ReactDOM.findDOMNode(this.refs.DriverName).value;
}
this.setState(s);
return config;
}
handleGenerate(e) {
e.preventDefault();
ReactDOM.findDOMNode(this.refs.PublicLinkSalt).value = crypto.randomBytes(256).toString('base64').substring(0, 32);
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.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() {
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;
}
renderTitle() {
return (
<div className='wrapper--fixed'>
<h3>
<FormattedMessage
id='admin.image.fileSettings'
defaultMessage='File Settings'
/>
</h3>
<form
className='form-horizontal'
role='form'
>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='DriverName'
>
<FormattedMessage
id='admin.image.storeTitle'
defaultMessage='Store Files In:'
/>
</label>
<div className='col-sm-8'>
<select
className='form-control'
id='DriverName'
ref='DriverName'
defaultValue={this.props.config.FileSettings.DriverName}
onChange={this.handleChange.bind(this, 'DriverName')}
>
<option value='local'>{formatMessage(holders.storeLocal)}</option>
<option value='amazons3'>{formatMessage(holders.storeAmazonS3)}</option>
</select>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='Directory'
>
<FormattedMessage
id='admin.image.localTitle'
defaultMessage='Local Directory Location:'
/>
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='Directory'
ref='Directory'
placeholder={formatMessage(holders.localExample)}
defaultValue={this.props.config.FileSettings.Directory}
onChange={this.handleChange}
disabled={!enableFile}
/>
<p className='help-text'>
<FormattedMessage
id='admin.image.localDescription'
defaultMessage='Directory to which image files are written. If blank, will be set to ./data/.'
/>
</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='AmazonS3AccessKeyId'
>
<FormattedMessage
id='admin.image.amazonS3IdTitle'
defaultMessage='Amazon S3 Access Key Id:'
/>
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='AmazonS3AccessKeyId'
ref='AmazonS3AccessKeyId'
placeholder={formatMessage(holders.amazonS3IdExample)}
defaultValue={this.props.config.FileSettings.AmazonS3AccessKeyId}
onChange={this.handleChange}
disabled={!enableS3}
/>
<p className='help-text'>
<FormattedMessage
id='admin.image.amazonS3IdDescription'
defaultMessage='Obtain this credential from your Amazon EC2 administrator.'
/>
</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='AmazonS3SecretAccessKey'
>
<FormattedMessage
id='admin.image.amazonS3SecretTitle'
defaultMessage='Amazon S3 Secret Access Key:'
/>
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='AmazonS3SecretAccessKey'
ref='AmazonS3SecretAccessKey'
placeholder={formatMessage(holders.amazonS3SecretExample)}
defaultValue={this.props.config.FileSettings.AmazonS3SecretAccessKey}
onChange={this.handleChange}
disabled={!enableS3}
/>
<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>
<h3>
<FormattedMessage
id='admin.files.title'
defaultMessage='File Settings'
/>
</h3>
);
}
}
FileSettings.propTypes = {
intl: intlShape.isRequired,
config: React.PropTypes.object
};
export default injectIntl(FileSettings);
renderSettings() {
return (
<SettingsGroup
header={
<FormattedMessage
id='admin.files.images'
defaultMessage='Images'
/>
}
>
<TextSetting
id='thumbnailWidth'
label={
<FormattedMessage
id='admin.image.thumbWidthTitle'
defaultMessage='Thumbnail Width:'
/>
}
placeholder={Utils.localizeMessage('admin.image.thumbWidthExample', 'Ex "120"')}
helpText={
<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.'
/>
}
value={this.state.thumbnailWidth}
onChange={this.handleChange}
/>
<TextSetting
id='thumbnailHeight'
label={
<FormattedMessage
id='admin.image.thumbHeightTitle'
defaultMessage='Thumbnail Height:'
/>
}
placeholder={Utils.localizeMessage('admin.image.thumbHeightExample', 'Ex "100"')}
helpText={
<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.'
/>
}
value={this.state.thumbnailHeight}
onChange={this.handleChange}
/>
<TextSetting
id='profileWidth'
label={
<FormattedMessage
id='admin.image.profileWidthTitle'
defaultMessage='Profile Width:'
/>
}
placeholder={Utils.localizeMessage('admin.image.profileWidthExample', 'Ex "1024"')}
helpText={
<FormattedMessage
id='admin.image.profileWidthDescription'
defaultMessage='Width of profile picture.'
/>
}
value={this.state.profileWidth}
onChange={this.handleChange}
/>
<TextSetting
id='profileHeight'
label={
<FormattedMessage
id='admin.image.profileHeightTitle'
defaultMessage='Profile Height:'
/>
}
placeholder={Utils.localizeMessage('admin.image.profileHeightExample', 'Ex "0"')}
helpText={
<FormattedMessage
id='admin.image.profileHeightDescription'
defaultMessage='Height of profile picture.'
/>
}
value={this.state.profileHeight}
onChange={this.handleChange}
/>
<TextSetting
id='previewWidth'
label={
<FormattedMessage
id='admin.image.previewWidthTitle'
defaultMessage='Preview Width:'
/>
}
placeholder={Utils.localizeMessage('admin.image.previewWidthExample', 'Ex "1024"')}
helpText={
<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.'
/>
}
value={this.state.previewWidth}
onChange={this.handleChange}
/>
<TextSetting
id='previewHeight'
label={
<FormattedMessage
id='admin.image.previewHeightTitle'
defaultMessage='Preview Height:'
/>
}
placeholder={Utils.localizeMessage('admin.image.previewHeightExample', 'Ex "0"')}
helpText={
<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.'
/>
}
value={this.state.previewHeight}
onChange={this.handleChange}
/>
</SettingsGroup>
);
}
}

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

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

@@ -1,309 +1,166 @@
// 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 {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl';
var holders = defineMessages({
saving: {
id: 'admin.support.saving',
defaultMessage: 'Saving Config...'
}
});
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) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.state = {
saveNeeded: false,
serverError: null
};
this.renderSettings = this.renderSettings.bind(this);
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() {
var s = {saveNeeded: true, serverError: this.state.serverError};
this.setState(s);
getConfigFromState(config) {
config.SupportSettings.TermsOfServiceLink = this.state.termsOfServiceLink;
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) {
e.preventDefault();
$('#save-button').button('loading');
var config = this.props.config;
config.SupportSettings.TermsOfServiceLink = ReactDOM.findDOMNode(this.refs.TermsOfServiceLink).value.trim();
config.SupportSettings.PrivacyPolicyLink = ReactDOM.findDOMNode(this.refs.PrivacyPolicyLink).value.trim();
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() {
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';
}
renderTitle() {
return (
<div className='wrapper--fixed'>
<div className='banner'>
<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
id='admin.support.title'
defaultMessage='Legal and Support Settings'
/>
</h3>
<form
className='form-horizontal'
role='form'
>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='TermsOfServiceLink'
>
<FormattedMessage
id='admin.support.termsTitle'
defaultMessage='Terms of Service link:'
/>
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='TermsOfServiceLink'
ref='TermsOfServiceLink'
defaultValue={this.props.config.SupportSettings.TermsOfServiceLink}
onChange={this.handleChange}
/>
<p className='help-text'>
<FormattedMessage
id='admin.support.termsDesc'
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.'
/>
</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='PrivacyPolicyLink'
>
<FormattedMessage
id='admin.support.privacyTitle'
defaultMessage='Privacy Policy link:'
/>
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='PrivacyPolicyLink'
ref='PrivacyPolicyLink'
defaultValue={this.props.config.SupportSettings.PrivacyPolicyLink}
onChange={this.handleChange}
/>
<p className='help-text'>
<FormattedMessage
id='admin.support.privacyDesc'
defaultMessage='Link to Privacy Policy available to users on desktop and on mobile. Leaving this blank will hide the option to display a notice.'
/>
</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='AboutLink'
>
<FormattedMessage
id='admin.support.aboutTitle'
defaultMessage='About link:'
/>
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='AboutLink'
ref='AboutLink'
defaultValue={this.props.config.SupportSettings.AboutLink}
onChange={this.handleChange}
/>
<p className='help-text'>
<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.'
/>
</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='HelpLink'
>
<FormattedMessage
id='admin.support.helpTitle'
defaultMessage='Help link:'
/>
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='HelpLink'
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>
<h3>
<FormattedMessage
id='admin.customization.title'
defaultMessage='Customization Settings'
/>
</h3>
);
}
}
LegalAndSupportSettings.propTypes = {
intl: intlShape.isRequired,
config: React.PropTypes.object
};
export default injectIntl(LegalAndSupportSettings);
renderSettings() {
return (
<SettingsGroup
header={
<FormattedMessage
id='admin.customization.support'
defaultMessage='Legal and Support'
/>
}
>
<TextSetting
id='termsOfServiceLink'
label={
<FormattedMessage
id='admin.support.termsTitle'
defaultMessage='Terms of Service link:'
/>
}
helpText={
<FormattedMessage
id='admin.support.termsDesc'
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.'
/>
}
value={this.state.termsOfServiceLink}
onChange={this.handleChange}
/>
<TextSetting
id='privacyPolicyLink'
label={
<FormattedMessage
id='admin.support.privacyTitle'
defaultMessage='Privacy Policy link:'
/>
}
helpText={
<FormattedMessage
id='admin.support.privacyDesc'
defaultMessage='Link to Privacy Policy available to users on desktop and on mobile. Leaving this blank will hide the option to display a notice.'
/>
}
value={this.state.privacyPolicyLink}
onChange={this.handleChange}
/>
<TextSetting
id='aboutLink'
label={
<FormattedMessage
id='admin.support.aboutTitle'
defaultMessage='About link:'
/>
}
helpText={
<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.aboutLink}
onChange={this.handleChange}
/>
<TextSetting
id='helpLink'
label={
<FormattedMessage
id='admin.support.helpTitle'
defaultMessage='Help link:'
/>
}
helpText={
<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.'
/>
}
value={this.state.helpLink}
onChange={this.handleChange}
/>
<TextSetting
id='reportAProblemLink'
label={
<FormattedMessage
id='admin.support.problemTitle'
defaultMessage='Report a Problem link:'
/>
}
helpText={
<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.'
/>
}
value={this.state.reportAProblemLink}
onChange={this.handleChange}
/>
<TextSetting
id='supportEmail'
label={
<FormattedMessage
id='admin.support.emailTitle'
defaultMessage='Support email:'
/>
}
helpText={
<FormattedMessage
id='admin.support.emailHelp'
defaultMessage='Email shown during tutorial for end users to ask support questions.'
/>
}
value={this.state.supportEmail}
onChange={this.handleChange}
/>
</SettingsGroup>
);
}
}

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

@@ -1,418 +1,246 @@
// 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 {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';
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) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.state = {
consoleEnable: this.props.config.LogSettings.EnableConsole,
fileEnable: this.props.config.LogSettings.EnableFile,
saveNeeded: false,
serverError: null
};
this.renderSettings = this.renderSettings.bind(this);
this.state = Object.assign(this.state, {
enableConsole: props.config.LogSettings.EnableConsole,
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) {
var s = {saveNeeded: true, serverError: this.state.serverError};
getConfigFromState(config) {
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') {
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);
return config;
}
handleSubmit(e) {
e.preventDefault();
$('#save-button').button('loading');
var config = this.props.config;
config.LogSettings.EnableConsole = ReactDOM.findDOMNode(this.refs.consoleEnable).checked;
config.LogSettings.ConsoleLevel = ReactDOM.findDOMNode(this.refs.consoleLevel).value;
config.LogSettings.EnableFile = ReactDOM.findDOMNode(this.refs.fileEnable).checked;
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');
}
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.general.title'
defaultMessage='General Settings'
/>
</h3>
);
}
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';
}
renderSettings() {
const logLevels = [
{value: 'DEBUG', text: 'DEBUG'},
{value: 'INFO', text: 'INFO'},
{value: 'ERROR', text: 'ERROR'}
];
return (
<div className='wrapper--fixed'>
<h3>
<SettingsGroup
header={
<FormattedMessage
id='admin.log.logSettings'
defaultMessage='Log Settings'
id='admin.general.log'
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'
>
<div className='form-group'>
<label
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')}
/>
<tbody>
<tr>
<td className='help-text'>{'%T'}</td><td className='help-text'>
<FormattedMessage
id='admin.log.true'
defaultMessage='true'
/>
</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')}
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.false'
defaultMessage='false'
id='admin.log.formatDateLong'
defaultMessage='Date (2006/01/02)'
/>
</label>
<p className='help-text'>
</td>
</tr>
<tr>
<td className='help-text'>{'%d'}</td><td className='help-text'>
<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).'
id='admin.log.formatDateShort'
defaultMessage='Date (01/02/06)'
/>
</p>
</div>
</div>
<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'>
</td>
</tr>
<tr>
<td className='help-text'>{'%L'}</td><td className='help-text'>
<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.'
/>
</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')}
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.true'
defaultMessage='true'
/>
</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')}
id='admin.log.formatSource'
defaultMessage='Source'
/>
</td>
</tr>
<tr>
<td className='help-text'>{'%M'}</td><td className='help-text'>
<FormattedMessage
id='admin.log.false'
defaultMessage='false'
id='admin.log.formatMessage'
defaultMessage='Message'
/>
</label>
<p className='help-text'>
<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.'
/>
</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>
</td>
</tr>
</tbody>
</table>
</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>
);
}
}

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

@@ -99,4 +99,4 @@ export default class Logs extends React.Component {
</div>
);
}
}
}

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

@@ -1,215 +1,90 @@
// 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 {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl';
const holders = defineMessages({
saving: {
id: 'admin.privacy.saving',
defaultMessage: 'Saving Config...'
}
});
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) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.state = {
saveNeeded: false,
serverError: null
};
this.renderSettings = this.renderSettings.bind(this);
this.state = Object.assign(this.state, {
showEmailAddress: props.config.PrivacySettings.ShowEmailAddress,
showFullName: props.config.PrivacySettings.ShowFullName
});
}
handleChange() {
var s = {saveNeeded: true, serverError: this.state.serverError};
getConfigFromState(config) {
config.PrivacySettings.ShowEmailAddress = this.state.showEmailAddress;
config.PrivacySettings.ShowFullName = this.state.showFullName;
this.setState(s);
return config;
}
handleSubmit(e) {
e.preventDefault();
$('#save-button').button('loading');
var config = this.props.config;
config.PrivacySettings.ShowEmailAddress = ReactDOM.findDOMNode(this.refs.ShowEmailAddress).checked;
config.PrivacySettings.ShowFullName = ReactDOM.findDOMNode(this.refs.ShowFullName).checked;
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() {
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';
}
renderTitle() {
return (
<div className='wrapper--fixed'>
<h3>
<FormattedMessage
id='admin.privacy.title'
defaultMessage='Privacy Settings'
/>
</h3>
<form
className='form-horizontal'
role='form'
>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='ShowEmailAddress'
>
<FormattedMessage
id='admin.privacy.showEmailTitle'
defaultMessage='Show Email Address: '
/>
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
<input
type='radio'
name='ShowEmailAddress'
value='true'
ref='ShowEmailAddress'
defaultChecked={this.props.config.PrivacySettings.ShowEmailAddress}
onChange={this.handleChange}
/>
<FormattedMessage
id='admin.privacy.true'
defaultMessage='true'
/>
</label>
<label className='radio-inline'>
<input
type='radio'
name='ShowEmailAddress'
value='false'
defaultChecked={!this.props.config.PrivacySettings.ShowEmailAddress}
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>
<h3>
<FormattedMessage
id='admin.general.title'
defaultMessage='General Settings'
/>
</h3>
);
}
}
PrivacySettings.propTypes = {
intl: intlShape.isRequired,
config: React.PropTypes.object
};
export default injectIntl(PrivacySettings);
renderSettings() {
return (
<SettingsGroup
header={
<FormattedMessage
id='admin.general.privacy'
defaultMessage='Privacy'
/>
}
>
<BooleanSetting
id='showEmailAddress'
label={
<FormattedMessage
id='admin.privacy.showEmailTitle'
defaultMessage='Show Email Address: '
/>
}
helpText={
<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.'
/>
}
value={this.state.showEmailAddress}
onChange={this.handleChange}
/>
<BooleanSetting
id='showFullName'
label={
<FormattedMessage
id='admin.privacy.showFullNameTitle'
defaultMessage='Show Full Name: '
/>
}
helpText={
<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.'
/>
}
value={this.state.showFullName}
onChange={this.handleChange}
/>
</SettingsGroup>
);
}
}

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

@@ -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.
// 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';
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) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.state = {
EnableRateLimiter: this.props.config.RateLimitSettings.EnableRateLimiter,
VaryByRemoteAddr: this.props.config.RateLimitSettings.VaryByRemoteAddr,
saveNeeded: false,
serverError: null
};
this.renderSettings = this.renderSettings.bind(this);
this.state = Object.assign(this.state, {
enableRateLimiter: props.config.RateLimitSettings.EnableRateLimiter,
perSec: props.config.RateLimitSettings.PerSec,
memoryStoreSize: props.config.RateLimitSettings.MemoryStoreSize,
varyByRemoteAddr: props.config.RateLimitSettings.VaryByRemoteAddr,
varyByHeader: props.config.RateLimitSettings.VaryByHeader
});
}
handleChange(action) {
var s = {saveNeeded: true, serverError: this.state.serverError};
getConfigFromState(config) {
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') {
s.EnableRateLimiter = true;
}
if (action === 'EnableRateLimiterFalse') {
s.EnableRateLimiter = false;
}
if (action === 'VaryByRemoteAddrTrue') {
s.VaryByRemoteAddr = true;
}
if (action === 'VaryByRemoteAddrFalse') {
s.VaryByRemoteAddr = false;
}
this.setState(s);
return config;
}
handleSubmit(e) {
e.preventDefault();
$('#save-button').button('loading');
var config = this.props.config;
config.RateLimitSettings.EnableRateLimiter = ReactDOM.findDOMNode(this.refs.EnableRateLimiter).checked;
config.RateLimitSettings.VaryByRemoteAddr = ReactDOM.findDOMNode(this.refs.VaryByRemoteAddr).checked;
config.RateLimitSettings.VaryByHeader = ReactDOM.findDOMNode(this.refs.VaryByHeader).value.trim();
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');
}
renderTitle() {
return (
<h3>
<FormattedMessage
id='admin.rate.title'
defaultMessage='Rate Limit Settings'
/>
</h3>
);
}
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';
}
renderSettings() {
return (
<div className='wrapper--fixed'>
<SettingsGroup>
<div className='banner'>
<div className='banner__content'>
<h4 className='banner__heading'>
<FormattedMessage
id='admin.rate.noteTitle'
defaultMessage='Note:'
/>
</h4>
<p>
<FormattedMessage
id='admin.rate.noteDescription'
defaultMessage='Changing properties in this section will require a server restart before taking effect.'
/>
</p>
<FormattedMessage
id='admin.rate.noteDescription'
defaultMessage='Changing properties in this section will require a server restart before taking effect.'
/>
</div>
</div>
<h3>
<FormattedMessage
id='admin.rate.title'
defaultMessage='Rate Limit Settings'
/>
</h3>
<form
className='form-horizontal'
role='form'
>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='EnableRateLimiter'
>
<FormattedMessage
id='admin.rate.enableLimiterTitle'
defaultMessage='Enable Rate Limiter: '
/>
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
<input
type='radio'
name='EnableRateLimiter'
value='true'
ref='EnableRateLimiter'
defaultChecked={this.props.config.RateLimitSettings.EnableRateLimiter}
onChange={this.handleChange.bind(this, 'EnableRateLimiterTrue')}
/>
<FormattedMessage
id='admin.rate.true'
defaultMessage='true'
/>
</label>
<label className='radio-inline'>
<input
type='radio'
name='EnableRateLimiter'
value='false'
defaultChecked={!this.props.config.RateLimitSettings.EnableRateLimiter}
onChange={this.handleChange.bind(this, 'EnableRateLimiterFalse')}
/>
<FormattedMessage
id='admin.rate.false'
defaultMessage='false'
/>
</label>
<p className='help-text'>
<FormattedMessage
id='admin.rate.enableLimiterDescription'
defaultMessage='When true, APIs are throttled at rates specified below.'
/>
</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='PerSec'
>
<FormattedMessage
id='admin.rate.queriesTitle'
defaultMessage='Number Of Queries Per Second:'
/>
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
id='PerSec'
ref='PerSec'
placeholder={formatMessage(holders.queriesExample)}
defaultValue={this.props.config.RateLimitSettings.PerSec}
onChange={this.handleChange}
disabled={!this.state.EnableRateLimiter}
/>
<p className='help-text'>
<FormattedMessage
id='admin.rate.queriesDescription'
defaultMessage='Throttles API at this number of requests per second.'
/>
</p>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='MemoryStoreSize'
>
<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>
<BooleanSetting
id='enableRateLimiter'
label={
<FormattedMessage
id='admin.rate.enableLimiterTitle'
defaultMessage='Enable Rate Limiter: '
/>
}
helpText={
<FormattedMessage
id='admin.rate.enableLimiterDescription'
defaultMessage='When true, APIs are throttled at rates specified below.'
/>
}
value={this.state.enableRateLimiter}
onChange={this.handleChange}
/>
<TextSetting
id='perSec'
label={
<FormattedMessage
id='admin.rate.queriesTitle'
defaultMessage='Number Of Queries Per Second:'
/>
}
placeholder={Utils.localizeMessage('admin.rate.queriesExample', 'Ex "10"')}
helpText={
<FormattedMessage
id='admin.rate.queriesDescription'
defaultMessage='Throttles API at this number of requests per second.'
/>
}
value={this.state.perSec}
onChange={this.handleChange}
disabled={!this.state.enableRateLimiter}
/>
<TextSetting
id='memoryStoreSize'
label={
<FormattedMessage
id='admin.rate.memoryTitle'
defaultMessage='Memory Store Size:'
/>
}
placeholder={Utils.localizeMessage('admin.rate.memoryExample', 'Ex "10000"')}
helpText={
<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.'
/>
}
value={this.state.memoryStoreSize}
onChange={this.handleChange}
disabled={!this.state.enableRateLimiter}
/>
<BooleanSetting
id='varyByRemoteAddr'
label={
<FormattedMessage
id='admin.rate.remoteTitle'
defaultMessage='Vary By Remote Address: '
/>
}
helpText={
<FormattedMessage
id='admin.rate.remoteDescription'
defaultMessage='When true, rate limit API access by IP address.'
/>
}
value={this.state.varyByRemoteAddr}
onChange={this.handleChange}
disabled={!this.state.enableRateLimiter}
/>
<TextSetting
id='varyByHeader'
label={
<FormattedMessage
id='admin.rate.httpHeaderTitle'
defaultMessage='Vary By HTTP Header:'
/>
}
placeholder={Utils.localizeMessage('admin.rate.httpHeaderExample', 'Ex "X-Real-IP", "X-Forwarded-For"')}
helpText={
<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").'
/>
}
value={this.state.varyByHeader}
onChange={this.handleChange}
disabled={!this.state.enableRateLimiter || this.state.varyByRemoteAddr}
/>
</SettingsGroup>
);
}
}
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 {
render() {
let marginClass = '';
if (this.props.margin === 'small') {
marginClass = ' form-group--small';
}
return (
<div className={'form-group' + marginClass}>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor={this.props.inputId}
>
{this.props.label}
</label>
<div className='col-sm-8'>
{this.props.children}
<div className='help-text'>
{this.props.helpText}
</div>
</div>
</div>
);
@@ -28,7 +27,8 @@ Setting.defaultProps = {
};
Setting.propTypes = {
inputId: React.PropTypes.string,
label: 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.
// See License.txt for license information.
import AdminStore from 'stores/admin_store.jsx';
import Client from 'utils/web_client.jsx';
import FormError from 'components/form_error.jsx';
import LoadingScreen from '../loading_screen.jsx';
import UserItem from './user_item.jsx';
import ResetPasswordModal from './reset_password_modal.jsx';
@@ -11,9 +13,17 @@ import {FormattedMessage} from 'react-intl';
import React from 'react';
export default class UserList extends React.Component {
static get propTypes() {
return {
params: React.PropTypes.object.isRequired
};
}
constructor(props) {
super(props);
this.onAllTeamsChange = this.onAllTeamsChange.bind(this);
this.getTeamProfiles = this.getTeamProfiles.bind(this);
this.getCurrentTeamProfiles = this.getCurrentTeamProfiles.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.state = {
teamId: props.team.id,
team: AdminStore.getTeam(this.props.params.team),
users: null,
teamMembers: null,
serverError: null,
@@ -35,8 +45,14 @@ export default class UserList extends React.Component {
this.getCurrentTeamProfiles();
}
onAllTeamsChange() {
this.setState({
team: AdminStore.getTeam(this.props.params.team)
});
}
getCurrentTeamProfiles() {
this.getTeamProfiles(this.props.team.id);
this.getTeamProfiles(this.props.params.team);
}
getTeamProfiles(teamId) {
@@ -133,9 +149,8 @@ export default class UserList extends React.Component {
}
render() {
var serverError = '';
if (this.state.serverError) {
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
if (!this.state.team) {
return 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'
defaultMessage='Users for {team}'
values={{
team: this.props.team.name
team: this.state.team.name
}}
/>
</h3>
{serverError}
<FormError error={this.state.serverError}/>
<LoadingScreen/>
</div>
);
@@ -161,7 +176,7 @@ export default class UserList extends React.Component {
return (
<UserItem
team={this.props.team}
team={this.state.team}
key={'user_' + user.id}
user={user}
teamMember={teamMember}
@@ -177,12 +192,12 @@ export default class UserList extends React.Component {
id='admin.userList.title2'
defaultMessage='Users for {team} ({count})'
values={{
team: this.props.team.name,
team: this.state.team.name,
count: this.state.users.length
}}
/>
</h3>
{serverError}
<FormError error={this.state.serverError}/>
<form
className='form-horizontal'
role='form'
@@ -194,7 +209,7 @@ export default class UserList extends React.Component {
<ResetPasswordModal
user={this.state.user}
show={this.state.showPasswordModal}
team={this.props.team}
team={this.state.team}
onModalSubmit={this.doPasswordResetSubmit}
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 = {
intl: intlShape.isRequired,
team: React.PropTypes.object
intl: intlShape.isRequired
};
export default injectIntl(SystemAnalytics);

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

@@ -5,6 +5,7 @@ import LineChart from './line_chart.jsx';
import StatisticCount from './statistic_count.jsx';
import TableChart from './table_chart.jsx';
import AdminStore from 'stores/admin_store.jsx';
import AnalyticsStore from 'stores/analytics_store.jsx';
import * as Utils from 'utils/utils.jsx';
@@ -13,23 +14,34 @@ import Constants from 'utils/constants.jsx';
const StatTypes = Constants.StatTypes;
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';
class TeamAnalytics extends React.Component {
export default class TeamAnalytics extends React.Component {
static get propTypes() {
return {
params: React.PropTypes.object.isRequired
};
}
constructor(props) {
super(props);
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() {
AnalyticsStore.addChangeListener(this.onChange);
AdminStore.addAllTeamsChangeListener(this.onAllTeamsChange);
this.getData(this.props.team.id);
this.getData(this.props.params.team);
}
getData(id) {
@@ -41,11 +53,14 @@ class TeamAnalytics extends React.Component {
componentWillUnmount() {
AnalyticsStore.removeChangeListener(this.onChange);
AdminStore.removeAllTeamsChangeListener(this.onAllTeamsChange);
}
componentWillReceiveProps(nextProps) {
this.getData(nextProps.team.id);
this.setState({stats: AnalyticsStore.getAllTeam(nextProps.team.id)});
this.getData(nextProps.params.team);
this.setState({
stats: AnalyticsStore.getAllTeam(nextProps.params.team)
});
}
shouldComponentUpdate(nextProps, nextState) {
@@ -53,7 +68,7 @@ class TeamAnalytics extends React.Component {
return true;
}
if (!Utils.areObjectsEqual(nextProps.team, this.props.team)) {
if (!Utils.areObjectsEqual(nextProps.params.team, this.props.params.team)) {
return true;
}
@@ -61,10 +76,22 @@ class TeamAnalytics extends React.Component {
}
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() {
if (!this.state.team || !this.state.stats) {
return null;
}
const stats = this.state.stats;
const postCountsDay = formatPostsPerDayData(stats[StatTypes.POST_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'
defaultMessage='Team Statistics for {team}'
values={{
team: this.props.team.name
team: this.state.team.name
}}
/>
</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) {
if (data == null) {
return [];

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

@@ -436,29 +436,46 @@
"admin.service.webhooksTitle": "Enable Incoming Webhooks: ",
"admin.sidebar.addTeamSidebar": "Add team from sidebar menu",
"admin.sidebar.audits": "Compliance and Auditing",
"admin.sidebar.compliance": "Compliance Settings",
"admin.sidebar.email": "Email Settings",
"admin.sidebar.file": "File Settings",
"admin.sidebar.gitlab": "GitLab Settings",
"admin.sidebar.ldap": "LDAP Settings",
"admin.sidebar.authentication": "Authentication",
"admin.sidebar.compliance": "Compliance",
"admin.sidebar.configuration": "Configuration",
"admin.sidebar.connections": "Connections",
"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.loading": "Loading",
"admin.sidebar.log": "Log Settings",
"admin.sidebar.logging": "Logging",
"admin.sidebar.login": "Login",
"admin.sidebar.logs": "Logs",
"admin.sidebar.notifications": "Notifications",
"admin.sidebar.other": "OTHER",
"admin.sidebar.privacy": "Privacy Settings",
"admin.sidebar.rate_limit": "Rate Limit Settings",
"admin.sidebar.privacy": "Privacy",
"admin.sidebar.publicLinks": "Public Links",
"admin.sidebar.push": "Mobile Push",
"admin.sidebar.rateLimiting": "Rate Limiting",
"admin.sidebar.reports": "SITE REPORTS",
"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.sql": "SQL Settings",
"admin.sidebar.statistics": "- Statistics",
"admin.sidebar.support": "Legal and Support Settings",
"admin.sidebar.team": "Team Settings",
"admin.sidebar.teams": "TEAMS ({count})",
"admin.sidebar.users": "- Users",
"admin.sidebar.statistics": "Statistics",
"admin.sidebar.storage": "Storage",
"admin.sidebar.support": "Legal and Support",
"admin.sidebar.teams": "TEAMS ({count, number})",
"admin.sidebar.users": "Users",
"admin.sidebar.usersAndTeams": "Users and Teams",
"admin.sidebar.view_statistics": "View Statistics",
"admin.sidebar.webhooks": "Webhooks and Commands",
"admin.sidebarHeader.systemConsole": "System Console",
"admin.sql.dataSource": "Data Source:",
"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.webhooksTitle": "Habilitar Webhooks de Entrada: ",
"admin.sidebar.addTeamSidebar": "Agregar un equipo el menú lateral",
"admin.sidebar.audits": "Auditorías",
"admin.sidebar.compliance": "Configuración de Cumplimiento",
"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.gitlab": "GitLab",
"admin.sidebar.ldap": "LDAP",
"admin.sidebar.license": "Edición y Licencia",
"admin.sidebar.loading": "Cargando",
"admin.sidebar.log": "Configuracion de log",
"admin.sidebar.logs": "Registros",
"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.rmTeamSidebar": "Remover un equipo del menú lateral",
"admin.sidebar.service": "Configuración de servicio",
"admin.sidebar.settings": "CONFIGURACIONES",
"admin.sidebar.sql": "Configuración de SQL",
"admin.sidebar.statistics": "- Estadísticas",
"admin.sidebar.support": "Configuración de Soporte",
"admin.sidebar.team": "Configuración de equipo",
"admin.sidebar.teams": "EQUIPOS ({count})",
"admin.sidebar.users": "- Usuarios",
"admin.sidebar.statistics": "Estadísticas",
"admin.sidebar.teams": "EQUIPOS ({count, number})",
"admin.sidebar.users": "Usuarios",
"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.sql.dataSource": "Origen de datos:",
"admin.sql.driverName": "Nombre de controlador:",

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

@@ -370,29 +370,27 @@
"admin.service.webhooksTitle": "Activer les webhooks entrants :",
"admin.sidebar.addTeamSidebar": "Afficher l'équipe dans le menu",
"admin.sidebar.audits": "Conformité et vérification",
"admin.sidebar.compliance": "Paramètres de conformité",
"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.gitlab": "GitLab",
"admin.sidebar.other": "AUTRES",
"admin.sidebar.privacy": "Paramètres de confidentialité",
"admin.sidebar.rate_limit": "Configuration des limites de débits",
"admin.sidebar.ldap": "LDAP",
"admin.sidebar.license": "Édition et licence",
"admin.sidebar.logs": "Journaux",
"admin.sidebar.reports": "RAPPORTS DU SITE",
"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.settings": "REGLAGES",
"admin.sidebar.sql": "Configuration SQL",
"admin.sidebar.statistics": "- Statistiques",
"admin.sidebar.support": "Paramètres légaux et support",
"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.sql.dataSource": "Source de données :",
"admin.sql.driverName": "Nom du pilote :",

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

@@ -436,28 +436,16 @@
"admin.service.webhooksTitle": "Ativar Webhooks Entrada: ",
"admin.sidebar.addTeamSidebar": "Adicionar equipe do menu lateral",
"admin.sidebar.audits": "Conformidade e Auditoria",
"admin.sidebar.compliance": "Configurações Compliance",
"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.gitlab": "GitLab",
"admin.sidebar.other": "OUTROS",
"admin.sidebar.privacy": "Configurações De Privacidade",
"admin.sidebar.rate_limit": "Configurações Rate Limit",
"admin.sidebar.ldap": "LDAP",
"admin.sidebar.license": "Edição e Licença",
"admin.sidebar.logs": "Logs",
"admin.sidebar.reports": "RELATÓRIOS DO SITE",
"admin.sidebar.rmTeamSidebar": "Remover equipe do menu lateral",
"admin.sidebar.service": "Configurações do Serviço",
"admin.sidebar.settings": "CONFIGURAÇÕES",
"admin.sidebar.sql": "Configurações SQL",
"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.statistics": "Estátisticas",
"admin.sidebar.teams": "EQUIPES ({count, number})",
"admin.sidebar.users": "Usuários",
"admin.sidebar.view_statistics": "Ver Estatísticas",
"admin.sidebarHeader.systemConsole": "Console do Sistema",
"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 ShouldVerifyEmail from 'components/should_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 BackstageNavbar from 'components/backstage/backstage_navbar.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';
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 EmailToOAuth from 'components/claim/components/email_to_oauth.jsx';
import OAuthToEmail from 'components/claim/components/oauth_to_email.jsx';
@@ -319,7 +350,161 @@ function renderRootComponent() {
<Route
path='admin_console'
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
path=':team'
component={NeedsTeam}

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

@@ -11,6 +11,7 @@
@import 'modal';
@import 'oauth';
@import 'popover';
@import 'save-button';
@import 'scrollbar';
@import 'search';
@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
.sidebar--left,
.sidebar--menu {
.sidebar--menu,
.admin-sidebar {
.team__header {
@include legacy-pie-clearfix;
padding: 9px 10px;

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

@@ -1,6 +1,12 @@
@charset 'UTF-8';
.admin-controller {
.admin-console {
color: #333;
height: 100%;
margin-left: 220px;
overflow: auto;
padding: 0 20px;
> div {
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 {
background-color: white;
border: 1px solid #ddd;
@@ -160,168 +50,160 @@
width: 100%;
}
.app__content {
color: #333;
&.admin {
background-color: #f1f1f1;
min-height: 600px;
overflow: auto;
padding: 0 40px 20px;
}
&.admin {
background-color: #f1f1f1;
min-height: 600px;
overflow: auto;
padding: 0 40px 20px;
.wrapper--fixed {
max-width: 800px;
}
.form-horizontal {
margin-top: 40px;
.control-label {
font-weight: 600;
padding-right: 0;
text-align: left;
}
.wrapper--fixed {
max-width: 800px;
.form-group {
margin-bottom: 25px;
}
.form-horizontal {
margin-top: 40px;
.file__upload {
display: inline-block;
margin: 0 10px 10px 0;
position: relative;
.control-label {
font-weight: 600;
padding-right: 0;
text-align: left;
}
.form-group {
margin-bottom: 25px;
&.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;
}
input {
@include opacity(0);
height: 100%;
left: 0;
position: absolute;
top: 0;
width: 100%;
z-index: 5;
}
}
.banner {
background: $white;
border: 1px solid #ddd;
font-size: .95em;
margin: 2em 0;
padding: .7em 1.5em;
.help-text {
color: #777;
margin: 10px 0 0 15px;
.banner__heading {
font-size: 1.5em;
&.no-margin {
margin: 0;
}
.banner__content {
width: 80%;
&.no-margin--top {
margin-top: 0;
}
&.warning {
background: #e60000;
ul,
ol {
padding-left: 23px;
}
.help-link {
margin-right: 5px;
}
.btn {
font-size: 13px;
}
}
.popover {
border-radius: 3px;
font-size: .95em;
width: 100%;
.alert {
display: inline-block;
margin: 1em 0 0;
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;
border: none;
padding: 10px 0;
}
.panel-default {
> .panel-heading {
background-color: transparent;
padding: 10px 0;
}
.panel-body {
padding: 30px 0 10px;
}
.panel-body {
padding: 30px 0 10px;
}
}
.panel-group {
margin-bottom: 50px;
}
.panel-group {
margin-bottom: 50px;
}
.panel-title {
font-size: 24px;
line-height: 1.5;
.panel-title {
font-size: 24px;
line-height: 1.5;
a {
@include clearfix;
display: block;
text-decoration: none;
a {
@include clearfix;
display: block;
text-decoration: none;
&.collapsed {
.fa-minus {
display: none;
}
.fa-plus {
display: inline-block;
}
}
.fa {
color: #aaa;
float: right;
font-size: 18px;
margin-top: 8px;
&.collapsed {
.fa-minus {
display: none;
}
.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;
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.audits = null;
this.config = null;
this.teams = null;
this.teams = {};
this.complianceReports = null;
}
@@ -126,6 +126,10 @@ class AdminStoreClass extends EventEmitter {
this.teams = teams;
}
getTeam(id) {
return this.teams[id];
}
getSelectedTeams() {
const result = BrowserStore.getItem('seleted_teams');
if (!result) {