mirror of
https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
synced 2024-12-27 03:44:19 +00:00
Compare commits
No commits in common. "df2bffe0fd28c122fb20056b34f3058e282ca02f" and "12a3ac1d5dae0829f20b3660086c7b96d6437a1a" have entirely different histories.
df2bffe0fd
...
12a3ac1d5d
@ -132,9 +132,6 @@ def create_app(config: Config = Config) -> Flask:
|
|||||||
# region SocketIO Namespaces
|
# region SocketIO Namespaces
|
||||||
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.users import UsersNamespace
|
|
||||||
socketio.on_namespace(UsersNamespace('/users'))
|
|
||||||
# endregion SocketIO Namespaces
|
# endregion SocketIO Namespaces
|
||||||
|
|
||||||
# region Database event Listeners
|
# region Database event Listeners
|
||||||
|
@ -15,4 +15,4 @@ def before_request():
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
from . import cli, json_routes, routes, settings
|
from . import cli, events, json_routes, routes, settings
|
||||||
|
122
app/blueprints/users/events.py
Normal file
122
app/blueprints/users/events.py
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
from flask import current_app, Flask
|
||||||
|
from flask_login import current_user
|
||||||
|
from flask_socketio import join_room, leave_room
|
||||||
|
from app import db, hashids, socketio
|
||||||
|
from app.decorators import socketio_login_required
|
||||||
|
from app.models import User
|
||||||
|
|
||||||
|
|
||||||
|
def _delete_user(app: Flask, user_id: int):
|
||||||
|
with app.app_context():
|
||||||
|
user = User.query.get(user_id)
|
||||||
|
user.delete()
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
@socketio.on('users.delete')
|
||||||
|
@socketio_login_required
|
||||||
|
def delete_user(user_hashid: str) -> dict:
|
||||||
|
user_id = hashids.decode(user_hashid)
|
||||||
|
|
||||||
|
if not isinstance(user_id, int):
|
||||||
|
return {'status': 400, 'statusText': 'Bad Request'}
|
||||||
|
|
||||||
|
user = User.query.get(user_id)
|
||||||
|
|
||||||
|
if user is None:
|
||||||
|
return {'status': 404, 'statusText': 'Not found'}
|
||||||
|
|
||||||
|
if not (
|
||||||
|
user == current_user
|
||||||
|
or current_user.is_administrator
|
||||||
|
):
|
||||||
|
return {'status': 403, 'statusText': 'Forbidden'}
|
||||||
|
|
||||||
|
socketio.start_background_task(
|
||||||
|
_delete_user,
|
||||||
|
current_app._get_current_object(),
|
||||||
|
user.id
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'body': f'User "{user.username}" marked for deletion',
|
||||||
|
'status': 202,
|
||||||
|
'statusText': 'Accepted'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@socketio.on('users.get')
|
||||||
|
@socketio_login_required
|
||||||
|
def get_user(user_hashid: str) -> dict:
|
||||||
|
user_id = hashids.decode(user_hashid)
|
||||||
|
|
||||||
|
if not isinstance(user_id, int):
|
||||||
|
return {'status': 400, 'statusText': 'Bad Request'}
|
||||||
|
|
||||||
|
user = User.query.get(user_id)
|
||||||
|
|
||||||
|
if user is None:
|
||||||
|
return {'status': 404, 'statusText': 'Not found'}
|
||||||
|
|
||||||
|
if not (
|
||||||
|
user == current_user
|
||||||
|
or current_user.is_administrator
|
||||||
|
):
|
||||||
|
return {'status': 403, 'statusText': 'Forbidden'}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'body': user.to_json_serializeable(
|
||||||
|
backrefs=True,
|
||||||
|
relationships=True
|
||||||
|
),
|
||||||
|
'status': 200,
|
||||||
|
'statusText': 'OK'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@socketio.on('users.subscribe')
|
||||||
|
@socketio_login_required
|
||||||
|
def subscribe_user(user_hashid: str) -> dict:
|
||||||
|
user_id = hashids.decode(user_hashid)
|
||||||
|
|
||||||
|
if not isinstance(user_id, int):
|
||||||
|
return {'status': 400, 'statusText': 'Bad Request'}
|
||||||
|
|
||||||
|
user = User.query.get(user_id)
|
||||||
|
|
||||||
|
if user is None:
|
||||||
|
return {'status': 404, 'statusText': 'Not found'}
|
||||||
|
|
||||||
|
if not (
|
||||||
|
user == current_user
|
||||||
|
or current_user.is_administrator
|
||||||
|
):
|
||||||
|
return {'status': 403, 'statusText': 'Forbidden'}
|
||||||
|
|
||||||
|
join_room(f'/users/{user.hashid}')
|
||||||
|
|
||||||
|
return {'status': 200, 'statusText': 'OK'}
|
||||||
|
|
||||||
|
|
||||||
|
@socketio.on('users.unsubscribe')
|
||||||
|
@socketio_login_required
|
||||||
|
def unsubscribe_user(user_hashid: str) -> dict:
|
||||||
|
user_id = hashids.decode(user_hashid)
|
||||||
|
|
||||||
|
if not isinstance(user_id, int):
|
||||||
|
return {'status': 400, 'statusText': 'Bad Request'}
|
||||||
|
|
||||||
|
user = User.query.get(user_id)
|
||||||
|
|
||||||
|
if user is None:
|
||||||
|
return {'status': 404, 'statusText': 'Not found'}
|
||||||
|
|
||||||
|
if not (
|
||||||
|
user == current_user
|
||||||
|
or current_user.is_administrator
|
||||||
|
):
|
||||||
|
return {'status': 403, 'statusText': 'Forbidden'}
|
||||||
|
|
||||||
|
leave_room(f'/users/{user.hashid}')
|
||||||
|
|
||||||
|
return {'status': 200, 'statusText': 'OK'}
|
@ -26,7 +26,7 @@ def socketio_login_required(f):
|
|||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
if current_user.is_authenticated:
|
if current_user.is_authenticated:
|
||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
return {'status': 401, 'statusText': 'Unauthorized'}
|
return {'code': 401, 'body': 'Unauthorized'}
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ def socketio_permission_required(permission):
|
|||||||
@wraps(f)
|
@wraps(f)
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
if not current_user.can(permission):
|
if not current_user.can(permission):
|
||||||
return {'status': 403, 'statusText': 'Forbidden'}
|
return {'code': 403, 'body': 'Forbidden'}
|
||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
return wrapper
|
return wrapper
|
||||||
return decorator
|
return decorator
|
||||||
|
@ -42,9 +42,8 @@ def resource_after_delete(mapper, connection, resource):
|
|||||||
'path': resource.jsonpatch_path
|
'path': resource.jsonpatch_path
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
namespace = '/users'
|
|
||||||
room = f'/users/{resource.user_hashid}'
|
room = f'/users/{resource.user_hashid}'
|
||||||
socketio.emit('patch', jsonpatch, namespace=namespace, room=room)
|
socketio.emit('users.patch', jsonpatch, room=room)
|
||||||
|
|
||||||
|
|
||||||
def cfa_after_delete(mapper, connection, cfa):
|
def cfa_after_delete(mapper, connection, cfa):
|
||||||
@ -55,9 +54,8 @@ def cfa_after_delete(mapper, connection, cfa):
|
|||||||
'path': jsonpatch_path
|
'path': jsonpatch_path
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
namespace = '/users'
|
|
||||||
room = f'/users/{cfa.corpus.user.hashid}'
|
room = f'/users/{cfa.corpus.user.hashid}'
|
||||||
socketio.emit('patch', jsonpatch, namespace=namespace, room=room)
|
socketio.emit('users.patch', jsonpatch, room=room)
|
||||||
|
|
||||||
|
|
||||||
def resource_after_insert(mapper, connection, resource):
|
def resource_after_insert(mapper, connection, resource):
|
||||||
@ -71,9 +69,8 @@ def resource_after_insert(mapper, connection, resource):
|
|||||||
'value': jsonpatch_value
|
'value': jsonpatch_value
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
namespace = '/users'
|
|
||||||
room = f'/users/{resource.user_hashid}'
|
room = f'/users/{resource.user_hashid}'
|
||||||
socketio.emit('patch', jsonpatch, namespace=namespace, room=room)
|
socketio.emit('users.patch', jsonpatch, room=room)
|
||||||
|
|
||||||
|
|
||||||
def cfa_after_insert(mapper, connection, cfa):
|
def cfa_after_insert(mapper, connection, cfa):
|
||||||
@ -86,9 +83,8 @@ def cfa_after_insert(mapper, connection, cfa):
|
|||||||
'value': jsonpatch_value
|
'value': jsonpatch_value
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
namespace = '/users'
|
|
||||||
room = f'/users/{cfa.corpus.user.hashid}'
|
room = f'/users/{cfa.corpus.user.hashid}'
|
||||||
socketio.emit('patch', jsonpatch, namespace=namespace, room=room)
|
socketio.emit('users.patch', jsonpatch, room=room)
|
||||||
|
|
||||||
|
|
||||||
def resource_after_update(mapper, connection, resource):
|
def resource_after_update(mapper, connection, resource):
|
||||||
@ -113,9 +109,8 @@ def resource_after_update(mapper, connection, resource):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
if jsonpatch:
|
if jsonpatch:
|
||||||
namespace = '/users'
|
|
||||||
room = f'/users/{resource.user_hashid}'
|
room = f'/users/{resource.user_hashid}'
|
||||||
socketio.emit('patch', jsonpatch, namespace=namespace, room=room)
|
socketio.emit('users.patch', jsonpatch, room=room)
|
||||||
|
|
||||||
|
|
||||||
def job_after_update(mapper, connection, job):
|
def job_after_update(mapper, connection, job):
|
||||||
|
@ -9,8 +9,8 @@ from threading import Lock
|
|||||||
from app import db, docker_client, hashids, socketio
|
from app import db, docker_client, hashids, socketio
|
||||||
from app.decorators import socketio_login_required
|
from app.decorators import socketio_login_required
|
||||||
from app.models import Corpus, CorpusStatus
|
from app.models import Corpus, CorpusStatus
|
||||||
from . import cqi_extension_functions
|
from . import cqi_extensions
|
||||||
from .utils import SessionManager
|
from .utils import CQiOverSocketIOSessionManager
|
||||||
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
@ -85,16 +85,6 @@ CQI_API_FUNCTION_NAMES = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
CQI_EXTENSION_FUNCTION_NAMES = [
|
|
||||||
'ext_corpus_update_db',
|
|
||||||
'ext_corpus_static_data',
|
|
||||||
'ext_corpus_paginate_corpus',
|
|
||||||
'ext_cqp_paginate_subcorpus',
|
|
||||||
'ext_cqp_partial_export_subcorpus',
|
|
||||||
'ext_cqp_export_subcorpus',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class CQiOverSocketIONamespace(Namespace):
|
class CQiOverSocketIONamespace(Namespace):
|
||||||
@socketio_login_required
|
@socketio_login_required
|
||||||
def on_connect(self):
|
def on_connect(self):
|
||||||
@ -145,25 +135,25 @@ class CQiOverSocketIONamespace(Namespace):
|
|||||||
cqi_client = CQiClient(cqpserver_ip_address)
|
cqi_client = CQiClient(cqpserver_ip_address)
|
||||||
cqi_client_lock = Lock()
|
cqi_client_lock = Lock()
|
||||||
|
|
||||||
SessionManager.setup()
|
CQiOverSocketIOSessionManager.setup()
|
||||||
SessionManager.set_corpus_id(corpus_id)
|
CQiOverSocketIOSessionManager.set_corpus_id(corpus_id)
|
||||||
SessionManager.set_cqi_client(cqi_client)
|
CQiOverSocketIOSessionManager.set_cqi_client(cqi_client)
|
||||||
SessionManager.set_cqi_client_lock(cqi_client_lock)
|
CQiOverSocketIOSessionManager.set_cqi_client_lock(cqi_client_lock)
|
||||||
|
|
||||||
return {'code': 200, 'msg': 'OK'}
|
return {'code': 200, 'msg': 'OK'}
|
||||||
|
|
||||||
@socketio_login_required
|
@socketio_login_required
|
||||||
def on_exec(self, fn_name: str, fn_args: dict = {}) -> dict:
|
def on_exec(self, fn_name: str, fn_args: dict = {}) -> dict:
|
||||||
try:
|
try:
|
||||||
cqi_client = SessionManager.get_cqi_client()
|
cqi_client = CQiOverSocketIOSessionManager.get_cqi_client()
|
||||||
cqi_client_lock = SessionManager.get_cqi_client_lock()
|
cqi_client_lock = CQiOverSocketIOSessionManager.get_cqi_client_lock()
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return {'code': 424, 'msg': 'Failed Dependency'}
|
return {'code': 424, 'msg': 'Failed Dependency'}
|
||||||
|
|
||||||
if fn_name in CQI_API_FUNCTION_NAMES:
|
if fn_name in CQI_API_FUNCTION_NAMES:
|
||||||
fn = getattr(cqi_client.api, fn_name)
|
fn = getattr(cqi_client.api, fn_name)
|
||||||
elif fn_name in cqi_extension_functions.CQI_EXTENSION_FUNCTION_NAMES:
|
elif fn_name in cqi_extensions.CQI_EXTENSION_FUNCTION_NAMES:
|
||||||
fn = getattr(cqi_extension_functions, fn_name)
|
fn = getattr(cqi_extensions, fn_name)
|
||||||
else:
|
else:
|
||||||
return {'code': 400, 'msg': 'Bad Request'}
|
return {'code': 400, 'msg': 'Bad Request'}
|
||||||
|
|
||||||
@ -208,10 +198,10 @@ class CQiOverSocketIONamespace(Namespace):
|
|||||||
|
|
||||||
def on_disconnect(self):
|
def on_disconnect(self):
|
||||||
try:
|
try:
|
||||||
corpus_id = SessionManager.get_corpus_id()
|
corpus_id = CQiOverSocketIOSessionManager.get_corpus_id()
|
||||||
cqi_client = SessionManager.get_cqi_client()
|
cqi_client = CQiOverSocketIOSessionManager.get_cqi_client()
|
||||||
cqi_client_lock = SessionManager.get_cqi_client_lock()
|
cqi_client_lock = CQiOverSocketIOSessionManager.get_cqi_client_lock()
|
||||||
SessionManager.teardown()
|
CQiOverSocketIOSessionManager.teardown()
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -8,12 +8,22 @@ import json
|
|||||||
import math
|
import math
|
||||||
from app import db
|
from app import db
|
||||||
from app.models import Corpus
|
from app.models import Corpus
|
||||||
from .utils import SessionManager
|
from .utils import CQiOverSocketIOSessionManager
|
||||||
|
|
||||||
|
|
||||||
|
CQI_EXTENSION_FUNCTION_NAMES = [
|
||||||
|
'ext_corpus_update_db',
|
||||||
|
'ext_corpus_static_data',
|
||||||
|
'ext_corpus_paginate_corpus',
|
||||||
|
'ext_cqp_paginate_subcorpus',
|
||||||
|
'ext_cqp_partial_export_subcorpus',
|
||||||
|
'ext_cqp_export_subcorpus',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def ext_corpus_update_db(corpus: str) -> CQiStatusOk:
|
def ext_corpus_update_db(corpus: str) -> CQiStatusOk:
|
||||||
corpus_id = SessionManager.get_corpus_id()
|
corpus_id = CQiOverSocketIOSessionManager.get_corpus_id()
|
||||||
cqi_client = SessionManager.get_cqi_client()
|
cqi_client = CQiOverSocketIOSessionManager.get_cqi_client()
|
||||||
db_corpus = Corpus.query.get(corpus_id)
|
db_corpus = Corpus.query.get(corpus_id)
|
||||||
cqi_corpus = cqi_client.corpora.get(corpus)
|
cqi_corpus = cqi_client.corpora.get(corpus)
|
||||||
db_corpus.num_tokens = cqi_corpus.size
|
db_corpus.num_tokens = cqi_corpus.size
|
||||||
@ -22,7 +32,7 @@ def ext_corpus_update_db(corpus: str) -> CQiStatusOk:
|
|||||||
|
|
||||||
|
|
||||||
def ext_corpus_static_data(corpus: str) -> dict:
|
def ext_corpus_static_data(corpus: str) -> dict:
|
||||||
corpus_id = SessionManager.get_corpus_id()
|
corpus_id = CQiOverSocketIOSessionManager.get_corpus_id()
|
||||||
db_corpus = Corpus.query.get(corpus_id)
|
db_corpus = Corpus.query.get(corpus_id)
|
||||||
|
|
||||||
static_data_file_path = db_corpus.path / 'cwb' / 'static.json.gz'
|
static_data_file_path = db_corpus.path / 'cwb' / 'static.json.gz'
|
||||||
@ -30,7 +40,7 @@ def ext_corpus_static_data(corpus: str) -> dict:
|
|||||||
with static_data_file_path.open('rb') as f:
|
with static_data_file_path.open('rb') as f:
|
||||||
return f.read()
|
return f.read()
|
||||||
|
|
||||||
cqi_client = SessionManager.get_cqi_client()
|
cqi_client = CQiOverSocketIOSessionManager.get_cqi_client()
|
||||||
cqi_corpus = cqi_client.corpora.get(corpus)
|
cqi_corpus = cqi_client.corpora.get(corpus)
|
||||||
cqi_p_attrs = cqi_corpus.positional_attributes.list()
|
cqi_p_attrs = cqi_corpus.positional_attributes.list()
|
||||||
cqi_s_attrs = cqi_corpus.structural_attributes.list()
|
cqi_s_attrs = cqi_corpus.structural_attributes.list()
|
||||||
@ -158,7 +168,7 @@ def ext_corpus_paginate_corpus(
|
|||||||
page: int = 1,
|
page: int = 1,
|
||||||
per_page: int = 20
|
per_page: int = 20
|
||||||
) -> dict:
|
) -> dict:
|
||||||
cqi_client = SessionManager.get_cqi_client()
|
cqi_client = CQiOverSocketIOSessionManager.get_cqi_client()
|
||||||
cqi_corpus = cqi_client.corpora.get(corpus)
|
cqi_corpus = cqi_client.corpora.get(corpus)
|
||||||
# Sanity checks
|
# Sanity checks
|
||||||
if (
|
if (
|
||||||
@ -205,7 +215,7 @@ def ext_cqp_paginate_subcorpus(
|
|||||||
per_page: int = 20
|
per_page: int = 20
|
||||||
) -> dict:
|
) -> dict:
|
||||||
corpus_name, subcorpus_name = subcorpus.split(':', 1)
|
corpus_name, subcorpus_name = subcorpus.split(':', 1)
|
||||||
cqi_client = SessionManager.get_cqi_client()
|
cqi_client = CQiOverSocketIOSessionManager.get_cqi_client()
|
||||||
cqi_corpus = cqi_client.corpora.get(corpus_name)
|
cqi_corpus = cqi_client.corpora.get(corpus_name)
|
||||||
cqi_subcorpus = cqi_corpus.subcorpora.get(subcorpus_name)
|
cqi_subcorpus = cqi_corpus.subcorpora.get(subcorpus_name)
|
||||||
# Sanity checks
|
# Sanity checks
|
||||||
@ -252,7 +262,7 @@ def ext_cqp_partial_export_subcorpus(
|
|||||||
context: int = 50
|
context: int = 50
|
||||||
) -> dict:
|
) -> dict:
|
||||||
corpus_name, subcorpus_name = subcorpus.split(':', 1)
|
corpus_name, subcorpus_name = subcorpus.split(':', 1)
|
||||||
cqi_client = SessionManager.get_cqi_client()
|
cqi_client = CQiOverSocketIOSessionManager.get_cqi_client()
|
||||||
cqi_corpus = cqi_client.corpora.get(corpus_name)
|
cqi_corpus = cqi_client.corpora.get(corpus_name)
|
||||||
cqi_subcorpus = cqi_corpus.subcorpora.get(subcorpus_name)
|
cqi_subcorpus = cqi_corpus.subcorpora.get(subcorpus_name)
|
||||||
cqi_subcorpus_partial_export = _partial_export_subcorpus(cqi_subcorpus, match_id_list, context=context)
|
cqi_subcorpus_partial_export = _partial_export_subcorpus(cqi_subcorpus, match_id_list, context=context)
|
||||||
@ -261,7 +271,7 @@ def ext_cqp_partial_export_subcorpus(
|
|||||||
|
|
||||||
def ext_cqp_export_subcorpus(subcorpus: str, context: int = 50) -> dict:
|
def ext_cqp_export_subcorpus(subcorpus: str, context: int = 50) -> dict:
|
||||||
corpus_name, subcorpus_name = subcorpus.split(':', 1)
|
corpus_name, subcorpus_name = subcorpus.split(':', 1)
|
||||||
cqi_client = SessionManager.get_cqi_client()
|
cqi_client = CQiOverSocketIOSessionManager.get_cqi_client()
|
||||||
cqi_corpus = cqi_client.corpora.get(corpus_name)
|
cqi_corpus = cqi_client.corpora.get(corpus_name)
|
||||||
cqi_subcorpus = cqi_corpus.subcorpora.get(subcorpus_name)
|
cqi_subcorpus = cqi_corpus.subcorpora.get(subcorpus_name)
|
||||||
cqi_subcorpus_export = _export_subcorpus(cqi_subcorpus, context=context)
|
cqi_subcorpus_export = _export_subcorpus(cqi_subcorpus, context=context)
|
@ -3,7 +3,7 @@ from threading import Lock
|
|||||||
from flask import session
|
from flask import session
|
||||||
|
|
||||||
|
|
||||||
class SessionManager:
|
class CQiOverSocketIOSessionManager:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def setup():
|
def setup():
|
||||||
session['cqi_over_sio'] = {}
|
session['cqi_over_sio'] = {}
|
||||||
|
@ -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'
|
|
||||||
}
|
|
@ -1,116 +0,0 @@
|
|||||||
from flask import current_app, Flask
|
|
||||||
from flask_login import current_user
|
|
||||||
from flask_socketio import join_room, leave_room, Namespace
|
|
||||||
from app import db, hashids, socketio
|
|
||||||
from app.decorators import socketio_login_required
|
|
||||||
from app.models import User
|
|
||||||
|
|
||||||
|
|
||||||
def _delete_user(app: Flask, user_id: int):
|
|
||||||
with app.app_context():
|
|
||||||
user = User.query.get(user_id)
|
|
||||||
user.delete()
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
|
|
||||||
class UsersNamespace(Namespace):
|
|
||||||
@socketio_login_required
|
|
||||||
def on_get(self, user_hashid: str) -> dict:
|
|
||||||
user_id = hashids.decode(user_hashid)
|
|
||||||
|
|
||||||
if not isinstance(user_id, int):
|
|
||||||
return {'status': 400, 'statusText': 'Bad Request'}
|
|
||||||
|
|
||||||
user = User.query.get(user_id)
|
|
||||||
|
|
||||||
if user is None:
|
|
||||||
return {'status': 404, 'statusText': 'Not found'}
|
|
||||||
|
|
||||||
if not (
|
|
||||||
user == current_user
|
|
||||||
or current_user.is_administrator
|
|
||||||
):
|
|
||||||
return {'status': 403, 'statusText': 'Forbidden'}
|
|
||||||
|
|
||||||
return {
|
|
||||||
'body': user.to_json_serializeable(
|
|
||||||
backrefs=True,
|
|
||||||
relationships=True
|
|
||||||
),
|
|
||||||
'status': 200,
|
|
||||||
'statusText': 'OK'
|
|
||||||
}
|
|
||||||
|
|
||||||
@socketio_login_required
|
|
||||||
def on_subscribe(self, user_hashid: str) -> dict:
|
|
||||||
user_id = hashids.decode(user_hashid)
|
|
||||||
|
|
||||||
if not isinstance(user_id, int):
|
|
||||||
return {'status': 400, 'statusText': 'Bad Request'}
|
|
||||||
|
|
||||||
user = User.query.get(user_id)
|
|
||||||
|
|
||||||
if user is None:
|
|
||||||
return {'status': 404, 'statusText': 'Not found'}
|
|
||||||
|
|
||||||
if not (
|
|
||||||
user == current_user
|
|
||||||
or current_user.is_administrator
|
|
||||||
):
|
|
||||||
return {'status': 403, 'statusText': 'Forbidden'}
|
|
||||||
|
|
||||||
join_room(f'/users/{user.hashid}')
|
|
||||||
|
|
||||||
return {'status': 200, 'statusText': 'OK'}
|
|
||||||
|
|
||||||
@socketio_login_required
|
|
||||||
def on_unsubscribe(self, user_hashid: str) -> dict:
|
|
||||||
user_id = hashids.decode(user_hashid)
|
|
||||||
|
|
||||||
if not isinstance(user_id, int):
|
|
||||||
return {'status': 400, 'statusText': 'Bad Request'}
|
|
||||||
|
|
||||||
user = User.query.get(user_id)
|
|
||||||
|
|
||||||
if user is None:
|
|
||||||
return {'status': 404, 'statusText': 'Not found'}
|
|
||||||
|
|
||||||
if not (
|
|
||||||
user == current_user
|
|
||||||
or current_user.is_administrator
|
|
||||||
):
|
|
||||||
return {'status': 403, 'statusText': 'Forbidden'}
|
|
||||||
|
|
||||||
leave_room(f'/users/{user.hashid}')
|
|
||||||
|
|
||||||
return {'status': 200, 'statusText': 'OK'}
|
|
||||||
|
|
||||||
@socketio_login_required
|
|
||||||
def on_delete(self, user_hashid: str) -> dict:
|
|
||||||
user_id = hashids.decode(user_hashid)
|
|
||||||
|
|
||||||
if not isinstance(user_id, int):
|
|
||||||
return {'status': 400, 'statusText': 'Bad Request'}
|
|
||||||
|
|
||||||
user = User.query.get(user_id)
|
|
||||||
|
|
||||||
if user is None:
|
|
||||||
return {'status': 404, 'statusText': 'Not found'}
|
|
||||||
|
|
||||||
if not (
|
|
||||||
user == current_user
|
|
||||||
or current_user.is_administrator
|
|
||||||
):
|
|
||||||
return {'status': 403, 'statusText': 'Forbidden'}
|
|
||||||
|
|
||||||
socketio.start_background_task(
|
|
||||||
_delete_user,
|
|
||||||
current_app._get_current_object(),
|
|
||||||
user.id
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
'body': f'User "{user.username}" marked for deletion',
|
|
||||||
'status': 202,
|
|
||||||
'statusText': 'Accepted'
|
|
||||||
}
|
|
@ -1,9 +1,10 @@
|
|||||||
nopaque.App = class App {
|
nopaque.App = class App {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
this.data = {};
|
||||||
|
|
||||||
this.socket = io({transports: ['websocket'], upgrade: false});
|
this.socket = io({transports: ['websocket'], upgrade: false});
|
||||||
|
|
||||||
this.ui = new nopaque.UIExtension(this);
|
this.ui = new nopaque.UIExtension(this);
|
||||||
this.liveUserRegistry = new nopaque.LiveUserRegistryExtension(this);
|
|
||||||
this.users = new nopaque.UsersExtension(this);
|
this.users = new nopaque.UsersExtension(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,7 +29,5 @@ nopaque.App = class App {
|
|||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.ui.init();
|
this.ui.init();
|
||||||
this.liveUserRegistry.init();
|
|
||||||
this.users.init();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
53
app/static/js/app.users.js
Normal file
53
app/static/js/app.users.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
nopaque.UsersExtension = class UsersExtension {
|
||||||
|
#data;
|
||||||
|
#promises;
|
||||||
|
|
||||||
|
constructor(app) {
|
||||||
|
this.app = app;
|
||||||
|
|
||||||
|
this.#data = {};
|
||||||
|
this.app.data.users = this.#data;
|
||||||
|
|
||||||
|
this.#promises = {
|
||||||
|
get: {},
|
||||||
|
subscribe: {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async #get(userId) {
|
||||||
|
const response = await this.app.socket.emitWithAck('users.get', userId);
|
||||||
|
|
||||||
|
if (response.status != 200) {
|
||||||
|
throw new Error(`[${response.status}] ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#data[userId] = response.body;
|
||||||
|
return this.#data[userId];
|
||||||
|
}
|
||||||
|
|
||||||
|
get(userId) {
|
||||||
|
if (userId in this.#promises.get) {
|
||||||
|
return this.#promises.get[userId];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#promises.get[userId] = this.#get(userId);
|
||||||
|
return this.#promises.get[userId];
|
||||||
|
}
|
||||||
|
|
||||||
|
async #subscribe(userId) {
|
||||||
|
const response = await this.app.socket.emitWithAck('users.subscribe', userId);
|
||||||
|
|
||||||
|
if (response.status != 200) {
|
||||||
|
throw new Error(`[${response.status}] ${response.statusText}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribe(userId) {
|
||||||
|
if (userId in this.#promises.subscribe) {
|
||||||
|
return this.#promises.subscribe[userId];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#promises.subscribe[userId] = this.#subscribe(userId);
|
||||||
|
return this.#promises.subscribe[userId];
|
||||||
|
}
|
||||||
|
}
|
@ -1,70 +0,0 @@
|
|||||||
nopaque.LiveUserRegistryExtension = class LiveUserRegistryExtension extends EventTarget {
|
|
||||||
#data;
|
|
||||||
|
|
||||||
constructor(app) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.app = app;
|
|
||||||
|
|
||||||
this.#data = {
|
|
||||||
users: {},
|
|
||||||
promises: {}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
this.app.users.socket.on('patch', (patch) => {this.#onPatch(patch)});
|
|
||||||
}
|
|
||||||
|
|
||||||
add(userId) {
|
|
||||||
if (!(userId in this.#data.promises)) {
|
|
||||||
this.#data.promises[userId] = this.#add(userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.#data.promises[userId];
|
|
||||||
}
|
|
||||||
|
|
||||||
async #add(userId) {
|
|
||||||
await this.app.users.subscribe(userId);
|
|
||||||
this.#data.users[userId] = await this.app.users.get(userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
async get(userId) {
|
|
||||||
await this.add(userId);
|
|
||||||
return this.#data.users[userId];
|
|
||||||
}
|
|
||||||
|
|
||||||
#onPatch(patch) {
|
|
||||||
// Filter patch to only include operations on users that are initialized
|
|
||||||
let filterRegExp = new RegExp(`^/users/(${Object.keys(this.#data.users).join('|')})`);
|
|
||||||
let filteredPatch = patch.filter(operation => filterRegExp.test(operation.path));
|
|
||||||
|
|
||||||
// Apply patch
|
|
||||||
jsonpatch.applyPatch(this.#data, filteredPatch);
|
|
||||||
|
|
||||||
// Notify event listeners
|
|
||||||
let event = new CustomEvent('patch', {detail: filteredPatch});
|
|
||||||
this.dispatchEvent(event);
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Notify event listeners. Event type: "patch *"
|
|
||||||
let event = new CustomEvent('patch *', {detail: filteredPatch});
|
|
||||||
this.dispatchEvent(event);
|
|
||||||
|
|
||||||
// Group patches by user id: {<user-id>: [op, ...], ...}
|
|
||||||
let patches = {};
|
|
||||||
let matchRegExp = new RegExp(`^/users/([A-Za-z0-9]+)`);
|
|
||||||
for (let operation of filteredPatch) {
|
|
||||||
let [match, userId] = operation.path.match(matchRegExp);
|
|
||||||
if (!(userId in patches)) {patches[userId] = [];}
|
|
||||||
patches[userId].push(operation);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notify event listeners. Event type: "patch <user-id>"
|
|
||||||
for (let [userId, patch] of Object.entries(patches)) {
|
|
||||||
let event = new CustomEvent(`patch ${userId}`, {detail: patch});
|
|
||||||
this.dispatchEvent(event);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
nopaque.UsersExtension = class UsersExtension {
|
|
||||||
constructor(app) {
|
|
||||||
this.app = app;
|
|
||||||
|
|
||||||
this.socket = io('/users', {transports: ['websocket'], upgrade: false});
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {}
|
|
||||||
|
|
||||||
async get(userId) {
|
|
||||||
const response = await this.socket.emitWithAck('get', userId);
|
|
||||||
|
|
||||||
if (response.status !== 200) {
|
|
||||||
throw new Error(`[${response.status}] ${response.statusText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.body;
|
|
||||||
}
|
|
||||||
|
|
||||||
async subscribe(userId) {
|
|
||||||
const response = await this.socket.emitWithAck('subscribe', userId);
|
|
||||||
|
|
||||||
if (response.status != 200) {
|
|
||||||
throw new Error(`[${response.status}] ${response.statusText}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async unsubscribe(userId) {
|
|
||||||
const response = await this.socket.emitWithAck('unsubscribe', userId);
|
|
||||||
|
|
||||||
if (response.status != 200) {
|
|
||||||
throw new Error(`[${response.status}] ${response.statusText}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async delete(userId) {
|
|
||||||
const response = await this.socket.emitWithAck('delete', userId);
|
|
||||||
|
|
||||||
if (response.status != 202) {
|
|
||||||
throw new Error(`[${response.status}] ${response.statusText}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,14 +5,19 @@ nopaque.resource_displays.ResourceDisplay = class ResourceDisplay {
|
|||||||
this.displayElement = displayElement;
|
this.displayElement = displayElement;
|
||||||
this.userId = this.displayElement.dataset.userId;
|
this.userId = this.displayElement.dataset.userId;
|
||||||
this.isInitialized = false;
|
this.isInitialized = false;
|
||||||
if (this.userId === undefined) {return;}
|
if (this.userId) {
|
||||||
app.liveUserRegistry.addEventListener('patch', (event) => {
|
app.users.subscribe(this.userId)
|
||||||
if (this.isInitialized) {this.onPatch(event.detail);}
|
.then((response) => {
|
||||||
});
|
app.socket.on('users.patch', (patch) => {
|
||||||
app.liveUserRegistry.get(this.userId).then((user) => {
|
if (this.isInitialized) {this.onPatch(patch);}
|
||||||
this.init(user);
|
});
|
||||||
this.isInitialized = true;
|
});
|
||||||
});
|
app.users.get(this.userId)
|
||||||
|
.then((user) => {
|
||||||
|
this.init(user);
|
||||||
|
this.isInitialized = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init(user) {throw 'Not implemented';}
|
init(user) {throw 'Not implemented';}
|
||||||
|
@ -14,11 +14,12 @@ nopaque.resource_lists.CorpusFileList = class CorpusFileList extends nopaque.res
|
|||||||
this.hasPermissionView = listContainerElement.dataset?.hasPermissionView == 'true' || false;
|
this.hasPermissionView = listContainerElement.dataset?.hasPermissionView == 'true' || false;
|
||||||
this.hasPermissionManageFiles = listContainerElement.dataset?.hasPermissionManageFiles == 'true' || false;
|
this.hasPermissionManageFiles = listContainerElement.dataset?.hasPermissionManageFiles == 'true' || false;
|
||||||
if (this.userId === undefined || this.corpusId === undefined) {return;}
|
if (this.userId === undefined || this.corpusId === undefined) {return;}
|
||||||
app.liveUserRegistry.addEventListener('patch', (event) => {
|
app.users.subscribe(this.userId).then((response) => {
|
||||||
if (this.isInitialized) {this.onPatch(event.detail);}
|
app.socket.on('users.patch', (patch) => {
|
||||||
|
if (this.isInitialized) {this.onPatch(patch);}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
app.liveUserRegistry.get(this.userId).then((user) => {
|
app.users.get(this.userId).then((user) => {
|
||||||
// TODO: Make this better understandable
|
|
||||||
this.add(Object.values(user.corpora[this.corpusId].files || user.followed_corpora[this.corpusId].files));
|
this.add(Object.values(user.corpora[this.corpusId].files || user.followed_corpora[this.corpusId].files));
|
||||||
this.isInitialized = true;
|
this.isInitialized = true;
|
||||||
});
|
});
|
||||||
|
@ -12,16 +12,15 @@ nopaque.resource_lists.CorpusFollowerList = class CorpusFollowerList extends nop
|
|||||||
this.userId = listContainerElement.dataset.userId;
|
this.userId = listContainerElement.dataset.userId;
|
||||||
this.corpusId = listContainerElement.dataset.corpusId;
|
this.corpusId = listContainerElement.dataset.corpusId;
|
||||||
if (this.userId === undefined || this.corpusId === undefined) {return;}
|
if (this.userId === undefined || this.corpusId === undefined) {return;}
|
||||||
app.liveUserRegistry.addEventListener('patch', (event) => {
|
app.users.subscribe(this.userId).then((response) => {
|
||||||
if (this.isInitialized) {this.onPatch(event.detail);}
|
app.socket.on('users.patch', (patch) => {
|
||||||
|
if (this.isInitialized) {this.onPatch(patch);}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
app.liveUserRegistry.get(this.userId).then((user) => {
|
app.users.get(this.userId).then((user) => {
|
||||||
// TODO: Check if the following is better
|
|
||||||
// let corpusFollowerAssociations = Object.values(user.corpora[this.corpusId].corpus_follower_associations);
|
// let corpusFollowerAssociations = Object.values(user.corpora[this.corpusId].corpus_follower_associations);
|
||||||
// let filteredList = corpusFollowerAssociations.filter(association => association.follower.id != currentUserId);
|
// let filteredList = corpusFollowerAssociations.filter(association => association.follower.id != currentUserId);
|
||||||
// this.add(filteredList);
|
// this.add(filteredList);
|
||||||
|
|
||||||
// TODO: Make this better understandable
|
|
||||||
this.add(Object.values(user.corpora[this.corpusId].corpus_follower_associations));
|
this.add(Object.values(user.corpora[this.corpusId].corpus_follower_associations));
|
||||||
this.isInitialized = true;
|
this.isInitialized = true;
|
||||||
});
|
});
|
||||||
|
@ -11,10 +11,12 @@ nopaque.resource_lists.CorpusList = class CorpusList extends nopaque.resource_li
|
|||||||
this.selectedItemIds = new Set();
|
this.selectedItemIds = new Set();
|
||||||
this.userId = listContainerElement.dataset.userId;
|
this.userId = listContainerElement.dataset.userId;
|
||||||
if (this.userId === undefined) {return;}
|
if (this.userId === undefined) {return;}
|
||||||
app.liveUserRegistry.addEventListener('patch', (event) => {
|
app.users.subscribe(this.userId).then((response) => {
|
||||||
if (this.isInitialized) {this.onPatch(event.detail);}
|
app.socket.on('users.patch', (patch) => {
|
||||||
|
if (this.isInitialized) {this.onPatch(patch);}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
app.liveUserRegistry.get(this.userId).then((user) => {
|
app.users.get(this.userId).then((user) => {
|
||||||
this.add(this.aggregateData(user));
|
this.add(this.aggregateData(user));
|
||||||
this.isInitialized = true;
|
this.isInitialized = true;
|
||||||
});
|
});
|
||||||
|
@ -8,10 +8,8 @@ nopaque.resource_lists.JobInputList = class JobInputList extends nopaque.resourc
|
|||||||
this.userId = listContainerElement.dataset.userId;
|
this.userId = listContainerElement.dataset.userId;
|
||||||
this.jobId = listContainerElement.dataset.jobId;
|
this.jobId = listContainerElement.dataset.jobId;
|
||||||
if (this.userId === undefined || this.jobId === undefined) {return;}
|
if (this.userId === undefined || this.jobId === undefined) {return;}
|
||||||
// app.liveUserRegistry.addEventListener('patch', (event) => {
|
app.users.subscribe(this.userId);
|
||||||
// if (this.isInitialized) {this.onPatch(event.detail);}
|
app.users.get(this.userId).then((user) => {
|
||||||
// });
|
|
||||||
app.liveUserRegistry.get(this.userId).then((user) => {
|
|
||||||
this.add(Object.values(user.jobs[this.jobId].inputs));
|
this.add(Object.values(user.jobs[this.jobId].inputs));
|
||||||
this.isInitialized = true;
|
this.isInitialized = true;
|
||||||
});
|
});
|
||||||
|
@ -12,10 +12,12 @@ nopaque.resource_lists.JobList = class JobList extends nopaque.resource_lists.Re
|
|||||||
this.selectedItemIds = new Set();
|
this.selectedItemIds = new Set();
|
||||||
this.userId = listContainerElement.dataset.userId;
|
this.userId = listContainerElement.dataset.userId;
|
||||||
if (this.userId === undefined) {return;}
|
if (this.userId === undefined) {return;}
|
||||||
app.liveUserRegistry.addEventListener('patch', (event) => {
|
app.users.subscribe(this.userId).then((response) => {
|
||||||
if (this.isInitialized) {this.onPatch(event.detail);}
|
app.socket.on('users.patch', (patch) => {
|
||||||
|
if (this.isInitialized) {this.onPatch(patch);}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
app.liveUserRegistry.get(this.userId).then((user) => {
|
app.users.get(this.userId).then((user) => {
|
||||||
this.add(Object.values(user.jobs));
|
this.add(Object.values(user.jobs));
|
||||||
this.isInitialized = true;
|
this.isInitialized = true;
|
||||||
});
|
});
|
||||||
|
@ -8,10 +8,12 @@ nopaque.resource_lists.JobResultList = class JobResultList extends nopaque.resou
|
|||||||
this.userId = listContainerElement.dataset.userId;
|
this.userId = listContainerElement.dataset.userId;
|
||||||
this.jobId = listContainerElement.dataset.jobId;
|
this.jobId = listContainerElement.dataset.jobId;
|
||||||
if (this.userId === undefined || this.jobId === undefined) {return;}
|
if (this.userId === undefined || this.jobId === undefined) {return;}
|
||||||
app.liveUserRegistry.addEventListener('patch', (event) => {
|
app.users.subscribe(this.userId).then((response) => {
|
||||||
if (this.isInitialized) {this.onPatch(event.detail);}
|
app.socket.on('users.patch', (patch) => {
|
||||||
|
if (this.isInitialized) {this.onPatch(patch);}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
app.liveUserRegistry.get(this.userId).then((user) => {
|
app.users.get(this.userId).then((user) => {
|
||||||
this.add(Object.values(user.jobs[this.jobId].results));
|
this.add(Object.values(user.jobs[this.jobId].results));
|
||||||
this.isInitialized = true;
|
this.isInitialized = true;
|
||||||
});
|
});
|
||||||
|
@ -8,10 +8,12 @@ nopaque.resource_lists.SpaCyNLPPipelineModelList = class SpaCyNLPPipelineModelLi
|
|||||||
this.isInitialized = false;
|
this.isInitialized = false;
|
||||||
this.userId = listContainerElement.dataset.userId;
|
this.userId = listContainerElement.dataset.userId;
|
||||||
if (this.userId === undefined) {return;}
|
if (this.userId === undefined) {return;}
|
||||||
app.liveUserRegistry.addEventListener('patch', (event) => {
|
app.users.subscribe(this.userId).then((response) => {
|
||||||
if (this.isInitialized) {this.onPatch(event.detail);}
|
app.socket.on('users.patch', (patch) => {
|
||||||
|
if (this.isInitialized) {this.onPatch(patch);}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
app.liveUserRegistry.get(this.userId).then((user) => {
|
app.users.get(this.userId).then((user) => {
|
||||||
this.add(Object.values(user.spacy_nlp_pipeline_models));
|
this.add(Object.values(user.spacy_nlp_pipeline_models));
|
||||||
this.isInitialized = true;
|
this.isInitialized = true;
|
||||||
});
|
});
|
||||||
|
@ -8,11 +8,21 @@ nopaque.resource_lists.TesseractOCRPipelineModelList = class TesseractOCRPipelin
|
|||||||
this.isInitialized = false;
|
this.isInitialized = false;
|
||||||
this.userId = listContainerElement.dataset.userId;
|
this.userId = listContainerElement.dataset.userId;
|
||||||
if (this.userId === undefined) {return;}
|
if (this.userId === undefined) {return;}
|
||||||
app.liveUserRegistry.addEventListener('patch', (event) => {
|
app.users.subscribe(this.userId).then((response) => {
|
||||||
if (this.isInitialized) {this.onPatch(event.detail);}
|
app.socket.on('users.patch', (patch) => {
|
||||||
|
if (this.isInitialized) {this.onPatch(patch);}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
app.liveUserRegistry.get(this.userId).then((user) => {
|
app.users.get(this.userId).then((user) => {
|
||||||
this.add(Object.values(user.tesseract_ocr_pipeline_models));
|
this.add(Object.values(user.tesseract_ocr_pipeline_models));
|
||||||
|
for (let uncheckedCheckbox of this.listjs.list.querySelectorAll('input[data-checked="True"]')) {
|
||||||
|
uncheckedCheckbox.setAttribute('checked', '');
|
||||||
|
}
|
||||||
|
if (user.role.name !== ('Administrator' || 'Contributor')) {
|
||||||
|
for (let switchElement of this.listjs.list.querySelectorAll('.is_public')) {
|
||||||
|
switchElement.setAttribute('disabled', '');
|
||||||
|
}
|
||||||
|
}
|
||||||
this.isInitialized = true;
|
this.isInitialized = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -9,9 +9,8 @@
|
|||||||
output='gen/nopaque.%(version)s.js',
|
output='gen/nopaque.%(version)s.js',
|
||||||
'js/index.js',
|
'js/index.js',
|
||||||
'js/app.js',
|
'js/app.js',
|
||||||
'js/app/ui.js',
|
'js/app.ui.js',
|
||||||
'js/app/user-live-registry.js',
|
'js/app.users.js',
|
||||||
'js/app/users.js',
|
|
||||||
'js/utils.js',
|
'js/utils.js',
|
||||||
|
|
||||||
'js/forms/index.js',
|
'js/forms/index.js',
|
||||||
@ -76,35 +75,41 @@
|
|||||||
{% endassets -%}
|
{% endassets -%}
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// TODO: Implement an app.run method and use this for all of the following
|
// TODO: Implement an app.run method and use this for all of the following
|
||||||
const app = new nopaque.App();
|
const app = new nopaque.App();
|
||||||
app.init();
|
app.init();
|
||||||
|
|
||||||
{% if current_user.is_authenticated -%}
|
{% if current_user.is_authenticated -%}
|
||||||
const currentUserId = {{ current_user.hashid|tojson }};
|
// TODO: Set this as a property of the app object
|
||||||
|
const currentUserId = {{ current_user.hashid|tojson }};
|
||||||
|
|
||||||
app.liveUserRegistry.add(currentUserId)
|
// Subscribe to the current user's data events
|
||||||
|
app.users.subscribe(currentUserId)
|
||||||
.catch((error) => {throw JSON.stringify(error);});
|
.catch((error) => {throw JSON.stringify(error);});
|
||||||
|
|
||||||
{% if not current_user.terms_of_use_accepted -%}
|
// Get the current user's data
|
||||||
M.Modal.getInstance(document.querySelector('#terms-of-use-modal')).open();
|
app.users.get(currentUserId, true, true)
|
||||||
{% endif -%}
|
.catch((error) => {throw JSON.stringify(error);});
|
||||||
{% endif -%}
|
|
||||||
|
|
||||||
// Display flashed messages
|
{% if not current_user.terms_of_use_accepted -%}
|
||||||
for (let [category, message] of {{ get_flashed_messages(with_categories=True)|tojson }}) {
|
M.Modal.getInstance(document.querySelector('#terms-of-use-modal')).open();
|
||||||
|
{% endif -%}
|
||||||
|
{% endif -%}
|
||||||
|
|
||||||
|
// Display flashed messages
|
||||||
|
for (let [category, message] of {{ get_flashed_messages(with_categories=True)|tojson }}) {
|
||||||
app.ui.flash(message, message);
|
app.ui.flash(message, message);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
let languageModalSwitch = document.querySelector('#terms-of-use-modal-switch');
|
let languageModalSwitch = document.querySelector('#terms-of-use-modal-switch');
|
||||||
let termsOfUseModalContent = document.querySelectorAll('.terms-of-use-modal-content');
|
let termsOfUseModalContent = document.querySelectorAll('.terms-of-use-modal-content');
|
||||||
if (languageModalSwitch) {
|
if (languageModalSwitch) {
|
||||||
languageModalSwitch.addEventListener('change', () => {
|
languageModalSwitch.addEventListener('change', function() {
|
||||||
termsOfUseModalContent.forEach(content => {
|
termsOfUseModalContent.forEach(content => {
|
||||||
content.classList.toggle('hide');
|
content.classList.toggle('hide');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
Loading…
Reference in New Issue
Block a user