mirror of
https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
synced 2025-07-01 10:20:34 +00:00
Compare commits
4 Commits
1.1.0
...
bb60a2ba67
Author | SHA1 | Date | |
---|---|---|---|
bb60a2ba67 | |||
328f85ba52 | |||
93344c9573 | |||
1372c86609 |
@ -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.corpora import CorporaNamespace
|
||||
socketio.on_namespace(CorporaNamespace('/corpora'))
|
||||
|
||||
from .namespaces.users import UsersNamespace
|
||||
socketio.on_namespace(UsersNamespace('/users'))
|
||||
# endregion SocketIO Namespaces
|
||||
|
@ -1,45 +0,0 @@
|
||||
from flask_login import current_user
|
||||
from flask_socketio import join_room
|
||||
from app import hashids, socketio
|
||||
from app.decorators import socketio_login_required
|
||||
from app.models import Corpus
|
||||
|
||||
|
||||
@socketio.on('GET /corpora/<corpus_id>')
|
||||
@socketio_login_required
|
||||
def get_corpus(corpus_hashid):
|
||||
corpus_id = hashids.decode(corpus_hashid)
|
||||
corpus = Corpus.query.get(corpus_id)
|
||||
if corpus is None:
|
||||
return {'options': {'status': 404, 'statusText': 'Not found'}}
|
||||
if not (
|
||||
corpus.is_public
|
||||
or corpus.user == current_user
|
||||
or current_user.is_administrator
|
||||
):
|
||||
return {'options': {'status': 403, 'statusText': 'Forbidden'}}
|
||||
return {
|
||||
'body': corpus.to_json_serializable(),
|
||||
'options': {
|
||||
'status': 200,
|
||||
'statusText': 'OK',
|
||||
'headers': {'Content-Type: application/json'}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@socketio.on('SUBSCRIBE /corpora/<corpus_id>')
|
||||
@socketio_login_required
|
||||
def subscribe_corpus(corpus_hashid):
|
||||
corpus_id = hashids.decode(corpus_hashid)
|
||||
corpus = Corpus.query.get(corpus_id)
|
||||
if corpus is None:
|
||||
return {'options': {'status': 404, 'statusText': 'Not found'}}
|
||||
if not (
|
||||
corpus.is_public
|
||||
or corpus.user == current_user
|
||||
or current_user.is_administrator
|
||||
):
|
||||
return {'options': {'status': 403, 'statusText': 'Forbidden'}}
|
||||
join_room(f'/corpora/{corpus.hashid}')
|
||||
return {'options': {'status': 200, 'statusText': 'OK'}}
|
@ -4,11 +4,17 @@ from . import bp
|
||||
|
||||
|
||||
@bp.app_errorhandler(HTTPException)
|
||||
def handle_http_exception(error):
|
||||
def handle_http_exception(e: HTTPException):
|
||||
''' Generic HTTP exception handler '''
|
||||
accept_json = request.accept_mimetypes.accept_json
|
||||
accept_html = request.accept_mimetypes.accept_html
|
||||
|
||||
if accept_json and not accept_html:
|
||||
response = jsonify(str(error))
|
||||
return response, error.code
|
||||
return render_template('errors/error.html.j2', error=error), error.code
|
||||
error = {
|
||||
'code': e.code,
|
||||
'name': e.name,
|
||||
'description': e.description
|
||||
}
|
||||
return jsonify(error), e.code
|
||||
|
||||
return render_template('errors/error.html.j2', error=e), e.code
|
||||
|
@ -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
|
@ -1,25 +1,49 @@
|
||||
from flask import (
|
||||
abort,
|
||||
current_app,
|
||||
Flask,
|
||||
jsonify,
|
||||
redirect,
|
||||
render_template,
|
||||
send_from_directory,
|
||||
url_for
|
||||
)
|
||||
from flask_login import current_user
|
||||
from app.models import Job, JobInput, JobResult
|
||||
from threading import Thread
|
||||
from app import db
|
||||
from app.models import Job, JobInput, JobResult, JobStatus
|
||||
from . import bp
|
||||
|
||||
|
||||
def _delete_job(app: Flask, job_id: int):
|
||||
with app.app_context():
|
||||
job = Job.query.get(job_id)
|
||||
job.delete()
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def _restart_job(app: Flask, job_id: int):
|
||||
with app.app_context():
|
||||
job = Job.query.get(job_id)
|
||||
job.restart()
|
||||
db.session.commit()
|
||||
|
||||
|
||||
@bp.route('')
|
||||
def jobs():
|
||||
return redirect(url_for('main.dashboard', _anchor='jobs'))
|
||||
|
||||
|
||||
@bp.route('/<hashid:job_id>')
|
||||
def job(job_id):
|
||||
def job(job_id: int):
|
||||
job = Job.query.get_or_404(job_id)
|
||||
if not (job.user == current_user or current_user.is_administrator):
|
||||
|
||||
if not (
|
||||
job.user == current_user
|
||||
or current_user.is_administrator
|
||||
):
|
||||
abort(403)
|
||||
|
||||
return render_template(
|
||||
'jobs/job.html.j2',
|
||||
title='Job',
|
||||
@ -27,11 +51,77 @@ def job(job_id):
|
||||
)
|
||||
|
||||
|
||||
@bp.route('/<hashid:job_id>/inputs/<hashid:job_input_id>/download')
|
||||
def download_job_input(job_id, job_input_id):
|
||||
job_input = JobInput.query.filter_by(job_id=job_id, id=job_input_id).first_or_404()
|
||||
if not (job_input.job.user == current_user or current_user.is_administrator):
|
||||
@bp.route('/<hashid:job_id>', methods=['DELETE'])
|
||||
def delete_job(job_id: int):
|
||||
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()
|
||||
|
||||
return jsonify(f'Job "{job.title}" marked for deletion.'), 202
|
||||
|
||||
|
||||
@bp.route('/<hashid:job_id>/log')
|
||||
def get_job_log(job_id: int):
|
||||
job = Job.query.get_or_404(job_id)
|
||||
|
||||
if not current_user.is_administrator:
|
||||
abort(403)
|
||||
|
||||
if job.status not in [JobStatus.COMPLETED, JobStatus.FAILED]:
|
||||
abort(409)
|
||||
|
||||
log_file_path = job.path / 'pipeline_data' / 'logs' / 'pyflow_log.txt'
|
||||
with log_file_path.open() as log_file:
|
||||
log = log_file.read()
|
||||
|
||||
return jsonify(log)
|
||||
|
||||
|
||||
@bp.route('/<hashid:job_id>/restart')
|
||||
def restart_job(job_id: int):
|
||||
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:
|
||||
abort(409)
|
||||
|
||||
thread = Thread(
|
||||
target=_restart_job,
|
||||
args=(current_app._get_current_object(), job.id)
|
||||
)
|
||||
thread.start()
|
||||
|
||||
return jsonify(f'Job "{job.title}" marked for restarting.'), 202
|
||||
|
||||
|
||||
@bp.route('/<hashid:job_id>/inputs/<hashid:job_input_id>/download')
|
||||
def download_job_input(job_id: int, job_input_id: int):
|
||||
job_input = JobInput.query.filter_by(
|
||||
job_id=job_id,
|
||||
id=job_input_id
|
||||
).first_or_404()
|
||||
|
||||
if not (
|
||||
job_input.job.user == current_user
|
||||
or current_user.is_administrator
|
||||
):
|
||||
abort(403)
|
||||
|
||||
return send_from_directory(
|
||||
job_input.path.parent,
|
||||
job_input.path.name,
|
||||
@ -42,10 +132,18 @@ def download_job_input(job_id, job_input_id):
|
||||
|
||||
|
||||
@bp.route('/<hashid:job_id>/results/<hashid:job_result_id>/download')
|
||||
def download_job_result(job_id, job_result_id):
|
||||
job_result = JobResult.query.filter_by(job_id=job_id, id=job_result_id).first_or_404()
|
||||
if not (job_result.job.user == current_user or current_user.is_administrator):
|
||||
def download_job_result(job_id: int, job_result_id: int):
|
||||
job_result = JobResult.query.filter_by(
|
||||
job_id=job_id,
|
||||
id=job_result_id
|
||||
).first_or_404()
|
||||
|
||||
if not (
|
||||
job_result.job.user == current_user
|
||||
or current_user.is_administrator
|
||||
):
|
||||
abort(403)
|
||||
|
||||
return send_from_directory(
|
||||
job_result.path.parent,
|
||||
job_result.path.name,
|
||||
|
215
app/namespaces/corpora.py
Normal file
215
app/namespaces/corpora.py
Normal file
@ -0,0 +1,215 @@
|
||||
from datetime import datetime
|
||||
from flask import current_app, Flask, url_for
|
||||
from flask_login import current_user
|
||||
from flask_socketio import Namespace
|
||||
from string import punctuation
|
||||
import nltk
|
||||
from app import db, hashids, socketio
|
||||
from app.decorators import socketio_login_required
|
||||
from app.models import Corpus, CorpusFollowerAssociation, CorpusFollowerRole
|
||||
|
||||
|
||||
def _delete_corpus(app: Flask, corpus_id: int):
|
||||
with app.app_context():
|
||||
corpus = Corpus.query.get(corpus_id)
|
||||
corpus.delete()
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def _build_corpus(app: Flask, corpus_id: int):
|
||||
with app.app_context():
|
||||
corpus = Corpus.query.get(corpus_id)
|
||||
corpus.build()
|
||||
db.session.commit()
|
||||
|
||||
|
||||
class CorporaNamespace(Namespace):
|
||||
@socketio_login_required
|
||||
def on_delete(self, corpus_hashid: str) -> dict:
|
||||
if not isinstance(corpus_hashid, str):
|
||||
return {'status': 400, 'statusText': 'Bad Request'}
|
||||
|
||||
corpus_id = hashids.decode(corpus_hashid)
|
||||
|
||||
if not isinstance(corpus_id, int):
|
||||
return {'status': 400, 'statusText': 'Bad Request'}
|
||||
|
||||
corpus = Corpus.query.get(corpus_id)
|
||||
|
||||
if corpus is None:
|
||||
return {'status': 404, 'statusText': 'Not Found'}
|
||||
|
||||
if not (
|
||||
corpus.user == current_user
|
||||
or current_user.is_administrator
|
||||
):
|
||||
return {'status': 403, 'statusText': 'Forbidden'}
|
||||
|
||||
socketio.start_background_task(
|
||||
_delete_corpus,
|
||||
current_app._get_current_object(),
|
||||
corpus_id
|
||||
)
|
||||
|
||||
return {
|
||||
'body': f'Corpus "{corpus.title}" marked for deletion',
|
||||
'status': 202,
|
||||
'statusText': 'Accepted'
|
||||
}
|
||||
|
||||
@socketio_login_required
|
||||
def on_build(self, corpus_hashid: str) -> dict:
|
||||
if not isinstance(corpus_hashid, str):
|
||||
return {'status': 400, 'statusText': 'Bad Request'}
|
||||
|
||||
corpus_id = hashids.decode(corpus_hashid)
|
||||
|
||||
if not isinstance(corpus_id, int):
|
||||
return {'status': 400, 'statusText': 'Bad Request'}
|
||||
|
||||
corpus = Corpus.query.get(corpus_id)
|
||||
|
||||
if corpus is None:
|
||||
return {'status': 404, 'statusText': 'Not Found'}
|
||||
|
||||
cfa = CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=current_user.id).first()
|
||||
if not (
|
||||
cfa is not None and cfa.role.has_permission('MANAGE_FILES')
|
||||
or corpus.user == current_user
|
||||
or current_user.is_administrator
|
||||
):
|
||||
return {'status': 403, 'statusText': 'Forbidden'}
|
||||
|
||||
if len(corpus.files.all()) == 0:
|
||||
return {'status': 409, 'statusText': 'Conflict'}
|
||||
|
||||
socketio.start_background_task(
|
||||
_build_corpus,
|
||||
current_app._get_current_object(),
|
||||
corpus_id
|
||||
)
|
||||
|
||||
return {
|
||||
'body': f'Corpus "{corpus.title}" marked for building',
|
||||
'status': 202,
|
||||
'statusText': 'Accepted'
|
||||
}
|
||||
|
||||
# TODO: Think about where to place this, as this does not belong here...
|
||||
@socketio_login_required
|
||||
def on_get_stopwords(self):
|
||||
languages = [
|
||||
'german',
|
||||
'english',
|
||||
'catalan',
|
||||
'greek',
|
||||
'spanish',
|
||||
'french',
|
||||
'italian',
|
||||
'russian',
|
||||
'chinese'
|
||||
]
|
||||
|
||||
nltk.download('stopwords', quiet=True)
|
||||
stopwords = {
|
||||
language: nltk.corpus.stopwords.words(language)
|
||||
for language in languages
|
||||
}
|
||||
stopwords['punctuation'] = list(punctuation)
|
||||
stopwords['punctuation'] += ['—', '|', '–', '“', '„', '--']
|
||||
stopwords['user_stopwords'] = []
|
||||
|
||||
return {
|
||||
'body': stopwords,
|
||||
'status': 200,
|
||||
'statusText': 'OK'
|
||||
}
|
||||
|
||||
@socketio_login_required
|
||||
def on_create_share_link(self, corpus_hashid: str, expiration_date: str, role_name: str) -> dict:
|
||||
if not isinstance(corpus_hashid, str):
|
||||
return {'status': 400, 'statusText': 'Bad Request'}
|
||||
|
||||
if not isinstance(expiration_date, str):
|
||||
return {'status': 400, 'statusText': 'Bad Request'}
|
||||
|
||||
if not isinstance(role_name, str):
|
||||
return {'status': 400, 'statusText': 'Bad Request'}
|
||||
|
||||
print(corpus_hashid, expiration_date, role_name)
|
||||
|
||||
corpus_id = hashids.decode(corpus_hashid)
|
||||
|
||||
if not isinstance(corpus_id, int):
|
||||
return {'status': 400, 'statusText': 'Bad Request'}
|
||||
|
||||
corpus = Corpus.query.get(corpus_id)
|
||||
|
||||
if corpus is None:
|
||||
return {'status': 404, 'statusText': 'Not Found'}
|
||||
|
||||
cfa = CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=current_user.id).first()
|
||||
if not (
|
||||
cfa is not None and cfa.role.has_permission('MANAGE_FOLLOWERS')
|
||||
or corpus.user == current_user
|
||||
or current_user.is_administrator
|
||||
):
|
||||
return {'status': 403, 'statusText': 'Forbidden'}
|
||||
|
||||
_expiration_date = datetime.strptime(expiration_date, '%b %d, %Y')
|
||||
|
||||
cfr = CorpusFollowerRole.query.filter_by(name=role_name).first()
|
||||
if cfr is None:
|
||||
return {'status': 400, 'statusText': 'Bad Request'}
|
||||
|
||||
token = current_user.generate_follow_corpus_token(
|
||||
corpus.hashid,
|
||||
role_name,
|
||||
_expiration_date
|
||||
)
|
||||
|
||||
corpus_share_link = url_for(
|
||||
'corpora.follow_corpus',
|
||||
corpus_id=corpus_id,
|
||||
token=token,
|
||||
_external=True
|
||||
)
|
||||
|
||||
return {
|
||||
'body': corpus_share_link,
|
||||
'status': 200,
|
||||
'statusText': 'OK'
|
||||
}
|
||||
|
||||
@socketio_login_required
|
||||
def on_set_is_public(corpus_hashid: str, new_value: bool) -> dict:
|
||||
if not isinstance(corpus_id, str):
|
||||
return {'status': 400, 'statusText': 'Bad Request'}
|
||||
|
||||
if not isinstance(new_value, bool):
|
||||
return {'status': 400, 'statusText': 'Bad Request'}
|
||||
|
||||
corpus_id = hashids.decode(corpus_hashid)
|
||||
|
||||
if not isinstance(corpus_id, int):
|
||||
return {'status': 400, 'statusText': 'Bad Request'}
|
||||
|
||||
corpus = Corpus.query.get(corpus_id)
|
||||
|
||||
if corpus is None:
|
||||
return {'status': 404, 'statusText': 'Not Found'}
|
||||
|
||||
if not (
|
||||
corpus.user == current_user
|
||||
or current_user.is_administrator
|
||||
):
|
||||
return {'status': 403, 'statusText': 'Forbidden'}
|
||||
|
||||
corpus.is_public = new_value
|
||||
db.session.commit()
|
||||
|
||||
return {
|
||||
'body': f'Corpus "{corpus.title}" is now {"public" if new_value else "private"}',
|
||||
'status': 200,
|
||||
'statusText': 'OK'
|
||||
}
|
@ -169,6 +169,7 @@ class CQiOverSocketIONamespace(Namespace):
|
||||
|
||||
for param in signature(fn).parameters.values():
|
||||
# Check if the parameter is optional or required
|
||||
# The following is true for required parameters
|
||||
if param.default is param.empty:
|
||||
if param.name not in fn_args:
|
||||
return {'code': 400, 'msg': 'Bad Request'}
|
||||
|
@ -1,109 +0,0 @@
|
||||
from flask import current_app, Flask
|
||||
from flask_login import current_user
|
||||
from flask_socketio import Namespace
|
||||
from app import db, hashids, socketio
|
||||
from app.decorators import socketio_admin_required, socketio_login_required
|
||||
from app.models import Job, JobStatus
|
||||
|
||||
|
||||
def _delete_job(app: Flask, job_id: int):
|
||||
with app.app_context():
|
||||
job = Job.query.get(job_id)
|
||||
job.delete()
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def _restart_job(app, job_id):
|
||||
with app.app_context():
|
||||
job = Job.query.get(job_id)
|
||||
job.restart()
|
||||
db.session.commit()
|
||||
|
||||
|
||||
class UsersNamespace(Namespace):
|
||||
@socketio_login_required
|
||||
def on_delete(self, job_hashid: str) -> dict:
|
||||
job_id = hashids.decode(job_hashid)
|
||||
|
||||
if not isinstance(job_id, int):
|
||||
return {'status': 400, 'statusText': 'Bad Request'}
|
||||
|
||||
job = Job.query.get(job_id)
|
||||
|
||||
if job is None:
|
||||
return {'status': 404, 'statusText': 'Not found'}
|
||||
|
||||
if not (
|
||||
job.user == current_user
|
||||
or current_user.is_administrator
|
||||
):
|
||||
return {'status': 403, 'statusText': 'Forbidden'}
|
||||
|
||||
socketio.start_background_task(
|
||||
_delete_job,
|
||||
current_app._get_current_object(),
|
||||
job_id
|
||||
)
|
||||
|
||||
return {
|
||||
'body': f'Job "{job.title}" marked for deletion',
|
||||
'status': 202,
|
||||
'statusText': 'Accepted'
|
||||
}
|
||||
|
||||
@socketio_admin_required
|
||||
def on_log(self, job_hashid: str):
|
||||
job_id = hashids.decode(job_hashid)
|
||||
|
||||
if not isinstance(job_id, int):
|
||||
return {'status': 400, 'statusText': 'Bad Request'}
|
||||
|
||||
job = Job.query.get(job_id)
|
||||
|
||||
if job is None:
|
||||
return {'status': 404, 'statusText': 'Not found'}
|
||||
|
||||
if job.status not in [JobStatus.COMPLETED, JobStatus.FAILED]:
|
||||
return {'status': 409, 'statusText': 'Conflict'}
|
||||
|
||||
with open(job.path / 'pipeline_data' / 'logs' / 'pyflow_log.txt') as log_file:
|
||||
log = log_file.read()
|
||||
|
||||
return {
|
||||
'body': log,
|
||||
'status': 200,
|
||||
'statusText': 'Forbidden'
|
||||
}
|
||||
|
||||
socketio_login_required
|
||||
def on_restart(self, job_hashid: str):
|
||||
job_id = hashids.decode(job_hashid)
|
||||
|
||||
if not isinstance(job_id, int):
|
||||
return {'status': 400, 'statusText': 'Bad Request'}
|
||||
|
||||
job = Job.query.get(job_id)
|
||||
|
||||
if job is None:
|
||||
return {'status': 404, 'statusText': 'Not found'}
|
||||
|
||||
if not (
|
||||
job.user == current_user
|
||||
or current_user.is_administrator
|
||||
):
|
||||
return {'status': 403, 'statusText': 'Forbidden'}
|
||||
|
||||
if job.status == JobStatus.FAILED:
|
||||
return {'status': 409, 'statusText': 'Conflict'}
|
||||
|
||||
socketio.start_background_task(
|
||||
_restart_job,
|
||||
current_app._get_current_object(),
|
||||
job_id
|
||||
)
|
||||
|
||||
return {
|
||||
'body': f'Job "{job.title}" marked for restarting',
|
||||
'status': 202,
|
||||
'statusText': 'Accepted'
|
||||
}
|
@ -16,6 +16,9 @@ def _delete_user(app: Flask, user_id: int):
|
||||
class UsersNamespace(Namespace):
|
||||
@socketio_login_required
|
||||
def on_get(self, user_hashid: str) -> dict:
|
||||
if not isinstance(user_hashid, str):
|
||||
return {'status': 400, 'statusText': 'Bad Request'}
|
||||
|
||||
user_id = hashids.decode(user_hashid)
|
||||
|
||||
if not isinstance(user_id, int):
|
||||
@ -24,7 +27,7 @@ class UsersNamespace(Namespace):
|
||||
user = User.query.get(user_id)
|
||||
|
||||
if user is None:
|
||||
return {'status': 404, 'statusText': 'Not found'}
|
||||
return {'status': 404, 'statusText': 'Not Found'}
|
||||
|
||||
if not (
|
||||
user == current_user
|
||||
@ -43,6 +46,9 @@ class UsersNamespace(Namespace):
|
||||
|
||||
@socketio_login_required
|
||||
def on_subscribe(self, user_hashid: str) -> dict:
|
||||
if not isinstance(user_hashid, str):
|
||||
return {'status': 400, 'statusText': 'Bad Request'}
|
||||
|
||||
user_id = hashids.decode(user_hashid)
|
||||
|
||||
if not isinstance(user_id, int):
|
||||
@ -51,7 +57,7 @@ class UsersNamespace(Namespace):
|
||||
user = User.query.get(user_id)
|
||||
|
||||
if user is None:
|
||||
return {'status': 404, 'statusText': 'Not found'}
|
||||
return {'status': 404, 'statusText': 'Not Found'}
|
||||
|
||||
if not (
|
||||
user == current_user
|
||||
@ -65,6 +71,9 @@ class UsersNamespace(Namespace):
|
||||
|
||||
@socketio_login_required
|
||||
def on_unsubscribe(self, user_hashid: str) -> dict:
|
||||
if not isinstance(user_hashid, str):
|
||||
return {'status': 400, 'statusText': 'Bad Request'}
|
||||
|
||||
user_id = hashids.decode(user_hashid)
|
||||
|
||||
if not isinstance(user_id, int):
|
||||
@ -73,7 +82,7 @@ class UsersNamespace(Namespace):
|
||||
user = User.query.get(user_id)
|
||||
|
||||
if user is None:
|
||||
return {'status': 404, 'statusText': 'Not found'}
|
||||
return {'status': 404, 'statusText': 'Not Found'}
|
||||
|
||||
if not (
|
||||
user == current_user
|
||||
@ -87,6 +96,9 @@ class UsersNamespace(Namespace):
|
||||
|
||||
@socketio_login_required
|
||||
def on_delete(self, user_hashid: str) -> dict:
|
||||
if not isinstance(user_hashid, str):
|
||||
return {'status': 400, 'statusText': 'Bad Request'}
|
||||
|
||||
user_id = hashids.decode(user_hashid)
|
||||
|
||||
if not isinstance(user_id, int):
|
||||
@ -95,7 +107,7 @@ class UsersNamespace(Namespace):
|
||||
user = User.query.get(user_id)
|
||||
|
||||
if user is None:
|
||||
return {'status': 404, 'statusText': 'Not found'}
|
||||
return {'status': 404, 'statusText': 'Not Found'}
|
||||
|
||||
if not (
|
||||
user == current_user
|
||||
|
@ -1,8 +1,10 @@
|
||||
nopaque.App = class App {
|
||||
nopaque.app.Client = class Client {
|
||||
constructor() {
|
||||
this.socket = io({transports: ['websocket'], upgrade: false});
|
||||
|
||||
// Endpoints
|
||||
this.corpora = new nopaque.app.endpoints.Corpora(this);
|
||||
this.jobs = new nopaque.app.endpoints.Jobs(this);
|
||||
this.users = new nopaque.app.endpoints.Users(this);
|
||||
|
||||
// Extensions
|
57
app/static/js/app/endpoints/corpora.js
Normal file
57
app/static/js/app/endpoints/corpora.js
Normal file
@ -0,0 +1,57 @@
|
||||
nopaque.app.endpoints.Corpora = class Corpora {
|
||||
constructor(app) {
|
||||
this.app = app;
|
||||
|
||||
this.socket = io('/corpora', {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 build(id) {
|
||||
const response = await this.socket.emitWithAck('build', id);
|
||||
|
||||
if (response.status != 202) {
|
||||
throw new Error(`[${response.status}] ${response.statusText}`);
|
||||
}
|
||||
|
||||
return response.body;
|
||||
}
|
||||
|
||||
async getStopwords() {
|
||||
const response = await this.socket.emitWithAck('get_stopwords');
|
||||
|
||||
if (response.status != 200) {
|
||||
throw new Error(`[${response.status}] ${response.statusText}`);
|
||||
}
|
||||
|
||||
return response.body;
|
||||
}
|
||||
|
||||
async createShareLink(id, expirationDate, roleName) {
|
||||
const response = await this.socket.emitWithAck('create_share_link', id, expirationDate, roleName);
|
||||
|
||||
if (response.status != 200) {
|
||||
throw new Error(`[${response.status}] ${response.statusText}`);
|
||||
}
|
||||
|
||||
return response.body;
|
||||
}
|
||||
|
||||
async setIsPublic(id, newValue) {
|
||||
const response = await this.socket.emitWithAck('set_is_public', id, newValue);
|
||||
|
||||
if (response.status != 200) {
|
||||
throw new Error(`[${response.status}] ${response.statusText}`);
|
||||
}
|
||||
|
||||
return response.body;
|
||||
}
|
||||
}
|
47
app/static/js/app/endpoints/jobs.js
Normal file
47
app/static/js/app/endpoints/jobs.js
Normal file
@ -0,0 +1,47 @@
|
||||
nopaque.app.endpoints.Jobs = class Jobs {
|
||||
async delete(jobId) {
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json'
|
||||
},
|
||||
method: 'DELETE'
|
||||
};
|
||||
|
||||
const response = await fetch(`/jobs/${jobId}`, options);
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {throw new Error(`${data.name}: ${data.description}`);}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async log(jobId) {
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
const response = await fetch(`/jobs/${jobId}/log`, options);
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {throw new Error(`${data.name}: ${data.description}`);}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async restart(jobId) {
|
||||
const options = {
|
||||
headers: {
|
||||
Accept: 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
const response = await fetch(`/jobs/${jobId}/restart`, options);
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {throw new Error(`${data.name}: ${data.description}`);}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ nopaque.corpus_analysis.StaticVisualizationExtension = class StaticVisualization
|
||||
stopwords: undefined,
|
||||
originalStopwords: {},
|
||||
stopwordCache: {},
|
||||
promises: {getStopwords: undefined},
|
||||
tokenSet: new Set()
|
||||
};
|
||||
|
||||
@ -73,22 +72,11 @@ nopaque.corpus_analysis.StaticVisualizationExtension = class StaticVisualization
|
||||
}
|
||||
}
|
||||
|
||||
getStopwords() {
|
||||
this.data.promises.getStopwords = new Promise((resolve, reject) => {
|
||||
nopaque.requests.corpora.entity.getStopwords()
|
||||
.then((response) => {
|
||||
response.json()
|
||||
.then((json) => {
|
||||
this.data.originalStopwords = structuredClone(json);
|
||||
this.data.stopwords = structuredClone(json);
|
||||
resolve(this.data.stopwords);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
return this.data.promises.getStopwords;
|
||||
async getStopwords() {
|
||||
const stopwords = await app.corpora.getStopwords();
|
||||
this.data.originalStopwords = structuredClone(stopwords);
|
||||
this.data.stopwords = structuredClone(stopwords);
|
||||
return stopwords;
|
||||
}
|
||||
|
||||
renderGeneralCorpusInfo() {
|
||||
|
@ -5,50 +5,6 @@ nopaque.requests.corpora = {};
|
||||
|
||||
nopaque.requests.corpora.entity = {};
|
||||
|
||||
nopaque.requests.corpora.entity.delete = (corpusId) => {
|
||||
let input = `/corpora/${corpusId}`;
|
||||
let init = {
|
||||
method: 'DELETE'
|
||||
};
|
||||
return nopaque.requests.JSONfetch(input, init);
|
||||
};
|
||||
|
||||
nopaque.requests.corpora.entity.build = (corpusId) => {
|
||||
let input = `/corpora/${corpusId}/build`;
|
||||
let init = {
|
||||
method: 'POST',
|
||||
};
|
||||
return nopaque.requests.JSONfetch(input, init);
|
||||
};
|
||||
|
||||
nopaque.requests.corpora.entity.generateShareLink = (corpusId, role, expiration) => {
|
||||
let input = `/corpora/${corpusId}/generate-share-link`;
|
||||
let init = {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({role: role, expiration: expiration})
|
||||
};
|
||||
return nopaque.requests.JSONfetch(input, init);
|
||||
};
|
||||
|
||||
nopaque.requests.corpora.entity.getStopwords = () => {
|
||||
let input = `/corpora/stopwords`;
|
||||
let init = {
|
||||
method: 'GET'
|
||||
};
|
||||
return nopaque.requests.JSONfetch(input, init);
|
||||
};
|
||||
|
||||
nopaque.requests.corpora.entity.isPublic = {};
|
||||
|
||||
nopaque.requests.corpora.entity.isPublic.update = (corpusId, isPublic) => {
|
||||
let input = `/corpora/${corpusId}/is_public`;
|
||||
let init = {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(isPublic)
|
||||
};
|
||||
return nopaque.requests.JSONfetch(input, init);
|
||||
};
|
||||
|
||||
|
||||
/*****************************************************************************
|
||||
* Requests for /corpora/<entity>/files routes *
|
||||
|
@ -1,30 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Requests for /jobs routes *
|
||||
*****************************************************************************/
|
||||
nopaque.requests.jobs = {};
|
||||
|
||||
nopaque.requests.jobs.entity = {};
|
||||
|
||||
nopaque.requests.jobs.entity.delete = (jobId) => {
|
||||
let input = `/jobs/${jobId}`;
|
||||
let init = {
|
||||
method: 'DELETE'
|
||||
};
|
||||
return nopaque.requests.JSONfetch(input, init);
|
||||
};
|
||||
|
||||
nopaque.requests.jobs.entity.log = (jobId) => {
|
||||
let input = `/jobs/${jobId}/log`;
|
||||
let init = {
|
||||
method: 'GET'
|
||||
};
|
||||
return nopaque.requests.JSONfetch(input, init);
|
||||
};
|
||||
|
||||
nopaque.requests.jobs.entity.restart = (jobId) => {
|
||||
let input = `/jobs/${jobId}/restart`;
|
||||
let init = {
|
||||
method: 'POST'
|
||||
};
|
||||
return nopaque.requests.JSONfetch(input, init);
|
||||
};
|
@ -7,7 +7,7 @@ nopaque.resource_displays.CorpusDisplay = class CorpusDisplay extends nopaque.re
|
||||
this.displayElement
|
||||
.querySelector('.action-button[data-action="build-request"]')
|
||||
.addEventListener('click', (event) => {
|
||||
nopaque.requests.corpora.entity.build(this.corpusId);
|
||||
app.corpora.build(this.corpusId);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -180,7 +180,7 @@ nopaque.resource_lists.CorpusList = class CorpusList extends nopaque.resource_li
|
||||
window.location.reload();
|
||||
});
|
||||
} else {
|
||||
nopaque.requests.corpora.entity.delete(itemId);
|
||||
app.corpora.delete(itemId);
|
||||
}
|
||||
});
|
||||
modal.open();
|
||||
@ -276,7 +276,7 @@ nopaque.resource_lists.CorpusList = class CorpusList extends nopaque.resource_li
|
||||
let listItem = this.listjs.get('id', selectedItemId)[0].elm;
|
||||
let values = this.listjs.get('id', listItem.dataset.id)[0].values();
|
||||
if (values['is-owner']) {
|
||||
nopaque.requests.corpora.entity.delete(selectedItemId);
|
||||
app.corpora.delete(selectedItemId);
|
||||
} else {
|
||||
nopaque.requests.corpora.entity.followers.entity.delete(selectedItemId, currentUserId);
|
||||
setTimeout(() => {
|
||||
|
@ -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();
|
||||
|
@ -56,5 +56,24 @@
|
||||
Log out
|
||||
</a>
|
||||
</li>
|
||||
{% if current_user.can('USE_API') or current_user.is_administrator %}
|
||||
<li class="divider" tabindex="-1"></li>
|
||||
{% endif %}
|
||||
{% if current_user.can('USE_API') %}
|
||||
<li>
|
||||
<a href="{{ url_for('apifairy.docs') }}">
|
||||
<i class="material-icons left">api</i>
|
||||
API
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if current_user.is_administrator %}
|
||||
<li {% if request.path == url_for('admin.admin') %}class="active"{% endif %}>
|
||||
<a href="{{ url_for('admin.admin') }}">
|
||||
<i class="material-icons left">admin_panel_settings</i>
|
||||
Administration
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
@ -8,9 +8,11 @@
|
||||
filters='rjsmin',
|
||||
output='gen/nopaque.%(version)s.js',
|
||||
'js/index.js',
|
||||
'js/app.js',
|
||||
'js/app/index.js',
|
||||
'js/app/client.js',
|
||||
'js/app/endpoints/index.js',
|
||||
'js/app/endpoints/corpora.js',
|
||||
'js/app/endpoints/jobs.js',
|
||||
'js/app/endpoints/users.js',
|
||||
'js/app/extensions/index.js',
|
||||
'js/app/extensions/toaster.js',
|
||||
@ -50,7 +52,6 @@
|
||||
'js/requests/admin.js',
|
||||
'js/requests/contributions.js',
|
||||
'js/requests/corpora.js',
|
||||
'js/requests/jobs.js',
|
||||
'js/requests/users.js',
|
||||
|
||||
'js/corpus-analysis/index.js',
|
||||
@ -79,9 +80,9 @@
|
||||
<script src="{{ ASSET_URL }}"></script>
|
||||
{% endassets -%}
|
||||
|
||||
{# TODO: Think about implementing the following inside a main.js(.j2) #}
|
||||
<script>
|
||||
// TODO: Implement an app.run method and use this for all of the following
|
||||
const app = new nopaque.App();
|
||||
const app = new nopaque.app.Client();
|
||||
app.init();
|
||||
|
||||
{% if current_user.is_authenticated %}
|
||||
|
@ -105,19 +105,21 @@
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if current_user.is_authenticated and current_user.can('ADMINISTRATE') %}
|
||||
{# administration section #}
|
||||
{% if current_user.can('USE_API') or current_user.is_administrator %}
|
||||
<li><div class="divider"></div></li>
|
||||
<li><a class="subheader">Administration</a></li>
|
||||
{% endif %}
|
||||
|
||||
{# corpora #}
|
||||
{% if current_user.can('USE_API') %}
|
||||
{# API #}
|
||||
<li>
|
||||
<a class="waves-effect" href="{{ url_for('admin.corpora') }}"><i class="nopaque-icons">I</i>Corpora</a>
|
||||
<a class="waves-effect" href="{{ url_for('apifairy.docs') }}"><i class="material-icons">api</i>API</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{# users #}
|
||||
<li>
|
||||
<a class="waves-effect" href="{{ url_for('admin.users') }}"><i class="material-icons">manage_accounts</i>Users</a>
|
||||
{% if current_user.is_administrator %}
|
||||
{# Administration #}
|
||||
<li {% if request.path == url_for('admin.admin') %}class="active"{% endif %}>
|
||||
<a class="waves-effect" href="{{ url_for('admin.admin') }}"><i class="material-icons">admin_panel_settings</i>Administration</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
@ -246,7 +246,7 @@
|
||||
let publishingModalIsPublicSwitchElement = document.querySelector('#publishing-modal-is-public-switch');
|
||||
publishingModalIsPublicSwitchElement.addEventListener('change', (event) => {
|
||||
let newIsPublic = publishingModalIsPublicSwitchElement.checked;
|
||||
nopaque.requests.corpora.entity.isPublic.update({{ corpus.hashid|tojson }}, newIsPublic)
|
||||
app.corpora.setIsPublic.update({{ corpus.hashid|tojson }}, newIsPublic)
|
||||
.catch((response) => {
|
||||
publishingModalIsPublicSwitchElement.checked = !newIsPublic;
|
||||
});
|
||||
@ -256,7 +256,7 @@ publishingModalIsPublicSwitchElement.addEventListener('change', (event) => {
|
||||
// #region Delete
|
||||
let deleteModalDeleteButtonElement = document.querySelector('#delete-modal-delete-button');
|
||||
deleteModalDeleteButtonElement.addEventListener('click', (event) => {
|
||||
nopaque.requests.corpora.entity.delete({{ corpus.hashid|tojson }})
|
||||
app.corpora.delete({{ corpus.hashid|tojson }})
|
||||
.then((response) => {
|
||||
window.location.href = {{ url_for('main.dashboard')|tojson }};
|
||||
});
|
||||
@ -346,19 +346,14 @@ M.Modal.init(
|
||||
shareLinkModalOutputContainerElement.classList.add('hide');
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
shareLinkModalCreateButtonElement.addEventListener('click', (event) => {
|
||||
let role = shareLinkModalCorpusFollowerRoleSelectElement.value;
|
||||
let expiration = shareLinkModalExpirationDateDatepickerElement.value
|
||||
nopaque.requests.corpora.entity.generateShareLink({{ corpus.hashid|tojson }}, role, expiration)
|
||||
.then((response) => {
|
||||
response.json()
|
||||
.then((json) => {
|
||||
shareLinkModalCreateButtonElement.addEventListener('click', async (event) => {
|
||||
const role = shareLinkModalCorpusFollowerRoleSelectElement.value;
|
||||
const expiration = shareLinkModalExpirationDateDatepickerElement.value;
|
||||
const shareLink = await app.corpora.createShareLink({{ corpus.hashid|tojson }}, expiration, role);
|
||||
shareLinkModalOutputContainerElement.classList.remove('hide');
|
||||
shareLinkModalOutputFieldElement.value = json.corpusShareLink;
|
||||
});
|
||||
});
|
||||
shareLinkModalOutputFieldElement.value = shareLink;
|
||||
});
|
||||
|
||||
shareLinkModalOutputCopyButtonElement.addEventListener('click', (event) => {
|
||||
|
@ -273,7 +273,7 @@ publicCorpusFollowerList.add(
|
||||
{% if cfr.has_permission('MANAGE_FILES') %}
|
||||
let followerBuildRequest = document.querySelector('#follower-build-request');
|
||||
followerBuildRequest.addEventListener('click', () => {
|
||||
nopaque.requests.corpora.entity.build({{ corpus.hashid|tojson }})
|
||||
app.corpora.build({{ corpus.hashid|tojson }})
|
||||
.then((response) => {
|
||||
window.location.reload();
|
||||
});
|
||||
@ -380,17 +380,12 @@ M.Modal.init(
|
||||
}
|
||||
)
|
||||
|
||||
shareLinkModalCreateButtonElement.addEventListener('click', (event) => {
|
||||
let role = shareLinkModalCorpusFollowerRoleSelectElement.value;
|
||||
let expiration = shareLinkModalExpirationDateDatepickerElement.value
|
||||
nopaque.requests.corpora.entity.generateShareLink({{ corpus.hashid|tojson }}, role, expiration)
|
||||
.then((response) => {
|
||||
response.json()
|
||||
.then((json) => {
|
||||
shareLinkModalCreateButtonElement.addEventListener('click', async (event) => {
|
||||
const roleName = shareLinkModalCorpusFollowerRoleSelectElement.value;
|
||||
const expirationDate = shareLinkModalExpirationDateDatepickerElement.value;
|
||||
const shareLink = await app.corpora.createShareLink({{ corpus.hashid|tojson }}, expiration, role);
|
||||
shareLinkModalOutputContainerElement.classList.remove('hide');
|
||||
shareLinkModalOutputFieldElement.value = json.corpusShareLink;
|
||||
});
|
||||
});
|
||||
shareLinkModalOutputFieldElement.value = shareLink;
|
||||
});
|
||||
|
||||
shareLinkModalOutputCopyButtonElement.addEventListener('click', (event) => {
|
||||
|
@ -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 }});
|
||||
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', (event) => {
|
||||
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 }}) {
|
||||
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');
|
||||
const jobLogModalElement = document.querySelector('#job-log-modal');
|
||||
|
||||
jobLogButtonElement.addEventListener('click', async (event) => {
|
||||
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