diff --git a/webapp/components/admin_console/admin_console.jsx b/webapp/components/admin_console/admin_console.jsx new file mode 100644 index 0000000000..e5c5286144 --- /dev/null +++ b/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
; + } + + // 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 ( +
+ + {children} +
+ ); + } +} diff --git a/webapp/components/admin_console/admin_controller.jsx b/webapp/components/admin_console/admin_controller.jsx deleted file mode 100644 index aea2a0197b..0000000000 --- a/webapp/components/admin_console/admin_controller.jsx +++ /dev/null @@ -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 = ; - - if (this.state.config != null) { - if (this.state.selected === 'email_settings') { - tab = ; - } else if (this.state.selected === 'log_settings') { - tab = ; - } else if (this.state.selected === 'logs') { - tab = ; - } else if (this.state.selected === 'audits') { - tab = ; - } else if (this.state.selected === 'image_settings') { - tab = ; - } else if (this.state.selected === 'privacy_settings') { - tab = ; - } else if (this.state.selected === 'rate_settings') { - tab = ; - } else if (this.state.selected === 'gitlab_settings') { - tab = ; - } else if (this.state.selected === 'sql_settings') { - tab = ; - } else if (this.state.selected === 'team_settings') { - tab = ; - } else if (this.state.selected === 'service_settings') { - tab = ; - } else if (this.state.selected === 'legal_and_support_settings') { - tab = ; - } else if (this.state.selected === 'ldap_settings') { - tab = ; - } else if (this.state.selected === 'compliance_settings') { - tab = ; - } else if (this.state.selected === 'license') { - tab = ; - } else if (this.state.selected === 'team_users') { - if (this.state.teams) { - tab = ; - } - } else if (this.state.selected === 'team_analytics') { - if (this.state.teams) { - tab = ; - } - } else if (this.state.selected === 'system_analytics') { - tab = ; - } - } - - return ( -
- - ); - } -} - -AdminController.defaultProps = { -}; - -AdminController.propTypes = { - tab: React.PropTypes.string, - teamId: React.PropTypes.string -}; diff --git a/webapp/components/admin_console/admin_settings.jsx b/webapp/components/admin_console/admin_settings.jsx new file mode 100644 index 0000000000..d76e1331a8 --- /dev/null +++ b/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 ( +
+ {this.renderTitle()} +
+ {this.renderSettings()} +
+
+ + +
+
+
+
+ ); + } +} diff --git a/webapp/components/admin_console/admin_sidebar.jsx b/webapp/components/admin_console/admin_sidebar.jsx index 4ffc318155..cdb7e29d53 100644 --- a/webapp/components/admin_console/admin_sidebar.jsx +++ b/webapp/components/admin_console/admin_sidebar.jsx @@ -2,69 +2,80 @@ // See License.txt for license information. import $ from 'jquery'; - -import AdminSidebarHeader from './admin_sidebar_header.jsx'; -import SelectTeamModal from './select_team_modal.jsx'; -import * as Utils from 'utils/utils.jsx'; - -import {FormattedMessage} from 'react-intl'; - -import {Tooltip, OverlayTrigger} from 'react-bootstrap'; - import React from 'react'; +import AdminStore from 'stores/admin_store.jsx'; +import * as AsyncClient from 'utils/async_client.jsx'; +import * as Utils from 'utils/utils.jsx'; + +import AdminSidebarHeader from './admin_sidebar_header.jsx'; +import AdminSidebarTeam from './admin_sidebar_team.jsx'; +import {FormattedMessage} from 'react-intl'; +import {browserHistory} from 'react-router'; +import {OverlayTrigger, Tooltip} from 'react-bootstrap'; +import SelectTeamModal from './select_team_modal.jsx'; +import AdminSidebarCategory from './admin_sidebar_category.jsx'; +import AdminSidebarSection from './admin_sidebar_section.jsx'; + export default class AdminSidebar extends React.Component { + static get contextTypes() { + return { + router: React.PropTypes.object.isRequired + }; + } + constructor(props) { super(props); - this.isSelected = this.isSelected.bind(this); - this.handleClick = this.handleClick.bind(this); + this.handleAllTeamsChange = this.handleAllTeamsChange.bind(this); + this.removeTeam = this.removeTeam.bind(this); this.showTeamSelect = this.showTeamSelect.bind(this); this.teamSelectedModal = this.teamSelectedModal.bind(this); this.teamSelectedModalDismissed = this.teamSelectedModalDismissed.bind(this); + this.renderAddTeamButton = this.renderAddTeamButton.bind(this); + this.renderTeams = this.renderTeams.bind(this); + this.state = { + teams: AdminStore.getAllTeams(), + selectedTeams: AdminStore.getSelectedTeams(), showSelectModal: false }; } + componentDidMount() { + AdminStore.addAllTeamsChangeListener(this.handleAllTeamsChange); + AsyncClient.getAllTeams(); + } + componentDidUpdate() { if (!Utils.isMobile()) { - $('.sidebar--left .nav-pills__container').perfectScrollbar(); + $('.admin-sidebar .nav-pills__container').perfectScrollbar(); } } - handleClick(name, teamId, e) { - e.preventDefault(); - this.props.selectTab(name, teamId); + componentWillUnmount() { + AdminStore.removeAllTeamsChangeListener(this.handleAllTeamsChange); } - isSelected(name, teamId) { - if (this.props.selected === name) { - if (name === 'team_users' || name === 'team_analytics') { - if (this.props.selectedTeam != null && this.props.selectedTeam === teamId) { - return 'active'; - } - } else { - return 'active'; - } - } - - return ''; + handleAllTeamsChange() { + this.setState({ + teams: AdminStore.getAllTeams(), + selectedTeams: AdminStore.getSelectedTeams() + }); } - removeTeam(teamId, e) { - e.preventDefault(); - e.stopPropagation(); - Reflect.deleteProperty(this.props.selectedTeams, teamId); - this.props.removeSelectedTeam(teamId); + removeTeam(team) { + const selectedTeams = Object.assign({}, this.state.selectedTeams); + Reflect.deleteProperty(selectedTeams, team.id); + AdminStore.saveSelectedTeams(selectedTeams); - if (this.props.selected === 'team_users') { - if (this.props.selectedTeam != null && this.props.selectedTeam === teamId) { - this.props.selectTab('service_settings', null); - } + this.handleAllTeamsChange(); + + if (this.context.router.isActive('/admin_console/team/' + team.id)) { + browserHistory.push('/admin_console'); } } @@ -74,31 +85,23 @@ export default class AdminSidebar extends React.Component { } teamSelectedModal(teamId) { - this.setState({showSelectModal: false}); - this.props.addSelectedTeam(teamId); - this.forceUpdate(); + this.setState({ + showSelectModal: false + }); + + const selectedTeams = Object.assign({}, this.state.selectedTeams); + selectedTeams[teamId] = true; + + AdminStore.saveSelectedTeams(selectedTeams); + + this.handleAllTeamsChange(); } teamSelectedModalDismissed() { this.setState({showSelectModal: false}); } - render() { - var count = '*'; - var teams = ( - - ); - const removeTooltip = ( - - - - ); + renderAddTeamButton() { const addTeamTooltip = ( ); - if (this.props.teams != null) { - count = '' + Object.keys(this.props.teams).length; + return ( + + + + + + + + ); + } - teams = []; - for (var key in this.props.selectedTeams) { - if (this.props.selectedTeams.hasOwnProperty(key)) { - var team = this.props.teams[key]; + renderTeams() { + const teams = []; - if (team != null) { - teams.push( - - ); - } - } + for (const key in this.state.selectedTeams) { + if (!this.state.selectedTeams.hasOwnProperty(key)) { + continue; } + + const team = this.state.teams[key]; + + if (!team) { + continue; + } + + teams.push( + + ); } - let ldapSettings; - let complianceSettings; - let licenseSettings; - if (global.window.mm_config.BuildEnterpriseReady === 'true') { - if (global.window.mm_license.IsLicensed === 'true') { + return ( + + } + action={this.renderAddTeamButton()} + > + {teams} + + ); + } + + render() { + let ldapSettings = null; + let complianceSettings = null; + + let license = null; + let audits = null; + + if (window.mm_config.BuildEnterpriseReady === 'true') { + if (window.mm_license.IsLicensed === 'true') { if (global.window.mm_license.LDAP === 'true') { ldapSettings = ( -
  • - + - -
  • + } + /> ); } if (global.window.mm_license.Compliance === 'true') { complianceSettings = ( -
  • - + - -
  • + } + /> ); } } - licenseSettings = ( -
  • - + license = ( + - -
  • + } + /> ); } - let audits; - if (global.window.mm_license.IsLicensed === 'true') { + if (window.mm_license.IsLicensed === 'true') { audits = ( -
  • - + - -
  • + } + /> ); } return ( -
    +
    - + + + {this.props.title} + + {this.props.action} +
    + ); + + if (this.props.name) { + link += '/' + name; + title = ( + + {title} + + ); + } + + let clonedChildren = null; + if (this.props.children && this.context.router.isActive(link)) { + clonedChildren = ( +
      + { + React.Children.map(this.props.children, (child) => { + if (child === null) { + return null; + } + + return React.cloneElement(child, { + parentLink: link + }); + }) + } +
    + ); + } + + return ( +
  • + {title} + {clonedChildren} +
  • + ); + } +} diff --git a/webapp/components/admin_console/admin_sidebar_section.jsx b/webapp/components/admin_console/admin_sidebar_section.jsx new file mode 100644 index 0000000000..0492745ca3 --- /dev/null +++ b/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 = ( +
      + { + React.Children.map(this.props.children, (child) => { + if (child === null) { + return null; + } + + return React.cloneElement(child, { + parentLink: link, + subsection: true + }); + }) + } +
    + ); + } + + let className = 'sidebar-section'; + if (this.props.subsection) { + className += ' sidebar-subsection'; + } + + return ( +
  • + + + {this.props.title} + + {this.props.action} + + {clonedChildren} +
  • + ); + } +} diff --git a/webapp/components/admin_console/admin_sidebar_team.jsx b/webapp/components/admin_console/admin_sidebar_team.jsx new file mode 100644 index 0000000000..2b85c712c0 --- /dev/null +++ b/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 = ( + + + + ); + + const removeTeamButton = ( + + + {'×'} + + + ); + + return ( + + + } + /> + + } + /> + + ); + } +} diff --git a/webapp/components/admin_console/boolean_setting.jsx b/webapp/components/admin_console/boolean_setting.jsx index 99d508d684..a0bd2aa36c 100644 --- a/webapp/components/admin_console/boolean_setting.jsx +++ b/webapp/components/admin_console/boolean_setting.jsx @@ -8,16 +8,43 @@ import Setting from './setting.jsx'; import {FormattedMessage} from 'react-intl'; export default class BooleanSetting extends React.Component { + constructor(props) { + super(props); + + this.handleChange = this.handleChange.bind(this); + } + + handleChange(e) { + this.props.onChange(this.props.id, e.target.value === 'true'); + } + render() { + let helpText; + if (this.props.disabled && this.props.disabledText) { + helpText = ( +
    + + {this.props.disabledText} + + {this.props.helpText} +
    + ); + } else { + helpText = this.props.helpText; + } + return ( - + @@ -25,13 +52,12 @@ export default class BooleanSetting extends React.Component { {this.props.falseText} - {this.props.helpText} ); } @@ -48,15 +74,18 @@ BooleanSetting.defaultProps = { id='admin.ldap.false' defaultMessage='false' /> - ) + ), + disabled: false }; BooleanSetting.propTypes = { + id: React.PropTypes.string.isRequired, label: React.PropTypes.node.isRequired, - currentValue: React.PropTypes.bool.isRequired, + value: React.PropTypes.bool.isRequired, + onChange: React.PropTypes.func.isRequired, trueText: React.PropTypes.node, falseText: React.PropTypes.node, - isDisabled: React.PropTypes.bool.isRequired, - handleChange: React.PropTypes.func.isRequired, + disabled: React.PropTypes.bool.isRequired, + disabledText: React.PropTypes.node, helpText: React.PropTypes.node.isRequired }; diff --git a/webapp/components/admin_console/brand_image_setting.jsx b/webapp/components/admin_console/brand_image_setting.jsx new file mode 100644 index 0000000000..74f2290afd --- /dev/null +++ b/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 = ( + + ); + } else if (this.state.brandImageExists) { + img = ( + +

    + ); + } + + return ( +
    + +
    + {img} +
    +
    +
    +
    + + +
    + +
    + +

    + +

    +
    +
    + ); + } +} diff --git a/webapp/components/admin_console/compliance_settings.jsx b/webapp/components/admin_console/compliance_settings.jsx index 53f060e111..d317591504 100644 --- a/webapp/components/admin_console/compliance_settings.jsx +++ b/webapp/components/admin_console/compliance_settings.jsx @@ -1,83 +1,51 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import $ from 'jquery'; -import Client from 'utils/web_client.jsx'; -import * as AsyncClient from '../../utils/async_client.jsx'; -import * as Utils from '../../utils/utils.jsx'; - -import {FormattedMessage, FormattedHTMLMessage} from 'react-intl'; - import React from 'react'; -import ReactDOM from 'react-dom'; -export default class ComplianceSettings extends React.Component { +import * as Utils from 'utils/utils.jsx'; + +import AdminSettings from './admin_settings.jsx'; +import BooleanSetting from './boolean_setting.jsx'; +import {FormattedHTMLMessage, FormattedMessage} from 'react-intl'; +import SettingsGroup from './settings_group.jsx'; +import TextSetting from './text_setting.jsx'; + +export default class ComplianceSettings extends AdminSettings { constructor(props) { super(props); - this.handleSubmit = this.handleSubmit.bind(this); - this.handleChange = this.handleChange.bind(this); - this.handleEnable = this.handleEnable.bind(this); - this.handleDisable = this.handleDisable.bind(this); + this.getConfigFromState = this.getConfigFromState.bind(this); - this.state = { - saveNeeded: false, - serverError: null, - enable: this.props.config.ComplianceSettings.Enable - }; - } - handleChange() { - this.setState({saveNeeded: true}); - } - handleEnable() { - this.setState({saveNeeded: true, enable: true}); - } - handleDisable() { - this.setState({saveNeeded: true, enable: false}); - } - handleSubmit(e) { - e.preventDefault(); - $('#save-button').button('loading'); + this.renderSettings = this.renderSettings.bind(this); - const config = this.props.config; - const oldEnable = config.ComplianceSettings.Enable; - config.ComplianceSettings.Enable = this.refs.Enable.checked; - config.ComplianceSettings.Directory = ReactDOM.findDOMNode(this.refs.Directory).value; - config.ComplianceSettings.EnableDaily = this.refs.EnableDaily.checked; + this.state = Object.assign(this.state, { + enable: props.config.ComplianceSettings.Enable, + directory: props.config.ComplianceSettings.Directory, + enableDaily: props.config.ComplianceSettings.EnableDaily + }); + } - Client.saveConfig( - config, - () => { - $('#save-button').button('reset'); - AsyncClient.getConfig(); - this.setState({ - serverError: null, - saveNeeded: false - }); - if (oldEnable !== config.ComplianceSettings.Enable) { - window.location.reload(); - } - }, - (err) => { - this.setState({ - serverError: err.message, - saveNeeded: true - }); - $('#save-button').button('reset'); - } + getConfigFromState(config) { + config.ComplianceSettings.Enable = this.state.enable; + config.ComplianceSettings.Directory = this.state.directory; + config.ComplianceSettings.EnableDaily = this.state.enableDaily; + + return config; + } + + renderTitle() { + return ( +

    + +

    ); } - render() { - let serverError = ''; - if (this.state.serverError) { - serverError =
    ; - } - - let saveClass = 'btn'; - if (this.state.saveNeeded) { - saveClass = 'btn btn-primary'; - } + renderSettings() { const licenseEnabled = global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.Compliance === 'true'; let bannerContent; @@ -95,170 +63,64 @@ export default class ComplianceSettings extends React.Component { } return ( -
    + {bannerContent} -

    - -

    -
    - -
    - -
    - - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - - -

    - -

    -
    -
    - -
    -
    - {serverError} - -
    -
    -
    -
    + + } + helpText={ + + } + value={this.state.enable} + onChange={this.handleChange} + disabled={!licenseEnabled} + /> + + } + placeholder={Utils.localizeMessage('admin.sql.maxOpenExample', 'Ex "10"')} + helpText={ + + } + value={this.state.directory} + onChange={this.handleChange} + disabled={!licenseEnabled || !this.state.enable} + /> + + } + helpText={ + + } + value={this.state.enableDaily} + onChange={this.handleChange} + disabled={!licenseEnabled || !this.state.enable} + /> + ); } -} - -ComplianceSettings.propTypes = { - config: React.PropTypes.object -}; - +} \ No newline at end of file diff --git a/webapp/components/admin_console/configuration_settings.jsx b/webapp/components/admin_console/configuration_settings.jsx new file mode 100644 index 0000000000..2f80f0be3a --- /dev/null +++ b/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 ( +

    + +

    + ); + } + + renderSettings() { + return ( + + } + > + + } + placeholder={Utils.localizeMessage('admin.service.listenExample', 'Ex ":8065"')} + helpText={ + + } + value={this.state.listenAddress} + onChange={this.handleChange} + /> + + ); + } +} \ No newline at end of file diff --git a/webapp/components/admin_console/connection_security_dropdown_setting.jsx b/webapp/components/admin_console/connection_security_dropdown_setting.jsx index 02b56b192e..b3e9ac31ce 100644 --- a/webapp/components/admin_console/connection_security_dropdown_setting.jsx +++ b/webapp/components/admin_console/connection_security_dropdown_setting.jsx @@ -8,63 +8,62 @@ import DropdownSetting from './dropdown_setting.jsx'; import {FormattedMessage} from 'react-intl'; const CONNECTION_SECURITY_HELP_TEXT = ( -
    - - - - - - - - - - - - - - - -
    - - - -
    - - - -
    - - - -
    -
    + + + + + + + + + + + + + + + +
    + + + +
    + + + +
    + + + +
    ); export default class ConnectionSecurityDropdownSetting extends React.Component { render() { return ( } - currentValue={this.props.currentValue} - handleChange={this.props.handleChange} - isDisabled={this.props.isDisabled} + value={this.props.value} + onChange={this.props.onChange} + disabled={this.props.disabled} helpText={CONNECTION_SECURITY_HELP_TEXT} - margin='small' /> ); } @@ -89,7 +87,7 @@ ConnectionSecurityDropdownSetting.defaultProps = { }; ConnectionSecurityDropdownSetting.propTypes = { - currentValue: React.PropTypes.string.isRequired, - handleChange: React.PropTypes.func.isRequired, - isDisabled: React.PropTypes.bool.isRequired + value: React.PropTypes.string.isRequired, + onChange: React.PropTypes.func.isRequired, + disabled: React.PropTypes.bool.isRequired }; diff --git a/webapp/components/admin_console/connection_settings.jsx b/webapp/components/admin_console/connection_settings.jsx new file mode 100644 index 0000000000..59b32ec239 --- /dev/null +++ b/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 ( +

    + +

    + ); + } + + renderSettings() { + return ( + + } + > + + } + placeholder={Utils.localizeMessage('admin.service.corsEx', 'http://example.com')} + helpText={ + + } + value={this.state.allowCorsFrom} + onChange={this.handleChange} + /> + + } + helpText={ + + } + value={this.state.enableInsecureOutgoingConnections} + onChange={this.handleChange} + /> + + ); + } +} \ No newline at end of file diff --git a/webapp/components/admin_console/custom_brand_settings.jsx b/webapp/components/admin_console/custom_brand_settings.jsx new file mode 100644 index 0000000000..307bbad8c5 --- /dev/null +++ b/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 ( +

    + +

    + ); + } + + renderSettings() { + const enterpriseSettings = []; + if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.CustomBrand === 'true') { + enterpriseSettings.push( + + } + helpText={ + + } + value={this.state.enableCustomBrand} + onChange={this.handleChange} + /> + ); + + enterpriseSettings.push( + + ); + + enterpriseSettings.push( + + } + helpText={ + + } + value={this.state.customBrandText} + onChange={this.handleChange} + disabled={!this.state.enableCustomBrand} + /> + ); + } + + return ( + + } + > + + } + placeholder={Utils.localizeMessage('admin.team.siteNameExample', 'Ex "Mattermost"')} + helpText={ + + } + value={this.state.siteName} + onChange={this.handleChange} + /> + {enterpriseSettings} + + ); + } +} \ No newline at end of file diff --git a/webapp/components/admin_console/database_settings.jsx b/webapp/components/admin_console/database_settings.jsx new file mode 100644 index 0000000000..42b3727ecc --- /dev/null +++ b/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 ( +

    + +

    + ); + } + + 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 ( + +

    + +

    +
    + +
    +

    {this.state.driverName}

    +
    +
    +
    + +
    +

    {dataSource}

    +
    +
    +
    + +
    +

    {dataSourceReplicas}

    +
    +
    + + } + placeholder={Utils.localizeMessage('admin.sql.maxConnectionsExample', 'Ex "10"')} + helpText={ + + } + value={this.state.maxIdleConns} + onChange={this.handleChange} + /> + + } + placeholder={Utils.localizeMessage('admin.sql.maxOpenExample', 'Ex "10"')} + helpText={ + + } + value={this.state.maxOpenConns} + onChange={this.handleChange} + /> + + } + placeholder={Utils.localizeMessage('admin.sql.keyExample', 'Ex "gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6"')} + helpText={ + + } + value={this.state.atRestEncryptKey} + onChange={this.handleChange} + /> + + } + helpText={ + + } + value={this.state.trace} + onChange={this.handleChange} + /> +
    + ); + } +} \ No newline at end of file diff --git a/webapp/components/admin_console/developer_settings.jsx b/webapp/components/admin_console/developer_settings.jsx new file mode 100644 index 0000000000..9b153ed260 --- /dev/null +++ b/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 ( +

    + +

    + ); + } + + renderSettings() { + return ( + + + } + helpText={ + + } + value={this.state.enableTesting} + onChange={this.handleChange} + /> + + } + helpText={ + + } + value={this.state.enableDeveloper} + onChange={this.handleChange} + /> + + ); + } +} \ No newline at end of file diff --git a/webapp/components/admin_console/dropdown_setting.jsx b/webapp/components/admin_console/dropdown_setting.jsx index fca8dd1709..cf733ec907 100644 --- a/webapp/components/admin_console/dropdown_setting.jsx +++ b/webapp/components/admin_console/dropdown_setting.jsx @@ -6,6 +6,16 @@ import React from 'react'; import Setting from './setting.jsx'; export default class DropdownSetting extends React.Component { + constructor(props) { + super(props); + + this.handleChange = this.handleChange.bind(this); + } + + handleChange(e) { + this.props.onChange(this.props.id, e.target.value); + } + render() { const options = []; for (const {value, text} of this.props.values) { @@ -22,30 +32,33 @@ export default class DropdownSetting extends React.Component { return ( - {this.props.helpText} ); } } + DropdownSetting.defaultProps = { + isDisabled: false }; DropdownSetting.propTypes = { + id: React.PropTypes.string.isRequired, values: React.PropTypes.array.isRequired, label: React.PropTypes.node.isRequired, - currentValue: React.PropTypes.string.isRequired, - handleChange: React.PropTypes.func.isRequired, - isDisabled: React.PropTypes.bool.isRequired, - helpText: React.PropTypes.node.isRequired, - margin: React.PropTypes.oneOf(['', 'small']) + value: React.PropTypes.string.isRequired, + onChange: React.PropTypes.func.isRequired, + disabled: React.PropTypes.bool, + helpText: React.PropTypes.node }; diff --git a/webapp/components/admin_console/email_authentication_settings.jsx b/webapp/components/admin_console/email_authentication_settings.jsx new file mode 100644 index 0000000000..2f5c423bf0 --- /dev/null +++ b/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 ( +

    + +

    + ); + } + + renderSettings() { + return ( + + } + > + + } + helpText={ + + } + value={this.state.enableSignUpWithEmail} + onChange={this.handleChange} + /> + + } + helpText={ + + } + value={this.state.enableSignInWithEmail} + onChange={this.handleChange} + /> + + } + helpText={ + + } + value={this.state.enableSignInWithUsername} + onChange={this.handleChange} + /> + + ); + } +} \ No newline at end of file diff --git a/webapp/components/admin_console/email_connection_test.jsx b/webapp/components/admin_console/email_connection_test.jsx new file mode 100644 index 0000000000..87612e4d58 --- /dev/null +++ b/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 = ( +
    + + +
    + ); + } else if (this.state.fail) { + testMessage = ( +
    + + +
    + ); + } + + let contents = null; + if (this.state.testing) { + contents = ( + + + {Utils.localizeMessage('admin.email.testing', 'Testing...')} + + ); + } else { + contents = ( + + ); + } + + return ( +
    +
    +
    + + {testMessage} +
    +
    +
    + ); + } +} \ No newline at end of file diff --git a/webapp/components/admin_console/email_settings.jsx b/webapp/components/admin_console/email_settings.jsx index 71add9983c..5067b562b3 100644 --- a/webapp/components/admin_console/email_settings.jsx +++ b/webapp/components/admin_console/email_settings.jsx @@ -1,1052 +1,232 @@ // 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 ConnectionSecurityDropdownSetting from './connection_security_dropdown_setting.jsx'; - -import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl'; - -import * as Utils from 'utils/utils.jsx'; -import Constants from 'utils/constants.jsx'; - -var holders = defineMessages({ - notificationDisplayExample: { - id: 'admin.email.notificationDisplayExample', - defaultMessage: 'Ex: "Mattermost Notification", "System", "No-Reply"' - }, - notificationEmailExample: { - id: 'admin.email.notificationEmailExample', - defaultMessage: 'Ex: "mattermost@yourcompany.com", "admin@yourcompany.com"' - }, - smtpUsernameExample: { - id: 'admin.email.smtpUsernameExample', - defaultMessage: 'Ex: "admin@yourcompany.com", "AKIADTOVBGERKLCBV"' - }, - smtpPasswordExample: { - id: 'admin.email.smtpPasswordExample', - defaultMessage: 'Ex: "yourpassword", "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"' - }, - smtpServerExample: { - id: 'admin.email.smtpServerExample', - defaultMessage: 'Ex: "smtp.yourcompany.com", "email-smtp.us-east-1.amazonaws.com"' - }, - smtpPortExample: { - id: 'admin.email.smtpPortExample', - defaultMessage: 'Ex: "25", "465"' - }, - inviteSaltExample: { - id: 'admin.email.inviteSaltExample', - defaultMessage: 'Ex "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo"' - }, - passwordSaltExample: { - id: 'admin.email.passwordSaltExample', - defaultMessage: 'Ex "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo"' - }, - testing: { - id: 'admin.email.testing', - defaultMessage: 'Testing...' - }, - saving: { - id: 'admin.email.saving', - defaultMessage: 'Saving Config...' - } -}); - import React from 'react'; -class EmailSettings extends React.Component { +import * as Utils from 'utils/utils.jsx'; + +import AdminSettings from './admin_settings.jsx'; +import BooleanSetting from './boolean_setting.jsx'; +import ConnectionSecurityDropdownSetting from './connection_security_dropdown_setting.jsx'; +import EmailConnectionTest from './email_connection_test.jsx'; +import {FormattedHTMLMessage, FormattedMessage} from 'react-intl'; +import SettingsGroup from './settings_group.jsx'; +import TextSetting from './text_setting.jsx'; + +export default class EmailSettings extends AdminSettings { constructor(props) { super(props); - this.handleChange = this.handleChange.bind(this); - this.handleTestConnection = this.handleTestConnection.bind(this); - this.handleSubmit = this.handleSubmit.bind(this); - this.buildConfig = this.buildConfig.bind(this); - this.handleGenerateInvite = this.handleGenerateInvite.bind(this); - this.handleGenerateReset = this.handleGenerateReset.bind(this); - this.handleSendPushNotificationsChange = this.handleSendPushNotificationsChange.bind(this); - this.handlePushServerChange = this.handlePushServerChange.bind(this); - this.handleAgreeChange = this.handleAgreeChange.bind(this); + this.getConfigFromState = this.getConfigFromState.bind(this); - let sendNotificationValue; - let agree = false; - if (!props.config.EmailSettings.SendPushNotifications) { - sendNotificationValue = 'off'; - } else if (props.config.EmailSettings.PushNotificationServer === Constants.MHPNS && global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.MHPNS === 'true') { - sendNotificationValue = 'mhpns'; - agree = true; - } else if (props.config.EmailSettings.PushNotificationServer === Constants.MTPNS) { - sendNotificationValue = 'mtpns'; - } else { - sendNotificationValue = 'self'; - } + this.renderSettings = this.renderSettings.bind(this); - let pushNotificationServer = this.props.config.EmailSettings.PushNotificationServer; - if (sendNotificationValue === 'mtpns') { - pushNotificationServer = Constants.MTPNS; - } else if (sendNotificationValue === 'mhpns') { - pushNotificationServer = Constants.MHPNS; - } - - this.state = { - sendEmailNotifications: this.props.config.EmailSettings.SendEmailNotifications, - sendPushNotifications: this.props.config.EmailSettings.SendPushNotifications, - saveNeeded: false, - serverError: null, - emailSuccess: null, - emailFail: null, - pushNotificationContents: this.props.config.EmailSettings.PushNotificationContents, - connectionSecurity: this.props.config.EmailSettings.ConnectionSecurity, - sendNotificationValue, - pushNotificationServer, - agree - }; + this.state = Object.assign(this.state, { + sendEmailNotifications: props.config.EmailSettings.SendEmailNotifications, + feedbackName: props.config.EmailSettings.FeedbackName, + feedbackEmail: props.config.EmailSettings.FeedbackEmail, + smtpUsername: props.config.EmailSettings.SMTPUsername, + smtpPassword: props.config.EmailSettings.SMTPPassword, + smtpServer: props.config.EmailSettings.SMTPServer, + smtpPort: props.config.EmailSettings.SMTPPort, + connectionSecurity: props.config.EmailSettings.ConnectionSecurity, + enableSecurityFixAlert: props.config.ServiceSettings.EnableSecurityFixAlert + }); } - handleChange(action) { - const s = {saveNeeded: true}; - - if (action === 'sendEmailNotifications_true') { - s.sendEmailNotifications = true; - } - - if (action === 'sendEmailNotifications_false') { - s.sendEmailNotifications = false; - } - - if (action === 'sendPushNotifications_true') { - s.sendPushNotifications = true; - } - - if (action === 'sendPushNotifications_false') { - s.sendPushNotifications = false; - } - - this.setState(s); - } - - buildConfig() { - const config = this.props.config; - config.EmailSettings.EnableSignUpWithEmail = ReactDOM.findDOMNode(this.refs.allowSignUpWithEmail).checked; - config.EmailSettings.EnableSignInWithEmail = ReactDOM.findDOMNode(this.refs.allowSignInWithEmail).checked; - config.EmailSettings.EnableSignInWithUsername = ReactDOM.findDOMNode(this.refs.allowSignInWithUsername).checked; - config.EmailSettings.SendEmailNotifications = ReactDOM.findDOMNode(this.refs.sendEmailNotifications).checked; - config.EmailSettings.RequireEmailVerification = ReactDOM.findDOMNode(this.refs.requireEmailVerification).checked; - config.EmailSettings.FeedbackName = ReactDOM.findDOMNode(this.refs.feedbackName).value.trim(); - config.EmailSettings.FeedbackEmail = ReactDOM.findDOMNode(this.refs.feedbackEmail).value.trim(); - config.EmailSettings.SMTPServer = ReactDOM.findDOMNode(this.refs.SMTPServer).value.trim(); - config.EmailSettings.SMTPPort = ReactDOM.findDOMNode(this.refs.SMTPPort).value.trim(); - config.EmailSettings.SMTPUsername = ReactDOM.findDOMNode(this.refs.SMTPUsername).value.trim(); - config.EmailSettings.SMTPPassword = ReactDOM.findDOMNode(this.refs.SMTPPassword).value.trim(); - config.EmailSettings.ConnectionSecurity = this.state.connectionSecurity.trim(); - - config.EmailSettings.InviteSalt = ReactDOM.findDOMNode(this.refs.InviteSalt).value.trim(); - if (config.EmailSettings.InviteSalt === '') { - config.EmailSettings.InviteSalt = crypto.randomBytes(256).toString('base64').substring(0, 32); - ReactDOM.findDOMNode(this.refs.InviteSalt).value = config.EmailSettings.InviteSalt; - } - - config.EmailSettings.PasswordResetSalt = ReactDOM.findDOMNode(this.refs.PasswordResetSalt).value.trim(); - if (config.EmailSettings.PasswordResetSalt === '') { - config.EmailSettings.PasswordResetSalt = crypto.randomBytes(256).toString('base64').substring(0, 32); - ReactDOM.findDOMNode(this.refs.PasswordResetSalt).value = config.EmailSettings.PasswordResetSalt; - } - - const sendPushNotifications = this.refs.sendPushNotifications.value; - if (sendPushNotifications === 'off') { - config.EmailSettings.SendPushNotifications = false; - } else { - config.EmailSettings.SendPushNotifications = true; - } - - if (this.refs.PushNotificationServer) { - config.EmailSettings.PushNotificationServer = this.refs.PushNotificationServer.value.trim(); - } - - if (this.refs.PushNotificationContents) { - config.EmailSettings.PushNotificationContents = this.refs.PushNotificationContents.value; - } + getConfigFromState(config) { + config.EmailSettings.SendEmailNotifications = this.state.sendEmailNotifications; + config.EmailSettings.FeedbackName = this.state.feedbackName; + config.EmailSettings.FeedbackEmail = this.state.feedbackEmail; + config.EmailSettings.SMTPUsername = this.state.smtpUsername; + config.EmailSettings.SMTPPassword = this.state.smtpPassword; + config.EmailSettings.SMTPServer = this.state.smtpServer; + config.EmailSettings.SMTPPort = this.state.smtpPort; + config.EmailSettings.ConnectionSecurity = this.state.connectionSecurity; + config.ServiceSettings.EnableSecurityFixAlert = this.state.enableSecurityFixAlert; return config; } - handleSendPushNotificationsChange(e) { - const sendNotificationValue = e.target.value; - let pushNotificationServer = this.state.pushNotificationServer; - if (sendNotificationValue === 'mtpns') { - pushNotificationServer = Constants.MTPNS; - } else if (sendNotificationValue === 'mhpns') { - pushNotificationServer = Constants.MHPNS; - } - this.setState({saveNeeded: true, sendNotificationValue, pushNotificationServer, agree: false}); - } - - handlePushServerChange(e) { - this.setState({saveNeeded: true, pushNotificationServer: e.target.value}); - } - - handleAgreeChange(e) { - this.setState({agree: e.target.checked}); - } - - handleGenerateInvite(e) { - e.preventDefault(); - ReactDOM.findDOMNode(this.refs.InviteSalt).value = crypto.randomBytes(256).toString('base64').substring(0, 32); - var s = {saveNeeded: true, serverError: this.state.serverError}; - this.setState(s); - } - - handleGenerateReset(e) { - e.preventDefault(); - ReactDOM.findDOMNode(this.refs.PasswordResetSalt).value = crypto.randomBytes(256).toString('base64').substring(0, 32); - var s = {saveNeeded: true, serverError: this.state.serverError}; - this.setState(s); - } - - handleTestConnection(e) { - e.preventDefault(); - $('#connection-button').button('loading'); - - var config = this.buildConfig(); - - Client.testEmail( - config, - () => { - this.setState({ - sendEmailNotifications: config.EmailSettings.SendEmailNotifications, - serverError: null, - saveNeeded: true, - emailSuccess: true, - emailFail: null - }); - $('#connection-button').button('reset'); - }, - (err) => { - this.setState({ - sendEmailNotifications: config.EmailSettings.SendEmailNotifications, - serverError: null, - saveNeeded: true, - emailSuccess: null, - emailFail: err.message + ' - ' + err.detailed_error - }); - $('#connection-button').button('reset'); - } - ); - } - - handleSubmit(e) { - e.preventDefault(); - $('#save-button').button('loading'); - - var config = this.buildConfig(); - - Client.saveConfig( - config, - () => { - AsyncClient.getConfig(); - this.setState({ - sendEmailNotifications: config.EmailSettings.SendEmailNotifications, - serverError: null, - saveNeeded: false, - emailSuccess: null, - emailFail: null - }); - $('#save-button').button('reset'); - }, - (err) => { - this.setState({ - sendEmailNotifications: config.EmailSettings.SendEmailNotifications, - serverError: err.message, - saveNeeded: true, - emailSuccess: null, - emailFail: null - }); - $('#save-button').button('reset'); - } - ); - } - - render() { - const {formatMessage} = this.props.intl; - var serverError = ''; - if (this.state.serverError) { - serverError =
    ; - } - - var saveClass = 'btn'; - if (this.state.saveNeeded) { - saveClass = 'btn btn-primary'; - } - - var emailSuccess = ''; - if (this.state.emailSuccess) { - emailSuccess = ( -
    - - -
    - ); - } - - var emailFail = ''; - if (this.state.emailFail) { - emailSuccess = ( -
    - - -
    - ); - } - - let mhpnsOption; - if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.MHPNS === 'true') { - mhpnsOption = ; - } - - let disableSave = !this.state.saveNeeded; - - let tosCheckbox; - if (this.state.sendNotificationValue === 'mhpns') { - tosCheckbox = ( -
    - -
    - - -
    -
    - ); - - disableSave = disableSave || !this.state.agree; - } - - let sendHelpText; - let pushServerHelpText; - if (this.state.sendNotificationValue === 'off') { - sendHelpText = ( - - ); - } else if (this.state.sendNotificationValue === 'mhpns') { - pushServerHelpText = ( - - ); - } else if (this.state.sendNotificationValue === 'mtpns') { - pushServerHelpText = ( - - ); - } else { - pushServerHelpText = ( - - ); - } - - const sendPushNotifications = ( -
    - -
    - -

    - {sendHelpText} -

    -
    -
    - ); - - let pushNotificationServer; - let pushNotificationContent; - if (this.state.sendNotificationValue !== 'off') { - pushNotificationServer = ( -
    - -
    - -

    - {pushServerHelpText} -

    -
    -
    - ); - - pushNotificationContent = ( -
    - -
    - -

    - -

    -
    -
    - ); - } - + renderTitle() { return ( -
    -

    - -

    -
    - -
    - -
    - - -

    - -

    -
    -
    - -
    - -
    - - -

    - -

    -
    -
    - -
    - -
    - - -

    - -

    -
    -
    - -
    - -
    - - -

    - -

    -
    -
    - -
    - -
    - - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - - this.setState({connectionSecurity: e.target.value, saveNeeded: true})} - isDisabled={!this.state.sendEmailNotifications} - /> -
    -
    -
    - - {emailSuccess} - {emailFail} -
    -
    -
    - -
    - -
    - -

    - -

    -
    - -
    -
    -
    - -
    - -
    - -

    - -

    -
    - -
    -
    -
    - - {sendPushNotifications} - {tosCheckbox} - {pushNotificationServer} - {pushNotificationContent} - -
    -
    - {serverError} - -
    -
    - - -
    +

    + +

    ); } -} -EmailSettings.propTypes = { - intl: intlShape.isRequired, - config: React.PropTypes.object -}; - -export default injectIntl(EmailSettings); + renderSettings() { + return ( + + } + > + + } + helpText={ + + } + value={this.state.sendEmailNotifications} + onChange={this.handleChange} + /> + + } + placeholder={Utils.localizeMessage('admin.email.notificationDisplayExample', 'Ex: "Mattermost Notification", "System", "No-Reply"')} + helpText={ + + } + value={this.state.feedbackName} + onChange={this.handleChange} + disabled={!this.state.sendEmailNotifications} + /> + + } + placeholder={Utils.localizeMessage('admin.email.notificationEmailExample', 'Ex: "mattermost@yourcompany.com", "admin@yourcompany.com"')} + helpText={ + + } + value={this.state.feedbackEmail} + onChange={this.handleChange} + disabled={!this.state.sendEmailNotifications} + /> + + } + placeholder={Utils.localizeMessage('admin.email.smtpUsernameExample', 'Ex: "admin@yourcompany.com", "AKIADTOVBGERKLCBV"')} + helpText={ + + } + value={this.state.smtpUsername} + onChange={this.handleChange} + disabled={!this.state.sendEmailNotifications} + /> + + } + placeholder={Utils.localizeMessage('admin.email.smtpPasswordExample', 'Ex: "yourpassword", "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"')} + helpText={ + + } + value={this.state.smtpPassword} + onChange={this.handleChange} + disabled={!this.state.sendEmailNotifications} + /> + + } + placeholder={Utils.localizeMessage('admin.email.smtpServerExample', 'Ex: "smtp.yourcompany.com", "email-smtp.us-east-1.amazonaws.com"')} + helpText={ + + } + value={this.state.smtpServer} + onChange={this.handleChange} + disabled={!this.state.sendEmailNotifications} + /> + + } + placeholder={Utils.localizeMessage('admin.email.smtpPortExample', 'Ex: "25", "465"')} + helpText={ + + } + value={this.state.smtpPort} + onChange={this.handleChange} + disabled={!this.state.sendEmailNotifications} + /> + + + + } + helpText={ + + } + value={this.state.enableSecurityFixAlert} + onChange={this.handleChange} + /> + + ); + } +} \ No newline at end of file diff --git a/webapp/components/admin_console/external_service_settings.jsx b/webapp/components/admin_console/external_service_settings.jsx new file mode 100644 index 0000000000..88c6c28eac --- /dev/null +++ b/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 ( +

    + +

    + ); + } + + renderSettings() { + return ( + + } + > + + } + placeholder={Utils.localizeMessage('admin.service.segmentExample', 'Ex "g3fgGOXJAQ43QV7rAh6iwQCkV4cA1Gs"')} + helpText={ + + } + value={this.state.segmentDeveloperKey} + onChange={this.handleChange} + /> + + } + placeholder={Utils.localizeMessage('admin.service.googleExample', 'Ex "7rAh6iwQCkV4cA1Gsg3fgGOXJAQ43QV"')} + helpText={ + + } + value={this.state.googleDeveloperKey} + onChange={this.handleChange} + /> + + ); + } +} \ No newline at end of file diff --git a/webapp/components/admin_console/generated_setting.jsx b/webapp/components/admin_console/generated_setting.jsx new file mode 100644 index 0000000000..a83407cb6d --- /dev/null +++ b/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: ( + + ) + }; + } + + 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 = ( +
    + {this.props.disabledText} +
    + ); + } + + return ( +
    + +
    + + {disabledText} +
    + {this.props.helpText} +
    + +
    +
    + ); + } +} diff --git a/webapp/components/admin_console/gitlab_settings.jsx b/webapp/components/admin_console/gitlab_settings.jsx index 510fd0887b..bd3cd8dec4 100644 --- a/webapp/components/admin_console/gitlab_settings.jsx +++ b/webapp/components/admin_console/gitlab_settings.jsx @@ -1,382 +1,186 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import $ from 'jquery'; -import ReactDOM from 'react-dom'; -import Client from 'utils/web_client.jsx'; -import * as AsyncClient from 'utils/async_client.jsx'; - -import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl'; - -const holders = defineMessages({ - clientIdExample: { - id: 'admin.gitlab.clientIdExample', - defaultMessage: 'Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"' - }, - clientSecretExample: { - id: 'admin.gitlab.clientSecretExample', - defaultMessage: 'Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"' - }, - authExample: { - id: 'admin.gitlab.authExample', - defaultMessage: 'Ex ""' - }, - tokenExample: { - id: 'admin.gitlab.tokenExample', - defaultMessage: 'Ex ""' - }, - userExample: { - id: 'admin.gitlab.userExample', - defaultMessage: 'Ex ""' - }, - saving: { - id: 'admin.gitlab.saving', - defaultMessage: 'Saving Config...' - } -}); - import React from 'react'; -class GitLabSettings extends React.Component { +import * as Utils from 'utils/utils.jsx'; + +import AdminSettings from './admin_settings.jsx'; +import BooleanSetting from './boolean_setting.jsx'; +import {FormattedHTMLMessage, FormattedMessage} from 'react-intl'; +import SettingsGroup from './settings_group.jsx'; +import TextSetting from './text_setting.jsx'; + +export default class GitLabSettings extends AdminSettings { constructor(props) { super(props); - this.handleChange = this.handleChange.bind(this); - this.handleSubmit = this.handleSubmit.bind(this); + this.getConfigFromState = this.getConfigFromState.bind(this); - this.state = { - Enable: this.props.config.GitLabSettings.Enable, - saveNeeded: false, - serverError: null - }; + this.renderSettings = this.renderSettings.bind(this); + + this.state = Object.assign(this.state, { + enable: props.config.GitLabSettings.Enable, + id: props.config.GitLabSettings.Id, + secret: props.config.GitLabSettings.Secret, + userApiEndpoint: props.config.GitLabSettings.UserApiEndpoint, + authEndpoint: props.config.GitLabSettings.AuthEndpoint, + tokenEndpoint: props.config.GitLabSettings.TokenEndpoint + }); } - handleChange(action) { - var s = {saveNeeded: true, serverError: this.state.serverError}; + getConfigFromState(config) { + config.GitLabSettings.Enable = this.state.enable; + config.GitLabSettings.Id = this.state.id; + config.GitLabSettings.Secret = this.state.secret; + config.GitLabSettings.UserApiEndpoint = this.state.userApiEndpoint; + config.GitLabSettings.AuthEndpoint = this.state.authEndpoint; + config.GitLabSettings.TokenEndpoint = this.state.tokenEndpoint; - if (action === 'EnableTrue') { - s.Enable = true; - } - - if (action === 'EnableFalse') { - s.Enable = false; - } - - this.setState(s); + return config; } - handleSubmit(e) { - e.preventDefault(); - $('#save-button').button('loading'); - - var config = this.props.config; - config.GitLabSettings.Enable = ReactDOM.findDOMNode(this.refs.Enable).checked; - config.GitLabSettings.Secret = ReactDOM.findDOMNode(this.refs.Secret).value.trim(); - config.GitLabSettings.Id = ReactDOM.findDOMNode(this.refs.Id).value.trim(); - config.GitLabSettings.AuthEndpoint = ReactDOM.findDOMNode(this.refs.AuthEndpoint).value.trim(); - config.GitLabSettings.TokenEndpoint = ReactDOM.findDOMNode(this.refs.TokenEndpoint).value.trim(); - config.GitLabSettings.UserApiEndpoint = ReactDOM.findDOMNode(this.refs.UserApiEndpoint).value.trim(); - - Client.saveConfig( - config, - () => { - AsyncClient.getConfig(); - this.setState({ - serverError: null, - saveNeeded: false - }); - $('#save-button').button('reset'); - }, - (err) => { - this.setState({ - serverError: err.message, - saveNeeded: true - }); - $('#save-button').button('reset'); - } - ); - } - - render() { - const {formatMessage} = this.props.intl; - var serverError = ''; - if (this.state.serverError) { - serverError =
    ; - } - - var saveClass = 'btn'; - if (this.state.saveNeeded) { - saveClass = 'btn btn-primary'; - } - + renderTitle() { return ( -
    - -

    - -

    -
    -
    - -
    - - -

    - -
    -

    -
    - -
    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    -
    - {serverError} - -
    -
    - -
    -
    +

    + +

    ); } -} -//config.GitLabSettings.Scope = ReactDOM.findDOMNode(this.refs.Scope).value.trim(); -//
    -// -//
    -// -//

    {'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.'}

    -//
    -//
    - -GitLabSettings.propTypes = { - intl: intlShape.isRequired, - config: React.PropTypes.object -}; - -export default injectIntl(GitLabSettings); + renderSettings() { + return ( + + } + > + + } + helpText={ +
    + +
    + +
    + } + value={this.state.enable} + onChange={this.handleChange} + /> + + } + placeholder={Utils.localizeMessage('admin.gitlab.clientIdExample', 'Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"')} + helpText={ + + } + value={this.state.id} + onChange={this.handleChange} + disabled={!this.state.enable} + /> + + } + placeholder={Utils.localizeMessage('admin.gitlab.clientSecretExample', 'Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"')} + helpText={ + + } + value={this.state.secret} + onChange={this.handleChange} + disabled={!this.state.enable} + /> + + } + placeholder={Utils.localizeMessage('admin.gitlab.userExample', 'Ex ""')} + helpText={ + + } + value={this.state.userApiEndpoint} + onChange={this.handleChange} + disabled={!this.state.enable} + /> + + } + placeholder={Utils.localizeMessage('admin.gitlab.authExample', 'Ex ""')} + helpText={ + + } + value={this.state.authEndpoint} + onChange={this.handleChange} + disabled={!this.state.enable} + /> + + } + placeholder={Utils.localizeMessage('admin.gitlab.tokenExample', 'Ex ""')} + helpText={ + + } + value={this.state.tokenEndpoint} + onChange={this.handleChange} + disabled={!this.state.enable} + /> +
    + ); + } +} \ No newline at end of file diff --git a/webapp/components/admin_console/image_settings.jsx b/webapp/components/admin_console/image_settings.jsx index 64a7663c66..86d8795cc1 100644 --- a/webapp/components/admin_console/image_settings.jsx +++ b/webapp/components/admin_console/image_settings.jsx @@ -1,692 +1,174 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import $ from 'jquery'; -import ReactDOM from 'react-dom'; -import Client from 'utils/web_client.jsx'; -import * as AsyncClient from 'utils/async_client.jsx'; -import crypto from 'crypto'; - -import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl'; - -const holders = defineMessages({ - storeLocal: { - id: 'admin.image.storeLocal', - defaultMessage: 'Local File System' - }, - storeAmazonS3: { - id: 'admin.image.storeAmazonS3', - defaultMessage: 'Amazon S3' - }, - localExample: { - id: 'admin.image.localExample', - defaultMessage: 'Ex "./data/"' - }, - amazonS3IdExample: { - id: 'admin.image.amazonS3IdExample', - defaultMessage: 'Ex "AKIADTOVBGERKLCBV"' - }, - amazonS3SecretExample: { - id: 'admin.image.amazonS3SecretExample', - defaultMessage: 'Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"' - }, - amazonS3BucketExample: { - id: 'admin.image.amazonS3BucketExample', - defaultMessage: 'Ex "mattermost-media"' - }, - amazonS3RegionExample: { - id: 'admin.image.amazonS3RegionExample', - defaultMessage: 'Ex "us-east-1"' - }, - thumbWidthExample: { - id: 'admin.image.thumbWidthExample', - defaultMessage: 'Ex "120"' - }, - thumbHeightExample: { - id: 'admin.image.thumbHeightExample', - defaultMessage: 'Ex "100"' - }, - previewWidthExample: { - id: 'admin.image.previewWidthExample', - defaultMessage: 'Ex "1024"' - }, - previewHeightExample: { - id: 'admin.image.previewHeightExample', - defaultMessage: 'Ex "0"' - }, - profileWidthExample: { - id: 'admin.image.profileWidthExample', - defaultMessage: 'Ex "1024"' - }, - profileHeightExample: { - id: 'admin.image.profileHeightExample', - defaultMessage: 'Ex "0"' - }, - publicLinkExample: { - id: 'admin.image.publicLinkExample', - defaultMessage: 'Ex "gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6"' - }, - saving: { - id: 'admin.image.saving', - defaultMessage: 'Saving Config...' - } -}); - import React from 'react'; -class FileSettings extends React.Component { +import * as Utils from 'utils/utils.jsx'; + +import AdminSettings from './admin_settings.jsx'; +import {FormattedMessage} from 'react-intl'; +import SettingsGroup from './settings_group.jsx'; +import TextSetting from './text_setting.jsx'; + +export default class ImageSettings extends AdminSettings { constructor(props) { super(props); - this.handleChange = this.handleChange.bind(this); - this.handleSubmit = this.handleSubmit.bind(this); - this.handleGenerate = this.handleGenerate.bind(this); + this.getConfigFromState = this.getConfigFromState.bind(this); - this.state = { - saveNeeded: false, - serverError: null, - DriverName: this.props.config.FileSettings.DriverName - }; + this.renderSettings = this.renderSettings.bind(this); + + this.state = Object.assign(this.state, { + thumbnailWidth: props.config.FileSettings.ThumbnailWidth, + thumbnailHeight: props.config.FileSettings.ThumbnailHeight, + profileWidth: props.config.FileSettings.ProfileWidth, + profileHeight: props.config.FileSettings.ProfileHeight, + previewWidth: props.config.FileSettings.PreviewWidth, + previewHeight: props.config.FileSettings.PreviewHeight + }); } - handleChange(action) { - var s = {saveNeeded: true, serverError: this.state.serverError}; + getConfigFromState(config) { + config.FileSettings.ThumbnailWidth = this.parseInt(this.state.thumbnailWidth); + config.FileSettings.ThumbnailHeight = this.parseInt(this.state.thumbnailHeight); + config.FileSettings.ProfileWidth = this.parseInt(this.state.profileWidth); + config.FileSettings.ProfileHeight = this.parseInt(this.state.profileHeight); + config.FileSettings.PreviewWidth = this.parseInt(this.state.previewWidth); + config.FileSettings.PreviewHeight = this.parseInt(this.state.previewHeight); - if (action === 'DriverName') { - s.DriverName = ReactDOM.findDOMNode(this.refs.DriverName).value; - } - - this.setState(s); + return config; } - handleGenerate(e) { - e.preventDefault(); - ReactDOM.findDOMNode(this.refs.PublicLinkSalt).value = crypto.randomBytes(256).toString('base64').substring(0, 32); - var s = {saveNeeded: true, serverError: this.state.serverError}; - this.setState(s); - } - - handleSubmit(e) { - e.preventDefault(); - $('#save-button').button('loading'); - - var config = this.props.config; - config.FileSettings.DriverName = ReactDOM.findDOMNode(this.refs.DriverName).value; - config.FileSettings.Directory = ReactDOM.findDOMNode(this.refs.Directory).value; - config.FileSettings.AmazonS3AccessKeyId = ReactDOM.findDOMNode(this.refs.AmazonS3AccessKeyId).value; - config.FileSettings.AmazonS3SecretAccessKey = ReactDOM.findDOMNode(this.refs.AmazonS3SecretAccessKey).value; - config.FileSettings.AmazonS3Bucket = ReactDOM.findDOMNode(this.refs.AmazonS3Bucket).value; - config.FileSettings.AmazonS3Region = ReactDOM.findDOMNode(this.refs.AmazonS3Region).value; - config.FileSettings.EnablePublicLink = ReactDOM.findDOMNode(this.refs.EnablePublicLink).checked; - - config.FileSettings.PublicLinkSalt = ReactDOM.findDOMNode(this.refs.PublicLinkSalt).value.trim(); - - if (config.FileSettings.PublicLinkSalt === '') { - config.FileSettings.PublicLinkSalt = crypto.randomBytes(256).toString('base64').substring(0, 32); - ReactDOM.findDOMNode(this.refs.PublicLinkSalt).value = config.FileSettings.PublicLinkSalt; - } - - var thumbnailWidth = 120; - if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.ThumbnailWidth).value, 10))) { - thumbnailWidth = parseInt(ReactDOM.findDOMNode(this.refs.ThumbnailWidth).value, 10); - } - config.FileSettings.ThumbnailWidth = thumbnailWidth; - ReactDOM.findDOMNode(this.refs.ThumbnailWidth).value = thumbnailWidth; - - var thumbnailHeight = 100; - if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.ThumbnailHeight).value, 10))) { - thumbnailHeight = parseInt(ReactDOM.findDOMNode(this.refs.ThumbnailHeight).value, 10); - } - config.FileSettings.ThumbnailHeight = thumbnailHeight; - ReactDOM.findDOMNode(this.refs.ThumbnailHeight).value = thumbnailHeight; - - var previewWidth = 1024; - if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.PreviewWidth).value, 10))) { - previewWidth = parseInt(ReactDOM.findDOMNode(this.refs.PreviewWidth).value, 10); - } - config.FileSettings.PreviewWidth = previewWidth; - ReactDOM.findDOMNode(this.refs.PreviewWidth).value = previewWidth; - - var previewHeight = 0; - if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.PreviewHeight).value, 10))) { - previewHeight = parseInt(ReactDOM.findDOMNode(this.refs.PreviewHeight).value, 10); - } - config.FileSettings.PreviewHeight = previewHeight; - ReactDOM.findDOMNode(this.refs.PreviewHeight).value = previewHeight; - - var profileWidth = 128; - if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.ProfileWidth).value, 10))) { - profileWidth = parseInt(ReactDOM.findDOMNode(this.refs.ProfileWidth).value, 10); - } - config.FileSettings.ProfileWidth = profileWidth; - ReactDOM.findDOMNode(this.refs.ProfileWidth).value = profileWidth; - - var profileHeight = 128; - if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.ProfileHeight).value, 10))) { - profileHeight = parseInt(ReactDOM.findDOMNode(this.refs.ProfileHeight).value, 10); - } - config.FileSettings.ProfileHeight = profileHeight; - ReactDOM.findDOMNode(this.refs.ProfileHeight).value = profileHeight; - - Client.saveConfig( - config, - () => { - AsyncClient.getConfig(); - this.setState({ - serverError: null, - saveNeeded: false - }); - $('#save-button').button('reset'); - }, - (err) => { - this.setState({ - serverError: err.message, - saveNeeded: true - }); - $('#save-button').button('reset'); - } - ); - } - - render() { - const {formatMessage} = this.props.intl; - var serverError = ''; - if (this.state.serverError) { - serverError =
    ; - } - - var saveClass = 'btn'; - if (this.state.saveNeeded) { - saveClass = 'btn btn-primary'; - } - - var enableFile = false; - var enableS3 = false; - - if (this.state.DriverName === 'local') { - enableFile = true; - } - - if (this.state.DriverName === 'amazons3') { - enableS3 = true; - } - + renderTitle() { return ( -
    -

    - -

    -
    - -
    - -
    - -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    - -
    -
    -
    - -
    -
    - {serverError} - -
    -
    - -
    -
    +

    + +

    ); } -} -FileSettings.propTypes = { - intl: intlShape.isRequired, - config: React.PropTypes.object -}; - -export default injectIntl(FileSettings); + renderSettings() { + return ( + + } + > + + } + placeholder={Utils.localizeMessage('admin.image.thumbWidthExample', 'Ex "120"')} + helpText={ + + } + value={this.state.thumbnailWidth} + onChange={this.handleChange} + /> + + } + placeholder={Utils.localizeMessage('admin.image.thumbHeightExample', 'Ex "100"')} + helpText={ + + } + value={this.state.thumbnailHeight} + onChange={this.handleChange} + /> + + } + placeholder={Utils.localizeMessage('admin.image.profileWidthExample', 'Ex "1024"')} + helpText={ + + } + value={this.state.profileWidth} + onChange={this.handleChange} + /> + + } + placeholder={Utils.localizeMessage('admin.image.profileHeightExample', 'Ex "0"')} + helpText={ + + } + value={this.state.profileHeight} + onChange={this.handleChange} + /> + + } + placeholder={Utils.localizeMessage('admin.image.previewWidthExample', 'Ex "1024"')} + helpText={ + + } + value={this.state.previewWidth} + onChange={this.handleChange} + /> + + } + placeholder={Utils.localizeMessage('admin.image.previewHeightExample', 'Ex "0"')} + helpText={ + + } + value={this.state.previewHeight} + onChange={this.handleChange} + /> + + ); + } +} \ No newline at end of file diff --git a/webapp/components/admin_console/ldap_settings.jsx b/webapp/components/admin_console/ldap_settings.jsx index 3ced65e504..d47a1f8c2d 100644 --- a/webapp/components/admin_console/ldap_settings.jsx +++ b/webapp/components/admin_console/ldap_settings.jsx @@ -1,116 +1,95 @@ // 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 Utils from 'utils/utils.jsx'; -import * as AsyncClient from 'utils/async_client.jsx'; - -import {FormattedMessage, FormattedHTMLMessage} from 'react-intl'; -import ConnectionSecurityDropdownSetting from './connection_security_dropdown_setting.jsx'; -import BooleanSetting from './boolean_setting.jsx'; - -const DEFAULT_LDAP_PORT = 389; -const DEFAULT_QUERY_TIMEOUT = 60; - import React from 'react'; -class LdapSettings extends React.Component { +import * as Utils from 'utils/utils.jsx'; + +import AdminSettings from './admin_settings.jsx'; +import BooleanSetting from './boolean_setting.jsx'; +import ConnectionSecurityDropdownSetting from './connection_security_dropdown_setting.jsx'; +import {FormattedMessage} from 'react-intl'; +import SettingsGroup from './settings_group.jsx'; +import TextSetting from './text_setting.jsx'; + +export default class LdapSettings extends AdminSettings { constructor(props) { super(props); - this.handleSubmit = this.handleSubmit.bind(this); - this.handleChange = this.handleChange.bind(this); - this.handleEnable = this.handleEnable.bind(this); - this.handleDisable = this.handleDisable.bind(this); + this.getConfigFromState = this.getConfigFromState.bind(this); - this.state = { - saveNeeded: false, - serverError: null, - enable: this.props.config.LdapSettings.Enable, - connectionSecurity: this.props.config.LdapSettings.ConnectionSecurity, - skipCertificateVerification: this.props.config.LdapSettings.SkipCertificateVerification - }; - } - handleChange() { - this.setState({saveNeeded: true}); - } - handleEnable() { - this.setState({saveNeeded: true, enable: true}); - } - handleDisable() { - this.setState({saveNeeded: true, enable: false}); - } - handleSubmit(e) { - e.preventDefault(); - $('#save-button').button('loading'); + this.renderSettings = this.renderSettings.bind(this); - const config = this.props.config; - config.LdapSettings.Enable = this.refs.Enable.checked; - config.LdapSettings.LdapServer = this.refs.LdapServer.value.trim(); + this.state = Object.assign(this.state, { + enable: props.config.LdapSettings.Enable, + ldapServer: props.config.LdapSettings.LdapServer, + ldapPort: props.config.LdapSettings.LdapPort, + connectionSecurity: props.config.LdapSettings.ConnectionSecurity, + baseDN: props.config.LdapSettings.BaseDN, + bindUsername: props.config.LdapSettings.BindUsername, + bindPassword: props.config.LdapSettings.BindPassword, + userFilter: props.config.LdapSettings.UserFilter, + firstNameAttribute: props.config.LdapSettings.FirstNameAttribute, + lastNameAttribute: props.config.LdapSettings.LastNameAttribute, + nicknameAttribute: props.config.LdapSettings.NicknameAttribute, + emailAttribute: props.config.LdapSettings.EmailAttribute, + usernameAttribute: props.config.LdapSettings.UsernameAttribute, + idAttribute: props.config.LdapSettings.IdAttribute, + skipCertificateVerification: props.config.LdapSettings.SkipCertificateVerification, + queryTimeout: props.config.LdapSettings.QueryTimeout, + loginFieldName: props.config.LdapSettings.LoginFieldName + }); + } - let LdapPort = DEFAULT_LDAP_PORT; - if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.LdapPort).value, 10))) { - LdapPort = parseInt(ReactDOM.findDOMNode(this.refs.LdapPort).value, 10); - } - config.LdapSettings.LdapPort = LdapPort; - - config.LdapSettings.BaseDN = this.refs.BaseDN.value.trim(); - config.LdapSettings.BindUsername = this.refs.BindUsername.value.trim(); - config.LdapSettings.BindPassword = this.refs.BindPassword.value.trim(); - config.LdapSettings.FirstNameAttribute = this.refs.FirstNameAttribute.value.trim(); - config.LdapSettings.LastNameAttribute = this.refs.LastNameAttribute.value.trim(); - config.LdapSettings.NicknameAttribute = this.refs.NicknameAttribute.value.trim(); - config.LdapSettings.EmailAttribute = this.refs.EmailAttribute.value.trim(); - config.LdapSettings.UsernameAttribute = this.refs.UsernameAttribute.value.trim(); - config.LdapSettings.IdAttribute = this.refs.IdAttribute.value.trim(); - config.LdapSettings.UserFilter = this.refs.UserFilter.value.trim(); - config.LdapSettings.ConnectionSecurity = this.state.connectionSecurity.trim(); + getConfigFromState(config) { + config.LdapSettings.Enable = this.state.enable; + config.LdapSettings.LdapServer = this.state.ldapServer; + config.LdapSettings.LdapPort = this.parseIntNonZero(this.state.ldapPort); + config.LdapSettings.ConnectionSecurity = this.state.connectionSecurity; + config.LdapSettings.BaseDN = this.state.baseDN; + config.LdapSettings.BindUsername = this.state.bindUsername; + config.LdapSettings.BindPassword = this.state.bindPassword; + config.LdapSettings.UserFilter = this.state.userFilter; + config.LdapSettings.FirstNameAttribute = this.state.firstNameAttribute; + config.LdapSettings.LastNameAttribute = this.state.lastNameAttribute; + config.LdapSettings.NicknameAttribute = this.state.nicknameAttribute; + config.LdapSettings.EmailAttribute = this.state.emailAttribute; + config.LdapSettings.UsernameAttribute = this.state.usernameAttribute; + config.LdapSettings.IdAttribute = this.state.idAttribute; config.LdapSettings.SkipCertificateVerification = this.state.skipCertificateVerification; - config.LdapSettings.LoginFieldName = this.refs.LoginFieldName.value.trim(); + config.LdapSettings.QueryTimeout = this.parseIntNonZero(this.state.queryTimeout); + config.LdapSettings.LoginFieldName = this.state.loginFieldName; - let QueryTimeout = DEFAULT_QUERY_TIMEOUT; - if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.QueryTimeout).value, 10))) { - QueryTimeout = parseInt(ReactDOM.findDOMNode(this.refs.QueryTimeout).value, 10); - } - config.LdapSettings.QueryTimeout = QueryTimeout; + return config; + } - Client.saveConfig( - config, - () => { - AsyncClient.getConfig(); - this.setState({ - serverError: null, - saveNeeded: false - }); - $('#save-button').button('reset'); - }, - (err) => { - this.setState({ - serverError: err.message, - saveNeeded: true - }); - $('#save-button').button('reset'); - } + renderTitle() { + return ( +

    + +

    ); } - render() { - let serverError = ''; - if (this.state.serverError) { - serverError =
    ; - } - - let saveClass = 'btn'; - if (this.state.saveNeeded) { - saveClass = 'btn btn-primary'; - } + renderSettings() { const licenseEnabled = global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.LDAP === 'true'; + if (!licenseEnabled) { + return null; + } - let bannerContent; - if (licenseEnabled) { - bannerContent = ( + return ( + + + } + >

    @@ -127,540 +106,310 @@ class LdapSettings extends React.Component {

    - ); - } else { - bannerContent = ( -
    -
    - -
    -
    - ); - } - - return ( -
    - {bannerContent} -

    - -

    -
    -
    - -
    - - -

    - -

    -
    -
    -
    - -
    - -

    - -

    -
    -
    -
    - -
    - -

    - -

    -
    -
    - this.setState({connectionSecurity: e.target.value, saveNeeded: true})} - isDisabled={!this.state.enable} - /> -
    - -
    - -

    - -

    -
    -
    -
    - -
    - -

    - -

    -
    -
    -
    - -
    - -

    - -

    -
    -
    -
    - -
    - -

    - -

    -
    -
    -
    - -
    - -

    - -

    -
    -
    -
    - -
    - -

    - -

    -
    -
    -
    - -
    - -

    - -

    -
    -
    -
    - -
    - -

    - -

    -
    -
    -
    - -
    - -

    - -

    -
    -
    -
    - -
    - -

    - -

    -
    -
    - - } - currentValue={this.state.skipCertificateVerification} - isDisabled={!this.state.enable} - handleChange={(e) => this.setState({skipCertificateVerification: e.target.value.trim() === 'true', saveNeeded: true})} - helpText={ -

    - -

    - } - /> -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    -
    - {serverError} - -
    -
    - -
    + } + helpText={ + + } + value={this.state.enable} + onChange={this.handleChange} + /> + + } + placeholder={Utils.localizeMessage('admin.ldap.serverEx', 'Ex "10.0.0.23"')} + helpText={ + + } + value={this.state.ldapServer} + onChange={this.handleChange} + disabled={!this.state.enable} + /> + + } + placeholder={Utils.localizeMessage('admin.ldap.portEx', 'Ex "389"')} + helpText={ + + } + value={this.state.ldapPort} + onChange={this.handleChange} + disabled={!this.state.enable} + /> + + + } + placeholder={Utils.localizeMessage('admin.ldap.baseEx', 'Ex "ou=Unit Name,dc=corp,dc=example,dc=com"')} + helpText={ + + } + value={this.state.baseDN} + onChange={this.handleChange} + disabled={!this.state.enable} + /> + + } + helpText={ + + } + value={this.state.bindUsername} + onChange={this.handleChange} + disabled={!this.state.enable} + /> + + } + helpText={ + + } + value={this.state.bindPassword} + onChange={this.handleChange} + disabled={!this.state.enable} + /> + + } + placeholder={Utils.localizeMessage('admin.ldap.userFilterEx', 'Ex. "(objectClass=user)"')} + helpText={ + + } + value={this.state.userFilter} + onChange={this.handleChange} + disabled={!this.state.enable} + /> + + } + placeholder={Utils.localizeMessage('admin.ldap.firstnameAttrEx', 'Ex "givenName"')} + helpText={ + + } + value={this.state.firstNameAttribute} + onChange={this.handleChange} + disabled={!this.state.enable} + /> + + } + placeholder={Utils.localizeMessage('admin.ldap.lastnameAttrEx', 'Ex "sn"')} + helpText={ + + } + value={this.state.lastNameAttribute} + onChange={this.handleChange} + disabled={!this.state.enable} + /> + + } + placeholder={Utils.localizeMessage('admin.ldap.nicknameAttrEx', 'Ex "nickname"')} + helpText={ + + } + value={this.state.nicknameAttribute} + onChange={this.handleChange} + disabled={!this.state.enable} + /> + + } + placeholder={Utils.localizeMessage('admin.ldap.emailAttrEx', 'Ex "mail" or "userPrincipalName"')} + helpText={ + + } + value={this.state.emailAttribute} + onChange={this.handleChange} + disabled={!this.state.enable} + /> + + } + placeholder={Utils.localizeMessage('admin.ldap.usernameAttrEx', 'Ex "sAMAccountName"')} + helpText={ + + } + value={this.state.usernameAttribute} + onChange={this.handleChange} + disabled={!this.state.enable} + /> + + } + placeholder={Utils.localizeMessage('admin.ldap.idAttrEx', 'Ex "sAMAccountName"')} + helpText={ + + } + value={this.state.idAttribute} + onChange={this.handleChange} + disabled={!this.state.enable} + /> + + } + helpText={ + + } + value={this.state.skipCertificateVerification} + onChange={this.handleChange} + /> + + } + placeholder={Utils.localizeMessage('admin.ldap.queryEx', 'Ex "60"')} + helpText={ + + } + value={this.state.queryTimeout} + onChange={this.handleChange} + disabled={!this.state.enable} + /> + + } + placeholder={Utils.localizeMessage('admin.ldap.loginNameEx', 'Ex "LDAP Username"')} + helpText={ + + } + value={this.state.loginFieldName} + onChange={this.handleChange} + disabled={!this.state.enable} + /> +
    ); } -} -LdapSettings.defaultProps = { -}; - -LdapSettings.propTypes = { - config: React.PropTypes.object -}; - -export default LdapSettings; +} \ No newline at end of file diff --git a/webapp/components/admin_console/legal_and_support_settings.jsx b/webapp/components/admin_console/legal_and_support_settings.jsx index 9f72f5fdf6..cb152e4149 100644 --- a/webapp/components/admin_console/legal_and_support_settings.jsx +++ b/webapp/components/admin_console/legal_and_support_settings.jsx @@ -1,309 +1,166 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import $ from 'jquery'; -import ReactDOM from 'react-dom'; -import Client from 'utils/web_client.jsx'; -import * as AsyncClient from 'utils/async_client.jsx'; - -import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl'; - -var holders = defineMessages({ - saving: { - id: 'admin.support.saving', - defaultMessage: 'Saving Config...' - } -}); - import React from 'react'; -class LegalAndSupportSettings extends React.Component { +import AdminSettings from './admin_settings.jsx'; +import {FormattedMessage} from 'react-intl'; +import SettingsGroup from './settings_group.jsx'; +import TextSetting from './text_setting.jsx'; + +export default class LegalAndSupportSettings extends AdminSettings { constructor(props) { super(props); - this.handleChange = this.handleChange.bind(this); - this.handleSubmit = this.handleSubmit.bind(this); + this.getConfigFromState = this.getConfigFromState.bind(this); - this.state = { - saveNeeded: false, - serverError: null - }; + this.renderSettings = this.renderSettings.bind(this); + + this.state = Object.assign(this.state, { + termsOfServiceLink: props.config.SupportSettings.TermsOfServiceLink, + privacyPolicyLink: props.config.SupportSettings.PrivacyPolicyLink, + aboutLink: props.config.SupportSettings.AboutLink, + helpLink: props.config.SupportSettings.HelpLink, + reportAProblemLink: props.config.SupportSettings.ReportAProblemLink, + supportEmail: props.config.SupportSettings.SupportEmail + }); } - handleChange() { - var s = {saveNeeded: true, serverError: this.state.serverError}; - this.setState(s); + getConfigFromState(config) { + config.SupportSettings.TermsOfServiceLink = this.state.termsOfServiceLink; + config.SupportSettings.PrivacyPolicyLink = this.state.privacyPolicyLink; + config.SupportSettings.AboutLink = this.state.aboutLink; + config.SupportSettings.HelpLink = this.state.helpLink; + config.SupportSettings.ReportAProblemLink = this.state.reportAProblemLink; + config.SupportSettings.SupportEmail = this.state.supportEmail; + + return config; } - handleSubmit(e) { - e.preventDefault(); - $('#save-button').button('loading'); - - var config = this.props.config; - - config.SupportSettings.TermsOfServiceLink = ReactDOM.findDOMNode(this.refs.TermsOfServiceLink).value.trim(); - config.SupportSettings.PrivacyPolicyLink = ReactDOM.findDOMNode(this.refs.PrivacyPolicyLink).value.trim(); - config.SupportSettings.AboutLink = ReactDOM.findDOMNode(this.refs.AboutLink).value.trim(); - config.SupportSettings.HelpLink = ReactDOM.findDOMNode(this.refs.HelpLink).value.trim(); - config.SupportSettings.ReportAProblemLink = ReactDOM.findDOMNode(this.refs.ReportAProblemLink).value.trim(); - config.SupportSettings.SupportEmail = ReactDOM.findDOMNode(this.refs.SupportEmail).value.trim(); - - Client.saveConfig( - config, - () => { - AsyncClient.getConfig(); - this.setState({ - serverError: null, - saveNeeded: false - }); - $('#save-button').button('reset'); - }, - (err) => { - this.setState({ - serverError: err.message, - saveNeeded: true - }); - $('#save-button').button('reset'); - } - ); - } - - render() { - var serverError = ''; - if (this.state.serverError) { - serverError =
    ; - } - - var saveClass = 'btn'; - if (this.state.saveNeeded) { - saveClass = 'btn btn-primary'; - } - + renderTitle() { return ( -
    -
    -
    -

    - -

    -

    - -

    -
    -
    -

    - -

    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    -
    - {serverError} - -
    -
    - -
    -
    +

    + +

    ); } -} -LegalAndSupportSettings.propTypes = { - intl: intlShape.isRequired, - config: React.PropTypes.object -}; - -export default injectIntl(LegalAndSupportSettings); \ No newline at end of file + renderSettings() { + return ( + + } + > + + } + helpText={ + + } + value={this.state.termsOfServiceLink} + onChange={this.handleChange} + /> + + } + helpText={ + + } + value={this.state.privacyPolicyLink} + onChange={this.handleChange} + /> + + } + helpText={ + + } + value={this.state.aboutLink} + onChange={this.handleChange} + /> + + } + helpText={ + + } + value={this.state.helpLink} + onChange={this.handleChange} + /> + + } + helpText={ + + } + value={this.state.reportAProblemLink} + onChange={this.handleChange} + /> + + } + helpText={ + + } + value={this.state.supportEmail} + onChange={this.handleChange} + /> + + ); + } +} \ No newline at end of file diff --git a/webapp/components/admin_console/log_settings.jsx b/webapp/components/admin_console/log_settings.jsx index 9229c62bc5..fa29074d83 100644 --- a/webapp/components/admin_console/log_settings.jsx +++ b/webapp/components/admin_console/log_settings.jsx @@ -1,418 +1,246 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import $ from 'jquery'; -import ReactDOM from 'react-dom'; -import Client from 'utils/web_client.jsx'; -import * as AsyncClient from 'utils/async_client.jsx'; - -import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl'; - -const holders = defineMessages({ - locationPlaceholder: { - id: 'admin.log.locationPlaceholder', - defaultMessage: 'Enter your file location' - }, - formatPlaceholder: { - id: 'admin.log.formatPlaceholder', - defaultMessage: 'Enter your file format' - }, - saving: { - id: 'admin.log.saving', - defaultMessage: 'Saving Config...' - } -}); - import React from 'react'; -class LogSettings extends React.Component { +import * as Utils from 'utils/utils.jsx'; + +import AdminSettings from './admin_settings.jsx'; +import BooleanSetting from './boolean_setting.jsx'; +import DropdownSetting from './dropdown_setting.jsx'; +import {FormattedMessage} from 'react-intl'; +import SettingsGroup from './settings_group.jsx'; +import TextSetting from './text_setting.jsx'; + +export default class LogSettings extends AdminSettings { constructor(props) { super(props); - this.handleChange = this.handleChange.bind(this); - this.handleSubmit = this.handleSubmit.bind(this); + this.getConfigFromState = this.getConfigFromState.bind(this); - this.state = { - consoleEnable: this.props.config.LogSettings.EnableConsole, - fileEnable: this.props.config.LogSettings.EnableFile, - saveNeeded: false, - serverError: null - }; + this.renderSettings = this.renderSettings.bind(this); + + this.state = Object.assign(this.state, { + enableConsole: props.config.LogSettings.EnableConsole, + consoleLevel: props.config.LogSettings.ConsoleLevel, + enableFile: props.config.LogSettings.EnableFile, + fileLevel: props.config.LogSettings.FileLevel, + fileLocation: props.config.LogSettings.FileLocation, + fileFormat: props.config.LogSettings.FileFormat + }); } - handleChange(action) { - var s = {saveNeeded: true, serverError: this.state.serverError}; + getConfigFromState(config) { + config.LogSettings.EnableConsole = this.state.enableConsole; + config.LogSettings.ConsoleLevel = this.state.consoleLevel; + config.LogSettings.EnableFile = this.state.enableFile; + config.LogSettings.FileLevel = this.state.fileLevel; + config.LogSettings.FileLocation = this.state.fileLocation; + config.LogSettings.FileFormat = this.state.fileFormat; - if (action === 'console_true') { - s.consoleEnable = true; - } - - if (action === 'console_false') { - s.consoleEnable = false; - } - - if (action === 'file_true') { - s.fileEnable = true; - } - - if (action === 'file_false') { - s.fileEnable = false; - } - - this.setState(s); + return config; } - handleSubmit(e) { - e.preventDefault(); - $('#save-button').button('loading'); - - var config = this.props.config; - config.LogSettings.EnableConsole = ReactDOM.findDOMNode(this.refs.consoleEnable).checked; - config.LogSettings.ConsoleLevel = ReactDOM.findDOMNode(this.refs.consoleLevel).value; - config.LogSettings.EnableFile = ReactDOM.findDOMNode(this.refs.fileEnable).checked; - config.LogSettings.FileLevel = ReactDOM.findDOMNode(this.refs.fileLevel).value; - config.LogSettings.FileLocation = ReactDOM.findDOMNode(this.refs.fileLocation).value.trim(); - config.LogSettings.FileFormat = ReactDOM.findDOMNode(this.refs.fileFormat).value.trim(); - - Client.saveConfig( - config, - () => { - AsyncClient.getConfig(); - this.setState({ - consoleEnable: config.LogSettings.EnableConsole, - fileEnable: config.LogSettings.EnableFile, - serverError: null, - saveNeeded: false - }); - $('#save-button').button('reset'); - }, - (err) => { - this.setState({ - consoleEnable: config.LogSettings.EnableConsole, - fileEnable: config.LogSettings.EnableFile, - serverError: err.message, - saveNeeded: true - }); - $('#save-button').button('reset'); - } + renderTitle() { + return ( +

    + +

    ); } - render() { - const {formatMessage} = this.props.intl; - var serverError = ''; - if (this.state.serverError) { - serverError =
    ; - } - - var saveClass = 'btn'; - if (this.state.saveNeeded) { - saveClass = 'btn btn-primary'; - } + renderSettings() { + const logLevels = [ + {value: 'DEBUG', text: 'DEBUG'}, + {value: 'INFO', text: 'INFO'}, + {value: 'ERROR', text: 'ERROR'} + ]; return ( -
    -

    + -

    -
    + + } + helpText={ + + } + value={this.state.enableConsole} + onChange={this.handleChange} + /> + + } + value={this.state.consoleLevel} + onChange={this.handleChange} + disabled={!this.state.enableConsole} + helpText={ + + } + /> + + } + helpText={ + + } + value={this.state.enableFile} + onChange={this.handleChange} + /> + + } + value={this.state.fileLevel} + onChange={this.handleChange} + disabled={!this.state.enableFile} + helpText={ + + } + /> + + } + placeholder={Utils.localizeMessage('admin.log.locationPlaceholder', 'Enter your file location')} + helpText={ + + } + value={this.state.fileLocation} + onChange={this.handleChange} + disabled={!this.state.enableFile} + /> + + } + placeholder={Utils.localizeMessage('admin.log.formatPlaceholder', 'Enter your file format')} + helpText={this.renderFileFormatHelpText()} + value={this.state.fileFormat} + onChange={this.handleChange} + disabled={!this.state.enableFile} + /> + + ); + } + + renderFileFormatHelpText() { + return ( +
    + + - -
    - -
    -
    + + + + + + + + + + + + + + + + + + + +
    {'%T'} - -
    {'%D'} - -

    +

    {'%d'} -

    - - - -
    - -
    - -

    +

    {'%L'} -

    - - - -
    - -
    -
    {'%S'} - -
    {'%M'} - -

    - -

    - - - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -
    - -
    - - - - - - - - - -
    {'%T'} - -
    {'%D'} - -
    {'%d'} - -
    {'%L'} - -
    {'%S'} - -
    {'%M'} - -
    -
    -
    -
    -
    - -
    -
    - {serverError} - -
    -
    - - +
    ); } -} - -LogSettings.propTypes = { - intl: intlShape.isRequired, - config: React.PropTypes.object -}; - -export default injectIntl(LogSettings); +} \ No newline at end of file diff --git a/webapp/components/admin_console/login_settings.jsx b/webapp/components/admin_console/login_settings.jsx new file mode 100644 index 0000000000..f473d8f564 --- /dev/null +++ b/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 ( +

    + +

    + ); + } + + renderSettings() { + let mfaSetting = null; + if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.MFA === 'true') { + mfaSetting = ( + + } + helpText={ + + } + value={this.state.enableMultifactorAuthentication} + onChange={this.handleChange} + /> + ); + } + + return ( + + } + > + + } + helpText={ + + } + value={this.state.passwordResetSalt} + onChange={this.handleChange} + disabled={this.state.sendEmailNotifications} + disabledText={ + + } + /> + + } + placeholder={Utils.localizeMessage('admin.service.attemptExample', 'Ex "10"')} + helpText={ + + } + value={this.state.maximumLoginAttempts} + onChange={this.handleChange} + /> + {mfaSetting} + + ); + } +} \ No newline at end of file diff --git a/webapp/components/admin_console/logs.jsx b/webapp/components/admin_console/logs.jsx index f2c6d92c3f..ad0277b7f9 100644 --- a/webapp/components/admin_console/logs.jsx +++ b/webapp/components/admin_console/logs.jsx @@ -99,4 +99,4 @@ export default class Logs extends React.Component {
    ); } -} \ No newline at end of file +} diff --git a/webapp/components/admin_console/privacy_settings.jsx b/webapp/components/admin_console/privacy_settings.jsx index 8759472a29..8905e57ef8 100644 --- a/webapp/components/admin_console/privacy_settings.jsx +++ b/webapp/components/admin_console/privacy_settings.jsx @@ -1,215 +1,90 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import $ from 'jquery'; -import ReactDOM from 'react-dom'; -import Client from 'utils/web_client.jsx'; -import * as AsyncClient from 'utils/async_client.jsx'; - -import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl'; - -const holders = defineMessages({ - saving: { - id: 'admin.privacy.saving', - defaultMessage: 'Saving Config...' - } -}); - import React from 'react'; -class PrivacySettings extends React.Component { +import AdminSettings from './admin_settings.jsx'; +import BooleanSetting from './boolean_setting.jsx'; +import {FormattedMessage} from 'react-intl'; +import SettingsGroup from './settings_group.jsx'; + +export default class PrivacySettings extends AdminSettings { constructor(props) { super(props); - this.handleChange = this.handleChange.bind(this); - this.handleSubmit = this.handleSubmit.bind(this); + this.getConfigFromState = this.getConfigFromState.bind(this); - this.state = { - saveNeeded: false, - serverError: null - }; + this.renderSettings = this.renderSettings.bind(this); + + this.state = Object.assign(this.state, { + showEmailAddress: props.config.PrivacySettings.ShowEmailAddress, + showFullName: props.config.PrivacySettings.ShowFullName + }); } - handleChange() { - var s = {saveNeeded: true, serverError: this.state.serverError}; + getConfigFromState(config) { + config.PrivacySettings.ShowEmailAddress = this.state.showEmailAddress; + config.PrivacySettings.ShowFullName = this.state.showFullName; - this.setState(s); + return config; } - handleSubmit(e) { - e.preventDefault(); - $('#save-button').button('loading'); - - var config = this.props.config; - config.PrivacySettings.ShowEmailAddress = ReactDOM.findDOMNode(this.refs.ShowEmailAddress).checked; - config.PrivacySettings.ShowFullName = ReactDOM.findDOMNode(this.refs.ShowFullName).checked; - - Client.saveConfig( - config, - () => { - AsyncClient.getConfig(); - this.setState({ - serverError: null, - saveNeeded: false - }); - $('#save-button').button('reset'); - }, - (err) => { - this.setState({ - serverError: err.message, - saveNeeded: true - }); - $('#save-button').button('reset'); - } - ); - } - - render() { - var serverError = ''; - if (this.state.serverError) { - serverError =
    ; - } - - var saveClass = 'btn'; - if (this.state.saveNeeded) { - saveClass = 'btn btn-primary'; - } - + renderTitle() { return ( -
    -

    - -

    -
    - -
    - -
    - - -

    - -

    -
    -
    - -
    - -
    - - -

    - -

    -
    -
    - -
    -
    - {serverError} - -
    -
    - -
    -
    +

    + +

    ); } -} -PrivacySettings.propTypes = { - intl: intlShape.isRequired, - config: React.PropTypes.object -}; - -export default injectIntl(PrivacySettings); + renderSettings() { + return ( + + } + > + + } + helpText={ + + } + value={this.state.showEmailAddress} + onChange={this.handleChange} + /> + + } + helpText={ + + } + value={this.state.showFullName} + onChange={this.handleChange} + /> + + ); + } +} \ No newline at end of file diff --git a/webapp/components/admin_console/public_link_settings.jsx b/webapp/components/admin_console/public_link_settings.jsx new file mode 100644 index 0000000000..9024261fa7 --- /dev/null +++ b/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 ( +

    + +

    + ); + } + + renderSettings() { + return ( + + } + > + + } + helpText={ + + } + value={this.state.enablePublicLink} + onChange={this.handleChange} + /> + + } + helpText={ + + } + value={this.state.publicLinkSalt} + onChange={this.handleChange} + /> + + ); + } +} \ No newline at end of file diff --git a/webapp/components/admin_console/push_settings.jsx b/webapp/components/admin_console/push_settings.jsx new file mode 100644 index 0000000000..660c23e97c --- /dev/null +++ b/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 ( +

    + +

    + ); + } + + 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 = ( + + ); + } else if (this.state.pushNotificationServerType === PUSH_NOTIFICATIONS_MHPNS) { + pushServerHelpText = ( + + ); + } else if (this.state.pushNotificationServerType === PUSH_NOTIFICATIONS_MTPNS) { + pushServerHelpText = ( + + ); + } else { + pushServerHelpText = ( + + ); + } + + let tosCheckbox; + if (this.state.pushNotificationServerType === PUSH_NOTIFICATIONS_MHPNS) { + tosCheckbox = ( +
    +
    +
    + + +
    +
    + ); + } + + return ( + + } + > + + } + value={this.state.pushNotificationServerType} + onChange={this.handleChange} + helpText={sendHelpText} + /> + {tosCheckbox} + + } + 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} + /> + + } + value={this.state.pushNotificationContents} + onChange={this.handleChange} + disabled={this.state.pushNotificationServerType === PUSH_NOTIFICATIONS_OFF} + helpText={ + + } + /> + + ); + } +} \ No newline at end of file diff --git a/webapp/components/admin_console/rate_settings.jsx b/webapp/components/admin_console/rate_settings.jsx index 5eb099b8a9..60818aaf9d 100644 --- a/webapp/components/admin_console/rate_settings.jsx +++ b/webapp/components/admin_console/rate_settings.jsx @@ -1,371 +1,158 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import $ from 'jquery'; -import ReactDOM from 'react-dom'; -import Client from 'utils/web_client.jsx'; -import * as AsyncClient from 'utils/async_client.jsx'; - -import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl'; - -const holders = defineMessages({ - queriesExample: { - id: 'admin.rate.queriesExample', - defaultMessage: 'Ex "10"' - }, - memoryExample: { - id: 'admin.rate.memoryExample', - defaultMessage: 'Ex "10000"' - }, - httpHeaderExample: { - id: 'admin.rate.httpHeaderExample', - defaultMessage: 'Ex "X-Real-IP", "X-Forwarded-For"' - }, - saving: { - id: 'admin.rate.saving', - defaultMessage: 'Saving Config...' - } -}); - import React from 'react'; -class RateSettings extends React.Component { +import * as Utils from 'utils/utils.jsx'; + +import AdminSettings from './admin_settings.jsx'; +import BooleanSetting from './boolean_setting.jsx'; +import {FormattedMessage} from 'react-intl'; +import SettingsGroup from './settings_group.jsx'; +import TextSetting from './text_setting.jsx'; + +export default class RateSettings extends AdminSettings { constructor(props) { super(props); - this.handleChange = this.handleChange.bind(this); - this.handleSubmit = this.handleSubmit.bind(this); + this.getConfigFromState = this.getConfigFromState.bind(this); - this.state = { - EnableRateLimiter: this.props.config.RateLimitSettings.EnableRateLimiter, - VaryByRemoteAddr: this.props.config.RateLimitSettings.VaryByRemoteAddr, - saveNeeded: false, - serverError: null - }; + this.renderSettings = this.renderSettings.bind(this); + + this.state = Object.assign(this.state, { + enableRateLimiter: props.config.RateLimitSettings.EnableRateLimiter, + perSec: props.config.RateLimitSettings.PerSec, + memoryStoreSize: props.config.RateLimitSettings.MemoryStoreSize, + varyByRemoteAddr: props.config.RateLimitSettings.VaryByRemoteAddr, + varyByHeader: props.config.RateLimitSettings.VaryByHeader + }); } - handleChange(action) { - var s = {saveNeeded: true, serverError: this.state.serverError}; + getConfigFromState(config) { + config.RateLimitSettings.EnableRateLimiter = this.state.enableRateLimiter; + config.RateLimitSettings.PerSec = this.parseIntNonZero(this.state.perSec); + config.RateLimitSettings.MemoryStoreSize = this.parseIntNonZero(this.state.memoryStoreSize); + config.RateLimitSettings.VaryByRemoteAddr = this.state.varyByRemoteAddr; + config.RateLimitSettings.VaryByHeader = this.state.varyByHeader; - if (action === 'EnableRateLimiterTrue') { - s.EnableRateLimiter = true; - } - - if (action === 'EnableRateLimiterFalse') { - s.EnableRateLimiter = false; - } - - if (action === 'VaryByRemoteAddrTrue') { - s.VaryByRemoteAddr = true; - } - - if (action === 'VaryByRemoteAddrFalse') { - s.VaryByRemoteAddr = false; - } - - this.setState(s); + return config; } - handleSubmit(e) { - e.preventDefault(); - $('#save-button').button('loading'); - - var config = this.props.config; - config.RateLimitSettings.EnableRateLimiter = ReactDOM.findDOMNode(this.refs.EnableRateLimiter).checked; - config.RateLimitSettings.VaryByRemoteAddr = ReactDOM.findDOMNode(this.refs.VaryByRemoteAddr).checked; - config.RateLimitSettings.VaryByHeader = ReactDOM.findDOMNode(this.refs.VaryByHeader).value.trim(); - - var PerSec = 10; - if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.PerSec).value, 10))) { - PerSec = parseInt(ReactDOM.findDOMNode(this.refs.PerSec).value, 10); - } - config.RateLimitSettings.PerSec = PerSec; - ReactDOM.findDOMNode(this.refs.PerSec).value = PerSec; - - var MemoryStoreSize = 10000; - if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.MemoryStoreSize).value, 10))) { - MemoryStoreSize = parseInt(ReactDOM.findDOMNode(this.refs.MemoryStoreSize).value, 10); - } - config.RateLimitSettings.MemoryStoreSize = MemoryStoreSize; - ReactDOM.findDOMNode(this.refs.MemoryStoreSize).value = MemoryStoreSize; - - Client.saveConfig( - config, - () => { - AsyncClient.getConfig(); - this.setState({ - serverError: null, - saveNeeded: false - }); - $('#save-button').button('reset'); - }, - (err) => { - this.setState({ - serverError: err.message, - saveNeeded: true - }); - $('#save-button').button('reset'); - } + renderTitle() { + return ( +

    + +

    ); } - render() { - const {formatMessage} = this.props.intl; - var serverError = ''; - if (this.state.serverError) { - serverError =
    ; - } - - var saveClass = 'btn'; - if (this.state.saveNeeded) { - saveClass = 'btn btn-primary'; - } - + renderSettings() { return ( -
    - +
    -

    - -

    -

    - -

    +
    - -

    - -

    -
    - -
    - -
    - - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    -
    - {serverError} - -
    -
    - -
    -
    + + } + helpText={ + + } + value={this.state.enableRateLimiter} + onChange={this.handleChange} + /> + + } + placeholder={Utils.localizeMessage('admin.rate.queriesExample', 'Ex "10"')} + helpText={ + + } + value={this.state.perSec} + onChange={this.handleChange} + disabled={!this.state.enableRateLimiter} + /> + + } + placeholder={Utils.localizeMessage('admin.rate.memoryExample', 'Ex "10000"')} + helpText={ + + } + value={this.state.memoryStoreSize} + onChange={this.handleChange} + disabled={!this.state.enableRateLimiter} + /> + + } + helpText={ + + } + value={this.state.varyByRemoteAddr} + onChange={this.handleChange} + disabled={!this.state.enableRateLimiter} + /> + + } + placeholder={Utils.localizeMessage('admin.rate.httpHeaderExample', 'Ex "X-Real-IP", "X-Forwarded-For"')} + helpText={ + + } + value={this.state.varyByHeader} + onChange={this.handleChange} + disabled={!this.state.enableRateLimiter || this.state.varyByRemoteAddr} + /> + ); } -} - -RateSettings.propTypes = { - intl: intlShape.isRequired, - config: React.PropTypes.object -}; - -export default injectIntl(RateSettings); +} \ No newline at end of file diff --git a/webapp/components/admin_console/save_button.jsx b/webapp/components/admin_console/save_button.jsx new file mode 100644 index 0000000000..18bb6e96dc --- /dev/null +++ b/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 = ( + + + + + ); + } else { + contents = ( + + ); + } + + let className = 'save-button btn'; + if (!disabled) { + className += ' btn-primary'; + } + + return ( + + ); + } +} diff --git a/webapp/components/admin_console/service_settings.jsx b/webapp/components/admin_console/service_settings.jsx deleted file mode 100644 index dfd19d0579..0000000000 --- a/webapp/components/admin_console/service_settings.jsx +++ /dev/null @@ -1,1042 +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 {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl'; - -const DefaultSessionLength = 30; -const DefaultMaximumLoginAttempts = 10; -const DefaultSessionCacheInMinutes = 10; - -var holders = defineMessages({ - listenExample: { - id: 'admin.service.listenExample', - defaultMessage: 'Ex ":8065"' - }, - attemptExample: { - id: 'admin.service.attemptExample', - defaultMessage: 'Ex "10"' - }, - segmentExample: { - id: 'admin.service.segmentExample', - defaultMessage: 'Ex "g3fgGOXJAQ43QV7rAh6iwQCkV4cA1Gs"' - }, - googleExample: { - id: 'admin.service.googleExample', - defaultMessage: 'Ex "7rAh6iwQCkV4cA1Gsg3fgGOXJAQ43QV"' - }, - sessionDaysEx: { - id: 'admin.service.sessionDaysEx', - defaultMessage: 'Ex "30"' - }, - corsExample: { - id: 'admin.service.corsEx', - defaultMessage: 'http://example.com' - }, - saving: { - id: 'admin.service.saving', - defaultMessage: 'Saving Config...' - } -}); - -import React from 'react'; - -class ServiceSettings extends React.Component { - constructor(props) { - super(props); - - this.handleChange = this.handleChange.bind(this); - this.handleSubmit = this.handleSubmit.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.ServiceSettings.ListenAddress = ReactDOM.findDOMNode(this.refs.ListenAddress).value.trim(); - if (config.ServiceSettings.ListenAddress === '') { - config.ServiceSettings.ListenAddress = ':8065'; - ReactDOM.findDOMNode(this.refs.ListenAddress).value = config.ServiceSettings.ListenAddress; - } - - config.ServiceSettings.SegmentDeveloperKey = ReactDOM.findDOMNode(this.refs.SegmentDeveloperKey).value.trim(); - config.ServiceSettings.GoogleDeveloperKey = ReactDOM.findDOMNode(this.refs.GoogleDeveloperKey).value.trim(); - config.ServiceSettings.EnableIncomingWebhooks = ReactDOM.findDOMNode(this.refs.EnableIncomingWebhooks).checked; - config.ServiceSettings.EnableOutgoingWebhooks = ReactDOM.findDOMNode(this.refs.EnableOutgoingWebhooks).checked; - config.ServiceSettings.EnablePostUsernameOverride = ReactDOM.findDOMNode(this.refs.EnablePostUsernameOverride).checked; - config.ServiceSettings.EnablePostIconOverride = ReactDOM.findDOMNode(this.refs.EnablePostIconOverride).checked; - config.ServiceSettings.EnableTesting = ReactDOM.findDOMNode(this.refs.EnableTesting).checked; - config.ServiceSettings.EnableDeveloper = ReactDOM.findDOMNode(this.refs.EnableDeveloper).checked; - config.ServiceSettings.EnableSecurityFixAlert = ReactDOM.findDOMNode(this.refs.EnableSecurityFixAlert).checked; - config.ServiceSettings.EnableInsecureOutgoingConnections = ReactDOM.findDOMNode(this.refs.EnableInsecureOutgoingConnections).checked; - config.ServiceSettings.EnableCommands = ReactDOM.findDOMNode(this.refs.EnableCommands).checked; - config.ServiceSettings.EnableOnlyAdminIntegrations = ReactDOM.findDOMNode(this.refs.EnableOnlyAdminIntegrations).checked; - - if (this.refs.EnableMultifactorAuthentication) { - config.ServiceSettings.EnableMultifactorAuthentication = ReactDOM.findDOMNode(this.refs.EnableMultifactorAuthentication).checked; - } - - //config.ServiceSettings.EnableOAuthServiceProvider = ReactDOM.findDOMNode(this.refs.EnableOAuthServiceProvider).checked; - - var MaximumLoginAttempts = DefaultMaximumLoginAttempts; - if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.MaximumLoginAttempts).value, 10))) { - MaximumLoginAttempts = parseInt(ReactDOM.findDOMNode(this.refs.MaximumLoginAttempts).value, 10); - } - if (MaximumLoginAttempts < 1) { - MaximumLoginAttempts = 1; - } - config.ServiceSettings.MaximumLoginAttempts = MaximumLoginAttempts; - ReactDOM.findDOMNode(this.refs.MaximumLoginAttempts).value = MaximumLoginAttempts; - - var SessionLengthWebInDays = DefaultSessionLength; - if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.SessionLengthWebInDays).value, 10))) { - SessionLengthWebInDays = parseInt(ReactDOM.findDOMNode(this.refs.SessionLengthWebInDays).value, 10); - } - if (SessionLengthWebInDays < 1) { - SessionLengthWebInDays = 1; - } - config.ServiceSettings.SessionLengthWebInDays = SessionLengthWebInDays; - ReactDOM.findDOMNode(this.refs.SessionLengthWebInDays).value = SessionLengthWebInDays; - - var SessionLengthMobileInDays = DefaultSessionLength; - if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.SessionLengthMobileInDays).value, 10))) { - SessionLengthMobileInDays = parseInt(ReactDOM.findDOMNode(this.refs.SessionLengthMobileInDays).value, 10); - } - if (SessionLengthMobileInDays < 1) { - SessionLengthMobileInDays = 1; - } - config.ServiceSettings.SessionLengthMobileInDays = SessionLengthMobileInDays; - ReactDOM.findDOMNode(this.refs.SessionLengthMobileInDays).value = SessionLengthMobileInDays; - - var SessionLengthSSOInDays = DefaultSessionLength; - if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.SessionLengthSSOInDays).value, 10))) { - SessionLengthSSOInDays = parseInt(ReactDOM.findDOMNode(this.refs.SessionLengthSSOInDays).value, 10); - } - if (SessionLengthSSOInDays < 1) { - SessionLengthSSOInDays = 1; - } - config.ServiceSettings.SessionLengthSSOInDays = SessionLengthSSOInDays; - ReactDOM.findDOMNode(this.refs.SessionLengthSSOInDays).value = SessionLengthSSOInDays; - - var SessionCacheInMinutes = DefaultSessionCacheInMinutes; - if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.SessionCacheInMinutes).value, 10))) { - SessionCacheInMinutes = parseInt(ReactDOM.findDOMNode(this.refs.SessionCacheInMinutes).value, 10); - } - if (SessionCacheInMinutes < -1) { - SessionCacheInMinutes = -1; - } - config.ServiceSettings.SessionCacheInMinutes = SessionCacheInMinutes; - ReactDOM.findDOMNode(this.refs.SessionCacheInMinutes).value = SessionCacheInMinutes; - - config.ServiceSettings.AllowCorsFrom = ReactDOM.findDOMNode(this.refs.AllowCorsFrom).value.trim(); - - Client.saveConfig( - config, - () => { - AsyncClient.getConfig(); - this.setState({ - serverError: null, - saveNeeded: false - }); - $('#save-button').button('reset'); - }, - (err) => { - this.setState({ - serverError: err.message, - saveNeeded: true - }); - $('#save-button').button('reset'); - } - ); - } - - render() { - const {formatMessage} = this.props.intl; - var serverError = ''; - if (this.state.serverError) { - serverError =
    ; - } - - var saveClass = 'btn'; - if (this.state.saveNeeded) { - saveClass = 'btn btn-primary'; - } - - let mfaSetting; - if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.MFA === 'true') { - mfaSetting = ( -
    - -
    - - -

    - -

    -
    -
    - ); - } - - return ( -
    - -

    - -

    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - - -

    - -

    -
    -
    - -
    - -
    - - -

    - -

    -
    -
    - -
    - -
    - - -

    - -

    -
    -
    - -
    - -
    - - -

    - -

    -
    -
    - -
    - -
    - - -

    - -

    -
    -
    - -
    - -
    - - -

    - -

    -
    -
    - -
    - -
    - - -

    - -

    -
    -
    - -
    - -
    - - -

    - -

    -
    -
    - -
    - -
    - - -

    - -

    -
    -
    - -
    - -
    - - -

    - -

    -
    -
    - - {mfaSetting} - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    -
    - {serverError} - -
    -
    - -
    -
    - ); - } -} - -//
    -// -//
    -// -// -//

    {'When enabled Mattermost will act as an OAuth2 Provider. Changing this will require a server restart before taking effect.'}

    -//
    -//
    - -ServiceSettings.propTypes = { - intl: intlShape.isRequired, - config: React.PropTypes.object -}; - -export default injectIntl(ServiceSettings); diff --git a/webapp/components/admin_console/session_settings.jsx b/webapp/components/admin_console/session_settings.jsx new file mode 100644 index 0000000000..79f3c7ee5c --- /dev/null +++ b/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 ( +

    + +

    + ); + } + + renderSettings() { + return ( + + } + > + + } + placeholder={Utils.localizeMessage('admin.service.sessionDaysEx', 'Ex "30"')} + helpText={ + + } + value={this.state.sessionLengthWebInDays} + onChange={this.handleChange} + /> + + } + placeholder={Utils.localizeMessage('admin.service.sessionDaysEx', 'Ex "30"')} + helpText={ + + } + value={this.state.sessionLengthMobileInDays} + onChange={this.handleChange} + /> + + } + placeholder={Utils.localizeMessage('admin.service.sessionDaysEx', 'Ex "30"')} + helpText={ + + } + value={this.state.sessionLengthSSOInDays} + onChange={this.handleChange} + /> + + } + placeholder={Utils.localizeMessage('admin.service.sessionDaysEx', 'Ex "30"')} + helpText={ + + } + value={this.state.sessionCacheInMinutes} + onChange={this.handleChange} + /> + + ); + } +} \ No newline at end of file diff --git a/webapp/components/admin_console/setting.jsx b/webapp/components/admin_console/setting.jsx index 7dee6c8dc2..024111fa5b 100644 --- a/webapp/components/admin_console/setting.jsx +++ b/webapp/components/admin_console/setting.jsx @@ -5,20 +5,19 @@ import React from 'react'; export default class Setting extends React.Component { render() { - let marginClass = ''; - if (this.props.margin === 'small') { - marginClass = ' form-group--small'; - } - return ( -
    +
    {this.props.children} +
    + {this.props.helpText} +
    ); @@ -28,7 +27,8 @@ Setting.defaultProps = { }; Setting.propTypes = { + inputId: React.PropTypes.string, label: React.PropTypes.node.isRequired, children: React.PropTypes.node.isRequired, - margin: React.PropTypes.oneOf(['', 'small']) + helpText: React.PropTypes.node }; diff --git a/webapp/components/admin_console/settings_group.jsx b/webapp/components/admin_console/settings_group.jsx new file mode 100644 index 0000000000..10b3444d8d --- /dev/null +++ b/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 = ( +

    + {this.props.header} +

    + ); + } + + return ( +
    + {header} + {this.props.children} +
    + ); + } +} diff --git a/webapp/components/admin_console/signup_settings.jsx b/webapp/components/admin_console/signup_settings.jsx new file mode 100644 index 0000000000..fd64e4ea53 --- /dev/null +++ b/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 ( +

    + +

    + ); + } + + renderSettings() { + return ( + + } + > + + } + helpText={ + + } + value={this.state.requireEmailVerification} + onChange={this.handleChange} + disabled={this.state.sendEmailNotifications} + disabledText={ + + } + /> + + } + helpText={ + + } + value={this.state.inviteSalt} + onChange={this.handleChange} + disabled={this.state.sendEmailNotifications} + disabledText={ + + } + /> + + } + helpText={ + + } + value={this.state.enableOpenServer} + onChange={this.handleChange} + /> + + ); + } +} \ No newline at end of file diff --git a/webapp/components/admin_console/sql_settings.jsx b/webapp/components/admin_console/sql_settings.jsx deleted file mode 100644 index a6e09b4a0e..0000000000 --- a/webapp/components/admin_console/sql_settings.jsx +++ /dev/null @@ -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 =
    ; - } - - 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 ( -
    - -
    -
    -

    - -

    -

    - -

    -
    -
    - -

    - -

    -
    - -
    - -
    -

    {this.props.config.SqlSettings.DriverName}

    -
    -
    - -
    - -
    -

    {dataSource}

    -
    -
    - -
    - -
    -

    {dataSourceReplicas}

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    -
    - -
    - -
    - -

    - -

    -
    - -
    -
    -
    - -
    - -
    - - -

    - -

    -
    -
    - -
    -
    - {serverError} - -
    -
    - -
    -
    - ); - } -} - -SqlSettings.propTypes = { - intl: intlShape.isRequired, - config: React.PropTypes.object -}; - -export default injectIntl(SqlSettings); diff --git a/webapp/components/admin_console/storage_settings.jsx b/webapp/components/admin_console/storage_settings.jsx new file mode 100644 index 0000000000..339876b18b --- /dev/null +++ b/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 ( +

    + +

    + ); + } + + renderSettings() { + return ( + + } + > + + } + value={this.state.driverName} + onChange={this.handleChange} + /> + + } + placeholder={Utils.localizeMessage('admin.image.localExample', 'Ex "./data/"')} + helpText={ + + } + value={this.state.directory} + onChange={this.handleChange} + disabled={this.state.driverName !== DRIVER_LOCAL} + /> + + } + placeholder={Utils.localizeMessage('admin.image.amazonS3IdExample', 'Ex "AKIADTOVBGERKLCBV"')} + helpText={ + + } + value={this.state.amazonS3AccessKeyId} + onChange={this.handleChange} + disabled={this.state.driverName !== DRIVER_S3} + /> + + } + placeholder={Utils.localizeMessage('admin.image.amazonS3SecretExample', 'Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"')} + helpText={ + + } + value={this.state.amazonS3SecretAccessKey} + onChange={this.handleChange} + disabled={this.state.driverName !== DRIVER_S3} + /> + + } + placeholder={Utils.localizeMessage('admin.image.amazonS3BucketExample', 'Ex "mattermost-media"')} + helpText={ + + } + value={this.state.amazonS3Bucket} + onChange={this.handleChange} + disabled={this.state.driverName !== DRIVER_S3} + /> + + } + placeholder={Utils.localizeMessage('admin.image.amazonS3RegionExample', 'Ex "us-east-1"')} + helpText={ + + } + value={this.state.amazonS3Region} + onChange={this.handleChange} + disabled={this.state.driverName !== DRIVER_S3} + /> + + ); + } +} \ No newline at end of file diff --git a/webapp/components/admin_console/team_users.jsx b/webapp/components/admin_console/team_users.jsx index 00aa1a8322..89fbd0e3ad 100644 --- a/webapp/components/admin_console/team_users.jsx +++ b/webapp/components/admin_console/team_users.jsx @@ -1,7 +1,9 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. +import AdminStore from 'stores/admin_store.jsx'; import Client from 'utils/web_client.jsx'; +import FormError from 'components/form_error.jsx'; import LoadingScreen from '../loading_screen.jsx'; import UserItem from './user_item.jsx'; import ResetPasswordModal from './reset_password_modal.jsx'; @@ -11,9 +13,17 @@ import {FormattedMessage} from 'react-intl'; import React from 'react'; export default class UserList extends React.Component { + static get propTypes() { + return { + params: React.PropTypes.object.isRequired + }; + } + constructor(props) { super(props); + this.onAllTeamsChange = this.onAllTeamsChange.bind(this); + this.getTeamProfiles = this.getTeamProfiles.bind(this); this.getCurrentTeamProfiles = this.getCurrentTeamProfiles.bind(this); this.doPasswordReset = this.doPasswordReset.bind(this); @@ -22,7 +32,7 @@ export default class UserList extends React.Component { this.getTeamMemberForUser = this.getTeamMemberForUser.bind(this); this.state = { - teamId: props.team.id, + team: AdminStore.getTeam(this.props.params.team), users: null, teamMembers: null, serverError: null, @@ -35,8 +45,14 @@ export default class UserList extends React.Component { this.getCurrentTeamProfiles(); } + onAllTeamsChange() { + this.setState({ + team: AdminStore.getTeam(this.props.params.team) + }); + } + getCurrentTeamProfiles() { - this.getTeamProfiles(this.props.team.id); + this.getTeamProfiles(this.props.params.team); } getTeamProfiles(teamId) { @@ -133,9 +149,8 @@ export default class UserList extends React.Component { } render() { - var serverError = ''; - if (this.state.serverError) { - serverError =
    ; + if (!this.state.team) { + return null; } if (this.state.users == null || this.state.teamMembers == null) { @@ -146,11 +161,11 @@ export default class UserList extends React.Component { id='admin.userList.title' defaultMessage='Users for {team}' values={{ - team: this.props.team.name + team: this.state.team.name }} /> - {serverError} +
    ); @@ -161,7 +176,7 @@ export default class UserList extends React.Component { return ( - {serverError} +
    @@ -202,7 +217,3 @@ export default class UserList extends React.Component { ); } } - -UserList.propTypes = { - team: React.PropTypes.object -}; diff --git a/webapp/components/admin_console/text_setting.jsx b/webapp/components/admin_console/text_setting.jsx new file mode 100644 index 0000000000..bb37f8e292 --- /dev/null +++ b/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 = ( + + ); + } else if (this.props.type === 'textarea') { + input = ( +