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

View File

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

View File

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

View File

@ -1,12 +1,12 @@
from flask_login import current_user from flask_login import current_user
from flask_socketio import join_room from flask_socketio import join_room
from app import hashids, socketio 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 from app.models import Corpus
@socketio.on('GET /corpora/<corpus_id>') @socketio.on('GET /corpora/<corpus_id>')
@login_required @socketio_login_required
def get_corpus(corpus_hashid): def get_corpus(corpus_hashid):
corpus_id = hashids.decode(corpus_hashid) corpus_id = hashids.decode(corpus_hashid)
corpus = Corpus.query.get(corpus_id) corpus = Corpus.query.get(corpus_id)
@ -29,7 +29,7 @@ def get_corpus(corpus_hashid):
@socketio.on('SUBSCRIBE /corpora/<corpus_id>') @socketio.on('SUBSCRIBE /corpora/<corpus_id>')
@login_required @socketio_login_required
def subscribe_corpus(corpus_hashid): def subscribe_corpus(corpus_hashid):
corpus_id = hashids.decode(corpus_hashid) corpus_id = hashids.decode(corpus_hashid)
corpus = Corpus.query.get(corpus_id) 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 flask_login import current_user
from functools import wraps from functools import wraps
from threading import Thread
from typing import List, Union from typing import List, Union
from werkzeug.exceptions import NotAcceptable from werkzeug.exceptions import NotAcceptable
from app.models import Permission from app.models import Permission
@ -22,22 +21,28 @@ def admin_required(f):
return permission_required(Permission.ADMINISTRATE)(f) return permission_required(Permission.ADMINISTRATE)(f)
def background(f): def socketio_login_required(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.
'''
@wraps(f) @wraps(f)
def wrapped(*args, **kwargs): def wrapper(*args, **kwargs):
kwargs['app'] = current_app._get_current_object() if current_user.is_authenticated:
thread = Thread(target=f, args=args, kwargs=kwargs) return f(*args, **kwargs)
thread.start() return {'code': 401, 'body': 'Unauthorized'}
return thread return wrapper
return wrapped
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( 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="row">
<div class="col s12 m4"> <div class="col s12 m4">
<img alt="Services" class="materialboxed responsive-img" src="{{ url_for('static', filename='images/manual/services.png') }}"> <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"> <div class="navbar-fixed">
<nav> <nav>
<div class="nav-wrapper primary-color"> <div class="nav-wrapper primary-color">
{# menu icon #}
{# shown for small/medium devices #}
{% if current_user.is_authenticated %} {% 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> <a href="#!" class="sidenav-trigger" data-target="sidenav"><i class="material-icons">menu</i></a>
{% endif %} {% endif %}
{# nopaque logo+wordmark #} <!-- nopaque logo+wordmark -->
<a href="{{ url_for('main.index') }}" class="brand-logo center" style="height: 100%;"> <!-- 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%;"> <img src="{{ url_for('static', filename='images/nopaque_-_logo+wordmark.png') }}" alt="" class="py-3" style="height: 100%;">
</a> </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 #} <!-- right aligned navigation links -->
{# shown on large devices #} <!-- large devices -->
<ul class="right hide-on-med-and-down" style="height: 100%;"> <ul class="right hide-on-med-and-down" style="height: 100%;">
{% if current_user.is_authenticated %} {% if current_user.is_authenticated %}
{# avatar, username and email #} <!-- avatar, username and email -->
{# shown for authenticated users #}
<li style="height: 100%;"> <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%;"> <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%;"> <img src="{{ url_for('users.user_avatar', user_id=current_user.id) }}" alt="" class="left circle py-3" style="height: 100%;">
{{ current_user.username }} ({{ current_user.email }}) <span class="ml-1">{{ current_user.username }} ({{ current_user.email }})</span>
</a> </a>
</li> </li>
{% else %} {% else %}
{# log in and register items #} <!-- log in -->
{# shown for unauthenticated users on all devices #}
<li {% if request.path == url_for('auth.login') %}class="active"{% endif %}> <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> <a href="{{ url_for('auth.login') }}"><i class="material-icons left">login</i>Log in</a>
</li> </li>
<!-- register -->
<li {% if request.path == url_for('auth.register') %}class="active"{% endif %}> <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> <a href="{{ url_for('auth.register') }}"><i class="material-icons left">assignment</i>Register</a>
</li> </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/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/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/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/plotly.js/js/plotly.min.js') }}"></script>
<script src="{{ url_for('static', filename='external/socket.io/js/socket.io.min.js') }}"></script> <script src="{{ url_for('static', filename='external/socket.io/js/socket.io.min.js') }}"></script>
{% assets
{%- assets
filters='rjsmin', filters='rjsmin',
output='gen/app.%(version)s.js', output='gen/app.%(version)s.js',
'js/index.js', 'js/index.js',
'js/app.js', 'js/app.js',
'js/utils.js' 'js/utils.js'
%} -%}
<script src="{{ ASSET_URL }}"></script> <script src="{{ ASSET_URL }}"></script>
{%- endassets %} {% endassets -%}
{%- assets {% assets
filters='rjsmin', filters='rjsmin',
output='gen/Forms.%(version)s.js', output='gen/Forms.%(version)s.js',
'js/forms/index.js', 'js/forms/index.js',
@ -22,22 +22,22 @@
'js/forms/create-contribution-form.js', 'js/forms/create-contribution-form.js',
'js/forms/create-corpus-file-form.js', 'js/forms/create-corpus-file-form.js',
'js/forms/create-job-form.js' 'js/forms/create-job-form.js'
%} -%}
<script src="{{ ASSET_URL }}"></script> <script src="{{ ASSET_URL }}"></script>
{%- endassets %} {% endassets -%}
{%- assets {% assets
filters='rjsmin', filters='rjsmin',
output='gen/resource-displays.%(version)s.js', output='gen/resource-displays.%(version)s.js',
'js/resource-displays/index.js', 'js/resource-displays/index.js',
'js/resource-displays/resource-display.js', 'js/resource-displays/resource-display.js',
'js/resource-displays/corpus-display.js', 'js/resource-displays/corpus-display.js',
'js/resource-displays/job-display.js' 'js/resource-displays/job-display.js'
%} -%}
<script src="{{ ASSET_URL }}"></script> <script src="{{ ASSET_URL }}"></script>
{%- endassets %} {% endassets -%}
{%- assets {% assets
filters='rjsmin', filters='rjsmin',
output='gen/resource-lists.%(version)s.js', output='gen/resource-lists.%(version)s.js',
'js/resource-lists/index.js', 'js/resource-lists/index.js',
@ -56,11 +56,11 @@
'js/resource-lists/public-user-list.js', 'js/resource-lists/public-user-list.js',
'js/resource-lists/spacy-nlp-pipeline-model-list.js', 'js/resource-lists/spacy-nlp-pipeline-model-list.js',
'js/resource-lists/tesseract-ocr-pipeline-model-list.js' 'js/resource-lists/tesseract-ocr-pipeline-model-list.js'
%} -%}
<script src="{{ ASSET_URL }}"></script> <script src="{{ ASSET_URL }}"></script>
{%- endassets %} {% endassets -%}
{%- assets {% assets
filters='rjsmin', filters='rjsmin',
output='gen/requests.%(version)s.js', output='gen/requests.%(version)s.js',
'js/requests/index.js', 'js/requests/index.js',
@ -69,11 +69,11 @@
'js/requests/corpora.js', 'js/requests/corpora.js',
'js/requests/jobs.js', 'js/requests/jobs.js',
'js/requests/users.js' 'js/requests/users.js'
%} -%}
<script src="{{ ASSET_URL }}"></script> <script src="{{ ASSET_URL }}"></script>
{%- endassets %} {% endassets -%}
{%- assets {% assets
filters='rjsmin', filters='rjsmin',
output='gen/corpus-analysis.%(version)s.js', output='gen/corpus-analysis.%(version)s.js',
'js/corpus-analysis/index.js', 'js/corpus-analysis/index.js',
@ -98,17 +98,16 @@
'js/corpus-analysis/concordance-extension.js', 'js/corpus-analysis/concordance-extension.js',
'js/corpus-analysis/reader-extension.js', 'js/corpus-analysis/reader-extension.js',
'js/corpus-analysis/static-visualization-extension.js' 'js/corpus-analysis/static-visualization-extension.js'
%} -%}
<script src="{{ ASSET_URL }}"></script> <script src="{{ ASSET_URL }}"></script>
{%- 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();
// 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 // TODO: Set this as a property of the app object
const currentUserId = {{ current_user.hashid|tojson }}; const currentUserId = {{ current_user.hashid|tojson }};
@ -120,11 +119,10 @@
app.getUser(currentUserId, true, true) app.getUser(currentUserId, true, true)
.catch((error) => {throw JSON.stringify(error);}); .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(); M.Modal.getInstance(document.querySelector('#terms-of-use-modal')).open();
{%- endif %} {% endif -%}
{%- endif %} {% endif -%}
// Display flashed messages // Display flashed messages
for (let [category, message] of {{ get_flashed_messages(with_categories=True)|tojson }}) { for (let [category, message] of {{ get_flashed_messages(with_categories=True)|tojson }}) {

View File

@ -11,11 +11,11 @@
</li> </li>
{# general items #} {# general items #}
{# {% if current_user.can('USE_API') %} {% if current_user.can('USE_API') %}
<li> <li>
<a class="waves-effect" href="{{ url_for('apifairy.docs') }}"><i class="material-icons">api</i>API</a> <a class="waves-effect" href="{{ url_for('apifairy.docs') }}"><i class="material-icons">api</i>API</a>
</li> </li>
{% endif %} #} {% endif %}
<li> <li>
<a class="waves-effect modal-trigger" href="#manual-modal"><i class="material-icons">school</i>Manual</a> <a class="waves-effect modal-trigger" href="#manual-modal"><i class="material-icons">school</i>Manual</a>
</li> </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"> <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/sticky-footer.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='materialize/css/fixes.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='nopaque-icons/css/nopaque-icons.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/queryBuilder.css') }}" rel="stylesheet"> <link href="{{ url_for('static', filename='css/queryBuilder.css') }}" rel="stylesheet">
{%- assets {% assets
filters='pyscss', filters='pyscss',
output='gen/app.%(version)s.css', output='gen/app.%(version)s.css',
'css/colors.scss', 'css/colors.scss',
'css/helpers.scss', 'css/helpers.scss',
'css/style.css' 'css/style.css'
%} -%}
<link href="{{ ASSET_URL }}" rel="stylesheet"> <link href="{{ ASSET_URL }}" rel="stylesheet">
{%- endassets %} {% endassets -%}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %} {% 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 %} {% block main_attribs %} class="service-scheme" data-service="spacy-nlp-pipeline"{% endblock main_attribs %}

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %} {% 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 %} {% block main_attribs %} class="service-scheme" data-service="spacy-nlp-pipeline"{% endblock main_attribs %}

View File

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

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %} {% 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 %} {% block main_attribs %} class="service-scheme" data-service="tesseract-ocr-pipeline"{% endblock main_attribs %}

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %} {% 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 %} {% block main_attribs %} class="service-scheme" data-service="tesseract-ocr-pipeline"{% endblock main_attribs %}

View File

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

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %} {% 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/concordance.html.j2' as concordance_extension %}
{% import 'corpora/_analysis/reader.html.j2' as reader_extension %} {% import 'corpora/_analysis/reader.html.j2' as reader_extension %}
{% import 'corpora/_analysis/static_visualization.html.j2' as static_visualization_extension %} {% import 'corpora/_analysis/static_visualization.html.j2' as static_visualization_extension %}

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %} {% 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 %} {% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %} {% 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 %} {% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %} {% 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 %} {% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %} {% 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 %} {% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %} {% 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 %} {% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %} {% 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 %} {% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}

View File

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

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %} {% 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 %} {% 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" %} {% 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 %} {% block main_attribs %} class="service-scheme" data-service="file-setup-pipeline"{% endblock main_attribs %}

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %} {% 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 %} {% block main_attribs %} class="service-scheme" data-service="spacy-nlp-pipeline"{% endblock main_attribs %}

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %} {% 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 %} {% block main_attribs %} class="service-scheme" data-service="tesseract-ocr-pipeline"{% endblock main_attribs %}

View File

@ -1,5 +1,5 @@
{% extends "base.html.j2" %} {% 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 %} {% block main_attribs %} class="service-scheme" data-service="transkribus-htr-pipeline"{% endblock main_attribs %}

View File

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

View File

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

View File

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

View File

@ -1,12 +1,12 @@
from flask_login import current_user from flask_login import current_user
from flask_socketio import join_room from flask_socketio import join_room
from app import hashids, socketio 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 from app.models import User
@socketio.on('GET /users') @socketio.on('GET /users')
@admin_required @socketio_admin_required
def get_users(): def get_users():
users = User.query.filter_by().all() users = User.query.filter_by().all()
return { return {
@ -20,14 +20,14 @@ def get_users():
@socketio.on('SUBSCRIBE /users') @socketio.on('SUBSCRIBE /users')
@admin_required @socketio_admin_required
def subscribe_users(): def subscribe_users():
join_room('/users') join_room('/users')
return {'options': {'status': 200, 'statusText': 'OK'}} return {'options': {'status': 200, 'statusText': 'OK'}}
@socketio.on('GET /users/<user_id>') @socketio.on('GET /users/<user_id>')
@login_required @socketio_login_required
def get_user(user_hashid): def get_user(user_hashid):
user_id = hashids.decode(user_hashid) user_id = hashids.decode(user_hashid)
user = User.query.get(user_id) user = User.query.get(user_id)
@ -46,7 +46,7 @@ def get_user(user_hashid):
@socketio.on('SUBSCRIBE /users/<user_id>') @socketio.on('SUBSCRIBE /users/<user_id>')
@login_required @socketio_login_required
def subscribe_user(user_hashid): def subscribe_user(user_hashid):
user_id = hashids.decode(user_hashid) user_id = hashids.decode(user_hashid)
user = User.query.get(user_id) user = User.query.get(user_id)
@ -59,7 +59,7 @@ def subscribe_user(user_hashid):
@socketio.on('GET /public_users') @socketio.on('GET /public_users')
@login_required @socketio_login_required
def get_public_users(): def get_public_users():
users = User.query.filter_by(is_public=True).all() users = User.query.filter_by(is_public=True).all()
return { return {
@ -76,14 +76,14 @@ def get_public_users():
@socketio.on('SUBSCRIBE /users') @socketio.on('SUBSCRIBE /users')
@admin_required @socketio_admin_required
def subscribe_users(): def subscribe_users():
join_room('/public_users') join_room('/public_users')
return {'options': {'status': 200, 'statusText': 'OK'}} return {'options': {'status': 200, 'statusText': 'OK'}}
@socketio.on('GET /public_users/<user_id>') @socketio.on('GET /public_users/<user_id>')
@login_required @socketio_login_required
def get_user(user_hashid): def get_user(user_hashid):
user_id = hashids.decode(user_hashid) user_id = hashids.decode(user_hashid)
user = User.query.filter_by(id=user_id, is_public=True).first() 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>') @socketio.on('SUBSCRIBE /public_users/<user_id>')
@login_required @socketio_login_required
def subscribe_user(user_hashid): def subscribe_user(user_hashid):
user_id = hashids.decode(user_hashid) user_id = hashids.decode(user_hashid)
user = User.query.filter_by(id=user_id, is_public=True).first() 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 import FlaskForm
from flask_wtf.file import FileField, FileRequired from flask_wtf.file import FileField, FileRequired, FileSize
from wtforms import ( from wtforms import (
PasswordField, PasswordField,
SelectField, SelectField,
@ -16,7 +16,6 @@ from wtforms.validators import (
Regexp Regexp
) )
from app.models import User, UserSettingJobStatusMailNotificationLevel from app.models import User, UserSettingJobStatusMailNotificationLevel
from app.extensions.wtforms_extras.validators import FileSize
class UpdateAccountInformationForm(FlaskForm): class UpdateAccountInformationForm(FlaskForm):
@ -99,7 +98,7 @@ class UpdateProfileInformationForm(FlaskForm):
class UpdateAvatarForm(FlaskForm): class UpdateAvatarForm(FlaskForm):
avatar = FileField('File', validators=[FileRequired(), FileSize(2)]) avatar = FileField('File', validators=[FileRequired(), FileSize(2_000_000)])
submit = SubmitField() submit = SubmitField()
def validate_avatar(self, field): def validate_avatar(self, field):

View File

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

View File

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