From df085d273d7ed3fe0d53b78364da46cdd6429c53 Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Tue, 5 Sep 2017 18:12:55 -0400 Subject: Experimental plugin system console UI (#7338) * Add system console UI for uploading/listing/removing plugins * Add localization strings * Add banner to plugin settings * Updating UI for experimental plugins (#7362) * Text updates * Updating UI for experimental plugin stuff (#7377) * Properly clear file input after upload --- .../admin_console/plugin_settings/index.js | 27 ++ .../plugin_settings/plugin_settings.jsx | 293 +++++++++++++++++++++ 2 files changed, 320 insertions(+) create mode 100644 webapp/components/admin_console/plugin_settings/index.js create mode 100644 webapp/components/admin_console/plugin_settings/plugin_settings.jsx (limited to 'webapp/components/admin_console/plugin_settings') diff --git a/webapp/components/admin_console/plugin_settings/index.js b/webapp/components/admin_console/plugin_settings/index.js new file mode 100644 index 000000000..469d4ee2e --- /dev/null +++ b/webapp/components/admin_console/plugin_settings/index.js @@ -0,0 +1,27 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; +import {uploadPlugin, removePlugin, getPlugins} from 'mattermost-redux/actions/admin'; + +import PluginSettings from './plugin_settings.jsx'; + +function mapStateToProps(state, ownProps) { + return { + ...ownProps, + plugins: state.entities.admin.plugins + }; +} + +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators({ + uploadPlugin, + removePlugin, + getPlugins + }, dispatch) + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(PluginSettings); diff --git a/webapp/components/admin_console/plugin_settings/plugin_settings.jsx b/webapp/components/admin_console/plugin_settings/plugin_settings.jsx new file mode 100644 index 000000000..286e05c06 --- /dev/null +++ b/webapp/components/admin_console/plugin_settings/plugin_settings.jsx @@ -0,0 +1,293 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import LoadingScreen from 'components/loading_screen.jsx'; +import Banner from 'components/admin_console/banner.jsx'; + +import * as Utils from 'utils/utils.jsx'; + +import React from 'react'; +import PropTypes from 'prop-types'; +import {FormattedMessage, FormattedHTMLMessage} from 'react-intl'; + +export default class PluginSettings extends React.Component { + static propTypes = { + + /* + * The config + */ + config: PropTypes.object.isRequired, + + /* + * Plugins object with ids as keys and manifests as values + */ + plugins: PropTypes.object.isRequired, + + actions: PropTypes.shape({ + + /* + * Function to upload a plugin + */ + uploadPlugin: PropTypes.func.isRequired, + + /* + * Function to remove a plugin + */ + removePlugin: PropTypes.func.isRequired, + + /* + * Function to get installed plugins + */ + getPlugins: PropTypes.func.isRequired + }).isRequired + } + + constructor(props) { + super(props); + + this.state = { + loading: true, + fileSelected: false, + fileName: null, + serverError: null + }; + } + + componentDidMount() { + this.props.actions.getPlugins().then( + () => 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 =
; + } + + 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} +
+ +
+
+ ); + } + ); + + activePluginsContainer = ( +
+ {activePluginsList} +
+ ); + } + + return ( +
+

+ +

+ } + description={ + + } + /> +
+
+ +
+
+ + +
+ +
+ {fileName} +
+ {serverError} +

+ +

+
+
+
+ +
+ {activePluginsContainer} +
+
+
+
+ ); + } +} -- cgit v1.2.3-1-g7c22