diff --git a/utils/config.go b/utils/config.go index b8ec43eb58..c77d655dce 100644 --- a/utils/config.go +++ b/utils/config.go @@ -536,6 +536,8 @@ func getClientConfig(c *model.Config) map[string]string { props["DiagnosticId"] = CfgDiagnosticId props["DiagnosticsEnabled"] = strconv.FormatBool(*c.LogSettings.EnableDiagnostics) + props["PluginsEnabled"] = strconv.FormatBool(*c.PluginSettings.Enable) + if IsLicensed() { License := License() diff --git a/webapp/components/admin_console/admin_sidebar.jsx b/webapp/components/admin_console/admin_sidebar.jsx index 9a726c65c0..4918cdac05 100644 --- a/webapp/components/admin_console/admin_sidebar.jsx +++ b/webapp/components/admin_console/admin_sidebar.jsx @@ -60,6 +60,7 @@ export default class AdminSidebar extends React.Component { let metricsSettings = null; let complianceSettings = null; let mfaSettings = null; + let pluginSettings = null; let license = null; let audits = null; @@ -277,6 +278,20 @@ export default class AdminSidebar extends React.Component { ); } + if (window.mm_config.PluginsEnabled === 'true' && window.mm_license.IsLicensed === 'true') { + pluginSettings = ( + + } + /> + ); + } + const SHOW_CLIENT_VERSIONS = false; let clientVersions = null; if (SHOW_CLIENT_VERSIONS) { @@ -562,6 +577,7 @@ export default class AdminSidebar extends React.Component { /> } /> + {pluginSettings} this.setState({loading: false}) + ); + } + + handleChange = () => { + const element = this.refs.fileInput; + if (element.files.length > 0) { + this.setState({fileSelected: true, fileName: element.files[0].name}); + } + } + + handleSubmit = async (e) => { + e.preventDefault(); + + const element = this.refs.fileInput; + if (element.files.length === 0) { + return; + } + const file = element.files[0]; + + this.setState({uploading: true}); + + const {error} = await this.props.actions.uploadPlugin(file); + this.setState({fileSelected: false, fileName: null, uploading: false}); + Utils.clearFileInput(element); + + if (error) { + if (error.server_error_id === 'app.plugin.activate.app_error') { + this.setState({serverError: Utils.localizeMessage('admin.plugin.error.activate', 'Unable to upload the plugin. It may conflict with another plugin on your server.')}); + } else if (error.server_error_id === 'app.plugin.extract.app_error') { + this.setState({serverError: Utils.localizeMessage('admin.plugin.error.extract', 'Encountered an error when extracting the plugin. Review your plugin file content and try again.')}); + } else { + this.setState({serverError: error.message}); + } + } + } + + handleRemove = async (pluginId) => { + this.setState({removing: pluginId}); + + const {error} = await this.props.actions.removePlugin(pluginId); + this.setState({removing: null}); + + if (error) { + this.setState({serverError: error.message}); + } + } + + render() { + let serverError = ''; + if (this.state.serverError) { + serverError = {this.state.serverError}; + } + + let btnClass = 'btn'; + if (this.state.fileSelected) { + btnClass = 'btn btn-primary'; + } + + let fileName; + if (this.state.fileName) { + fileName = this.state.fileName; + } + + let uploadButtonText; + if (this.state.uploading) { + uploadButtonText = ( + + ); + } else { + uploadButtonText = ( + + ); + } + + let activePluginsList; + let activePluginsContainer; + const plugins = Object.values(this.props.plugins); + if (this.state.loading) { + activePluginsList = ; + } else if (plugins.length === 0) { + activePluginsContainer = ( + + ); + } else { + activePluginsList = plugins.map( + (p) => { + let removeButtonText; + if (this.state.removing === p.id) { + removeButtonText = ( + + ); + } else { + removeButtonText = ( + + ); + } + + return ( + + + + + + {' ' + p.id} + + + + + + {' ' + p.description} + + + this.handleRemove(p.id)} + > + {removeButtonText} + + + + + ); + } + ); + + activePluginsContainer = ( + + {activePluginsList} + + ); + } + + return ( + + + + + } + description={ + + } + /> + + + + + + + + + + + + + + {uploadButtonText} + + + {fileName} + + {serverError} + + + + + + + + + + + {activePluginsContainer} + + + + + ); + } +} diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json index 7546209693..4ad2f6abf0 100755 --- a/webapp/i18n/en.json +++ b/webapp/i18n/en.json @@ -565,6 +565,20 @@ "admin.ldap.userFilterTitle": "User Filter:", "admin.ldap.usernameAttrEx": "E.g.: \"sAMAccountName\"", "admin.ldap.usernameAttrTitle": "Username Attribute:", + "admin.plugin.banner": "Plugins are experimental and not recommended for use in production.", + "admin.plugin.uploading": "Uploading...", + "admin.plugin.upload": "Upload", + "admin.plugin.error.extract": "Encountered an error when extracting the plugin. Review your plugin file content and try again.", + "admin.plugin.error.activate": "Unable to upload the plugin. It may conflict with another plugin on your server.", + "admin.plugin.no_plugins": "No active plugins.", + "admin.plugin.removing": "Removing...", + "admin.plugin.remove": "Remove", + "admin.plugin.id": "ID:", + "admin.plugin.desc": "Description:", + "admin.plugin.title": "Plugins (Experimental)", + "admin.plugin.uploadTitle": "Upload Plugin: ", + "admin.plugin.uploadDesc": "Upload a plugin for your Mattermost server. Adding or removing a webapp plugin requires users to refresh their browser or Desktop App before taking effect. See documentation to learn more.", + "admin.plugin.activeTitle": "Active Plugins: ", "admin.license.choose": "Choose File", "admin.license.chooseFile": "Choose File", "admin.license.edition": "Edition: ", @@ -885,6 +899,7 @@ "admin.sidebar.email": "Email", "admin.sidebar.emoji": "Emoji", "admin.sidebar.external": "External Services", + "admin.sidebar.plugins": "Plugins (Experimental)", "admin.sidebar.files": "Files", "admin.sidebar.general": "General", "admin.sidebar.gitlab": "GitLab", diff --git a/webapp/routes/route_admin_console.jsx b/webapp/routes/route_admin_console.jsx index 43245556f0..80950bc068 100644 --- a/webapp/routes/route_admin_console.jsx +++ b/webapp/routes/route_admin_console.jsx @@ -30,6 +30,7 @@ import EmailSettings from 'components/admin_console/email_settings.jsx'; import PushSettings from 'components/admin_console/push_settings.jsx'; import CustomIntegrationsSettings from 'components/admin_console/custom_integrations_settings.jsx'; import ExternalServiceSettings from 'components/admin_console/external_service_settings.jsx'; +import PluginSettings from 'components/admin_console/plugin_settings'; import WebrtcSettings from 'components/admin_console/webrtc_settings.jsx'; import DatabaseSettings from 'components/admin_console/database_settings.jsx'; import StorageSettings from 'components/admin_console/storage_settings.jsx'; @@ -169,6 +170,10 @@ export default ( path='jira' component={JIRASettings} /> + diff --git a/webapp/sass/routes/_admin-console.scss b/webapp/sass/routes/_admin-console.scss index ff02ca17e0..7983cf1314 100644 --- a/webapp/sass/routes/_admin-console.scss +++ b/webapp/sass/routes/_admin-console.scss @@ -114,6 +114,10 @@ .form-group { margin-bottom: 25px; + + &.half { + margin-bottom: 14px; + } } .file__upload { @@ -162,8 +166,8 @@ &.remove-filename { margin-bottom: 5px; - top: -2px; position: relative; + top: -2px; } } @@ -177,6 +181,22 @@ .fa { margin-right: 5px; } + + &.alert-transparent { + background: $white; + border: $border-gray; + margin: 0; + padding: 8px 12px; + width: 100%; + } + + hr { + margin: .8em 0; + + &:last-child { + display: none; + } + } } } diff --git a/webapp/yarn.lock b/webapp/yarn.lock index b4b8875a2a..fb33ce38e7 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -5065,7 +5065,7 @@ math-expression-evaluator@^1.2.14: mattermost-redux@mattermost/mattermost-redux#master: version "0.0.1" - resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/8a1736b94b5718ee939a8fb61a9a6a275c6ad703" + resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/e732a173be8c2547d7c8269020dff2f5e44baa26" dependencies: deep-equal "1.0.1" harmony-reflect "1.5.1"
+ +