Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
frontend/node_modules/
frontend/build/
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ ElasticSurgery is an Elastcsearch cluster _management interface_. Designed to vi

## Development

Setup your environment like so - you'll need [`kubetools-client`](https://github.com/EDITD/kubetools-client) installed:
Setup your environment like so - you'll need [`kubetools`](https://github.com/EDITD/kubetools) installed:

```sh
# Install the node modules
Expand Down
15 changes: 9 additions & 6 deletions backend/elasticsurgery/settings.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# TODO: Open-source shouldn't store settings like this.
SECRET_KEY = '5xP9_fMwqRnF047ZEwnQd9nCLknz3TrdQvggugBMxlo0zT3yEJ0f3g'
DEBUG = True
from os import environ

ES_STATE_HOSTS = ['elasticsurgery-elasticsearch:9200']

ES_CLUSTERS_INDEX_NAME = '.elasticsurgery-clusters'
ES_LOGS_INDEX_NAME = '.elasticsurgery-logs'
SECRET_KEY = environ.get('SECRET_KEY', 'not-a-secret')

DEBUG = environ.get('DEBUG') == 'on' or environ.get('KTD_ENV')

ES_STATE_HOSTS = environ.get('ES_STATE_HOSTS', 'elasticsurgery-elasticsearch:9200').split(',')

ES_CLUSTERS_INDEX_NAME = environ.get('ES_CLUSTERS_INDEX_NAME', '.elasticsurgery-clusters')
ES_LOGS_INDEX_NAME = environ.get('ES_LOGS_INDEX_NAME', '.elasticsurgery-logs')
23 changes: 21 additions & 2 deletions backend/elasticsurgery/views/clusters_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,24 @@ def put_cluster_settings(cluster_slug):
@app.route('/api/clusters/<cluster_slug>/tasks', methods=('GET',))
@pass_cluster_client
def get_cluster_tasks(cluster_client):
tasks = cluster_client.tasks.list(group_by='parents')
return jsonify(**tasks)
tasks = cluster_client.tasks.list(group_by='parents', detailed=True)

def process_task(task):
if task['action'] == 'indices:data/write/reindex':
status = task['status']
percentage_complete = round((
(status['created'] + status['updated']) / status['total']
* 100
), 2)
task['status_string'] = (
f'(created={status["created"]} + updated={status["updated"]})'
f'/{status["total"]} ({percentage_complete}%)'
)

return task

tasks = {
task_id: process_task(task)
for task_id, task in tasks['tasks'].items()
}
return jsonify(tasks=tasks)
3 changes: 2 additions & 1 deletion docker/uwsgi.conf
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ http-socket = 0.0.0.0:80
# App
plugin = python
module = boot:app
chdir = backend

# Worker processes
processes = 4
processes = 4
106 changes: 93 additions & 13 deletions frontend/src/TasksDashboard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import Typography from '@material-ui/core/Typography';
import CircularProgress from '@material-ui/core/CircularProgress';
import Button from '@material-ui/core/Button';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import TextField from '@material-ui/core/TextField';
import Checkbox from '@material-ui/core/Checkbox';
import Select from '@material-ui/core/Select';
import MenuItem from '@material-ui/core/MenuItem';
import pretty from 'pretty-time';

import { loadTasks } from './data/tasks/actions';
Expand All @@ -32,6 +35,22 @@ const mapDispatchToProps = {
loadNodes,
};

const filterTaskTableData = (taskDatas, tasksFilter) => {
if (!tasksFilter) {
return taskDatas;
}

return taskDatas.filter(taskData => {
if (taskData.action.indexOf(tasksFilter) > 0) {
return true;
}
if (taskData.type.indexOf(tasksFilter) > 0) {
return true;
}
return false;
});
};

const generateTaskTableData = (taskDatas, includeChildren) => {
return taskDatas.reduce((rows, taskData) => {
let childRows = [];
Expand Down Expand Up @@ -84,6 +103,8 @@ class TasksDashboard extends React.Component {

state = {
showChildren: false,
tasksFilter: '',
refreshInterval: 0,
};

componentDidMount() {
Expand All @@ -101,11 +122,31 @@ class TasksDashboard extends React.Component {
}
}

componentDidUpdate(prevProps) {
componentWillUnmount() {
this.clearAnyRefreshInterval();
}

componentDidUpdate(prevProps, prevState) {
if (prevProps.clusters.currentCluster !== this.props.clusters.currentCluster) {
this.props.loadNodes();
this.props.loadTasks();
}

if (prevState.refreshInterval !== this.state.refreshInterval) {
this.clearAnyRefreshInterval();
if (this.state.refreshInterval) {
this.refreshIntervalTimer = setInterval(
this.props.loadTasks,
this.state.refreshInterval * 1000,
);
}
}
}

clearAnyRefreshInterval() {
if (this.refreshIntervalTimer) {
clearInterval(this.refreshIntervalTimer);
}
}

getContainerStyles(dataLoaded) {
Expand All @@ -128,12 +169,11 @@ class TasksDashboard extends React.Component {
});
};

render() {
renderTable() {
const { tasks, nodes } = this.props;

if (isNotLoaded(tasks) || isLoading(tasks) || isNotLoaded(nodes) || isLoading(nodes)) {
return <div style={this.getContainerStyles()}>
<CircularProgress />
</div>;
return <CircularProgress />;
}

if (isErrored(tasks) || isErrored(nodes)) {
Expand All @@ -148,30 +188,38 @@ class TasksDashboard extends React.Component {
{
title: 'ID',
dataKey: 'id',
width: 300,
width: 100,
sortable: true,
searchable: true,
},
{
title: 'Type',
dataKey: 'type',
width: 300,
width: 100,
sortable: true,
searchable: true,
},
{
title: 'Action',
dataKey: 'action',
width: 300,
sortable: true,
},
{
title: 'Description',
dataKey: 'description',
width: 500,
sortable: true,
},
{
title: 'Status',
dataKey: 'status_string',
width: 500,
sortable: true,
searchable: true,
},
{
title: 'Node',
dataKey: 'node',
width: 300,
sortable: true,
searchable: true,
formatter: nodeId => {
const nodeData = nodes.data.nodes[nodeId];
return nodeData ? nodeData.name : nodeId;
Expand All @@ -193,13 +241,18 @@ class TasksDashboard extends React.Component {
];

const tableData = generateTaskTableData(
Object.values(tasks.data.tasks),
filterTaskTableData(Object.values(tasks.data.tasks), this.state.tasksFilter),
this.state.showChildren,
);

return <Table config={tableConfig} data={tableData} />;
}

render() {
return <div style={this.getContainerStyles(true)}>
<div style={this.styles.controls}>
<FormControlLabel
style={{marginRight: 40}}
control={
<Button
variant="contained"
Expand All @@ -209,18 +262,45 @@ class TasksDashboard extends React.Component {
}
/>
<FormControlLabel
style={{marginRight: 20}}
label="Refresh interval"
control={
<Select
value={this.state.refreshInterval}
onChange={(ev) => this.setState({refreshInterval: ev.target.value})}
>
<MenuItem value={0}>Disabled</MenuItem>
<MenuItem value={5}>5s</MenuItem>
<MenuItem value={10}>10s</MenuItem>
<MenuItem value={30}>30s</MenuItem>
<MenuItem value={60}>60s</MenuItem>
</Select>
}
/>
<FormControlLabel
style={{marginRight: 40}}
control={
<Checkbox
value={this.state.showChildren}
checked={this.state.showChildren}
onClick={this.toggleShowChildren}
/>
}
label="Show child tasks"
/>
<FormControlLabel
control={
<TextField
value={this.state.tasksFilter}
onChange={(ev) => this.setState({tasksFilter: ev.target.value})}
/>
}
label="Filter tasks"
/>
</div>

<div style={this.styles.tableWrapper}>
<Table config={tableConfig} data={tableData} />
{this.renderTable()}
</div>
</div>;
}
Expand Down