diff --git a/app/api/tasks.py b/app/api/tasks.py index 5519236a..00c68330 100644 --- a/app/api/tasks.py +++ b/app/api/tasks.py @@ -48,6 +48,12 @@ class TaskViewSet(viewsets.ViewSet): raise exceptions.NotFound() return project + @staticmethod + def task_output_only(request, task): + line_num = max(0, int(request.query_params.get('line', 0))) + output = task.console_output or "" + return '\n'.join(output.split('\n')[line_num:]) + def list(self, request, project_pk=None): project = self.get_and_check_project(request, project_pk) tasks = self.queryset.filter(project=project_pk) @@ -61,8 +67,16 @@ class TaskViewSet(viewsets.ViewSet): task = self.queryset.get(pk=pk, project=project_pk) except ObjectDoesNotExist: raise exceptions.NotFound() - serializer = TaskSerializer(task) - return Response(serializer.data) + + response_data = None + + if request.query_params.get('output_only', '').lower() in ['true', '1']: + response_data = self.task_output_only(request, task) + else: + serializer = TaskSerializer(task) + response_data = serializer.data + + return Response(response_data) def create(self, request, project_pk=None): project = self.get_and_check_project(request, project_pk, ('change_project', )) diff --git a/app/scheduler.py b/app/scheduler.py index c8209043..d4a54c34 100644 --- a/app/scheduler.py +++ b/app/scheduler.py @@ -84,7 +84,7 @@ def setup(): try: scheduler.start() scheduler.add_job(update_nodes_info, 'interval', seconds=30) - scheduler.add_job(process_pending_tasks, 'interval', seconds=5) + scheduler.add_job(process_pending_tasks, 'interval', seconds=15) except SchedulerAlreadyRunningError: logger.warn("Scheduler already running (this is OK while testing)") diff --git a/app/static/app/js/Console.jsx b/app/static/app/js/Console.jsx index c3ebd26a..35c7669a 100644 --- a/app/static/app/js/Console.jsx +++ b/app/static/app/js/Console.jsx @@ -26,6 +26,36 @@ class Console extends React.Component { componentDidMount(){ this.checkAutoscroll(); + + // Dynamic source? + if (this.props.source !== undefined){ + let currentLineNumber = 0; + + const updateFromSource = () => { + let sourceUrl = typeof this.props.source === 'function' ? + this.props.source(currentLineNumber) : + this.props.source; + + // Fetch + this.sourceRequest = $.get(sourceUrl, text => { + let lines = text.split("\n"); + lines.forEach(line => this.addLine(line)); + currentLineNumber += (lines.length - 1); + }) + .always(() => { + if (this.props.refreshInterval !== undefined){ + this.sourceTimeout = setTimeout(updateFromSource, this.props.refreshInterval); + } + }); + }; + + updateFromSource(); + } + } + + componentWillUnmount(){ + if (this.sourceTimeout) clearTimeout(this.sourceTimeout); + if (this.sourceRequest) this.sourceRequest.abort(); } setRef(domNode){ diff --git a/app/static/app/js/components/TaskListItem.jsx b/app/static/app/js/components/TaskListItem.jsx index 9f892a00..f49eb209 100644 --- a/app/static/app/js/components/TaskListItem.jsx +++ b/app/static/app/js/components/TaskListItem.jsx @@ -11,9 +11,11 @@ class TaskListItem extends React.Component { } this.toggleExpanded = this.toggleExpanded.bind(this); + this.consoleOutputUrl = this.consoleOutputUrl.bind(this); } componentDidMount(){ + } toggleExpanded(){ @@ -22,17 +24,32 @@ class TaskListItem extends React.Component { }); } + consoleOutputUrl(line){ + return `/api/projects/${this.props.data.project}/tasks/${this.props.data.id}/?output_only=true&line=${line}`; + } + render() { let name = this.props.data.name !== null ? this.props.data.name : `Task #${this.props.data.id}`; let expanded = ""; if (this.state.expanded){ expanded = ( -