Compare commits

...

3 Commits

Author SHA1 Message Date
Patrick Jentsch
54c4295bf7 Fixes and more descriptions 2024-11-18 13:32:55 +01:00
Patrick Jentsch
1e5c26b8e3 Reorganize Socket.IO code 2024-11-18 12:36:37 +01:00
Patrick Jentsch
9f56647cf7 highlight active items in top navbar 2024-11-18 12:35:53 +01:00
17 changed files with 127 additions and 118 deletions

View File

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

View File

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

View File

@ -0,0 +1,82 @@
from flask_login import current_user
from flask_socketio import join_room, leave_room
from app import hashids, socketio
from app.decorators import socketio_login_required
from app.models import User
@socketio.on('users.get_user')
@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_user')
@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_user')
@socketio_login_required
def on_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'}

View File

@ -1,78 +0,0 @@
from flask_login import current_user
from flask_socketio import join_room, leave_room, Namespace
from app import hashids
from app.decorators import socketio_login_required
from app.models import User
class UsersNamespace(Namespace):
@socketio_login_required
def on_get_user(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_user(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_user(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'}

View File

@ -11,11 +11,9 @@ nopaque.App = class App {
subscribeUser: {} subscribeUser: {}
}; };
this.sockets = { this.socket = io({transports: ['websocket'], upgrade: false});
users: io('/users', {transports: ['websocket'], upgrade: false})
};
this.sockets.users.on('patch_user', (patch) => {this.onPatch(patch);}); this.socket.on('patch_user', (patch) => {this.onPatch(patch);});
} }
getUser(userId) { getUser(userId) {
@ -23,10 +21,8 @@ nopaque.App = class App {
return this.#promises.getUser[userId]; return this.#promises.getUser[userId];
} }
let socket = this.sockets.users;
this.#promises.getUser[userId] = new Promise((resolve, reject) => { this.#promises.getUser[userId] = new Promise((resolve, reject) => {
socket.emit('get_user', userId, (response) => { this.socket.emit('users.get_user', userId, (response) => {
if (response.status === 200) { if (response.status === 200) {
this.data.users[userId] = response.body; this.data.users[userId] = response.body;
resolve(this.data.users[userId]); resolve(this.data.users[userId]);
@ -44,10 +40,8 @@ nopaque.App = class App {
return this.#promises.subscribeUser[userId]; return this.#promises.subscribeUser[userId];
} }
let socket = this.sockets.users;
this.#promises.subscribeUser[userId] = new Promise((resolve, reject) => { this.#promises.subscribeUser[userId] = new Promise((resolve, reject) => {
socket.emit('subscribe_user', userId, (response) => { this.socket.emit('users.subscribe_user', userId, (response) => {
if (response.status === 200) { if (response.status === 200) {
resolve(response); resolve(response);
} else { } else {

View File

@ -8,7 +8,7 @@ nopaque.resource_displays.ResourceDisplay = class ResourceDisplay {
if (this.userId) { if (this.userId) {
app.subscribeUser(this.userId) app.subscribeUser(this.userId)
.then((response) => { .then((response) => {
app.sockets.users.on('patch_user', (patch) => { app.socket.on('patch_user', (patch) => {
if (this.isInitialized) {this.onPatch(patch);} if (this.isInitialized) {this.onPatch(patch);}
}); });
}); });

View File

@ -15,7 +15,7 @@ nopaque.resource_lists.CorpusFileList = class CorpusFileList extends nopaque.res
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.subscribeUser(this.userId).then((response) => { app.subscribeUser(this.userId).then((response) => {
app.sockets.users.on('patch_user', (patch) => { app.socket.on('patch_user', (patch) => {
if (this.isInitialized) {this.onPatch(patch);} if (this.isInitialized) {this.onPatch(patch);}
}); });
}); });

View File

@ -13,7 +13,7 @@ nopaque.resource_lists.CorpusFollowerList = class CorpusFollowerList extends nop
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.subscribeUser(this.userId).then((response) => { app.subscribeUser(this.userId).then((response) => {
app.sockets.users.on('patch_user', (patch) => { app.socket.on('patch_user', (patch) => {
if (this.isInitialized) {this.onPatch(patch);} if (this.isInitialized) {this.onPatch(patch);}
}); });
}); });

View File

@ -12,7 +12,7 @@ nopaque.resource_lists.CorpusList = class CorpusList extends nopaque.resource_li
this.userId = listContainerElement.dataset.userId; this.userId = listContainerElement.dataset.userId;
if (this.userId === undefined) {return;} if (this.userId === undefined) {return;}
app.subscribeUser(this.userId).then((response) => { app.subscribeUser(this.userId).then((response) => {
app.sockets.users.on('patch_user', (patch) => { app.socket.on('patch_user', (patch) => {
if (this.isInitialized) {this.onPatch(patch);} if (this.isInitialized) {this.onPatch(patch);}
}); });
}); });

View File

@ -13,7 +13,7 @@ nopaque.resource_lists.JobList = class JobList extends nopaque.resource_lists.Re
this.userId = listContainerElement.dataset.userId; this.userId = listContainerElement.dataset.userId;
if (this.userId === undefined) {return;} if (this.userId === undefined) {return;}
app.subscribeUser(this.userId).then((response) => { app.subscribeUser(this.userId).then((response) => {
app.sockets.users.on('patch_user', (patch) => { app.socket.on('patch_user', (patch) => {
if (this.isInitialized) {this.onPatch(patch);} if (this.isInitialized) {this.onPatch(patch);}
}); });
}); });

View File

@ -9,7 +9,7 @@ nopaque.resource_lists.JobResultList = class JobResultList extends nopaque.resou
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.subscribeUser(this.userId).then((response) => { app.subscribeUser(this.userId).then((response) => {
app.sockets.users.on('patch_user', (patch) => { app.socket.on('patch_user', (patch) => {
if (this.isInitialized) {this.onPatch(patch);} if (this.isInitialized) {this.onPatch(patch);}
}); });
}); });

View File

@ -9,7 +9,7 @@ nopaque.resource_lists.SpaCyNLPPipelineModelList = class SpaCyNLPPipelineModelLi
this.userId = listContainerElement.dataset.userId; this.userId = listContainerElement.dataset.userId;
if (this.userId === undefined) {return;} if (this.userId === undefined) {return;}
app.subscribeUser(this.userId).then((response) => { app.subscribeUser(this.userId).then((response) => {
app.sockets.users.on('patch_user', (patch) => { app.socket.on('patch_user', (patch) => {
if (this.isInitialized) {this.onPatch(patch);} if (this.isInitialized) {this.onPatch(patch);}
}); });
}); });

View File

@ -9,7 +9,7 @@ nopaque.resource_lists.TesseractOCRPipelineModelList = class TesseractOCRPipelin
this.userId = listContainerElement.dataset.userId; this.userId = listContainerElement.dataset.userId;
if (this.userId === undefined) {return;} if (this.userId === undefined) {return;}
app.subscribeUser(this.userId).then((response) => { app.subscribeUser(this.userId).then((response) => {
app.sockets.users.on('patch_user', (patch) => { app.socket.on('patch_user', (patch) => {
if (this.isInitialized) {this.onPatch(patch);} if (this.isInitialized) {this.onPatch(patch);}
}); });
}); });

View File

@ -13,7 +13,7 @@
{# shown for large devices #} {# shown for large devices #}
<ul class="hide-on-med-and-down" style="margin-left: calc(57px + 1.5rem);"> <ul class="hide-on-med-and-down" style="margin-left: calc(57px + 1.5rem);">
{# dashboard #} {# dashboard #}
<li> <li {% if request.path == url_for('main.dashboard') %}class="active"{% endif %}>
<a href="{{ url_for('main.dashboard') }}"> <a href="{{ url_for('main.dashboard') }}">
<i class="material-icons left">dashboard</i> <i class="material-icons left">dashboard</i>
Dashboard Dashboard
@ -29,7 +29,7 @@
</li> </li>
{# contributions #} {# contributions #}
<li> <li {% if request.path == url_for('contributions.index') %}class="active"{% endif %}>
<a href="{{ url_for('contributions.index') }}"> <a href="{{ url_for('contributions.index') }}">
<i class="material-icons left">new_label</i> <i class="material-icons left">new_label</i>
Contributions Contributions
@ -37,7 +37,7 @@
</li> </li>
{# social #} {# social #}
<li> <li {% if request.path == url_for('main.social') %}class="active"{% endif %}>
<a href="{{ url_for('main.social') }}"> <a href="{{ url_for('main.social') }}">
<i class="material-icons left">groups</i> <i class="material-icons left">groups</i>
Social Social
@ -62,14 +62,14 @@
{# large devices #} {# large devices #}
<ul class="right hide-on-med-and-down" style="height: 64px;"> <ul class="right hide-on-med-and-down" style="height: 64px;">
{# manual #} {# manual #}
<li class="tooltipped" data-position="bottom" data-tooltip="Manual"> <li class="tooltipped {% if request.path == url_for('main.manual') %}active{% endif %}" data-position="bottom" data-tooltip="Manual">
<a href="{{ url_for('main.manual') }}"> <a href="{{ url_for('main.manual') }}">
<i class="material-icons">help_outline</i> <i class="material-icons">help_outline</i>
</a> </a>
</li> </li>
{# news #} {# news #}
<li class="tooltipped" data-position="bottom" data-tooltip="News"> <li class="tooltipped {% if request.path == url_for('main.news') %}active{% endif %}" data-position="bottom" data-tooltip="News">
<a href="{{ url_for('main.news') }}"> <a href="{{ url_for('main.news') }}">
<i class="material-icons">email</i> <i class="material-icons">email</i>
</a> </a>

View File

@ -5,21 +5,20 @@
<div class="row"> <div class="row">
<div class="col s12"> <div class="col s12">
<h1 id="title">{{ title }}</h1> <h1 id="title">{{ title }}</h1>
<p>Here you can see and edit the models that you have created. You can also create new models.</p> <p>
Upload your own language models into nopaque. This is useful for
working with different languages that are not available as standard in
nopaque or if a you want to work with a language model that you have
developed by yourself. Uploaded models can be found in the model list
of the corresponding service and can be used immediately.
</p>
</div> </div>
<div class="col s12"> <div class="col s12 l4">
<div class="card"> <h4>Tesseract OCR Pipeline Models</h4>
<div class="card-content">
<div class="spacy-nlp-pipeline-model-list" data-user-id="{{ current_user.hashid }}"></div>
</div>
<div class="card-action right-align">
<a href="{{ url_for('.spacy_nlp_pipeline_models.create') }}" class="btn waves-effect waves-light"><i class="material-icons left">add</i>Create</a>
</div>
</div>
</div> </div>
<div class="col s12"> <div class="col s12 l8">
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<div class="tesseract-ocr-pipeline-model-list" data-user-id="{{ current_user.hashid }}"></div> <div class="tesseract-ocr-pipeline-model-list" data-user-id="{{ current_user.hashid }}"></div>
@ -29,6 +28,21 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col s12 l4">
<h4>SpaCy NLP Pipeline Models</h4>
</div>
<div class="col s12 l8">
<div class="card">
<div class="card-content">
<div class="spacy-nlp-pipeline-model-list" data-user-id="{{ current_user.hashid }}"></div>
</div>
<div class="card-action right-align">
<a href="{{ url_for('.spacy_nlp_pipeline_models.create') }}" class="btn waves-effect waves-light"><i class="material-icons left">add</i>Create</a>
</div>
</div>
</div>
</div> </div>
</div> </div>
{% endblock page_content %} {% endblock page_content %}

View File

@ -74,7 +74,7 @@
{{ form.model.label }} {{ form.model.label }}
<span class="helper-text"> <span class="helper-text">
<a class="modal-trigger tooltipped" href="#models-modal" data-position="bottom" data-tooltip="See more information about models"><i class="material-icons">help_outline</i></a> <a class="modal-trigger tooltipped" href="#models-modal" data-position="bottom" data-tooltip="See more information about models"><i class="material-icons">help_outline</i></a>
<a class="tooltipped" href="{{ url_for('spacy_nlp_pipeline_models.create') }}" data-position="bottom" data-tooltip="Add your own spaCy NLP models"><i class="material-icons">new_label</i></a> <a class="tooltipped" href="{{ url_for('contributions.spacy_nlp_pipeline_models.create') }}" data-position="bottom" data-tooltip="Add your own spaCy NLP models"><i class="material-icons">new_label</i></a>
</span> </span>
</div> </div>
</div> </div>

View File

@ -56,7 +56,7 @@
{{ form.model.label }} {{ form.model.label }}
<span class="helper-text"> <span class="helper-text">
<a class="modal-trigger tooltipped" href="#models-modal" data-position="bottom" data-tooltip="See more information about models"><i class="material-icons">help_outline</i></a> <a class="modal-trigger tooltipped" href="#models-modal" data-position="bottom" data-tooltip="See more information about models"><i class="material-icons">help_outline</i></a>
<a class="tooltipped" href="{{ url_for('tesseract_ocr_pipeline_models.create') }}" data-position="bottom" data-tooltip="Add your own Tesseract OCR models"><i class="material-icons">new_label</i></a> <a class="tooltipped" href="{{ url_for('contributions.tesseract_ocr_pipeline_models.create') }}" data-position="bottom" data-tooltip="Add your own Tesseract OCR models"><i class="material-icons">new_label</i></a>
</span> </span>
{% for error in form.model.errors %} {% for error in form.model.errors %}
<span class="helper-text error-color-text">{{ error }}</span> <span class="helper-text error-color-text">{{ error }}</span>