diff --git a/app/__init__.py b/app/__init__.py index 9953a1cf..4a198125 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -133,6 +133,9 @@ def create_app(config: Config = Config) -> Flask: from .namespaces.cqi_over_sio import CQiOverSocketIONamespace socketio.on_namespace(CQiOverSocketIONamespace('/cqi_over_sio')) + from .namespaces.jobs import JobsNamespace + socketio.on_namespace(JobsNamespace('/jobs')) + from .namespaces.users import UsersNamespace socketio.on_namespace(UsersNamespace('/users')) # endregion SocketIO Namespaces diff --git a/app/blueprints/jobs/__init__.py b/app/blueprints/jobs/__init__.py index 1350e7e1..1f311bd2 100644 --- a/app/blueprints/jobs/__init__.py +++ b/app/blueprints/jobs/__init__.py @@ -15,4 +15,4 @@ def before_request(): pass -from . import routes, json_routes +from . import routes diff --git a/app/blueprints/jobs/json_routes.py b/app/blueprints/jobs/json_routes.py deleted file mode 100644 index 54a682cb..00000000 --- a/app/blueprints/jobs/json_routes.py +++ /dev/null @@ -1,72 +0,0 @@ -from flask import abort, current_app -from flask_login import current_user -from threading import Thread -from app import db -from app.decorators import admin_required, content_negotiation -from app.models import Job, JobStatus -from . import bp - - -@bp.route('/', methods=['DELETE']) -@content_negotiation(produces='application/json') -def delete_job(job_id): - def _delete_job(app, job_id): - with app.app_context(): - job = Job.query.get(job_id) - job.delete() - db.session.commit() - - job = Job.query.get_or_404(job_id) - if not (job.user == current_user or current_user.is_administrator): - abort(403) - thread = Thread( - target=_delete_job, - args=(current_app._get_current_object(), job_id) - ) - thread.start() - response_data = { - 'message': f'Job "{job.title}" marked for deletion' - } - return response_data, 202 - - -@bp.route('//log') -@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]: - response = {'errors': {'message': 'Job status is not completed or failed'}} - return response, 409 - with open(job.path / 'pipeline_data' / 'logs' / 'pyflow_log.txt') as log_file: - log = log_file.read() - response_data = { - 'jobLog': log - } - return response_data, 200 - - -@bp.route('//restart', methods=['POST']) -@content_negotiation(produces='application/json') -def restart_job(job_id): - def _restart_job(app, job_id): - with app.app_context(): - job = Job.query.get(job_id) - job.restart() - db.session.commit() - - job = Job.query.get_or_404(job_id) - if not (job.user == current_user or current_user.is_administrator): - abort(403) - if job.status == JobStatus.FAILED: - response = {'errors': {'message': 'Job status is not "failed"'}} - return response, 409 - thread = Thread( - target=_restart_job, - args=(current_app._get_current_object(), job_id) - ) - thread.start() - response_data = { - 'message': f'Job "{job.title}" marked for restarting' - } - return response_data, 202 diff --git a/app/namespaces/jobs.py b/app/namespaces/jobs.py index 03da5543..53c6a2b4 100644 --- a/app/namespaces/jobs.py +++ b/app/namespaces/jobs.py @@ -13,14 +13,14 @@ def _delete_job(app: Flask, job_id: int): db.session.commit() -def _restart_job(app, job_id): +def _restart_job(app: Flask, job_id: int): with app.app_context(): job = Job.query.get(job_id) job.restart() db.session.commit() -class UsersNamespace(Namespace): +class JobsNamespace(Namespace): @socketio_login_required def on_delete(self, job_hashid: str) -> dict: job_id = hashids.decode(job_hashid) @@ -52,7 +52,7 @@ class UsersNamespace(Namespace): } @socketio_admin_required - def on_log(self, job_hashid: str): + def on_log(self, job_hashid: str) -> dict: job_id = hashids.decode(job_hashid) if not isinstance(job_id, int): @@ -76,7 +76,7 @@ class UsersNamespace(Namespace): } socketio_login_required - def on_restart(self, job_hashid: str): + def on_restart(self, job_hashid: str) -> dict: job_id = hashids.decode(job_hashid) if not isinstance(job_id, int): diff --git a/app/static/js/app.js b/app/static/js/app.js index fc52a7f2..e9469df4 100644 --- a/app/static/js/app.js +++ b/app/static/js/app.js @@ -3,6 +3,7 @@ nopaque.App = class App { this.socket = io({transports: ['websocket'], upgrade: false}); // Endpoints + this.jobs = new nopaque.app.endpoints.Jobs(this); this.users = new nopaque.app.endpoints.Users(this); // Extensions diff --git a/app/static/js/app/endpoints/jobs.js b/app/static/js/app/endpoints/jobs.js new file mode 100644 index 00000000..aa42fa81 --- /dev/null +++ b/app/static/js/app/endpoints/jobs.js @@ -0,0 +1,37 @@ +nopaque.app.endpoints.Jobs = class Jobs { + constructor(app) { + this.app = app; + + this.socket = io('/jobs', {transports: ['websocket'], upgrade: false}); + } + + async delete(id) { + const response = await this.socket.emitWithAck('delete', id); + + if (response.status != 202) { + throw new Error(`[${response.status}] ${response.statusText}`); + } + + return response.body; + } + + async log(id) { + const response = await this.socket.emitWithAck('log', id); + + if (response.status != 200) { + throw new Error(`[${response.status}] ${response.statusText}`); + } + + return response.body; + } + + async restart(id) { + const response = await this.socket.emitWithAck('restart', id); + + if (response.status != 202) { + throw new Error(`[${response.status}] ${response.statusText}`); + } + + return response.body; + } +} diff --git a/app/static/js/app/endpoints/users.js b/app/static/js/app/endpoints/users.js index b1bfa7fd..b7dec629 100644 --- a/app/static/js/app/endpoints/users.js +++ b/app/static/js/app/endpoints/users.js @@ -5,8 +5,8 @@ nopaque.app.endpoints.Users = class Users { this.socket = io('/users', {transports: ['websocket'], upgrade: false}); } - async get(userId) { - const response = await this.socket.emitWithAck('get', userId); + async get(id) { + const response = await this.socket.emitWithAck('get', id); if (response.status !== 200) { throw new Error(`[${response.status}] ${response.statusText}`); @@ -15,27 +15,29 @@ nopaque.app.endpoints.Users = class Users { return response.body; } - async subscribe(userId) { - const response = await this.socket.emitWithAck('subscribe', userId); + async subscribe(id) { + const response = await this.socket.emitWithAck('subscribe', id); if (response.status != 200) { throw new Error(`[${response.status}] ${response.statusText}`); } } - async unsubscribe(userId) { - const response = await this.socket.emitWithAck('unsubscribe', userId); + async unsubscribe(id) { + const response = await this.socket.emitWithAck('unsubscribe', id); if (response.status != 200) { throw new Error(`[${response.status}] ${response.statusText}`); } } - async delete(userId) { - const response = await this.socket.emitWithAck('delete', userId); + async delete(id) { + const response = await this.socket.emitWithAck('delete', id); if (response.status != 202) { throw new Error(`[${response.status}] ${response.statusText}`); } + + return response.body; } } diff --git a/app/static/js/resource-lists/job-list.js b/app/static/js/resource-lists/job-list.js index 43a3b58f..cef0ea83 100644 --- a/app/static/js/resource-lists/job-list.js +++ b/app/static/js/resource-lists/job-list.js @@ -136,8 +136,9 @@ nopaque.resource_lists.JobList = class JobList extends nopaque.resource_lists.Re } ); let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]'); - confirmElement.addEventListener('click', (event) => { - nopaque.requests.jobs.entity.delete(itemId); + confirmElement.addEventListener('click', async (event) => { + const message = await app.jobs.delete(itemId); + app.ui.flash(message, 'job'); }); modal.open(); break; @@ -221,8 +222,9 @@ nopaque.resource_lists.JobList = class JobList extends nopaque.resource_lists.Re ); let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]'); confirmElement.addEventListener('click', (event) => { - this.selectedItemIds.forEach(selectedItemId => { - nopaque.requests.jobs.entity.delete(selectedItemId); + this.selectedItemIds.forEach(async (selectedItemId) => { + const message = await app.jobs.delete(selectedItemId); + app.ui.flash(message, 'job'); }); this.selectedItemIds.clear(); this.renderingItemSelection(); diff --git a/app/templates/_base/scripts.html.j2 b/app/templates/_base/scripts.html.j2 index 5bb3d1de..d4eb6c7f 100644 --- a/app/templates/_base/scripts.html.j2 +++ b/app/templates/_base/scripts.html.j2 @@ -11,6 +11,7 @@ 'js/app.js', 'js/app/index.js', 'js/app/endpoints/index.js', + 'js/app/endpoints/jobs.js', 'js/app/endpoints/users.js', 'js/app/extensions/index.js', 'js/app/extensions/toaster.js', diff --git a/app/templates/jobs/job.html.j2 b/app/templates/jobs/job.html.j2 index b852b5ed..1430666f 100644 --- a/app/templates/jobs/job.html.j2 +++ b/app/templates/jobs/job.html.j2 @@ -137,28 +137,26 @@ {% block scripts %} {{ super() }} {% endblock scripts %}