From 12896bd23eeba79884245c1c29fdc568cf21a7fa Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Mon, 14 Mar 2016 08:50:46 -0400 Subject: Converting to Webpack. Stage 1. --- .../components/admin_console/admin_controller.jsx | 218 +++++ .../admin_console/admin_navbar_dropdown.jsx | 114 +++ webapp/components/admin_console/admin_sidebar.jsx | 487 ++++++++++ .../admin_console/admin_sidebar_header.jsx | 70 ++ webapp/components/admin_console/audits.jsx | 97 ++ webapp/components/admin_console/email_settings.jsx | 961 ++++++++++++++++++++ .../components/admin_console/gitlab_settings.jsx | 383 ++++++++ webapp/components/admin_console/image_settings.jsx | 692 +++++++++++++++ webapp/components/admin_console/ldap_settings.jsx | 588 ++++++++++++ .../admin_console/legal_and_support_settings.jsx | 294 ++++++ .../components/admin_console/license_settings.jsx | 295 ++++++ webapp/components/admin_console/log_settings.jsx | 418 +++++++++ webapp/components/admin_console/logs.jsx | 102 +++ .../components/admin_console/privacy_settings.jsx | 215 +++++ webapp/components/admin_console/rate_settings.jsx | 371 ++++++++ .../admin_console/reset_password_modal.jsx | 162 ++++ .../components/admin_console/select_team_modal.jsx | 115 +++ .../components/admin_console/service_settings.jsx | 984 +++++++++++++++++++++ webapp/components/admin_console/sql_settings.jsx | 390 ++++++++ webapp/components/admin_console/team_settings.jsx | 420 +++++++++ webapp/components/admin_console/team_users.jsx | 188 ++++ webapp/components/admin_console/user_item.jsx | 423 +++++++++ 22 files changed, 7987 insertions(+) create mode 100644 webapp/components/admin_console/admin_controller.jsx create mode 100644 webapp/components/admin_console/admin_navbar_dropdown.jsx create mode 100644 webapp/components/admin_console/admin_sidebar.jsx create mode 100644 webapp/components/admin_console/admin_sidebar_header.jsx create mode 100644 webapp/components/admin_console/audits.jsx create mode 100644 webapp/components/admin_console/email_settings.jsx create mode 100644 webapp/components/admin_console/gitlab_settings.jsx create mode 100644 webapp/components/admin_console/image_settings.jsx create mode 100644 webapp/components/admin_console/ldap_settings.jsx create mode 100644 webapp/components/admin_console/legal_and_support_settings.jsx create mode 100644 webapp/components/admin_console/license_settings.jsx create mode 100644 webapp/components/admin_console/log_settings.jsx create mode 100644 webapp/components/admin_console/logs.jsx create mode 100644 webapp/components/admin_console/privacy_settings.jsx create mode 100644 webapp/components/admin_console/rate_settings.jsx create mode 100644 webapp/components/admin_console/reset_password_modal.jsx create mode 100644 webapp/components/admin_console/select_team_modal.jsx create mode 100644 webapp/components/admin_console/service_settings.jsx create mode 100644 webapp/components/admin_console/sql_settings.jsx create mode 100644 webapp/components/admin_console/team_settings.jsx create mode 100644 webapp/components/admin_console/team_users.jsx create mode 100644 webapp/components/admin_console/user_item.jsx (limited to 'webapp/components/admin_console') diff --git a/webapp/components/admin_console/admin_controller.jsx b/webapp/components/admin_console/admin_controller.jsx new file mode 100644 index 000000000..e4a4e28fc --- /dev/null +++ b/webapp/components/admin_console/admin_controller.jsx @@ -0,0 +1,218 @@ +// 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 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 === '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_navbar_dropdown.jsx b/webapp/components/admin_console/admin_navbar_dropdown.jsx new file mode 100644 index 000000000..56b78448a --- /dev/null +++ b/webapp/components/admin_console/admin_navbar_dropdown.jsx @@ -0,0 +1,114 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import $ from 'jquery'; +import ReactDOM from 'react-dom'; +import * as Utils from 'utils/utils.jsx'; +import TeamStore from 'stores/team_store.jsx'; + +import Constants from 'utils/constants.jsx'; + +import {FormattedMessage} from 'react-intl'; + +import {Link} from 'react-router'; + +function getStateFromStores() { + return {currentTeam: TeamStore.getCurrent()}; +} + +import React from 'react'; + +export default class AdminNavbarDropdown extends React.Component { + constructor(props) { + super(props); + this.blockToggle = false; + + this.state = getStateFromStores(); + } + + componentDidMount() { + $(ReactDOM.findDOMNode(this.refs.dropdown)).on('hide.bs.dropdown', () => { + this.blockToggle = true; + setTimeout(() => { + this.blockToggle = false; + }, 100); + }); + } + + componentWillUnmount() { + $(ReactDOM.findDOMNode(this.refs.dropdown)).off('hide.bs.dropdown'); + } + + render() { + return ( + + ); + } +} diff --git a/webapp/components/admin_console/admin_sidebar.jsx b/webapp/components/admin_console/admin_sidebar.jsx new file mode 100644 index 000000000..27d4a4112 --- /dev/null +++ b/webapp/components/admin_console/admin_sidebar.jsx @@ -0,0 +1,487 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import AdminSidebarHeader from './admin_sidebar_header.jsx'; +import SelectTeamModal from './select_team_modal.jsx'; + +import {FormattedMessage} from 'react-intl'; + +import {Tooltip, OverlayTrigger} from 'react-bootstrap'; + +import React from 'react'; + +export default class AdminSidebar extends React.Component { + constructor(props) { + super(props); + + this.isSelected = this.isSelected.bind(this); + this.handleClick = this.handleClick.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.state = { + showSelectModal: false + }; + } + + handleClick(name, teamId, e) { + e.preventDefault(); + this.props.selectTab(name, teamId); + } + + 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 ''; + } + + removeTeam(teamId, e) { + e.preventDefault(); + e.stopPropagation(); + Reflect.deleteProperty(this.props.selectedTeams, teamId); + this.props.removeSelectedTeam(teamId); + + if (this.props.selected === 'team_users') { + if (this.props.selectedTeam != null && this.props.selectedTeam === teamId) { + this.props.selectTab('service_settings', null); + } + } + } + + showTeamSelect(e) { + e.preventDefault(); + this.setState({showSelectModal: true}); + } + + teamSelectedModal(teamId) { + this.setState({showSelectModal: false}); + this.props.addSelectedTeam(teamId); + this.forceUpdate(); + } + + teamSelectedModalDismissed() { + this.setState({showSelectModal: false}); + } + + render() { + var count = '*'; + var teams = ( + + ); + const removeTooltip = ( + + + + ); + const addTeamTooltip = ( + + + + ); + + if (this.props.teams != null) { + count = '' + Object.keys(this.props.teams).length; + + teams = []; + for (var key in this.props.selectedTeams) { + if (this.props.selectedTeams.hasOwnProperty(key)) { + var team = this.props.teams[key]; + + if (team != null) { + teams.push( + + ); + } + } + } + } + + let ldapSettings; + let licenseSettings; + if (global.window.mm_config.BuildEnterpriseReady === 'true') { + if (global.window.mm_license.IsLicensed === 'true') { + ldapSettings = ( +
  • + + + +
  • + ); + } + + licenseSettings = ( +
  • + + + +
  • + ); + } + + let audits; + if (global.window.mm_license.IsLicensed === 'true') { + audits = ( +
  • + + + +
  • + ); + } + + return ( +
    +
    + +
    + +
    +
    + + +
    + ); + } +} + +AdminSidebar.propTypes = { + teams: React.PropTypes.object, + selectedTeams: React.PropTypes.object, + removeSelectedTeam: React.PropTypes.func, + addSelectedTeam: React.PropTypes.func, + selected: React.PropTypes.string, + selectedTeam: React.PropTypes.string, + selectTab: React.PropTypes.func +}; diff --git a/webapp/components/admin_console/admin_sidebar_header.jsx b/webapp/components/admin_console/admin_sidebar_header.jsx new file mode 100644 index 000000000..2e6252075 --- /dev/null +++ b/webapp/components/admin_console/admin_sidebar_header.jsx @@ -0,0 +1,70 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import $ from 'jquery'; +import AdminNavbarDropdown from './admin_navbar_dropdown.jsx'; +import UserStore from 'stores/user_store.jsx'; + +import {FormattedMessage} from 'react-intl'; + +import React from 'react'; + +export default class SidebarHeader extends React.Component { + constructor(props) { + super(props); + + this.toggleDropdown = this.toggleDropdown.bind(this); + + this.state = {}; + } + + toggleDropdown(e) { + e.preventDefault(); + + if (this.refs.dropdown.blockToggle) { + this.refs.dropdown.blockToggle = false; + return; + } + + $('.team__header').find('.dropdown-toggle').dropdown('toggle'); + } + + render() { + var me = UserStore.getCurrentUser(); + var profilePicture = null; + + if (!me) { + return null; + } + + if (me.last_picture_update) { + profilePicture = ( + + + {profilePicture} +
    +
    {'@' + me.username}
    +
    + +
    +
    +
    + +
    + ); + } +} diff --git a/webapp/components/admin_console/audits.jsx b/webapp/components/admin_console/audits.jsx new file mode 100644 index 000000000..28503d783 --- /dev/null +++ b/webapp/components/admin_console/audits.jsx @@ -0,0 +1,97 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import LoadingScreen from '../loading_screen.jsx'; +import AuditTable from '../audit_table.jsx'; + +import AdminStore from 'stores/admin_store.jsx'; + +import * as AsyncClient from 'utils/async_client.jsx'; + +import {FormattedMessage} from 'react-intl'; + +import React from 'react'; + +export default class Audits extends React.Component { + constructor(props) { + super(props); + + this.onAuditListenerChange = this.onAuditListenerChange.bind(this); + this.reload = this.reload.bind(this); + + this.state = { + audits: AdminStore.getAudits() + }; + } + + componentDidMount() { + AdminStore.addAuditChangeListener(this.onAuditListenerChange); + AsyncClient.getServerAudits(); + } + + componentWillUnmount() { + AdminStore.removeAuditChangeListener(this.onAuditListenerChange); + } + + onAuditListenerChange() { + this.setState({ + audits: AdminStore.getAudits() + }); + } + + reload() { + AdminStore.saveAudits(null); + this.setState({ + audits: null + }); + + AsyncClient.getServerAudits(); + } + + render() { + var content = null; + + if (global.window.mm_license.IsLicensed !== 'true') { + return
    ; + } + + if (this.state.audits === null) { + content = ; + } else { + content = ( +
    + +
    + ); + } + + return ( +
    +

    + +

    + +
    + {content} +
    +
    + ); + } +} diff --git a/webapp/components/admin_console/email_settings.jsx b/webapp/components/admin_console/email_settings.jsx new file mode 100644 index 000000000..1decdae91 --- /dev/null +++ b/webapp/components/admin_console/email_settings.jsx @@ -0,0 +1,961 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import $ from 'jquery'; +import ReactDOM from 'react-dom'; +import * as Client from 'utils/client.jsx'; +import * as AsyncClient from 'utils/async_client.jsx'; +import crypto from 'crypto'; + +import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl'; + +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"' + }, + connectionSecurityNone: { + id: 'admin.email.connectionSecurityNone', + defaultMessage: 'None' + }, + connectionSecurityTls: { + id: 'admin.email.connectionSecurityTls', + defaultMessage: 'TLS (Recommended)' + }, + connectionSecurityStart: { + id: 'admin.email.connectionSecurityStart', + defaultMessage: 'STARTTLS' + }, + inviteSaltExample: { + id: 'admin.email.inviteSaltExample', + defaultMessage: 'Ex "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo"' + }, + passwordSaltExample: { + id: 'admin.email.passwordSaltExample', + defaultMessage: 'Ex "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo"' + }, + pushServerEx: { + id: 'admin.email.pushServerEx', + defaultMessage: 'E.g.: "http://push-test.mattermost.com"' + }, + testing: { + id: 'admin.email.testing', + defaultMessage: 'Testing...' + }, + saving: { + id: 'admin.email.saving', + defaultMessage: 'Saving Config...' + } +}); + +import React from 'react'; + +class EmailSettings extends React.Component { + 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.state = { + sendEmailNotifications: this.props.config.EmailSettings.SendEmailNotifications, + sendPushNotifications: this.props.config.EmailSettings.SendPushNotifications, + saveNeeded: false, + serverError: null, + emailSuccess: null, + emailFail: null + }; + } + + handleChange(action) { + var s = {saveNeeded: true, serverError: this.state.serverError}; + + 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() { + var 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.SendPushNotifications = ReactDOM.findDOMNode(this.refs.sendPushNotifications).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.PushNotificationServer = ReactDOM.findDOMNode(this.refs.PushNotificationServer).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 = ReactDOM.findDOMNode(this.refs.ConnectionSecurity).value.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; + } + + return config; + } + + 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 = ( +
    + + +
    + ); + } + + return ( +
    +

    + +

    +
    + +
    + +
    + + +

    + +

    +
    +
    + +
    + +
    + + +

    + +

    +
    +
    + +
    + +
    + + +

    + +

    +
    +
    + +
    + +
    + + +

    + +

    +
    +
    + +
    + +
    + + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + +
    + + + + + + +
    + + + +
    {'TLS'} + +
    {'STARTTLS'} + +
    +
    +
    + + {emailSuccess} + {emailFail} +
    +
    +
    + +
    + +
    + +

    + +

    +
    + +
    +
    +
    + +
    + +
    + +

    + +

    +
    + +
    +
    +
    + +
    + +
    + + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    +
    + {serverError} + +
    +
    + +
    +
    + ); + } +} + +EmailSettings.propTypes = { + intl: intlShape.isRequired, + config: React.PropTypes.object +}; + +export default injectIntl(EmailSettings); diff --git a/webapp/components/admin_console/gitlab_settings.jsx b/webapp/components/admin_console/gitlab_settings.jsx new file mode 100644 index 000000000..7fdedde13 --- /dev/null +++ b/webapp/components/admin_console/gitlab_settings.jsx @@ -0,0 +1,383 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import $ from 'jquery'; +import ReactDOM from 'react-dom'; +import * as Client from 'utils/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 { + constructor(props) { + super(props); + + this.handleChange = this.handleChange.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + + this.state = { + Enable: this.props.config.GitLabSettings.Enable, + saveNeeded: false, + serverError: null + }; + } + + handleChange(action) { + var s = {saveNeeded: true, serverError: this.state.serverError}; + + if (action === 'EnableTrue') { + s.Enable = true; + } + + if (action === 'EnableFalse') { + s.Enable = false; + } + + this.setState(s); + } + + 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'; + } + + 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); diff --git a/webapp/components/admin_console/image_settings.jsx b/webapp/components/admin_console/image_settings.jsx new file mode 100644 index 000000000..576ff18fd --- /dev/null +++ b/webapp/components/admin_console/image_settings.jsx @@ -0,0 +1,692 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import $ from 'jquery'; +import ReactDOM from 'react-dom'; +import * as Client from 'utils/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 { + 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, + DriverName: this.props.config.FileSettings.DriverName + }; + } + + handleChange(action) { + var s = {saveNeeded: true, serverError: this.state.serverError}; + + if (action === 'DriverName') { + s.DriverName = ReactDOM.findDOMNode(this.refs.DriverName).value; + } + + this.setState(s); + } + + 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; + } + + return ( +
    +

    + +

    +
    + +
    + +
    + +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    + +
    +
    +
    + +
    +
    + {serverError} + +
    +
    + +
    +
    + ); + } +} + +FileSettings.propTypes = { + intl: intlShape.isRequired, + config: React.PropTypes.object +}; + +export default injectIntl(FileSettings); \ No newline at end of file diff --git a/webapp/components/admin_console/ldap_settings.jsx b/webapp/components/admin_console/ldap_settings.jsx new file mode 100644 index 000000000..7996a3aab --- /dev/null +++ b/webapp/components/admin_console/ldap_settings.jsx @@ -0,0 +1,588 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import $ from 'jquery'; +import ReactDOM from 'react-dom'; +import * as Client from 'utils/client.jsx'; +import * as AsyncClient from 'utils/async_client.jsx'; + +import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl'; + +const DEFAULT_LDAP_PORT = 389; +const DEFAULT_QUERY_TIMEOUT = 60; + +var holders = defineMessages({ + serverEx: { + id: 'admin.ldap.serverEx', + defaultMessage: 'Ex "10.0.0.23"' + }, + portEx: { + id: 'admin.ldap.portEx', + defaultMessage: 'Ex "389"' + }, + baseEx: { + id: 'admin.ldap.baseEx', + defaultMessage: 'Ex "ou=Unit Name,dc=corp,dc=example,dc=com"' + }, + firstnameAttrEx: { + id: 'admin.ldap.firstnameAttrEx', + defaultMessage: 'Ex "givenName"' + }, + lastnameAttrEx: { + id: 'admin.ldap.lastnameAttrEx', + defaultMessage: 'Ex "sn"' + }, + emailAttrEx: { + id: 'admin.ldap.emailAttrEx', + defaultMessage: 'Ex "mail" or "userPrincipalName"' + }, + usernameAttrEx: { + id: 'admin.ldap.usernameAttrEx', + defaultMessage: 'Ex "sAMAccountName"' + }, + idAttrEx: { + id: 'admin.ldap.idAttrEx', + defaultMessage: 'Ex "sAMAccountName"' + }, + queryEx: { + id: 'admin.ldap.queryEx', + defaultMessage: 'Ex "60"' + }, + saving: { + id: 'admin.ldap.saving', + defaultMessage: 'Saving Config...' + } +}); + +import React from 'react'; + +class LdapSettings extends React.Component { + 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.state = { + saveNeeded: false, + serverError: null, + enable: this.props.config.LdapSettings.Enable + }; + } + handleChange() { + this.setState({saveNeeded: true}); + } + handleEnable() { + this.setState({saveNeeded: true, enable: true}); + } + handleDisable() { + this.setState({saveNeeded: true, enable: false}); + } + handleSubmit(e) { + e.preventDefault(); + $('#save-button').button('loading'); + + const config = this.props.config; + config.LdapSettings.Enable = this.refs.Enable.checked; + config.LdapSettings.LdapServer = this.refs.LdapServer.value.trim(); + + 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.EmailAttribute = this.refs.EmailAttribute.value.trim(); + config.LdapSettings.UsernameAttribute = this.refs.UsernameAttribute.value.trim(); + config.LdapSettings.IdAttribute = this.refs.IdAttribute.value.trim(); + + 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; + + 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; + let serverError = ''; + if (this.state.serverError) { + serverError =
    ; + } + + let saveClass = 'btn'; + if (this.state.saveNeeded) { + saveClass = 'btn btn-primary'; + } + + const licenseEnabled = global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.LDAP === 'true'; + + let bannerContent; + if (licenseEnabled) { + bannerContent = ( +
    +
    +

    + +

    +

    + +

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

    + +

    +
    +
    + +
    + + +

    + +

    +
    +
    +
    + +
    + +

    + +

    +
    +
    +
    + +
    + +

    + +

    +
    +
    +
    + +
    + +

    + +

    +
    +
    +
    + +
    + +

    + +

    +
    +
    +
    + +
    + +

    + +

    +
    +
    +
    + +
    + +

    + +

    +
    +
    +
    + +
    + +

    + +

    +
    +
    +
    + +
    + +

    + +

    +
    +
    +
    + +
    + +

    + +

    +
    +
    +
    + +
    + +

    + +

    +
    +
    +
    + +
    + +

    + +

    +
    +
    +
    +
    + {serverError} + +
    +
    +
    +
    + ); + } +} +LdapSettings.defaultProps = { +}; + +LdapSettings.propTypes = { + intl: intlShape.isRequired, + config: React.PropTypes.object +}; + +export default injectIntl(LdapSettings); diff --git a/webapp/components/admin_console/legal_and_support_settings.jsx b/webapp/components/admin_console/legal_and_support_settings.jsx new file mode 100644 index 000000000..4997a1385 --- /dev/null +++ b/webapp/components/admin_console/legal_and_support_settings.jsx @@ -0,0 +1,294 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import $ from 'jquery'; +import ReactDOM from 'react-dom'; +import * as Client from 'utils/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 { + 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.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'; + } + + return ( +
    + +

    + +

    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    +
    + {serverError} + +
    +
    + +
    +
    + ); + } +} + +LegalAndSupportSettings.propTypes = { + intl: intlShape.isRequired, + config: React.PropTypes.object +}; + +export default injectIntl(LegalAndSupportSettings); \ No newline at end of file diff --git a/webapp/components/admin_console/license_settings.jsx b/webapp/components/admin_console/license_settings.jsx new file mode 100644 index 000000000..5aa0dba7e --- /dev/null +++ b/webapp/components/admin_console/license_settings.jsx @@ -0,0 +1,295 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import $ from 'jquery'; +import ReactDOM from 'react-dom'; +import * as Utils from 'utils/utils.jsx'; +import * as Client from 'utils/client.jsx'; + +import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl'; + +const holders = defineMessages({ + removing: { + id: 'admin.license.removing', + defaultMessage: 'Removing License...' + }, + uploading: { + id: 'admin.license.uploading', + defaultMessage: 'Uploading License...' + } +}); + +import React from 'react'; + +class LicenseSettings extends React.Component { + constructor(props) { + super(props); + + this.handleChange = this.handleChange.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + this.handleRemove = this.handleRemove.bind(this); + + this.state = { + fileSelected: false, + fileName: null, + serverError: null + }; + } + + handleChange() { + const element = $(ReactDOM.findDOMNode(this.refs.fileInput)); + if (element.prop('files').length > 0) { + this.setState({fileSelected: true, fileName: element.prop('files')[0].name}); + } + } + + handleSubmit(e) { + e.preventDefault(); + + const element = $(ReactDOM.findDOMNode(this.refs.fileInput)); + if (element.prop('files').length === 0) { + return; + } + const file = element.prop('files')[0]; + + $('#upload-button').button('loading'); + + const formData = new FormData(); + formData.append('license', file, file.name); + + Client.uploadLicenseFile(formData, + () => { + Utils.clearFileInput(element[0]); + $('#upload-button').button('reset'); + this.setState({fileSelected: false, fileName: null, serverError: null}); + window.location.reload(true); + }, + (error) => { + Utils.clearFileInput(element[0]); + $('#upload-button').button('reset'); + this.setState({fileSelected: false, fileName: null, serverError: error.message}); + } + ); + } + + handleRemove(e) { + e.preventDefault(); + + $('#remove-button').button('loading'); + + Client.removeLicenseFile( + () => { + $('#remove-button').button('reset'); + this.setState({fileSelected: false, fileName: null, serverError: null}); + window.location.reload(true); + }, + (error) => { + $('#remove-button').button('reset'); + this.setState({fileSelected: false, fileName: null, serverError: error.message}); + } + ); + } + + render() { + var serverError = ''; + if (this.state.serverError) { + serverError =
    ; + } + + var btnClass = 'btn'; + if (this.state.fileSelected) { + btnClass = 'btn btn-primary'; + } + + let edition; + let licenseType; + let licenseKey; + + if (global.window.mm_license.IsLicensed === 'true') { + edition = ( + + ); + licenseType = ( + + ); + + licenseKey = ( +
    + +
    +
    +

    + +

    +
    + ); + } else { + edition = ( + + ); + + licenseType = ( + + ); + + let fileName; + if (this.state.fileName) { + fileName = this.state.fileName; + } else { + fileName = ( + + ); + } + + licenseKey = ( +
    +
    + + +
    + +
    + {fileName} +
    +
    + {serverError} +

    + +

    +
    + ); + } + + return ( +
    +

    + +

    +
    +
    + +
    + {edition} +
    +
    +
    + +
    + {licenseType} +
    +
    +
    + + {licenseKey} +
    +
    +
    + ); + } +} + +LicenseSettings.propTypes = { + intl: intlShape.isRequired, + config: React.PropTypes.object +}; + +export default injectIntl(LicenseSettings); diff --git a/webapp/components/admin_console/log_settings.jsx b/webapp/components/admin_console/log_settings.jsx new file mode 100644 index 000000000..5aa3ca1e0 --- /dev/null +++ b/webapp/components/admin_console/log_settings.jsx @@ -0,0 +1,418 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import $ from 'jquery'; +import ReactDOM from 'react-dom'; +import * as Client from 'utils/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 { + constructor(props) { + super(props); + + this.handleChange = this.handleChange.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + + this.state = { + consoleEnable: this.props.config.LogSettings.EnableConsole, + fileEnable: this.props.config.LogSettings.EnableFile, + saveNeeded: false, + serverError: null + }; + } + + handleChange(action) { + var s = {saveNeeded: true, serverError: this.state.serverError}; + + 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); + } + + 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'); + } + ); + } + + render() { + const {formatMessage} = this.props.intl; + var serverError = ''; + if (this.state.serverError) { + serverError =
    ; + } + + var saveClass = 'btn'; + if (this.state.saveNeeded) { + saveClass = 'btn btn-primary'; + } + + return ( +
    +

    + +

    +
    + +
    + +
    + + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + +
    + +
    + + + + + + + + + +
    {'%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/logs.jsx b/webapp/components/admin_console/logs.jsx new file mode 100644 index 000000000..f2c6d92c3 --- /dev/null +++ b/webapp/components/admin_console/logs.jsx @@ -0,0 +1,102 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import AdminStore from 'stores/admin_store.jsx'; +import LoadingScreen from '../loading_screen.jsx'; +import * as AsyncClient from 'utils/async_client.jsx'; + +import {FormattedMessage} from 'react-intl'; + +import React from 'react'; + +export default class Logs extends React.Component { + constructor(props) { + super(props); + + this.onLogListenerChange = this.onLogListenerChange.bind(this); + this.reload = this.reload.bind(this); + + this.state = { + logs: AdminStore.getLogs() + }; + } + + componentDidMount() { + AdminStore.addLogChangeListener(this.onLogListenerChange); + AsyncClient.getLogs(); + } + + componentWillUnmount() { + AdminStore.removeLogChangeListener(this.onLogListenerChange); + } + + onLogListenerChange() { + this.setState({ + logs: AdminStore.getLogs() + }); + } + + reload() { + AdminStore.saveLogs(null); + this.setState({ + logs: null + }); + + AsyncClient.getLogs(); + } + + render() { + var content = null; + + if (this.state.logs === null) { + content = ; + } else { + content = []; + + for (var i = 0; i < this.state.logs.length; i++) { + var style = { + whiteSpace: 'nowrap', + fontFamily: 'monospace' + }; + + if (this.state.logs[i].indexOf('[EROR]') > 0) { + style.color = 'red'; + } + + content.push(
    ); + content.push( + + {this.state.logs[i]} + + ); + } + } + + return ( +
    +

    + +

    + +
    + {content} +
    +
    + ); + } +} \ No newline at end of file diff --git a/webapp/components/admin_console/privacy_settings.jsx b/webapp/components/admin_console/privacy_settings.jsx new file mode 100644 index 000000000..a312dddca --- /dev/null +++ b/webapp/components/admin_console/privacy_settings.jsx @@ -0,0 +1,215 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import $ from 'jquery'; +import ReactDOM from 'react-dom'; +import * as Client from 'utils/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 { + 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.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'; + } + + return ( +
    +

    + +

    +
    + +
    + +
    + + +

    + +

    +
    +
    + +
    + +
    + + +

    + +

    +
    +
    + +
    +
    + {serverError} + +
    +
    + +
    +
    + ); + } +} + +PrivacySettings.propTypes = { + intl: intlShape.isRequired, + config: React.PropTypes.object +}; + +export default injectIntl(PrivacySettings); \ No newline at end of file diff --git a/webapp/components/admin_console/rate_settings.jsx b/webapp/components/admin_console/rate_settings.jsx new file mode 100644 index 000000000..f3fb1742c --- /dev/null +++ b/webapp/components/admin_console/rate_settings.jsx @@ -0,0 +1,371 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import $ from 'jquery'; +import ReactDOM from 'react-dom'; +import * as Client from 'utils/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 { + constructor(props) { + super(props); + + this.handleChange = this.handleChange.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + + this.state = { + EnableRateLimiter: this.props.config.RateLimitSettings.EnableRateLimiter, + VaryByRemoteAddr: this.props.config.RateLimitSettings.VaryByRemoteAddr, + saveNeeded: false, + serverError: null + }; + } + + handleChange(action) { + var s = {saveNeeded: true, serverError: this.state.serverError}; + + 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); + } + + 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'); + } + ); + } + + render() { + const {formatMessage} = this.props.intl; + var serverError = ''; + if (this.state.serverError) { + serverError =
    ; + } + + var saveClass = 'btn'; + if (this.state.saveNeeded) { + saveClass = 'btn btn-primary'; + } + + return ( +
    + +
    +
    +

    + +

    +

    + +

    +
    +
    + +

    + +

    +
    + +
    + +
    + + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    +
    + {serverError} + +
    +
    + +
    +
    + ); + } +} + +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/reset_password_modal.jsx b/webapp/components/admin_console/reset_password_modal.jsx new file mode 100644 index 000000000..f80c740e3 --- /dev/null +++ b/webapp/components/admin_console/reset_password_modal.jsx @@ -0,0 +1,162 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import ReactDOM from 'react-dom'; +import * as Client from 'utils/client.jsx'; +import Constants from 'utils/constants.jsx'; +import {Modal} from 'react-bootstrap'; + +import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl'; + +var holders = defineMessages({ + submit: { + id: 'admin.reset_password.submit', + defaultMessage: 'Please enter at least {chars} characters.' + } +}); + +import React from 'react'; + +class ResetPasswordModal extends React.Component { + constructor(props) { + super(props); + + this.doSubmit = this.doSubmit.bind(this); + this.doCancel = this.doCancel.bind(this); + + this.state = { + serverError: null + }; + } + + doSubmit(e) { + e.preventDefault(); + var password = ReactDOM.findDOMNode(this.refs.password).value; + + if (!password || password.length < Constants.MIN_PASSWORD_LENGTH) { + this.setState({serverError: this.props.intl.formatMessage(holders.submit, {chars: Constants.MIN_PASSWORD_LENGTH})}); + return; + } + + this.setState({serverError: null}); + + var data = {}; + data.new_password = password; + data.name = this.props.team.name; + data.user_id = this.props.user.id; + + Client.resetPassword(data, + () => { + this.props.onModalSubmit(ReactDOM.findDOMNode(this.refs.password).value); + }, + (err) => { + this.setState({serverError: err.message}); + } + ); + } + + doCancel() { + this.setState({serverError: null}); + this.props.onModalDismissed(); + } + + render() { + if (this.props.user == null) { + return
    ; + } + + let urlClass = 'input-group input-group--limit'; + let serverError = null; + + if (this.state.serverError) { + urlClass += ' has-error'; + serverError =

    {this.state.serverError}

    ; + } + + return ( + + + + + + +
    + +
    +
    +
    + + + + +
    + {serverError} +
    +
    +
    + + + + +
    +
    + ); + } +} + +ResetPasswordModal.defaultProps = { + show: false +}; + +ResetPasswordModal.propTypes = { + intl: intlShape.isRequired, + user: React.PropTypes.object, + team: React.PropTypes.object, + show: React.PropTypes.bool.isRequired, + onModalSubmit: React.PropTypes.func, + onModalDismissed: React.PropTypes.func +}; + +export default injectIntl(ResetPasswordModal); \ No newline at end of file diff --git a/webapp/components/admin_console/select_team_modal.jsx b/webapp/components/admin_console/select_team_modal.jsx new file mode 100644 index 000000000..ed6d33056 --- /dev/null +++ b/webapp/components/admin_console/select_team_modal.jsx @@ -0,0 +1,115 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import ReactDOM from 'react-dom'; +import {FormattedMessage} from 'react-intl'; + +import {Modal} from 'react-bootstrap'; + +import React from 'react'; + +export default class SelectTeamModal extends React.Component { + constructor(props) { + super(props); + + this.doSubmit = this.doSubmit.bind(this); + this.doCancel = this.doCancel.bind(this); + } + + doSubmit(e) { + e.preventDefault(); + this.props.onModalSubmit(ReactDOM.findDOMNode(this.refs.team).value); + } + doCancel() { + this.props.onModalDismissed(); + } + render() { + if (this.props.teams == null) { + return
    ; + } + + var options = []; + + for (var key in this.props.teams) { + if (this.props.teams.hasOwnProperty(key)) { + var team = this.props.teams[key]; + options.push( + + ); + } + } + + return ( + + + + + + +
    + +
    +
    + +
    +
    +
    + + + + +
    +
    + ); + } +} + +SelectTeamModal.defaultProps = { + show: false +}; + +SelectTeamModal.propTypes = { + teams: React.PropTypes.object, + show: React.PropTypes.bool.isRequired, + onModalSubmit: React.PropTypes.func, + onModalDismissed: React.PropTypes.func +}; \ No newline at end of file diff --git a/webapp/components/admin_console/service_settings.jsx b/webapp/components/admin_console/service_settings.jsx new file mode 100644 index 000000000..881d22d76 --- /dev/null +++ b/webapp/components/admin_console/service_settings.jsx @@ -0,0 +1,984 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import $ from 'jquery'; +import ReactDOM from 'react-dom'; +import * as Client from 'utils/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; + + //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'; + } + + return ( +
    + +

    + +

    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + + +

    + +

    +
    +
    + +
    + +
    + + +

    + +

    +
    +
    + +
    + +
    + + +

    + +

    +
    +
    + +
    + +
    + + +

    + +

    +
    +
    + +
    + +
    + + +

    + +

    +
    +
    + +
    + +
    + + +

    + +

    +
    +
    + +
    + +
    + + +

    + +

    +
    +
    + +
    + +
    + + +

    + +

    +
    +
    + +
    + +
    + + +

    + +

    +
    +
    + +
    + +
    + + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    +
    + {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/sql_settings.jsx b/webapp/components/admin_console/sql_settings.jsx new file mode 100644 index 000000000..33bb2cece --- /dev/null +++ b/webapp/components/admin_console/sql_settings.jsx @@ -0,0 +1,390 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import $ from 'jquery'; +import ReactDOM from 'react-dom'; +import * as Client from 'utils/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); \ No newline at end of file diff --git a/webapp/components/admin_console/team_settings.jsx b/webapp/components/admin_console/team_settings.jsx new file mode 100644 index 000000000..654f0085d --- /dev/null +++ b/webapp/components/admin_console/team_settings.jsx @@ -0,0 +1,420 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import $ from 'jquery'; +import ReactDOM from 'react-dom'; +import * as Client from 'utils/client.jsx'; +import * as AsyncClient from 'utils/async_client.jsx'; + +import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl'; + +const holders = defineMessages({ + siteNameExample: { + id: 'admin.team.siteNameExample', + defaultMessage: 'Ex "Mattermost"' + }, + maxUsersExample: { + id: 'admin.team.maxUsersExample', + defaultMessage: 'Ex "25"' + }, + restrictExample: { + id: 'admin.team.restrictExample', + defaultMessage: 'Ex "corp.mattermost.com, mattermost.org"' + }, + saving: { + id: 'admin.team.saving', + defaultMessage: 'Saving Config...' + } +}); + +import React from 'react'; + +class TeamSettings 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.TeamSettings.SiteName = ReactDOM.findDOMNode(this.refs.SiteName).value.trim(); + config.TeamSettings.RestrictCreationToDomains = ReactDOM.findDOMNode(this.refs.RestrictCreationToDomains).value.trim(); + config.TeamSettings.EnableTeamCreation = ReactDOM.findDOMNode(this.refs.EnableTeamCreation).checked; + config.TeamSettings.EnableUserCreation = ReactDOM.findDOMNode(this.refs.EnableUserCreation).checked; + config.TeamSettings.RestrictTeamNames = ReactDOM.findDOMNode(this.refs.RestrictTeamNames).checked; + config.TeamSettings.EnableTeamListing = ReactDOM.findDOMNode(this.refs.EnableTeamListing).checked; + + var MaxUsersPerTeam = 50; + if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.MaxUsersPerTeam).value, 10))) { + MaxUsersPerTeam = parseInt(ReactDOM.findDOMNode(this.refs.MaxUsersPerTeam).value, 10); + } + config.TeamSettings.MaxUsersPerTeam = MaxUsersPerTeam; + ReactDOM.findDOMNode(this.refs.MaxUsersPerTeam).value = MaxUsersPerTeam; + + 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'; + } + + return ( +
    + +

    + +

    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + + +

    + +

    +
    +
    + +
    + +
    + + +

    + +

    +
    +
    + +
    + +
    + +

    + +

    +
    +
    + +
    + +
    + + +

    + +

    +
    +
    + +
    + +
    + + +

    + +

    +
    +
    + +
    +
    + {serverError} + +
    +
    + +
    +
    + ); + } +} + +TeamSettings.propTypes = { + intl: intlShape.isRequired, + config: React.PropTypes.object +}; + +export default injectIntl(TeamSettings); \ No newline at end of file diff --git a/webapp/components/admin_console/team_users.jsx b/webapp/components/admin_console/team_users.jsx new file mode 100644 index 000000000..1bf3f785b --- /dev/null +++ b/webapp/components/admin_console/team_users.jsx @@ -0,0 +1,188 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import * as Client from 'utils/client.jsx'; +import LoadingScreen from '../loading_screen.jsx'; +import UserItem from './user_item.jsx'; +import ResetPasswordModal from './reset_password_modal.jsx'; + +import {FormattedMessage} from 'react-intl'; + +import React from 'react'; + +export default class UserList extends React.Component { + constructor(props) { + super(props); + + this.getTeamProfiles = this.getTeamProfiles.bind(this); + this.getCurrentTeamProfiles = this.getCurrentTeamProfiles.bind(this); + this.doPasswordReset = this.doPasswordReset.bind(this); + this.doPasswordResetDismiss = this.doPasswordResetDismiss.bind(this); + this.doPasswordResetSubmit = this.doPasswordResetSubmit.bind(this); + + this.state = { + teamId: props.team.id, + users: null, + serverError: null, + showPasswordModal: false, + user: null + }; + } + + componentDidMount() { + this.getCurrentTeamProfiles(); + } + + getCurrentTeamProfiles() { + this.getTeamProfiles(this.props.team.id); + } + + getTeamProfiles(teamId) { + Client.getProfilesForTeam( + teamId, + (users) => { + var memberList = []; + for (var id in users) { + if (users.hasOwnProperty(id)) { + memberList.push(users[id]); + } + } + + memberList.sort((a, b) => { + if (a.username < b.username) { + return -1; + } + + if (a.username > b.username) { + return 1; + } + + return 0; + }); + + this.setState({ + teamId: this.state.teamId, + users: memberList, + serverError: this.state.serverError, + showPasswordModal: this.state.showPasswordModal, + user: this.state.user + }); + }, + (err) => { + this.setState({ + teamId: this.state.teamId, + users: null, + serverError: err.message, + showPasswordModal: this.state.showPasswordModal, + user: this.state.user + }); + } + ); + } + + doPasswordReset(user) { + this.setState({ + teamId: this.state.teamId, + users: this.state.users, + serverError: this.state.serverError, + showPasswordModal: true, + user + }); + } + + doPasswordResetDismiss() { + this.setState({ + teamId: this.state.teamId, + users: this.state.users, + serverError: this.state.serverError, + showPasswordModal: false, + user: null + }); + } + + doPasswordResetSubmit() { + this.setState({ + teamId: this.state.teamId, + users: this.state.users, + serverError: this.state.serverError, + showPasswordModal: false, + user: null + }); + } + + componentWillReceiveProps(newProps) { + this.getTeamProfiles(newProps.team.id); + } + + render() { + var serverError = ''; + if (this.state.serverError) { + serverError =
    ; + } + + if (this.state.users == null) { + return ( +
    +

    + +

    + {serverError} + +
    + ); + } + + var memberList = this.state.users.map((user) => { + return ( + ); + }); + + return ( +
    +

    + +

    + {serverError} +
    + + + {memberList} + +
    +
    + +
    + ); + } +} + +UserList.propTypes = { + team: React.PropTypes.object +}; diff --git a/webapp/components/admin_console/user_item.jsx b/webapp/components/admin_console/user_item.jsx new file mode 100644 index 000000000..c6498eafc --- /dev/null +++ b/webapp/components/admin_console/user_item.jsx @@ -0,0 +1,423 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import * as Client from 'utils/client.jsx'; +import * as Utils from 'utils/utils.jsx'; +import UserStore from 'stores/user_store.jsx'; +import ConfirmModal from '../confirm_modal.jsx'; +import TeamStore from 'stores/team_store.jsx'; + +import {FormattedMessage} from 'react-intl'; + +import React from 'react'; + +export default class UserItem extends React.Component { + constructor(props) { + super(props); + + this.handleMakeMember = this.handleMakeMember.bind(this); + this.handleMakeActive = this.handleMakeActive.bind(this); + this.handleMakeNotActive = this.handleMakeNotActive.bind(this); + this.handleMakeAdmin = this.handleMakeAdmin.bind(this); + this.handleMakeSystemAdmin = this.handleMakeSystemAdmin.bind(this); + this.handleResetPassword = this.handleResetPassword.bind(this); + this.handleDemote = this.handleDemote.bind(this); + this.handleDemoteSubmit = this.handleDemoteSubmit.bind(this); + this.handleDemoteCancel = this.handleDemoteCancel.bind(this); + + this.state = { + serverError: null, + showDemoteModal: false, + user: null, + role: null + }; + } + + handleMakeMember(e) { + e.preventDefault(); + const me = UserStore.getCurrentUser(); + if (this.props.user.id === me.id) { + this.handleDemote(this.props.user, ''); + } else { + const data = { + user_id: this.props.user.id, + new_roles: '' + }; + + Client.updateRoles(data, + () => { + this.props.refreshProfiles(); + }, + (err) => { + this.setState({serverError: err.message}); + } + ); + } + } + + handleMakeActive(e) { + e.preventDefault(); + Client.updateActive(this.props.user.id, true, + () => { + this.props.refreshProfiles(); + }, + (err) => { + this.setState({serverError: err.message}); + } + ); + } + + handleMakeNotActive(e) { + e.preventDefault(); + Client.updateActive(this.props.user.id, false, + () => { + this.props.refreshProfiles(); + }, + (err) => { + this.setState({serverError: err.message}); + } + ); + } + + handleMakeAdmin(e) { + e.preventDefault(); + const me = UserStore.getCurrentUser(); + if (this.props.user.id === me.id) { + this.handleDemote(this.props.user, 'admin'); + } else { + const data = { + user_id: this.props.user.id, + new_roles: 'admin' + }; + + Client.updateRoles(data, + () => { + this.props.refreshProfiles(); + }, + (err) => { + this.setState({serverError: err.message}); + } + ); + } + } + + handleMakeSystemAdmin(e) { + e.preventDefault(); + const data = { + user_id: this.props.user.id, + new_roles: 'system_admin' + }; + + Client.updateRoles(data, + () => { + this.props.refreshProfiles(); + }, + (err) => { + this.setState({serverError: err.message}); + } + ); + } + + handleResetPassword(e) { + e.preventDefault(); + this.props.doPasswordReset(this.props.user); + } + + handleDemote(user, role) { + this.setState({ + serverError: this.state.serverError, + showDemoteModal: true, + user, + role + }); + } + + handleDemoteCancel() { + this.setState({ + serverError: null, + showDemoteModal: false, + user: null, + role: null + }); + } + + handleDemoteSubmit() { + const data = { + user_id: this.props.user.id, + new_roles: this.state.role + }; + + Client.updateRoles(data, + () => { + this.setState({ + serverError: null, + showDemoteModal: false, + user: null, + role: null + }); + + const teamUrl = TeamStore.getCurrentTeamUrl(); + if (teamUrl) { + window.location.href = teamUrl; + } else { + window.location.href = '/'; + } + }, + (err) => { + this.setState({ + serverError: err.message + }); + } + ); + } + + render() { + let serverError = null; + if (this.state.serverError) { + serverError = ( +
    + +
    + ); + } + + const user = this.props.user; + let currentRoles = ( + + ); + if (user.roles.length > 0) { + if (Utils.isSystemAdmin(user.roles)) { + currentRoles = ( + + ); + } else if (Utils.isAdmin(user.roles)) { + currentRoles = ( + + ); + } else { + currentRoles = user.roles.charAt(0).toUpperCase() + user.roles.slice(1); + } + } + + const email = user.email; + let showMakeMember = user.roles === 'admin' || user.roles === 'system_admin'; + let showMakeAdmin = user.roles === '' || user.roles === 'system_admin'; + let showMakeSystemAdmin = user.roles === '' || user.roles === 'admin'; + let showMakeActive = false; + let showMakeNotActive = user.roles !== 'system_admin'; + + if (user.delete_at > 0) { + currentRoles = ( + + ); + showMakeMember = false; + showMakeAdmin = false; + showMakeSystemAdmin = false; + showMakeActive = true; + showMakeNotActive = false; + } + + let makeSystemAdmin = null; + if (showMakeSystemAdmin) { + makeSystemAdmin = ( +
  • + + + +
  • + ); + } + + let makeAdmin = null; + if (showMakeAdmin) { + makeAdmin = ( +
  • + + + +
  • + ); + } + + let makeMember = null; + if (showMakeMember) { + makeMember = ( +
  • + + + +
  • + ); + } + + let makeActive = null; + if (showMakeActive) { + makeActive = ( +
  • + + + +
  • + ); + } + + let makeNotActive = null; + if (showMakeNotActive) { + makeNotActive = ( +
  • + + + +
  • + ); + } + const me = UserStore.getCurrentUser(); + let makeDemoteModal = null; + if (this.props.user.id === me.id) { + const title = ( + + ); + + const message = ( +
    + +
    +
    + + {serverError} +
    + ); + + const confirmButton = ( + + ); + + makeDemoteModal = ( + + ); + } + + return ( + + + + {Utils.getDisplayName(user)} + {email} +
    + + {currentRoles} + + +
      + {makeAdmin} + {makeMember} + {makeActive} + {makeNotActive} + {makeSystemAdmin} +
    • + + + +
    • +
    +
    + {makeDemoteModal} + {serverError} + + + ); + } +} + +UserItem.propTypes = { + user: React.PropTypes.object.isRequired, + refreshProfiles: React.PropTypes.func.isRequired, + doPasswordReset: React.PropTypes.func.isRequired +}; -- cgit v1.2.3-1-g7c22