mirror of
https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
synced 2024-12-27 03:44:19 +00:00
Compare commits
2 Commits
12a3ac1d5d
...
df2bffe0fd
Author | SHA1 | Date | |
---|---|---|---|
|
df2bffe0fd | ||
|
aafb3ca3ec |
@ -132,6 +132,9 @@ 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, events, json_routes, routes, settings
|
from . import cli, json_routes, routes, settings
|
||||||
|
@ -1,122 +0,0 @@
|
|||||||
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 {'code': 401, 'body': 'Unauthorized'}
|
return {'status': 401, 'statusText': '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 {'code': 403, 'body': 'Forbidden'}
|
return {'status': 403, 'statusText': 'Forbidden'}
|
||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
return wrapper
|
return wrapper
|
||||||
return decorator
|
return decorator
|
||||||
|
@ -42,8 +42,9 @@ 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('users.patch', jsonpatch, room=room)
|
socketio.emit('patch', jsonpatch, namespace=namespace, room=room)
|
||||||
|
|
||||||
|
|
||||||
def cfa_after_delete(mapper, connection, cfa):
|
def cfa_after_delete(mapper, connection, cfa):
|
||||||
@ -54,8 +55,9 @@ 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('users.patch', jsonpatch, room=room)
|
socketio.emit('patch', jsonpatch, namespace=namespace, room=room)
|
||||||
|
|
||||||
|
|
||||||
def resource_after_insert(mapper, connection, resource):
|
def resource_after_insert(mapper, connection, resource):
|
||||||
@ -69,8 +71,9 @@ 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('users.patch', jsonpatch, room=room)
|
socketio.emit('patch', jsonpatch, namespace=namespace, room=room)
|
||||||
|
|
||||||
|
|
||||||
def cfa_after_insert(mapper, connection, cfa):
|
def cfa_after_insert(mapper, connection, cfa):
|
||||||
@ -83,8 +86,9 @@ 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('users.patch', jsonpatch, room=room)
|
socketio.emit('patch', jsonpatch, namespace=namespace, room=room)
|
||||||
|
|
||||||
|
|
||||||
def resource_after_update(mapper, connection, resource):
|
def resource_after_update(mapper, connection, resource):
|
||||||
@ -109,8 +113,9 @@ 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('users.patch', jsonpatch, room=room)
|
socketio.emit('patch', jsonpatch, namespace=namespace, 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_extensions
|
from . import cqi_extension_functions
|
||||||
from .utils import CQiOverSocketIOSessionManager
|
from .utils import SessionManager
|
||||||
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
@ -85,6 +85,16 @@ 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):
|
||||||
@ -135,25 +145,25 @@ class CQiOverSocketIONamespace(Namespace):
|
|||||||
cqi_client = CQiClient(cqpserver_ip_address)
|
cqi_client = CQiClient(cqpserver_ip_address)
|
||||||
cqi_client_lock = Lock()
|
cqi_client_lock = Lock()
|
||||||
|
|
||||||
CQiOverSocketIOSessionManager.setup()
|
SessionManager.setup()
|
||||||
CQiOverSocketIOSessionManager.set_corpus_id(corpus_id)
|
SessionManager.set_corpus_id(corpus_id)
|
||||||
CQiOverSocketIOSessionManager.set_cqi_client(cqi_client)
|
SessionManager.set_cqi_client(cqi_client)
|
||||||
CQiOverSocketIOSessionManager.set_cqi_client_lock(cqi_client_lock)
|
SessionManager.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 = CQiOverSocketIOSessionManager.get_cqi_client()
|
cqi_client = SessionManager.get_cqi_client()
|
||||||
cqi_client_lock = CQiOverSocketIOSessionManager.get_cqi_client_lock()
|
cqi_client_lock = SessionManager.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_extensions.CQI_EXTENSION_FUNCTION_NAMES:
|
elif fn_name in cqi_extension_functions.CQI_EXTENSION_FUNCTION_NAMES:
|
||||||
fn = getattr(cqi_extensions, fn_name)
|
fn = getattr(cqi_extension_functions, fn_name)
|
||||||
else:
|
else:
|
||||||
return {'code': 400, 'msg': 'Bad Request'}
|
return {'code': 400, 'msg': 'Bad Request'}
|
||||||
|
|
||||||
@ -198,10 +208,10 @@ class CQiOverSocketIONamespace(Namespace):
|
|||||||
|
|
||||||
def on_disconnect(self):
|
def on_disconnect(self):
|
||||||
try:
|
try:
|
||||||
corpus_id = CQiOverSocketIOSessionManager.get_corpus_id()
|
corpus_id = SessionManager.get_corpus_id()
|
||||||
cqi_client = CQiOverSocketIOSessionManager.get_cqi_client()
|
cqi_client = SessionManager.get_cqi_client()
|
||||||
cqi_client_lock = CQiOverSocketIOSessionManager.get_cqi_client_lock()
|
cqi_client_lock = SessionManager.get_cqi_client_lock()
|
||||||
CQiOverSocketIOSessionManager.teardown()
|
SessionManager.teardown()
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -8,22 +8,12 @@ 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 CQiOverSocketIOSessionManager
|
from .utils import SessionManager
|
||||||
|
|
||||||
|
|
||||||
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 = CQiOverSocketIOSessionManager.get_corpus_id()
|
corpus_id = SessionManager.get_corpus_id()
|
||||||
cqi_client = CQiOverSocketIOSessionManager.get_cqi_client()
|
cqi_client = SessionManager.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
|
||||||
@ -32,7 +22,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 = CQiOverSocketIOSessionManager.get_corpus_id()
|
corpus_id = SessionManager.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'
|
||||||
@ -40,7 +30,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 = CQiOverSocketIOSessionManager.get_cqi_client()
|
cqi_client = SessionManager.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()
|
||||||
@ -168,7 +158,7 @@ def ext_corpus_paginate_corpus(
|
|||||||
page: int = 1,
|
page: int = 1,
|
||||||
per_page: int = 20
|
per_page: int = 20
|
||||||
) -> dict:
|
) -> dict:
|
||||||
cqi_client = CQiOverSocketIOSessionManager.get_cqi_client()
|
cqi_client = SessionManager.get_cqi_client()
|
||||||
cqi_corpus = cqi_client.corpora.get(corpus)
|
cqi_corpus = cqi_client.corpora.get(corpus)
|
||||||
# Sanity checks
|
# Sanity checks
|
||||||
if (
|
if (
|
||||||
@ -215,7 +205,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 = CQiOverSocketIOSessionManager.get_cqi_client()
|
cqi_client = SessionManager.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
|
||||||
@ -262,7 +252,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 = CQiOverSocketIOSessionManager.get_cqi_client()
|
cqi_client = SessionManager.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)
|
||||||
@ -271,7 +261,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 = CQiOverSocketIOSessionManager.get_cqi_client()
|
cqi_client = SessionManager.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 CQiOverSocketIOSessionManager:
|
class SessionManager:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def setup():
|
def setup():
|
||||||
session['cqi_over_sio'] = {}
|
session['cqi_over_sio'] = {}
|
||||||
|
109
app/namespaces/jobs.py
Normal file
109
app/namespaces/jobs.py
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
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'
|
||||||
|
}
|
116
app/namespaces/users.py
Normal file
116
app/namespaces/users.py
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
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,10 +1,9 @@
|
|||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,5 +28,7 @@ nopaque.App = class App {
|
|||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.ui.init();
|
this.ui.init();
|
||||||
|
this.liveUserRegistry.init();
|
||||||
|
this.users.init();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,53 +0,0 @@
|
|||||||
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];
|
|
||||||
}
|
|
||||||
}
|
|
70
app/static/js/app/user-live-registry.js
Normal file
70
app/static/js/app/user-live-registry.js
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
43
app/static/js/app/users.js
Normal file
43
app/static/js/app/users.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
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,19 +5,14 @@ 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) {
|
if (this.userId === undefined) {return;}
|
||||||
app.users.subscribe(this.userId)
|
app.liveUserRegistry.addEventListener('patch', (event) => {
|
||||||
.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) => {
|
||||||
});
|
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,12 +14,11 @@ 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.users.subscribe(this.userId).then((response) => {
|
app.liveUserRegistry.addEventListener('patch', (event) => {
|
||||||
app.socket.on('users.patch', (patch) => {
|
if (this.isInitialized) {this.onPatch(event.detail);}
|
||||||
if (this.isInitialized) {this.onPatch(patch);}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
app.users.get(this.userId).then((user) => {
|
app.liveUserRegistry.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,15 +12,16 @@ 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.users.subscribe(this.userId).then((response) => {
|
app.liveUserRegistry.addEventListener('patch', (event) => {
|
||||||
app.socket.on('users.patch', (patch) => {
|
if (this.isInitialized) {this.onPatch(event.detail);}
|
||||||
if (this.isInitialized) {this.onPatch(patch);}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
app.users.get(this.userId).then((user) => {
|
app.liveUserRegistry.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,12 +11,10 @@ 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.users.subscribe(this.userId).then((response) => {
|
app.liveUserRegistry.addEventListener('patch', (event) => {
|
||||||
app.socket.on('users.patch', (patch) => {
|
if (this.isInitialized) {this.onPatch(event.detail);}
|
||||||
if (this.isInitialized) {this.onPatch(patch);}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
app.users.get(this.userId).then((user) => {
|
app.liveUserRegistry.get(this.userId).then((user) => {
|
||||||
this.add(this.aggregateData(user));
|
this.add(this.aggregateData(user));
|
||||||
this.isInitialized = true;
|
this.isInitialized = true;
|
||||||
});
|
});
|
||||||
|
@ -8,8 +8,10 @@ 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.users.subscribe(this.userId);
|
// app.liveUserRegistry.addEventListener('patch', (event) => {
|
||||||
app.users.get(this.userId).then((user) => {
|
// if (this.isInitialized) {this.onPatch(event.detail);}
|
||||||
|
// });
|
||||||
|
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,12 +12,10 @@ 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.users.subscribe(this.userId).then((response) => {
|
app.liveUserRegistry.addEventListener('patch', (event) => {
|
||||||
app.socket.on('users.patch', (patch) => {
|
if (this.isInitialized) {this.onPatch(event.detail);}
|
||||||
if (this.isInitialized) {this.onPatch(patch);}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
app.users.get(this.userId).then((user) => {
|
app.liveUserRegistry.get(this.userId).then((user) => {
|
||||||
this.add(Object.values(user.jobs));
|
this.add(Object.values(user.jobs));
|
||||||
this.isInitialized = true;
|
this.isInitialized = true;
|
||||||
});
|
});
|
||||||
|
@ -8,12 +8,10 @@ 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.users.subscribe(this.userId).then((response) => {
|
app.liveUserRegistry.addEventListener('patch', (event) => {
|
||||||
app.socket.on('users.patch', (patch) => {
|
if (this.isInitialized) {this.onPatch(event.detail);}
|
||||||
if (this.isInitialized) {this.onPatch(patch);}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
app.users.get(this.userId).then((user) => {
|
app.liveUserRegistry.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,12 +8,10 @@ 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.users.subscribe(this.userId).then((response) => {
|
app.liveUserRegistry.addEventListener('patch', (event) => {
|
||||||
app.socket.on('users.patch', (patch) => {
|
if (this.isInitialized) {this.onPatch(event.detail);}
|
||||||
if (this.isInitialized) {this.onPatch(patch);}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
app.users.get(this.userId).then((user) => {
|
app.liveUserRegistry.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,21 +8,11 @@ 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.users.subscribe(this.userId).then((response) => {
|
app.liveUserRegistry.addEventListener('patch', (event) => {
|
||||||
app.socket.on('users.patch', (patch) => {
|
if (this.isInitialized) {this.onPatch(event.detail);}
|
||||||
if (this.isInitialized) {this.onPatch(patch);}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
app.users.get(this.userId).then((user) => {
|
app.liveUserRegistry.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,8 +9,9 @@
|
|||||||
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.users.js',
|
'js/app/user-live-registry.js',
|
||||||
|
'js/app/users.js',
|
||||||
'js/utils.js',
|
'js/utils.js',
|
||||||
|
|
||||||
'js/forms/index.js',
|
'js/forms/index.js',
|
||||||
@ -75,41 +76,35 @@
|
|||||||
{% 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 -%}
|
||||||
// TODO: Set this as a property of the app object
|
const currentUserId = {{ current_user.hashid|tojson }};
|
||||||
const currentUserId = {{ current_user.hashid|tojson }};
|
|
||||||
|
|
||||||
// Subscribe to the current user's data events
|
app.liveUserRegistry.add(currentUserId)
|
||||||
app.users.subscribe(currentUserId)
|
|
||||||
.catch((error) => {throw JSON.stringify(error);});
|
.catch((error) => {throw JSON.stringify(error);});
|
||||||
|
|
||||||
// Get the current user's data
|
{% if not current_user.terms_of_use_accepted -%}
|
||||||
app.users.get(currentUserId, true, true)
|
M.Modal.getInstance(document.querySelector('#terms-of-use-modal')).open();
|
||||||
.catch((error) => {throw JSON.stringify(error);});
|
{% endif -%}
|
||||||
|
{% endif -%}
|
||||||
|
|
||||||
{% if not current_user.terms_of_use_accepted -%}
|
// Display flashed messages
|
||||||
M.Modal.getInstance(document.querySelector('#terms-of-use-modal')).open();
|
for (let [category, message] of {{ get_flashed_messages(with_categories=True)|tojson }}) {
|
||||||
{% 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', function() {
|
languageModalSwitch.addEventListener('change', () => {
|
||||||
termsOfUseModalContent.forEach(content => {
|
termsOfUseModalContent.forEach(content => {
|
||||||
content.classList.toggle('hide');
|
content.classList.toggle('hide');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
Loading…
Reference in New Issue
Block a user