diff --git a/client/galaxy/scripts/components/admin/Jobs.vue b/client/galaxy/scripts/components/admin/Jobs.vue new file mode 100644 index 00000000000..5efa1cec465 --- /dev/null +++ b/client/galaxy/scripts/components/admin/Jobs.vue @@ -0,0 +1,371 @@ + + + + + diff --git a/client/galaxy/scripts/entry/admin/AdminRouter.js b/client/galaxy/scripts/entry/admin/AdminRouter.js index 1eeae6a2138..8e8aa024e06 100644 --- a/client/galaxy/scripts/entry/admin/AdminRouter.js +++ b/client/galaxy/scripts/entry/admin/AdminRouter.js @@ -7,6 +7,7 @@ import QueryStringParsing from "utils/query-string-parsing"; import Router from "layout/router"; import DataTables from "components/admin/DataTables.vue"; import DataTypes from "components/admin/DataTypes.vue"; +import Jobs from "components/admin/Jobs.vue"; import DataManagerView from "components/admin/DataManager/DataManagerView.vue"; import DataManagerRouter from "components/admin/DataManager/DataManagerRouter.vue"; import Register from "components/login/Register.vue"; @@ -33,6 +34,7 @@ export const getAdminRouter = (Galaxy, options) => { "(/)admin(/)form(/)(:form_id)": "show_form", "(/)admin/data_tables": "show_data_tables", "(/)admin/data_types": "show_data_types", + "(/)admin/jobs": "show_jobs", "(/)admin/data_manager*path": "show_data_manager", "*notFound": "not_found" }, @@ -115,6 +117,10 @@ export const getAdminRouter = (Galaxy, options) => { this._display_vue_helper(DataTypes); }, + show_jobs: function() { + this._display_vue_helper(Jobs); + }, + show_error_stack: function() { this._display_vue_helper(ErrorStack); }, diff --git a/client/galaxy/scripts/entry/panels/admin-panel.js b/client/galaxy/scripts/entry/panels/admin-panel.js index a94035e382e..44e60acc517 100644 --- a/client/galaxy/scripts/entry/panels/admin-panel.js +++ b/client/galaxy/scripts/entry/panels/admin-panel.js @@ -39,6 +39,7 @@ var AdminPanel = Backbone.View.extend({ { title: _l("Manage jobs"), url: "admin/jobs", + target: "__use_router__", id: "admin-link-jobs" }, { diff --git a/lib/galaxy/webapps/galaxy/buildapp.py b/lib/galaxy/webapps/galaxy/buildapp.py index 6eb8fa7a76c..9289ab0b1f5 100644 --- a/lib/galaxy/webapps/galaxy/buildapp.py +++ b/lib/galaxy/webapps/galaxy/buildapp.py @@ -101,6 +101,7 @@ def app_factory(global_conf, load_app_kwds={}, **kwargs): webapp.add_client_route('/admin/data_tables', 'admin') webapp.add_client_route('/admin/data_types', 'admin') + webapp.add_client_route('/admin/jobs', 'admin') webapp.add_client_route('/admin/data_manager{path_info:.*}', 'admin') webapp.add_client_route('/admin/error_stack', 'admin') webapp.add_client_route('/admin/users', 'admin') diff --git a/lib/galaxy/webapps/galaxy/controllers/admin.py b/lib/galaxy/webapps/galaxy/controllers/admin.py index f001821285a..63d0f2039a1 100644 --- a/lib/galaxy/webapps/galaxy/controllers/admin.py +++ b/lib/galaxy/webapps/galaxy/controllers/admin.py @@ -1600,14 +1600,25 @@ class AdminGalaxy(controller.JSAppLauncher, AdminActions, UsesQuotaMixin, QuotaP return {'message' : 'User \'%s\' has been updated with %d associated roles and %d associated groups (private roles are not displayed).' % (user.email, len(in_roles) - 1, len(in_groups))} @web.expose + @web.json @web.require_admin - def jobs(self, trans, stop=[], stop_msg=None, cutoff=180, job_lock=None, ajl_submit=None, **kwd): + def jobs_control(self, trans, job_lock=None, **kwd): + if job_lock is not None: + job_lock = True if job_lock == 'true' else False + galaxy.queue_worker.send_control_task(trans.app, 'admin_job_lock', kwargs={'job_lock': job_lock}) + job_lock = trans.app.job_manager.job_lock + return {'job_lock': job_lock} + + @web.expose + @web.json + @web.require_admin + def jobs_list(self, trans, stop=[], stop_msg=None, cutoff=180, **kwd): deleted = [] - msg = None - status = None + message = kwd.get('message', '') + status = kwd.get('status', 'info') job_ids = util.listify(stop) if job_ids and stop_msg in [None, '']: - msg = 'Please enter an error message to display to the user describing why the job was terminated' + message = 'Please enter an error message to display to the user describing why the job was terminated' status = 'error' elif job_ids: if stop_msg[-1] not in PUNCTUATION: @@ -1624,24 +1635,14 @@ class AdminGalaxy(controller.JSAppLauncher, AdminActions, UsesQuotaMixin, QuotaP trans.app.job_manager.stop(job, message=error_msg) deleted.append(str(job_id)) if deleted: - msg = 'Queued job' + message = 'Queued job' if len(deleted) > 1: - msg += 's' - msg += ' for deletion: ' - msg += ', '.join(deleted) + message += 's' + message += ' for deletion: ' + message += ', '.join(deleted) status = 'done' trans.sa_session.flush() - if ajl_submit: - if job_lock == 'on': - galaxy.queue_worker.send_control_task(trans.app, 'admin_job_lock', - kwargs={'job_lock': True}) - job_lock = True - else: - galaxy.queue_worker.send_control_task(trans.app, 'admin_job_lock', - kwargs={'job_lock': False}) - job_lock = False - else: - job_lock = trans.app.job_manager.job_lock + job_lock = trans.app.job_manager.job_lock cutoff_time = datetime.utcnow() - timedelta(seconds=int(cutoff)) jobs = trans.sa_session.query(trans.app.model.Job) \ .filter(and_(trans.app.model.Job.table.c.update_time < cutoff_time, @@ -1655,33 +1656,44 @@ class AdminGalaxy(controller.JSAppLauncher, AdminActions, UsesQuotaMixin, QuotaP or_(trans.app.model.Job.state == trans.app.model.Job.states.ERROR, trans.app.model.Job.state == trans.app.model.Job.states.OK))) \ .order_by(trans.app.model.Job.table.c.update_time.desc()).all() - last_updated = {} - for job in jobs: - delta = datetime.utcnow() - job.update_time - if delta.days > 0: - last_updated[job.id] = '%s hours' % (delta.days * 24 + int(delta.seconds / 60 / 60)) - elif delta > timedelta(minutes=59): - last_updated[job.id] = '%s hours' % int(delta.seconds / 60 / 60) - else: - last_updated[job.id] = '%s minutes' % int(delta.seconds / 60) - finished = {} - for job in recent_jobs: - delta = datetime.utcnow() - job.update_time - if delta.days > 0: - finished[job.id] = '%s hours' % (delta.days * 24 + int(delta.seconds / 60 / 60)) - elif delta > timedelta(minutes=59): - finished[job.id] = '%s hours' % int(delta.seconds / 60 / 60) - else: - finished[job.id] = '%s minutes' % int(delta.seconds / 60) - return trans.fill_template('/admin/jobs.mako', - jobs=jobs, - recent_jobs=recent_jobs, - last_updated=last_updated, - finished=finished, - cutoff=cutoff, - msg=msg, - status=status, - job_lock=job_lock) + + def prepare_jobs_list(jobs): + res = [] + for job in jobs: + delta = datetime.utcnow() - job.update_time + update_time = "" + if delta.days > 0: + update_time = '%s hours ago' % (delta.days * 24 + int(delta.seconds / 60 / 60)) + elif delta > timedelta(minutes=59): + update_time = '%s hours ago' % int(delta.seconds / 60 / 60) + else: + update_time = '%s minutes ago' % int(delta.seconds / 60) + inputs = "" + try: + inputs = ", ".join(['{} {}'.format(da.dataset.id, da.dataset.state) for da in job.input_datasets]) + except Exception: + inputs = 'Unable to determine inputs' + res.append({ + 'job_info': { + 'id': job.id, + 'info_url': "{}?jobid={}".format(web.url_for(controller="admin", action="job_info"), job.id) + }, + 'user': job.history.user.email if job.history and job.history.user else 'anonymous', + 'update_time': update_time, + 'tool_id': job.tool_id, + 'state': job.state, + 'input_dataset': inputs, + 'command_line': job.command_line, + 'job_runner_name': job.job_runner_name, + 'job_runner_external_id': job.job_runner_external_id + }) + return res + return {'jobs': prepare_jobs_list(jobs), + 'recent_jobs': prepare_jobs_list(recent_jobs), + 'cutoff': cutoff, + 'message': message, + 'status': status, + 'job_lock': job_lock} @web.expose @web.require_admin diff --git a/templates/admin/jobs.mako b/templates/admin/jobs.mako deleted file mode 100644 index 36a372044f9..00000000000 --- a/templates/admin/jobs.mako +++ /dev/null @@ -1,209 +0,0 @@ -<%inherit file="/base.mako"/> -<%namespace file="/message.mako" import="render_msg" /> - -<%def name="javascripts()"> - ${parent.javascripts()} - - - -<%def name="title()">Jobs - -

Jobs

- -%if message: - ${render_msg( message, status )} -%endif - -

- Unfinished and recently finished jobs are displayed on this page. The - 'cutoff' input box will do two things -- it will limit the display of - unfinished jobs to only those jobs that have not had their job state - updated recently, and it will limit the recently finished jobs list to only - displaying jobs that have finished since the cutoff. -

-

- If any jobs are displayed, you may choose to stop them. Your stop message - will be displayed to the user as: "This job was stopped by an - administrator: <YOUR MESSAGE> For more information or help, - report this error". -

- -%if jobs: -
-

- Unfinished Jobs: These jobs are unfinished and have had their state updated in the previous ${cutoff} seconds. -

- - - - - - - - - - - - - - %for job in jobs: - - - %if job.history and job.history.user: - - %else: - - %endif - - - - <% - try: - inputs = ", ".join( [ '%s %s' % ( da.dataset.id, da.dataset.state ) for da in job.input_datasets ] ) - except: - inputs = 'Unable to determine inputs' - %> - - - - - - %endfor -
Job IDUserLast UpdateToolStateInputsCommand LineJob RunnerPID/Cluster ID
- - ${job.id}${job.history.user.email|h}anonymous${last_updated[job.id]} ago${job.tool_id|h}${job.state}${inputs}${job.command_line|h}${job.job_runner_name|h}${job.job_runner_external_id}
-

-

-
- Stop Jobs -
-
-
- -
- -
-
- to be displayed to the user -
-
-
- -
-
-
-

-

-%else: -
There are no unfinished jobs to show with current cutoff time.
-

-%endif - -%if recent_jobs: -

- Recent Jobs: These jobs have completed in the previous ${cutoff} seconds. -

- - - - - - - - - - - - - %for job in recent_jobs: - - %if job.history and job.history.user: - - %else: - - %endif - - - - <% - try: - inputs = ", ".join( [ '%s %s' % ( da.dataset.id, da.dataset.state ) for da in job.input_datasets ] ) - except: - inputs = 'Unable to determine inputs' - %> - - - - - - %endfor -
Job IDUserFinishedToolStateInputsCommand LineJob RunnerPID/Cluster ID
${job.id}${job.history.user.email|h}anonymous${finished[job.id]} ago${job.tool_id|h}${job.state}${inputs}${job.command_line|h}${job.job_runner_name|h}${job.job_runner_external_id|h}
-

-%else: -

There are no recently finished jobs to show with current cutoff time.
-

-%endif - -

-
-
- Update Jobs -
-
- -
- -
- -
-
- In seconds -
-
-
- -
-
-
-
- -
-

-

-
- Administrative Job Lock -
-
-
- - %if job_lock==True: -

Job dispatching is currently locked.

- - %else: -

Job dispatching is currently unlocked.

- - %endif -
-
- -
-
-
-