mirror of
				https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
				synced 2025-10-31 02:32:45 +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 |     from .namespaces.cqi_over_sio import CQiOverSocketIONamespace | ||||||
|     socketio.on_namespace(CQiOverSocketIONamespace('/cqi_over_sio')) |     socketio.on_namespace(CQiOverSocketIONamespace('/cqi_over_sio')) | ||||||
|  |  | ||||||
|  |     from .namespaces.jobs import JobsNamespace | ||||||
|  |     socketio.on_namespace(JobsNamespace('/jobs')) | ||||||
|  |  | ||||||
|     from .namespaces.users import UsersNamespace |     from .namespaces.users import UsersNamespace | ||||||
|     socketio.on_namespace(UsersNamespace('/users')) |     socketio.on_namespace(UsersNamespace('/users')) | ||||||
|     # endregion SocketIO Namespaces |     # endregion SocketIO Namespaces | ||||||
|   | |||||||
| @@ -15,4 +15,4 @@ def before_request(): | |||||||
|     pass |     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() |         db.session.commit() | ||||||
|  |  | ||||||
|  |  | ||||||
| def _restart_job(app, job_id): | def _restart_job(app: Flask, job_id: int): | ||||||
|     with app.app_context(): |     with app.app_context(): | ||||||
|         job = Job.query.get(job_id) |         job = Job.query.get(job_id) | ||||||
|         job.restart() |         job.restart() | ||||||
|         db.session.commit() |         db.session.commit() | ||||||
|  |  | ||||||
|  |  | ||||||
| class UsersNamespace(Namespace): | class JobsNamespace(Namespace): | ||||||
|     @socketio_login_required |     @socketio_login_required | ||||||
|     def on_delete(self, job_hashid: str) -> dict: |     def on_delete(self, job_hashid: str) -> dict: | ||||||
|         job_id = hashids.decode(job_hashid) |         job_id = hashids.decode(job_hashid) | ||||||
| @@ -52,7 +52,7 @@ class UsersNamespace(Namespace): | |||||||
|         } |         } | ||||||
|  |  | ||||||
|     @socketio_admin_required |     @socketio_admin_required | ||||||
|     def on_log(self, job_hashid: str): |     def on_log(self, job_hashid: str) -> dict: | ||||||
|         job_id = hashids.decode(job_hashid) |         job_id = hashids.decode(job_hashid) | ||||||
|  |  | ||||||
|         if not isinstance(job_id, int): |         if not isinstance(job_id, int): | ||||||
| @@ -76,7 +76,7 @@ class UsersNamespace(Namespace): | |||||||
|         } |         } | ||||||
|  |  | ||||||
|     socketio_login_required |     socketio_login_required | ||||||
|     def on_restart(self, job_hashid: str): |     def on_restart(self, job_hashid: str) -> dict: | ||||||
|         job_id = hashids.decode(job_hashid) |         job_id = hashids.decode(job_hashid) | ||||||
|  |  | ||||||
|         if not isinstance(job_id, int): |         if not isinstance(job_id, int): | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ nopaque.App = class App { | |||||||
|     this.socket = io({transports: ['websocket'], upgrade: false}); |     this.socket = io({transports: ['websocket'], upgrade: false}); | ||||||
|  |  | ||||||
|     // Endpoints |     // Endpoints | ||||||
|  |     this.jobs = new nopaque.app.endpoints.Jobs(this); | ||||||
|     this.users = new nopaque.app.endpoints.Users(this); |     this.users = new nopaque.app.endpoints.Users(this); | ||||||
|  |  | ||||||
|     // Extensions |     // 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}); |     this.socket = io('/users', {transports: ['websocket'], upgrade: false}); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   async get(userId) { |   async get(id) { | ||||||
|     const response = await this.socket.emitWithAck('get', userId); |     const response = await this.socket.emitWithAck('get', id); | ||||||
|  |  | ||||||
|     if (response.status !== 200) { |     if (response.status !== 200) { | ||||||
|       throw new Error(`[${response.status}] ${response.statusText}`); |       throw new Error(`[${response.status}] ${response.statusText}`); | ||||||
| @@ -15,27 +15,29 @@ nopaque.app.endpoints.Users = class Users { | |||||||
|     return response.body; |     return response.body; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   async subscribe(userId) { |   async subscribe(id) { | ||||||
|     const response = await this.socket.emitWithAck('subscribe', userId); |     const response = await this.socket.emitWithAck('subscribe', id); | ||||||
|  |  | ||||||
|     if (response.status != 200) { |     if (response.status != 200) { | ||||||
|       throw new Error(`[${response.status}] ${response.statusText}`); |       throw new Error(`[${response.status}] ${response.statusText}`); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   async unsubscribe(userId) { |   async unsubscribe(id) { | ||||||
|     const response = await this.socket.emitWithAck('unsubscribe', userId); |     const response = await this.socket.emitWithAck('unsubscribe', id); | ||||||
|  |  | ||||||
|     if (response.status != 200) { |     if (response.status != 200) { | ||||||
|       throw new Error(`[${response.status}] ${response.statusText}`); |       throw new Error(`[${response.status}] ${response.statusText}`); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   async delete(userId) { |   async delete(id) { | ||||||
|     const response = await this.socket.emitWithAck('delete', userId); |     const response = await this.socket.emitWithAck('delete', id); | ||||||
|  |  | ||||||
|     if (response.status != 202) { |     if (response.status != 202) { | ||||||
|       throw new Error(`[${response.status}] ${response.statusText}`); |       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"]'); |         let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]'); | ||||||
|         confirmElement.addEventListener('click', (event) => { |         confirmElement.addEventListener('click', async (event) => { | ||||||
|           nopaque.requests.jobs.entity.delete(itemId); |           const message = await app.jobs.delete(itemId); | ||||||
|  |           app.ui.flash(message, 'job'); | ||||||
|         }); |         }); | ||||||
|         modal.open(); |         modal.open(); | ||||||
|         break; |         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"]'); |         let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]'); | ||||||
|         confirmElement.addEventListener('click', (event) => { |         confirmElement.addEventListener('click', (event) => { | ||||||
|           this.selectedItemIds.forEach(selectedItemId => { |           this.selectedItemIds.forEach(async (selectedItemId) => { | ||||||
|             nopaque.requests.jobs.entity.delete(selectedItemId); |             const message = await app.jobs.delete(selectedItemId); | ||||||
|  |             app.ui.flash(message, 'job'); | ||||||
|           }); |           }); | ||||||
|           this.selectedItemIds.clear(); |           this.selectedItemIds.clear(); | ||||||
|           this.renderingItemSelection(); |           this.renderingItemSelection(); | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ | |||||||
|     'js/app.js', |     'js/app.js', | ||||||
|     'js/app/index.js', |     'js/app/index.js', | ||||||
|     'js/app/endpoints/index.js', |     'js/app/endpoints/index.js', | ||||||
|  |     'js/app/endpoints/jobs.js', | ||||||
|     'js/app/endpoints/users.js', |     'js/app/endpoints/users.js', | ||||||
|     'js/app/extensions/index.js', |     'js/app/extensions/index.js', | ||||||
|     'js/app/extensions/toaster.js', |     'js/app/extensions/toaster.js', | ||||||
|   | |||||||
| @@ -137,28 +137,26 @@ | |||||||
| {% block scripts %} | {% block scripts %} | ||||||
| {{ super() }} | {{ super() }} | ||||||
| <script> | <script> | ||||||
|   let deleteJobRequestElement = document.querySelector('#delete-job-request'); |   const deleteJobRequestElement = document.querySelector('#delete-job-request'); | ||||||
|   let restartJobRequestElement = document.querySelector('#restart-job-request'); |   const restartJobRequestElement = document.querySelector('#restart-job-request'); | ||||||
|   deleteJobRequestElement.addEventListener('click', (event) => { |  | ||||||
|        nopaque.requests.jobs.entity.delete({{ job.hashid|tojson }}); |   deleteJobRequestElement.addEventListener('click', async (event) => { | ||||||
|     }); |     const message = await app.jobs.delete({{ job.hashid|tojson }}); | ||||||
|   restartJobRequestElement.addEventListener('click', (event) => { |     app.ui.flash(message, 'job'); | ||||||
|       nopaque.requests.jobs.entity.restart({{ job.hashid|tojson }}); |   }); | ||||||
|  |   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 }}) { |   {% if current_user.is_administrator %} | ||||||
|     let jobLogButtonElement = document.querySelector('#job-log-button'); |   const jobLogButtonElement = document.querySelector('#job-log-button'); | ||||||
|     jobLogButtonElement.addEventListener('click', (event) => { |  | ||||||
|       nopaque.requests.jobs.entity.log({{ job.hashid|tojson }}) |   jobLogButtonElement.addEventListener('click', async (event) => { | ||||||
|       .then( |     const jobLogModalElement = document.querySelector('#job-log-modal'); | ||||||
|         (response) => { |     const log = await app.jobs.log({{ job.hashid|tojson }}); | ||||||
|           response.json() |     jobLogModalElement.querySelector('pre code').textContent = log; | ||||||
|             .then((json) => { |   }); | ||||||
|               let jobLogModalElement = document.querySelector('#job-log-modal'); |   {% endif %} | ||||||
|               jobLogModalElement.querySelector('pre code').textContent = json.jobLog; |  | ||||||
|             }); |  | ||||||
|         }); |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
| </script> | </script> | ||||||
| {% endblock scripts %} | {% endblock scripts %} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user