mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Vue rewrite of admin jobs page
This commit is contained in:
@@ -0,0 +1,371 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2 class="mb-3">
|
||||
<span id="jobs-title">Jobs (vue)</span>
|
||||
</h2>
|
||||
<Alert :message="message" :variant="status" />
|
||||
<b-alert variant="info" show>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
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: <strong><YOUR MESSAGE></strong> For more information or
|
||||
help, report this error".
|
||||
</b-alert>
|
||||
<Alert v-if="loading" message="Waiting for data" variant="info" />
|
||||
<div v-else>
|
||||
<b-container class="mb-3">
|
||||
<b-row>
|
||||
<b-col md="6">
|
||||
<b-form @submit.prevent="onRefresh">
|
||||
<b-form-group
|
||||
id="cutoff"
|
||||
label="Update Jobs"
|
||||
label-for="cutoff-seconds"
|
||||
description="Cutoff in seconds"
|
||||
>
|
||||
<b-input-group>
|
||||
<b-form-input id="cutoff" type="number" placeholder="180" v-model="cutoff">
|
||||
</b-form-input>
|
||||
<b-input-group-append>
|
||||
<b-btn type="submit">Refresh</b-btn>
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
</b-form-group>
|
||||
</b-form>
|
||||
</b-col>
|
||||
<b-col>
|
||||
<b-form @submit.prevent="onUpdateJobLock">
|
||||
<b-form-group label="Administrative Job Lock" label-for="prevent-job-dispatching">
|
||||
<template slot="description">
|
||||
Job dispatching is currently
|
||||
<strong>{{ jobLockDisplay ? "locked" : "unlocked" }}</strong
|
||||
>.
|
||||
</template>
|
||||
<b-container>
|
||||
<b-row>
|
||||
<b-col md="auto">
|
||||
<b-form-checkbox id="prevent-job-dispatching" v-model="jobLock">
|
||||
<strong>Prevent jobs from dispatching</strong>
|
||||
</b-form-checkbox>
|
||||
</b-col>
|
||||
<b-col>
|
||||
<b-btn type="submit">Update</b-btn>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-container>
|
||||
</b-form-group>
|
||||
</b-form>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row>
|
||||
<b-col md="6">
|
||||
<b-form-group
|
||||
label="Filter Jobs"
|
||||
label-for="filter-regex"
|
||||
description="Search for strings or regular expressions"
|
||||
>
|
||||
<b-input-group id="filter-regex">
|
||||
<b-form-input
|
||||
v-model="filter"
|
||||
placeholder="Type to Search"
|
||||
@keyup.esc.native="filter = ''"
|
||||
/>
|
||||
<b-input-group-append>
|
||||
<b-btn :disabled="!filter" @click="filter = ''">Clear (esc)</b-btn>
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
</b-form-group>
|
||||
<b-button :pressed.sync="showCommandLine" variant="outline-secondary">
|
||||
{{ showCommandLine ? "Hide" : "Show" }} Command Line
|
||||
</b-button>
|
||||
</b-col>
|
||||
<b-col>
|
||||
<b-card
|
||||
v-if="jobsItemsComputed.length"
|
||||
header="Stop Selected Jobs"
|
||||
header-bg-variant="info"
|
||||
header-text-variant="white"
|
||||
border-variant="info"
|
||||
>
|
||||
<b-form @submit.prevent="onStopJobs">
|
||||
<b-form-group label-for="stop-message" description="will be displayed to the user">
|
||||
<b-input-group>
|
||||
<b-form-input
|
||||
id="stop-message"
|
||||
v-model="stopMessage"
|
||||
placeholder="Stop message"
|
||||
required
|
||||
>
|
||||
</b-form-input>
|
||||
<b-input-group-append>
|
||||
<b-btn type="submit">Submit</b-btn>
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
</b-form-group>
|
||||
</b-form>
|
||||
</b-card>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-container>
|
||||
<b-alert v-if="!jobsItemsComputed.length" variant="secondary" show>
|
||||
There are no unfinished jobs to show with current cutoff time.
|
||||
</b-alert>
|
||||
<b-table
|
||||
v-else
|
||||
:fields="jobsFieldsComputed"
|
||||
:items="jobsItemsComputed"
|
||||
v-model="jobsItemsModel"
|
||||
:filter="filter"
|
||||
hover
|
||||
responsive
|
||||
striped
|
||||
caption-top
|
||||
@row-clicked="showRowDetails"
|
||||
:busy="busy"
|
||||
>
|
||||
<template slot="table-caption">
|
||||
Unfinished Jobs: These jobs are unfinished and have had their state updated in the previous
|
||||
{{ cutoffDisplay }} seconds.
|
||||
</template>
|
||||
<!-- Enable cell formatting for the command line column -->
|
||||
<span slot="html" slot-scope="data" v-html="data.value" />
|
||||
<template slot="HEAD_selected" slot-scope="data">
|
||||
<b-form-checkbox
|
||||
v-model="allSelected"
|
||||
:indeterminate="indeterminate"
|
||||
@change="toggleAll"
|
||||
></b-form-checkbox>
|
||||
</template>
|
||||
<template slot="selected" slot-scope="data">
|
||||
<b-form-checkbox
|
||||
v-model="selectedStopJobIds"
|
||||
:checked="allSelected"
|
||||
:key="data.index"
|
||||
:value="data.item['job_info']['id']"
|
||||
></b-form-checkbox>
|
||||
</template>
|
||||
<template slot="job_info" slot-scope="data">
|
||||
<b-link :href="data.value['info_url']" target="galaxy_main">
|
||||
{{ data.value["id"] }}
|
||||
</b-link>
|
||||
</template>
|
||||
<template slot="row-details" slot-scope="row">
|
||||
<b-card>
|
||||
<h5>Command Line</h5>
|
||||
<pre class="code"><code class="break-word">{{ row.item.command_line }}</code></pre>
|
||||
</b-card>
|
||||
</template>
|
||||
</b-table>
|
||||
<b-alert v-if="!recentJobsItemsComputed.length" variant="secondary" show>
|
||||
There are no recently finished jobs to show with current cutoff time.
|
||||
</b-alert>
|
||||
<b-table
|
||||
v-else
|
||||
:fields="recentJobsFieldsComputed"
|
||||
:items="recentJobsItemsComputed"
|
||||
:filter="filter"
|
||||
hover
|
||||
responsive
|
||||
striped
|
||||
caption-top
|
||||
@row-clicked="showRowDetails"
|
||||
:busy="busy"
|
||||
>
|
||||
<template slot="table-caption">
|
||||
Recent Jobs: These jobs have completed in the previous {{ cutoffDisplay }} seconds.
|
||||
</template>
|
||||
<!-- Enable cell formatting for the command line column -->
|
||||
<span slot="html" slot-scope="data" v-html="data.value" />
|
||||
<template slot="job_info" slot-scope="data">
|
||||
<b-link :href="data.value['info_url']" target="galaxy_main">
|
||||
{{ data.value["id"] }}
|
||||
</b-link>
|
||||
</template>
|
||||
<template slot="row-details" slot-scope="row">
|
||||
<b-card>
|
||||
<h5>Command Line</h5>
|
||||
<pre class="code"><code class="break-word">{{ row.item.command_line }}</code></pre>
|
||||
</b-card>
|
||||
</template>
|
||||
</b-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
import axios from "axios";
|
||||
import Alert from "components/Alert.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Alert
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
jobsItems: [],
|
||||
jobsItemsModel: [],
|
||||
recentJobsItems: [],
|
||||
jobsFields: [
|
||||
{ key: "job_info", label: "Job ID", sortable: true },
|
||||
{ key: "user" },
|
||||
{ key: "tool_id", label: "Tool", tdClass: ["break-word"] },
|
||||
{ key: "state" },
|
||||
{ key: "input_dataset", label: "Inputs" },
|
||||
{ key: "job_runner_name", label: "Job Runner" },
|
||||
{ key: "job_runner_external_id", label: "PID/Cluster ID", sortable: true }
|
||||
],
|
||||
showCommandLine: false,
|
||||
cutoff: 180,
|
||||
cutoffDisplay: 180,
|
||||
jobLock: false,
|
||||
jobLockDisplay: false,
|
||||
selectedStopJobIds: [],
|
||||
allSelected: false,
|
||||
indeterminate: false,
|
||||
stopMessage: "",
|
||||
filter: "",
|
||||
message: "",
|
||||
status: "",
|
||||
loading: true,
|
||||
busy: true
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
selectedStopJobIds(newVal) {
|
||||
if (newVal.length === 0) {
|
||||
this.indeterminate = false;
|
||||
this.allSelected = false;
|
||||
} else if (newVal.length === this.jobsItemsModel.length) {
|
||||
this.indeterminate = false;
|
||||
this.allSelected = true;
|
||||
} else {
|
||||
this.indeterminate = true;
|
||||
this.allSelected = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
update() {
|
||||
this.busy = true;
|
||||
let params = [];
|
||||
params.push(`cutoff=${this.cutoff}`);
|
||||
params.push(`stop_msg=${this.stopMessage}`);
|
||||
params.push(`stop=${this.selectedStopJobIds.join()}`);
|
||||
params = params.join("&");
|
||||
axios
|
||||
.get(`${getAppRoot()}admin/jobs_list?${params}`)
|
||||
.then(response => {
|
||||
this.jobsItems = response.data.jobs;
|
||||
this.recentJobsItems = response.data.recent_jobs;
|
||||
this.cutoffDisplay = response.data.cutoff;
|
||||
this.message = response.data.message;
|
||||
this.status = response.data.status;
|
||||
this.jobLockDisplay = response.data.job_lock;
|
||||
this.loading = false;
|
||||
this.busy = false;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
},
|
||||
onRefresh() {
|
||||
this.update();
|
||||
},
|
||||
onUpdateJobLock() {
|
||||
axios
|
||||
.get(`${getAppRoot()}admin/jobs_control?job_lock=${this.jobLock}`)
|
||||
.then(response => {
|
||||
this.jobLockDisplay = response.data.job_lock;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
},
|
||||
onStopJobs() {
|
||||
this.update();
|
||||
this.selectedStopJobIds = [];
|
||||
this.stopMessage = "";
|
||||
},
|
||||
showRowDetails(row, index, e) {
|
||||
if (e.target.nodeName != "A") {
|
||||
row._showDetails = !row._showDetails;
|
||||
}
|
||||
},
|
||||
translateState(state) {
|
||||
const translateDict = {
|
||||
ok: "success",
|
||||
error: "danger",
|
||||
new: "primary",
|
||||
queued: "secondary",
|
||||
running: "info",
|
||||
upload: "dark"
|
||||
};
|
||||
return translateDict[state] || "primary";
|
||||
},
|
||||
computeItems(items) {
|
||||
return items.map(job => {
|
||||
return {
|
||||
...job,
|
||||
_showDetails: false,
|
||||
_cellVariants: { state: this.translateState(job.state) }
|
||||
};
|
||||
});
|
||||
},
|
||||
computeFields(fields) {
|
||||
let f = Array.from(fields).slice(0);
|
||||
if (this.showCommandLine) {
|
||||
f.splice(6, 0, {
|
||||
key: "command_line",
|
||||
tdClass: ["code", "break-word"]
|
||||
});
|
||||
}
|
||||
return f;
|
||||
},
|
||||
toggleAll(checked) {
|
||||
this.selectedStopJobIds = checked
|
||||
? this.jobsItemsModel.reduce((acc, j) => [...acc, j["job_info"]["id"]], [])
|
||||
: [];
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
jobsItemsComputed() {
|
||||
return this.computeItems(this.jobsItems);
|
||||
},
|
||||
recentJobsItemsComputed() {
|
||||
return this.computeItems(this.recentJobsItems);
|
||||
},
|
||||
jobsFieldsComputed() {
|
||||
let f = this.jobsFields.slice(0);
|
||||
f.splice(0, 0, { key: "selected", label: "", variant: "info" });
|
||||
f.splice(2, 0, { key: "update_time", label: "Last Update", sortable: true });
|
||||
return this.computeFields(f);
|
||||
},
|
||||
recentJobsFieldsComputed() {
|
||||
let f = this.jobsFields.slice(0);
|
||||
f.splice(2, 0, { key: "update_time", label: "Finished", sortable: true });
|
||||
return this.computeFields(f);
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.update();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* Can not be scoped because of command line tdClass */
|
||||
.code {
|
||||
background: black;
|
||||
color: white;
|
||||
padding: 1em;
|
||||
}
|
||||
.break-word {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
</style>
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
@@ -39,6 +39,7 @@ var AdminPanel = Backbone.View.extend({
|
||||
{
|
||||
title: _l("Manage jobs"),
|
||||
url: "admin/jobs",
|
||||
target: "__use_router__",
|
||||
id: "admin-link-jobs"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,209 +0,0 @@
|
||||
<%inherit file="/base.mako"/>
|
||||
<%namespace file="/message.mako" import="render_msg" />
|
||||
|
||||
<%def name="javascripts()">
|
||||
${parent.javascripts()}
|
||||
<script type="text/javascript">
|
||||
function toggle_all(source) {
|
||||
// sets all checkboxes in source's parent form to match source element.
|
||||
$.each($(source).closest("form").find(":checkbox"), function(i, v){
|
||||
v.checked = source.checked;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</%def>
|
||||
|
||||
<%def name="title()">Jobs</%def>
|
||||
|
||||
<h2>Jobs</h2>
|
||||
|
||||
%if message:
|
||||
${render_msg( message, status )}
|
||||
%endif
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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: <b><YOUR MESSAGE></b> For more information or help,
|
||||
report this error".
|
||||
</p>
|
||||
|
||||
%if jobs:
|
||||
<form name="jobs" action="${h.url_for(controller='admin', action='jobs')}" method="POST">
|
||||
<h4>
|
||||
Unfinished Jobs: These jobs are unfinished and have had their state updated in the previous ${cutoff} seconds.
|
||||
</h4>
|
||||
<table class="manage-table colored" border="0" cellspacing="0" cellpadding="0" width="100%">
|
||||
<tr class="header">
|
||||
<td><input type="checkbox" onClick="toggle_all(this)"/></td>
|
||||
<td>Job ID</td>
|
||||
<td>User</td>
|
||||
<td>Last Update</td>
|
||||
<td>Tool</td>
|
||||
<td>State</td>
|
||||
<td>Inputs</td>
|
||||
<td>Command Line</td>
|
||||
<td>Job Runner</td>
|
||||
<td>PID/Cluster ID</td>
|
||||
</tr>
|
||||
%for job in jobs:
|
||||
<td>
|
||||
<input type="checkbox" name="stop" value="${job.id}"/>
|
||||
</td>
|
||||
<td>${job.id}</td>
|
||||
%if job.history and job.history.user:
|
||||
<td>${job.history.user.email|h}</td>
|
||||
%else:
|
||||
<td>anonymous</td>
|
||||
%endif
|
||||
<td>${last_updated[job.id]} ago</td>
|
||||
<td>${job.tool_id|h}</td>
|
||||
<td>${job.state}</td>
|
||||
<%
|
||||
try:
|
||||
inputs = ", ".join( [ '%s %s' % ( da.dataset.id, da.dataset.state ) for da in job.input_datasets ] )
|
||||
except:
|
||||
inputs = 'Unable to determine inputs'
|
||||
%>
|
||||
<td>${inputs}</td>
|
||||
<td>${job.command_line|h}</td>
|
||||
<td>${job.job_runner_name|h}</td>
|
||||
<td>${job.job_runner_external_id}</td>
|
||||
</tr>
|
||||
%endfor
|
||||
</table>
|
||||
<p/>
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
Stop Jobs
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-row">
|
||||
<label>
|
||||
Stop message:
|
||||
</label>
|
||||
<div class="form-row-input">
|
||||
<input type="text" name="stop_msg" size="40"/>
|
||||
</div>
|
||||
<div class="toolParamHelp" style="clear: both;">
|
||||
to be displayed to the user
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<input type="submit" class="primary-button" name="submit" value="Submit">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p/>
|
||||
</form>
|
||||
%else:
|
||||
<div class="infomessage">There are no unfinished jobs to show with current cutoff time.</div>
|
||||
<p/>
|
||||
%endif
|
||||
|
||||
%if recent_jobs:
|
||||
<h4>
|
||||
Recent Jobs: These jobs have completed in the previous ${cutoff} seconds.
|
||||
</h4>
|
||||
<table class="manage-table colored" border="0" cellspacing="0" cellpadding="0" width="100%">
|
||||
<tr class="header">
|
||||
<td>Job ID</td>
|
||||
<td>User</td>
|
||||
<td>Finished</td>
|
||||
<td>Tool</td>
|
||||
<td>State</td>
|
||||
<td>Inputs</td>
|
||||
<td>Command Line</td>
|
||||
<td>Job Runner</td>
|
||||
<td>PID/Cluster ID</td>
|
||||
</tr>
|
||||
%for job in recent_jobs:
|
||||
<td><a href="${h.url_for( controller="admin", action="job_info" )}?jobid=${job.id}">${job.id}</a></td>
|
||||
%if job.history and job.history.user:
|
||||
<td>${job.history.user.email|h}</td>
|
||||
%else:
|
||||
<td>anonymous</td>
|
||||
%endif
|
||||
<td>${finished[job.id]} ago</td>
|
||||
<td>${job.tool_id|h}</td>
|
||||
<td>${job.state}</td>
|
||||
<%
|
||||
try:
|
||||
inputs = ", ".join( [ '%s %s' % ( da.dataset.id, da.dataset.state ) for da in job.input_datasets ] )
|
||||
except:
|
||||
inputs = 'Unable to determine inputs'
|
||||
%>
|
||||
<td>${inputs}</td>
|
||||
<td>${job.command_line|h}</td>
|
||||
<td>${job.job_runner_name|h}</td>
|
||||
<td>${job.job_runner_external_id|h}</td>
|
||||
</tr>
|
||||
%endfor
|
||||
</table>
|
||||
<p/>
|
||||
%else:
|
||||
<div class="infomessage">There are no recently finished jobs to show with current cutoff time.</div>
|
||||
<p/>
|
||||
%endif
|
||||
|
||||
<form name="jobs" action="${h.url_for(controller='admin', action='jobs')}" method="POST">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
Update Jobs
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
<div class="form-row">
|
||||
<label>
|
||||
Cutoff:
|
||||
</label>
|
||||
<div class="form-row-input">
|
||||
<input type="text" name="cutoff" size="4" value="${cutoff}"/>
|
||||
</div>
|
||||
<div class="toolParamHelp" style="clear: both;">
|
||||
In seconds
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<input type="submit" class="primary-button" name="submit" value="Refresh">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form name="jobs" action="${h.url_for(controller='admin', action='jobs')}" method="POST">
|
||||
<p/>
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
Administrative Job Lock
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-row">
|
||||
<input type="hidden" name="ajl_submit" value="True"/>
|
||||
%if job_lock==True:
|
||||
<p>Job dispatching is currently <strong>locked</strong>.</p>
|
||||
<label>
|
||||
<input type='checkbox' name='job_lock' checked='checked' />
|
||||
Prevent jobs from dispatching.
|
||||
</label>
|
||||
%else:
|
||||
<p>Job dispatching is currently <strong>unlocked</strong>.</p>
|
||||
<label>
|
||||
<input type='checkbox' name='job_lock' />
|
||||
Prevent jobs from dispatching.
|
||||
</label>
|
||||
%endif
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<input type="submit" class="primary-button" name="submit" value="Update">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
Reference in New Issue
Block a user