PLT-6474 Basic Elasticsearch System Console Page (#6825)
* PLT-6474: Basic System Console Elasticsearch Config. * Fix review comments. * More review fixes. * Review comments.
Этот коммит содержится в:
коммит произвёл
Christopher Speller
родитель
4eed88e0c2
Коммит
a1f17c1f84
@@ -1662,8 +1662,6 @@ func (o *Config) Sanitize() {
|
|||||||
o.SqlSettings.DataSourceSearchReplicas[i] = FAKE_SETTING
|
o.SqlSettings.DataSourceSearchReplicas[i] = FAKE_SETTING
|
||||||
}
|
}
|
||||||
|
|
||||||
*o.ElasticSearchSettings.ConnectionUrl = FAKE_SETTING
|
|
||||||
*o.ElasticSearchSettings.Username = FAKE_SETTING
|
|
||||||
*o.ElasticSearchSettings.Password = FAKE_SETTING
|
*o.ElasticSearchSettings.Password = FAKE_SETTING
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -536,11 +536,6 @@ func getClientConfig(c *model.Config) map[string]string {
|
|||||||
props["PasswordRequireSymbol"] = strconv.FormatBool(*c.PasswordSettings.Symbol)
|
props["PasswordRequireSymbol"] = strconv.FormatBool(*c.PasswordSettings.Symbol)
|
||||||
}
|
}
|
||||||
|
|
||||||
if *License.Features.Elasticsearch {
|
|
||||||
props["ElasticSearchEnableIndexing"] = strconv.FormatBool(*c.ElasticSearchSettings.EnableIndexing)
|
|
||||||
props["ElasticSearchEnableSearching"] = strconv.FormatBool(*c.ElasticSearchSettings.EnableSearching)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *License.Features.Announcement {
|
if *License.Features.Announcement {
|
||||||
props["EnableBanner"] = strconv.FormatBool(*c.AnnouncementSettings.EnableBanner)
|
props["EnableBanner"] = strconv.FormatBool(*c.AnnouncementSettings.EnableBanner)
|
||||||
props["BannerText"] = *c.AnnouncementSettings.BannerText
|
props["BannerText"] = *c.AnnouncementSettings.BannerText
|
||||||
@@ -616,12 +611,6 @@ func Desanitize(cfg *model.Config) {
|
|||||||
cfg.SqlSettings.AtRestEncryptKey = Cfg.SqlSettings.AtRestEncryptKey
|
cfg.SqlSettings.AtRestEncryptKey = Cfg.SqlSettings.AtRestEncryptKey
|
||||||
}
|
}
|
||||||
|
|
||||||
if *cfg.ElasticSearchSettings.ConnectionUrl == model.FAKE_SETTING {
|
|
||||||
*cfg.ElasticSearchSettings.ConnectionUrl = *Cfg.ElasticSearchSettings.ConnectionUrl
|
|
||||||
}
|
|
||||||
if *cfg.ElasticSearchSettings.Username == model.FAKE_SETTING {
|
|
||||||
*cfg.ElasticSearchSettings.Username = *Cfg.ElasticSearchSettings.Username
|
|
||||||
}
|
|
||||||
if *cfg.ElasticSearchSettings.Password == model.FAKE_SETTING {
|
if *cfg.ElasticSearchSettings.Password == model.FAKE_SETTING {
|
||||||
*cfg.ElasticSearchSettings.Password = *Cfg.ElasticSearchSettings.Password
|
*cfg.ElasticSearchSettings.Password = *Cfg.ElasticSearchSettings.Password
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -383,3 +383,16 @@ export function getPostsPerDayAnalytics(teamId) {
|
|||||||
export function getUsersPerDayAnalytics(teamId) {
|
export function getUsersPerDayAnalytics(teamId) {
|
||||||
AdminActions.getUsersPerDayAnalytics(teamId)(dispatch, getState);
|
AdminActions.getUsersPerDayAnalytics(teamId)(dispatch, getState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function elasticsearchTest(config, success, error) {
|
||||||
|
AdminActions.testElasticsearch(config)(dispatch, getState).then(
|
||||||
|
(data) => {
|
||||||
|
if (data && success) {
|
||||||
|
success(data);
|
||||||
|
} else if (data == null && error) {
|
||||||
|
const serverError = getState().requests.admin.testElasticsearch.error;
|
||||||
|
error({id: serverError.server_error_id, ...serverError});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -258,6 +258,21 @@ export default class AdminSidebar extends React.Component {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let elasticSearchSettings = null;
|
||||||
|
if (window.mm_license.IsLicensed === 'true') {
|
||||||
|
elasticSearchSettings = (
|
||||||
|
<AdminSidebarSection
|
||||||
|
name='elasticsearch'
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.sidebar.elasticsearch'
|
||||||
|
defaultMessage='Elasticsearch'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='admin-sidebar'>
|
<div className='admin-sidebar'>
|
||||||
<AdminSidebarHeader/>
|
<AdminSidebarHeader/>
|
||||||
@@ -618,6 +633,7 @@ export default class AdminSidebar extends React.Component {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
{elasticSearchSettings}
|
||||||
<AdminSidebarSection
|
<AdminSidebarSection
|
||||||
name='developer'
|
name='developer'
|
||||||
title={
|
title={
|
||||||
|
|||||||
280
webapp/components/admin_console/elasticsearch_settings.jsx
Обычный файл
280
webapp/components/admin_console/elasticsearch_settings.jsx
Обычный файл
@@ -0,0 +1,280 @@
|
|||||||
|
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
|
||||||
|
import AdminSettings from './admin_settings.jsx';
|
||||||
|
import {elasticsearchTest} from 'actions/admin_actions.jsx';
|
||||||
|
import BooleanSetting from './boolean_setting.jsx';
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
import SettingsGroup from './settings_group.jsx';
|
||||||
|
import TextSetting from './text_setting.jsx';
|
||||||
|
import RequestButton from './request_button/request_button.jsx';
|
||||||
|
|
||||||
|
export default class ElasticsearchSettings extends AdminSettings {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.getConfigFromState = this.getConfigFromState.bind(this);
|
||||||
|
|
||||||
|
this.doTestConfig = this.doTestConfig.bind(this);
|
||||||
|
this.handleChange = this.handleChange.bind(this);
|
||||||
|
|
||||||
|
this.renderSettings = this.renderSettings.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
getConfigFromState(config) {
|
||||||
|
config.ElasticSearchSettings.ConnectionUrl = this.state.connectionUrl;
|
||||||
|
config.ElasticSearchSettings.Username = this.state.username;
|
||||||
|
config.ElasticSearchSettings.Password = this.state.password;
|
||||||
|
config.ElasticSearchSettings.Sniff = this.state.sniff;
|
||||||
|
config.ElasticSearchSettings.EnableIndexing = this.state.enableIndexing;
|
||||||
|
config.ElasticSearchSettings.EnableSearching = this.state.enableSearching;
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
getStateFromConfig(config) {
|
||||||
|
return {
|
||||||
|
connectionUrl: config.ElasticSearchSettings.ConnectionUrl,
|
||||||
|
username: config.ElasticSearchSettings.Username,
|
||||||
|
password: config.ElasticSearchSettings.Password,
|
||||||
|
sniff: config.ElasticSearchSettings.Sniff,
|
||||||
|
enableIndexing: config.ElasticSearchSettings.EnableIndexing,
|
||||||
|
enableSearching: config.ElasticSearchSettings.EnableSearching,
|
||||||
|
configTested: true,
|
||||||
|
canSave: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange(id, value) {
|
||||||
|
if (id === 'enableIndexing') {
|
||||||
|
if (value === false) {
|
||||||
|
this.setState({
|
||||||
|
enableSearching: false
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
canSave: false,
|
||||||
|
configTested: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id === 'connectionUrl' || id === 'username' || id === 'password' || id === 'sniff') {
|
||||||
|
this.setState({
|
||||||
|
configTested: false,
|
||||||
|
canSave: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
super.handleChange(id, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
canSave() {
|
||||||
|
return this.state.canSave;
|
||||||
|
}
|
||||||
|
|
||||||
|
doTestConfig(success, error) {
|
||||||
|
const config = JSON.parse(JSON.stringify(this.props.config));
|
||||||
|
this.getConfigFromState(config);
|
||||||
|
|
||||||
|
elasticsearchTest(
|
||||||
|
config,
|
||||||
|
() => {
|
||||||
|
this.setState({
|
||||||
|
configTested: true,
|
||||||
|
canSave: true
|
||||||
|
});
|
||||||
|
success();
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
this.setState({
|
||||||
|
configTested: false,
|
||||||
|
canSave: false
|
||||||
|
});
|
||||||
|
error(err);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTitle() {
|
||||||
|
return (
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.elasticsearch.title'
|
||||||
|
defaultMessage='Elasticsearch Settings'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSettings() {
|
||||||
|
return (
|
||||||
|
<SettingsGroup>
|
||||||
|
<div className='banner'>
|
||||||
|
<div className='banner__content'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.elasticsearch.noteDescription'
|
||||||
|
defaultMessage='Changing properties in this section will require a server restart before taking effect.'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<BooleanSetting
|
||||||
|
id='enableIndexing'
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.elasticsearch.enableIndexingTitle'
|
||||||
|
defaultMessage='Enable Elasticsearch Indexing:'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
helpText={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.elasticsearch.enableIndexingDescription'
|
||||||
|
defaultMessage='When true, indexing of new posts occurs automatically. Search queries will use database search until "Enable Elasticsearch for search queries" is enabled. {documentationLink}'
|
||||||
|
values={{
|
||||||
|
documentationLink: (
|
||||||
|
<a
|
||||||
|
href='http://www.mattermost.com'
|
||||||
|
rel='noopener noreferrer'
|
||||||
|
target='_blank'
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.elasticsearch.enableIndexingDescription.documentationLinkText'
|
||||||
|
defaultMessage='Learn more about Elasticsearch in our documentation.'
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
value={this.state.enableIndexing}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
/>
|
||||||
|
<TextSetting
|
||||||
|
id='connectionUrl'
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.elasticsearch.connectionUrlTitle'
|
||||||
|
defaultMessage='Server Connection Address:'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
placeholder={Utils.localizeMessage('admin.elasticsearch.connectionUrlExample', 'E.g.: "https://elasticsearch.example.org:9200"')}
|
||||||
|
helpText={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.elasticsearch.connectionUrlDescription'
|
||||||
|
defaultMessage='The address of the Elasticsearch server. {documentationLink}'
|
||||||
|
values={{
|
||||||
|
documentationLink: (
|
||||||
|
<a
|
||||||
|
href='http://www.mattermost.com'
|
||||||
|
rel='noopener noreferrer'
|
||||||
|
target='_blank'
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.elasticsearch.connectionUrlExample.documentationLinkText'
|
||||||
|
defaultMessage='Please see documentation with server setup instructions.'
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
value={this.state.connectionUrl}
|
||||||
|
disabled={!this.state.enableIndexing}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
/>
|
||||||
|
<TextSetting
|
||||||
|
id='username'
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.elasticsearch.usernameTitle'
|
||||||
|
defaultMessage='Server Username:'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
placeholder={Utils.localizeMessage('admin.elasticsearch.usernameExample', 'E.g.: "elastic"')}
|
||||||
|
helpText={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.elasticsearch.usernameDescription'
|
||||||
|
defaultMessage='(Optional) The username to authenticate to the Elasticsearch server.'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
value={this.state.username}
|
||||||
|
disabled={!this.state.enableIndexing}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
/>
|
||||||
|
<TextSetting
|
||||||
|
id='password'
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.elasticsearch.passwordTitle'
|
||||||
|
defaultMessage='Server Password:'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
placeholder={Utils.localizeMessage('admin.elasticsearch.password', 'E.g.: "yourpassword"')}
|
||||||
|
helpText={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.elasticsearch.passwordDescription'
|
||||||
|
defaultMessage='(Optional) The password to authenticate to the Elasticsearch server.'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
value={this.state.password}
|
||||||
|
disabled={!this.state.enableIndexing}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
/>
|
||||||
|
<BooleanSetting
|
||||||
|
id='sniff'
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.elasticsearch.sniffTitle'
|
||||||
|
defaultMessage='Enable Cluster Sniffing:'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
helpText={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.elasticsearch.sniffDescription'
|
||||||
|
defaultMessage='When true, sniffing finds and connects to all data nodes in your cluster automatically.'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
value={this.state.sniff}
|
||||||
|
disabled={!this.state.enableIndexing}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
/>
|
||||||
|
<RequestButton
|
||||||
|
requestAction={this.doTestConfig}
|
||||||
|
helpText={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.elasticsearch.testHelpText'
|
||||||
|
defaultMessage='Tests if the Mattermost server can connect to the Elasticsearch server specified. Testing the connection does not save the configuration. See log file for more detailed error messages.'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
buttonText={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.elasticsearch.elasticsearch_test_button'
|
||||||
|
defaultMessage='Test Connection'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
disabled={!this.state.enableIndexing}
|
||||||
|
/>
|
||||||
|
<BooleanSetting
|
||||||
|
id='enableSearching'
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.elasticsearch.enableSearchingTitle'
|
||||||
|
defaultMessage='Enable Elasticsearch for search queries:'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
helpText={
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.elasticsearch.enableSearchingDescription'
|
||||||
|
defaultMessage='Requires a successful connection to the Elasticsearch server. When true, Elasticsearch will be used for all search queries using the latest index. Search results may be incomplete until a bulk index of the existing post database is finished. When false, database search is used.'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
value={this.state.enableSearching}
|
||||||
|
disabled={!this.state.enableIndexing || !this.state.configTested}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
/>
|
||||||
|
</SettingsGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -235,6 +235,27 @@
|
|||||||
"admin.customization.support": "Legal and Support",
|
"admin.customization.support": "Legal and Support",
|
||||||
"admin.database.title": "Database Settings",
|
"admin.database.title": "Database Settings",
|
||||||
"admin.developer.title": "Developer Settings",
|
"admin.developer.title": "Developer Settings",
|
||||||
|
"admin.elasticsearch.title": "Elasticsearch Settings",
|
||||||
|
"admin.elasticsearch.noteDescription": "Changing properties in this section will require a server restart before taking effect.",
|
||||||
|
"admin.elasticsearch.enableIndexingTitle": "Enable Elasticsearch Indexing:",
|
||||||
|
"admin.elasticsearch.enableIndexingDescription": "When true, indexing of new posts occurs automatically. Search queries will use database search until \"Enable Elasticsearch for search queries\" is enabled. {documentationLink}",
|
||||||
|
"admin.elasticsearch.enableIndexingDescription.documentationLinkText": "Learn more about Elasticsearch in our documentation.",
|
||||||
|
"admin.elasticsearch.connectionUrlTitle": "Server Connection Address:",
|
||||||
|
"admin.elasticsearch.connectionUrlDescription": "The address of the Elasticsearch server. {documentationLink}",
|
||||||
|
"admin.elasticsearch.connectionUrlExample.documentationLinkText": "Please see documentation with server setup instructions.",
|
||||||
|
"admin.elasticsearch.usernameTitle": "Server Username:",
|
||||||
|
"admin.elasticsearch.usernameDescription": "(Optional) The username to authenticate to the Elasticsearch server.",
|
||||||
|
"admin.elasticsearch.passwordTitle": "Server Password:",
|
||||||
|
"admin.elasticsearch.passwordDescription": "(Optional) The password to authenticate to the Elasticsearch server.",
|
||||||
|
"admin.elasticsearch.sniffTitle": "Enable Cluster Sniffing:",
|
||||||
|
"admin.elasticsearch.sniffDescription": "When true, sniffing finds and connects to all data nodes in your cluster automatically.",
|
||||||
|
"admin.elasticsearch.enableSearchingTitle": "Enable Elasticsearch for search queries:",
|
||||||
|
"admin.elasticsearch.enableSearchingDescription": "Requires a successful connection to the Elasticsearch server. When true, Elasticsearch will be used for all search queries using the latest index. Search results may be incomplete until a bulk index of the existing post database is finished. When false, database search is used.",
|
||||||
|
"admin.elasticsearch.connectionUrlExample": "E.g.: \"https://elasticsearch.example.org:9200\"",
|
||||||
|
"admin.elasticsearch.usernameExample": "E.g.: \"elastic\"",
|
||||||
|
"admin.elasticsearch.password": "E.g.: \"yourpassword\"",
|
||||||
|
"admin.elasticsearch.testHelpText": "Tests if the Mattermost server can connect to the Elasticsearch server specified. Testing the connection does not save the configuration. See log file for more detailed error messages.",
|
||||||
|
"admin.elasticsearch.elasticsearch_test_button": "Test Connection",
|
||||||
"admin.email.agreeHPNS": " I understand and accept the Mattermost Hosted Push Notification Service <a href=\"https://about.mattermost.com/hpns-terms/\" target='_blank'>Terms of Service</a> and <a href=\"https://about.mattermost.com/hpns-privacy/\" target='_blank'>Privacy Policy</a>.",
|
"admin.email.agreeHPNS": " I understand and accept the Mattermost Hosted Push Notification Service <a href=\"https://about.mattermost.com/hpns-terms/\" target='_blank'>Terms of Service</a> and <a href=\"https://about.mattermost.com/hpns-privacy/\" target='_blank'>Privacy Policy</a>.",
|
||||||
"admin.email.allowEmailSignInDescription": "When true, Mattermost allows users to sign in using their email and password.",
|
"admin.email.allowEmailSignInDescription": "When true, Mattermost allows users to sign in using their email and password.",
|
||||||
"admin.email.allowEmailSignInTitle": "Enable sign-in with email: ",
|
"admin.email.allowEmailSignInTitle": "Enable sign-in with email: ",
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import TeamAnalytics from 'components/analytics/team_analytics';
|
|||||||
import LicenseSettings from 'components/admin_console/license_settings.jsx';
|
import LicenseSettings from 'components/admin_console/license_settings.jsx';
|
||||||
import Audits from 'components/admin_console/audits';
|
import Audits from 'components/admin_console/audits';
|
||||||
import Logs from 'components/admin_console/server_logs';
|
import Logs from 'components/admin_console/server_logs';
|
||||||
|
import ElasticsearchSettings from 'components/admin_console/elasticsearch_settings.jsx';
|
||||||
|
|
||||||
export default (
|
export default (
|
||||||
<Route>
|
<Route>
|
||||||
@@ -199,6 +200,10 @@ export default (
|
|||||||
path='database'
|
path='database'
|
||||||
component={DatabaseSettings}
|
component={DatabaseSettings}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path='elasticsearch'
|
||||||
|
component={ElasticsearchSettings}
|
||||||
|
/>
|
||||||
<Route
|
<Route
|
||||||
path='developer'
|
path='developer'
|
||||||
component={DeveloperSettings}
|
component={DeveloperSettings}
|
||||||
|
|||||||
@@ -5047,7 +5047,7 @@ math-expression-evaluator@^1.2.14:
|
|||||||
|
|
||||||
mattermost-redux@mattermost/mattermost-redux#webapp-master:
|
mattermost-redux@mattermost/mattermost-redux#webapp-master:
|
||||||
version "0.0.1"
|
version "0.0.1"
|
||||||
resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/dd48556075c8be41aa5ac4a0165bbe830d496875"
|
resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/a850fc2696c7083a068f7852c1d4b8bce0ec72ba"
|
||||||
dependencies:
|
dependencies:
|
||||||
deep-equal "1.0.1"
|
deep-equal "1.0.1"
|
||||||
harmony-reflect "1.5.1"
|
harmony-reflect "1.5.1"
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user