From 9110dd54a15f3d0fcf6f60936e01d816b667b93c Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Mon, 4 Jan 2016 12:44:22 -0500 Subject: Added license validation and settings --- web/react/components/about_build_modal.jsx | 24 ++- .../components/admin_console/admin_controller.jsx | 3 + .../components/admin_console/admin_sidebar.jsx | 32 +++ .../components/admin_console/ldap_settings.jsx | 32 ++- .../components/admin_console/license_settings.jsx | 232 +++++++++++++++++++++ web/react/components/file_upload.jsx | 27 +-- web/react/utils/client.jsx | 35 ++++ web/react/utils/utils.jsx | 31 ++- 8 files changed, 391 insertions(+), 25 deletions(-) create mode 100644 web/react/components/admin_console/license_settings.jsx (limited to 'web/react') diff --git a/web/react/components/about_build_modal.jsx b/web/react/components/about_build_modal.jsx index 3143bec22..d54214632 100644 --- a/web/react/components/about_build_modal.jsx +++ b/web/react/components/about_build_modal.jsx @@ -15,6 +15,19 @@ export default class AboutBuildModal extends React.Component { render() { const config = global.window.mm_config; + const license = global.window.mm_license; + + let title = 'Team Edition'; + let licensee; + if (config.BuildEnterpriseReady === 'true' && license.IsLicensed === 'true') { + title = 'Enterprise Edition'; + licensee = ( +
+
{'Licensed by:'}
+
{license.Company}
+
+ ); + } return ( - {`Mattermost ${config.Version}`} + {'About Mattermost'} +
+
{'Edition:'}
+
{`Mattermost ${title}`}
+
+ {licensee} +
+
{'Version:'}
+
{config.Version}
+
{'Build Number:'}
{config.BuildNumber}
diff --git a/web/react/components/admin_console/admin_controller.jsx b/web/react/components/admin_console/admin_controller.jsx index 32b2e9bb7..0f85c238d 100644 --- a/web/react/components/admin_console/admin_controller.jsx +++ b/web/react/components/admin_console/admin_controller.jsx @@ -22,6 +22,7 @@ import LegalAndSupportSettingsTab from './legal_and_support_settings.jsx'; import TeamUsersTab from './team_users.jsx'; import TeamAnalyticsTab from './team_analytics.jsx'; import LdapSettingsTab from './ldap_settings.jsx'; +import LicenseSettingsTab from './license_settings.jsx'; export default class AdminController extends React.Component { constructor(props) { @@ -154,6 +155,8 @@ export default class AdminController extends React.Component { 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 = ; diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx index 1279f4d22..5a5eaa055 100644 --- a/web/react/components/admin_console/admin_sidebar.jsx +++ b/web/react/components/admin_console/admin_sidebar.jsx @@ -155,6 +155,36 @@ export default class AdminSidebar extends React.Component { } } + let ldapSettings; + let licenseSettings; + if (global.window.mm_config.BuildEnterpriseReady === 'true') { + if (global.window.mm_license.IsLicensed === 'true') { + ldapSettings = ( +
  • + + {'LDAP Settings'} + +
  • + ); + } + + licenseSettings = ( +
  • + + {'Edition and License'} + +
  • + ); + } + return (
    @@ -252,6 +282,7 @@ export default class AdminSidebar extends React.Component { {'GitLab Settings'} + {ldapSettings}
    • + {licenseSettings}
    • + const licenseEnabled = global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.LDAP === 'true'; + + let bannerContent; + if (licenseEnabled) { + bannerContent = (

      {'Note:'}

      {'If a user attribute changes on the LDAP server it will be updated the next time the user enters their credentials to log in to Mattermost. This includes if a user is made inactive or removed from an LDAP server. Synchronization with LDAP servers is planned in a future release.'}

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

      {'LDAP Settings'}

      {'true'} diff --git a/web/react/components/admin_console/license_settings.jsx b/web/react/components/admin_console/license_settings.jsx new file mode 100644 index 000000000..d49056601 --- /dev/null +++ b/web/react/components/admin_console/license_settings.jsx @@ -0,0 +1,232 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import * as Utils from '../../utils/utils.jsx'; +import * as Client from '../../utils/client.jsx'; + +export default 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, + serverError: null + }; + } + + handleChange() { + const element = $(ReactDOM.findDOMNode(this.refs.fileInput)); + if (element.prop('files').length > 0) { + this.setState({fileSelected: true}); + } + } + + 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'); + window.location.reload(true); + }, + (serverError) => { + this.setState({serverError}); + } + ); + } + + handleRemove(e) { + e.preventDefault(); + + $('#remove-button').button('loading'); + + Client.removeLicenseFile( + () => { + $('#remove-button').button('reset'); + window.location.reload(true); + }, + (serverError) => { + $('#remove-button').button('reset'); + this.setState({serverError}); + } + ); + } + + 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 = 'Mattermost Enterprise Edition. Designed for enterprise-scale communication.'; + licenseType = ( +
      +

      + {'This compiled release of Mattermost platform is provided under a '} + + {'commercial license'} + + {' from Mattermost, Inc. based on your subscription level and is subject to the '} + + {'Terms of Service.'} + +

      +

      {'Your subscription details are as follows:'}

      + {'Name: ' + global.window.mm_license.Name} +
      + {'Company or organization name: ' + global.window.mm_license.Company} +
      + {'Number of users: ' + global.window.mm_license.Users} +
      + {`License issued: ${Utils.displayDate(parseInt(global.window.mm_license.IssuedAt, 10))} ${Utils.displayTime(parseInt(global.window.mm_license.IssuedAt, 10), true)}`} +
      + {'Start date of license: ' + Utils.displayDate(parseInt(global.window.mm_license.StartsAt, 10))} +
      + {'Expiry date of license: ' + Utils.displayDate(parseInt(global.window.mm_license.ExpiresAt, 10))} +
      + {'LDAP: ' + global.window.mm_license.LDAP} +
      +
      + ); + + licenseKey = ( +
      + +
      +
      +

      + {'If you’re migrating servers you may need to remove your license key from this server in order to install it on a new server. To start, '} + + {'disable all Enterprise Edition features on this server'} + + {'. This will enable the ability to remove the license key and downgrade this server from Enterprise Edition to Team Edition.'} +

      +
      + ); + } else { + edition = 'Mattermost Team Edition. Designed for teams from 5 to 50 users.'; + + licenseType = ( + +

      {'This compiled release of Mattermost platform is offered under an MIT license.'}

      +

      {'See MIT-COMPILED-LICENSE.txt in your root install directory for details. See NOTICES.txt for information about open source software used in this system.'}

      +
      + ); + + licenseKey = ( +
      + + {serverError} + +
      +
      +

      + {'Upload a license key for Mattermost Enterprise Edition to upgrade this server. '} + + {'Visit us online'} + + {' to learn more about the benefits of Enterprise Edition or to purchase a key.'} +

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

      {'Edition and License'}

      + +
      + +
      + {edition} +
      +
      +
      + +
      + {licenseType} +
      +
      +
      + + {licenseKey} +
      + +
      + ); + } +} diff --git a/web/react/components/file_upload.jsx b/web/react/components/file_upload.jsx index 6337afabc..fef253c52 100644 --- a/web/react/components/file_upload.jsx +++ b/web/react/components/file_upload.jsx @@ -1,7 +1,7 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import * as client from '../utils/client.jsx'; +import * as Client from '../utils/client.jsx'; import Constants from '../utils/constants.jsx'; import ChannelStore from '../stores/channel_store.jsx'; import * as Utils from '../utils/utils.jsx'; @@ -26,7 +26,7 @@ export default class FileUpload extends React.Component { for (var j = 0; j < data.client_ids.length; j++) { delete requests[data.client_ids[j]]; } - this.setState({requests: requests}); + this.setState({requests}); } fileUploadFail(clientId, err) { @@ -52,7 +52,7 @@ export default class FileUpload extends React.Component { } // generate a unique id that can be used by other components to refer back to this upload - let clientId = Utils.generateId(); + const clientId = Utils.generateId(); // prepare data to be uploaded var formData = new FormData(); @@ -60,14 +60,14 @@ export default class FileUpload extends React.Component { formData.append('files', files[i], files[i].name); formData.append('client_ids', clientId); - var request = client.uploadFile(formData, + var request = Client.uploadFile(formData, this.fileUploadSuccess.bind(this, channelId), this.fileUploadFail.bind(this, clientId) ); var requests = this.state.requests; requests[clientId] = request; - this.setState({requests: requests}); + this.setState({requests}); this.props.onUploadStart([clientId], channelId); @@ -90,16 +90,7 @@ export default class FileUpload extends React.Component { this.uploadFiles(element.prop('files')); - // clear file input for all modern browsers - try { - element[0].value = ''; - if (element.value) { - element[0].type = 'text'; - element[0].type = 'file'; - } - } catch (e) { - // Do nothing - } + Utils.clearFileInput(element[0]); } handleDrop(e) { @@ -227,14 +218,14 @@ export default class FileUpload extends React.Component { formData.append('files', file, name); formData.append('client_ids', clientId); - var request = client.uploadFile(formData, + var request = Client.uploadFile(formData, self.fileUploadSuccess.bind(self, channelId), self.fileUploadFail.bind(self, clientId) ); var requests = self.state.requests; requests[clientId] = request; - self.setState({requests: requests}); + self.setState({requests}); self.props.onUploadStart([clientId], channelId); } @@ -263,7 +254,7 @@ export default class FileUpload extends React.Component { request.abort(); delete requests[clientId]; - this.setState({requests: requests}); + this.setState({requests}); } } diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx index 96d1ef720..d60fea872 100644 --- a/web/react/utils/client.jsx +++ b/web/react/utils/client.jsx @@ -1392,3 +1392,38 @@ export function regenOutgoingHookToken(data, success, error) { } }); } + +export function uploadLicenseFile(formData, success, error) { + $.ajax({ + url: '/api/v1/license/add', + type: 'POST', + data: formData, + cache: false, + contentType: false, + processData: false, + success, + error: function onError(xhr, status, err) { + var e = handleError('uploadLicenseFile', xhr, status, err); + error(e); + } + }); + + track('api', 'api_license_upload'); +} + +export function removeLicenseFile(success, error) { + $.ajax({ + url: '/api/v1/license/remove', + type: 'POST', + cache: false, + contentType: false, + processData: false, + success, + error: function onError(xhr, status, err) { + var e = handleError('removeLicenseFile', xhr, status, err); + error(e); + } + }); + + track('api', 'api_license_upload'); +} diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 31bb2ba0b..24042321f 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -201,11 +201,21 @@ export function displayDate(ticks) { return monthNames[d.getMonth()] + ' ' + d.getDate() + ', ' + d.getFullYear(); } -export function displayTime(ticks) { +export function displayTime(ticks, utc) { const d = new Date(ticks); - let hours = d.getHours(); - let minutes = d.getMinutes(); + let hours; + let minutes; let ampm = ''; + let timezone = ''; + + if (utc) { + hours = d.getUTCHours(); + minutes = d.getUTCMinutes(); + timezone = ' UTC'; + } else { + hours = d.getHours(); + minutes = d.getMinutes(); + } if (minutes <= 9) { minutes = '0' + minutes; @@ -224,7 +234,7 @@ export function displayTime(ticks) { } } - return hours + ':' + minutes + ampm; + return hours + ':' + minutes + ampm + timezone; } export function displayDateTime(ticks) { @@ -1301,3 +1311,16 @@ export function fillArray(value, length) { export function isFileTransfer(files) { return files.types != null && (files.types.indexOf ? files.types.indexOf('Files') !== -1 : files.types.contains('application/x-moz-file')); } + +export function clearFileInput(elm) { + // clear file input for all modern browsers + try { + elm.value = ''; + if (elm.value) { + elm.type = 'text'; + elm.type = 'file'; + } + } catch (e) { + // Do nothing + } +} -- cgit v1.2.3-1-g7c22