diff --git a/app/jobs/json_routes.py b/app/jobs/json_routes.py index 261fe3ccd12151a4c8dcbe33a75f9842c7f56e8e..50846baeacccc9d7a364313908d8c761ac657385 100644 --- a/app/jobs/json_routes.py +++ b/app/jobs/json_routes.py @@ -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 threading import Thread import os from app import db 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 @bp.route('/<hashid:job_id>', methods=['DELETE']) @@ -36,6 +36,7 @@ def delete_job(job_id): @bp.route('/<hashid:job_id>/log') @login_required @admin_required +@content_negotiation(produces='application/json') def job_log(job_id): job = Job.query.get_or_404(job_id) if job.status not in [JobStatus.COMPLETED, JobStatus.FAILED]: @@ -43,11 +44,18 @@ def job_log(job_id): return response, 409 with open(os.path.join(job.path, 'pipeline_data', 'logs', 'pyflow_log.txt')) as log_file: 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']) @login_required +@content_negotiation(produces='application/json') def restart_job(job_id): def _restart_job(app, job_id): with app.app_context(): @@ -66,4 +74,10 @@ def restart_job(job_id): args=(current_app._get_current_object(), job_id) ) thread.start() - return {}, 202 + response_data = { + 'message': \ + f'Job "{job.title}" marked for restarting' + } + response = jsonify(response_data) + response.status_code = 202 + return response diff --git a/app/static/js/Requests/jobs/jobs.js b/app/static/js/Requests/jobs/jobs.js index f1179658699ae6f6811ca9709bd1530896def3da..64e523db54371304c696aa6d7971e14b6868ced3 100644 --- a/app/static/js/Requests/jobs/jobs.js +++ b/app/static/js/Requests/jobs/jobs.js @@ -21,3 +21,11 @@ Requests.jobs.entity.log = (jobId) => { }; return Requests.JSONfetch(input, init); } + +Requests.jobs.entity.restart = (jobId) => { + let input = `/jobs/${jobId}/restart`; + let init = { + method: 'POST' + }; + return Requests.JSONfetch(input, init); +} diff --git a/app/static/js/ResourceDisplays/JobDisplay.js b/app/static/js/ResourceDisplays/JobDisplay.js index df8eb7b555e41b6ae82fbfe31357c3d2acae2e50..6287d934b3baa69b41e23783ec4dc510d618b539 100644 --- a/app/static/js/ResourceDisplays/JobDisplay.js +++ b/app/static/js/ResourceDisplays/JobDisplay.js @@ -2,16 +2,6 @@ class JobDisplay extends ResourceDisplay { constructor(displayElement) { super(displayElement); 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) { diff --git a/app/static/js/Utils.js b/app/static/js/Utils.js index ec16ff05030b48d9ca1acea8fe02db593e996c97..e7c5404e66f82722ef81bbc8dcf984264c98ea2c 100644 --- a/app/static/js/Utils.js +++ b/app/static/js/Utils.js @@ -229,138 +229,138 @@ class Utils { }); } - static deleteJobRequest(userId, jobId) { - return new Promise((resolve, reject) => { - let job; - try { - job = app.data.users[userId].jobs[jobId]; - } catch (error) { - job = {}; - } + // static deleteJobRequest(userId, jobId) { + // return new Promise((resolve, reject) => { + // let job; + // try { + // job = app.data.users[userId].jobs[jobId]; + // } catch (error) { + // job = {}; + // } - let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]'); - confirmElement.addEventListener('click', (event) => { - let jobTitle = job?.title; - fetch(`/jobs/${jobId}`, {method: 'DELETE', headers: {Accept: 'application/json'}}) - .then( - (response) => { - if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);} - if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);} - app.flash(`Job "${jobTitle}" marked for deletion`, 'job'); - resolve(response); - }, - (response) => { - app.flash('Something went wrong', 'error'); - reject(response); - } - ); - }); - modal.open(); - }); - } + // let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]'); + // confirmElement.addEventListener('click', (event) => { + // let jobTitle = job?.title; + // fetch(`/jobs/${jobId}`, {method: 'DELETE', headers: {Accept: 'application/json'}}) + // .then( + // (response) => { + // if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);} + // if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);} + // app.flash(`Job "${jobTitle}" marked for deletion`, 'job'); + // resolve(response); + // }, + // (response) => { + // app.flash('Something went wrong', 'error'); + // reject(response); + // } + // ); + // }); + // modal.open(); + // }); + // } - static getJobLogRequest(userId, jobId) { - return new Promise((resolve, reject) => { - fetch(`/jobs/${jobId}/log`, {method: 'GET', headers: {Accept: 'application/json, text/plain'}}) - .then( - (response) => { - if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);} - if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);} - return response.text(); - }, - (response) => { - app.flash('Something went wrong', 'error'); - reject(response); - } - ) - .then( - (text) => { - let modalElement = Utils.HTMLToElement( - ` - <div class="modal"> - <div class="modal-content"> - <h4>Job logs</h4> - <pre><code>${text}</code></pre> - </div> - <div class="modal-footer"> - <a class="btn modal-close waves-effect waves-light">Close</a> - </div> - </div> - ` - ); - document.querySelector('#modals').appendChild(modalElement); - let modal = M.Modal.init( - modalElement, - { - onCloseEnd: () => { - modal.destroy(); - modalElement.remove(); - } - } - ); - modal.open(); - resolve(text); - } - ); - }); - } + // static getJobLogRequest(userId, jobId) { + // return new Promise((resolve, reject) => { + // fetch(`/jobs/${jobId}/log`, {method: 'GET', headers: {Accept: 'application/json, text/plain'}}) + // .then( + // (response) => { + // if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);} + // if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);} + // return response.text(); + // }, + // (response) => { + // app.flash('Something went wrong', 'error'); + // reject(response); + // } + // ) + // .then( + // (text) => { + // let modalElement = Utils.HTMLToElement( + // ` + // <div class="modal"> + // <div class="modal-content"> + // <h4>Job logs</h4> + // <pre><code>${text}</code></pre> + // </div> + // <div class="modal-footer"> + // <a class="btn modal-close waves-effect waves-light">Close</a> + // </div> + // </div> + // ` + // ); + // document.querySelector('#modals').appendChild(modalElement); + // let modal = M.Modal.init( + // modalElement, + // { + // onCloseEnd: () => { + // modal.destroy(); + // modalElement.remove(); + // } + // } + // ); + // modal.open(); + // resolve(text); + // } + // ); + // }); + // } - static restartJobRequest(userId, jobId) { - return new Promise((resolve, reject) => { - let job; - try { - job = app.data.users[userId].jobs[jobId]; - } catch (error) { - job = {}; - } + // static restartJobRequest(userId, jobId) { + // return new Promise((resolve, reject) => { + // let job; + // try { + // job = app.data.users[userId].jobs[jobId]; + // } catch (error) { + // job = {}; + // } - let modalElement = Utils.HTMLToElement( - ` - <div class="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="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> - </div> - </div> - ` - ); - document.querySelector('#modals').appendChild(modalElement); - let modal = M.Modal.init( - modalElement, - { - dismissible: false, - onCloseEnd: () => { - modal.destroy(); - modalElement.remove(); - } - } - ); + // let modalElement = Utils.HTMLToElement( + // ` + // <div class="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="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> + // </div> + // </div> + // ` + // ); + // document.querySelector('#modals').appendChild(modalElement); + // let modal = M.Modal.init( + // modalElement, + // { + // dismissible: false, + // onCloseEnd: () => { + // modal.destroy(); + // modalElement.remove(); + // } + // } + // ); - let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]'); - confirmElement.addEventListener('click', (event) => { - let jobTitle = job?.title; - fetch(`/jobs/${jobId}/restart`, {method: 'POST', headers: {Accept: 'application/json'}}) - .then( - (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 === 409) {app.flash('Conflict', 'error'); reject(response);} - app.flash(`Job "${jobTitle}" restarted.`, 'job'); - resolve(response); - }, - (response) => { - app.flash('Something went wrong', 'error'); - reject(response); - } - ); - }); - modal.open(); - }); - } + // let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]'); + // confirmElement.addEventListener('click', (event) => { + // let jobTitle = job?.title; + // fetch(`/jobs/${jobId}/restart`, {method: 'POST', headers: {Accept: 'application/json'}}) + // .then( + // (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 === 409) {app.flash('Conflict', 'error'); reject(response);} + // app.flash(`Job "${jobTitle}" restarted.`, 'job'); + // resolve(response); + // }, + // (response) => { + // app.flash('Something went wrong', 'error'); + // reject(response); + // } + // ); + // }); + // modal.open(); + // }); + // } static deleteUserRequest(userId) { return new Promise((resolve, reject) => { diff --git a/app/templates/jobs/job.html.j2 b/app/templates/jobs/job.html.j2 index 30bedaa11f4e3cc14406541d29d30b16a2c7f1b4..b5055ce61fd84a1ae3dacbd2eedb20773b910503 100644 --- a/app/templates/jobs/job.html.j2 +++ b/app/templates/jobs/job.html.j2 @@ -79,9 +79,9 @@ </div> <div class="card-action right-align"> {% 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 %} - <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> </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> </div> <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> </div> </div> -<div class="modal" id="log-job-modal"> +<div class="modal" id="job-log-modal"> <div class="modal-content"> <h4>Job logs</h4> - <pre><code>${text}</code></pre> + <pre><code></code></pre> </div> <div class="modal-footer"> <a class="btn modal-close waves-effect waves-light">Close</a> </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 %} @@ -142,12 +153,24 @@ <script> let jobDisplay = new JobDisplay(document.querySelector('#job-display')); 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) => { Requests.jobs.entity.delete({{ job.hashid|tojson }}); }); - logJobModalElement.addEventListener('click', (event) => { - Requests.jobs.entity.log({{ job.hashid|tojson }}); - }); + jobLogButtonElement.addEventListener('click', (event) => { + 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> {% endblock scripts %}