mirror of
				https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
				synced 2025-10-31 10:42:43 +00:00 
			
		
		
		
	Add job namespace and remove old json_routes logic
This commit is contained in:
		| @@ -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 | ||||
|   | ||||
| @@ -15,4 +15,4 @@ def before_request(): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| from . import routes, json_routes | ||||
| from . import routes | ||||
|   | ||||
| @@ -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('/<hashid:job_id>', 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('/<hashid:job_id>/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('/<hashid:job_id>/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 | ||||
| @@ -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): | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
							
								
								
									
										37
									
								
								app/static/js/app/endpoints/jobs.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								app/static/js/app/endpoints/jobs.js
									
									
									
									
									
										Normal file
									
								
							| @@ -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; | ||||
|   } | ||||
| } | ||||
| @@ -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; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -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(); | ||||
|   | ||||
| @@ -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', | ||||
|   | ||||
| @@ -137,28 +137,26 @@ | ||||
| {% block scripts %} | ||||
| {{ super() }} | ||||
| <script> | ||||
|   let deleteJobRequestElement = document.querySelector('#delete-job-request'); | ||||
|   let restartJobRequestElement = document.querySelector('#restart-job-request'); | ||||
|   deleteJobRequestElement.addEventListener('click', (event) => { | ||||
|        nopaque.requests.jobs.entity.delete({{ job.hashid|tojson }}); | ||||
|     }); | ||||
|   restartJobRequestElement.addEventListener('click', (event) => { | ||||
|       nopaque.requests.jobs.entity.restart({{ job.hashid|tojson }}); | ||||
|   const deleteJobRequestElement = document.querySelector('#delete-job-request'); | ||||
|   const restartJobRequestElement = document.querySelector('#restart-job-request'); | ||||
|  | ||||
|   deleteJobRequestElement.addEventListener('click', async (event) => { | ||||
|     const message = await app.jobs.delete({{ job.hashid|tojson }}); | ||||
|     app.ui.flash(message, 'job'); | ||||
|   }); | ||||
|   restartJobRequestElement.addEventListener('click', async (event) => { | ||||
|     const message = await app.jobs.restart({{ job.hashid|tojson }}); | ||||
|     app.ui.flash(message, 'job'); | ||||
|   }); | ||||
|  | ||||
|   if ({{ current_user.is_administrator|tojson }}) { | ||||
|     let jobLogButtonElement = document.querySelector('#job-log-button'); | ||||
|     jobLogButtonElement.addEventListener('click', (event) => { | ||||
|       nopaque.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; | ||||
|             }); | ||||
|         }); | ||||
|     }); | ||||
|   } | ||||
|   {% if current_user.is_administrator %} | ||||
|   const jobLogButtonElement = document.querySelector('#job-log-button'); | ||||
|  | ||||
|   jobLogButtonElement.addEventListener('click', async (event) => { | ||||
|     const jobLogModalElement = document.querySelector('#job-log-modal'); | ||||
|     const log = await app.jobs.log({{ job.hashid|tojson }}); | ||||
|     jobLogModalElement.querySelector('pre code').textContent = log; | ||||
|   }); | ||||
|   {% endif %} | ||||
| </script> | ||||
| {% endblock scripts %} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user