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.
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
25a2013890
Коммит
00e8b6621a
28
webapp/components/admin_console/elasticsearch_status/index.js
Обычный файл
28
webapp/components/admin_console/elasticsearch_status/index.js
Обычный файл
@@ -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
Обычный файл
361
webapp/components/admin_console/elasticsearch_status/status.jsx
Обычный файл
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
Ссылка в новой задаче
Block a user