Compare commits

...

5 Commits

Author SHA1 Message Date
Patrick Jentsch
4a29a52f2a Remove implementation of FileSize validator 2024-05-04 17:17:06 +02:00
Patrick Jentsch
991810cff5 Use HTML comments in navbar.html.j2 2024-05-04 17:04:25 +02:00
Patrick Jentsch
6025a4a606 add API back 2024-05-04 15:14:21 +02:00
Patrick Jentsch
e1cfd394fa move socketio decorators 2024-05-04 14:55:05 +02:00
Patrick Jentsch
882987ba68 fixes 2024-05-04 14:41:40 +02:00
50 changed files with 197 additions and 268 deletions

View File

@ -1,4 +1,4 @@
# from apifairy import APIFairy
from apifairy import APIFairy
from config import Config
from docker import DockerClient
from flask import Flask
@ -6,7 +6,7 @@ from flask_apscheduler import APScheduler
from flask_assets import Environment
from flask_login import LoginManager
from flask_mail import Mail
# from flask_marshmallow import Marshmallow
from flask_marshmallow import Marshmallow
from flask_migrate import Migrate
from flask_paranoid import Paranoid
from flask_socketio import SocketIO
@ -14,7 +14,7 @@ from flask_sqlalchemy import SQLAlchemy
from flask_hashids import Hashids
# apifairy = APIFairy()
apifairy = APIFairy()
assets = Environment()
db = SQLAlchemy()
docker_client = DockerClient()
@ -22,7 +22,7 @@ hashids = Hashids()
login = LoginManager()
login.login_view = 'auth.login'
login.login_message = 'Please log in to access this page.'
# ma = Marshmallow()
ma = Marshmallow()
mail = Mail()
migrate = Migrate(compare_type=True)
paranoid = Paranoid()
@ -45,12 +45,12 @@ def create_app(config: Config = Config) -> Flask:
registry=app.config['NOPAQUE_DOCKER_REGISTRY']
)
# apifairy.init_app(app)
apifairy.init_app(app)
assets.init_app(app)
db.init_app(app)
hashids.init_app(app)
login.init_app(app)
# ma.init_app(app)
ma.init_app(app)
mail.init_app(app)
migrate.init_app(app, db)
paranoid.init_app(app)
@ -63,8 +63,8 @@ def create_app(config: Config = Config) -> Flask:
from .admin import bp as admin_blueprint
app.register_blueprint(admin_blueprint, url_prefix='/admin')
# from .api import bp as api_blueprint
# app.register_blueprint(api_blueprint, url_prefix='/api')
from .api import bp as api_blueprint
app.register_blueprint(api_blueprint, url_prefix='/api')
from .auth import bp as auth_blueprint
app.register_blueprint(auth_blueprint)

View File

@ -1,7 +1,7 @@
from flask_login import current_user
from flask_socketio import disconnect, Namespace
from app import db, hashids
from app.extensions.flask_socketio_extras import admin_required
from app.decorators import socketio_admin_required
from app.models import User
@ -12,7 +12,7 @@ class AdminNamespace(Namespace):
disconnect()
@admin_required
@socketio_admin_required
def on_set_user_confirmed(self, user_hashid: str, confirmed_value: bool):
# Decode the user hashid
user_id = hashids.decode(user_hashid)

View File

@ -9,7 +9,7 @@ from inspect import signature
from threading import Lock
from typing import Callable, Dict, List, Optional
from app import db, docker_client, hashids, socketio
from app.extensions.flask_socketio_extras import login_required
from app.decorators import socketio_login_required
from app.models import Corpus, CorpusStatus
from . import extensions
@ -87,11 +87,11 @@ CQI_API_FUNCTION_NAMES: List[str] = [
class CQiNamespace(Namespace):
@login_required
@socketio_login_required
def on_connect(self):
pass
@login_required
@socketio_login_required
def on_init(self, db_corpus_hashid: str):
db_corpus_id: int = hashids.decode(db_corpus_hashid)
db_corpus: Optional[Corpus] = Corpus.query.get(db_corpus_id)
@ -134,7 +134,7 @@ class CQiNamespace(Namespace):
}
return {'code': 200, 'msg': 'OK'}
@login_required
@socketio_login_required
def on_exec(self, fn_name: str, fn_args: Dict = {}):
try:
cqi_client: CQiClient = session['cqi_over_sio']['cqi_client']

View File

@ -1,12 +1,12 @@
from flask_login import current_user
from flask_socketio import join_room
from app import hashids, socketio
from app.extensions.flask_socketio_extras import login_required
from app.decorators import socketio_login_required
from app.models import Corpus
@socketio.on('GET /corpora/<corpus_id>')
@login_required
@socketio_login_required
def get_corpus(corpus_hashid):
corpus_id = hashids.decode(corpus_hashid)
corpus = Corpus.query.get(corpus_id)
@ -29,7 +29,7 @@ def get_corpus(corpus_hashid):
@socketio.on('SUBSCRIBE /corpora/<corpus_id>')
@login_required
@socketio_login_required
def subscribe_corpus(corpus_hashid):
corpus_id = hashids.decode(corpus_hashid)
corpus = Corpus.query.get(corpus_id)

View File

@ -1,7 +1,6 @@
from flask import abort, current_app, request
from flask import abort, request
from flask_login import current_user
from functools import wraps
from threading import Thread
from typing import List, Union
from werkzeug.exceptions import NotAcceptable
from app.models import Permission
@ -22,22 +21,28 @@ def admin_required(f):
return permission_required(Permission.ADMINISTRATE)(f)
def background(f):
'''
' This decorator executes a function in a Thread.
' Decorated functions need to be executed within a code block where an
' app context exists.
'
' NOTE: An app object is passed as a keyword argument to the decorated
' function.
'''
def socketio_login_required(f):
@wraps(f)
def wrapped(*args, **kwargs):
kwargs['app'] = current_app._get_current_object()
thread = Thread(target=f, args=args, kwargs=kwargs)
thread.start()
return thread
return wrapped
def wrapper(*args, **kwargs):
if current_user.is_authenticated:
return f(*args, **kwargs)
return {'code': 401, 'body': 'Unauthorized'}
return wrapper
def socketio_permission_required(permission):
def decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
if not current_user.can(permission):
return {'code': 403, 'body': 'Forbidden'}
return f(*args, **kwargs)
return wrapper
return decorator
def socketio_admin_required(f):
return socketio_permission_required(Permission.ADMINISTRATE)(f)
def content_negotiation(

View File

@ -1 +0,0 @@

View File

@ -1,3 +0,0 @@
from .decorators import login_required
from .decorators import permission_required
from .decorators import admin_required

View File

@ -1,27 +0,0 @@
from flask_login import current_user
from functools import wraps
from app.models import Permission as UserPermission
def login_required(f):
@wraps(f)
def wrapper(*args, **kwargs):
if current_user.is_authenticated:
return f(*args, **kwargs)
return {'code': 401, 'body': 'Unauthorized'}
return wrapper
def permission_required(permission):
def decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
if not current_user.can(permission):
return {'code': 403, 'body': 'Forbidden'}
return f(*args, **kwargs)
return wrapper
return decorator
def admin_required(f):
return permission_required(UserPermission.ADMINISTRATE)(f)

View File

@ -1,14 +0,0 @@
from wtforms.validators import ValidationError
def FileSize(max_size_mb):
max_size_b = max_size_mb * 1024 * 1024
def file_length_check(form, field):
if len(field.data.read()) >= max_size_b:
raise ValidationError(
f'File size must be less or equal than {max_size_mb} MB'
)
field.data.seek(0)
return file_length_check

View File

@ -1,4 +1,4 @@
<h3 class="manual-chapter-title">Services</h5>
<h3 class="manual-chapter-title">Services</h3>
<div class="row">
<div class="col s12 m4">
<img alt="Services" class="materialboxed responsive-img" src="{{ url_for('static', filename='images/manual/services.png') }}">

View File

@ -0,0 +1,2 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">

View File

@ -1,35 +1,39 @@
<div class="navbar-fixed">
<nav>
<div class="nav-wrapper primary-color">
{# menu icon #}
{# shown for small/medium devices #}
{% if current_user.is_authenticated %}
<!-- menu icon -->
<!-- small/medium devices -->
<a href="#!" class="sidenav-trigger" data-target="sidenav"><i class="material-icons">menu</i></a>
{% endif %}
{# nopaque logo+wordmark #}
<a href="{{ url_for('main.index') }}" class="brand-logo center" style="height: 100%;">
<!-- nopaque logo+wordmark -->
<!-- small/medium devices -->
<a href="{{ url_for('main.index') }}" class="brand-logo center hide-on-large-only" style="height: 100%;">
<img src="{{ url_for('static', filename='images/nopaque_-_logo+wordmark.png') }}" alt="" class="py-3" style="height: 100%;">
</a>
<!-- large devices -->
<a href="{{ url_for('main.index') }}" class="brand-logo hide-on-med-and-down" style="height: 100%;">
<img src="{{ url_for('static', filename='images/nopaque_-_logo+wordmark.png') }}" alt="" class="p-3" style="height: 100%;">
</a>
{# right items #}
{# shown on large devices #}
<!-- right aligned navigation links -->
<!-- large devices -->
<ul class="right hide-on-med-and-down" style="height: 100%;">
{% if current_user.is_authenticated %}
{# avatar, username and email #}
{# shown for authenticated users #}
<!-- avatar, username and email -->
<li style="height: 100%;">
<a href="#!" class="dropdown-trigger no-autoinit" data-target="nav-account-dropdown-content" id="nav-account-dropdown-trigger" style="height: 100%;">
<img src="{{ url_for('users.user_avatar', user_id=current_user.id) }}" alt="" class="left circle py-3 mr-1" style="height: 100%;">
{{ current_user.username }} ({{ current_user.email }})
<img src="{{ url_for('users.user_avatar', user_id=current_user.id) }}" alt="" class="left circle py-3" style="height: 100%;">
<span class="ml-1">{{ current_user.username }} ({{ current_user.email }})</span>
</a>
</li>
{% else %}
{# log in and register items #}
{# shown for unauthenticated users on all devices #}
<!-- log in -->
<li {% if request.path == url_for('auth.login') %}class="active"{% endif %}>
<a href="{{ url_for('auth.login') }}"><i class="material-icons left">login</i>Log in</a>
</li>
<!-- register -->
<li {% if request.path == url_for('auth.register') %}class="active"{% endif %}>
<a href="{{ url_for('auth.register') }}"><i class="material-icons left">assignment</i>Register</a>
</li>

View File

@ -1,20 +1,20 @@
<script src="{{ url_for('static', filename='external/materialize/js/materialize.min.js') }}"></script>
<script src="{{ url_for('static', filename='external/JSON-Patch/js/fast-json-patch.min.js') }}"></script>
<script src="{{ url_for('static', filename='external/list.js/js/list.min.js') }}"></script>
<script src="{{ url_for('static', filename='external/pako/js/pako_inflate.min.js') }}"></script>
<script src="{{ url_for('static', filename='external/plotly.js/js/plotly.min.js') }}"></script>
<script src="{{ url_for('static', filename='external/socket.io/js/socket.io.min.js') }}"></script>
{%- assets
{% assets
filters='rjsmin',
output='gen/app.%(version)s.js',
'js/index.js',
'js/app.js',
'js/utils.js'
%}
-%}
<script src="{{ ASSET_URL }}"></script>
{%- endassets %}
{% endassets -%}
{%- assets
{% assets
filters='rjsmin',
output='gen/Forms.%(version)s.js',
'js/forms/index.js',
@ -22,22 +22,22 @@
'js/forms/create-contribution-form.js',
'js/forms/create-corpus-file-form.js',
'js/forms/create-job-form.js'
%}
-%}
<script src="{{ ASSET_URL }}"></script>
{%- endassets %}
{% endassets -%}
{%- assets
{% assets
filters='rjsmin',
output='gen/resource-displays.%(version)s.js',
'js/resource-displays/index.js',
'js/resource-displays/resource-display.js',
'js/resource-displays/corpus-display.js',
'js/resource-displays/job-display.js'
%}
-%}
<script src="{{ ASSET_URL }}"></script>
{%- endassets %}
{% endassets -%}
{%- assets
{% assets
filters='rjsmin',
output='gen/resource-lists.%(version)s.js',
'js/resource-lists/index.js',
@ -56,11 +56,11 @@
'js/resource-lists/public-user-list.js',
'js/resource-lists/spacy-nlp-pipeline-model-list.js',
'js/resource-lists/tesseract-ocr-pipeline-model-list.js'
%}
-%}
<script src="{{ ASSET_URL }}"></script>
{%- endassets %}
{% endassets -%}
{%- assets
{% assets
filters='rjsmin',
output='gen/requests.%(version)s.js',
'js/requests/index.js',
@ -69,11 +69,11 @@
'js/requests/corpora.js',
'js/requests/jobs.js',
'js/requests/users.js'
%}
-%}
<script src="{{ ASSET_URL }}"></script>
{%- endassets %}
{% endassets -%}
{%- assets
{% assets
filters='rjsmin',
output='gen/corpus-analysis.%(version)s.js',
'js/corpus-analysis/index.js',
@ -98,17 +98,16 @@
'js/corpus-analysis/concordance-extension.js',
'js/corpus-analysis/reader-extension.js',
'js/corpus-analysis/static-visualization-extension.js'
%}
-%}
<script src="{{ ASSET_URL }}"></script>
{%- endassets %}
{% endassets -%}
<script>
// TODO: Implement an app.run method and use this for all of the following
const app = new nopaque.App();
app.init();
// Check if the current user is authenticated
{%- 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 }};
@ -120,11 +119,10 @@
app.getUser(currentUserId, true, true)
.catch((error) => {throw JSON.stringify(error);});
// Check if the current user hasn't accepted the terms of use yet
{%- if not current_user.terms_of_use_accepted %}
{% if not current_user.terms_of_use_accepted -%}
M.Modal.getInstance(document.querySelector('#terms-of-use-modal')).open();
{%- endif %}
{%- endif %}
{% endif -%}
{% endif -%}
// Display flashed messages
for (let [category, message] of {{ get_flashed_messages(with_categories=True)|tojson }}) {

View File

@ -11,11 +11,11 @@
</li>
{# general items #}
{# {% if current_user.can('USE_API') %}
{% if current_user.can('USE_API') %}
<li>
<a class="waves-effect" href="{{ url_for('apifairy.docs') }}"><i class="material-icons">api</i>API</a>
</li>
{% endif %} #}
{% endif %}
<li>
<a class="waves-effect modal-trigger" href="#manual-modal"><i class="material-icons">school</i>Manual</a>
</li>

View File

@ -1,16 +1,18 @@
{% if current_user.is_authenticated %}
<link href="{{ url_for('static', filename='external/material-design-icons/css/material-icons.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='external/materialize/css/materialize.min.css') }}" rel="stylesheet">
{% if current_user.is_authenticated -%}
<link href="{{ url_for('static', filename='materialize/css/sidenav-fixed.css') }}" rel="stylesheet">
{% endif %}
{% endif -%}
<link href="{{ url_for('static', filename='materialize/css/sticky-footer.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='materialize/css/fixes.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='nopaque-icons/css/nopaque-icons.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/queryBuilder.css') }}" rel="stylesheet">
{%- assets
{% assets
filters='pyscss',
output='gen/app.%(version)s.css',
'css/colors.scss',
'css/helpers.scss',
'css/style.css'
%}
-%}
<link href="{{ ASSET_URL }}" rel="stylesheet">
{%- endassets %}
{% endassets -%}

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %}
{% import "materialize/wtf.html.j2" as wtf %}
{% import "wtf.html.j2" as wtf %}
{% block page_content %}
<div class="container">

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %}
{% import "materialize/wtf.html.j2" as wtf %}
{% import "wtf.html.j2" as wtf %}
{% block page_content %}

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %}
{% import "materialize/wtf.html.j2" as wtf %}
{% import "wtf.html.j2" as wtf %}
{% block page_content %}

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %}
{% import "materialize/wtf.html.j2" as wtf %}
{% import "wtf.html.j2" as wtf %}
{% block page_content %}
<div class="container">

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %}
{% import "materialize/wtf.html.j2" as wtf %}
{% import "wtf.html.j2" as wtf %}
{% block page_content %}
<div class="container">

View File

@ -1,60 +1,72 @@
{% extends "materialize/base.html.j2" %}
{% if title is not defined %}
{% set title = 'nopaque' %}
{% endif %}
{% block html_attribs %}lang="en"{% endblock html_attribs %}
{% block doc %}
<!DOCTYPE html>
<html {% block html_attribs %}lang="en"{% endblock html_attribs %}>
{% block html %}
<head>
{% block head %}
{% block metas %}
{% include "_base/metas.html.j2" %}
{% endblock metas %}
{% block head %}
{{ super() }}
<link href="{{ url_for('static', filename='images/nopaque_-_favicon.png') }}" rel="icon">
{% endblock head %}
<title {% block title_attribs %}{% endblock title_attribs %}>
{%- block title %}{{ title }}{% endblock title -%}
</title>
{% block metas %}
<meta charset="UTF-8">
{{ super() }}
{% endblock metas %}
<link href="{{ url_for('static', filename='images/nopaque_-_favicon.png') }}" rel="icon">
{% block title %}{{ title }}{% endblock title %}
{% block stylesheets %}
{% include "_base/stylesheets.html.j2" %}
{% endblock stylesheets %}
{% endblock head %}
</head>
<body {% block body_attribs %}{% endblock body_attribs %}>
{% block body %}
<header {% block header_attribs %}{% endblock header_attribs %}>
{% block header %}
{% block navbar %}
{% include "_base/navbar.html.j2" %}
{% endblock navbar %}
{% block sidenav %}
{% if current_user.is_authenticated %}
{% include "_base/sidenav.html.j2" %}
{% endif %}
{% endblock sidenav %}
{% endblock header %}
</header>
{% block styles %}
{{ super() }}
{% include "_base/styles.html.j2" %}
{% endblock styles %}
<main {% block main_attribs %}class="background-color"{% endblock main_attribs %}>
{% block main %}
{% block page_content %}{% endblock page_content %}
{% block navbar %}
{% include "_base/navbar.html.j2" %}
{% endblock navbar %}
<div id="dropdowns">
{% block dropdowns %}
{% include "_base/dropdowns.html.j2" %}
{% endblock dropdowns %}
</div>
{% block sidenav %}
{% if current_user.is_authenticated %}
{% include "_base/sidenav.html.j2" %}
{% endif %}
{% endblock sidenav %}
<div id="modals">
{% block modals %}
{% include "_base/modals.html.j2" %}
{% endblock modals %}
</div>
{% endblock main %}
</main>
{% block main_attribs %} class="background-color"{% endblock main_attribs %}
{% block main %}
{% block page_content %}{% endblock page_content %}
<div id="dropdowns">
{% block dropdowns %}
{% include "_base/dropdowns.html.j2" %}
{% endblock dropdowns %}
</div>
<footer {% block footer_attribs %}class="page-footer primary-variant-color"{% endblock footer_attribs %}>
{% block footer %}
{% include "_base/footer.html.j2" %}
{% endblock footer %}
</footer>
<div id="modals">
{% block modals %}
{% include "_base/modals.html.j2" %}
{% endblock modals %}
</div>
{% endblock main %}
{% block footer_attribs %} class="page-footer primary-variant-color"{% endblock footer_attribs %}
{% block footer %}
{% include "_base/footer.html.j2" %}
{% endblock footer %}
{% block scripts %}
{{ super() }}
{% include "_base/scripts.html.j2" %}
{% endblock scripts %}
{% block scripts %}
{% include "_base/scripts.html.j2" %}
{% endblock scripts %}
{% endblock body %}
</body>
{% endblock html %}
</html>
{% endblock doc %}

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %}
{% import "materialize/wtf.html.j2" as wtf %}
{% import "wtf.html.j2" as wtf %}
{% block main_attribs %} class="service-scheme" data-service="spacy-nlp-pipeline"{% endblock main_attribs %}

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %}
{% import "materialize/wtf.html.j2" as wtf %}
{% import "wtf.html.j2" as wtf %}
{% block main_attribs %} class="service-scheme" data-service="spacy-nlp-pipeline"{% endblock main_attribs %}

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %}
{% import "materialize/wtf.html.j2" as wtf %}
{% import "wtf.html.j2" as wtf %}
{% block page_content %}
<div class="container">

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %}
{% import "materialize/wtf.html.j2" as wtf %}
{% import "wtf.html.j2" as wtf %}
{% block main_attribs %} class="service-scheme" data-service="tesseract-ocr-pipeline"{% endblock main_attribs %}

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %}
{% import "materialize/wtf.html.j2" as wtf %}
{% import "wtf.html.j2" as wtf %}
{% block main_attribs %} class="service-scheme" data-service="tesseract-ocr-pipeline"{% endblock main_attribs %}

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %}
{% import "materialize/wtf.html.j2" as wtf %}
{% import "wtf.html.j2" as wtf %}
{% block page_content %}
<div class="container">

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %}
{% import "materialize/wtf.html.j2" as wtf %}
{% import "wtf.html.j2" as wtf %}
{% import 'corpora/_analysis/concordance.html.j2' as concordance_extension %}
{% import 'corpora/_analysis/reader.html.j2' as reader_extension %}
{% import 'corpora/_analysis/static_visualization.html.j2' as static_visualization_extension %}

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %}
{% import "materialize/wtf.html.j2" as wtf %}
{% import "wtf.html.j2" as wtf %}
{% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %}
{% import "materialize/wtf.html.j2" as wtf %}
{% import "wtf.html.j2" as wtf %}
{% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %}
{% import "materialize/wtf.html.j2" as wtf %}
{% import "wtf.html.j2" as wtf %}
{% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %}
{% import "materialize/wtf.html.j2" as wtf %}
{% import "wtf.html.j2" as wtf %}
{% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %}
{% import "materialize/wtf.html.j2" as wtf %}
{% import "wtf.html.j2" as wtf %}
{% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %}
{% import "materialize/wtf.html.j2" as wtf %}
{% import "wtf.html.j2" as wtf %}
{% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %}
{% import "materialize/wtf.html.j2" as wtf %}
{% import "wtf.html.j2" as wtf %}
{% block page_content %}
<div class="parallax-container" id="title">

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %}
{% import "materialize/wtf.html.j2" as wtf %}
{% import "wtf.html.j2" as wtf %}
{% block main_attribs %} class="social-area-color-lighten" {% endblock main_attribs %}

View File

@ -1,49 +0,0 @@
{% if title is not defined %}
{% set title = 'Title' %}
{% endif %}
{% block doc %}
<!DOCTYPE html>
<html {% block html_attribs %}{% endblock html_attribs %}>
{% block html %}
<head>
{% block head %}
<title>{% block title %}{{ title }}{% endblock title %}</title>
{% block metas %}
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{% endblock metas %}
{% block styles %}
<link rel="stylesheet" href="{{ url_for('static', filename='external/material-design-icons/css/material-icons.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='external/materialize/css/materialize.min.css') }}">
{% endblock styles %}
{% endblock head %}
</head>
<body {% block body_attribs %}{% endblock body_attribs %}>
{% block body %}
<header {% block header_attribs %}{% endblock header_attribs %}>
{% block header %}
{% block navbar %}
{% endblock navbar %}
{% block sidenav %}
{% endblock sidenav %}
{% endblock header %}
</header>
<main {% block main_attribs %}{% endblock main_attribs %}>
{% block main %}{% endblock main %}
</main>
<footer {% block footer_attribs %}{% endblock footer_attribs %}>
{% block footer %}{% endblock footer %}
</footer>
{% block scripts %}
<script src="{{ url_for('static', filename='external/materialize/js/materialize.min.js') }}"></script>
{% endblock scripts %}
{% endblock body %}
</body>
{% endblock html %}
</html>
{% endblock doc %}

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %}
{% import "materialize/wtf.html.j2" as wtf %}
{% import "wtf.html.j2" as wtf %}
{% block main_attribs %} class="service-scheme" data-service="file-setup-pipeline"{% endblock main_attribs %}

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %}
{% import "materialize/wtf.html.j2" as wtf %}
{% import "wtf.html.j2" as wtf %}
{% block main_attribs %} class="service-scheme" data-service="spacy-nlp-pipeline"{% endblock main_attribs %}

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %}
{% import "materialize/wtf.html.j2" as wtf %}
{% import "wtf.html.j2" as wtf %}
{% block main_attribs %} class="service-scheme" data-service="tesseract-ocr-pipeline"{% endblock main_attribs %}

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %}
{% import "materialize/wtf.html.j2" as wtf %}
{% import "wtf.html.j2" as wtf %}
{% block main_attribs %} class="service-scheme" data-service="transkribus-htr-pipeline"{% endblock main_attribs %}

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %}
{% import "materialize/wtf.html.j2" as wtf %}
{% import "wtf.html.j2" as wtf %}
{% block page_content %}
<div class="section container">

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %}
{% import "materialize/wtf.html.j2" as wtf %}
{% import "wtf.html.j2" as wtf %}
{% block page_content %}

View File

@ -1,12 +1,12 @@
from flask_login import current_user
from flask_socketio import join_room, leave_room
from app import hashids, socketio
from app.extensions.flask_socketio_extras import login_required
from app.decorators import socketio_login_required
from app.models import User
@socketio.on('GET /users/<user_id>')
@login_required
@socketio_login_required
def get_user(user_hashid):
user_id = hashids.decode(user_hashid)
user = User.query.get(user_id)
@ -22,7 +22,7 @@ def get_user(user_hashid):
@socketio.on('SUBSCRIBE /users/<user_id>')
@login_required
@socketio_login_required
def subscribe_user(user_hashid):
user_id = hashids.decode(user_hashid)
user = User.query.get(user_id)
@ -35,7 +35,7 @@ def subscribe_user(user_hashid):
@socketio.on('UNSUBSCRIBE /users/<user_id>')
@login_required
@socketio_login_required
def unsubscribe_user(user_hashid):
user_id = hashids.decode(user_hashid)
user = User.query.get(user_id)

View File

@ -1,12 +1,12 @@
from flask_login import current_user
from flask_socketio import join_room
from app import hashids, socketio
from app.extensions.flask_socketio_extras import admin_required, login_required
from app.decorators import socketio_admin_required, socketio_login_required
from app.models import User
@socketio.on('GET /users')
@admin_required
@socketio_admin_required
def get_users():
users = User.query.filter_by().all()
return {
@ -20,14 +20,14 @@ def get_users():
@socketio.on('SUBSCRIBE /users')
@admin_required
@socketio_admin_required
def subscribe_users():
join_room('/users')
return {'options': {'status': 200, 'statusText': 'OK'}}
@socketio.on('GET /users/<user_id>')
@login_required
@socketio_login_required
def get_user(user_hashid):
user_id = hashids.decode(user_hashid)
user = User.query.get(user_id)
@ -46,7 +46,7 @@ def get_user(user_hashid):
@socketio.on('SUBSCRIBE /users/<user_id>')
@login_required
@socketio_login_required
def subscribe_user(user_hashid):
user_id = hashids.decode(user_hashid)
user = User.query.get(user_id)
@ -59,7 +59,7 @@ def subscribe_user(user_hashid):
@socketio.on('GET /public_users')
@login_required
@socketio_login_required
def get_public_users():
users = User.query.filter_by(is_public=True).all()
return {
@ -76,14 +76,14 @@ def get_public_users():
@socketio.on('SUBSCRIBE /users')
@admin_required
@socketio_admin_required
def subscribe_users():
join_room('/public_users')
return {'options': {'status': 200, 'statusText': 'OK'}}
@socketio.on('GET /public_users/<user_id>')
@login_required
@socketio_login_required
def get_user(user_hashid):
user_id = hashids.decode(user_hashid)
user = User.query.filter_by(id=user_id, is_public=True).first()
@ -102,7 +102,7 @@ def get_user(user_hashid):
@socketio.on('SUBSCRIBE /public_users/<user_id>')
@login_required
@socketio_login_required
def subscribe_user(user_hashid):
user_id = hashids.decode(user_hashid)
user = User.query.filter_by(id=user_id, is_public=True).first()

View File

@ -1,5 +1,5 @@
from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileRequired
from flask_wtf.file import FileField, FileRequired, FileSize
from wtforms import (
PasswordField,
SelectField,
@ -16,7 +16,6 @@ from wtforms.validators import (
Regexp
)
from app.models import User, UserSettingJobStatusMailNotificationLevel
from app.extensions.wtforms_extras.validators import FileSize
class UpdateAccountInformationForm(FlaskForm):
@ -99,7 +98,7 @@ class UpdateProfileInformationForm(FlaskForm):
class UpdateAvatarForm(FlaskForm):
avatar = FileField('File', validators=[FileRequired(), FileSize(2)])
avatar = FileField('File', validators=[FileRequired(), FileSize(2_000_000)])
submit = SubmitField()
def validate_avatar(self, field):

View File

@ -20,7 +20,7 @@ Flask-Hashids==1.0.3
Flask-HTTPAuth==4.8.0
Flask-Login==0.6.3
Flask-Mail==0.9.1
flask-marshmallow==1.2.1
flask-marshmallow==0.14.0
Flask-Migrate==4.0.7
Flask-Paranoid==0.3.0
Flask-SocketIO==5.3.6
@ -36,6 +36,7 @@ joblib==1.4.0
Mako==1.3.3
MarkupSafe==2.1.5
marshmallow==3.21.1
marshmallow-sqlalchemy==1.0.0
nltk==3.8.1
packaging==24.0
psycopg2==2.9.9
@ -53,7 +54,7 @@ requests==2.31.0
simple-websocket==1.0.0
six==1.16.0
SQLAlchemy==1.4.52
tqdm==4.66.2
tqdm==4.66.4
typing_extensions==4.11.0
tzlocal==5.2
urllib3==2.2.1

View File

@ -7,16 +7,16 @@ Flask==2.3.3
Flask-APScheduler
Flask-Assets
Flask-Hashids
# Flask-HTTPAuth
Flask-HTTPAuth
Flask-Login
Flask-Mail
# flask-marshmallow
flask-marshmallow==0.14.0
Flask-Migrate
Flask-Paranoid
Flask-SocketIO
Flask-SQLAlchemy==2.5.1
Flask-WTF
# marshmallow-sqlalchemy
marshmallow-sqlalchemy
nltk
psycopg2
PyJWT