mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
PLT-6595 (Client): Elasticsearch indexing system console UI (#6991)
* PLT-6595: System Console for Elasticsearch Job Management. * Fixing UI issues * Fixing colors * ESLint Fixes. * Update test snapshots. * Fixing cancel button * Fix review comments. * Config capitalisation. * Review fixes.
This commit is contained in:
@@ -79,7 +79,7 @@ func (watcher *Watcher) PollAndNotify() {
|
||||
default:
|
||||
}
|
||||
}
|
||||
} else if js.Type == model.JOB_TYPE_SEARCH_INDEXING {
|
||||
} else if js.Type == model.JOB_TYPE_ELASTICSEARCH_POST_INDEXING {
|
||||
if watcher.workers.ElasticsearchIndexing != nil {
|
||||
select {
|
||||
case watcher.workers.ElasticsearchIndexing.JobChannel() <- j:
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
JOB_TYPE_DATA_RETENTION = "data_retention"
|
||||
JOB_TYPE_SEARCH_INDEXING = "search_indexing"
|
||||
JOB_TYPE_DATA_RETENTION = "data_retention"
|
||||
JOB_TYPE_ELASTICSEARCH_POST_INDEXING = "elasticsearch_post_indexing"
|
||||
|
||||
JOB_STATUS_PENDING = "pending"
|
||||
JOB_STATUS_IN_PROGRESS = "in_progress"
|
||||
@@ -44,7 +44,7 @@ func (j *Job) IsValid() *AppError {
|
||||
|
||||
switch j.Type {
|
||||
case JOB_TYPE_DATA_RETENTION:
|
||||
case JOB_TYPE_SEARCH_INDEXING:
|
||||
case JOB_TYPE_ELASTICSEARCH_POST_INDEXING:
|
||||
default:
|
||||
return NewAppError("Job.IsValid", "model.job.is_valid.type.app_error", nil, "id="+j.Id, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
@@ -396,3 +396,16 @@ export function elasticsearchTest(config, success, error) {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function elasticsearchPurgeIndexes(success, error) {
|
||||
AdminActions.purgeElasticsearchIndexes()(dispatch, getState).then(
|
||||
(data) => {
|
||||
if (data && success) {
|
||||
success(data);
|
||||
} else if (data == null && error) {
|
||||
const serverError = getState().requests.admin.purgeElasticsearchIndexes.error;
|
||||
error({id: serverError.server_error_id, ...serverError});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
34
webapp/actions/job_actions.jsx
Normal file
34
webapp/actions/job_actions.jsx
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import * as JobsActions from 'mattermost-redux/actions/jobs';
|
||||
|
||||
import store from 'stores/redux_store.jsx';
|
||||
const dispatch = store.dispatch;
|
||||
const getState = store.getState;
|
||||
|
||||
export function createJob(job, success, error) {
|
||||
JobsActions.createJob(job)(dispatch, getState).then(
|
||||
(data) => {
|
||||
if (data && success) {
|
||||
success(data);
|
||||
} else if (data == null && error) {
|
||||
const serverError = getState().requests.jobs.createJob.error;
|
||||
error({id: serverError.server_error_id, ...serverError});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function cancelJob(jobId, success, error) {
|
||||
JobsActions.cancelJob(jobId)(dispatch, getState).then(
|
||||
(data) => {
|
||||
if (data && success) {
|
||||
success(data);
|
||||
} else if (data == null && error) {
|
||||
const serverError = getState().requests.jobs.cancelJob.error;
|
||||
error({id: serverError.server_error_id, ...serverError});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -6,12 +6,13 @@ 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 {elasticsearchTest, elasticsearchPurgeIndexes} 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';
|
||||
import ElasticsearchStatus from './elasticsearch_status';
|
||||
|
||||
export default class ElasticsearchSettings extends AdminSettings {
|
||||
constructor(props) {
|
||||
@@ -21,6 +22,7 @@ export default class ElasticsearchSettings extends AdminSettings {
|
||||
|
||||
this.doTestConfig = this.doTestConfig.bind(this);
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.handleSaved = this.handleSaved.bind(this);
|
||||
|
||||
this.renderSettings = this.renderSettings.bind(this);
|
||||
}
|
||||
@@ -45,7 +47,8 @@ export default class ElasticsearchSettings extends AdminSettings {
|
||||
enableIndexing: config.ElasticsearchSettings.EnableIndexing,
|
||||
enableSearching: config.ElasticsearchSettings.EnableSearching,
|
||||
configTested: true,
|
||||
canSave: true
|
||||
canSave: true,
|
||||
canPurgeAndIndex: config.ElasticsearchSettings.EnableIndexing
|
||||
};
|
||||
}
|
||||
|
||||
@@ -70,9 +73,21 @@ export default class ElasticsearchSettings extends AdminSettings {
|
||||
});
|
||||
}
|
||||
|
||||
if (id !== 'enableSearching') {
|
||||
this.setState({
|
||||
canPurgeAndIndex: false
|
||||
});
|
||||
}
|
||||
|
||||
super.handleChange(id, value);
|
||||
}
|
||||
|
||||
handleSaved() {
|
||||
this.setState({
|
||||
canPurgeAndIndex: this.state.enableIndexing
|
||||
});
|
||||
}
|
||||
|
||||
canSave() {
|
||||
return this.state.canSave;
|
||||
}
|
||||
@@ -89,6 +104,7 @@ export default class ElasticsearchSettings extends AdminSettings {
|
||||
canSave: true
|
||||
});
|
||||
success();
|
||||
this.doSubmit();
|
||||
},
|
||||
(err) => {
|
||||
this.setState({
|
||||
@@ -135,7 +151,7 @@ export default class ElasticsearchSettings extends AdminSettings {
|
||||
values={{
|
||||
documentationLink: (
|
||||
<a
|
||||
href='http://www.mattermost.com'
|
||||
href='https://about.mattermost.com/default-elasticsearch-documentation/'
|
||||
rel='noopener noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
@@ -167,7 +183,7 @@ export default class ElasticsearchSettings extends AdminSettings {
|
||||
values={{
|
||||
documentationLink: (
|
||||
<a
|
||||
href='http://www.mattermost.com'
|
||||
href='https://about.mattermost.com/default-elasticsearch-server-setup/'
|
||||
rel='noopener noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
@@ -245,7 +261,7 @@ export default class ElasticsearchSettings extends AdminSettings {
|
||||
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.'
|
||||
defaultMessage='Tests if the Mattermost server can connect to the Elasticsearch server specified. Testing the connection only saves the configuration if the test is successful. See log file for more detailed error messages.'
|
||||
/>
|
||||
}
|
||||
buttonText={
|
||||
@@ -254,8 +270,45 @@ export default class ElasticsearchSettings extends AdminSettings {
|
||||
defaultMessage='Test Connection'
|
||||
/>
|
||||
}
|
||||
successMessage={{
|
||||
id: 'admin.elasticsearch.testConfigSuccess',
|
||||
defaultMessage: 'Test successful. Configuration saved.'
|
||||
}}
|
||||
disabled={!this.state.enableIndexing}
|
||||
/>
|
||||
<ElasticsearchStatus
|
||||
isConfigured={this.state.canPurgeAndIndex}
|
||||
/>
|
||||
<RequestButton
|
||||
requestAction={elasticsearchPurgeIndexes}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id='admin.elasticsearch.purgeIndexesHelpText'
|
||||
defaultMessage='Purging will entirely remove the index on the Elasticsearch server. Search results may be incomplete until a bulk index of the existing post database is rebuilt.'
|
||||
/>
|
||||
}
|
||||
buttonText={
|
||||
<FormattedMessage
|
||||
id='admin.elasticsearch.purgeIndexesButton'
|
||||
defaultMessage='Purge Index'
|
||||
/>
|
||||
}
|
||||
successMessage={{
|
||||
id: 'admin.elasticsearch.purgeIndexesButton.success',
|
||||
defaultMessage: 'Indexes purged successfully.'
|
||||
}}
|
||||
errorMessage={{
|
||||
id: 'admin.elasticsearch.purgeIndexesButton.error',
|
||||
defaultMessage: 'Failed to purge indexes: {error}'
|
||||
}}
|
||||
disabled={!this.state.canPurgeAndIndex}
|
||||
label={(
|
||||
<FormattedMessage
|
||||
id='admin.elasticsearch.purgeIndexesButton.label'
|
||||
defaultMessage='Purge Indexes:'
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<BooleanSetting
|
||||
id='enableSearching'
|
||||
label={
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {getJobsByType} from 'mattermost-redux/actions/jobs';
|
||||
import {JobTypes} from 'utils/constants.jsx';
|
||||
|
||||
import * as Selectors from 'mattermost-redux/selectors/entities/jobs';
|
||||
|
||||
import Status from './status.jsx';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
...ownProps,
|
||||
jobs: Selectors.makeGetJobsByType(JobTypes.ELASTICSEARCH_POST_INDEXING)(state)
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
getJobsByType
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Status);
|
||||
361
webapp/components/admin_console/elasticsearch_status/status.jsx
Normal file
361
webapp/components/admin_console/elasticsearch_status/status.jsx
Normal file
@@ -0,0 +1,361 @@
|
||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import {createJob, cancelJob} from 'actions/job_actions.jsx';
|
||||
import {JobTypes, JobStatuses} from 'utils/constants.jsx';
|
||||
import RequestButton from '../request_button/request_button.jsx';
|
||||
|
||||
export default class Status extends React.PureComponent {
|
||||
static propTypes = {
|
||||
|
||||
/**
|
||||
* Array of jobs
|
||||
*/
|
||||
jobs: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
|
||||
/**
|
||||
* Whether Elasticsearch is properly configured.
|
||||
*/
|
||||
isConfigured: PropTypes.bool.isRequired,
|
||||
|
||||
actions: PropTypes.shape({
|
||||
|
||||
/**
|
||||
* Function to fetch jobs
|
||||
*/
|
||||
getJobsByType: PropTypes.func.isRequired
|
||||
}).isRequired
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.interval = null;
|
||||
|
||||
this.state = {
|
||||
loading: true,
|
||||
cancelInProgress: false
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
// reload the cluster status every 15 seconds
|
||||
this.interval = setInterval(this.reload, 15000);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.actions.getJobsByType(JobTypes.ELASTICSEARCH_POST_INDEXING).then(
|
||||
() => this.setState({loading: false})
|
||||
);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.interval) {
|
||||
clearInterval(this.interval);
|
||||
}
|
||||
}
|
||||
|
||||
reload = () => {
|
||||
this.props.actions.getJobsByType(JobTypes.ELASTICSEARCH_POST_INDEXING).then(
|
||||
() => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
cancelInProgress: false
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
createIndexJob = (success, error) => {
|
||||
const job = {
|
||||
type: JobTypes.ELASTICSEARCH_POST_INDEXING
|
||||
};
|
||||
|
||||
createJob(
|
||||
job,
|
||||
() => {
|
||||
this.reload();
|
||||
success();
|
||||
},
|
||||
error
|
||||
);
|
||||
};
|
||||
|
||||
cancelIndexJob = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const chosenJob = this.getChosenJob();
|
||||
if (!chosenJob) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
cancelInProgress: true
|
||||
});
|
||||
|
||||
cancelJob(
|
||||
chosenJob.id,
|
||||
() => {
|
||||
this.reload();
|
||||
},
|
||||
() => {
|
||||
this.reload();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
getChosenJob = () => {
|
||||
let chosenJob = null;
|
||||
|
||||
if (this.props.jobs.length > 0) {
|
||||
for (let i = 0; i < this.props.jobs.length; i++) {
|
||||
const job = this.props.jobs[i];
|
||||
if (job.status === JobStatuses.CANCEL_REQUESTED || job.status === JobStatuses.IN_PROGRESS) {
|
||||
chosenJob = job;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!chosenJob) {
|
||||
for (let i = 0; i < this.props.jobs.length; i++) {
|
||||
const job = this.props.jobs[i];
|
||||
if (job.status !== JobStatuses.PENDING && chosenJob) {
|
||||
continue;
|
||||
} else {
|
||||
chosenJob = job;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return chosenJob;
|
||||
};
|
||||
|
||||
render() {
|
||||
const chosenJob = this.getChosenJob();
|
||||
|
||||
let indexButtonDisabled = !this.props.isConfigured;
|
||||
let buttonText = (
|
||||
<FormattedMessage
|
||||
id='admin.elasticsearch.indexButton.ready'
|
||||
defaultMessage='Build Index'
|
||||
/>
|
||||
);
|
||||
let cancelButton = null;
|
||||
let indexButtonHelp = (
|
||||
<FormattedMessage
|
||||
id='admin.elasticsearch.indexHelpText.buildIndex'
|
||||
defaultMessage='All posts in the database will be indexed from oldest to newest. Elasticsearch is available during indexing but search results may be incomplete until the indexing job is complete.'
|
||||
/>
|
||||
);
|
||||
|
||||
if (this.state.loading) {
|
||||
indexButtonDisabled = true;
|
||||
} else if (chosenJob) {
|
||||
if (chosenJob.status === JobStatuses.PENDING || chosenJob.status === JobStatuses.IN_PROGRESS || chosenJob.status === JobStatuses.CANCEL_REQUESTED) {
|
||||
indexButtonDisabled = true;
|
||||
buttonText = (
|
||||
<span>
|
||||
<span className='fa fa-refresh icon--rotate'/>
|
||||
<FormattedMessage
|
||||
id='admin.elasticsearch.indexButton.inProgress'
|
||||
defaultMessage='Indexing in progress'
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (chosenJob.status === JobStatuses.PENDING || chosenJob.status === JobStatuses.IN_PROGRESS || chosenJob.status === JobStatuses.CANCEL_REQUESTED) {
|
||||
indexButtonHelp = (
|
||||
<FormattedMessage
|
||||
id='admin.elasticsearch.indexHelpText.cancelIndexing'
|
||||
defaultMessage='Cancelling stops the indexing job and removes it from the queue. Posts that have already been indexed will not be deleted.'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (!this.state.cancelInProgress && (chosenJob.status === JobStatuses.PENDING || chosenJob.status === JobStatuses.IN_PROGRESS)) {
|
||||
cancelButton = (
|
||||
<a
|
||||
href='#'
|
||||
className='btn btn-link'
|
||||
onClick={this.cancelIndexJob}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='admin.elasticsearchStatus.cancelButton'
|
||||
defaultMessage='Cancel'
|
||||
/>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const indexButton = (
|
||||
<RequestButton
|
||||
requestAction={this.createIndexJob}
|
||||
helpText={indexButtonHelp}
|
||||
buttonText={buttonText}
|
||||
disabled={indexButtonDisabled}
|
||||
showSuccessMessage={false}
|
||||
errorMessage={{
|
||||
id: 'admin.elasticsearch.bulkIndexButton.error',
|
||||
defaultMessage: 'Failed to schedule Bulk Index Job: {error}'
|
||||
}}
|
||||
alternativeActionElement={cancelButton}
|
||||
label={(
|
||||
<FormattedMessage
|
||||
id='admin.elasticsearchStatus.bulkIndexLabel'
|
||||
defaultMessage='Bulk Indexing:'
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
||||
let status = null;
|
||||
let statusHelp = null;
|
||||
let statusClass = null;
|
||||
if (!this.props.isConfigured) {
|
||||
status = (
|
||||
<FormattedMessage
|
||||
id='admin.elasticsearchStatus.statusIndexingDisabled'
|
||||
defaultMessage='Indexing disabled.'
|
||||
/>
|
||||
);
|
||||
} else if (this.state.loading) {
|
||||
status = (
|
||||
<FormattedMessage
|
||||
id='admin.elasticsearchStatus.statusLoading'
|
||||
defaultMessage='Loading...'
|
||||
/>
|
||||
);
|
||||
statusClass = 'status-icon-unknown';
|
||||
} else if (chosenJob) {
|
||||
if (chosenJob.status === JobStatuses.PENDING) {
|
||||
status = (
|
||||
<FormattedMessage
|
||||
id='admin.elasticsearchStatus.statusPending'
|
||||
defaultMessage='Job pending.'
|
||||
/>
|
||||
);
|
||||
statusHelp = (
|
||||
<FormattedMessage
|
||||
id='admin.elasticsearchStatus.statusPending.help'
|
||||
defaultMessage='Elasticsearch index job is queued on the job server. If Elasticsearch is enabled, search results may be incomplete until the job is finished.'
|
||||
/>
|
||||
);
|
||||
statusClass = 'status-icon-warning';
|
||||
} else if (chosenJob.status === JobStatuses.IN_PROGRESS) {
|
||||
status = (
|
||||
<FormattedMessage
|
||||
id='admin.elasticsearchStatus.statusInProgress'
|
||||
defaultMessage='Job in progress. {percent}% complete.'
|
||||
values={{
|
||||
percent: chosenJob.progress
|
||||
}}
|
||||
/>
|
||||
);
|
||||
statusHelp = (
|
||||
<FormattedMessage
|
||||
id='admin.elasticsearchStatus.statusInProgress.help'
|
||||
defaultMessage='Indexing is in progress on the job server. If Elasticsearch is enabled, search results may be incomplete until the job is finished.'
|
||||
/>
|
||||
);
|
||||
statusClass = 'status-icon-warning';
|
||||
} else if (chosenJob.status === JobStatuses.SUCCESS) {
|
||||
status = (
|
||||
<FormattedMessage
|
||||
id='admin.elasticsearchStatus.statusSuccess'
|
||||
defaultMessage='Indexing complete.'
|
||||
/>
|
||||
);
|
||||
statusHelp = (
|
||||
<FormattedMessage
|
||||
id='admin.elasticsearchStatus.statusSuccess.help'
|
||||
defaultMessage='Indexing is complete and new posts are being automatically indexed.'
|
||||
/>
|
||||
);
|
||||
statusClass = 'status-icon-success';
|
||||
} else if (chosenJob.status === JobStatuses.ERROR) {
|
||||
status = (
|
||||
<FormattedMessage
|
||||
id='admin.elasticsearchStatus.statusError'
|
||||
defaultMessage='Indexing error.'
|
||||
/>
|
||||
);
|
||||
statusHelp = (
|
||||
<FormattedMessage
|
||||
id='admin.elasticsearchStatus.statusError.help'
|
||||
defaultMessage='Mattermost encountered an error building the Elasticsearch index: {error}'
|
||||
values={{
|
||||
error: chosenJob.data ? (chosenJob.data.error || '') : ''
|
||||
}}
|
||||
/>
|
||||
);
|
||||
statusClass = 'status-icon-error';
|
||||
} else if (chosenJob.status === JobStatuses.CANCEL_REQUESTED) {
|
||||
status = (
|
||||
<FormattedMessage
|
||||
id='admin.elasticsearchStatus.statusRequestCancel'
|
||||
defaultMessage='Canceling Job...'
|
||||
/>
|
||||
);
|
||||
statusClass = 'status-icon-warning';
|
||||
} else if (chosenJob.status === JobStatuses.CANCELED) {
|
||||
status = (
|
||||
<FormattedMessage
|
||||
id='admin.elasticsearchStatus.statusCancelled'
|
||||
defaultMessage='Indexing job cancelled.'
|
||||
/>
|
||||
);
|
||||
statusClass = 'status-icon-error';
|
||||
}
|
||||
} else {
|
||||
status = (
|
||||
<FormattedMessage
|
||||
id='admin.elasticsearchStatus.statusNoJobs'
|
||||
defaultMessage='No indexing jobs queued.'
|
||||
/>
|
||||
);
|
||||
statusClass = 'status-icon-unknown';
|
||||
}
|
||||
|
||||
if (statusHelp !== null) {
|
||||
statusHelp = (
|
||||
<div className='col-sm-offset-4 col-sm-8'>
|
||||
<div className='help-text'>
|
||||
{statusHelp}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
statusClass = 'fa fa-circle margin--right ' + statusClass;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{indexButton}
|
||||
<div className='form-group'>
|
||||
<div className='col-sm-offset-4 col-sm-8'>
|
||||
<div className='help-text no-margin'>
|
||||
<FormattedMessage
|
||||
id='admin.elasticsearchStatus.status'
|
||||
defaultMessage='Status: '
|
||||
/>
|
||||
<i
|
||||
className={statusClass}
|
||||
/>
|
||||
{status}
|
||||
</div>
|
||||
</div>
|
||||
{statusHelp}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,13 @@ export default class RequestButton extends React.Component {
|
||||
*/
|
||||
buttonText: PropTypes.element.isRequired,
|
||||
|
||||
/**
|
||||
* The element to display as the field label.
|
||||
*
|
||||
* Typically, this will be a <FormattedMessage/>
|
||||
*/
|
||||
label: PropTypes.element,
|
||||
|
||||
/**
|
||||
* True if the button form control should be disabled, otherwise false.
|
||||
*/
|
||||
@@ -100,7 +107,12 @@ export default class RequestButton extends React.Component {
|
||||
* the `message` and `detailed_error` properties of the error returned from the server,
|
||||
* otherwise false to include only the `message` property.
|
||||
*/
|
||||
includeDetailedError: PropTypes.bool
|
||||
includeDetailedError: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* An element to display adjacent to the request button.
|
||||
*/
|
||||
alternativeActionElement: PropTypes.element
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
@@ -211,9 +223,24 @@ export default class RequestButton extends React.Component {
|
||||
contents = this.props.buttonText;
|
||||
}
|
||||
|
||||
let widgetClassNames = 'col-sm-8';
|
||||
let label = null;
|
||||
if (this.props.label) {
|
||||
label = (
|
||||
<label
|
||||
className='control-label col-sm-4'
|
||||
>
|
||||
{this.props.label}
|
||||
</label>
|
||||
);
|
||||
} else {
|
||||
widgetClassNames = 'col-sm-offset-4 ' + widgetClassNames;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='form-group reload-config'>
|
||||
<div className='col-sm-offset-4 col-sm-8'>
|
||||
<div className='form-group'>
|
||||
{label}
|
||||
<div className={widgetClassNames}>
|
||||
<div>
|
||||
<button
|
||||
className='btn btn-default'
|
||||
@@ -222,6 +249,7 @@ export default class RequestButton extends React.Component {
|
||||
>
|
||||
{contents}
|
||||
</button>
|
||||
{this.props.alternativeActionElement}
|
||||
{message}
|
||||
</div>
|
||||
<div className='help-text'>
|
||||
|
||||
@@ -233,6 +233,7 @@
|
||||
"admin.customization.support": "Legal and Support",
|
||||
"admin.database.title": "Database Settings",
|
||||
"admin.developer.title": "Developer Settings",
|
||||
"admin.elasticsearchStatus.bulkIndexLabel": "Bulk Indexing:",
|
||||
"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:",
|
||||
@@ -252,8 +253,34 @@
|
||||
"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.testHelpText": "Tests if the Mattermost server can connect to the Elasticsearch server specified. Testing the connection only saves the configuration if the test is successful. See log file for more detailed error messages.",
|
||||
"admin.elasticsearch.testConfigSuccess": "Test successful. Configuration saved.",
|
||||
"admin.elasticsearch.elasticsearch_test_button": "Test Connection",
|
||||
"admin.elasticsearch.indexButton.ready": "Build Index",
|
||||
"admin.elasticsearch.indexHelpText.buildIndex": "All posts in the database will be indexed from oldest to newest. Elasticsearch is available during indexing but search results may be incomplete until the indexing job is complete.",
|
||||
"admin.elasticsearch.indexButton.inProgress": "Indexing in progress",
|
||||
"admin.elasticsearch.indexHelpText.cancelIndexing": "Cancelling stops the indexing job and removes it from the queue. Posts that have already been indexed will not be deleted.",
|
||||
"admin.elasticsearch.bulkIndexButton.error": "Failed to schedule Bulk Index Job: {error}",
|
||||
"admin.elasticsearchStatus.statusLoading": "Loading...",
|
||||
"admin.elasticsearchStatus.statusPending": "Job pending.",
|
||||
"admin.elasticsearchStatus.statusPending.help": "Elasticsearch index job is queued on the job server. If Elasticsearch is enabled, search results may be incomplete until the job is finished.",
|
||||
"admin.elasticsearchStatus.statusInProgress": "Job in progress. {percent}% complete.",
|
||||
"admin.elasticsearchStatus.statusInProgress.help": "Indexing is in progress on the job server. If Elasticsearch is enabled, search results may be incomplete until the job is finished.",
|
||||
"admin.elasticsearchStatus.statusSuccess": "Indexing complete.",
|
||||
"admin.elasticsearchStatus.statusSuccess.help": "Indexing is complete and new posts are being automatically indexed.",
|
||||
"admin.elasticsearchStatus.statusError": "Indexing error.",
|
||||
"admin.elasticsearchStatus.statusError.help": "Mattermost encountered an error building the Elasticsearch index: {error}",
|
||||
"admin.elasticsearchStatus.statusRequestCancel": "Canceling Job...",
|
||||
"admin.elasticsearchStatus.statusCancelled": "Indexing job cancelled.",
|
||||
"admin.elasticsearchStatus.statusNoJobs": "No indexing jobs queued.",
|
||||
"admin.elasticsearchStatus.status": "Status: ",
|
||||
"admin.elasticsearchStatus.cancelButton": "Cancel",
|
||||
"admin.elasticsearchStatus.statusIndexingDisabled": "Indexing disabled.",
|
||||
"admin.elasticsearch.purgeIndexesHelpText": "Purging will entirely remove the index on the Elasticsearch server. Search results may be incomplete until a bulk index of the existing post database is rebuilt.",
|
||||
"admin.elasticsearch.purgeIndexesButton": "Purge Indexes",
|
||||
"admin.elasticsearch.purgeIndexesButton.success": "Indexes purged successfully.",
|
||||
"admin.elasticsearch.purgeIndexesButton.error": "Failed to purge indexes: {error}",
|
||||
"admin.elasticsearch.purgeIndexesButton.label": "Purge Indexes:",
|
||||
"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.allowEmailSignInTitle": "Enable sign-in with email: ",
|
||||
|
||||
@@ -289,6 +289,22 @@
|
||||
.admin-console-header {
|
||||
border-bottom: 1px solid alpha-color($black, .1);
|
||||
}
|
||||
|
||||
.status-icon-unknown {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.status-icon-success {
|
||||
color: #69c169;
|
||||
}
|
||||
|
||||
.status-icon-warning {
|
||||
color: #eac262;
|
||||
}
|
||||
|
||||
.status-icon-error {
|
||||
color: #ea6262;
|
||||
}
|
||||
}
|
||||
|
||||
.brand-img {
|
||||
@@ -476,10 +492,6 @@
|
||||
margin-top: -15px;
|
||||
}
|
||||
|
||||
.reload-config {
|
||||
margin-bottom: 50px !important;
|
||||
}
|
||||
|
||||
.recycle-db {
|
||||
margin-top: 50px !important;
|
||||
}
|
||||
|
||||
33
webapp/sass/utils/_modifiers.scss
Normal file
33
webapp/sass/utils/_modifiers.scss
Normal file
@@ -0,0 +1,33 @@
|
||||
@charset 'UTF-8';
|
||||
|
||||
.margin--right {
|
||||
margin-right: 5px;
|
||||
|
||||
&.x2 {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.margin--left {
|
||||
margin-left: 5px;
|
||||
|
||||
&.x2 {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.padding--right {
|
||||
padding-right: 5px;
|
||||
|
||||
&.x2 {
|
||||
padding-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.padding--left {
|
||||
padding-left: 5px;
|
||||
|
||||
&.x2 {
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
@@ -3,3 +3,4 @@
|
||||
@import 'functions';
|
||||
@import 'mixins';
|
||||
@import 'animations';
|
||||
@import 'modifiers';
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
exports[`components/admin_console/request_button/request_button.jsx should match snapshot 1`] = `
|
||||
<div
|
||||
className="form-group reload-config"
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="col-sm-offset-4 col-sm-8"
|
||||
@@ -93,7 +93,7 @@ exports[`components/admin_console/request_button/request_button.jsx should match
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group reload-config"
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="col-sm-offset-4 col-sm-8"
|
||||
@@ -215,7 +215,7 @@ exports[`components/admin_console/request_button/request_button.jsx should match
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group reload-config"
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="col-sm-offset-4 col-sm-8"
|
||||
@@ -337,7 +337,7 @@ exports[`components/admin_console/request_button/request_button.jsx should match
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group reload-config"
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="col-sm-offset-4 col-sm-8"
|
||||
@@ -455,7 +455,7 @@ exports[`components/admin_console/request_button/request_button.jsx should match
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group reload-config"
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="col-sm-offset-4 col-sm-8"
|
||||
|
||||
@@ -296,6 +296,20 @@ export const ErrorPageTypes = {
|
||||
LOCAL_STORAGE: 'local_storage'
|
||||
};
|
||||
|
||||
export const JobTypes = {
|
||||
DATA_RETENTION: 'data_retention',
|
||||
ELASTICSEARCH_POST_INDEXING: 'elasticsearch_post_indexing'
|
||||
};
|
||||
|
||||
export const JobStatuses = {
|
||||
PENDING: 'pending',
|
||||
IN_PROGRESS: 'in_progress',
|
||||
SUCCESS: 'success',
|
||||
ERROR: 'error',
|
||||
CANCEL_REQUESTED: 'cancel_requested',
|
||||
CANCELED: 'canceled'
|
||||
};
|
||||
|
||||
export const ErrorBarTypes = {
|
||||
LICENSE_EXPIRING: 'error_bar.license_expiring',
|
||||
LICENSE_EXPIRED: 'error_bar.license_expired',
|
||||
|
||||
Reference in New Issue
Block a user