diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..85fddf4 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +frontend/node_modules/ +frontend/build/ diff --git a/README.md b/README.md index 9351d53..635c93d 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/backend/elasticsurgery/settings.py b/backend/elasticsurgery/settings.py index e35ae36..d342cf3 100644 --- a/backend/elasticsurgery/settings.py +++ b/backend/elasticsurgery/settings.py @@ -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') diff --git a/backend/elasticsurgery/views/clusters_proxy.py b/backend/elasticsurgery/views/clusters_proxy.py index 41513f7..4f5205e 100644 --- a/backend/elasticsurgery/views/clusters_proxy.py +++ b/backend/elasticsurgery/views/clusters_proxy.py @@ -42,5 +42,24 @@ def put_cluster_settings(cluster_slug): @app.route('/api/clusters//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) diff --git a/docker/uwsgi.conf b/docker/uwsgi.conf index daa9bc1..3098e09 100644 --- a/docker/uwsgi.conf +++ b/docker/uwsgi.conf @@ -10,6 +10,7 @@ http-socket = 0.0.0.0:80 # App plugin = python module = boot:app +chdir = backend # Worker processes -processes = 4 \ No newline at end of file +processes = 4 diff --git a/frontend/src/TasksDashboard.jsx b/frontend/src/TasksDashboard.jsx index 712ae0d..7f731fc 100644 --- a/frontend/src/TasksDashboard.jsx +++ b/frontend/src/TasksDashboard.jsx @@ -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'; @@ -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 = []; @@ -84,6 +103,8 @@ class TasksDashboard extends React.Component { state = { showChildren: false, + tasksFilter: '', + refreshInterval: 0, }; componentDidMount() { @@ -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) { @@ -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
- -
; + return ; } if (isErrored(tasks) || isErrored(nodes)) { @@ -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; @@ -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 ; + } + + render() { return
this.setState({refreshInterval: ev.target.value})} + > + Disabled + 5s + 10s + 30s + 60s + + } + /> + } label="Show child tasks" /> + this.setState({tasksFilter: ev.target.value})} + /> + } + label="Filter tasks" + />
-
+ {this.renderTable()} ; }