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
Этот коммит содержится в:
коммит произвёл
Christopher Speller
родитель
5f5f813387
Коммит
fd53e3b786
61
webapp/components/admin_console/admin_console.jsx
Обычный файл
61
webapp/components/admin_console/admin_console.jsx
Обычный файл
@@ -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
|
||||
};
|
||||
115
webapp/components/admin_console/admin_settings.jsx
Обычный файл
115
webapp/components/admin_console/admin_settings.jsx
Обычный файл
@@ -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
|
||||
};
|
||||
|
||||
83
webapp/components/admin_console/admin_sidebar_category.jsx
Обычный файл
83
webapp/components/admin_console/admin_sidebar_category.jsx
Обычный файл
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
80
webapp/components/admin_console/admin_sidebar_section.jsx
Обычный файл
80
webapp/components/admin_console/admin_sidebar_section.jsx
Обычный файл
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {Link} from 'react-router';
|
||||
|
||||
export default class 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
87
webapp/components/admin_console/admin_sidebar_team.jsx
Обычный файл
87
webapp/components/admin_console/admin_sidebar_team.jsx
Обычный файл
@@ -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
|
||||
};
|
||||
|
||||
182
webapp/components/admin_console/brand_image_setting.jsx
Обычный файл
182
webapp/components/admin_console/brand_image_setting.jsx
Обычный файл
@@ -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
|
||||
};
|
||||
|
||||
}
|
||||
74
webapp/components/admin_console/configuration_settings.jsx
Обычный файл
74
webapp/components/admin_console/configuration_settings.jsx
Обычный файл
@@ -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
|
||||
};
|
||||
|
||||
94
webapp/components/admin_console/connection_settings.jsx
Обычный файл
94
webapp/components/admin_console/connection_settings.jsx
Обычный файл
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
137
webapp/components/admin_console/custom_brand_settings.jsx
Обычный файл
137
webapp/components/admin_console/custom_brand_settings.jsx
Обычный файл
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
192
webapp/components/admin_console/database_settings.jsx
Обычный файл
192
webapp/components/admin_console/database_settings.jsx
Обычный файл
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
83
webapp/components/admin_console/developer_settings.jsx
Обычный файл
83
webapp/components/admin_console/developer_settings.jsx
Обычный файл
@@ -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
|
||||
};
|
||||
|
||||
109
webapp/components/admin_console/email_authentication_settings.jsx
Обычный файл
109
webapp/components/admin_console/email_authentication_settings.jsx
Обычный файл
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
118
webapp/components/admin_console/email_connection_test.jsx
Обычный файл
118
webapp/components/admin_console/email_connection_test.jsx
Обычный файл
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
94
webapp/components/admin_console/external_service_settings.jsx
Обычный файл
94
webapp/components/admin_console/external_service_settings.jsx
Обычный файл
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
97
webapp/components/admin_console/generated_setting.jsx
Обычный файл
97
webapp/components/admin_console/generated_setting.jsx
Обычный файл
@@ -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);
|
||||
}
|
||||
130
webapp/components/admin_console/login_settings.jsx
Обычный файл
130
webapp/components/admin_console/login_settings.jsx
Обычный файл
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
91
webapp/components/admin_console/public_link_settings.jsx
Обычный файл
91
webapp/components/admin_console/public_link_settings.jsx
Обычный файл
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
235
webapp/components/admin_console/push_settings.jsx
Обычный файл
235
webapp/components/admin_console/push_settings.jsx
Обычный файл
@@ -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);
|
||||
}
|
||||
61
webapp/components/admin_console/save_button.jsx
Обычный файл
61
webapp/components/admin_console/save_button.jsx
Обычный файл
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
134
webapp/components/admin_console/session_settings.jsx
Обычный файл
134
webapp/components/admin_console/session_settings.jsx
Обычный файл
@@ -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
|
||||
};
|
||||
|
||||
42
webapp/components/admin_console/settings_group.jsx
Обычный файл
42
webapp/components/admin_console/settings_group.jsx
Обычный файл
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
124
webapp/components/admin_console/signup_settings.jsx
Обычный файл
124
webapp/components/admin_console/signup_settings.jsx
Обычный файл
@@ -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);
|
||||
180
webapp/components/admin_console/storage_settings.jsx
Обычный файл
180
webapp/components/admin_console/storage_settings.jsx
Обычный файл
@@ -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
|
||||
};
|
||||
|
||||
83
webapp/components/admin_console/text_setting.jsx
Обычный файл
83
webapp/components/admin_console/text_setting.jsx
Обычный файл
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
179
webapp/components/admin_console/users_and_teams_settings.jsx
Обычный файл
179
webapp/components/admin_console/users_and_teams_settings.jsx
Обычный файл
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
166
webapp/components/admin_console/webhook_settings.jsx
Обычный файл
166
webapp/components/admin_console/webhook_settings.jsx
Обычный файл
@@ -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:",
|
||||
|
||||
189
webapp/root.jsx
189
webapp/root.jsx
@@ -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
Обычный файл
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) {
|
||||
|
||||
Ссылка в новой задаче
Block a user