Reviewed Job Package

This commit is contained in:
Inga Kirschnick 2023-03-10 08:47:03 +01:00
parent 3789f61ca4
commit 57a598ed20
5 changed files with 185 additions and 150 deletions

View File

@ -1,10 +1,10 @@
from flask import (abort, current_app, jsonify) from flask import abort, current_app, jsonify
from flask_login import current_user, login_required from flask_login import current_user, login_required
from threading import Thread from threading import Thread
import os import os
from app import db from app import db
from app.decorators import admin_required, content_negotiation from app.decorators import admin_required, content_negotiation
from app.models import Job, JobInput, JobResult, JobStatus from app.models import Job, JobStatus
from . import bp from . import bp
@bp.route('/<hashid:job_id>', methods=['DELETE']) @bp.route('/<hashid:job_id>', methods=['DELETE'])
@ -36,6 +36,7 @@ def delete_job(job_id):
@bp.route('/<hashid:job_id>/log') @bp.route('/<hashid:job_id>/log')
@login_required @login_required
@admin_required @admin_required
@content_negotiation(produces='application/json')
def job_log(job_id): def job_log(job_id):
job = Job.query.get_or_404(job_id) job = Job.query.get_or_404(job_id)
if job.status not in [JobStatus.COMPLETED, JobStatus.FAILED]: if job.status not in [JobStatus.COMPLETED, JobStatus.FAILED]:
@ -43,11 +44,18 @@ def job_log(job_id):
return response, 409 return response, 409
with open(os.path.join(job.path, 'pipeline_data', 'logs', 'pyflow_log.txt')) as log_file: with open(os.path.join(job.path, 'pipeline_data', 'logs', 'pyflow_log.txt')) as log_file:
log = log_file.read() log = log_file.read()
return log, 200, {'Content-Type': 'text/plain; charset=utf-8'} response_data = {
'message': '',
'jobLog': log
}
response = jsonify(response_data)
response.status_code = 200
return response
@bp.route('/<hashid:job_id>/restart', methods=['POST']) @bp.route('/<hashid:job_id>/restart', methods=['POST'])
@login_required @login_required
@content_negotiation(produces='application/json')
def restart_job(job_id): def restart_job(job_id):
def _restart_job(app, job_id): def _restart_job(app, job_id):
with app.app_context(): with app.app_context():
@ -66,4 +74,10 @@ def restart_job(job_id):
args=(current_app._get_current_object(), job_id) args=(current_app._get_current_object(), job_id)
) )
thread.start() thread.start()
return {}, 202 response_data = {
'message': \
f'Job "{job.title}" marked for restarting'
}
response = jsonify(response_data)
response.status_code = 202
return response

View File

@ -21,3 +21,11 @@ Requests.jobs.entity.log = (jobId) => {
}; };
return Requests.JSONfetch(input, init); return Requests.JSONfetch(input, init);
} }
Requests.jobs.entity.restart = (jobId) => {
let input = `/jobs/${jobId}/restart`;
let init = {
method: 'POST'
};
return Requests.JSONfetch(input, init);
}

View File

@ -2,16 +2,6 @@ class JobDisplay extends ResourceDisplay {
constructor(displayElement) { constructor(displayElement) {
super(displayElement); super(displayElement);
this.jobId = this.displayElement.dataset.jobId; this.jobId = this.displayElement.dataset.jobId;
this.displayElement
.querySelector('.action-button[data-action="get-log-request"]')
.addEventListener('click', (event) => {
Utils.getJobLogRequest(this.userId, this.jobId);
});
this.displayElement
.querySelector('.action-button[data-action="restart-request"]')
.addEventListener('click', (event) => {
Utils.restartJobRequest(this.userId, this.jobId);
});
} }
init(user) { init(user) {

View File

@ -229,138 +229,138 @@ class Utils {
}); });
} }
static deleteJobRequest(userId, jobId) { // static deleteJobRequest(userId, jobId) {
return new Promise((resolve, reject) => { // return new Promise((resolve, reject) => {
let job; // let job;
try { // try {
job = app.data.users[userId].jobs[jobId]; // job = app.data.users[userId].jobs[jobId];
} catch (error) { // } catch (error) {
job = {}; // job = {};
} // }
let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]'); // let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]');
confirmElement.addEventListener('click', (event) => { // confirmElement.addEventListener('click', (event) => {
let jobTitle = job?.title; // let jobTitle = job?.title;
fetch(`/jobs/${jobId}`, {method: 'DELETE', headers: {Accept: 'application/json'}}) // fetch(`/jobs/${jobId}`, {method: 'DELETE', headers: {Accept: 'application/json'}})
.then( // .then(
(response) => { // (response) => {
if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);} // if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);} // if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);}
app.flash(`Job "${jobTitle}" marked for deletion`, 'job'); // app.flash(`Job "${jobTitle}" marked for deletion`, 'job');
resolve(response); // resolve(response);
}, // },
(response) => { // (response) => {
app.flash('Something went wrong', 'error'); // app.flash('Something went wrong', 'error');
reject(response); // reject(response);
} // }
); // );
}); // });
modal.open(); // modal.open();
}); // });
} // }
static getJobLogRequest(userId, jobId) { // static getJobLogRequest(userId, jobId) {
return new Promise((resolve, reject) => { // return new Promise((resolve, reject) => {
fetch(`/jobs/${jobId}/log`, {method: 'GET', headers: {Accept: 'application/json, text/plain'}}) // fetch(`/jobs/${jobId}/log`, {method: 'GET', headers: {Accept: 'application/json, text/plain'}})
.then( // .then(
(response) => { // (response) => {
if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);} // if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);} // if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);}
return response.text(); // return response.text();
}, // },
(response) => { // (response) => {
app.flash('Something went wrong', 'error'); // app.flash('Something went wrong', 'error');
reject(response); // reject(response);
} // }
) // )
.then( // .then(
(text) => { // (text) => {
let modalElement = Utils.HTMLToElement( // let modalElement = Utils.HTMLToElement(
` // `
<div class="modal"> // <div class="modal">
<div class="modal-content"> // <div class="modal-content">
<h4>Job logs</h4> // <h4>Job logs</h4>
<pre><code>${text}</code></pre> // <pre><code>${text}</code></pre>
</div> // </div>
<div class="modal-footer"> // <div class="modal-footer">
<a class="btn modal-close waves-effect waves-light">Close</a> // <a class="btn modal-close waves-effect waves-light">Close</a>
</div> // </div>
</div> // </div>
` // `
); // );
document.querySelector('#modals').appendChild(modalElement); // document.querySelector('#modals').appendChild(modalElement);
let modal = M.Modal.init( // let modal = M.Modal.init(
modalElement, // modalElement,
{ // {
onCloseEnd: () => { // onCloseEnd: () => {
modal.destroy(); // modal.destroy();
modalElement.remove(); // modalElement.remove();
} // }
} // }
); // );
modal.open(); // modal.open();
resolve(text); // resolve(text);
} // }
); // );
}); // });
} // }
static restartJobRequest(userId, jobId) { // static restartJobRequest(userId, jobId) {
return new Promise((resolve, reject) => { // return new Promise((resolve, reject) => {
let job; // let job;
try { // try {
job = app.data.users[userId].jobs[jobId]; // job = app.data.users[userId].jobs[jobId];
} catch (error) { // } catch (error) {
job = {}; // job = {};
} // }
let modalElement = Utils.HTMLToElement( // let modalElement = Utils.HTMLToElement(
` // `
<div class="modal"> // <div class="modal">
<div class="modal-content"> // <div class="modal-content">
<h4>Confirm Job restart</h4> // <h4>Confirm Job restart</h4>
<p>Do you really want to restart the Job <b>${job?.title}</b>? All Job Results will be permanently deleted.</p> // <p>Do you really want to restart the Job <b>${job?.title}</b>? All Job Results will be permanently deleted.</p>
</div> // </div>
<div class="modal-footer"> // <div class="modal-footer">
<a class="action-button btn modal-close waves-effect waves-light" data-action="cancel">Cancel</a> // <a class="action-button btn modal-close waves-effect waves-light" data-action="cancel">Cancel</a>
<a class="action-button btn modal-close red waves-effect waves-light" data-action="confirm">Restart</a> // <a class="action-button btn modal-close red waves-effect waves-light" data-action="confirm">Restart</a>
</div> // </div>
</div> // </div>
` // `
); // );
document.querySelector('#modals').appendChild(modalElement); // document.querySelector('#modals').appendChild(modalElement);
let modal = M.Modal.init( // let modal = M.Modal.init(
modalElement, // modalElement,
{ // {
dismissible: false, // dismissible: false,
onCloseEnd: () => { // onCloseEnd: () => {
modal.destroy(); // modal.destroy();
modalElement.remove(); // modalElement.remove();
} // }
} // }
); // );
let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]'); // let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]');
confirmElement.addEventListener('click', (event) => { // confirmElement.addEventListener('click', (event) => {
let jobTitle = job?.title; // let jobTitle = job?.title;
fetch(`/jobs/${jobId}/restart`, {method: 'POST', headers: {Accept: 'application/json'}}) // fetch(`/jobs/${jobId}/restart`, {method: 'POST', headers: {Accept: 'application/json'}})
.then( // .then(
(response) => { // (response) => {
if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);} // if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);} // if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);}
if (response.status === 409) {app.flash('Conflict', 'error'); reject(response);} // if (response.status === 409) {app.flash('Conflict', 'error'); reject(response);}
app.flash(`Job "${jobTitle}" restarted.`, 'job'); // app.flash(`Job "${jobTitle}" restarted.`, 'job');
resolve(response); // resolve(response);
}, // },
(response) => { // (response) => {
app.flash('Something went wrong', 'error'); // app.flash('Something went wrong', 'error');
reject(response); // reject(response);
} // }
); // );
}); // });
modal.open(); // modal.open();
}); // });
} // }
static deleteUserRequest(userId) { static deleteUserRequest(userId) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {

View File

@ -79,9 +79,9 @@
</div> </div>
<div class="card-action right-align"> <div class="card-action right-align">
{% if current_user.is_administrator() %} {% if current_user.is_administrator() %}
<a class="btn disabled waves-effect waves-light modal-trigger" id="log-job-modal"><i class="material-icons left">text_snippet</i>Log</a> <a class="action-button btn disabled waves-effect waves-light modal-trigger" data-action="get-log-request" id="job-log-button" href="#job-log-modal"><i class="material-icons left">text_snippet</i>Log</a>
{% endif %} {% endif %}
<a class="action-button btn disabled waves-effect waves-light" data-action="restart-request"><i class="material-icons left">repeat</i>Restart</a> <a class="btn disabled waves-effect waves-light modal-trigger" href="#restart-job-modal"><i class="material-icons left">repeat</i>Restart</a>
<a class="btn red waves-effect waves-light modal-trigger" href="#delete-job-modal"><i class="material-icons left">delete</i>Delete</a> <a class="btn red waves-effect waves-light modal-trigger" href="#delete-job-modal"><i class="material-icons left">delete</i>Delete</a>
</div> </div>
</div> </div>
@ -120,20 +120,31 @@
<p>Do you really want to delete the Job <b>{{job.title}}</b>? All files will be permanently deleted!</p> <p>Do you really want to delete the Job <b>{{job.title}}</b>? All files will be permanently deleted!</p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<a class="action-button btn modal-close waves-effect waves-light" data-action="cancel">Cancel</a> <a class="btn modal-close waves-effect waves-light">Cancel</a>
<a class="btn modal-close red waves-effect waves-light" id="delete-job-request">Delete</a> <a class="btn modal-close red waves-effect waves-light" id="delete-job-request">Delete</a>
</div> </div>
</div> </div>
<div class="modal" id="log-job-modal"> <div class="modal" id="job-log-modal">
<div class="modal-content"> <div class="modal-content">
<h4>Job logs</h4> <h4>Job logs</h4>
<pre><code>${text}</code></pre> <pre><code></code></pre>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<a class="btn modal-close waves-effect waves-light">Close</a> <a class="btn modal-close waves-effect waves-light">Close</a>
</div> </div>
</div> </div>
<div class="modal" id="restart-job-modal">
<div class="modal-content">
<h4>Confirm Job restart</h4>
<p>Do you really want to restart the Job <b>{{ job.title }}</b>? All Job Results will be permanently deleted.</p>
</div>
<div class="modal-footer">
<a class="btn modal-close waves-effect waves-light">Cancel</a>
<a class="btn modal-close red waves-effect waves-light" id="restart-job-request">Restart</a>
</div>
</div>
{% endblock modals %} {% endblock modals %}
@ -142,12 +153,24 @@
<script> <script>
let jobDisplay = new JobDisplay(document.querySelector('#job-display')); let jobDisplay = new JobDisplay(document.querySelector('#job-display'));
let deleteJobRequestElement = document.querySelector('#delete-job-request'); let deleteJobRequestElement = document.querySelector('#delete-job-request');
let logJobModalElement = document.querySelector('#log-job-modal'); let jobLogButtonElement = document.querySelector('#job-log-button');
let restartJobRequestElement = document.querySelector('#restart-job-request');
deleteJobRequestElement.addEventListener('click', (event) => { deleteJobRequestElement.addEventListener('click', (event) => {
Requests.jobs.entity.delete({{ job.hashid|tojson }}); Requests.jobs.entity.delete({{ job.hashid|tojson }});
}); });
logJobModalElement.addEventListener('click', (event) => { jobLogButtonElement.addEventListener('click', (event) => {
Requests.jobs.entity.log({{ job.hashid|tojson }}); Requests.jobs.entity.log({{ job.hashid|tojson }})
}); .then(
(response) => {
response.json()
.then((json) => {
let jobLogModalElement = document.querySelector('#job-log-modal');
jobLogModalElement.querySelector('pre code').textContent = json.jobLog;
});
});
});
restartJobRequestElement.addEventListener('click', (event) => {
Requests.jobs.entity.restart({{ job.hashid|tojson }});
});
</script> </script>
{% endblock scripts %} {% endblock scripts %}