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.
|
// See License.txt for license information.
|
||||||
|
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
|
|
||||||
import AdminSidebarHeader from './admin_sidebar_header.jsx';
|
|
||||||
import SelectTeamModal from './select_team_modal.jsx';
|
|
||||||
import * as Utils from 'utils/utils.jsx';
|
|
||||||
|
|
||||||
import {FormattedMessage} from 'react-intl';
|
|
||||||
|
|
||||||
import {Tooltip, OverlayTrigger} from 'react-bootstrap';
|
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import AdminStore from 'stores/admin_store.jsx';
|
||||||
|
import * as AsyncClient from 'utils/async_client.jsx';
|
||||||
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
|
||||||
|
import AdminSidebarHeader from './admin_sidebar_header.jsx';
|
||||||
|
import AdminSidebarTeam from './admin_sidebar_team.jsx';
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
import {browserHistory} from 'react-router';
|
||||||
|
import {OverlayTrigger, Tooltip} from 'react-bootstrap';
|
||||||
|
import SelectTeamModal from './select_team_modal.jsx';
|
||||||
|
import AdminSidebarCategory from './admin_sidebar_category.jsx';
|
||||||
|
import AdminSidebarSection from './admin_sidebar_section.jsx';
|
||||||
|
|
||||||
export default class AdminSidebar extends React.Component {
|
export default class AdminSidebar extends React.Component {
|
||||||
|
static get contextTypes() {
|
||||||
|
return {
|
||||||
|
router: React.PropTypes.object.isRequired
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.isSelected = this.isSelected.bind(this);
|
this.handleAllTeamsChange = this.handleAllTeamsChange.bind(this);
|
||||||
this.handleClick = this.handleClick.bind(this);
|
|
||||||
this.removeTeam = this.removeTeam.bind(this);
|
this.removeTeam = this.removeTeam.bind(this);
|
||||||
|
|
||||||
this.showTeamSelect = this.showTeamSelect.bind(this);
|
this.showTeamSelect = this.showTeamSelect.bind(this);
|
||||||
this.teamSelectedModal = this.teamSelectedModal.bind(this);
|
this.teamSelectedModal = this.teamSelectedModal.bind(this);
|
||||||
this.teamSelectedModalDismissed = this.teamSelectedModalDismissed.bind(this);
|
this.teamSelectedModalDismissed = this.teamSelectedModalDismissed.bind(this);
|
||||||
|
|
||||||
|
this.renderAddTeamButton = this.renderAddTeamButton.bind(this);
|
||||||
|
this.renderTeams = this.renderTeams.bind(this);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
teams: AdminStore.getAllTeams(),
|
||||||
|
selectedTeams: AdminStore.getSelectedTeams(),
|
||||||
showSelectModal: false
|
showSelectModal: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
AdminStore.addAllTeamsChangeListener(this.handleAllTeamsChange);
|
||||||
|
AsyncClient.getAllTeams();
|
||||||
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
if (!Utils.isMobile()) {
|
if (!Utils.isMobile()) {
|
||||||
$('.sidebar--left .nav-pills__container').perfectScrollbar();
|
$('.admin-sidebar .nav-pills__container').perfectScrollbar();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClick(name, teamId, e) {
|
componentWillUnmount() {
|
||||||
e.preventDefault();
|
AdminStore.removeAllTeamsChangeListener(this.handleAllTeamsChange);
|
||||||
this.props.selectTab(name, teamId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isSelected(name, teamId) {
|
handleAllTeamsChange() {
|
||||||
if (this.props.selected === name) {
|
this.setState({
|
||||||
if (name === 'team_users' || name === 'team_analytics') {
|
teams: AdminStore.getAllTeams(),
|
||||||
if (this.props.selectedTeam != null && this.props.selectedTeam === teamId) {
|
selectedTeams: AdminStore.getSelectedTeams()
|
||||||
return 'active';
|
});
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return 'active';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
removeTeam(teamId, e) {
|
removeTeam(team) {
|
||||||
e.preventDefault();
|
const selectedTeams = Object.assign({}, this.state.selectedTeams);
|
||||||
e.stopPropagation();
|
Reflect.deleteProperty(selectedTeams, team.id);
|
||||||
Reflect.deleteProperty(this.props.selectedTeams, teamId);
|
AdminStore.saveSelectedTeams(selectedTeams);
|
||||||
this.props.removeSelectedTeam(teamId);
|
|
||||||
|
|
||||||
if (this.props.selected === 'team_users') {
|
this.handleAllTeamsChange();
|
||||||
if (this.props.selectedTeam != null && this.props.selectedTeam === teamId) {
|
|
||||||
this.props.selectTab('service_settings', null);
|
if (this.context.router.isActive('/admin_console/team/' + team.id)) {
|
||||||
}
|
browserHistory.push('/admin_console');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,31 +85,23 @@ export default class AdminSidebar extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
teamSelectedModal(teamId) {
|
teamSelectedModal(teamId) {
|
||||||
this.setState({showSelectModal: false});
|
this.setState({
|
||||||
this.props.addSelectedTeam(teamId);
|
showSelectModal: false
|
||||||
this.forceUpdate();
|
});
|
||||||
|
|
||||||
|
const selectedTeams = Object.assign({}, this.state.selectedTeams);
|
||||||
|
selectedTeams[teamId] = true;
|
||||||
|
|
||||||
|
AdminStore.saveSelectedTeams(selectedTeams);
|
||||||
|
|
||||||
|
this.handleAllTeamsChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
teamSelectedModalDismissed() {
|
teamSelectedModalDismissed() {
|
||||||
this.setState({showSelectModal: false});
|
this.setState({showSelectModal: false});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
renderAddTeamButton() {
|
||||||
var count = '*';
|
|
||||||
var teams = (
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.sidebar.loading'
|
|
||||||
defaultMessage='Loading'
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
const removeTooltip = (
|
|
||||||
<Tooltip id='remove-team-tooltip'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.sidebar.rmTeamSidebar'
|
|
||||||
defaultMessage='Remove team from sidebar menu'
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
const addTeamTooltip = (
|
const addTeamTooltip = (
|
||||||
<Tooltip id='add-team-tooltip'>
|
<Tooltip id='add-team-tooltip'>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
@@ -108,393 +111,468 @@ export default class AdminSidebar extends React.Component {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.props.teams != null) {
|
return (
|
||||||
count = '' + Object.keys(this.props.teams).length;
|
<span className='menu-icon--right'>
|
||||||
|
<OverlayTrigger
|
||||||
|
delayShow={1000}
|
||||||
|
placement='top'
|
||||||
|
overlay={addTeamTooltip}
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href='#'
|
||||||
|
onClick={this.showTeamSelect}
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
className='fa fa-plus'
|
||||||
|
></i>
|
||||||
|
</a>
|
||||||
|
</OverlayTrigger>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
teams = [];
|
renderTeams() {
|
||||||
for (var key in this.props.selectedTeams) {
|
const teams = [];
|
||||||
if (this.props.selectedTeams.hasOwnProperty(key)) {
|
|
||||||
var team = this.props.teams[key];
|
|
||||||
|
|
||||||
if (team != null) {
|
for (const key in this.state.selectedTeams) {
|
||||||
teams.push(
|
if (!this.state.selectedTeams.hasOwnProperty(key)) {
|
||||||
<ul
|
continue;
|
||||||
key={'team_' + team.id}
|
|
||||||
className='nav nav__sub-menu'
|
|
||||||
>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href='#'
|
|
||||||
onClick={this.handleClick.bind(this, 'team_users', team.id)}
|
|
||||||
className={'nav__sub-menu-item ' + this.isSelected('team_users', team.id) + ' ' + this.isSelected('team_analytics', team.id)}
|
|
||||||
>
|
|
||||||
{team.name}
|
|
||||||
<OverlayTrigger
|
|
||||||
delayShow={1000}
|
|
||||||
placement='top'
|
|
||||||
overlay={removeTooltip}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className='menu-icon--right menu__close'
|
|
||||||
onClick={this.removeTeam.bind(this, team.id)}
|
|
||||||
style={{cursor: 'pointer'}}
|
|
||||||
>
|
|
||||||
{'×'}
|
|
||||||
</span>
|
|
||||||
</OverlayTrigger>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<ul className='nav nav__inner-menu'>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href='#'
|
|
||||||
className={this.isSelected('team_users', team.id)}
|
|
||||||
onClick={this.handleClick.bind(this, 'team_users', team.id)}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.sidebar.users'
|
|
||||||
defaultMessage='- Users'
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href='#'
|
|
||||||
className={this.isSelected('team_analytics', team.id)}
|
|
||||||
onClick={this.handleClick.bind(this, 'team_analytics', team.id)}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.sidebar.statistics'
|
|
||||||
defaultMessage='- Statistics'
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const team = this.state.teams[key];
|
||||||
|
|
||||||
|
if (!team) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
teams.push(
|
||||||
|
<AdminSidebarTeam
|
||||||
|
key={team.id}
|
||||||
|
team={team}
|
||||||
|
onRemoveTeam={this.removeTeam}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let ldapSettings;
|
return (
|
||||||
let complianceSettings;
|
<AdminSidebarCategory
|
||||||
let licenseSettings;
|
parentLink='/admin_console'
|
||||||
if (global.window.mm_config.BuildEnterpriseReady === 'true') {
|
icon='fa-gear'
|
||||||
if (global.window.mm_license.IsLicensed === 'true') {
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.sidebar.teams'
|
||||||
|
defaultMessage='TEAMS ({count, number})'
|
||||||
|
values={{
|
||||||
|
count: Object.keys(this.state.teams).length
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
action={this.renderAddTeamButton()}
|
||||||
|
>
|
||||||
|
{teams}
|
||||||
|
</AdminSidebarCategory>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let ldapSettings = null;
|
||||||
|
let complianceSettings = null;
|
||||||
|
|
||||||
|
let license = null;
|
||||||
|
let audits = null;
|
||||||
|
|
||||||
|
if (window.mm_config.BuildEnterpriseReady === 'true') {
|
||||||
|
if (window.mm_license.IsLicensed === 'true') {
|
||||||
if (global.window.mm_license.LDAP === 'true') {
|
if (global.window.mm_license.LDAP === 'true') {
|
||||||
ldapSettings = (
|
ldapSettings = (
|
||||||
<li>
|
<AdminSidebarSection
|
||||||
<a
|
name='ldap'
|
||||||
href='#'
|
title={
|
||||||
className={this.isSelected('ldap_settings')}
|
|
||||||
onClick={this.handleClick.bind(this, 'ldap_settings', null)}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.sidebar.ldap'
|
id='admin.sidebar.ldap'
|
||||||
defaultMessage='LDAP Settings'
|
defaultMessage='LDAP'
|
||||||
/>
|
/>
|
||||||
</a>
|
}
|
||||||
</li>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (global.window.mm_license.Compliance === 'true') {
|
if (global.window.mm_license.Compliance === 'true') {
|
||||||
complianceSettings = (
|
complianceSettings = (
|
||||||
<li>
|
<AdminSidebarSection
|
||||||
<a
|
name='compliance'
|
||||||
href='#'
|
title={
|
||||||
className={this.isSelected('compliance_settings')}
|
|
||||||
onClick={this.handleClick.bind(this, 'compliance_settings', null)}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.sidebar.compliance'
|
id='admin.sidebar.compliance'
|
||||||
defaultMessage='Compliance Settings'
|
defaultMessage='Compliance'
|
||||||
/>
|
/>
|
||||||
</a>
|
}
|
||||||
</li>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
licenseSettings = (
|
license = (
|
||||||
<li>
|
<AdminSidebarSection
|
||||||
<a
|
name='license'
|
||||||
href='#'
|
title={
|
||||||
className={this.isSelected('license')}
|
|
||||||
onClick={this.handleClick.bind(this, 'license', null)}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.sidebar.license'
|
id='admin.sidebar.license'
|
||||||
defaultMessage='Edition and License'
|
defaultMessage='Edition and License'
|
||||||
/>
|
/>
|
||||||
</a>
|
}
|
||||||
</li>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let audits;
|
if (window.mm_license.IsLicensed === 'true') {
|
||||||
if (global.window.mm_license.IsLicensed === 'true') {
|
|
||||||
audits = (
|
audits = (
|
||||||
<li>
|
<AdminSidebarSection
|
||||||
<a
|
name='audits'
|
||||||
href='#'
|
title={
|
||||||
className={this.isSelected('audits')}
|
|
||||||
onClick={this.handleClick.bind(this, 'audits', null)}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.sidebar.audits'
|
id='admin.sidebar.audits'
|
||||||
defaultMessage='Compliance and Auditing'
|
defaultMessage='Complaince and Auditing'
|
||||||
/>
|
/>
|
||||||
</a>
|
}
|
||||||
</li>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='sidebar--left sidebar--collapsable'>
|
<div className='admin-sidebar'>
|
||||||
<AdminSidebarHeader/>
|
<AdminSidebarHeader/>
|
||||||
<div className='nav-pills__container'>
|
<div className='nav-pills__container'>
|
||||||
<ul className='nav nav-pills nav-stacked'>
|
<ul className='nav nav-pills nav-stacked'>
|
||||||
<li>
|
<AdminSidebarCategory
|
||||||
<ul className='nav nav__sub-menu'>
|
parentLink='/admin_console'
|
||||||
<li>
|
icon='fa-gear'
|
||||||
<h4>
|
title={
|
||||||
<span className='icon fa fa-gear'></span>
|
<FormattedMessage
|
||||||
<span>
|
id='admin.sidebar.reports'
|
||||||
<FormattedMessage
|
defaultMessage='SITE REPORTS'
|
||||||
id='admin.sidebar.reports'
|
/>
|
||||||
defaultMessage='SITE REPORTS'
|
}
|
||||||
/>
|
>
|
||||||
</span>
|
<AdminSidebarSection
|
||||||
</h4>
|
name='system_analytics'
|
||||||
</li>
|
title={
|
||||||
</ul>
|
<FormattedMessage
|
||||||
<ul className='nav nav__sub-menu padded'>
|
id='admin.sidebar.view_statistics'
|
||||||
<li>
|
defaultMessage='View Statistics'
|
||||||
<a
|
/>
|
||||||
href='#'
|
}
|
||||||
className={this.isSelected('system_analytics')}
|
/>
|
||||||
onClick={this.handleClick.bind(this, 'system_analytics', null)}
|
</AdminSidebarCategory>
|
||||||
>
|
<AdminSidebarCategory
|
||||||
|
parentLink='/admin_console'
|
||||||
|
icon='fa-gear'
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.sidebar.settings'
|
||||||
|
defaultMessage='SETTINGS'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<AdminSidebarSection
|
||||||
|
name='general'
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.sidebar.general'
|
||||||
|
defaultMessage='General'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<AdminSidebarSection
|
||||||
|
name='configuration'
|
||||||
|
title={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.sidebar.view_statistics'
|
id='admin.sidebar.configuration'
|
||||||
defaultMessage='View Statistics'
|
defaultMessage='Configuration'
|
||||||
/>
|
/>
|
||||||
</a>
|
}
|
||||||
</li>
|
/>
|
||||||
</ul>
|
<AdminSidebarSection
|
||||||
<ul className='nav nav__sub-menu'>
|
name='users_and_teams'
|
||||||
<li>
|
title={
|
||||||
<h4>
|
|
||||||
<span className='icon fa fa-gear'></span>
|
|
||||||
<span>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.sidebar.settings'
|
|
||||||
defaultMessage='SETTINGS'
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</h4>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<ul className='nav nav__sub-menu padded'>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href='#'
|
|
||||||
className={this.isSelected('service_settings')}
|
|
||||||
onClick={this.handleClick.bind(this, 'service_settings', null)}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.sidebar.service'
|
id='admin.sidebar.usersAndTeams'
|
||||||
defaultMessage='Service Settings'
|
defaultMessage='Users and Teams'
|
||||||
/>
|
/>
|
||||||
</a>
|
}
|
||||||
</li>
|
/>
|
||||||
<li>
|
<AdminSidebarSection
|
||||||
<a
|
name='privacy'
|
||||||
href='#'
|
title={
|
||||||
className={this.isSelected('team_settings')}
|
|
||||||
onClick={this.handleClick.bind(this, 'team_settings', null)}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.sidebar.team'
|
|
||||||
defaultMessage='Team Settings'
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href='#'
|
|
||||||
className={this.isSelected('sql_settings')}
|
|
||||||
onClick={this.handleClick.bind(this, 'sql_settings', null)}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.sidebar.sql'
|
|
||||||
defaultMessage='SQL Settings'
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href='#'
|
|
||||||
className={this.isSelected('email_settings')}
|
|
||||||
onClick={this.handleClick.bind(this, 'email_settings', null)}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.sidebar.email'
|
|
||||||
defaultMessage='Email Settings'
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href='#'
|
|
||||||
className={this.isSelected('image_settings')}
|
|
||||||
onClick={this.handleClick.bind(this, 'image_settings', null)}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.sidebar.file'
|
|
||||||
defaultMessage='File Settings'
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href='#'
|
|
||||||
className={this.isSelected('log_settings')}
|
|
||||||
onClick={this.handleClick.bind(this, 'log_settings', null)}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.sidebar.log'
|
|
||||||
defaultMessage='Log Settings'
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href='#'
|
|
||||||
className={this.isSelected('rate_settings')}
|
|
||||||
onClick={this.handleClick.bind(this, 'rate_settings', null)}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.sidebar.rate_limit'
|
|
||||||
defaultMessage='Rate Limit Settings'
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href='#'
|
|
||||||
className={this.isSelected('privacy_settings')}
|
|
||||||
onClick={this.handleClick.bind(this, 'privacy_settings', null)}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.sidebar.privacy'
|
id='admin.sidebar.privacy'
|
||||||
defaultMessage='Privacy Settings'
|
defaultMessage='Privacy'
|
||||||
/>
|
/>
|
||||||
</a>
|
}
|
||||||
</li>
|
/>
|
||||||
<li>
|
<AdminSidebarSection
|
||||||
<a
|
name='logging'
|
||||||
href='#'
|
title={
|
||||||
className={this.isSelected('gitlab_settings')}
|
<FormattedMessage
|
||||||
onClick={this.handleClick.bind(this, 'gitlab_settings', null)}
|
id='admin.sidebar.logging'
|
||||||
>
|
defaultMessage='Logging'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</AdminSidebarSection>
|
||||||
|
<AdminSidebarSection
|
||||||
|
name='authentication'
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.sidebar.authentication'
|
||||||
|
defaultMessage='Authentication'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<AdminSidebarSection
|
||||||
|
name='email'
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.sidebar.email'
|
||||||
|
defaultMessage='Email'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<AdminSidebarSection
|
||||||
|
name='gitlab'
|
||||||
|
title={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.sidebar.gitlab'
|
id='admin.sidebar.gitlab'
|
||||||
defaultMessage='GitLab Settings'
|
defaultMessage='GitLab'
|
||||||
/>
|
/>
|
||||||
</a>
|
}
|
||||||
</li>
|
/>
|
||||||
{ldapSettings}
|
{ldapSettings}
|
||||||
{complianceSettings}
|
</AdminSidebarSection>
|
||||||
<li>
|
<AdminSidebarSection
|
||||||
<a
|
name='security'
|
||||||
href='#'
|
title={
|
||||||
className={this.isSelected('legal_and_support_settings')}
|
<FormattedMessage
|
||||||
onClick={this.handleClick.bind(this, 'legal_and_support_settings', null)}
|
id='admin.sidebar.security'
|
||||||
>
|
defaultMessage='Security'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<AdminSidebarSection
|
||||||
|
name='sign_up'
|
||||||
|
title={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.sidebar.support'
|
id='admin.sidebar.signUp'
|
||||||
defaultMessage='Legal and Support Settings'
|
defaultMessage='Sign Up'
|
||||||
/>
|
/>
|
||||||
</a>
|
}
|
||||||
</li>
|
/>
|
||||||
</ul>
|
<AdminSidebarSection
|
||||||
<ul className='nav nav__sub-menu'>
|
name='login'
|
||||||
<li>
|
title={
|
||||||
<h4>
|
|
||||||
<span className='icon fa fa-gear'></span>
|
|
||||||
<span>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.sidebar.teams'
|
|
||||||
defaultMessage='TEAMS ({count})'
|
|
||||||
values={{
|
|
||||||
count: count
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<span className='menu-icon--right'>
|
|
||||||
<OverlayTrigger
|
|
||||||
delayShow={1000}
|
|
||||||
placement='top'
|
|
||||||
overlay={addTeamTooltip}
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href='#'
|
|
||||||
onClick={this.showTeamSelect}
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
className='fa fa-plus'
|
|
||||||
></i>
|
|
||||||
</a>
|
|
||||||
</OverlayTrigger>
|
|
||||||
</span>
|
|
||||||
</h4>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<ul className='nav nav__sub-menu padded'>
|
|
||||||
<li>
|
|
||||||
{teams}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<ul className='nav nav__sub-menu'>
|
|
||||||
<li>
|
|
||||||
<h4>
|
|
||||||
<span className='icon fa fa-gear'></span>
|
|
||||||
<span>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.sidebar.other'
|
|
||||||
defaultMessage='OTHER'
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</h4>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<ul className='nav nav__sub-menu padded'>
|
|
||||||
{licenseSettings}
|
|
||||||
{audits}
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href='#'
|
|
||||||
className={this.isSelected('logs')}
|
|
||||||
onClick={this.handleClick.bind(this, 'logs', null)}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.sidebar.logs'
|
id='admin.sidebar.login'
|
||||||
defaultMessage='Logs'
|
defaultMessage='Login'
|
||||||
/>
|
/>
|
||||||
</a>
|
}
|
||||||
</li>
|
/>
|
||||||
</ul>
|
<AdminSidebarSection
|
||||||
</li>
|
name='public_links'
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.sidebar.publicLinks'
|
||||||
|
defaultMessage='Public Links'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<AdminSidebarSection
|
||||||
|
name='sessions'
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.sidebar.sessions'
|
||||||
|
defaultMessage='Sessions'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<AdminSidebarSection
|
||||||
|
name='connections'
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.sidebar.connections'
|
||||||
|
defaultMessage='Connections'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</AdminSidebarSection>
|
||||||
|
<AdminSidebarSection
|
||||||
|
name='notifications'
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.sidebar.notifications'
|
||||||
|
defaultMessage='Notifications'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<AdminSidebarSection
|
||||||
|
name='email'
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.sidebar.email'
|
||||||
|
defaultMessage='Email'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<AdminSidebarSection
|
||||||
|
name='push'
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.sidebar.push'
|
||||||
|
defaultMessage='Mobile Push'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</AdminSidebarSection>
|
||||||
|
<AdminSidebarSection
|
||||||
|
name='integrations'
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.sidebar.integrations'
|
||||||
|
defaultMessage='Integrations'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<AdminSidebarSection
|
||||||
|
name='webhooks'
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.sidebar.webhooks'
|
||||||
|
defaultMessage='Webhooks and Commands'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<AdminSidebarSection
|
||||||
|
name='external'
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.sidebar.external'
|
||||||
|
defaultMessage='External Services'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</AdminSidebarSection>
|
||||||
|
<AdminSidebarSection
|
||||||
|
name='database'
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.sidebar.database'
|
||||||
|
defaultMessage='Database'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<AdminSidebarSection
|
||||||
|
name='files'
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.sidebar.files'
|
||||||
|
defaultMessage='Files'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<AdminSidebarSection
|
||||||
|
name='storage'
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.sidebar.storage'
|
||||||
|
defaultMessage='Storage'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<AdminSidebarSection
|
||||||
|
name='images'
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.sidebar.images'
|
||||||
|
defaultMessage='Images'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</AdminSidebarSection>
|
||||||
|
<AdminSidebarSection
|
||||||
|
name='customization'
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.sidebar.customization'
|
||||||
|
defaultMessage='Customization'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<AdminSidebarSection
|
||||||
|
name='custom_brand'
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.sidebar.customBrand'
|
||||||
|
defaultMessage='Custom Branding'
|
||||||
|
/>
|
||||||
|
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<AdminSidebarSection
|
||||||
|
name='legal_and_support'
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.sidebar.legalAndSupport'
|
||||||
|
defaultMessage='Legal and Support'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</AdminSidebarSection>
|
||||||
|
{complianceSettings}
|
||||||
|
<AdminSidebarSection
|
||||||
|
name='rate'
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.sidebar.rate'
|
||||||
|
defaultMessage='Rate Limiting'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<AdminSidebarSection
|
||||||
|
name='developer'
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.sidebar.developer'
|
||||||
|
defaultMessage='Developer'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</AdminSidebarCategory>
|
||||||
|
{this.renderTeams()}
|
||||||
|
<AdminSidebarCategory
|
||||||
|
parentLink='/admin_console'
|
||||||
|
icon='fa-gear'
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.sidebar.other'
|
||||||
|
defaultMessage='OTHER'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{license}
|
||||||
|
{audits}
|
||||||
|
<AdminSidebarSection
|
||||||
|
name='logs'
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.sidebar.logs'
|
||||||
|
defaultMessage='Logs'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</AdminSidebarCategory>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SelectTeamModal
|
<SelectTeamModal
|
||||||
teams={this.props.teams}
|
teams={this.state.teams}
|
||||||
show={this.state.showSelectModal}
|
show={this.state.showSelectModal}
|
||||||
onModalSubmit={this.teamSelectedModal}
|
onModalSubmit={this.teamSelectedModal}
|
||||||
onModalDismissed={this.teamSelectedModalDismissed}
|
onModalDismissed={this.teamSelectedModalDismissed}
|
||||||
@@ -503,13 +581,3 @@ export default class AdminSidebar extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AdminSidebar.propTypes = {
|
|
||||||
teams: React.PropTypes.object,
|
|
||||||
selectedTeams: React.PropTypes.object,
|
|
||||||
removeSelectedTeam: React.PropTypes.func,
|
|
||||||
addSelectedTeam: React.PropTypes.func,
|
|
||||||
selected: React.PropTypes.string,
|
|
||||||
selectedTeam: React.PropTypes.string,
|
|
||||||
selectTab: React.PropTypes.func
|
|
||||||
};
|
|
||||||
|
|||||||
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';
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
export default class BooleanSetting extends React.Component {
|
export default class BooleanSetting extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.handleChange = this.handleChange.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange(e) {
|
||||||
|
this.props.onChange(this.props.id, e.target.value === 'true');
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
let helpText;
|
||||||
|
if (this.props.disabled && this.props.disabledText) {
|
||||||
|
helpText = (
|
||||||
|
<div>
|
||||||
|
<span className='admin-console__disabled-text'>
|
||||||
|
{this.props.disabledText}
|
||||||
|
</span>
|
||||||
|
{this.props.helpText}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
helpText = this.props.helpText;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Setting label={this.props.label}>
|
<Setting
|
||||||
|
label={this.props.label}
|
||||||
|
helpText={helpText}
|
||||||
|
>
|
||||||
<label className='radio-inline'>
|
<label className='radio-inline'>
|
||||||
<input
|
<input
|
||||||
type='radio'
|
type='radio'
|
||||||
value='true'
|
value='true'
|
||||||
checked={this.props.currentValue}
|
checked={this.props.value}
|
||||||
onChange={this.props.handleChange}
|
onChange={this.handleChange}
|
||||||
disabled={this.props.isDisabled}
|
disabled={this.props.disabled}
|
||||||
/>
|
/>
|
||||||
{this.props.trueText}
|
{this.props.trueText}
|
||||||
</label>
|
</label>
|
||||||
@@ -25,13 +52,12 @@ export default class BooleanSetting extends React.Component {
|
|||||||
<input
|
<input
|
||||||
type='radio'
|
type='radio'
|
||||||
value='false'
|
value='false'
|
||||||
checked={!this.props.currentValue}
|
checked={!this.props.value}
|
||||||
onChange={this.props.handleChange}
|
onChange={this.handleChange}
|
||||||
disabled={this.props.isDisabled}
|
disabled={this.props.disabled}
|
||||||
/>
|
/>
|
||||||
{this.props.falseText}
|
{this.props.falseText}
|
||||||
</label>
|
</label>
|
||||||
{this.props.helpText}
|
|
||||||
</Setting>
|
</Setting>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -48,15 +74,18 @@ BooleanSetting.defaultProps = {
|
|||||||
id='admin.ldap.false'
|
id='admin.ldap.false'
|
||||||
defaultMessage='false'
|
defaultMessage='false'
|
||||||
/>
|
/>
|
||||||
)
|
),
|
||||||
|
disabled: false
|
||||||
};
|
};
|
||||||
|
|
||||||
BooleanSetting.propTypes = {
|
BooleanSetting.propTypes = {
|
||||||
|
id: React.PropTypes.string.isRequired,
|
||||||
label: React.PropTypes.node.isRequired,
|
label: React.PropTypes.node.isRequired,
|
||||||
currentValue: React.PropTypes.bool.isRequired,
|
value: React.PropTypes.bool.isRequired,
|
||||||
|
onChange: React.PropTypes.func.isRequired,
|
||||||
trueText: React.PropTypes.node,
|
trueText: React.PropTypes.node,
|
||||||
falseText: React.PropTypes.node,
|
falseText: React.PropTypes.node,
|
||||||
isDisabled: React.PropTypes.bool.isRequired,
|
disabled: React.PropTypes.bool.isRequired,
|
||||||
handleChange: React.PropTypes.func.isRequired,
|
disabledText: React.PropTypes.node,
|
||||||
helpText: React.PropTypes.node.isRequired
|
helpText: React.PropTypes.node.isRequired
|
||||||
};
|
};
|
||||||
|
|||||||
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.
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
import $ from 'jquery';
|
|
||||||
import Client from 'utils/web_client.jsx';
|
|
||||||
import * as AsyncClient from '../../utils/async_client.jsx';
|
|
||||||
import * as Utils from '../../utils/utils.jsx';
|
|
||||||
|
|
||||||
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
|
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
|
|
||||||
export default class ComplianceSettings extends React.Component {
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
|
||||||
|
import AdminSettings from './admin_settings.jsx';
|
||||||
|
import BooleanSetting from './boolean_setting.jsx';
|
||||||
|
import {FormattedHTMLMessage, FormattedMessage} from 'react-intl';
|
||||||
|
import SettingsGroup from './settings_group.jsx';
|
||||||
|
import TextSetting from './text_setting.jsx';
|
||||||
|
|
||||||
|
export default class ComplianceSettings extends AdminSettings {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.handleSubmit = this.handleSubmit.bind(this);
|
this.getConfigFromState = this.getConfigFromState.bind(this);
|
||||||
this.handleChange = this.handleChange.bind(this);
|
|
||||||
this.handleEnable = this.handleEnable.bind(this);
|
|
||||||
this.handleDisable = this.handleDisable.bind(this);
|
|
||||||
|
|
||||||
this.state = {
|
this.renderSettings = this.renderSettings.bind(this);
|
||||||
saveNeeded: false,
|
|
||||||
serverError: null,
|
|
||||||
enable: this.props.config.ComplianceSettings.Enable
|
|
||||||
};
|
|
||||||
}
|
|
||||||
handleChange() {
|
|
||||||
this.setState({saveNeeded: true});
|
|
||||||
}
|
|
||||||
handleEnable() {
|
|
||||||
this.setState({saveNeeded: true, enable: true});
|
|
||||||
}
|
|
||||||
handleDisable() {
|
|
||||||
this.setState({saveNeeded: true, enable: false});
|
|
||||||
}
|
|
||||||
handleSubmit(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
$('#save-button').button('loading');
|
|
||||||
|
|
||||||
const config = this.props.config;
|
this.state = Object.assign(this.state, {
|
||||||
const oldEnable = config.ComplianceSettings.Enable;
|
enable: props.config.ComplianceSettings.Enable,
|
||||||
config.ComplianceSettings.Enable = this.refs.Enable.checked;
|
directory: props.config.ComplianceSettings.Directory,
|
||||||
config.ComplianceSettings.Directory = ReactDOM.findDOMNode(this.refs.Directory).value;
|
enableDaily: props.config.ComplianceSettings.EnableDaily
|
||||||
config.ComplianceSettings.EnableDaily = this.refs.EnableDaily.checked;
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Client.saveConfig(
|
getConfigFromState(config) {
|
||||||
config,
|
config.ComplianceSettings.Enable = this.state.enable;
|
||||||
() => {
|
config.ComplianceSettings.Directory = this.state.directory;
|
||||||
$('#save-button').button('reset');
|
config.ComplianceSettings.EnableDaily = this.state.enableDaily;
|
||||||
AsyncClient.getConfig();
|
|
||||||
this.setState({
|
return config;
|
||||||
serverError: null,
|
}
|
||||||
saveNeeded: false
|
|
||||||
});
|
renderTitle() {
|
||||||
if (oldEnable !== config.ComplianceSettings.Enable) {
|
return (
|
||||||
window.location.reload();
|
<h3>
|
||||||
}
|
<FormattedMessage
|
||||||
},
|
id='admin.compliance.title'
|
||||||
(err) => {
|
defaultMessage='Compliance Settings'
|
||||||
this.setState({
|
/>
|
||||||
serverError: err.message,
|
</h3>
|
||||||
saveNeeded: true
|
|
||||||
});
|
|
||||||
$('#save-button').button('reset');
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
render() {
|
|
||||||
let serverError = '';
|
|
||||||
if (this.state.serverError) {
|
|
||||||
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
let saveClass = 'btn';
|
|
||||||
if (this.state.saveNeeded) {
|
|
||||||
saveClass = 'btn btn-primary';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
renderSettings() {
|
||||||
const licenseEnabled = global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.Compliance === 'true';
|
const licenseEnabled = global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.Compliance === 'true';
|
||||||
|
|
||||||
let bannerContent;
|
let bannerContent;
|
||||||
@@ -95,170 +63,64 @@ export default class ComplianceSettings extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='wrapper--fixed'>
|
<SettingsGroup>
|
||||||
{bannerContent}
|
{bannerContent}
|
||||||
<h3>
|
<BooleanSetting
|
||||||
<FormattedMessage
|
id='enable'
|
||||||
id='admin.compliance.title'
|
label={
|
||||||
defaultMessage='Compliance Settings'
|
<FormattedMessage
|
||||||
/>
|
id='admin.compliance.enableTitle'
|
||||||
</h3>
|
defaultMessage='Enable Compliance:'
|
||||||
<form
|
/>
|
||||||
className='form-horizontal'
|
}
|
||||||
role='form'
|
helpText={
|
||||||
>
|
<FormattedMessage
|
||||||
|
id='admin.compliance.enableDesc'
|
||||||
<div className='form-group'>
|
defaultMessage='When true, Mattermost allows compliance reporting'
|
||||||
<label
|
/>
|
||||||
className='control-label col-sm-4'
|
}
|
||||||
htmlFor='Enable'
|
value={this.state.enable}
|
||||||
>
|
onChange={this.handleChange}
|
||||||
<FormattedMessage
|
disabled={!licenseEnabled}
|
||||||
id='admin.compliance.enableTitle'
|
/>
|
||||||
defaultMessage='Enable Compliance:'
|
<TextSetting
|
||||||
/>
|
id='directory'
|
||||||
</label>
|
label={
|
||||||
<div className='col-sm-8'>
|
<FormattedMessage
|
||||||
<label className='radio-inline'>
|
id='admin.compliance.directoryTitle'
|
||||||
<input
|
defaultMessage='Compliance Directory Location:'
|
||||||
type='radio'
|
/>
|
||||||
name='Enable'
|
}
|
||||||
value='true'
|
placeholder={Utils.localizeMessage('admin.sql.maxOpenExample', 'Ex "10"')}
|
||||||
ref='Enable'
|
helpText={
|
||||||
defaultChecked={this.props.config.ComplianceSettings.Enable}
|
<FormattedMessage
|
||||||
onChange={this.handleEnable}
|
id='admin.compliance.directoryDescription'
|
||||||
disabled={!licenseEnabled}
|
defaultMessage='Directory to which compliance reports are written. If blank, will be set to ./data/.'
|
||||||
/>
|
/>
|
||||||
<FormattedMessage
|
}
|
||||||
id='admin.compliance.true'
|
value={this.state.directory}
|
||||||
defaultMessage='true'
|
onChange={this.handleChange}
|
||||||
/>
|
disabled={!licenseEnabled || !this.state.enable}
|
||||||
</label>
|
/>
|
||||||
<label className='radio-inline'>
|
<BooleanSetting
|
||||||
<input
|
id='enableDaily'
|
||||||
type='radio'
|
label={
|
||||||
name='Enable'
|
<FormattedMessage
|
||||||
value='false'
|
id='admin.compliance.enableDailyTitle'
|
||||||
defaultChecked={!this.props.config.ComplianceSettings.Enable}
|
defaultMessage='Enable Daily Report:'
|
||||||
onChange={this.handleDisable}
|
/>
|
||||||
/>
|
}
|
||||||
<FormattedMessage
|
helpText={
|
||||||
id='admin.compliance.false'
|
<FormattedMessage
|
||||||
defaultMessage='false'
|
id='admin.compliance.enableDailyDesc'
|
||||||
/>
|
defaultMessage='When true, Mattermost will generate a daily compliance report.'
|
||||||
</label>
|
/>
|
||||||
<p className='help-text'>
|
}
|
||||||
<FormattedMessage
|
value={this.state.enableDaily}
|
||||||
id='admin.compliance.enableDesc'
|
onChange={this.handleChange}
|
||||||
defaultMessage='When true, Mattermost allows compliance reporting'
|
disabled={!licenseEnabled || !this.state.enable}
|
||||||
/>
|
/>
|
||||||
</p>
|
</SettingsGroup>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='form-group'>
|
|
||||||
<label
|
|
||||||
className='control-label col-sm-4'
|
|
||||||
htmlFor='Directory'
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.compliance.directoryTitle'
|
|
||||||
defaultMessage='Compliance Directory Location:'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div className='col-sm-8'>
|
|
||||||
<input
|
|
||||||
type='text'
|
|
||||||
className='form-control'
|
|
||||||
id='Directory'
|
|
||||||
ref='Directory'
|
|
||||||
placeholder={Utils.localizeMessage('admin.compliance.directoryExample', 'Ex "./data/"')}
|
|
||||||
defaultValue={this.props.config.ComplianceSettings.Directory}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
disabled={!this.state.enable}
|
|
||||||
/>
|
|
||||||
<p className='help-text'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.compliance.directoryDescription'
|
|
||||||
defaultMessage='Directory to which compliance reports are written. If blank, will be set to ./data/.'
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='form-group'>
|
|
||||||
<label
|
|
||||||
className='control-label col-sm-4'
|
|
||||||
htmlFor='EnableDaily'
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.compliance.enableDailyTitle'
|
|
||||||
defaultMessage='Enable Daily Report:'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div className='col-sm-8'>
|
|
||||||
<label className='radio-inline'>
|
|
||||||
<input
|
|
||||||
type='radio'
|
|
||||||
name='EnableDaily'
|
|
||||||
value='true'
|
|
||||||
ref='EnableDaily'
|
|
||||||
defaultChecked={this.props.config.ComplianceSettings.EnableDaily}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
disabled={!this.state.enable}
|
|
||||||
/>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.compliance.true'
|
|
||||||
defaultMessage='true'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<label className='radio-inline'>
|
|
||||||
<input
|
|
||||||
type='radio'
|
|
||||||
name='EnableDaily'
|
|
||||||
value='false'
|
|
||||||
defaultChecked={!this.props.config.ComplianceSettings.EnableDaily}
|
|
||||||
disabled={!this.state.enable}
|
|
||||||
/>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.compliance.false'
|
|
||||||
defaultMessage='false'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<p className='help-text'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.compliance.enableDailyDesc'
|
|
||||||
defaultMessage='When true, Mattermost will generate a daily compliance report.'
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='form-group'>
|
|
||||||
<div className='col-sm-12'>
|
|
||||||
{serverError}
|
|
||||||
<button
|
|
||||||
disabled={!this.state.saveNeeded}
|
|
||||||
type='submit'
|
|
||||||
className={saveClass}
|
|
||||||
onClick={this.handleSubmit}
|
|
||||||
id='save-button'
|
|
||||||
data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + Utils.localizeMessage('admin.compliance.saving', 'Saving Config...')}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.compliance.save'
|
|
||||||
defaultMessage='Save'
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ComplianceSettings.propTypes = {
|
|
||||||
config: React.PropTypes.object
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|||||||
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';
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
const CONNECTION_SECURITY_HELP_TEXT = (
|
const CONNECTION_SECURITY_HELP_TEXT = (
|
||||||
<div className='help-text'>
|
<table
|
||||||
<table
|
className='table table-bordered table-margin--none'
|
||||||
className='table table-bordered table-margin--none'
|
cellPadding='5'
|
||||||
cellPadding='5'
|
>
|
||||||
>
|
<tbody>
|
||||||
<tbody>
|
<tr>
|
||||||
<tr>
|
<td className='help-text'>
|
||||||
<td className='help-text'>
|
<FormattedMessage
|
||||||
<FormattedMessage
|
id='admin.connectionSecurityNone'
|
||||||
id='admin.connectionSecurityNone'
|
defaultMessage='None'
|
||||||
defaultMessage='None'
|
/>
|
||||||
/>
|
</td>
|
||||||
</td>
|
<td className='help-text'>
|
||||||
<td className='help-text'>
|
<FormattedMessage
|
||||||
<FormattedMessage
|
id='admin.connectionSecurityNoneDescription'
|
||||||
id='admin.connectionSecurityNoneDescription'
|
defaultMessage='Mattermost will connect over an unsecure connection.'
|
||||||
defaultMessage='Mattermost will connect over an unsecure connection.'
|
/>
|
||||||
/>
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
</tr>
|
<tr>
|
||||||
<tr>
|
<td className='help-text'>
|
||||||
<td className='help-text'>
|
<FormattedMessage
|
||||||
<FormattedMessage
|
id='admin.connectionSecurityTls'
|
||||||
id='admin.connectionSecurityTls'
|
defaultMessage='TLS'
|
||||||
defaultMessage='TLS'
|
/>
|
||||||
/>
|
</td>
|
||||||
</td>
|
<td className='help-text'>
|
||||||
<td className='help-text'>
|
<FormattedMessage
|
||||||
<FormattedMessage
|
id='admin.connectionSecurityTlsDescription'
|
||||||
id='admin.connectionSecurityTlsDescription'
|
defaultMessage='Encrypts the communication between Mattermost and your server.'
|
||||||
defaultMessage='Encrypts the communication between Mattermost and your server.'
|
/>
|
||||||
/>
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
</tr>
|
<tr>
|
||||||
<tr>
|
<td className='help-text'>
|
||||||
<td className='help-text'>
|
<FormattedMessage
|
||||||
<FormattedMessage
|
id='admin.connectionSecurityStart'
|
||||||
id='admin.connectionSecurityStart'
|
defaultMessage='STARTTLS'
|
||||||
defaultMessage='STARTTLS'
|
/>
|
||||||
/>
|
</td>
|
||||||
</td>
|
<td className='help-text'>
|
||||||
<td className='help-text'>
|
<FormattedMessage
|
||||||
<FormattedMessage
|
id='admin.connectionSecurityStartDescription'
|
||||||
id='admin.connectionSecurityStartDescription'
|
defaultMessage='Takes an existing insecure connection and attempts to upgrade it to a secure connection using TLS.'
|
||||||
defaultMessage='Takes an existing insecure connection and attempts to upgrade it to a secure connection using TLS.'
|
/>
|
||||||
/>
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
</tr>
|
</tbody>
|
||||||
</tbody>
|
</table>
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export default class ConnectionSecurityDropdownSetting extends React.Component {
|
export default class ConnectionSecurityDropdownSetting extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<DropdownSetting
|
<DropdownSetting
|
||||||
|
id='connectionSecurity'
|
||||||
values={[
|
values={[
|
||||||
{value: '', text: Utils.localizeMessage('admin.connectionSecurityNone', 'None')},
|
{value: '', text: Utils.localizeMessage('admin.connectionSecurityNone', 'None')},
|
||||||
{value: 'TLS', text: Utils.localizeMessage('admin.connectionSecurityTls', 'TLS (Recommended)')},
|
{value: 'TLS', text: Utils.localizeMessage('admin.connectionSecurityTls', 'TLS (Recommended)')},
|
||||||
@@ -76,11 +75,10 @@ export default class ConnectionSecurityDropdownSetting extends React.Component {
|
|||||||
defaultMessage='Connection Security:'
|
defaultMessage='Connection Security:'
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
currentValue={this.props.currentValue}
|
value={this.props.value}
|
||||||
handleChange={this.props.handleChange}
|
onChange={this.props.onChange}
|
||||||
isDisabled={this.props.isDisabled}
|
disabled={this.props.disabled}
|
||||||
helpText={CONNECTION_SECURITY_HELP_TEXT}
|
helpText={CONNECTION_SECURITY_HELP_TEXT}
|
||||||
margin='small'
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -89,7 +87,7 @@ ConnectionSecurityDropdownSetting.defaultProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
ConnectionSecurityDropdownSetting.propTypes = {
|
ConnectionSecurityDropdownSetting.propTypes = {
|
||||||
currentValue: React.PropTypes.string.isRequired,
|
value: React.PropTypes.string.isRequired,
|
||||||
handleChange: React.PropTypes.func.isRequired,
|
onChange: React.PropTypes.func.isRequired,
|
||||||
isDisabled: React.PropTypes.bool.isRequired
|
disabled: React.PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|||||||
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';
|
import Setting from './setting.jsx';
|
||||||
|
|
||||||
export default class DropdownSetting extends React.Component {
|
export default class DropdownSetting extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.handleChange = this.handleChange.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange(e) {
|
||||||
|
this.props.onChange(this.props.id, e.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const options = [];
|
const options = [];
|
||||||
for (const {value, text} of this.props.values) {
|
for (const {value, text} of this.props.values) {
|
||||||
@@ -22,30 +32,33 @@ export default class DropdownSetting extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<Setting
|
<Setting
|
||||||
label={this.props.label}
|
label={this.props.label}
|
||||||
margin={this.props.margin}
|
inputId={this.props.id}
|
||||||
|
helpText={this.props.helpText}
|
||||||
>
|
>
|
||||||
<select
|
<select
|
||||||
className='form-control'
|
className='form-control'
|
||||||
value={this.props.currentValue}
|
id={this.props.id}
|
||||||
onChange={this.props.handleChange}
|
value={this.props.value}
|
||||||
disabled={this.props.isDisabled}
|
onChange={this.handleChange}
|
||||||
|
disabled={this.props.disabled}
|
||||||
>
|
>
|
||||||
{options}
|
{options}
|
||||||
</select>
|
</select>
|
||||||
{this.props.helpText}
|
|
||||||
</Setting>
|
</Setting>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DropdownSetting.defaultProps = {
|
DropdownSetting.defaultProps = {
|
||||||
|
isDisabled: false
|
||||||
};
|
};
|
||||||
|
|
||||||
DropdownSetting.propTypes = {
|
DropdownSetting.propTypes = {
|
||||||
|
id: React.PropTypes.string.isRequired,
|
||||||
values: React.PropTypes.array.isRequired,
|
values: React.PropTypes.array.isRequired,
|
||||||
label: React.PropTypes.node.isRequired,
|
label: React.PropTypes.node.isRequired,
|
||||||
currentValue: React.PropTypes.string.isRequired,
|
value: React.PropTypes.string.isRequired,
|
||||||
handleChange: React.PropTypes.func.isRequired,
|
onChange: React.PropTypes.func.isRequired,
|
||||||
isDisabled: React.PropTypes.bool.isRequired,
|
disabled: React.PropTypes.bool,
|
||||||
helpText: React.PropTypes.node.isRequired,
|
helpText: React.PropTypes.node
|
||||||
margin: React.PropTypes.oneOf(['', 'small'])
|
|
||||||
};
|
};
|
||||||
|
|||||||
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.
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
import $ from 'jquery';
|
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import Client from 'utils/web_client.jsx';
|
|
||||||
import * as AsyncClient from 'utils/async_client.jsx';
|
|
||||||
|
|
||||||
import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl';
|
|
||||||
|
|
||||||
const holders = defineMessages({
|
|
||||||
clientIdExample: {
|
|
||||||
id: 'admin.gitlab.clientIdExample',
|
|
||||||
defaultMessage: 'Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"'
|
|
||||||
},
|
|
||||||
clientSecretExample: {
|
|
||||||
id: 'admin.gitlab.clientSecretExample',
|
|
||||||
defaultMessage: 'Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"'
|
|
||||||
},
|
|
||||||
authExample: {
|
|
||||||
id: 'admin.gitlab.authExample',
|
|
||||||
defaultMessage: 'Ex ""'
|
|
||||||
},
|
|
||||||
tokenExample: {
|
|
||||||
id: 'admin.gitlab.tokenExample',
|
|
||||||
defaultMessage: 'Ex ""'
|
|
||||||
},
|
|
||||||
userExample: {
|
|
||||||
id: 'admin.gitlab.userExample',
|
|
||||||
defaultMessage: 'Ex ""'
|
|
||||||
},
|
|
||||||
saving: {
|
|
||||||
id: 'admin.gitlab.saving',
|
|
||||||
defaultMessage: 'Saving Config...'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
class GitLabSettings extends React.Component {
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
|
||||||
|
import AdminSettings from './admin_settings.jsx';
|
||||||
|
import BooleanSetting from './boolean_setting.jsx';
|
||||||
|
import {FormattedHTMLMessage, FormattedMessage} from 'react-intl';
|
||||||
|
import SettingsGroup from './settings_group.jsx';
|
||||||
|
import TextSetting from './text_setting.jsx';
|
||||||
|
|
||||||
|
export default class GitLabSettings extends AdminSettings {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.handleChange = this.handleChange.bind(this);
|
this.getConfigFromState = this.getConfigFromState.bind(this);
|
||||||
this.handleSubmit = this.handleSubmit.bind(this);
|
|
||||||
|
|
||||||
this.state = {
|
this.renderSettings = this.renderSettings.bind(this);
|
||||||
Enable: this.props.config.GitLabSettings.Enable,
|
|
||||||
saveNeeded: false,
|
this.state = Object.assign(this.state, {
|
||||||
serverError: null
|
enable: props.config.GitLabSettings.Enable,
|
||||||
};
|
id: props.config.GitLabSettings.Id,
|
||||||
|
secret: props.config.GitLabSettings.Secret,
|
||||||
|
userApiEndpoint: props.config.GitLabSettings.UserApiEndpoint,
|
||||||
|
authEndpoint: props.config.GitLabSettings.AuthEndpoint,
|
||||||
|
tokenEndpoint: props.config.GitLabSettings.TokenEndpoint
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChange(action) {
|
getConfigFromState(config) {
|
||||||
var s = {saveNeeded: true, serverError: this.state.serverError};
|
config.GitLabSettings.Enable = this.state.enable;
|
||||||
|
config.GitLabSettings.Id = this.state.id;
|
||||||
|
config.GitLabSettings.Secret = this.state.secret;
|
||||||
|
config.GitLabSettings.UserApiEndpoint = this.state.userApiEndpoint;
|
||||||
|
config.GitLabSettings.AuthEndpoint = this.state.authEndpoint;
|
||||||
|
config.GitLabSettings.TokenEndpoint = this.state.tokenEndpoint;
|
||||||
|
|
||||||
if (action === 'EnableTrue') {
|
return config;
|
||||||
s.Enable = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action === 'EnableFalse') {
|
|
||||||
s.Enable = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState(s);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSubmit(e) {
|
renderTitle() {
|
||||||
e.preventDefault();
|
return (
|
||||||
$('#save-button').button('loading');
|
<h3>
|
||||||
|
<FormattedMessage
|
||||||
var config = this.props.config;
|
id='admin.authentication.title'
|
||||||
config.GitLabSettings.Enable = ReactDOM.findDOMNode(this.refs.Enable).checked;
|
defaultMessage='Authentication Settings'
|
||||||
config.GitLabSettings.Secret = ReactDOM.findDOMNode(this.refs.Secret).value.trim();
|
/>
|
||||||
config.GitLabSettings.Id = ReactDOM.findDOMNode(this.refs.Id).value.trim();
|
</h3>
|
||||||
config.GitLabSettings.AuthEndpoint = ReactDOM.findDOMNode(this.refs.AuthEndpoint).value.trim();
|
|
||||||
config.GitLabSettings.TokenEndpoint = ReactDOM.findDOMNode(this.refs.TokenEndpoint).value.trim();
|
|
||||||
config.GitLabSettings.UserApiEndpoint = ReactDOM.findDOMNode(this.refs.UserApiEndpoint).value.trim();
|
|
||||||
|
|
||||||
Client.saveConfig(
|
|
||||||
config,
|
|
||||||
() => {
|
|
||||||
AsyncClient.getConfig();
|
|
||||||
this.setState({
|
|
||||||
serverError: null,
|
|
||||||
saveNeeded: false
|
|
||||||
});
|
|
||||||
$('#save-button').button('reset');
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
this.setState({
|
|
||||||
serverError: err.message,
|
|
||||||
saveNeeded: true
|
|
||||||
});
|
|
||||||
$('#save-button').button('reset');
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
renderSettings() {
|
||||||
const {formatMessage} = this.props.intl;
|
|
||||||
var serverError = '';
|
|
||||||
if (this.state.serverError) {
|
|
||||||
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
var saveClass = 'btn';
|
|
||||||
if (this.state.saveNeeded) {
|
|
||||||
saveClass = 'btn btn-primary';
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='wrapper--fixed'>
|
<SettingsGroup
|
||||||
|
header={
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.gitlab.settingsTitle'
|
id='admin.authentication.gitlab'
|
||||||
defaultMessage='GitLab Settings'
|
defaultMessage='GitLab'
|
||||||
/>
|
/>
|
||||||
</h3>
|
}
|
||||||
<form
|
>
|
||||||
className='form-horizontal'
|
<BooleanSetting
|
||||||
role='form'
|
id='enable'
|
||||||
>
|
label={
|
||||||
<div className='form-group'>
|
<FormattedMessage
|
||||||
<label
|
id='admin.gitlab.enableTitle'
|
||||||
className='control-label col-sm-4'
|
defaultMessage='Enable Sign Up With GitLab: '
|
||||||
htmlFor='Enable'
|
/>
|
||||||
>
|
}
|
||||||
|
helpText={
|
||||||
|
<div>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.gitlab.enableTitle'
|
id='admin.gitlab.enableDescription'
|
||||||
defaultMessage='Enable Sign Up With GitLab: '
|
defaultMessage='When true, Mattermost allows team creation and account signup using GitLab OAuth.'
|
||||||
|
/>
|
||||||
|
<br/>
|
||||||
|
<FormattedHTMLMessage
|
||||||
|
id='admin.gitlab.EnableHtmlDesc'
|
||||||
|
defaultMessage='<ol><li>Log in to your GitLab account and go to Profile Settings -> Applications.</li><li>Enter Redirect URIs "<your-mattermost-url>/login/gitlab/complete" (example: http://localhost:8065/login/gitlab/complete) and "<your-mattermost-url>/signup/gitlab/complete". </li><li>Then use "Secret" and "Id" fields from GitLab to complete the options below.</li><li>Complete the Endpoint URLs below. </li></ol>'
|
||||||
/>
|
/>
|
||||||
</label>
|
|
||||||
<div className='col-sm-8'>
|
|
||||||
<label className='radio-inline'>
|
|
||||||
<input
|
|
||||||
type='radio'
|
|
||||||
name='Enable'
|
|
||||||
value='true'
|
|
||||||
ref='Enable'
|
|
||||||
defaultChecked={this.props.config.GitLabSettings.Enable}
|
|
||||||
onChange={this.handleChange.bind(this, 'EnableTrue')}
|
|
||||||
/>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.gitlab.true'
|
|
||||||
defaultMessage='true'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<label className='radio-inline'>
|
|
||||||
<input
|
|
||||||
type='radio'
|
|
||||||
name='Enable'
|
|
||||||
value='false'
|
|
||||||
defaultChecked={!this.props.config.GitLabSettings.Enable}
|
|
||||||
onChange={this.handleChange.bind(this, 'EnableFalse')}
|
|
||||||
/>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.gitlab.false'
|
|
||||||
defaultMessage='false'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<p className='help-text'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.gitlab.enableDescription'
|
|
||||||
defaultMessage='When true, Mattermost allows team creation and account signup using GitLab OAuth.'
|
|
||||||
/>
|
|
||||||
<br/>
|
|
||||||
</p>
|
|
||||||
<div className='help-text'>
|
|
||||||
<FormattedHTMLMessage
|
|
||||||
id='admin.gitlab.EnableHtmlDesc'
|
|
||||||
defaultMessage='<ol><li>Log in to your GitLab account and go to Profile Settings -> Applications.</li><li>Enter Redirect URIs "<your-mattermost-url>/login/gitlab/complete" (example: http://localhost:8065/login/gitlab/complete) and "<your-mattermost-url>/signup/gitlab/complete". </li><li>Then use "Secret" and "Id" fields from GitLab to complete the options below.</li><li>Complete the Endpoint URLs below. </li></ol>'
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
}
|
||||||
|
value={this.state.enable}
|
||||||
<div className='form-group'>
|
onChange={this.handleChange}
|
||||||
<label
|
/>
|
||||||
className='control-label col-sm-4'
|
<TextSetting
|
||||||
htmlFor='Id'
|
id='id'
|
||||||
>
|
label={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.gitlab.clientIdTitle'
|
id='admin.gitlab.clientIdTitle'
|
||||||
defaultMessage='Id:'
|
defaultMessage='Id:'
|
||||||
/>
|
/>
|
||||||
</label>
|
}
|
||||||
<div className='col-sm-8'>
|
placeholder={Utils.localizeMessage('admin.gitlab.clientIdExample', 'Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"')}
|
||||||
<input
|
helpText={
|
||||||
type='text'
|
<FormattedMessage
|
||||||
className='form-control'
|
id='admin.gitlab.clientIdDescription'
|
||||||
id='Id'
|
defaultMessage='Obtain this value via the instructions above for logging into GitLab'
|
||||||
ref='Id'
|
/>
|
||||||
placeholder={formatMessage(holders.clientIdExample)}
|
}
|
||||||
defaultValue={this.props.config.GitLabSettings.Id}
|
value={this.state.id}
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
disabled={!this.state.Enable}
|
disabled={!this.state.enable}
|
||||||
/>
|
/>
|
||||||
<p className='help-text'>
|
<TextSetting
|
||||||
<FormattedMessage
|
id='secret'
|
||||||
id='admin.gitlab.clientIdDescription'
|
label={
|
||||||
defaultMessage='Obtain this value via the instructions above for logging into GitLab'
|
<FormattedMessage
|
||||||
/>
|
id='admin.gitlab.clientSecretTitle'
|
||||||
</p>
|
defaultMessage='Secret:'
|
||||||
</div>
|
/>
|
||||||
</div>
|
}
|
||||||
|
placeholder={Utils.localizeMessage('admin.gitlab.clientSecretExample', 'Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"')}
|
||||||
<div className='form-group'>
|
helpText={
|
||||||
<label
|
<FormattedMessage
|
||||||
className='control-label col-sm-4'
|
id='admin.gitab.clientSecretDescription'
|
||||||
htmlFor='Secret'
|
defaultMessage='Obtain this value via the instructions above for logging into GitLab.'
|
||||||
>
|
/>
|
||||||
<FormattedMessage
|
}
|
||||||
id='admin.gitlab.clientSecretTitle'
|
value={this.state.secret}
|
||||||
defaultMessage='Secret:'
|
onChange={this.handleChange}
|
||||||
/>
|
disabled={!this.state.enable}
|
||||||
</label>
|
/>
|
||||||
<div className='col-sm-8'>
|
<TextSetting
|
||||||
<input
|
id='userApiEndpoint'
|
||||||
type='text'
|
label={
|
||||||
className='form-control'
|
<FormattedMessage
|
||||||
id='Secret'
|
id='admin.gitlab.userTitle'
|
||||||
ref='Secret'
|
defaultMessage='User API Endpoint:'
|
||||||
placeholder={formatMessage(holders.clientSecretExample)}
|
/>
|
||||||
defaultValue={this.props.config.GitLabSettings.Secret}
|
}
|
||||||
onChange={this.handleChange}
|
placeholder={Utils.localizeMessage('admin.gitlab.userExample', 'Ex ""')}
|
||||||
disabled={!this.state.Enable}
|
helpText={
|
||||||
/>
|
<FormattedMessage
|
||||||
<p className='help-text'>
|
id='admin.gitlab.userDescription'
|
||||||
<FormattedMessage
|
defaultMessage='Enter https://<your-gitlab-url>/api/v3/user. Make sure you use HTTP or HTTPS in your URL depending on your server configuration.'
|
||||||
id='admin.gitab.clientSecretDescription'
|
/>
|
||||||
defaultMessage='Obtain this value via the instructions above for logging into GitLab.'
|
}
|
||||||
/>
|
value={this.state.userApiEndpoint}
|
||||||
</p>
|
onChange={this.handleChange}
|
||||||
</div>
|
disabled={!this.state.enable}
|
||||||
</div>
|
/>
|
||||||
|
<TextSetting
|
||||||
<div className='form-group'>
|
id='authEndpoint'
|
||||||
<label
|
label={
|
||||||
className='control-label col-sm-4'
|
<FormattedMessage
|
||||||
htmlFor='AuthEndpoint'
|
id='admin.gitlab.authTitle'
|
||||||
>
|
defaultMessage='Auth Endpoint:'
|
||||||
<FormattedMessage
|
/>
|
||||||
id='admin.gitlab.authTitle'
|
}
|
||||||
defaultMessage='Auth Endpoint:'
|
placeholder={Utils.localizeMessage('admin.gitlab.authExample', 'Ex ""')}
|
||||||
/>
|
helpText={
|
||||||
</label>
|
<FormattedMessage
|
||||||
<div className='col-sm-8'>
|
id='admin.gitlab.authDescription'
|
||||||
<input
|
defaultMessage='Enter https://<your-gitlab-url>/oauth/authorize (example https://example.com:3000/oauth/authorize). Make sure you use HTTP or HTTPS in your URL depending on your server configuration.'
|
||||||
type='text'
|
/>
|
||||||
className='form-control'
|
}
|
||||||
id='AuthEndpoint'
|
value={this.state.authEndpoint}
|
||||||
ref='AuthEndpoint'
|
onChange={this.handleChange}
|
||||||
placeholder={formatMessage(holders.authExample)}
|
disabled={!this.state.enable}
|
||||||
defaultValue={this.props.config.GitLabSettings.AuthEndpoint}
|
/>
|
||||||
onChange={this.handleChange}
|
<TextSetting
|
||||||
disabled={!this.state.Enable}
|
id='tokenEndpoint'
|
||||||
/>
|
label={
|
||||||
<p className='help-text'>
|
<FormattedMessage
|
||||||
<FormattedMessage
|
id='admin.gitlab.tokenTitle'
|
||||||
id='admin.gitlab.authDescription'
|
defaultMessage='Token Endpoint:'
|
||||||
defaultMessage='Enter https://<your-gitlab-url>/oauth/authorize (example https://example.com:3000/oauth/authorize). Make sure you use HTTP or HTTPS in your URL depending on your server configuration.'
|
/>
|
||||||
/>
|
}
|
||||||
</p>
|
placeholder={Utils.localizeMessage('admin.gitlab.tokenExample', 'Ex ""')}
|
||||||
</div>
|
helpText={
|
||||||
</div>
|
<FormattedMessage
|
||||||
|
id='admin.gitlab.tokenDescription'
|
||||||
<div className='form-group'>
|
defaultMessage='Enter https://<your-gitlab-url>/oauth/token. Make sure you use HTTP or HTTPS in your URL depending on your server configuration.'
|
||||||
<label
|
/>
|
||||||
className='control-label col-sm-4'
|
}
|
||||||
htmlFor='TokenEndpoint'
|
value={this.state.tokenEndpoint}
|
||||||
>
|
onChange={this.handleChange}
|
||||||
<FormattedMessage
|
disabled={!this.state.enable}
|
||||||
id='admin.gitlab.tokenTitle'
|
/>
|
||||||
defaultMessage='Token Endpoint:'
|
</SettingsGroup>
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div className='col-sm-8'>
|
|
||||||
<input
|
|
||||||
type='text'
|
|
||||||
className='form-control'
|
|
||||||
id='TokenEndpoint'
|
|
||||||
ref='TokenEndpoint'
|
|
||||||
placeholder={formatMessage(holders.tokenExample)}
|
|
||||||
defaultValue={this.props.config.GitLabSettings.TokenEndpoint}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
disabled={!this.state.Enable}
|
|
||||||
/>
|
|
||||||
<p className='help-text'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.gitlab.tokenDescription'
|
|
||||||
defaultMessage='Enter https://<your-gitlab-url>/oauth/token. Make sure you use HTTP or HTTPS in your URL depending on your server configuration.'
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='form-group'>
|
|
||||||
<label
|
|
||||||
className='control-label col-sm-4'
|
|
||||||
htmlFor='UserApiEndpoint'
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.gitlab.userTitle'
|
|
||||||
defaultMessage='User API Endpoint:'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div className='col-sm-8'>
|
|
||||||
<input
|
|
||||||
type='text'
|
|
||||||
className='form-control'
|
|
||||||
id='UserApiEndpoint'
|
|
||||||
ref='UserApiEndpoint'
|
|
||||||
placeholder={formatMessage(holders.userExample)}
|
|
||||||
defaultValue={this.props.config.GitLabSettings.UserApiEndpoint}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
disabled={!this.state.Enable}
|
|
||||||
/>
|
|
||||||
<p className='help-text'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.gitlab.userDescription'
|
|
||||||
defaultMessage='Enter https://<your-gitlab-url>/api/v3/user. Make sure you use HTTP or HTTPS in your URL depending on your server configuration.'
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='form-group'>
|
|
||||||
<div className='col-sm-12'>
|
|
||||||
{serverError}
|
|
||||||
<button
|
|
||||||
disabled={!this.state.saveNeeded}
|
|
||||||
type='submit'
|
|
||||||
className={saveClass}
|
|
||||||
onClick={this.handleSubmit}
|
|
||||||
id='save-button'
|
|
||||||
data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + formatMessage(holders.saving)}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.gitlab.save'
|
|
||||||
defaultMessage='Save'
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//config.GitLabSettings.Scope = ReactDOM.findDOMNode(this.refs.Scope).value.trim();
|
|
||||||
// <div className='form-group'>
|
|
||||||
// <label
|
|
||||||
// className='control-label col-sm-4'
|
|
||||||
// htmlFor='Scope'
|
|
||||||
// >
|
|
||||||
// {'Scope:'}
|
|
||||||
// </label>
|
|
||||||
// <div className='col-sm-8'>
|
|
||||||
// <input
|
|
||||||
// type='text'
|
|
||||||
// className='form-control'
|
|
||||||
// id='Scope'
|
|
||||||
// ref='Scope'
|
|
||||||
// placeholder='Not currently used by GitLab. Please leave blank'
|
|
||||||
// defaultValue={this.props.config.GitLabSettings.Scope}
|
|
||||||
// onChange={this.handleChange}
|
|
||||||
// disabled={!this.state.Allow}
|
|
||||||
// />
|
|
||||||
// <p className='help-text'>{'This field is not yet used by GitLab OAuth. Other OAuth providers may use this field to specify the scope of account data from OAuth provider that is sent to Mattermost.'}</p>
|
|
||||||
// </div>
|
|
||||||
// </div>
|
|
||||||
|
|
||||||
GitLabSettings.propTypes = {
|
|
||||||
intl: intlShape.isRequired,
|
|
||||||
config: React.PropTypes.object
|
|
||||||
};
|
|
||||||
|
|
||||||
export default injectIntl(GitLabSettings);
|
|
||||||
|
|||||||
@@ -1,692 +1,174 @@
|
|||||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
import $ from 'jquery';
|
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import Client from 'utils/web_client.jsx';
|
|
||||||
import * as AsyncClient from 'utils/async_client.jsx';
|
|
||||||
import crypto from 'crypto';
|
|
||||||
|
|
||||||
import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl';
|
|
||||||
|
|
||||||
const holders = defineMessages({
|
|
||||||
storeLocal: {
|
|
||||||
id: 'admin.image.storeLocal',
|
|
||||||
defaultMessage: 'Local File System'
|
|
||||||
},
|
|
||||||
storeAmazonS3: {
|
|
||||||
id: 'admin.image.storeAmazonS3',
|
|
||||||
defaultMessage: 'Amazon S3'
|
|
||||||
},
|
|
||||||
localExample: {
|
|
||||||
id: 'admin.image.localExample',
|
|
||||||
defaultMessage: 'Ex "./data/"'
|
|
||||||
},
|
|
||||||
amazonS3IdExample: {
|
|
||||||
id: 'admin.image.amazonS3IdExample',
|
|
||||||
defaultMessage: 'Ex "AKIADTOVBGERKLCBV"'
|
|
||||||
},
|
|
||||||
amazonS3SecretExample: {
|
|
||||||
id: 'admin.image.amazonS3SecretExample',
|
|
||||||
defaultMessage: 'Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"'
|
|
||||||
},
|
|
||||||
amazonS3BucketExample: {
|
|
||||||
id: 'admin.image.amazonS3BucketExample',
|
|
||||||
defaultMessage: 'Ex "mattermost-media"'
|
|
||||||
},
|
|
||||||
amazonS3RegionExample: {
|
|
||||||
id: 'admin.image.amazonS3RegionExample',
|
|
||||||
defaultMessage: 'Ex "us-east-1"'
|
|
||||||
},
|
|
||||||
thumbWidthExample: {
|
|
||||||
id: 'admin.image.thumbWidthExample',
|
|
||||||
defaultMessage: 'Ex "120"'
|
|
||||||
},
|
|
||||||
thumbHeightExample: {
|
|
||||||
id: 'admin.image.thumbHeightExample',
|
|
||||||
defaultMessage: 'Ex "100"'
|
|
||||||
},
|
|
||||||
previewWidthExample: {
|
|
||||||
id: 'admin.image.previewWidthExample',
|
|
||||||
defaultMessage: 'Ex "1024"'
|
|
||||||
},
|
|
||||||
previewHeightExample: {
|
|
||||||
id: 'admin.image.previewHeightExample',
|
|
||||||
defaultMessage: 'Ex "0"'
|
|
||||||
},
|
|
||||||
profileWidthExample: {
|
|
||||||
id: 'admin.image.profileWidthExample',
|
|
||||||
defaultMessage: 'Ex "1024"'
|
|
||||||
},
|
|
||||||
profileHeightExample: {
|
|
||||||
id: 'admin.image.profileHeightExample',
|
|
||||||
defaultMessage: 'Ex "0"'
|
|
||||||
},
|
|
||||||
publicLinkExample: {
|
|
||||||
id: 'admin.image.publicLinkExample',
|
|
||||||
defaultMessage: 'Ex "gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6"'
|
|
||||||
},
|
|
||||||
saving: {
|
|
||||||
id: 'admin.image.saving',
|
|
||||||
defaultMessage: 'Saving Config...'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
class FileSettings extends React.Component {
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
|
||||||
|
import AdminSettings from './admin_settings.jsx';
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
import SettingsGroup from './settings_group.jsx';
|
||||||
|
import TextSetting from './text_setting.jsx';
|
||||||
|
|
||||||
|
export default class ImageSettings extends AdminSettings {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.handleChange = this.handleChange.bind(this);
|
this.getConfigFromState = this.getConfigFromState.bind(this);
|
||||||
this.handleSubmit = this.handleSubmit.bind(this);
|
|
||||||
this.handleGenerate = this.handleGenerate.bind(this);
|
|
||||||
|
|
||||||
this.state = {
|
this.renderSettings = this.renderSettings.bind(this);
|
||||||
saveNeeded: false,
|
|
||||||
serverError: null,
|
this.state = Object.assign(this.state, {
|
||||||
DriverName: this.props.config.FileSettings.DriverName
|
thumbnailWidth: props.config.FileSettings.ThumbnailWidth,
|
||||||
};
|
thumbnailHeight: props.config.FileSettings.ThumbnailHeight,
|
||||||
|
profileWidth: props.config.FileSettings.ProfileWidth,
|
||||||
|
profileHeight: props.config.FileSettings.ProfileHeight,
|
||||||
|
previewWidth: props.config.FileSettings.PreviewWidth,
|
||||||
|
previewHeight: props.config.FileSettings.PreviewHeight
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChange(action) {
|
getConfigFromState(config) {
|
||||||
var s = {saveNeeded: true, serverError: this.state.serverError};
|
config.FileSettings.ThumbnailWidth = this.parseInt(this.state.thumbnailWidth);
|
||||||
|
config.FileSettings.ThumbnailHeight = this.parseInt(this.state.thumbnailHeight);
|
||||||
|
config.FileSettings.ProfileWidth = this.parseInt(this.state.profileWidth);
|
||||||
|
config.FileSettings.ProfileHeight = this.parseInt(this.state.profileHeight);
|
||||||
|
config.FileSettings.PreviewWidth = this.parseInt(this.state.previewWidth);
|
||||||
|
config.FileSettings.PreviewHeight = this.parseInt(this.state.previewHeight);
|
||||||
|
|
||||||
if (action === 'DriverName') {
|
return config;
|
||||||
s.DriverName = ReactDOM.findDOMNode(this.refs.DriverName).value;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState(s);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleGenerate(e) {
|
renderTitle() {
|
||||||
e.preventDefault();
|
return (
|
||||||
ReactDOM.findDOMNode(this.refs.PublicLinkSalt).value = crypto.randomBytes(256).toString('base64').substring(0, 32);
|
<h3>
|
||||||
var s = {saveNeeded: true, serverError: this.state.serverError};
|
<FormattedMessage
|
||||||
this.setState(s);
|
id='admin.files.title'
|
||||||
}
|
defaultMessage='File Settings'
|
||||||
|
/>
|
||||||
handleSubmit(e) {
|
</h3>
|
||||||
e.preventDefault();
|
|
||||||
$('#save-button').button('loading');
|
|
||||||
|
|
||||||
var config = this.props.config;
|
|
||||||
config.FileSettings.DriverName = ReactDOM.findDOMNode(this.refs.DriverName).value;
|
|
||||||
config.FileSettings.Directory = ReactDOM.findDOMNode(this.refs.Directory).value;
|
|
||||||
config.FileSettings.AmazonS3AccessKeyId = ReactDOM.findDOMNode(this.refs.AmazonS3AccessKeyId).value;
|
|
||||||
config.FileSettings.AmazonS3SecretAccessKey = ReactDOM.findDOMNode(this.refs.AmazonS3SecretAccessKey).value;
|
|
||||||
config.FileSettings.AmazonS3Bucket = ReactDOM.findDOMNode(this.refs.AmazonS3Bucket).value;
|
|
||||||
config.FileSettings.AmazonS3Region = ReactDOM.findDOMNode(this.refs.AmazonS3Region).value;
|
|
||||||
config.FileSettings.EnablePublicLink = ReactDOM.findDOMNode(this.refs.EnablePublicLink).checked;
|
|
||||||
|
|
||||||
config.FileSettings.PublicLinkSalt = ReactDOM.findDOMNode(this.refs.PublicLinkSalt).value.trim();
|
|
||||||
|
|
||||||
if (config.FileSettings.PublicLinkSalt === '') {
|
|
||||||
config.FileSettings.PublicLinkSalt = crypto.randomBytes(256).toString('base64').substring(0, 32);
|
|
||||||
ReactDOM.findDOMNode(this.refs.PublicLinkSalt).value = config.FileSettings.PublicLinkSalt;
|
|
||||||
}
|
|
||||||
|
|
||||||
var thumbnailWidth = 120;
|
|
||||||
if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.ThumbnailWidth).value, 10))) {
|
|
||||||
thumbnailWidth = parseInt(ReactDOM.findDOMNode(this.refs.ThumbnailWidth).value, 10);
|
|
||||||
}
|
|
||||||
config.FileSettings.ThumbnailWidth = thumbnailWidth;
|
|
||||||
ReactDOM.findDOMNode(this.refs.ThumbnailWidth).value = thumbnailWidth;
|
|
||||||
|
|
||||||
var thumbnailHeight = 100;
|
|
||||||
if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.ThumbnailHeight).value, 10))) {
|
|
||||||
thumbnailHeight = parseInt(ReactDOM.findDOMNode(this.refs.ThumbnailHeight).value, 10);
|
|
||||||
}
|
|
||||||
config.FileSettings.ThumbnailHeight = thumbnailHeight;
|
|
||||||
ReactDOM.findDOMNode(this.refs.ThumbnailHeight).value = thumbnailHeight;
|
|
||||||
|
|
||||||
var previewWidth = 1024;
|
|
||||||
if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.PreviewWidth).value, 10))) {
|
|
||||||
previewWidth = parseInt(ReactDOM.findDOMNode(this.refs.PreviewWidth).value, 10);
|
|
||||||
}
|
|
||||||
config.FileSettings.PreviewWidth = previewWidth;
|
|
||||||
ReactDOM.findDOMNode(this.refs.PreviewWidth).value = previewWidth;
|
|
||||||
|
|
||||||
var previewHeight = 0;
|
|
||||||
if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.PreviewHeight).value, 10))) {
|
|
||||||
previewHeight = parseInt(ReactDOM.findDOMNode(this.refs.PreviewHeight).value, 10);
|
|
||||||
}
|
|
||||||
config.FileSettings.PreviewHeight = previewHeight;
|
|
||||||
ReactDOM.findDOMNode(this.refs.PreviewHeight).value = previewHeight;
|
|
||||||
|
|
||||||
var profileWidth = 128;
|
|
||||||
if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.ProfileWidth).value, 10))) {
|
|
||||||
profileWidth = parseInt(ReactDOM.findDOMNode(this.refs.ProfileWidth).value, 10);
|
|
||||||
}
|
|
||||||
config.FileSettings.ProfileWidth = profileWidth;
|
|
||||||
ReactDOM.findDOMNode(this.refs.ProfileWidth).value = profileWidth;
|
|
||||||
|
|
||||||
var profileHeight = 128;
|
|
||||||
if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.ProfileHeight).value, 10))) {
|
|
||||||
profileHeight = parseInt(ReactDOM.findDOMNode(this.refs.ProfileHeight).value, 10);
|
|
||||||
}
|
|
||||||
config.FileSettings.ProfileHeight = profileHeight;
|
|
||||||
ReactDOM.findDOMNode(this.refs.ProfileHeight).value = profileHeight;
|
|
||||||
|
|
||||||
Client.saveConfig(
|
|
||||||
config,
|
|
||||||
() => {
|
|
||||||
AsyncClient.getConfig();
|
|
||||||
this.setState({
|
|
||||||
serverError: null,
|
|
||||||
saveNeeded: false
|
|
||||||
});
|
|
||||||
$('#save-button').button('reset');
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
this.setState({
|
|
||||||
serverError: err.message,
|
|
||||||
saveNeeded: true
|
|
||||||
});
|
|
||||||
$('#save-button').button('reset');
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
renderSettings() {
|
||||||
const {formatMessage} = this.props.intl;
|
|
||||||
var serverError = '';
|
|
||||||
if (this.state.serverError) {
|
|
||||||
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
var saveClass = 'btn';
|
|
||||||
if (this.state.saveNeeded) {
|
|
||||||
saveClass = 'btn btn-primary';
|
|
||||||
}
|
|
||||||
|
|
||||||
var enableFile = false;
|
|
||||||
var enableS3 = false;
|
|
||||||
|
|
||||||
if (this.state.DriverName === 'local') {
|
|
||||||
enableFile = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.state.DriverName === 'amazons3') {
|
|
||||||
enableS3 = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='wrapper--fixed'>
|
<SettingsGroup
|
||||||
<h3>
|
header={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.image.fileSettings'
|
id='admin.files.images'
|
||||||
defaultMessage='File Settings'
|
defaultMessage='Images'
|
||||||
/>
|
/>
|
||||||
</h3>
|
}
|
||||||
<form
|
>
|
||||||
className='form-horizontal'
|
<TextSetting
|
||||||
role='form'
|
id='thumbnailWidth'
|
||||||
>
|
label={
|
||||||
|
<FormattedMessage
|
||||||
<div className='form-group'>
|
id='admin.image.thumbWidthTitle'
|
||||||
<label
|
defaultMessage='Thumbnail Width:'
|
||||||
className='control-label col-sm-4'
|
/>
|
||||||
htmlFor='DriverName'
|
}
|
||||||
>
|
placeholder={Utils.localizeMessage('admin.image.thumbWidthExample', 'Ex "120"')}
|
||||||
<FormattedMessage
|
helpText={
|
||||||
id='admin.image.storeTitle'
|
<FormattedMessage
|
||||||
defaultMessage='Store Files In:'
|
id='admin.image.thumbWidthDescription'
|
||||||
/>
|
defaultMessage='Width of thumbnails generated from uploaded images. Updating this value changes how thumbnail images render in future, but does not change images created in the past.'
|
||||||
</label>
|
/>
|
||||||
<div className='col-sm-8'>
|
}
|
||||||
<select
|
value={this.state.thumbnailWidth}
|
||||||
className='form-control'
|
onChange={this.handleChange}
|
||||||
id='DriverName'
|
/>
|
||||||
ref='DriverName'
|
<TextSetting
|
||||||
defaultValue={this.props.config.FileSettings.DriverName}
|
id='thumbnailHeight'
|
||||||
onChange={this.handleChange.bind(this, 'DriverName')}
|
label={
|
||||||
>
|
<FormattedMessage
|
||||||
<option value='local'>{formatMessage(holders.storeLocal)}</option>
|
id='admin.image.thumbHeightTitle'
|
||||||
<option value='amazons3'>{formatMessage(holders.storeAmazonS3)}</option>
|
defaultMessage='Thumbnail Height:'
|
||||||
</select>
|
/>
|
||||||
</div>
|
}
|
||||||
</div>
|
placeholder={Utils.localizeMessage('admin.image.thumbHeightExample', 'Ex "100"')}
|
||||||
|
helpText={
|
||||||
<div className='form-group'>
|
<FormattedMessage
|
||||||
<label
|
id='admin.image.thumbHeightDescription'
|
||||||
className='control-label col-sm-4'
|
defaultMessage='Height of thumbnails generated from uploaded images. Updating this value changes how thumbnail images render in future, but does not change images created in the past.'
|
||||||
htmlFor='Directory'
|
/>
|
||||||
>
|
}
|
||||||
<FormattedMessage
|
value={this.state.thumbnailHeight}
|
||||||
id='admin.image.localTitle'
|
onChange={this.handleChange}
|
||||||
defaultMessage='Local Directory Location:'
|
/>
|
||||||
/>
|
<TextSetting
|
||||||
</label>
|
id='profileWidth'
|
||||||
<div className='col-sm-8'>
|
label={
|
||||||
<input
|
<FormattedMessage
|
||||||
type='text'
|
id='admin.image.profileWidthTitle'
|
||||||
className='form-control'
|
defaultMessage='Profile Width:'
|
||||||
id='Directory'
|
/>
|
||||||
ref='Directory'
|
}
|
||||||
placeholder={formatMessage(holders.localExample)}
|
placeholder={Utils.localizeMessage('admin.image.profileWidthExample', 'Ex "1024"')}
|
||||||
defaultValue={this.props.config.FileSettings.Directory}
|
helpText={
|
||||||
onChange={this.handleChange}
|
<FormattedMessage
|
||||||
disabled={!enableFile}
|
id='admin.image.profileWidthDescription'
|
||||||
/>
|
defaultMessage='Width of profile picture.'
|
||||||
<p className='help-text'>
|
/>
|
||||||
<FormattedMessage
|
}
|
||||||
id='admin.image.localDescription'
|
value={this.state.profileWidth}
|
||||||
defaultMessage='Directory to which image files are written. If blank, will be set to ./data/.'
|
onChange={this.handleChange}
|
||||||
/>
|
/>
|
||||||
</p>
|
<TextSetting
|
||||||
</div>
|
id='profileHeight'
|
||||||
</div>
|
label={
|
||||||
|
<FormattedMessage
|
||||||
<div className='form-group'>
|
id='admin.image.profileHeightTitle'
|
||||||
<label
|
defaultMessage='Profile Height:'
|
||||||
className='control-label col-sm-4'
|
/>
|
||||||
htmlFor='AmazonS3AccessKeyId'
|
}
|
||||||
>
|
placeholder={Utils.localizeMessage('admin.image.profileHeightExample', 'Ex "0"')}
|
||||||
<FormattedMessage
|
helpText={
|
||||||
id='admin.image.amazonS3IdTitle'
|
<FormattedMessage
|
||||||
defaultMessage='Amazon S3 Access Key Id:'
|
id='admin.image.profileHeightDescription'
|
||||||
/>
|
defaultMessage='Height of profile picture.'
|
||||||
</label>
|
/>
|
||||||
<div className='col-sm-8'>
|
}
|
||||||
<input
|
value={this.state.profileHeight}
|
||||||
type='text'
|
onChange={this.handleChange}
|
||||||
className='form-control'
|
/>
|
||||||
id='AmazonS3AccessKeyId'
|
<TextSetting
|
||||||
ref='AmazonS3AccessKeyId'
|
id='previewWidth'
|
||||||
placeholder={formatMessage(holders.amazonS3IdExample)}
|
label={
|
||||||
defaultValue={this.props.config.FileSettings.AmazonS3AccessKeyId}
|
<FormattedMessage
|
||||||
onChange={this.handleChange}
|
id='admin.image.previewWidthTitle'
|
||||||
disabled={!enableS3}
|
defaultMessage='Preview Width:'
|
||||||
/>
|
/>
|
||||||
<p className='help-text'>
|
}
|
||||||
<FormattedMessage
|
placeholder={Utils.localizeMessage('admin.image.previewWidthExample', 'Ex "1024"')}
|
||||||
id='admin.image.amazonS3IdDescription'
|
helpText={
|
||||||
defaultMessage='Obtain this credential from your Amazon EC2 administrator.'
|
<FormattedMessage
|
||||||
/>
|
id='admin.image.previewWidthDescription'
|
||||||
</p>
|
defaultMessage='Maximum width of preview image. Updating this value changes how preview images render in future, but does not change images created in the past.'
|
||||||
</div>
|
/>
|
||||||
</div>
|
}
|
||||||
|
value={this.state.previewWidth}
|
||||||
<div className='form-group'>
|
onChange={this.handleChange}
|
||||||
<label
|
/>
|
||||||
className='control-label col-sm-4'
|
<TextSetting
|
||||||
htmlFor='AmazonS3SecretAccessKey'
|
id='previewHeight'
|
||||||
>
|
label={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.image.amazonS3SecretTitle'
|
id='admin.image.previewHeightTitle'
|
||||||
defaultMessage='Amazon S3 Secret Access Key:'
|
defaultMessage='Preview Height:'
|
||||||
/>
|
/>
|
||||||
</label>
|
}
|
||||||
<div className='col-sm-8'>
|
placeholder={Utils.localizeMessage('admin.image.previewHeightExample', 'Ex "0"')}
|
||||||
<input
|
helpText={
|
||||||
type='text'
|
<FormattedMessage
|
||||||
className='form-control'
|
id='admin.image.previewHeightDescription'
|
||||||
id='AmazonS3SecretAccessKey'
|
defaultMessage='Maximum height of preview image ("0": Sets to auto-size). Updating this value changes how preview images render in future, but does not change images created in the past.'
|
||||||
ref='AmazonS3SecretAccessKey'
|
/>
|
||||||
placeholder={formatMessage(holders.amazonS3SecretExample)}
|
}
|
||||||
defaultValue={this.props.config.FileSettings.AmazonS3SecretAccessKey}
|
value={this.state.previewHeight}
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
disabled={!enableS3}
|
/>
|
||||||
/>
|
</SettingsGroup>
|
||||||
<p className='help-text'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.image.amazonS3SecretDescription'
|
|
||||||
defaultMessage='Obtain this credential from your Amazon EC2 administrator.'
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='form-group'>
|
|
||||||
<label
|
|
||||||
className='control-label col-sm-4'
|
|
||||||
htmlFor='AmazonS3Bucket'
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.image.amazonS3BucketTitle'
|
|
||||||
defaultMessage='Amazon S3 Bucket:'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div className='col-sm-8'>
|
|
||||||
<input
|
|
||||||
type='text'
|
|
||||||
className='form-control'
|
|
||||||
id='AmazonS3Bucket'
|
|
||||||
ref='AmazonS3Bucket'
|
|
||||||
placeholder={formatMessage(holders.amazonS3BucketExample)}
|
|
||||||
defaultValue={this.props.config.FileSettings.AmazonS3Bucket}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
disabled={!enableS3}
|
|
||||||
/>
|
|
||||||
<p className='help-text'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.image.amazonS3BucketDescription'
|
|
||||||
defaultMessage='Name you selected for your S3 bucket in AWS.'
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='form-group'>
|
|
||||||
<label
|
|
||||||
className='control-label col-sm-4'
|
|
||||||
htmlFor='AmazonS3Region'
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.image.amazonS3RegionTitle'
|
|
||||||
defaultMessage='Amazon S3 Region:'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div className='col-sm-8'>
|
|
||||||
<input
|
|
||||||
type='text'
|
|
||||||
className='form-control'
|
|
||||||
id='AmazonS3Region'
|
|
||||||
ref='AmazonS3Region'
|
|
||||||
placeholder={formatMessage(holders.amazonS3RegionExample)}
|
|
||||||
defaultValue={this.props.config.FileSettings.AmazonS3Region}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
disabled={!enableS3}
|
|
||||||
/>
|
|
||||||
<p className='help-text'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.image.amazonS3RegionDescription'
|
|
||||||
defaultMessage='AWS region you selected for creating your S3 bucket.'
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='form-group'>
|
|
||||||
<label
|
|
||||||
className='control-label col-sm-4'
|
|
||||||
htmlFor='ThumbnailWidth'
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.image.thumbWidthTitle'
|
|
||||||
defaultMessage='Thumbnail Width:'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div className='col-sm-8'>
|
|
||||||
<input
|
|
||||||
type='text'
|
|
||||||
className='form-control'
|
|
||||||
id='ThumbnailWidth'
|
|
||||||
ref='ThumbnailWidth'
|
|
||||||
placeholder={formatMessage(holders.thumbWidthExample)}
|
|
||||||
defaultValue={this.props.config.FileSettings.ThumbnailWidth}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
/>
|
|
||||||
<p className='help-text'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.image.thumbWidthDescription'
|
|
||||||
defaultMessage='Width of thumbnails generated from uploaded images. Updating this value changes how thumbnail images render in future, but does not change images created in the past.'
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='form-group'>
|
|
||||||
<label
|
|
||||||
className='control-label col-sm-4'
|
|
||||||
htmlFor='ThumbnailHeight'
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.image.thumbHeightTitle'
|
|
||||||
defaultMessage='Thumbnail Height:'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div className='col-sm-8'>
|
|
||||||
<input
|
|
||||||
type='text'
|
|
||||||
className='form-control'
|
|
||||||
id='ThumbnailHeight'
|
|
||||||
ref='ThumbnailHeight'
|
|
||||||
placeholder={formatMessage(holders.thumbHeightExample)}
|
|
||||||
defaultValue={this.props.config.FileSettings.ThumbnailHeight}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
/>
|
|
||||||
<p className='help-text'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.image.thumbHeightDescription'
|
|
||||||
defaultMessage='Height of thumbnails generated from uploaded images. Updating this value changes how thumbnail images render in future, but does not change images created in the past.'
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='form-group'>
|
|
||||||
<label
|
|
||||||
className='control-label col-sm-4'
|
|
||||||
htmlFor='PreviewWidth'
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.image.previewWidthTitle'
|
|
||||||
defaultMessage='Preview Width:'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div className='col-sm-8'>
|
|
||||||
<input
|
|
||||||
type='text'
|
|
||||||
className='form-control'
|
|
||||||
id='PreviewWidth'
|
|
||||||
ref='PreviewWidth'
|
|
||||||
placeholder={formatMessage(holders.previewWidthExample)}
|
|
||||||
defaultValue={this.props.config.FileSettings.PreviewWidth}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
/>
|
|
||||||
<p className='help-text'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.image.previewWidthDescription'
|
|
||||||
defaultMessage='Maximum width of preview image. Updating this value changes how preview images render in future, but does not change images created in the past.'
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='form-group'>
|
|
||||||
<label
|
|
||||||
className='control-label col-sm-4'
|
|
||||||
htmlFor='PreviewHeight'
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.image.previewHeightTitle'
|
|
||||||
defaultMessage='Preview Height:'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div className='col-sm-8'>
|
|
||||||
<input
|
|
||||||
type='text'
|
|
||||||
className='form-control'
|
|
||||||
id='PreviewHeight'
|
|
||||||
ref='PreviewHeight'
|
|
||||||
placeholder={formatMessage(holders.previewHeightExample)}
|
|
||||||
defaultValue={this.props.config.FileSettings.PreviewHeight}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
/>
|
|
||||||
<p className='help-text'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.image.previewHeightDescription'
|
|
||||||
defaultMessage='Maximum height of preview image ("0": Sets to auto-size). Updating this value changes how preview images render in future, but does not change images created in the past.'
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='form-group'>
|
|
||||||
<label
|
|
||||||
className='control-label col-sm-4'
|
|
||||||
htmlFor='ProfileWidth'
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.image.profileWidthTitle'
|
|
||||||
defaultMessage='Profile Width:'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div className='col-sm-8'>
|
|
||||||
<input
|
|
||||||
type='text'
|
|
||||||
className='form-control'
|
|
||||||
id='ProfileWidth'
|
|
||||||
ref='ProfileWidth'
|
|
||||||
placeholder={formatMessage(holders.profileWidthExample)}
|
|
||||||
defaultValue={this.props.config.FileSettings.ProfileWidth}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
/>
|
|
||||||
<p className='help-text'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.image.profileWidthDescription'
|
|
||||||
defaultMessage='Width of profile picture.'
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='form-group'>
|
|
||||||
<label
|
|
||||||
className='control-label col-sm-4'
|
|
||||||
htmlFor='ProfileHeight'
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.image.profileHeightTitle'
|
|
||||||
defaultMessage='Profile Height:'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div className='col-sm-8'>
|
|
||||||
<input
|
|
||||||
type='text'
|
|
||||||
className='form-control'
|
|
||||||
id='ProfileHeight'
|
|
||||||
ref='ProfileHeight'
|
|
||||||
placeholder={formatMessage(holders.profileHeightExample)}
|
|
||||||
defaultValue={this.props.config.FileSettings.ProfileHeight}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
/>
|
|
||||||
<p className='help-text'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.image.profileHeightDescription'
|
|
||||||
defaultMessage='Height of profile picture.'
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='form-group'>
|
|
||||||
<label
|
|
||||||
className='control-label col-sm-4'
|
|
||||||
htmlFor='EnablePublicLink'
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.image.shareTitle'
|
|
||||||
defaultMessage='Share Public File Link: '
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div className='col-sm-8'>
|
|
||||||
<label className='radio-inline'>
|
|
||||||
<input
|
|
||||||
type='radio'
|
|
||||||
name='EnablePublicLink'
|
|
||||||
value='true'
|
|
||||||
ref='EnablePublicLink'
|
|
||||||
defaultChecked={this.props.config.FileSettings.EnablePublicLink}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
/>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.image.true'
|
|
||||||
defaultMessage='true'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<label className='radio-inline'>
|
|
||||||
<input
|
|
||||||
type='radio'
|
|
||||||
name='EnablePublicLink'
|
|
||||||
value='false'
|
|
||||||
defaultChecked={!this.props.config.FileSettings.EnablePublicLink}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
/>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.image.false'
|
|
||||||
defaultMessage='false'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<p className='help-text'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.image.shareDescription'
|
|
||||||
defaultMessage='Allow users to share public links to files and images.'
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='form-group'>
|
|
||||||
<label
|
|
||||||
className='control-label col-sm-4'
|
|
||||||
htmlFor='PublicLinkSalt'
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.image.publicLinkTitle'
|
|
||||||
defaultMessage='Public Link Salt:'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div className='col-sm-8'>
|
|
||||||
<input
|
|
||||||
type='text'
|
|
||||||
className='form-control'
|
|
||||||
id='PublicLinkSalt'
|
|
||||||
ref='PublicLinkSalt'
|
|
||||||
placeholder={formatMessage(holders.publicLinkExample)}
|
|
||||||
defaultValue={this.props.config.FileSettings.PublicLinkSalt}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
/>
|
|
||||||
<p className='help-text'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.image.publicLinkDescription'
|
|
||||||
defaultMessage='32-character salt added to signing of public image links. Randomly generated on install. Click "Re-Generate" to create new salt.'
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
<div className='help-text'>
|
|
||||||
<button
|
|
||||||
className='btn btn-default'
|
|
||||||
onClick={this.handleGenerate}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.image.regenerate'
|
|
||||||
defaultMessage='Re-Generate'
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='form-group'>
|
|
||||||
<div className='col-sm-12'>
|
|
||||||
{serverError}
|
|
||||||
<button
|
|
||||||
disabled={!this.state.saveNeeded}
|
|
||||||
type='submit'
|
|
||||||
className={saveClass}
|
|
||||||
onClick={this.handleSubmit}
|
|
||||||
id='save-button'
|
|
||||||
data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + formatMessage(holders.saving)}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.image.save'
|
|
||||||
defaultMessage='Save'
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FileSettings.propTypes = {
|
|
||||||
intl: intlShape.isRequired,
|
|
||||||
config: React.PropTypes.object
|
|
||||||
};
|
|
||||||
|
|
||||||
export default injectIntl(FileSettings);
|
|
||||||
|
|||||||
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
@@ -1,309 +1,166 @@
|
|||||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
import $ from 'jquery';
|
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import Client from 'utils/web_client.jsx';
|
|
||||||
import * as AsyncClient from 'utils/async_client.jsx';
|
|
||||||
|
|
||||||
import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl';
|
|
||||||
|
|
||||||
var holders = defineMessages({
|
|
||||||
saving: {
|
|
||||||
id: 'admin.support.saving',
|
|
||||||
defaultMessage: 'Saving Config...'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
class LegalAndSupportSettings extends React.Component {
|
import AdminSettings from './admin_settings.jsx';
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
import SettingsGroup from './settings_group.jsx';
|
||||||
|
import TextSetting from './text_setting.jsx';
|
||||||
|
|
||||||
|
export default class LegalAndSupportSettings extends AdminSettings {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.handleChange = this.handleChange.bind(this);
|
this.getConfigFromState = this.getConfigFromState.bind(this);
|
||||||
this.handleSubmit = this.handleSubmit.bind(this);
|
|
||||||
|
|
||||||
this.state = {
|
this.renderSettings = this.renderSettings.bind(this);
|
||||||
saveNeeded: false,
|
|
||||||
serverError: null
|
this.state = Object.assign(this.state, {
|
||||||
};
|
termsOfServiceLink: props.config.SupportSettings.TermsOfServiceLink,
|
||||||
|
privacyPolicyLink: props.config.SupportSettings.PrivacyPolicyLink,
|
||||||
|
aboutLink: props.config.SupportSettings.AboutLink,
|
||||||
|
helpLink: props.config.SupportSettings.HelpLink,
|
||||||
|
reportAProblemLink: props.config.SupportSettings.ReportAProblemLink,
|
||||||
|
supportEmail: props.config.SupportSettings.SupportEmail
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChange() {
|
getConfigFromState(config) {
|
||||||
var s = {saveNeeded: true, serverError: this.state.serverError};
|
config.SupportSettings.TermsOfServiceLink = this.state.termsOfServiceLink;
|
||||||
this.setState(s);
|
config.SupportSettings.PrivacyPolicyLink = this.state.privacyPolicyLink;
|
||||||
|
config.SupportSettings.AboutLink = this.state.aboutLink;
|
||||||
|
config.SupportSettings.HelpLink = this.state.helpLink;
|
||||||
|
config.SupportSettings.ReportAProblemLink = this.state.reportAProblemLink;
|
||||||
|
config.SupportSettings.SupportEmail = this.state.supportEmail;
|
||||||
|
|
||||||
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSubmit(e) {
|
renderTitle() {
|
||||||
e.preventDefault();
|
return (
|
||||||
$('#save-button').button('loading');
|
<h3>
|
||||||
|
<FormattedMessage
|
||||||
var config = this.props.config;
|
id='admin.customization.title'
|
||||||
|
defaultMessage='Customization Settings'
|
||||||
config.SupportSettings.TermsOfServiceLink = ReactDOM.findDOMNode(this.refs.TermsOfServiceLink).value.trim();
|
/>
|
||||||
config.SupportSettings.PrivacyPolicyLink = ReactDOM.findDOMNode(this.refs.PrivacyPolicyLink).value.trim();
|
</h3>
|
||||||
config.SupportSettings.AboutLink = ReactDOM.findDOMNode(this.refs.AboutLink).value.trim();
|
|
||||||
config.SupportSettings.HelpLink = ReactDOM.findDOMNode(this.refs.HelpLink).value.trim();
|
|
||||||
config.SupportSettings.ReportAProblemLink = ReactDOM.findDOMNode(this.refs.ReportAProblemLink).value.trim();
|
|
||||||
config.SupportSettings.SupportEmail = ReactDOM.findDOMNode(this.refs.SupportEmail).value.trim();
|
|
||||||
|
|
||||||
Client.saveConfig(
|
|
||||||
config,
|
|
||||||
() => {
|
|
||||||
AsyncClient.getConfig();
|
|
||||||
this.setState({
|
|
||||||
serverError: null,
|
|
||||||
saveNeeded: false
|
|
||||||
});
|
|
||||||
$('#save-button').button('reset');
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
this.setState({
|
|
||||||
serverError: err.message,
|
|
||||||
saveNeeded: true
|
|
||||||
});
|
|
||||||
$('#save-button').button('reset');
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
renderSettings() {
|
||||||
var serverError = '';
|
|
||||||
if (this.state.serverError) {
|
|
||||||
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
var saveClass = 'btn';
|
|
||||||
if (this.state.saveNeeded) {
|
|
||||||
saveClass = 'btn btn-primary';
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='wrapper--fixed'>
|
<SettingsGroup
|
||||||
<div className='banner'>
|
header={
|
||||||
<div className='banner__content'>
|
|
||||||
<h4 className='banner__heading'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.support.noteTitle'
|
|
||||||
defaultMessage='Note:'
|
|
||||||
/>
|
|
||||||
</h4>
|
|
||||||
<p>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.support.noteDescription'
|
|
||||||
defaultMessage='If linking to an external site, URLs should begin with http:// or https://.'
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.support.title'
|
id='admin.customization.support'
|
||||||
defaultMessage='Legal and Support Settings'
|
defaultMessage='Legal and Support'
|
||||||
/>
|
/>
|
||||||
</h3>
|
}
|
||||||
<form
|
>
|
||||||
className='form-horizontal'
|
<TextSetting
|
||||||
role='form'
|
id='termsOfServiceLink'
|
||||||
>
|
label={
|
||||||
|
<FormattedMessage
|
||||||
<div className='form-group'>
|
id='admin.support.termsTitle'
|
||||||
<label
|
defaultMessage='Terms of Service link:'
|
||||||
className='control-label col-sm-4'
|
/>
|
||||||
htmlFor='TermsOfServiceLink'
|
}
|
||||||
>
|
helpText={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.support.termsTitle'
|
id='admin.support.termsDesc'
|
||||||
defaultMessage='Terms of Service link:'
|
defaultMessage='Link to Terms of Service available to users on desktop and on mobile. Leaving this blank will hide the option to display a notice.'
|
||||||
/>
|
/>
|
||||||
</label>
|
}
|
||||||
<div className='col-sm-8'>
|
value={this.state.termsOfServiceLink}
|
||||||
<input
|
onChange={this.handleChange}
|
||||||
type='text'
|
/>
|
||||||
className='form-control'
|
<TextSetting
|
||||||
id='TermsOfServiceLink'
|
id='privacyPolicyLink'
|
||||||
ref='TermsOfServiceLink'
|
label={
|
||||||
defaultValue={this.props.config.SupportSettings.TermsOfServiceLink}
|
<FormattedMessage
|
||||||
onChange={this.handleChange}
|
id='admin.support.privacyTitle'
|
||||||
/>
|
defaultMessage='Privacy Policy link:'
|
||||||
<p className='help-text'>
|
/>
|
||||||
<FormattedMessage
|
}
|
||||||
id='admin.support.termsDesc'
|
helpText={
|
||||||
defaultMessage='Link to Terms of Service available to users on desktop and on mobile. Leaving this blank will hide the option to display a notice.'
|
<FormattedMessage
|
||||||
/>
|
id='admin.support.privacyDesc'
|
||||||
</p>
|
defaultMessage='Link to Privacy Policy available to users on desktop and on mobile. Leaving this blank will hide the option to display a notice.'
|
||||||
</div>
|
/>
|
||||||
</div>
|
}
|
||||||
|
value={this.state.privacyPolicyLink}
|
||||||
<div className='form-group'>
|
onChange={this.handleChange}
|
||||||
<label
|
/>
|
||||||
className='control-label col-sm-4'
|
<TextSetting
|
||||||
htmlFor='PrivacyPolicyLink'
|
id='aboutLink'
|
||||||
>
|
label={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.support.privacyTitle'
|
id='admin.support.aboutTitle'
|
||||||
defaultMessage='Privacy Policy link:'
|
defaultMessage='About link:'
|
||||||
/>
|
/>
|
||||||
</label>
|
}
|
||||||
<div className='col-sm-8'>
|
helpText={
|
||||||
<input
|
<FormattedMessage
|
||||||
type='text'
|
id='admin.support.aboutDesc'
|
||||||
className='form-control'
|
defaultMessage='Link to About page for more information on your Mattermost deployment, for example its purpose and audience within your organization. Defaults to Mattermost information page.'
|
||||||
id='PrivacyPolicyLink'
|
/>
|
||||||
ref='PrivacyPolicyLink'
|
}
|
||||||
defaultValue={this.props.config.SupportSettings.PrivacyPolicyLink}
|
value={this.state.aboutLink}
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
/>
|
/>
|
||||||
<p className='help-text'>
|
<TextSetting
|
||||||
<FormattedMessage
|
id='helpLink'
|
||||||
id='admin.support.privacyDesc'
|
label={
|
||||||
defaultMessage='Link to Privacy Policy available to users on desktop and on mobile. Leaving this blank will hide the option to display a notice.'
|
<FormattedMessage
|
||||||
/>
|
id='admin.support.helpTitle'
|
||||||
</p>
|
defaultMessage='Help link:'
|
||||||
</div>
|
/>
|
||||||
</div>
|
}
|
||||||
|
helpText={
|
||||||
<div className='form-group'>
|
<FormattedMessage
|
||||||
<label
|
id='admin.support.helpDesc'
|
||||||
className='control-label col-sm-4'
|
defaultMessage='Link to help documentation from team site main menu. Typically not changed unless your organization chooses to create custom documentation.'
|
||||||
htmlFor='AboutLink'
|
/>
|
||||||
>
|
}
|
||||||
<FormattedMessage
|
value={this.state.helpLink}
|
||||||
id='admin.support.aboutTitle'
|
onChange={this.handleChange}
|
||||||
defaultMessage='About link:'
|
/>
|
||||||
/>
|
<TextSetting
|
||||||
</label>
|
id='reportAProblemLink'
|
||||||
<div className='col-sm-8'>
|
label={
|
||||||
<input
|
<FormattedMessage
|
||||||
type='text'
|
id='admin.support.problemTitle'
|
||||||
className='form-control'
|
defaultMessage='Report a Problem link:'
|
||||||
id='AboutLink'
|
/>
|
||||||
ref='AboutLink'
|
}
|
||||||
defaultValue={this.props.config.SupportSettings.AboutLink}
|
helpText={
|
||||||
onChange={this.handleChange}
|
<FormattedMessage
|
||||||
/>
|
id='admin.support.problemDesc'
|
||||||
<p className='help-text'>
|
defaultMessage='Link to help documentation from team site main menu. By default this points to the peer-to-peer troubleshooting forum where users can search for, find and request help with technical issues.'
|
||||||
<FormattedMessage
|
/>
|
||||||
id='admin.support.aboutDesc'
|
}
|
||||||
defaultMessage='Link to About page for more information on your Mattermost deployment, for example its purpose and audience within your organization. Defaults to Mattermost information page.'
|
value={this.state.reportAProblemLink}
|
||||||
/>
|
onChange={this.handleChange}
|
||||||
</p>
|
/>
|
||||||
</div>
|
<TextSetting
|
||||||
</div>
|
id='supportEmail'
|
||||||
|
label={
|
||||||
<div className='form-group'>
|
<FormattedMessage
|
||||||
<label
|
id='admin.support.emailTitle'
|
||||||
className='control-label col-sm-4'
|
defaultMessage='Support email:'
|
||||||
htmlFor='HelpLink'
|
/>
|
||||||
>
|
}
|
||||||
<FormattedMessage
|
helpText={
|
||||||
id='admin.support.helpTitle'
|
<FormattedMessage
|
||||||
defaultMessage='Help link:'
|
id='admin.support.emailHelp'
|
||||||
/>
|
defaultMessage='Email shown during tutorial for end users to ask support questions.'
|
||||||
</label>
|
/>
|
||||||
<div className='col-sm-8'>
|
}
|
||||||
<input
|
value={this.state.supportEmail}
|
||||||
type='text'
|
onChange={this.handleChange}
|
||||||
className='form-control'
|
/>
|
||||||
id='HelpLink'
|
</SettingsGroup>
|
||||||
ref='HelpLink'
|
|
||||||
defaultValue={this.props.config.SupportSettings.HelpLink}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
/>
|
|
||||||
<p className='help-text'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.support.helpDesc'
|
|
||||||
defaultMessage='Link to help documentation from team site main menu. Typically not changed unless your organization chooses to create custom documentation.'
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='form-group'>
|
|
||||||
<label
|
|
||||||
className='control-label col-sm-4'
|
|
||||||
htmlFor='ReportAProblemLink'
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.support.problemTitle'
|
|
||||||
defaultMessage='Report a Problem link:'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div className='col-sm-8'>
|
|
||||||
<input
|
|
||||||
type='text'
|
|
||||||
className='form-control'
|
|
||||||
id='ReportAProblemLink'
|
|
||||||
ref='ReportAProblemLink'
|
|
||||||
defaultValue={this.props.config.SupportSettings.ReportAProblemLink}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
/>
|
|
||||||
<p className='help-text'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.support.problemDesc'
|
|
||||||
defaultMessage='Link to help documentation from team site main menu. By default this points to the peer-to-peer troubleshooting forum where users can search for, find and request help with technical issues.'
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='form-group'>
|
|
||||||
<label
|
|
||||||
className='control-label col-sm-4'
|
|
||||||
htmlFor='SupportEmail'
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.support.emailTitle'
|
|
||||||
defaultMessage='Support email:'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div className='col-sm-8'>
|
|
||||||
<input
|
|
||||||
type='text'
|
|
||||||
className='form-control'
|
|
||||||
id='SupportEmail'
|
|
||||||
ref='SupportEmail'
|
|
||||||
defaultValue={this.props.config.SupportSettings.SupportEmail}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
/>
|
|
||||||
<p className='help-text'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.support.emailHelp'
|
|
||||||
defaultMessage='Email shown during tutorial for end users to ask support questions.'
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='form-group'>
|
|
||||||
<div className='col-sm-12'>
|
|
||||||
{serverError}
|
|
||||||
<button
|
|
||||||
disabled={!this.state.saveNeeded}
|
|
||||||
type='submit'
|
|
||||||
className={saveClass}
|
|
||||||
onClick={this.handleSubmit}
|
|
||||||
id='save-button'
|
|
||||||
data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + this.props.intl.formatMessage(holders.saving)}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.support.save'
|
|
||||||
defaultMessage='Save'
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LegalAndSupportSettings.propTypes = {
|
|
||||||
intl: intlShape.isRequired,
|
|
||||||
config: React.PropTypes.object
|
|
||||||
};
|
|
||||||
|
|
||||||
export default injectIntl(LegalAndSupportSettings);
|
|
||||||
@@ -1,418 +1,246 @@
|
|||||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
import $ from 'jquery';
|
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import Client from 'utils/web_client.jsx';
|
|
||||||
import * as AsyncClient from 'utils/async_client.jsx';
|
|
||||||
|
|
||||||
import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl';
|
|
||||||
|
|
||||||
const holders = defineMessages({
|
|
||||||
locationPlaceholder: {
|
|
||||||
id: 'admin.log.locationPlaceholder',
|
|
||||||
defaultMessage: 'Enter your file location'
|
|
||||||
},
|
|
||||||
formatPlaceholder: {
|
|
||||||
id: 'admin.log.formatPlaceholder',
|
|
||||||
defaultMessage: 'Enter your file format'
|
|
||||||
},
|
|
||||||
saving: {
|
|
||||||
id: 'admin.log.saving',
|
|
||||||
defaultMessage: 'Saving Config...'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
class LogSettings extends React.Component {
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
|
||||||
|
import AdminSettings from './admin_settings.jsx';
|
||||||
|
import BooleanSetting from './boolean_setting.jsx';
|
||||||
|
import DropdownSetting from './dropdown_setting.jsx';
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
import SettingsGroup from './settings_group.jsx';
|
||||||
|
import TextSetting from './text_setting.jsx';
|
||||||
|
|
||||||
|
export default class LogSettings extends AdminSettings {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.handleChange = this.handleChange.bind(this);
|
this.getConfigFromState = this.getConfigFromState.bind(this);
|
||||||
this.handleSubmit = this.handleSubmit.bind(this);
|
|
||||||
|
|
||||||
this.state = {
|
this.renderSettings = this.renderSettings.bind(this);
|
||||||
consoleEnable: this.props.config.LogSettings.EnableConsole,
|
|
||||||
fileEnable: this.props.config.LogSettings.EnableFile,
|
this.state = Object.assign(this.state, {
|
||||||
saveNeeded: false,
|
enableConsole: props.config.LogSettings.EnableConsole,
|
||||||
serverError: null
|
consoleLevel: props.config.LogSettings.ConsoleLevel,
|
||||||
};
|
enableFile: props.config.LogSettings.EnableFile,
|
||||||
|
fileLevel: props.config.LogSettings.FileLevel,
|
||||||
|
fileLocation: props.config.LogSettings.FileLocation,
|
||||||
|
fileFormat: props.config.LogSettings.FileFormat
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChange(action) {
|
getConfigFromState(config) {
|
||||||
var s = {saveNeeded: true, serverError: this.state.serverError};
|
config.LogSettings.EnableConsole = this.state.enableConsole;
|
||||||
|
config.LogSettings.ConsoleLevel = this.state.consoleLevel;
|
||||||
|
config.LogSettings.EnableFile = this.state.enableFile;
|
||||||
|
config.LogSettings.FileLevel = this.state.fileLevel;
|
||||||
|
config.LogSettings.FileLocation = this.state.fileLocation;
|
||||||
|
config.LogSettings.FileFormat = this.state.fileFormat;
|
||||||
|
|
||||||
if (action === 'console_true') {
|
return config;
|
||||||
s.consoleEnable = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action === 'console_false') {
|
|
||||||
s.consoleEnable = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action === 'file_true') {
|
|
||||||
s.fileEnable = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action === 'file_false') {
|
|
||||||
s.fileEnable = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState(s);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSubmit(e) {
|
renderTitle() {
|
||||||
e.preventDefault();
|
return (
|
||||||
$('#save-button').button('loading');
|
<h3>
|
||||||
|
<FormattedMessage
|
||||||
var config = this.props.config;
|
id='admin.general.title'
|
||||||
config.LogSettings.EnableConsole = ReactDOM.findDOMNode(this.refs.consoleEnable).checked;
|
defaultMessage='General Settings'
|
||||||
config.LogSettings.ConsoleLevel = ReactDOM.findDOMNode(this.refs.consoleLevel).value;
|
/>
|
||||||
config.LogSettings.EnableFile = ReactDOM.findDOMNode(this.refs.fileEnable).checked;
|
</h3>
|
||||||
config.LogSettings.FileLevel = ReactDOM.findDOMNode(this.refs.fileLevel).value;
|
|
||||||
config.LogSettings.FileLocation = ReactDOM.findDOMNode(this.refs.fileLocation).value.trim();
|
|
||||||
config.LogSettings.FileFormat = ReactDOM.findDOMNode(this.refs.fileFormat).value.trim();
|
|
||||||
|
|
||||||
Client.saveConfig(
|
|
||||||
config,
|
|
||||||
() => {
|
|
||||||
AsyncClient.getConfig();
|
|
||||||
this.setState({
|
|
||||||
consoleEnable: config.LogSettings.EnableConsole,
|
|
||||||
fileEnable: config.LogSettings.EnableFile,
|
|
||||||
serverError: null,
|
|
||||||
saveNeeded: false
|
|
||||||
});
|
|
||||||
$('#save-button').button('reset');
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
this.setState({
|
|
||||||
consoleEnable: config.LogSettings.EnableConsole,
|
|
||||||
fileEnable: config.LogSettings.EnableFile,
|
|
||||||
serverError: err.message,
|
|
||||||
saveNeeded: true
|
|
||||||
});
|
|
||||||
$('#save-button').button('reset');
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
renderSettings() {
|
||||||
const {formatMessage} = this.props.intl;
|
const logLevels = [
|
||||||
var serverError = '';
|
{value: 'DEBUG', text: 'DEBUG'},
|
||||||
if (this.state.serverError) {
|
{value: 'INFO', text: 'INFO'},
|
||||||
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
|
{value: 'ERROR', text: 'ERROR'}
|
||||||
}
|
];
|
||||||
|
|
||||||
var saveClass = 'btn';
|
|
||||||
if (this.state.saveNeeded) {
|
|
||||||
saveClass = 'btn btn-primary';
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='wrapper--fixed'>
|
<SettingsGroup
|
||||||
<h3>
|
header={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.log.logSettings'
|
id='admin.general.log'
|
||||||
defaultMessage='Log Settings'
|
defaultMessage='Logging'
|
||||||
/>
|
/>
|
||||||
</h3>
|
|
||||||
<form
|
}
|
||||||
className='form-horizontal'
|
>
|
||||||
role='form'
|
<BooleanSetting
|
||||||
|
id='enableConsole'
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.log.consoleTitle'
|
||||||
|
defaultMessage='Log To The Console: '
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
helpText={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.log.consoleDescription'
|
||||||
|
defaultMessage='Typically set to false in production. Developers may set this field to true to output log messages to console based on the console level option. If true, server writes messages to the standard output stream (stdout).'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
value={this.state.enableConsole}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
/>
|
||||||
|
<DropdownSetting
|
||||||
|
id='consoleLevel'
|
||||||
|
values={logLevels}
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.log.levelTitle'
|
||||||
|
defaultMessage='Console Log Level:'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
value={this.state.consoleLevel}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
disabled={!this.state.enableConsole}
|
||||||
|
helpText={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.log.levelDescription'
|
||||||
|
defaultMessage='This setting determines the level of detail at which log events are written to the console. ERROR: Outputs only error messages. INFO: Outputs error messages and information around startup and initialization. DEBUG: Prints high detail for developers working on debugging issues.'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<BooleanSetting
|
||||||
|
id='enableFile'
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.log.fileTitle'
|
||||||
|
defaultMessage='Log To File: '
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
helpText={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.log.fileDescription'
|
||||||
|
defaultMessage='Typically set to true in production. When true, log files are written to the log file specified in file location field below.'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
value={this.state.enableFile}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
/>
|
||||||
|
<DropdownSetting
|
||||||
|
id='fileLevel'
|
||||||
|
values={logLevels}
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.log.fileLevelTitle'
|
||||||
|
defaultMessage='File Log Level:'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
value={this.state.fileLevel}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
disabled={!this.state.enableFile}
|
||||||
|
helpText={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.log.fileLevelDescription'
|
||||||
|
defaultMessage='This setting determines the level of detail at which log events are written to the log file. ERROR: Outputs only error messages. INFO: Outputs error messages and information around startup and initialization. DEBUG: Prints high detail for developers working on debugging issues.'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TextSetting
|
||||||
|
id='fileLocation'
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.log.locationTitle'
|
||||||
|
defaultMessage='File Location:'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
placeholder={Utils.localizeMessage('admin.log.locationPlaceholder', 'Enter your file location')}
|
||||||
|
helpText={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.log.locationDescription'
|
||||||
|
defaultMessage='File to which log files are written. If blank, will be set to ./logs/mattermost, which writes logs to mattermost.log. Log rotation is enabled and every 10,000 lines of log information is written to new files stored in the same directory, for example mattermost.2015-09-23.001, mattermost.2015-09-23.002, and so forth.'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
value={this.state.fileLocation}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
disabled={!this.state.enableFile}
|
||||||
|
/>
|
||||||
|
<TextSetting
|
||||||
|
id='fileFormat'
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.log.formatTitle'
|
||||||
|
defaultMessage='File Format:'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
placeholder={Utils.localizeMessage('admin.log.formatPlaceholder', 'Enter your file format')}
|
||||||
|
helpText={this.renderFileFormatHelpText()}
|
||||||
|
value={this.state.fileFormat}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
disabled={!this.state.enableFile}
|
||||||
|
/>
|
||||||
|
</SettingsGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderFileFormatHelpText() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.log.formatDescription'
|
||||||
|
defaultMessage='Format of log message output. If blank will be set to "[%D %T] [%L] %M", where:'
|
||||||
|
/>
|
||||||
|
<table
|
||||||
|
className='table table-bordered'
|
||||||
|
cellPadding='5'
|
||||||
>
|
>
|
||||||
|
<tbody>
|
||||||
<div className='form-group'>
|
<tr>
|
||||||
<label
|
<td className='help-text'>{'%T'}</td><td className='help-text'>
|
||||||
className='control-label col-sm-4'
|
|
||||||
htmlFor='consoleEnable'
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.log.consoleTitle'
|
|
||||||
defaultMessage='Log To The Console: '
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div className='col-sm-8'>
|
|
||||||
<label className='radio-inline'>
|
|
||||||
<input
|
|
||||||
type='radio'
|
|
||||||
name='consoleEnable'
|
|
||||||
value='true'
|
|
||||||
ref='consoleEnable'
|
|
||||||
defaultChecked={this.props.config.LogSettings.EnableConsole}
|
|
||||||
onChange={this.handleChange.bind(this, 'console_true')}
|
|
||||||
/>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.log.true'
|
id='admin.log.formatTime'
|
||||||
defaultMessage='true'
|
defaultMessage='Time (15:04:05 MST)'
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<label className='radio-inline'>
|
|
||||||
<input
|
|
||||||
type='radio'
|
|
||||||
name='consoleEnable'
|
|
||||||
value='false'
|
|
||||||
defaultChecked={!this.props.config.LogSettings.EnableConsole}
|
|
||||||
onChange={this.handleChange.bind(this, 'console_false')}
|
|
||||||
/>
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className='help-text'>{'%D'}</td><td className='help-text'>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.log.false'
|
id='admin.log.formatDateLong'
|
||||||
defaultMessage='false'
|
defaultMessage='Date (2006/01/02)'
|
||||||
/>
|
/>
|
||||||
</label>
|
</td>
|
||||||
<p className='help-text'>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className='help-text'>{'%d'}</td><td className='help-text'>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.log.consoleDescription'
|
id='admin.log.formatDateShort'
|
||||||
defaultMessage='Typically set to false in production. Developers may set this field to true to output log messages to console based on the console level option. If true, server writes messages to the standard output stream (stdout).'
|
defaultMessage='Date (01/02/06)'
|
||||||
/>
|
/>
|
||||||
</p>
|
</td>
|
||||||
</div>
|
</tr>
|
||||||
</div>
|
<tr>
|
||||||
|
<td className='help-text'>{'%L'}</td><td className='help-text'>
|
||||||
<div className='form-group'>
|
|
||||||
<label
|
|
||||||
className='control-label col-sm-4'
|
|
||||||
htmlFor='consoleLevel'
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.log.levelTitle'
|
|
||||||
defaultMessage='Console Log Level:'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div className='col-sm-8'>
|
|
||||||
<select
|
|
||||||
className='form-control'
|
|
||||||
id='consoleLevel'
|
|
||||||
ref='consoleLevel'
|
|
||||||
defaultValue={this.props.config.LogSettings.consoleLevel}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
disabled={!this.state.consoleEnable}
|
|
||||||
>
|
|
||||||
<option value='DEBUG'>{'DEBUG'}</option>
|
|
||||||
<option value='INFO'>{'INFO'}</option>
|
|
||||||
<option value='ERROR'>{'ERROR'}</option>
|
|
||||||
</select>
|
|
||||||
<p className='help-text'>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.log.levelDescription'
|
id='admin.log.formatLevel'
|
||||||
defaultMessage='This setting determines the level of detail at which log events are written to the console. ERROR: Outputs only error messages. INFO: Outputs error messages and information around startup and initialization. DEBUG: Prints high detail for developers working on debugging issues.'
|
defaultMessage='Level (DEBG, INFO, EROR)'
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='form-group'>
|
|
||||||
<label
|
|
||||||
className='control-label col-sm-4'
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.log.fileTitle'
|
|
||||||
defaultMessage='Log To File: '
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div className='col-sm-8'>
|
|
||||||
<label className='radio-inline'>
|
|
||||||
<input
|
|
||||||
type='radio'
|
|
||||||
name='fileEnable'
|
|
||||||
ref='fileEnable'
|
|
||||||
value='true'
|
|
||||||
defaultChecked={this.props.config.LogSettings.EnableFile}
|
|
||||||
onChange={this.handleChange.bind(this, 'file_true')}
|
|
||||||
/>
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className='help-text'>{'%S'}</td><td className='help-text'>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.log.true'
|
id='admin.log.formatSource'
|
||||||
defaultMessage='true'
|
defaultMessage='Source'
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<label className='radio-inline'>
|
|
||||||
<input
|
|
||||||
type='radio'
|
|
||||||
name='fileEnable'
|
|
||||||
value='false'
|
|
||||||
defaultChecked={!this.props.config.LogSettings.EnableFile}
|
|
||||||
onChange={this.handleChange.bind(this, 'file_false')}
|
|
||||||
/>
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className='help-text'>{'%M'}</td><td className='help-text'>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.log.false'
|
id='admin.log.formatMessage'
|
||||||
defaultMessage='false'
|
defaultMessage='Message'
|
||||||
/>
|
/>
|
||||||
</label>
|
</td>
|
||||||
<p className='help-text'>
|
</tr>
|
||||||
<FormattedMessage
|
</tbody>
|
||||||
id='admin.log.fileDescription'
|
</table>
|
||||||
defaultMessage='Typically set to true in production. When true, log files are written to the log file specified in file location field below.'
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='form-group'>
|
|
||||||
<label
|
|
||||||
className='control-label col-sm-4'
|
|
||||||
htmlFor='fileLevel'
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.log.fileLevelTitle'
|
|
||||||
defaultMessage='File Log Level:'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div className='col-sm-8'>
|
|
||||||
<select
|
|
||||||
className='form-control'
|
|
||||||
id='fileLevel'
|
|
||||||
ref='fileLevel'
|
|
||||||
defaultValue={this.props.config.LogSettings.FileLevel}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
disabled={!this.state.fileEnable}
|
|
||||||
>
|
|
||||||
<option value='DEBUG'>{'DEBUG'}</option>
|
|
||||||
<option value='INFO'>{'INFO'}</option>
|
|
||||||
<option value='ERROR'>{'ERROR'}</option>
|
|
||||||
</select>
|
|
||||||
<p className='help-text'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.log.fileLevelDescription'
|
|
||||||
defaultMessage='This setting determines the level of detail at which log events are written to the log file. ERROR: Outputs only error messages. INFO: Outputs error messages and information around startup and initialization. DEBUG: Prints high detail for developers working on debugging issues.'
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='form-group'>
|
|
||||||
<label
|
|
||||||
className='control-label col-sm-4'
|
|
||||||
htmlFor='fileLocation'
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.log.locationTitle'
|
|
||||||
defaultMessage='File Location:'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div className='col-sm-8'>
|
|
||||||
<input
|
|
||||||
type='text'
|
|
||||||
className='form-control'
|
|
||||||
id='fileLocation'
|
|
||||||
ref='fileLocation'
|
|
||||||
placeholder={formatMessage(holders.locationPlaceholder)}
|
|
||||||
defaultValue={this.props.config.LogSettings.FileLocation}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
disabled={!this.state.fileEnable}
|
|
||||||
/>
|
|
||||||
<p className='help-text'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.log.locationDescription'
|
|
||||||
defaultMessage='File to which log files are written. If blank, will be set to ./logs/mattermost, which writes logs to mattermost.log. Log rotation is enabled and every 10,000 lines of log information is written to new files stored in the same directory, for example mattermost.2015-09-23.001, mattermost.2015-09-23.002, and so forth.'
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='form-group'>
|
|
||||||
<label
|
|
||||||
className='control-label col-sm-4'
|
|
||||||
htmlFor='fileFormat'
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.log.formatTitle'
|
|
||||||
defaultMessage='File Format:'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div className='col-sm-8'>
|
|
||||||
<input
|
|
||||||
type='text'
|
|
||||||
className='form-control'
|
|
||||||
id='fileFormat'
|
|
||||||
ref='fileFormat'
|
|
||||||
placeholder={formatMessage(holders.formatPlaceholder)}
|
|
||||||
defaultValue={this.props.config.LogSettings.FileFormat}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
disabled={!this.state.fileEnable}
|
|
||||||
/>
|
|
||||||
<div className='help-text'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.log.formatDescription'
|
|
||||||
defaultMessage='Format of log message output. If blank will be set to "[%D %T] [%L] %M", where:'
|
|
||||||
/>
|
|
||||||
<div className='help-text'>
|
|
||||||
<table
|
|
||||||
className='table table-bordered'
|
|
||||||
cellPadding='5'
|
|
||||||
>
|
|
||||||
<tbody>
|
|
||||||
<tr><td className='help-text'>{'%T'}</td><td className='help-text'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.log.formatTime'
|
|
||||||
defaultMessage='Time (15:04:05 MST)'
|
|
||||||
/>
|
|
||||||
</td></tr>
|
|
||||||
<tr><td className='help-text'>{'%D'}</td><td className='help-text'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.log.formatDateLong'
|
|
||||||
defaultMessage='Date (2006/01/02)'
|
|
||||||
/>
|
|
||||||
</td></tr>
|
|
||||||
<tr><td className='help-text'>{'%d'}</td><td className='help-text'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.log.formatDateShort'
|
|
||||||
defaultMessage='Date (01/02/06)'
|
|
||||||
/>
|
|
||||||
</td></tr>
|
|
||||||
<tr><td className='help-text'>{'%L'}</td><td className='help-text'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.log.formatLevel'
|
|
||||||
defaultMessage='Level (DEBG, INFO, EROR)'
|
|
||||||
/>
|
|
||||||
</td></tr>
|
|
||||||
<tr><td className='help-text'>{'%S'}</td><td className='help-text'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.log.formatSource'
|
|
||||||
defaultMessage='Source'
|
|
||||||
/>
|
|
||||||
</td></tr>
|
|
||||||
<tr><td className='help-text'>{'%M'}</td><td className='help-text'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.log.formatMessage'
|
|
||||||
defaultMessage='Message'
|
|
||||||
/>
|
|
||||||
</td></tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='form-group'>
|
|
||||||
<div className='col-sm-12'>
|
|
||||||
{serverError}
|
|
||||||
<button
|
|
||||||
disabled={!this.state.saveNeeded}
|
|
||||||
type='submit'
|
|
||||||
className={saveClass}
|
|
||||||
onClick={this.handleSubmit}
|
|
||||||
id='save-button'
|
|
||||||
data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + formatMessage(holders.saving)}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.log.save'
|
|
||||||
defaultMessage='Save'
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LogSettings.propTypes = {
|
|
||||||
intl: intlShape.isRequired,
|
|
||||||
config: React.PropTypes.object
|
|
||||||
};
|
|
||||||
|
|
||||||
export default injectIntl(LogSettings);
|
|
||||||
|
|||||||
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,215 +1,90 @@
|
|||||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
import $ from 'jquery';
|
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import Client from 'utils/web_client.jsx';
|
|
||||||
import * as AsyncClient from 'utils/async_client.jsx';
|
|
||||||
|
|
||||||
import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl';
|
|
||||||
|
|
||||||
const holders = defineMessages({
|
|
||||||
saving: {
|
|
||||||
id: 'admin.privacy.saving',
|
|
||||||
defaultMessage: 'Saving Config...'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
class PrivacySettings extends React.Component {
|
import AdminSettings from './admin_settings.jsx';
|
||||||
|
import BooleanSetting from './boolean_setting.jsx';
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
import SettingsGroup from './settings_group.jsx';
|
||||||
|
|
||||||
|
export default class PrivacySettings extends AdminSettings {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.handleChange = this.handleChange.bind(this);
|
this.getConfigFromState = this.getConfigFromState.bind(this);
|
||||||
this.handleSubmit = this.handleSubmit.bind(this);
|
|
||||||
|
|
||||||
this.state = {
|
this.renderSettings = this.renderSettings.bind(this);
|
||||||
saveNeeded: false,
|
|
||||||
serverError: null
|
this.state = Object.assign(this.state, {
|
||||||
};
|
showEmailAddress: props.config.PrivacySettings.ShowEmailAddress,
|
||||||
|
showFullName: props.config.PrivacySettings.ShowFullName
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChange() {
|
getConfigFromState(config) {
|
||||||
var s = {saveNeeded: true, serverError: this.state.serverError};
|
config.PrivacySettings.ShowEmailAddress = this.state.showEmailAddress;
|
||||||
|
config.PrivacySettings.ShowFullName = this.state.showFullName;
|
||||||
|
|
||||||
this.setState(s);
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSubmit(e) {
|
renderTitle() {
|
||||||
e.preventDefault();
|
return (
|
||||||
$('#save-button').button('loading');
|
<h3>
|
||||||
|
<FormattedMessage
|
||||||
var config = this.props.config;
|
id='admin.general.title'
|
||||||
config.PrivacySettings.ShowEmailAddress = ReactDOM.findDOMNode(this.refs.ShowEmailAddress).checked;
|
defaultMessage='General Settings'
|
||||||
config.PrivacySettings.ShowFullName = ReactDOM.findDOMNode(this.refs.ShowFullName).checked;
|
/>
|
||||||
|
</h3>
|
||||||
Client.saveConfig(
|
|
||||||
config,
|
|
||||||
() => {
|
|
||||||
AsyncClient.getConfig();
|
|
||||||
this.setState({
|
|
||||||
serverError: null,
|
|
||||||
saveNeeded: false
|
|
||||||
});
|
|
||||||
$('#save-button').button('reset');
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
this.setState({
|
|
||||||
serverError: err.message,
|
|
||||||
saveNeeded: true
|
|
||||||
});
|
|
||||||
$('#save-button').button('reset');
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
renderSettings() {
|
||||||
var serverError = '';
|
|
||||||
if (this.state.serverError) {
|
|
||||||
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
var saveClass = 'btn';
|
|
||||||
if (this.state.saveNeeded) {
|
|
||||||
saveClass = 'btn btn-primary';
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='wrapper--fixed'>
|
<SettingsGroup
|
||||||
<h3>
|
header={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.privacy.title'
|
id='admin.general.privacy'
|
||||||
defaultMessage='Privacy Settings'
|
defaultMessage='Privacy'
|
||||||
/>
|
/>
|
||||||
</h3>
|
}
|
||||||
<form
|
>
|
||||||
className='form-horizontal'
|
<BooleanSetting
|
||||||
role='form'
|
id='showEmailAddress'
|
||||||
>
|
label={
|
||||||
|
<FormattedMessage
|
||||||
<div className='form-group'>
|
id='admin.privacy.showEmailTitle'
|
||||||
<label
|
defaultMessage='Show Email Address: '
|
||||||
className='control-label col-sm-4'
|
/>
|
||||||
htmlFor='ShowEmailAddress'
|
}
|
||||||
>
|
helpText={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.privacy.showEmailTitle'
|
id='admin.privacy.showEmailDescription'
|
||||||
defaultMessage='Show Email Address: '
|
defaultMessage='When false, hides email address of users from other users in the user interface, including team owners and team administrators. Used when system is set up for managing teams where some users choose to keep their contact information private.'
|
||||||
/>
|
/>
|
||||||
</label>
|
}
|
||||||
<div className='col-sm-8'>
|
value={this.state.showEmailAddress}
|
||||||
<label className='radio-inline'>
|
onChange={this.handleChange}
|
||||||
<input
|
/>
|
||||||
type='radio'
|
<BooleanSetting
|
||||||
name='ShowEmailAddress'
|
id='showFullName'
|
||||||
value='true'
|
label={
|
||||||
ref='ShowEmailAddress'
|
<FormattedMessage
|
||||||
defaultChecked={this.props.config.PrivacySettings.ShowEmailAddress}
|
id='admin.privacy.showFullNameTitle'
|
||||||
onChange={this.handleChange}
|
defaultMessage='Show Full Name: '
|
||||||
/>
|
/>
|
||||||
<FormattedMessage
|
}
|
||||||
id='admin.privacy.true'
|
helpText={
|
||||||
defaultMessage='true'
|
<FormattedMessage
|
||||||
/>
|
id='admin.privacy.showFullNameDescription'
|
||||||
</label>
|
defaultMessage='When false, hides full name of users from other users, including team owners and team administrators. Username is shown in place of full name.'
|
||||||
<label className='radio-inline'>
|
/>
|
||||||
<input
|
}
|
||||||
type='radio'
|
value={this.state.showFullName}
|
||||||
name='ShowEmailAddress'
|
onChange={this.handleChange}
|
||||||
value='false'
|
/>
|
||||||
defaultChecked={!this.props.config.PrivacySettings.ShowEmailAddress}
|
</SettingsGroup>
|
||||||
onChange={this.handleChange}
|
|
||||||
/>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.privacy.false'
|
|
||||||
defaultMessage='false'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<p className='help-text'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.privacy.showEmailDescription'
|
|
||||||
defaultMessage='When false, hides email address of users from other users in the user interface, including team owners and team administrators. Used when system is set up for managing teams where some users choose to keep their contact information private.'
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='form-group'>
|
|
||||||
<label
|
|
||||||
className='control-label col-sm-4'
|
|
||||||
htmlFor='ShowFullName'
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.privacy.showFullNameTitle'
|
|
||||||
defaultMessage='Show Full Name: '
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div className='col-sm-8'>
|
|
||||||
<label className='radio-inline'>
|
|
||||||
<input
|
|
||||||
type='radio'
|
|
||||||
name='ShowFullName'
|
|
||||||
value='true'
|
|
||||||
ref='ShowFullName'
|
|
||||||
defaultChecked={this.props.config.PrivacySettings.ShowFullName}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
/>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.privacy.true'
|
|
||||||
defaultMessage='true'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<label className='radio-inline'>
|
|
||||||
<input
|
|
||||||
type='radio'
|
|
||||||
name='ShowFullName'
|
|
||||||
value='false'
|
|
||||||
defaultChecked={!this.props.config.PrivacySettings.ShowFullName}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
/>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.privacy.false'
|
|
||||||
defaultMessage='false'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<p className='help-text'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.privacy.showFullNameDescription'
|
|
||||||
defaultMessage='When false, hides full name of users from other users, including team owners and team administrators. Username is shown in place of full name.'
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='form-group'>
|
|
||||||
<div className='col-sm-12'>
|
|
||||||
{serverError}
|
|
||||||
<button
|
|
||||||
disabled={!this.state.saveNeeded}
|
|
||||||
type='submit'
|
|
||||||
className={saveClass}
|
|
||||||
onClick={this.handleSubmit}
|
|
||||||
id='save-button'
|
|
||||||
data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + this.props.intl.formatMessage(holders.saving)}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.privacy.save'
|
|
||||||
defaultMessage='Save'
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PrivacySettings.propTypes = {
|
|
||||||
intl: intlShape.isRequired,
|
|
||||||
config: React.PropTypes.object
|
|
||||||
};
|
|
||||||
|
|
||||||
export default injectIntl(PrivacySettings);
|
|
||||||
|
|||||||
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.
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
import $ from 'jquery';
|
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import Client from 'utils/web_client.jsx';
|
|
||||||
import * as AsyncClient from 'utils/async_client.jsx';
|
|
||||||
|
|
||||||
import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl';
|
|
||||||
|
|
||||||
const holders = defineMessages({
|
|
||||||
queriesExample: {
|
|
||||||
id: 'admin.rate.queriesExample',
|
|
||||||
defaultMessage: 'Ex "10"'
|
|
||||||
},
|
|
||||||
memoryExample: {
|
|
||||||
id: 'admin.rate.memoryExample',
|
|
||||||
defaultMessage: 'Ex "10000"'
|
|
||||||
},
|
|
||||||
httpHeaderExample: {
|
|
||||||
id: 'admin.rate.httpHeaderExample',
|
|
||||||
defaultMessage: 'Ex "X-Real-IP", "X-Forwarded-For"'
|
|
||||||
},
|
|
||||||
saving: {
|
|
||||||
id: 'admin.rate.saving',
|
|
||||||
defaultMessage: 'Saving Config...'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
class RateSettings extends React.Component {
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
|
||||||
|
import AdminSettings from './admin_settings.jsx';
|
||||||
|
import BooleanSetting from './boolean_setting.jsx';
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
import SettingsGroup from './settings_group.jsx';
|
||||||
|
import TextSetting from './text_setting.jsx';
|
||||||
|
|
||||||
|
export default class RateSettings extends AdminSettings {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.handleChange = this.handleChange.bind(this);
|
this.getConfigFromState = this.getConfigFromState.bind(this);
|
||||||
this.handleSubmit = this.handleSubmit.bind(this);
|
|
||||||
|
|
||||||
this.state = {
|
this.renderSettings = this.renderSettings.bind(this);
|
||||||
EnableRateLimiter: this.props.config.RateLimitSettings.EnableRateLimiter,
|
|
||||||
VaryByRemoteAddr: this.props.config.RateLimitSettings.VaryByRemoteAddr,
|
this.state = Object.assign(this.state, {
|
||||||
saveNeeded: false,
|
enableRateLimiter: props.config.RateLimitSettings.EnableRateLimiter,
|
||||||
serverError: null
|
perSec: props.config.RateLimitSettings.PerSec,
|
||||||
};
|
memoryStoreSize: props.config.RateLimitSettings.MemoryStoreSize,
|
||||||
|
varyByRemoteAddr: props.config.RateLimitSettings.VaryByRemoteAddr,
|
||||||
|
varyByHeader: props.config.RateLimitSettings.VaryByHeader
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChange(action) {
|
getConfigFromState(config) {
|
||||||
var s = {saveNeeded: true, serverError: this.state.serverError};
|
config.RateLimitSettings.EnableRateLimiter = this.state.enableRateLimiter;
|
||||||
|
config.RateLimitSettings.PerSec = this.parseIntNonZero(this.state.perSec);
|
||||||
|
config.RateLimitSettings.MemoryStoreSize = this.parseIntNonZero(this.state.memoryStoreSize);
|
||||||
|
config.RateLimitSettings.VaryByRemoteAddr = this.state.varyByRemoteAddr;
|
||||||
|
config.RateLimitSettings.VaryByHeader = this.state.varyByHeader;
|
||||||
|
|
||||||
if (action === 'EnableRateLimiterTrue') {
|
return config;
|
||||||
s.EnableRateLimiter = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action === 'EnableRateLimiterFalse') {
|
|
||||||
s.EnableRateLimiter = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action === 'VaryByRemoteAddrTrue') {
|
|
||||||
s.VaryByRemoteAddr = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action === 'VaryByRemoteAddrFalse') {
|
|
||||||
s.VaryByRemoteAddr = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState(s);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSubmit(e) {
|
renderTitle() {
|
||||||
e.preventDefault();
|
return (
|
||||||
$('#save-button').button('loading');
|
<h3>
|
||||||
|
<FormattedMessage
|
||||||
var config = this.props.config;
|
id='admin.rate.title'
|
||||||
config.RateLimitSettings.EnableRateLimiter = ReactDOM.findDOMNode(this.refs.EnableRateLimiter).checked;
|
defaultMessage='Rate Limit Settings'
|
||||||
config.RateLimitSettings.VaryByRemoteAddr = ReactDOM.findDOMNode(this.refs.VaryByRemoteAddr).checked;
|
/>
|
||||||
config.RateLimitSettings.VaryByHeader = ReactDOM.findDOMNode(this.refs.VaryByHeader).value.trim();
|
</h3>
|
||||||
|
|
||||||
var PerSec = 10;
|
|
||||||
if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.PerSec).value, 10))) {
|
|
||||||
PerSec = parseInt(ReactDOM.findDOMNode(this.refs.PerSec).value, 10);
|
|
||||||
}
|
|
||||||
config.RateLimitSettings.PerSec = PerSec;
|
|
||||||
ReactDOM.findDOMNode(this.refs.PerSec).value = PerSec;
|
|
||||||
|
|
||||||
var MemoryStoreSize = 10000;
|
|
||||||
if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.MemoryStoreSize).value, 10))) {
|
|
||||||
MemoryStoreSize = parseInt(ReactDOM.findDOMNode(this.refs.MemoryStoreSize).value, 10);
|
|
||||||
}
|
|
||||||
config.RateLimitSettings.MemoryStoreSize = MemoryStoreSize;
|
|
||||||
ReactDOM.findDOMNode(this.refs.MemoryStoreSize).value = MemoryStoreSize;
|
|
||||||
|
|
||||||
Client.saveConfig(
|
|
||||||
config,
|
|
||||||
() => {
|
|
||||||
AsyncClient.getConfig();
|
|
||||||
this.setState({
|
|
||||||
serverError: null,
|
|
||||||
saveNeeded: false
|
|
||||||
});
|
|
||||||
$('#save-button').button('reset');
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
this.setState({
|
|
||||||
serverError: err.message,
|
|
||||||
saveNeeded: true
|
|
||||||
});
|
|
||||||
$('#save-button').button('reset');
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
renderSettings() {
|
||||||
const {formatMessage} = this.props.intl;
|
|
||||||
var serverError = '';
|
|
||||||
if (this.state.serverError) {
|
|
||||||
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
var saveClass = 'btn';
|
|
||||||
if (this.state.saveNeeded) {
|
|
||||||
saveClass = 'btn btn-primary';
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='wrapper--fixed'>
|
<SettingsGroup>
|
||||||
|
|
||||||
<div className='banner'>
|
<div className='banner'>
|
||||||
<div className='banner__content'>
|
<div className='banner__content'>
|
||||||
<h4 className='banner__heading'>
|
<FormattedMessage
|
||||||
<FormattedMessage
|
id='admin.rate.noteDescription'
|
||||||
id='admin.rate.noteTitle'
|
defaultMessage='Changing properties in this section will require a server restart before taking effect.'
|
||||||
defaultMessage='Note:'
|
/>
|
||||||
/>
|
|
||||||
</h4>
|
|
||||||
<p>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.rate.noteDescription'
|
|
||||||
defaultMessage='Changing properties in this section will require a server restart before taking effect.'
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<BooleanSetting
|
||||||
<h3>
|
id='enableRateLimiter'
|
||||||
<FormattedMessage
|
label={
|
||||||
id='admin.rate.title'
|
<FormattedMessage
|
||||||
defaultMessage='Rate Limit Settings'
|
id='admin.rate.enableLimiterTitle'
|
||||||
/>
|
defaultMessage='Enable Rate Limiter: '
|
||||||
</h3>
|
/>
|
||||||
<form
|
}
|
||||||
className='form-horizontal'
|
helpText={
|
||||||
role='form'
|
<FormattedMessage
|
||||||
>
|
id='admin.rate.enableLimiterDescription'
|
||||||
|
defaultMessage='When true, APIs are throttled at rates specified below.'
|
||||||
<div className='form-group'>
|
/>
|
||||||
<label
|
}
|
||||||
className='control-label col-sm-4'
|
value={this.state.enableRateLimiter}
|
||||||
htmlFor='EnableRateLimiter'
|
onChange={this.handleChange}
|
||||||
>
|
/>
|
||||||
<FormattedMessage
|
<TextSetting
|
||||||
id='admin.rate.enableLimiterTitle'
|
id='perSec'
|
||||||
defaultMessage='Enable Rate Limiter: '
|
label={
|
||||||
/>
|
<FormattedMessage
|
||||||
</label>
|
id='admin.rate.queriesTitle'
|
||||||
<div className='col-sm-8'>
|
defaultMessage='Number Of Queries Per Second:'
|
||||||
<label className='radio-inline'>
|
/>
|
||||||
<input
|
}
|
||||||
type='radio'
|
placeholder={Utils.localizeMessage('admin.rate.queriesExample', 'Ex "10"')}
|
||||||
name='EnableRateLimiter'
|
helpText={
|
||||||
value='true'
|
<FormattedMessage
|
||||||
ref='EnableRateLimiter'
|
id='admin.rate.queriesDescription'
|
||||||
defaultChecked={this.props.config.RateLimitSettings.EnableRateLimiter}
|
defaultMessage='Throttles API at this number of requests per second.'
|
||||||
onChange={this.handleChange.bind(this, 'EnableRateLimiterTrue')}
|
/>
|
||||||
/>
|
}
|
||||||
<FormattedMessage
|
value={this.state.perSec}
|
||||||
id='admin.rate.true'
|
onChange={this.handleChange}
|
||||||
defaultMessage='true'
|
disabled={!this.state.enableRateLimiter}
|
||||||
/>
|
/>
|
||||||
</label>
|
<TextSetting
|
||||||
<label className='radio-inline'>
|
id='memoryStoreSize'
|
||||||
<input
|
label={
|
||||||
type='radio'
|
<FormattedMessage
|
||||||
name='EnableRateLimiter'
|
id='admin.rate.memoryTitle'
|
||||||
value='false'
|
defaultMessage='Memory Store Size:'
|
||||||
defaultChecked={!this.props.config.RateLimitSettings.EnableRateLimiter}
|
/>
|
||||||
onChange={this.handleChange.bind(this, 'EnableRateLimiterFalse')}
|
}
|
||||||
/>
|
placeholder={Utils.localizeMessage('admin.rate.memoryExample', 'Ex "10000"')}
|
||||||
<FormattedMessage
|
helpText={
|
||||||
id='admin.rate.false'
|
<FormattedMessage
|
||||||
defaultMessage='false'
|
id='admin.rate.memoryDescription'
|
||||||
/>
|
defaultMessage='Maximum number of users sessions connected to the system as determined by "Vary By Remote Address" and "Vary By Header" settings below.'
|
||||||
</label>
|
/>
|
||||||
<p className='help-text'>
|
}
|
||||||
<FormattedMessage
|
value={this.state.memoryStoreSize}
|
||||||
id='admin.rate.enableLimiterDescription'
|
onChange={this.handleChange}
|
||||||
defaultMessage='When true, APIs are throttled at rates specified below.'
|
disabled={!this.state.enableRateLimiter}
|
||||||
/>
|
/>
|
||||||
</p>
|
<BooleanSetting
|
||||||
</div>
|
id='varyByRemoteAddr'
|
||||||
</div>
|
label={
|
||||||
|
<FormattedMessage
|
||||||
<div className='form-group'>
|
id='admin.rate.remoteTitle'
|
||||||
<label
|
defaultMessage='Vary By Remote Address: '
|
||||||
className='control-label col-sm-4'
|
/>
|
||||||
htmlFor='PerSec'
|
}
|
||||||
>
|
helpText={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.rate.queriesTitle'
|
id='admin.rate.remoteDescription'
|
||||||
defaultMessage='Number Of Queries Per Second:'
|
defaultMessage='When true, rate limit API access by IP address.'
|
||||||
/>
|
/>
|
||||||
</label>
|
}
|
||||||
<div className='col-sm-8'>
|
value={this.state.varyByRemoteAddr}
|
||||||
<input
|
onChange={this.handleChange}
|
||||||
type='text'
|
disabled={!this.state.enableRateLimiter}
|
||||||
className='form-control'
|
/>
|
||||||
id='PerSec'
|
<TextSetting
|
||||||
ref='PerSec'
|
id='varyByHeader'
|
||||||
placeholder={formatMessage(holders.queriesExample)}
|
label={
|
||||||
defaultValue={this.props.config.RateLimitSettings.PerSec}
|
<FormattedMessage
|
||||||
onChange={this.handleChange}
|
id='admin.rate.httpHeaderTitle'
|
||||||
disabled={!this.state.EnableRateLimiter}
|
defaultMessage='Vary By HTTP Header:'
|
||||||
/>
|
/>
|
||||||
<p className='help-text'>
|
}
|
||||||
<FormattedMessage
|
placeholder={Utils.localizeMessage('admin.rate.httpHeaderExample', 'Ex "X-Real-IP", "X-Forwarded-For"')}
|
||||||
id='admin.rate.queriesDescription'
|
helpText={
|
||||||
defaultMessage='Throttles API at this number of requests per second.'
|
<FormattedMessage
|
||||||
/>
|
id='admin.rate.httpHeaderDescription'
|
||||||
</p>
|
defaultMessage='When filled in, vary rate limiting by HTTP header field specified (e.g. when configuring NGINX set to "X-Real-IP", when configuring AmazonELB set to "X-Forwarded-For").'
|
||||||
</div>
|
/>
|
||||||
</div>
|
}
|
||||||
|
value={this.state.varyByHeader}
|
||||||
<div className='form-group'>
|
onChange={this.handleChange}
|
||||||
<label
|
disabled={!this.state.enableRateLimiter || this.state.varyByRemoteAddr}
|
||||||
className='control-label col-sm-4'
|
/>
|
||||||
htmlFor='MemoryStoreSize'
|
</SettingsGroup>
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.rate.memoryTitle'
|
|
||||||
defaultMessage='Memory Store Size:'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div className='col-sm-8'>
|
|
||||||
<input
|
|
||||||
type='text'
|
|
||||||
className='form-control'
|
|
||||||
id='MemoryStoreSize'
|
|
||||||
ref='MemoryStoreSize'
|
|
||||||
placeholder={formatMessage(holders.memoryExample)}
|
|
||||||
defaultValue={this.props.config.RateLimitSettings.MemoryStoreSize}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
disabled={!this.state.EnableRateLimiter}
|
|
||||||
/>
|
|
||||||
<p className='help-text'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.rate.memoryDescription'
|
|
||||||
defaultMessage='Maximum number of users sessions connected to the system as determined by "Vary By Remote Address" and "Vary By Header" settings below.'
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='form-group'>
|
|
||||||
<label
|
|
||||||
className='control-label col-sm-4'
|
|
||||||
htmlFor='VaryByRemoteAddr'
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.rate.remoteTitle'
|
|
||||||
defaultMessage='Vary By Remote Address: '
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div className='col-sm-8'>
|
|
||||||
<label className='radio-inline'>
|
|
||||||
<input
|
|
||||||
type='radio'
|
|
||||||
name='VaryByRemoteAddr'
|
|
||||||
value='true'
|
|
||||||
ref='VaryByRemoteAddr'
|
|
||||||
defaultChecked={this.props.config.RateLimitSettings.VaryByRemoteAddr}
|
|
||||||
onChange={this.handleChange.bind(this, 'VaryByRemoteAddrTrue')}
|
|
||||||
disabled={!this.state.EnableRateLimiter}
|
|
||||||
/>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.rate.true'
|
|
||||||
defaultMessage='true'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<label className='radio-inline'>
|
|
||||||
<input
|
|
||||||
type='radio'
|
|
||||||
name='VaryByRemoteAddr'
|
|
||||||
value='false'
|
|
||||||
defaultChecked={!this.props.config.RateLimitSettings.VaryByRemoteAddr}
|
|
||||||
onChange={this.handleChange.bind(this, 'VaryByRemoteAddrFalse')}
|
|
||||||
disabled={!this.state.EnableRateLimiter}
|
|
||||||
/>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.rate.false'
|
|
||||||
defaultMessage='false'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<p className='help-text'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.rate.remoteDescription'
|
|
||||||
defaultMessage='When true, rate limit API access by IP address.'
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='form-group'>
|
|
||||||
<label
|
|
||||||
className='control-label col-sm-4'
|
|
||||||
htmlFor='VaryByHeader'
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.rate.httpHeaderTitle'
|
|
||||||
defaultMessage='Vary By HTTP Header:'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div className='col-sm-8'>
|
|
||||||
<input
|
|
||||||
type='text'
|
|
||||||
className='form-control'
|
|
||||||
id='VaryByHeader'
|
|
||||||
ref='VaryByHeader'
|
|
||||||
placeholder={formatMessage(holders.httpHeaderExample)}
|
|
||||||
defaultValue={this.props.config.RateLimitSettings.VaryByHeader}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
disabled={!this.state.EnableRateLimiter || this.state.VaryByRemoteAddr}
|
|
||||||
/>
|
|
||||||
<p className='help-text'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.rate.httpHeaderDescription'
|
|
||||||
defaultMessage='When filled in, vary rate limiting by HTTP header field specified (e.g. when configuring NGINX set to "X-Real-IP", when configuring AmazonELB set to "X-Forwarded-For").'
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='form-group'>
|
|
||||||
<div className='col-sm-12'>
|
|
||||||
{serverError}
|
|
||||||
<button
|
|
||||||
disabled={!this.state.saveNeeded}
|
|
||||||
type='submit'
|
|
||||||
className={saveClass}
|
|
||||||
onClick={this.handleSubmit}
|
|
||||||
id='save-button'
|
|
||||||
data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + formatMessage(holders.saving)}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='admin.rate.save'
|
|
||||||
defaultMessage='Save'
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RateSettings.propTypes = {
|
|
||||||
intl: intlShape.isRequired,
|
|
||||||
config: React.PropTypes.object
|
|
||||||
};
|
|
||||||
|
|
||||||
export default injectIntl(RateSettings);
|
|
||||||
|
|||||||
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 {
|
export default class Setting extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
let marginClass = '';
|
|
||||||
if (this.props.margin === 'small') {
|
|
||||||
marginClass = ' form-group--small';
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'form-group' + marginClass}>
|
<div className='form-group'>
|
||||||
<label
|
<label
|
||||||
className='control-label col-sm-4'
|
className='control-label col-sm-4'
|
||||||
|
htmlFor={this.props.inputId}
|
||||||
>
|
>
|
||||||
{this.props.label}
|
{this.props.label}
|
||||||
</label>
|
</label>
|
||||||
<div className='col-sm-8'>
|
<div className='col-sm-8'>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
|
<div className='help-text'>
|
||||||
|
{this.props.helpText}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -28,7 +27,8 @@ Setting.defaultProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Setting.propTypes = {
|
Setting.propTypes = {
|
||||||
|
inputId: React.PropTypes.string,
|
||||||
label: React.PropTypes.node.isRequired,
|
label: React.PropTypes.node.isRequired,
|
||||||
children: React.PropTypes.node.isRequired,
|
children: React.PropTypes.node.isRequired,
|
||||||
margin: React.PropTypes.oneOf(['', 'small'])
|
helpText: React.PropTypes.node
|
||||||
};
|
};
|
||||||
|
|||||||
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.
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
import AdminStore from 'stores/admin_store.jsx';
|
||||||
import Client from 'utils/web_client.jsx';
|
import Client from 'utils/web_client.jsx';
|
||||||
|
import FormError from 'components/form_error.jsx';
|
||||||
import LoadingScreen from '../loading_screen.jsx';
|
import LoadingScreen from '../loading_screen.jsx';
|
||||||
import UserItem from './user_item.jsx';
|
import UserItem from './user_item.jsx';
|
||||||
import ResetPasswordModal from './reset_password_modal.jsx';
|
import ResetPasswordModal from './reset_password_modal.jsx';
|
||||||
@@ -11,9 +13,17 @@ import {FormattedMessage} from 'react-intl';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
export default class UserList extends React.Component {
|
export default class UserList extends React.Component {
|
||||||
|
static get propTypes() {
|
||||||
|
return {
|
||||||
|
params: React.PropTypes.object.isRequired
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
this.onAllTeamsChange = this.onAllTeamsChange.bind(this);
|
||||||
|
|
||||||
this.getTeamProfiles = this.getTeamProfiles.bind(this);
|
this.getTeamProfiles = this.getTeamProfiles.bind(this);
|
||||||
this.getCurrentTeamProfiles = this.getCurrentTeamProfiles.bind(this);
|
this.getCurrentTeamProfiles = this.getCurrentTeamProfiles.bind(this);
|
||||||
this.doPasswordReset = this.doPasswordReset.bind(this);
|
this.doPasswordReset = this.doPasswordReset.bind(this);
|
||||||
@@ -22,7 +32,7 @@ export default class UserList extends React.Component {
|
|||||||
this.getTeamMemberForUser = this.getTeamMemberForUser.bind(this);
|
this.getTeamMemberForUser = this.getTeamMemberForUser.bind(this);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
teamId: props.team.id,
|
team: AdminStore.getTeam(this.props.params.team),
|
||||||
users: null,
|
users: null,
|
||||||
teamMembers: null,
|
teamMembers: null,
|
||||||
serverError: null,
|
serverError: null,
|
||||||
@@ -35,8 +45,14 @@ export default class UserList extends React.Component {
|
|||||||
this.getCurrentTeamProfiles();
|
this.getCurrentTeamProfiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onAllTeamsChange() {
|
||||||
|
this.setState({
|
||||||
|
team: AdminStore.getTeam(this.props.params.team)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
getCurrentTeamProfiles() {
|
getCurrentTeamProfiles() {
|
||||||
this.getTeamProfiles(this.props.team.id);
|
this.getTeamProfiles(this.props.params.team);
|
||||||
}
|
}
|
||||||
|
|
||||||
getTeamProfiles(teamId) {
|
getTeamProfiles(teamId) {
|
||||||
@@ -133,9 +149,8 @@ export default class UserList extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
var serverError = '';
|
if (!this.state.team) {
|
||||||
if (this.state.serverError) {
|
return null;
|
||||||
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.users == null || this.state.teamMembers == null) {
|
if (this.state.users == null || this.state.teamMembers == null) {
|
||||||
@@ -146,11 +161,11 @@ export default class UserList extends React.Component {
|
|||||||
id='admin.userList.title'
|
id='admin.userList.title'
|
||||||
defaultMessage='Users for {team}'
|
defaultMessage='Users for {team}'
|
||||||
values={{
|
values={{
|
||||||
team: this.props.team.name
|
team: this.state.team.name
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</h3>
|
</h3>
|
||||||
{serverError}
|
<FormError error={this.state.serverError}/>
|
||||||
<LoadingScreen/>
|
<LoadingScreen/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -161,7 +176,7 @@ export default class UserList extends React.Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<UserItem
|
<UserItem
|
||||||
team={this.props.team}
|
team={this.state.team}
|
||||||
key={'user_' + user.id}
|
key={'user_' + user.id}
|
||||||
user={user}
|
user={user}
|
||||||
teamMember={teamMember}
|
teamMember={teamMember}
|
||||||
@@ -177,12 +192,12 @@ export default class UserList extends React.Component {
|
|||||||
id='admin.userList.title2'
|
id='admin.userList.title2'
|
||||||
defaultMessage='Users for {team} ({count})'
|
defaultMessage='Users for {team} ({count})'
|
||||||
values={{
|
values={{
|
||||||
team: this.props.team.name,
|
team: this.state.team.name,
|
||||||
count: this.state.users.length
|
count: this.state.users.length
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</h3>
|
</h3>
|
||||||
{serverError}
|
<FormError error={this.state.serverError}/>
|
||||||
<form
|
<form
|
||||||
className='form-horizontal'
|
className='form-horizontal'
|
||||||
role='form'
|
role='form'
|
||||||
@@ -194,7 +209,7 @@ export default class UserList extends React.Component {
|
|||||||
<ResetPasswordModal
|
<ResetPasswordModal
|
||||||
user={this.state.user}
|
user={this.state.user}
|
||||||
show={this.state.showPasswordModal}
|
show={this.state.showPasswordModal}
|
||||||
team={this.props.team}
|
team={this.state.team}
|
||||||
onModalSubmit={this.doPasswordResetSubmit}
|
onModalSubmit={this.doPasswordResetSubmit}
|
||||||
onModalDismissed={this.doPasswordResetDismiss}
|
onModalDismissed={this.doPasswordResetDismiss}
|
||||||
/>
|
/>
|
||||||
@@ -202,7 +217,3 @@ export default class UserList extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UserList.propTypes = {
|
|
||||||
team: React.PropTypes.object
|
|
||||||
};
|
|
||||||
|
|||||||
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 = {
|
SystemAnalytics.propTypes = {
|
||||||
intl: intlShape.isRequired,
|
intl: intlShape.isRequired
|
||||||
team: React.PropTypes.object
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default injectIntl(SystemAnalytics);
|
export default injectIntl(SystemAnalytics);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import LineChart from './line_chart.jsx';
|
|||||||
import StatisticCount from './statistic_count.jsx';
|
import StatisticCount from './statistic_count.jsx';
|
||||||
import TableChart from './table_chart.jsx';
|
import TableChart from './table_chart.jsx';
|
||||||
|
|
||||||
|
import AdminStore from 'stores/admin_store.jsx';
|
||||||
import AnalyticsStore from 'stores/analytics_store.jsx';
|
import AnalyticsStore from 'stores/analytics_store.jsx';
|
||||||
|
|
||||||
import * as Utils from 'utils/utils.jsx';
|
import * as Utils from 'utils/utils.jsx';
|
||||||
@@ -13,23 +14,34 @@ import Constants from 'utils/constants.jsx';
|
|||||||
const StatTypes = Constants.StatTypes;
|
const StatTypes = Constants.StatTypes;
|
||||||
|
|
||||||
import {formatPostsPerDayData, formatUsersWithPostsPerDayData} from './system_analytics.jsx';
|
import {formatPostsPerDayData, formatUsersWithPostsPerDayData} from './system_analytics.jsx';
|
||||||
import {injectIntl, intlShape, FormattedMessage, FormattedDate} from 'react-intl';
|
import {FormattedMessage, FormattedDate} from 'react-intl';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
class TeamAnalytics extends React.Component {
|
export default class TeamAnalytics extends React.Component {
|
||||||
|
static get propTypes() {
|
||||||
|
return {
|
||||||
|
params: React.PropTypes.object.isRequired
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.onChange = this.onChange.bind(this);
|
this.onChange = this.onChange.bind(this);
|
||||||
|
this.onAllTeamsChange = this.onAllTeamsChange.bind(this);
|
||||||
|
|
||||||
this.state = {stats: AnalyticsStore.getAllTeam(this.props.team.id)};
|
this.state = {
|
||||||
|
team: AdminStore.getTeam(this.props.params.team),
|
||||||
|
stats: AnalyticsStore.getAllTeam(this.props.params.team)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
AnalyticsStore.addChangeListener(this.onChange);
|
AnalyticsStore.addChangeListener(this.onChange);
|
||||||
|
AdminStore.addAllTeamsChangeListener(this.onAllTeamsChange);
|
||||||
|
|
||||||
this.getData(this.props.team.id);
|
this.getData(this.props.params.team);
|
||||||
}
|
}
|
||||||
|
|
||||||
getData(id) {
|
getData(id) {
|
||||||
@@ -41,11 +53,14 @@ class TeamAnalytics extends React.Component {
|
|||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
AnalyticsStore.removeChangeListener(this.onChange);
|
AnalyticsStore.removeChangeListener(this.onChange);
|
||||||
|
AdminStore.removeAllTeamsChangeListener(this.onAllTeamsChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
this.getData(nextProps.team.id);
|
this.getData(nextProps.params.team);
|
||||||
this.setState({stats: AnalyticsStore.getAllTeam(nextProps.team.id)});
|
this.setState({
|
||||||
|
stats: AnalyticsStore.getAllTeam(nextProps.params.team)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
@@ -53,7 +68,7 @@ class TeamAnalytics extends React.Component {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Utils.areObjectsEqual(nextProps.team, this.props.team)) {
|
if (!Utils.areObjectsEqual(nextProps.params.team, this.props.params.team)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,10 +76,22 @@ class TeamAnalytics extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onChange() {
|
onChange() {
|
||||||
this.setState({stats: AnalyticsStore.getAllTeam(this.props.team.id)});
|
this.setState({
|
||||||
|
stats: AnalyticsStore.getAllTeam(this.props.params.team)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onAllTeamsChange() {
|
||||||
|
this.setState({
|
||||||
|
team: AdminStore.getTeam(this.props.params.team)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
if (!this.state.team || !this.state.stats) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const stats = this.state.stats;
|
const stats = this.state.stats;
|
||||||
const postCountsDay = formatPostsPerDayData(stats[StatTypes.POST_PER_DAY]);
|
const postCountsDay = formatPostsPerDayData(stats[StatTypes.POST_PER_DAY]);
|
||||||
const userCountsWithPostsDay = formatUsersWithPostsPerDayData(stats[StatTypes.USERS_WITH_POSTS_PER_DAY]);
|
const userCountsWithPostsDay = formatUsersWithPostsPerDayData(stats[StatTypes.USERS_WITH_POSTS_PER_DAY]);
|
||||||
@@ -78,7 +105,7 @@ class TeamAnalytics extends React.Component {
|
|||||||
id='analytics.team.title'
|
id='analytics.team.title'
|
||||||
defaultMessage='Team Statistics for {team}'
|
defaultMessage='Team Statistics for {team}'
|
||||||
values={{
|
values={{
|
||||||
team: this.props.team.name
|
team: this.state.team.name
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</h3>
|
</h3>
|
||||||
@@ -175,13 +202,6 @@ class TeamAnalytics extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TeamAnalytics.propTypes = {
|
|
||||||
intl: intlShape.isRequired,
|
|
||||||
team: React.PropTypes.object.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default injectIntl(TeamAnalytics);
|
|
||||||
|
|
||||||
export function formatRecentUsersData(data) {
|
export function formatRecentUsersData(data) {
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
return [];
|
return [];
|
||||||
|
|||||||
@@ -436,29 +436,46 @@
|
|||||||
"admin.service.webhooksTitle": "Enable Incoming Webhooks: ",
|
"admin.service.webhooksTitle": "Enable Incoming Webhooks: ",
|
||||||
"admin.sidebar.addTeamSidebar": "Add team from sidebar menu",
|
"admin.sidebar.addTeamSidebar": "Add team from sidebar menu",
|
||||||
"admin.sidebar.audits": "Compliance and Auditing",
|
"admin.sidebar.audits": "Compliance and Auditing",
|
||||||
"admin.sidebar.compliance": "Compliance Settings",
|
"admin.sidebar.authentication": "Authentication",
|
||||||
"admin.sidebar.email": "Email Settings",
|
"admin.sidebar.compliance": "Compliance",
|
||||||
"admin.sidebar.file": "File Settings",
|
"admin.sidebar.configuration": "Configuration",
|
||||||
"admin.sidebar.gitlab": "GitLab Settings",
|
"admin.sidebar.connections": "Connections",
|
||||||
"admin.sidebar.ldap": "LDAP Settings",
|
"admin.sidebar.customBrand": "Custom Branding",
|
||||||
|
"admin.sidebar.customization": "Customization",
|
||||||
|
"admin.sidebar.database": "Database",
|
||||||
|
"admin.sidebar.developer": "Developer",
|
||||||
|
"admin.sidebar.email": "Email",
|
||||||
|
"admin.sidebar.external": "External Services",
|
||||||
|
"admin.sidebar.files": "Files",
|
||||||
|
"admin.sidebar.general": "General",
|
||||||
|
"admin.sidebar.gitlab": "GitLab",
|
||||||
|
"admin.sidebar.integrations": "Integrations",
|
||||||
|
"admin.sidebar.images": "Images",
|
||||||
|
"admin.sidebar.ldap": "LDAP",
|
||||||
"admin.sidebar.license": "Edition and License",
|
"admin.sidebar.license": "Edition and License",
|
||||||
"admin.sidebar.loading": "Loading",
|
"admin.sidebar.logging": "Logging",
|
||||||
"admin.sidebar.log": "Log Settings",
|
"admin.sidebar.login": "Login",
|
||||||
"admin.sidebar.logs": "Logs",
|
"admin.sidebar.logs": "Logs",
|
||||||
|
"admin.sidebar.notifications": "Notifications",
|
||||||
"admin.sidebar.other": "OTHER",
|
"admin.sidebar.other": "OTHER",
|
||||||
"admin.sidebar.privacy": "Privacy Settings",
|
"admin.sidebar.privacy": "Privacy",
|
||||||
"admin.sidebar.rate_limit": "Rate Limit Settings",
|
"admin.sidebar.publicLinks": "Public Links",
|
||||||
|
"admin.sidebar.push": "Mobile Push",
|
||||||
|
"admin.sidebar.rateLimiting": "Rate Limiting",
|
||||||
"admin.sidebar.reports": "SITE REPORTS",
|
"admin.sidebar.reports": "SITE REPORTS",
|
||||||
"admin.sidebar.rmTeamSidebar": "Remove team from sidebar menu",
|
"admin.sidebar.rmTeamSidebar": "Remove team from sidebar menu",
|
||||||
"admin.sidebar.service": "Service Settings",
|
"admin.sidebar.security": "Security",
|
||||||
|
"admin.sidebar.sign_up": "Sign Up",
|
||||||
|
"admin.sidebar.sessions": "Sessions",
|
||||||
"admin.sidebar.settings": "SETTINGS",
|
"admin.sidebar.settings": "SETTINGS",
|
||||||
"admin.sidebar.sql": "SQL Settings",
|
"admin.sidebar.statistics": "Statistics",
|
||||||
"admin.sidebar.statistics": "- Statistics",
|
"admin.sidebar.storage": "Storage",
|
||||||
"admin.sidebar.support": "Legal and Support Settings",
|
"admin.sidebar.support": "Legal and Support",
|
||||||
"admin.sidebar.team": "Team Settings",
|
"admin.sidebar.teams": "TEAMS ({count, number})",
|
||||||
"admin.sidebar.teams": "TEAMS ({count})",
|
"admin.sidebar.users": "Users",
|
||||||
"admin.sidebar.users": "- Users",
|
"admin.sidebar.usersAndTeams": "Users and Teams",
|
||||||
"admin.sidebar.view_statistics": "View Statistics",
|
"admin.sidebar.view_statistics": "View Statistics",
|
||||||
|
"admin.sidebar.webhooks": "Webhooks and Commands",
|
||||||
"admin.sidebarHeader.systemConsole": "System Console",
|
"admin.sidebarHeader.systemConsole": "System Console",
|
||||||
"admin.sql.dataSource": "Data Source:",
|
"admin.sql.dataSource": "Data Source:",
|
||||||
"admin.sql.driverName": "Driver Name:",
|
"admin.sql.driverName": "Driver Name:",
|
||||||
|
|||||||
@@ -435,30 +435,25 @@
|
|||||||
"admin.service.webhooksDescription": "Cuando es verdadero, la entradas de webhooks será permitida. Para ayudar a combatir ataques phishing, todos los comentarios de webhooks serán marcados con una etiqueta BOT.",
|
"admin.service.webhooksDescription": "Cuando es verdadero, la entradas de webhooks será permitida. Para ayudar a combatir ataques phishing, todos los comentarios de webhooks serán marcados con una etiqueta BOT.",
|
||||||
"admin.service.webhooksTitle": "Habilitar Webhooks de Entrada: ",
|
"admin.service.webhooksTitle": "Habilitar Webhooks de Entrada: ",
|
||||||
"admin.sidebar.addTeamSidebar": "Agregar un equipo el menú lateral",
|
"admin.sidebar.addTeamSidebar": "Agregar un equipo el menú lateral",
|
||||||
"admin.sidebar.audits": "Auditorías",
|
"admin.sidebar.gitlab": "GitLab",
|
||||||
"admin.sidebar.compliance": "Configuración de Cumplimiento",
|
"admin.sidebar.ldap": "LDAP",
|
||||||
"admin.sidebar.email": "Configuración de correo",
|
|
||||||
"admin.sidebar.file": "Configuracion de archivos",
|
|
||||||
"admin.sidebar.gitlab": "Configuración de GitLab",
|
|
||||||
"admin.sidebar.ldap": "Configuración LDAP",
|
|
||||||
"admin.sidebar.license": "Edición y Licencia",
|
"admin.sidebar.license": "Edición y Licencia",
|
||||||
"admin.sidebar.loading": "Cargando",
|
|
||||||
"admin.sidebar.log": "Configuracion de log",
|
|
||||||
"admin.sidebar.logs": "Registros",
|
"admin.sidebar.logs": "Registros",
|
||||||
"admin.sidebar.other": "OTROS",
|
"admin.sidebar.other": "OTROS",
|
||||||
"admin.sidebar.privacy": "Configuración de privacidad",
|
|
||||||
"admin.sidebar.rate_limit": "Configuración de velocidad",
|
|
||||||
"admin.sidebar.reports": "REPORTES DEL SITIO",
|
"admin.sidebar.reports": "REPORTES DEL SITIO",
|
||||||
"admin.sidebar.rmTeamSidebar": "Remover un equipo del menú lateral",
|
"admin.sidebar.rmTeamSidebar": "Remover un equipo del menú lateral",
|
||||||
"admin.sidebar.service": "Configuración de servicio",
|
|
||||||
"admin.sidebar.settings": "CONFIGURACIONES",
|
"admin.sidebar.settings": "CONFIGURACIONES",
|
||||||
"admin.sidebar.sql": "Configuración de SQL",
|
"admin.sidebar.statistics": "Estadísticas",
|
||||||
"admin.sidebar.statistics": "- Estadísticas",
|
"admin.sidebar.teams": "EQUIPOS ({count, number})",
|
||||||
"admin.sidebar.support": "Configuración de Soporte",
|
"admin.sidebar.users": "Usuarios",
|
||||||
"admin.sidebar.team": "Configuración de equipo",
|
|
||||||
"admin.sidebar.teams": "EQUIPOS ({count})",
|
|
||||||
"admin.sidebar.users": "- Usuarios",
|
|
||||||
"admin.sidebar.view_statistics": "Ver Estadísticas",
|
"admin.sidebar.view_statistics": "Ver Estadísticas",
|
||||||
|
"admin.sidebar.file": "Configuracion de archivos",
|
||||||
|
"admin.sidebar.loading": "Cargando",
|
||||||
|
"admin.sidebar.log": "Configuracion de log",
|
||||||
|
"admin.sidebar.rate_limit": "Configuración de velocidad",
|
||||||
|
"admin.sidebar.service": "Configuración de servicio",
|
||||||
|
"admin.sidebar.sql": "Configuración de SQL",
|
||||||
|
"admin.sidebar.team": "Configuración de equipo",
|
||||||
"admin.sidebarHeader.systemConsole": "Consola de sistema",
|
"admin.sidebarHeader.systemConsole": "Consola de sistema",
|
||||||
"admin.sql.dataSource": "Origen de datos:",
|
"admin.sql.dataSource": "Origen de datos:",
|
||||||
"admin.sql.driverName": "Nombre de controlador:",
|
"admin.sql.driverName": "Nombre de controlador:",
|
||||||
|
|||||||
@@ -370,29 +370,27 @@
|
|||||||
"admin.service.webhooksTitle": "Activer les webhooks entrants :",
|
"admin.service.webhooksTitle": "Activer les webhooks entrants :",
|
||||||
"admin.sidebar.addTeamSidebar": "Afficher l'équipe dans le menu",
|
"admin.sidebar.addTeamSidebar": "Afficher l'équipe dans le menu",
|
||||||
"admin.sidebar.audits": "Conformité et vérification",
|
"admin.sidebar.audits": "Conformité et vérification",
|
||||||
"admin.sidebar.compliance": "Paramètres de conformité",
|
"admin.sidebar.gitlab": "GitLab",
|
||||||
"admin.sidebar.email": "Configuration messagerie",
|
|
||||||
"admin.sidebar.file": "Configuration du fichier",
|
|
||||||
"admin.sidebar.gitlab": "Configuration de GitLab",
|
|
||||||
"admin.sidebar.ldap": "Paramètres LDAP",
|
|
||||||
"admin.sidebar.license": "Édition et licence",
|
|
||||||
"admin.sidebar.loading": "Chargement",
|
|
||||||
"admin.sidebar.log": "Configuration du journal (log)",
|
|
||||||
"admin.sidebar.logs": "Journaux",
|
|
||||||
"admin.sidebar.other": "AUTRES",
|
"admin.sidebar.other": "AUTRES",
|
||||||
"admin.sidebar.privacy": "Paramètres de confidentialité",
|
"admin.sidebar.ldap": "LDAP",
|
||||||
"admin.sidebar.rate_limit": "Configuration des limites de débits",
|
"admin.sidebar.license": "Édition et licence",
|
||||||
|
"admin.sidebar.logs": "Journaux",
|
||||||
"admin.sidebar.reports": "RAPPORTS DU SITE",
|
"admin.sidebar.reports": "RAPPORTS DU SITE",
|
||||||
"admin.sidebar.rmTeamSidebar": "Ne plus afficher l'équipe dans le menu",
|
"admin.sidebar.rmTeamSidebar": "Ne plus afficher l'équipe dans le menu",
|
||||||
|
"admin.sidebar.statistics": "Statistiques",
|
||||||
|
"admin.sidebar.teams": "ÉQUIPES ({count, number})",
|
||||||
|
"admin.sidebar.users": "Utilisateurs",
|
||||||
|
"admin.sidebar.view_statistics": "Voir les statistiques",
|
||||||
|
"admin.sidebar.file": "Configuration du fichier",
|
||||||
|
"admin.sidebar.ldap": "LDAP",
|
||||||
|
"admin.sidebar.loading": "Chargement",
|
||||||
|
"admin.sidebar.log": "Configuration du journal (log)",
|
||||||
|
"admin.sidebar.rate_limit": "Configuration des limites de débits",
|
||||||
"admin.sidebar.service": "Configuration du service",
|
"admin.sidebar.service": "Configuration du service",
|
||||||
"admin.sidebar.settings": "REGLAGES",
|
"admin.sidebar.settings": "REGLAGES",
|
||||||
"admin.sidebar.sql": "Configuration SQL",
|
"admin.sidebar.sql": "Configuration SQL",
|
||||||
"admin.sidebar.statistics": "- Statistiques",
|
|
||||||
"admin.sidebar.support": "Paramètres légaux et support",
|
"admin.sidebar.support": "Paramètres légaux et support",
|
||||||
"admin.sidebar.team": "Configuration de l'équipe",
|
"admin.sidebar.team": "Configuration de l'équipe",
|
||||||
"admin.sidebar.teams": "ÉQUIPES ({count})",
|
|
||||||
"admin.sidebar.users": "- Utilisateurs",
|
|
||||||
"admin.sidebar.view_statistics": "Voir les statistiques",
|
|
||||||
"admin.sidebarHeader.systemConsole": "Console système",
|
"admin.sidebarHeader.systemConsole": "Console système",
|
||||||
"admin.sql.dataSource": "Source de données :",
|
"admin.sql.dataSource": "Source de données :",
|
||||||
"admin.sql.driverName": "Nom du pilote :",
|
"admin.sql.driverName": "Nom du pilote :",
|
||||||
|
|||||||
@@ -436,28 +436,16 @@
|
|||||||
"admin.service.webhooksTitle": "Ativar Webhooks Entrada: ",
|
"admin.service.webhooksTitle": "Ativar Webhooks Entrada: ",
|
||||||
"admin.sidebar.addTeamSidebar": "Adicionar equipe do menu lateral",
|
"admin.sidebar.addTeamSidebar": "Adicionar equipe do menu lateral",
|
||||||
"admin.sidebar.audits": "Conformidade e Auditoria",
|
"admin.sidebar.audits": "Conformidade e Auditoria",
|
||||||
"admin.sidebar.compliance": "Configurações Compliance",
|
"admin.sidebar.gitlab": "GitLab",
|
||||||
"admin.sidebar.email": "Configuração do e-mail",
|
|
||||||
"admin.sidebar.file": "Configurações do arquivo",
|
|
||||||
"admin.sidebar.gitlab": "Configurações GitLab",
|
|
||||||
"admin.sidebar.ldap": "Configurações LDAP",
|
|
||||||
"admin.sidebar.license": "Edição e Licença",
|
|
||||||
"admin.sidebar.loading": "Carregando",
|
|
||||||
"admin.sidebar.log": "Configurações de Log",
|
|
||||||
"admin.sidebar.logs": "Logs",
|
|
||||||
"admin.sidebar.other": "OUTROS",
|
"admin.sidebar.other": "OUTROS",
|
||||||
"admin.sidebar.privacy": "Configurações De Privacidade",
|
"admin.sidebar.ldap": "LDAP",
|
||||||
"admin.sidebar.rate_limit": "Configurações Rate Limit",
|
"admin.sidebar.license": "Edição e Licença",
|
||||||
|
"admin.sidebar.logs": "Logs",
|
||||||
"admin.sidebar.reports": "RELATÓRIOS DO SITE",
|
"admin.sidebar.reports": "RELATÓRIOS DO SITE",
|
||||||
"admin.sidebar.rmTeamSidebar": "Remover equipe do menu lateral",
|
"admin.sidebar.rmTeamSidebar": "Remover equipe do menu lateral",
|
||||||
"admin.sidebar.service": "Configurações do Serviço",
|
"admin.sidebar.statistics": "Estátisticas",
|
||||||
"admin.sidebar.settings": "CONFIGURAÇÕES",
|
"admin.sidebar.teams": "EQUIPES ({count, number})",
|
||||||
"admin.sidebar.sql": "Configurações SQL",
|
"admin.sidebar.users": "Usuários",
|
||||||
"admin.sidebar.statistics": "- Estátisticas",
|
|
||||||
"admin.sidebar.support": "Configurações jurídico e apoio",
|
|
||||||
"admin.sidebar.team": "Configurações De Equipe",
|
|
||||||
"admin.sidebar.teams": "EQUIPES ({count})",
|
|
||||||
"admin.sidebar.users": "- Usuários",
|
|
||||||
"admin.sidebar.view_statistics": "Ver Estatísticas",
|
"admin.sidebar.view_statistics": "Ver Estatísticas",
|
||||||
"admin.sidebarHeader.systemConsole": "Console do Sistema",
|
"admin.sidebarHeader.systemConsole": "Console do Sistema",
|
||||||
"admin.sql.dataSource": "Fonte de Dados:",
|
"admin.sql.dataSource": "Fonte de Dados:",
|
||||||
|
|||||||
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 SignupUserComplete from 'components/signup_user_complete.jsx';
|
||||||
import ShouldVerifyEmail from 'components/should_verify_email.jsx';
|
import ShouldVerifyEmail from 'components/should_verify_email.jsx';
|
||||||
import DoVerifyEmail from 'components/do_verify_email.jsx';
|
import DoVerifyEmail from 'components/do_verify_email.jsx';
|
||||||
import AdminConsole from 'components/admin_console/admin_controller.jsx';
|
|
||||||
import TutorialView from 'components/tutorial/tutorial_view.jsx';
|
import TutorialView from 'components/tutorial/tutorial_view.jsx';
|
||||||
import BackstageNavbar from 'components/backstage/backstage_navbar.jsx';
|
import BackstageNavbar from 'components/backstage/backstage_navbar.jsx';
|
||||||
import BackstageSidebar from 'components/backstage/backstage_sidebar.jsx';
|
import BackstageSidebar from 'components/backstage/backstage_sidebar.jsx';
|
||||||
@@ -51,6 +50,38 @@ import AppDispatcher from './dispatcher/app_dispatcher.jsx';
|
|||||||
import Constants from './utils/constants.jsx';
|
import Constants from './utils/constants.jsx';
|
||||||
const ActionTypes = Constants.ActionTypes;
|
const ActionTypes = Constants.ActionTypes;
|
||||||
|
|
||||||
|
import AdminConsole from 'components/admin_console/admin_console.jsx';
|
||||||
|
import SystemAnalytics from 'components/analytics/system_analytics.jsx';
|
||||||
|
import ConfigurationSettings from 'components/admin_console/configuration_settings.jsx';
|
||||||
|
import UsersAndTeamsSettings from 'components/admin_console/users_and_teams_settings.jsx';
|
||||||
|
import PrivacySettings from 'components/admin_console/privacy_settings.jsx';
|
||||||
|
import LogSettings from 'components/admin_console/log_settings.jsx';
|
||||||
|
import EmailAuthenticationSettings from 'components/admin_console/email_authentication_settings.jsx';
|
||||||
|
import GitLabSettings from 'components/admin_console/gitlab_settings.jsx';
|
||||||
|
import LdapSettings from 'components/admin_console/ldap_settings.jsx';
|
||||||
|
import SignupSettings from 'components/admin_console/signup_settings.jsx';
|
||||||
|
import LoginSettings from 'components/admin_console/login_settings.jsx';
|
||||||
|
import PublicLinkSettings from 'components/admin_console/public_link_settings.jsx';
|
||||||
|
import SessionSettings from 'components/admin_console/session_settings.jsx';
|
||||||
|
import ConnectionSettings from 'components/admin_console/connection_settings.jsx';
|
||||||
|
import EmailSettings from 'components/admin_console/email_settings.jsx';
|
||||||
|
import PushSettings from 'components/admin_console/push_settings.jsx';
|
||||||
|
import WebhookSettings from 'components/admin_console/webhook_settings.jsx';
|
||||||
|
import ExternalServiceSettings from 'components/admin_console/external_service_settings.jsx';
|
||||||
|
import DatabaseSettings from 'components/admin_console/database_settings.jsx';
|
||||||
|
import StorageSettings from 'components/admin_console/storage_settings.jsx';
|
||||||
|
import ImageSettings from 'components/admin_console/image_settings.jsx';
|
||||||
|
import CustomBrandSettings from 'components/admin_console/custom_brand_settings.jsx';
|
||||||
|
import LegalAndSupportSettings from 'components/admin_console/legal_and_support_settings.jsx';
|
||||||
|
import ComplianceSettings from 'components/admin_console/compliance_settings.jsx';
|
||||||
|
import RateSettings from 'components/admin_console/rate_settings.jsx';
|
||||||
|
import DeveloperSettings from 'components/admin_console/developer_settings.jsx';
|
||||||
|
import TeamUsers from 'components/admin_console/team_users.jsx';
|
||||||
|
import TeamAnalytics from 'components/analytics/team_analytics.jsx';
|
||||||
|
import LicenseSettings from 'components/admin_console/license_settings.jsx';
|
||||||
|
import Audits from 'components/admin_console/audits.jsx';
|
||||||
|
import Logs from 'components/admin_console/logs.jsx';
|
||||||
|
|
||||||
import Claim from 'components/claim/claim.jsx';
|
import Claim from 'components/claim/claim.jsx';
|
||||||
import EmailToOAuth from 'components/claim/components/email_to_oauth.jsx';
|
import EmailToOAuth from 'components/claim/components/email_to_oauth.jsx';
|
||||||
import OAuthToEmail from 'components/claim/components/oauth_to_email.jsx';
|
import OAuthToEmail from 'components/claim/components/oauth_to_email.jsx';
|
||||||
@@ -319,7 +350,161 @@ function renderRootComponent() {
|
|||||||
<Route
|
<Route
|
||||||
path='admin_console'
|
path='admin_console'
|
||||||
component={AdminConsole}
|
component={AdminConsole}
|
||||||
/>
|
>
|
||||||
|
<IndexRedirect to='system_analytics'/>
|
||||||
|
<Route
|
||||||
|
path='system_analytics'
|
||||||
|
component={SystemAnalytics}
|
||||||
|
/>
|
||||||
|
<Route path='general'>
|
||||||
|
<IndexRedirect to='configuration'/>
|
||||||
|
<Route
|
||||||
|
path='configuration'
|
||||||
|
component={ConfigurationSettings}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path='users_and_teams'
|
||||||
|
component={UsersAndTeamsSettings}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path='privacy'
|
||||||
|
component={PrivacySettings}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path='logging'
|
||||||
|
component={LogSettings}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
<Route path='authentication'>
|
||||||
|
<IndexRedirect to='email'/>
|
||||||
|
<Route
|
||||||
|
path='email'
|
||||||
|
component={EmailAuthenticationSettings}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path='gitlab'
|
||||||
|
component={GitLabSettings}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path='ldap'
|
||||||
|
component={LdapSettings}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
<Route path='security'>
|
||||||
|
<IndexRedirect to='sign_up'/>
|
||||||
|
<Route
|
||||||
|
path='sign_up'
|
||||||
|
component={SignupSettings}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path='login'
|
||||||
|
component={LoginSettings}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path='public_links'
|
||||||
|
component={PublicLinkSettings}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path='sessions'
|
||||||
|
component={SessionSettings}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path='connections'
|
||||||
|
component={ConnectionSettings}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
<Route path='notifications'>
|
||||||
|
<IndexRedirect to='email'/>
|
||||||
|
<Route
|
||||||
|
path='email'
|
||||||
|
component={EmailSettings}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path='push'
|
||||||
|
component={PushSettings}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
<Route path='integrations'>
|
||||||
|
<IndexRedirect to='webhooks'/>
|
||||||
|
<Route
|
||||||
|
path='webhooks'
|
||||||
|
component={WebhookSettings}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path='external'
|
||||||
|
component={ExternalServiceSettings}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
<Route
|
||||||
|
path='database'
|
||||||
|
component={DatabaseSettings}
|
||||||
|
/>
|
||||||
|
<Route path='files'>
|
||||||
|
<IndexRedirect to='storage'/>
|
||||||
|
<Route
|
||||||
|
path='storage'
|
||||||
|
component={StorageSettings}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path='images'
|
||||||
|
component={ImageSettings}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
<Route path='customization'>
|
||||||
|
<IndexRedirect to='custom_brand'/>
|
||||||
|
<Route
|
||||||
|
path='custom_brand'
|
||||||
|
component={CustomBrandSettings}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path='legal_and_support'
|
||||||
|
component={LegalAndSupportSettings}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
<Route
|
||||||
|
path='compliance'
|
||||||
|
component={ComplianceSettings}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path='rate'
|
||||||
|
component={RateSettings}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path='developer'
|
||||||
|
component={DeveloperSettings}
|
||||||
|
/>
|
||||||
|
<Route path='team'>
|
||||||
|
<Redirect
|
||||||
|
from=':team'
|
||||||
|
to=':team/users'
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path=':team/users'
|
||||||
|
component={TeamUsers}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path=':team/analytics'
|
||||||
|
component={TeamAnalytics}
|
||||||
|
/>
|
||||||
|
<Redirect
|
||||||
|
from='*'
|
||||||
|
to='/error'
|
||||||
|
query={notFoundParams}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
<Route
|
||||||
|
path='license'
|
||||||
|
component={LicenseSettings}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path='audits'
|
||||||
|
component={Audits}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path='logs'
|
||||||
|
component={Logs}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
<Route
|
<Route
|
||||||
path=':team'
|
path=':team'
|
||||||
component={NeedsTeam}
|
component={NeedsTeam}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
@import 'modal';
|
@import 'modal';
|
||||||
@import 'oauth';
|
@import 'oauth';
|
||||||
@import 'popover';
|
@import 'popover';
|
||||||
|
@import 'save-button';
|
||||||
@import 'scrollbar';
|
@import 'scrollbar';
|
||||||
@import 'search';
|
@import 'search';
|
||||||
@import 'suggestion-list';
|
@import 'suggestion-list';
|
||||||
|
|||||||
7
webapp/sass/components/_save-button.scss
Обычный файл
7
webapp/sass/components/_save-button.scss
Обычный файл
@@ -0,0 +1,7 @@
|
|||||||
|
@charset 'UTF-8';
|
||||||
|
|
||||||
|
.save-button {
|
||||||
|
.glyphicon {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -118,7 +118,8 @@
|
|||||||
|
|
||||||
// Team Header in Sidebar
|
// Team Header in Sidebar
|
||||||
.sidebar--left,
|
.sidebar--left,
|
||||||
.sidebar--menu {
|
.sidebar--menu,
|
||||||
|
.admin-sidebar {
|
||||||
.team__header {
|
.team__header {
|
||||||
@include legacy-pie-clearfix;
|
@include legacy-pie-clearfix;
|
||||||
padding: 9px 10px;
|
padding: 9px 10px;
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
@charset 'UTF-8';
|
@charset 'UTF-8';
|
||||||
|
|
||||||
.admin-controller {
|
.admin-console {
|
||||||
|
color: #333;
|
||||||
|
height: 100%;
|
||||||
|
margin-left: 220px;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 0 20px;
|
||||||
|
|
||||||
> div {
|
> div {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
@@ -34,122 +40,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar--left {
|
|
||||||
&.sidebar--collapsable {
|
|
||||||
background: #333;
|
|
||||||
|
|
||||||
.team__header {
|
|
||||||
background: transparent;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav {
|
|
||||||
li {
|
|
||||||
padding: 0;
|
|
||||||
@include opacity(1);
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
width: 17px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.divider {
|
|
||||||
@include alpha-property(background, $black, .1);
|
|
||||||
}
|
|
||||||
|
|
||||||
> a {
|
|
||||||
&:hover,
|
|
||||||
&:focus {
|
|
||||||
@include alpha-property(background, $black, .1);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> h4 {
|
|
||||||
background: alpha-color($white, .15);
|
|
||||||
color: $white;
|
|
||||||
margin: 1px 0 0;
|
|
||||||
padding: 10px;
|
|
||||||
|
|
||||||
.menu-icon--right {
|
|
||||||
right: 12px;
|
|
||||||
top: 6px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-icon--right {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
height: 20px;
|
|
||||||
line-height: 20px;
|
|
||||||
position: absolute;
|
|
||||||
right: 10px;
|
|
||||||
text-align: center;
|
|
||||||
top: 3px;
|
|
||||||
width: 20px;
|
|
||||||
|
|
||||||
.fa {
|
|
||||||
color: $white;
|
|
||||||
font-size: 13px;
|
|
||||||
position: relative;
|
|
||||||
right: -2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.nav__sub-menu {
|
|
||||||
@include font-smoothing(initial);
|
|
||||||
background: #111;
|
|
||||||
|
|
||||||
&.padded {
|
|
||||||
padding: 5px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
> a {
|
|
||||||
background: transparent;
|
|
||||||
color: #bbb;
|
|
||||||
font-size: 13px;
|
|
||||||
padding: 5px 35px 5px 15px;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: lighten($primary-color, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
color: $white;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-more {
|
|
||||||
background: transparent;
|
|
||||||
color: #bbb;
|
|
||||||
cursor: pointer;
|
|
||||||
display: block;
|
|
||||||
font-size: 13px;
|
|
||||||
padding: 5px 15px;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: lighten($primary-color, 10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.nav__inner-menu {
|
|
||||||
li {
|
|
||||||
> a {
|
|
||||||
padding-left: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.log__panel {
|
.log__panel {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
@@ -160,168 +50,160 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app__content {
|
&.admin {
|
||||||
color: #333;
|
background-color: #f1f1f1;
|
||||||
|
min-height: 600px;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 0 40px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
&.admin {
|
.wrapper--fixed {
|
||||||
background-color: #f1f1f1;
|
max-width: 800px;
|
||||||
min-height: 600px;
|
}
|
||||||
overflow: auto;
|
|
||||||
padding: 0 40px 20px;
|
.form-horizontal {
|
||||||
|
margin-top: 40px;
|
||||||
|
|
||||||
|
.control-label {
|
||||||
|
font-weight: 600;
|
||||||
|
padding-right: 0;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wrapper--fixed {
|
.form-group {
|
||||||
max-width: 800px;
|
margin-bottom: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-horizontal {
|
.file__upload {
|
||||||
margin-top: 40px;
|
display: inline-block;
|
||||||
|
margin: 0 10px 10px 0;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
.control-label {
|
input {
|
||||||
font-weight: 600;
|
@include opacity(0);
|
||||||
padding-right: 0;
|
height: 100%;
|
||||||
text-align: left;
|
left: 0;
|
||||||
}
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
.form-group {
|
width: 100%;
|
||||||
margin-bottom: 25px;
|
z-index: 5;
|
||||||
|
|
||||||
&.form-group--small {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.file__upload {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0 10px 10px 0;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
input {
|
|
||||||
@include opacity(0);
|
|
||||||
height: 100%;
|
|
||||||
left: 0;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
width: 100%;
|
|
||||||
z-index: 5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.help-text {
|
|
||||||
color: #777;
|
|
||||||
margin: 10px 0 0 15px;
|
|
||||||
|
|
||||||
&.no-margin {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.no-margin--top {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul,
|
|
||||||
ol {
|
|
||||||
padding-left: 23px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.help-link {
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 1em 0 0;
|
|
||||||
padding: 5px 7px;
|
|
||||||
position: relative;
|
|
||||||
top: 1px;
|
|
||||||
|
|
||||||
.fa {
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.banner {
|
.help-text {
|
||||||
background: $white;
|
color: #777;
|
||||||
border: 1px solid #ddd;
|
margin: 10px 0 0 15px;
|
||||||
font-size: .95em;
|
|
||||||
margin: 2em 0;
|
|
||||||
padding: .7em 1.5em;
|
|
||||||
|
|
||||||
.banner__heading {
|
&.no-margin {
|
||||||
font-size: 1.5em;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.banner__content {
|
&.no-margin--top {
|
||||||
width: 80%;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.warning {
|
ul,
|
||||||
background: #e60000;
|
ol {
|
||||||
|
padding-left: 23px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-link {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.popover {
|
.alert {
|
||||||
border-radius: 3px;
|
display: inline-block;
|
||||||
font-size: .95em;
|
margin: 1em 0 0;
|
||||||
width: 100%;
|
padding: 5px 7px;
|
||||||
|
position: relative;
|
||||||
|
top: 1px;
|
||||||
|
|
||||||
|
.fa {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner {
|
||||||
|
background: $white;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
font-size: .95em;
|
||||||
|
margin: 2em 0;
|
||||||
|
padding: .7em 1.5em;
|
||||||
|
|
||||||
|
.banner__heading {
|
||||||
|
font-size: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel {
|
.banner__content {
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.warning {
|
||||||
|
background: #e60000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.popover {
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: .95em;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-default {
|
||||||
|
> .panel-heading {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border: none;
|
padding: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-default {
|
.panel-body {
|
||||||
> .panel-heading {
|
padding: 30px 0 10px;
|
||||||
background-color: transparent;
|
|
||||||
padding: 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-body {
|
|
||||||
padding: 30px 0 10px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.panel-group {
|
.panel-group {
|
||||||
margin-bottom: 50px;
|
margin-bottom: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-title {
|
.panel-title {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
@include clearfix;
|
@include clearfix;
|
||||||
display: block;
|
display: block;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
&.collapsed {
|
&.collapsed {
|
||||||
.fa-minus {
|
.fa-minus {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
|
||||||
|
|
||||||
.fa-plus {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.fa {
|
|
||||||
color: #aaa;
|
|
||||||
float: right;
|
|
||||||
font-size: 18px;
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.fa-plus {
|
.fa-plus {
|
||||||
display: none;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fa {
|
||||||
|
color: #aaa;
|
||||||
|
float: right;
|
||||||
|
font-size: 18px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-plus {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,3 +225,108 @@
|
|||||||
margin-bottom: 1.5em;
|
margin-bottom: 1.5em;
|
||||||
max-width: 150px;
|
max-width: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.admin-console__disabled-text {
|
||||||
|
color: #777;
|
||||||
|
margin: 10px 0 0 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-sidebar {
|
||||||
|
background: #333;
|
||||||
|
border-right: 1px solid #ddd;
|
||||||
|
height: 100%;
|
||||||
|
left: 0;
|
||||||
|
position: absolute;
|
||||||
|
width: 220px;
|
||||||
|
z-index: 5;
|
||||||
|
|
||||||
|
.team__header {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-pills__container {
|
||||||
|
background: #111;
|
||||||
|
@include font-smoothing(initial);
|
||||||
|
height: calc(100% - 80px);
|
||||||
|
margin-top: 1px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-category {
|
||||||
|
.category-title {
|
||||||
|
background: alpha-color($white, .15);
|
||||||
|
color: $white;
|
||||||
|
line-height: 15.4px;
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
.category-icon {
|
||||||
|
right: 12px;
|
||||||
|
top: 6px;
|
||||||
|
width: 17px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sections {
|
||||||
|
padding: 5px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-section-title {
|
||||||
|
padding: 5px 35px 5px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-subsection-title {
|
||||||
|
padding: 5px 35px 5px 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-section-title,
|
||||||
|
.sidebar-subsection-title {
|
||||||
|
color: #bbb;
|
||||||
|
display: block;
|
||||||
|
font-size: 13px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: lighten($primary-color, 10);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--active {
|
||||||
|
color: $white;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-icon--right {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
height: 20px;
|
||||||
|
line-height: 20px;
|
||||||
|
position: absolute;
|
||||||
|
right: 12px;
|
||||||
|
text-align: center;
|
||||||
|
top: 8px;
|
||||||
|
width: 20px;
|
||||||
|
|
||||||
|
.fa {
|
||||||
|
color: $white;
|
||||||
|
font-size: 13px;
|
||||||
|
position: relative;
|
||||||
|
right: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.menu__close {
|
||||||
|
cursor: pointer;
|
||||||
|
right: 10px;
|
||||||
|
top: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-connection-test {
|
||||||
|
margin-top: -15px;
|
||||||
|
}
|
||||||
@@ -22,7 +22,7 @@ class AdminStoreClass extends EventEmitter {
|
|||||||
this.logs = null;
|
this.logs = null;
|
||||||
this.audits = null;
|
this.audits = null;
|
||||||
this.config = null;
|
this.config = null;
|
||||||
this.teams = null;
|
this.teams = {};
|
||||||
this.complianceReports = null;
|
this.complianceReports = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,6 +126,10 @@ class AdminStoreClass extends EventEmitter {
|
|||||||
this.teams = teams;
|
this.teams = teams;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTeam(id) {
|
||||||
|
return this.teams[id];
|
||||||
|
}
|
||||||
|
|
||||||
getSelectedTeams() {
|
getSelectedTeams() {
|
||||||
const result = BrowserStore.getItem('seleted_teams');
|
const result = BrowserStore.getItem('seleted_teams');
|
||||||
if (!result) {
|
if (!result) {
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user