Add job namespace and remove old json_routes logic

This commit is contained in:
Patrick Jentsch 2024-12-06 11:41:36 +01:00
parent 1372c86609
commit 93344c9573
10 changed files with 82 additions and 110 deletions

View File

@ -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

View File

@ -15,4 +15,4 @@ def before_request():
pass
from . import routes, json_routes
from . import routes

View File

@ -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

View File

@ -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):

View File

@ -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

View 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;
}
}

View File

@ -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;
}
}

View File

@ -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();

View File

@ -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',

View File

@ -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 %}