Merge branch 'template-rework' of gitlab.ub.uni-bielefeld.de:sfb1288inf/nopaque into template-rework

This commit is contained in:
Patrick Jentsch 2020-10-26 10:37:02 +01:00
commit 4f8426c0b0
42 changed files with 487 additions and 448 deletions

View File

@ -28,22 +28,23 @@ def create_app(config_name):
socketio.init_app( socketio.init_app(
app, message_queue=config[config_name].SOCKETIO_MESSAGE_QUEUE_URI) app, message_queue=config[config_name].SOCKETIO_MESSAGE_QUEUE_URI)
from . import events with app.app_context():
from .admin import admin as admin_blueprint from . import events
from .admin import admin as admin_blueprint
from .auth import auth as auth_blueprint
from .corpora import corpora as corpora_blueprint
from .errors import errors as errors_blueprint
from .jobs import jobs as jobs_blueprint
from .main import main as main_blueprint
from .services import services as services_blueprint
from .settings import settings as settings_blueprint
app.register_blueprint(admin_blueprint, url_prefix='/admin') app.register_blueprint(admin_blueprint, url_prefix='/admin')
from .auth import auth as auth_blueprint
app.register_blueprint(auth_blueprint, url_prefix='/auth') app.register_blueprint(auth_blueprint, url_prefix='/auth')
from .corpora import corpora as corpora_blueprint
app.register_blueprint(corpora_blueprint, url_prefix='/corpora') app.register_blueprint(corpora_blueprint, url_prefix='/corpora')
from .errors import errors as errors_blueprint
app.register_blueprint(errors_blueprint) app.register_blueprint(errors_blueprint)
from .jobs import jobs as jobs_blueprint
app.register_blueprint(jobs_blueprint, url_prefix='/jobs') app.register_blueprint(jobs_blueprint, url_prefix='/jobs')
from .main import main as main_blueprint
app.register_blueprint(main_blueprint) app.register_blueprint(main_blueprint)
from .profile import profile as profile_blueprint
app.register_blueprint(profile_blueprint, url_prefix='/profile')
from .services import services as services_blueprint
app.register_blueprint(services_blueprint, url_prefix='/services') app.register_blueprint(services_blueprint, url_prefix='/services')
app.register_blueprint(settings_blueprint, url_prefix='/settings')
return app return app

View File

@ -8,10 +8,10 @@ from ..models import Role, User
from ..profile import tasks as profile_tasks from ..profile import tasks as profile_tasks
@admin.route('/') @admin.route('/users')
@login_required @login_required
@admin_required @admin_required
def index(): def users():
users = User.query.all() users = User.query.all()
users = [dict(username=u.username, users = [dict(username=u.username,
email=u.email, email=u.email,
@ -19,21 +19,18 @@ def index():
confirmed=u.confirmed, confirmed=u.confirmed,
id=u.id) id=u.id)
for u in users] for u in users]
return render_template('admin/index.html.j2', return render_template('admin/users.html.j2', title='Users', users=users)
title='Administration tools',
users=users)
@admin.route('/user/<int:user_id>') @admin.route('/users/<int:user_id>')
@login_required @login_required
@admin_required @admin_required
def user(user_id): def user(user_id):
user = User.query.get_or_404(user_id) user = User.query.get_or_404(user_id)
return render_template('admin/user.html.j2', title='Administration: User', return render_template('admin/user.html.j2', title='Edit user', user=user)
user=user)
@admin.route('/user/<int:user_id>/delete') @admin.route('/users/<int:user_id>/delete')
@login_required @login_required
@admin_required @admin_required
def delete_user(user_id): def delete_user(user_id):
@ -42,7 +39,7 @@ def delete_user(user_id):
return redirect(url_for('admin.index')) return redirect(url_for('admin.index'))
@admin.route('/user/<int:user_id>/edit', methods=['GET', 'POST']) @admin.route('/users/<int:user_id>/edit', methods=['GET', 'POST'])
@login_required @login_required
@admin_required @admin_required
def edit_user(user_id): def edit_user(user_id):
@ -63,4 +60,5 @@ def edit_user(user_id):
edit_user_form.role.data = user.role_id edit_user_form.role.data = user.role_id
return render_template('admin/edit_user.html.j2', return render_template('admin/edit_user.html.j2',
edit_user_form=edit_user_form, edit_user_form=edit_user_form,
title='Administration: Edit user', user=user) title='Edit user',
user=user)

View File

@ -1,3 +1,4 @@
from flask import current_app
from ..models import User from ..models import User
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import (BooleanField, PasswordField, StringField, SubmitField, from wtforms import (BooleanField, PasswordField, StringField, SubmitField,
@ -17,9 +18,9 @@ class RegistrationForm(FlaskForm):
username = StringField( username = StringField(
'Username', 'Username',
validators=[DataRequired(), Length(1, 64), validators=[DataRequired(), Length(1, 64),
Regexp('^[A-Za-z][A-Za-z0-9_.]*$', 0, Regexp(current_app.config['ALLOWED_USERNAME_REGEX'],
'Usernames must have only letters, numbers, dots ' message='Usernames must have only letters, numbers,'
'or underscores')] ' dots or underscores')]
) )
password = PasswordField( password = PasswordField(
'Password', 'Password',

View File

@ -1,52 +0,0 @@
from flask_wtf import FlaskForm
from wtforms import (BooleanField, PasswordField, SelectField, StringField,
SubmitField, ValidationError)
from wtforms.validators import DataRequired, Email, EqualTo
class EditEmailForm(FlaskForm):
email = StringField('New email', validators=[Email(), DataRequired()])
save_email = SubmitField('Save email')
class EditGeneralSettingsForm(FlaskForm):
dark_mode = BooleanField('Dark mode')
job_status_mail_notifications = SelectField(
'Job status mail notifications',
choices=[('', 'Choose your option'),
('all', 'Notify on all status changes'),
('end', 'Notify only when a job ended'),
('none', 'No status update notifications')],
validators=[DataRequired()])
job_status_site_notifications = SelectField(
'Job status site notifications',
choices=[('', 'Choose your option'),
('all', 'Notify on all status changes'),
('end', 'Notify only when a job ended'),
('none', 'No status update notifications')],
validators=[DataRequired()])
save_settings = SubmitField('Save settings')
class EditPasswordForm(FlaskForm):
current_password = PasswordField('Current password',
validators=[DataRequired()])
password = PasswordField(
'New password',
validators=[DataRequired(), EqualTo('password_confirmation',
message='Passwords must match.')]
)
password_confirmation = PasswordField(
'Password confirmation',
validators=[DataRequired(),
EqualTo('password', message='Passwords must match.')]
)
save_password = SubmitField('Save password')
def __init__(self, user, *args, **kwargs):
super(EditPasswordForm, self).__init__(*args, **kwargs)
self.user = user
def validate_current_password(self, field):
if not self.user.verify_password(field.data):
raise ValidationError('Invalid password.')

View File

@ -1,13 +0,0 @@
from .. import db
from ..decorators import background
from ..models import User
@background
def delete_user(user_id, *args, **kwargs):
with kwargs['app'].app_context():
user = User.query.get(user_id)
if user is None:
raise Exception('User {} not found'.format(user_id))
user.delete()
db.session.commit()

View File

@ -1,69 +0,0 @@
from flask import flash, redirect, render_template, url_for
from flask_login import current_user, login_required, logout_user
from . import profile
from . import tasks
from .forms import EditEmailForm, EditGeneralSettingsForm, EditPasswordForm
from .. import db
@profile.route('/settings', methods=['GET', 'POST'])
@login_required
def settings():
edit_email_form = EditEmailForm(prefix='edit-email-form')
edit_general_settings_form = EditGeneralSettingsForm(
prefix='edit-general-settings-form')
edit_password_form = EditPasswordForm(prefix='edit-password-form',
user=current_user)
# Check if edit_email_form is submitted and valid
if (edit_email_form.save_email.data
and edit_email_form.validate_on_submit()):
db.session.add(current_user)
db.session.commit()
flash('Your email address has been updated.')
return redirect(url_for('profile.settings'))
# Check if edit_settings_form is submitted and valid
if (edit_general_settings_form.save_settings.data
and edit_general_settings_form.validate_on_submit()):
current_user.setting_dark_mode = \
edit_general_settings_form.dark_mode.data
current_user.setting_job_status_mail_notifications = \
edit_general_settings_form.job_status_mail_notifications.data
current_user.setting_job_status_site_notifications = \
edit_general_settings_form.job_status_site_notifications.data
db.session.add(current_user)
db.session.commit()
flash('Your settings have been updated.')
return redirect(url_for('profile.settings'))
# Check if edit_password_form is submitted and valid
if (edit_password_form.save_password.data
and edit_password_form.validate_on_submit()):
current_user.password = edit_password_form.password.data
db.session.add(current_user)
db.session.commit()
flash('Your password has been updated.')
return redirect(url_for('profile.settings'))
# If no form is submitted or valid, fill out fields with current values
edit_email_form.email.data = current_user.email
edit_general_settings_form.dark_mode.data = current_user.setting_dark_mode
edit_general_settings_form.job_status_site_notifications.data = \
current_user.setting_job_status_site_notifications
edit_general_settings_form.job_status_mail_notifications.data = \
current_user.setting_job_status_mail_notifications
return render_template(
'profile/settings.html.j2',
edit_email_form=edit_email_form,
edit_password_form=edit_password_form,
edit_general_settings_form=edit_general_settings_form,
title='Settings')
@profile.route('/delete', methods=['GET', 'POST'])
@login_required
def delete():
"""
View to delete yourslef and all associated data.
"""
tasks.delete_user(current_user.id)
logout_user()
flash('Your account has been deleted!')
return redirect(url_for('main.index'))

View File

@ -1,5 +1,5 @@
from flask import Blueprint from flask import Blueprint
profile = Blueprint('profile', __name__) settings = Blueprint('settings', __name__)
from . import views # noqa from . import views # noqa

86
web/app/settings/forms.py Normal file
View File

@ -0,0 +1,86 @@
from flask import current_app
from flask_login import current_user
from flask_wtf import FlaskForm
from wtforms import (BooleanField, PasswordField, SelectField, StringField,
SubmitField, ValidationError)
from wtforms.validators import DataRequired, Email, EqualTo, Length, Regexp
class ChangePasswordForm(FlaskForm):
password = PasswordField('Old password', validators=[DataRequired()])
new_password = PasswordField(
'New password',
validators=[DataRequired(), EqualTo('password_confirmation',
message='Passwords must match.')]
)
new_password2 = PasswordField(
'Confirm new password', validators=[DataRequired()])
submit = SubmitField('Change password')
def __init__(self, user=current_user, *args, **kwargs):
super().__init__(*args, **kwargs)
self.user = user
def validate_current_password(self, field):
if not self.user.verify_password(field.data):
raise ValidationError('Invalid password.')
class EditGeneralSettingsForm(FlaskForm):
dark_mode = BooleanField('Dark mode')
email = StringField('E-Mail',
validators=[DataRequired(), Length(1, 254), Email()])
username = StringField(
'Benutzername',
validators=[DataRequired(),
Length(1, 64),
Regexp(current_app.config['ALLOWED_USERNAME_REGEX'],
message='Usernames must have only letters, numbers,'
' dots or underscores')]
)
submit = SubmitField('Submit')
def __init__(self, user=current_user, *args, **kwargs):
super().__init__(*args, **kwargs)
self.user = user
self.email.data = self.email.data or user.email
self.dark_mode.data = self.dark_mode.data or user.setting_dark_mode
self.username.data = self.username.data or user.username
def validate_email(self, field):
if (field.data != self.user.email
and User.query.filter_by(email=field.data).first()):
raise ValidationError('Email already registered.')
def validate_username(self, field):
if (field.data != self.user.username
and User.query.filter_by(username=field.data).first()):
raise ValidationError('Username already in use.')
class EditNotificationSettingsForm(FlaskForm):
job_status_mail_notifications = SelectField(
'Job status mail notifications',
choices=[('', 'Choose your option'),
('all', 'Notify on all status changes'),
('end', 'Notify only when a job ended'),
('none', 'No status update notifications')],
validators=[DataRequired()])
job_status_site_notifications = SelectField(
'Job status site notifications',
choices=[('', 'Choose your option'),
('all', 'Notify on all status changes'),
('end', 'Notify only when a job ended'),
('none', 'No status update notifications')],
validators=[DataRequired()])
submit = SubmitField('Save settings')
def __init__(self, user=current_user, *args, **kwargs):
super().__init__(*args, **kwargs)
self.user = user
self.job_status_mail_notifications.data = \
self.job_status_mail_notifications.data \
or user.setting_job_status_mail_notifications
self.job_status_site_notifications.data = \
self.job_status_site_notifications.data \
or user.setting_job_status_site_notifications

73
web/app/settings/views.py Normal file
View File

@ -0,0 +1,73 @@
from flask import current_app, flash, redirect, render_template, url_for
from flask_login import current_user, login_required
from . import settings
from .forms import (ChangePasswordForm, EditGeneralSettingsForm,
EditNotificationSettingsForm)
from .. import db
from ..decorators import admin_required
from ..models import Role, User
import os
import uuid
@settings.route('/')
@login_required
def index():
return redirect(url_for('.edit_general_settings'))
@settings.route('/change_password', methods=['GET', 'POST'])
@login_required
def change_password():
form = ChangePasswordForm()
if form.validate_on_submit():
current_user.password = form.new_password.data
db.session.commit()
flash('Your password has been updated.')
return redirect(url_for('.change_password'))
return render_template('settings/change_password.html.j2',
form=form,
title='Change password')
@settings.route('/edit_general_settings')
@login_required
def edit_general_settings():
form = EditGeneralSettingsForm()
if form.validate_on_submit():
current_user.email = form.email.data
current_user.setting_dark_mode = form.dark_mode.data
current_user.username = form.username.data
db.session.commit()
flash('Your changes have been saved.')
return render_template('settings/edit_general_settings.html.j2',
form=form,
title='General settings')
@settings.route('/edit_notification_settings')
@login_required
def edit_notification_settings():
form = EditNotificationSettingsForm()
if form.validate_on_submit():
current_user.setting_job_status_mail_notifications = \
form.job_status_mail_notifications.data
current_user.setting_job_status_site_notifications = \
form.job_status_site_notifications.data
db.session.commit()
flash('Your changes have been saved.')
return render_template('settings/edit_notification_settings.html.j2',
form=form,
title='Notification settings')
@settings.route('/delete')
@login_required
def delete():
"""
View to delete current_user and all associated data.
"""
tasks.delete_user(current_user.id)
logout_user()
flash('Your account has been deleted!')
return redirect(url_for('main.index'))

View File

@ -131,11 +131,11 @@ RessourceList.dataMappers = {
confirmed: user.confirmed, confirmed: user.confirmed,
email: user.email, email: user.email,
id: user.id, id: user.id,
link: `user/${user.id}`, link: `users/${user.id}`,
role_id: user.role_id, role_id: user.role_id,
username: user.username, username: user.username,
username2: user.username, username2: user.username,
"delete-link": `/admin/user/${user.id}/delete`, "delete-link": `/admin/users/${user.id}/delete`,
"delete-modal": `delete-user-${user.id}-modal`, "delete-modal": `delete-user-${user.id}-modal`,
"delete-modal-trigger": `delete-user-${user.id}-modal`, "delete-modal-trigger": `delete-user-${user.id}-modal`,
}), }),

View File

@ -1,27 +1,35 @@
{% extends "nopaque.html.j2" %} {% extends "nopaque.html.j2" %}
{% import 'materialize/wtf.html.j2' as wtf %}
{% block page_content %} {% block page_content %}
<div class="col s12 m4"> <div class="container">
<h3 id="title">{{ user.username }}</h3> <div class="row">
<p id="description">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren,</p> <div class="col s12">
<a class="waves-effect waves-light btn" href="{{ url_for('admin.user', user_id=user.id) }}"><i class="material-icons left">arrow_back</i>Back to user administration</a> <h1 id="title">Edit user</h1>
</div> </div>
<div class="col s12 m8"> <div class="col s12 m4">
<div class="card"> <h2>{{ user.username }}</h2>
<form method="POST"> <p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren,</p>
<div class="card-content"> <a class="waves-effect waves-light btn" href="{{ url_for('.user', user_id=user.id) }}"><i class="material-icons left">arrow_back</i>Back to user administration</a>
{{ edit_user_form.hidden_tag() }} </div>
{{ M.render_field(edit_user_form.username, data_length='64', material_icon='account_circle') }}
{{ M.render_field(edit_user_form.email, class_='validate', material_icon='email', type='email') }} <div class="col s12 m8">
{{ M.render_field(edit_user_form.role, material_icon='swap_vert') }} <div class="card">
{{ M.render_field(edit_user_form.confirmed, material_icon='check') }} <form method="POST">
<div class="card-content">
{{ edit_user_form.hidden_tag() }}
{{ wtf.render_field(edit_user_form.username, data_length='64', material_icon='account_circle') }}
{{ wtf.render_field(edit_user_form.email, class_='validate', material_icon='email', type='email') }}
{{ wtf.render_field(edit_user_form.role, material_icon='swap_vert') }}
{{ wtf.render_field(edit_user_form.confirmed, material_icon='check') }}
</div>
<div class="card-action right-align">
{{ wtf.render_field(edit_user_form.submit, material_icon='send') }}
</div>
</form>
</div> </div>
<div class="card-action right-align"> </div>
{{ M.render_field(edit_user_form.submit, material_icon='send') }}
</div>
</form>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -1,42 +0,0 @@
{% extends "nopaque.html.j2" %}
{% set full_width = True %}
{% block page_content %}
<div class="col s12">
<div class="card">
<div class="card-content" id="users">
<span class="card-title">User list</span>
<div class="input-field">
<i class="material-icons prefix">search</i>
<input id="search-user" class="search" type="search"></input>
<label for="search-user">Search user</label>
</div>
<ul class="pagination paginationTop"></ul>
<table class="highlight responsive-table">
<thead>
<tr>
<th class="sort" data-sort="username">Username</th>
<th class="sort" data-sort="email">Email</th>
<th class="sort" data-sort="role_id">Role</th>
<th class="sort" data-sort="confirmed">Confirmed Status</th>
<th class="sort" data-sort="id">Id</th>
<th>{# Actions #}</th>
</tr>
</thead>
<tbody class="list">
</tbody>
</table>
<ul class="pagination paginationBottom"></ul>
</div>
</div>
</div>
<script type="module">
import {RessourceList} from '../../static/js/nopaque.lists.js';
let userList = new RessourceList('users', null, "User", RessourceList.options.extended);
document.addEventListener("DOMContentLoaded", () => {
userList._add({{ users|tojson|safe }});
});
</script>
{% endblock %}

View File

@ -1,34 +1,40 @@
{% extends "nopaque.html.j2" %} {% extends "nopaque.html.j2" %}
{% block page_content %} {% block page_content %}
<div class="col s12 m4"> <div class="container">
<h3 id="title">{{ user.username }}</h3> <div class="row">
<p id="description">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren,</p> <div class="col s12">
<a class="waves-effect waves-light btn" href="{{ url_for('admin.index') }}"><i class="material-icons left">arrow_back</i>Back to admin board</a> <h1 id="title">{{ title }}</h1>
</div> </div>
<div class="col s12 m8"> <div class="col s12 m4">
<div class="card"> <h2>{{ user.username }}</h2>
<div class="card-content"> <p id="description">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren,</p>
<span class="card-title">User information</span> <a class="waves-effect waves-light btn" href="{{ url_for('.users') }}"><i class="material-icons left">arrow_back</i>Back to Users</a>
<ul>
<li>Username: {{ user.username }}</li>
<li>Email: {{ user.email }}</li>
<li>ID: {{ user.id }}</li>
<li>Member since: {{ user.member_since.strftime('%m/%d/%Y, %H:%M:%S %p') }}</li>
<li>Confirmed status: {{ user.confirmed }}</li>
<li>Last seen: {{ user.last_seen.strftime('%m/%d/%Y, %H:%M:%S %p') }}</li>
<li>Role ID: {{ user.role_id }}</li>
<li>Permissions as Int: {{ user.role.permissions }}</li>
<li>Role name: {{ user.role.name }}</li>
</ul>
</div> </div>
<div class="card-action right-align">
<a href="{{ url_for('admin.edit_user', user_id=user.id) }}" class="waves-effect waves-light btn"><i class="material-icons left">edit</i>Edit</a> <div class="col s12 m8">
<a data-target="delete-user-modal" class="waves-effect waves-light btn red modal-trigger"><i class="material-icons left">delete</i>Delete</a> <div class="card">
<div class="card-content">
<span class="card-title">User information</span>
<ul>
<li>Username: {{ user.username }}</li>
<li>Email: {{ user.email }}</li>
<li>ID: {{ user.id }}</li>
<li>Member since: {{ user.member_since.strftime('%m/%d/%Y, %H:%M:%S %p') }}</li>
<li>Confirmed status: {{ user.confirmed }}</li>
<li>Last seen: {{ user.last_seen.strftime('%m/%d/%Y, %H:%M:%S %p') }}</li>
<li>Role ID: {{ user.role_id }}</li>
<li>Permissions as Int: {{ user.role.permissions }}</li>
<li>Role name: {{ user.role.name }}</li>
</ul>
</div>
<div class="card-action right-align">
<a href="{{ url_for('.edit_user', user_id=user.id) }}" class="waves-effect waves-light btn"><i class="material-icons left">edit</i>Edit</a>
<a data-target="delete-user-modal" class="waves-effect waves-light btn red modal-trigger"><i class="material-icons left">delete</i>Delete</a>
</div>
</div>
</div> </div>
</div>
</div>
<div class="col s12 l6"> <div class="col s12 l6">
<h3>Corpora</h3> <h3>Corpora</h3>
@ -92,21 +98,23 @@
<!-- Modals --> <!-- Modals -->
<div id="delete-user-modal" class="modal"> <div id="delete-user-modal" class="modal">
<div class="modal-content"> <div class="modal-content">
<h4>Confirm user deletion</h4> <h3>Delete user</h3>
<p>Do you really want to delete the user {{ user.username }}? All associated data will be permanently deleted!</p> <p>Do you really want to delete the user {{ user.username }}? All associated data will be permanently deleted!</p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<a href="#!" class="modal-close waves-effect waves-light btn">Cancel</a> <a href="#!" class="modal-close waves-effect waves-light btn">Cancel</a>
<a href="{{ url_for('admin.delete_user', user_id=user.id) }}" class="modal-close waves-effect waves-light btn red"><i class="material-icons left">delete</i>Delete</a> <a href="{{ url_for('.delete_user', user_id=user.id) }}" class="modal-close waves-effect waves-light btn red"><i class="material-icons left">delete</i>Delete</a>
</div> </div>
</div> </div>
<script type="module">
import {RessourceList} from '../../static/js/nopaque.lists.js';
let corpusList = new RessourceList("corpora", nopaque.foreignCorporaSubscribers, "Corpus");
let jobList = new RessourceList("jobs", nopaque.foreignJobsSubscribers, "Job");
document.addEventListener("DOMContentLoaded", () => {
nopaque.socket.emit("foreign_user_data_stream_init", {{ user.id }});
});
</script>
{% endblock %} {% endblock %}
{% block scripts %}
{{ super() }}
<script type="module">
import {RessourceList} from '{{ url_for('static', filename='js/nopaque.lists.js') }}';
let corpusList = new RessourceList("corpora", nopaque.foreignCorporaSubscribers, "Corpus");
let jobList = new RessourceList("jobs", nopaque.foreignJobsSubscribers, "Job");
nopaque.socket.emit("foreign_user_data_stream_init", {{ user.id }});
</script>
{% endblock scripts %}

View File

@ -0,0 +1,48 @@
{% extends "nopaque.html.j2" %}
{% block page_content %}
<div class="container">
<div class="row">
<div class="col s12">
<h1 id="title">{{ title }}</h1>
</div>
<div class="col s12">
<div class="card">
<div class="card-content" id="users">
<div class="input-field">
<i class="material-icons prefix">search</i>
<input id="search-user" class="search" type="text"></input>
<label for="search-user">Search user</label>
</div>
<ul class="pagination paginationTop"></ul>
<table class="highlight responsive-table">
<thead>
<tr>
<th class="sort" data-sort="username">Username</th>
<th class="sort" data-sort="email">Email</th>
<th class="sort" data-sort="role_id">Role</th>
<th class="sort" data-sort="confirmed">Confirmed Status</th>
<th class="sort" data-sort="id">Id</th>
<th>{# Actions #}</th>
</tr>
</thead>
<tbody class="list">
</tbody>
</table>
<ul class="pagination paginationBottom"></ul>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
{{ super() }}
<script type="module">
import {RessourceList} from '{{ url_for('static', filename='js/nopaque.lists.js') }}';
let userList = new RessourceList('users', null, "User", RessourceList.options.extended);
userList._add({{ users|tojson}});
</script>
{% endblock scripts %}

View File

@ -18,7 +18,7 @@
<div class="col s12 m4"> <div class="col s12 m4">
<div class="card medium"> <div class="card medium">
<div class="card-content"> <div class="card-content">
<h1>{{ title }}</h1> <h1 id="title">{{ title }}</h1>
<p>Want to boost your research and get going? nopaque is free and no download is needed. Register now!</p> <p>Want to boost your research and get going? nopaque is free and no download is needed. Register now!</p>
</div> </div>
<div class="card-action right-align"> <div class="card-action right-align">

View File

@ -18,7 +18,7 @@
<div class="col s12 m4"> <div class="col s12 m4">
<div class="card medium"> <div class="card medium">
<div class="card-content"> <div class="card-content">
<h1>Register</h1> <h1 id="title">{{ title }}</h1>
<p>Simply enter a username and password to receive your registration email. After that you can start right away.</p> <p>Simply enter a username and password to receive your registration email. After that you can start right away.</p>
<p>It goes without saying that the <a href="{{ url_for('main.privacy_policy') }}">General Data Protection Regulation</a> applies, only necessary data is stored.</p> <p>It goes without saying that the <a href="{{ url_for('main.privacy_policy') }}">General Data Protection Regulation</a> applies, only necessary data is stored.</p>
<p>Please also read our <a href="{{ url_for('main.terms_of_use') }}">terms of use</a> before signing up for nopaque!</p> <p>Please also read our <a href="{{ url_for('main.terms_of_use') }}">terms of use</a> before signing up for nopaque!</p>

View File

@ -5,11 +5,11 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col s12"> <div class="col s12">
<h1>{{ title }}</h1> <h1 id="title">{{ title }}</h1>
</div> </div>
<div class="col s12 m4"> <div class="col s12 m4">
<p>dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren,</p> <p>Enter a new password and confirm it! After that, the entered password is your new one!</p>
</div> </div>
<div class="col s12 m8"> <div class="col s12 m8">

View File

@ -5,7 +5,7 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col s12"> <div class="col s12">
<h1>{{ title }}</h1> <h1 id="title">{{ title }}</h1>
</div> </div>
<div class="col s12 m4"> <div class="col s12 m4">

View File

@ -4,7 +4,7 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col s12"> <div class="col s12">
<h1>{{ title }}</h1> <h1 id="title">{{ title }}</h1>
</div> </div>
<div class="col s12"> <div class="col s12">
@ -13,10 +13,10 @@
<span class="card-title">Hello, {{ current_user.username }}!</span> <span class="card-title">Hello, {{ current_user.username }}!</span>
<p><b>You have not confirmed your account yet.</b></p> <p><b>You have not confirmed your account yet.</b></p>
<p>Before you can access this site you need to confirm your account. Check your inbox, you should have received an email with a confirmation link.</p> <p>Before you can access this site you need to confirm your account. Check your inbox, you should have received an email with a confirmation link.</p>
<p>Need another confirmation email? <a href="{{ url_for('.resend_confirmation') }}">Click here</a></p> <p>Need another confirmation email? Click the button below!</p>
</div> </div>
<div class="card-action right-align"> <div class="card-action right-align">
<a class="btn" href="{{ url_for('.register') }}"><i class="material-icons left">person_add</i>Register</a> <a class="btn" href="{{ url_for('.resend_confirmation') }}">Resend confirmation mail</a>
</div> </div>
</div> </div>
</div> </div>

View File

@ -2,14 +2,14 @@
{% block page_content %} {% block page_content %}
<div class="container"> <div class="container">
<h1>{{ title }}</h1> <h1 id="title">{{ title }}</h1>
<p class="light">{{ request.path }}</p> <p class="light">{{ request.path }}</p>
<p>Alternatively, you can visit the <a href="{{ url_for('main.index') }}">Main Page</a> or read <a class="modal-trigger" href="#more-information-modal">more information</a> about this type of error.</p> <p>Alternatively, you can visit the <a href="{{ url_for('main.index') }}">Main Page</a> or read <a class="modal-trigger" href="#more-information-modal">more information</a> about this type of error.</p>
</div> </div>
<div class="modal" id="more-information-modal"> <div class="modal" id="more-information-modal">
<div class="modal-content"> <div class="modal-content">
<h4>{{ title }}</h4> <h2>About the "{{ title }}" error</h2>
<p>The request contained valid data and was understood by the server, but the server is refusing action. This may be due to the user not having the necessary permissions for a resource or needing an account of some sort, or attempting a prohibited action (e.g. creating a duplicate record where only one is allowed). This code is also typically used if the request provided authentication by answering the WWW-Authenticate header field challenge, but the server did not accept that authentication. The request should not be repeated.</p> <p>The request contained valid data and was understood by the server, but the server is refusing action. This may be due to the user not having the necessary permissions for a resource or needing an account of some sort, or attempting a prohibited action (e.g. creating a duplicate record where only one is allowed). This code is also typically used if the request provided authentication by answering the WWW-Authenticate header field challenge, but the server did not accept that authentication. The request should not be repeated.</p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">

View File

@ -2,14 +2,14 @@
{% block page_content %} {% block page_content %}
<div class="container"> <div class="container">
<h1>{{ title }}</h1> <h1 id="title">{{ title }}</h1>
<p class="light">{{ request.path }}</p> <p class="light">{{ request.path }}</p>
<p>Alternatively, you can visit the <a href="{{ url_for('main.index') }}">Main Page</a> or read <a class="modal-trigger" href="#more-information-modal">more information</a> about this type of error.</p> <p>Alternatively, you can visit the <a href="{{ url_for('main.index') }}">Main Page</a> or read <a class="modal-trigger" href="#more-information-modal">more information</a> about this type of error.</p>
</div> </div>
<div class="modal" id="more-information-modal"> <div class="modal" id="more-information-modal">
<div class="modal-content"> <div class="modal-content">
<h4>{{ title }}</h4> <h2>About the "{{ title }}" error</h2>
<p>The requested resource could not be found but may be available in the future. Subsequent requests by the client are permissible.</p> <p>The requested resource could not be found but may be available in the future. Subsequent requests by the client are permissible.</p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">

View File

@ -2,14 +2,14 @@
{% block page_content %} {% block page_content %}
<div class="container"> <div class="container">
<h1>{{ title }}</h1> <h1 id="title">{{ title }}</h1>
<p class="light">{{ request.path }}</p> <p class="light">{{ request.path }}</p>
<p>Alternatively, you can visit the <a href="{{ url_for('main.index') }}">Main Page</a> or read <a class="modal-trigger" href="#more-information-modal">more information</a> about this type of error.</p> <p>Alternatively, you can visit the <a href="{{ url_for('main.index') }}">Main Page</a> or read <a class="modal-trigger" href="#more-information-modal">more information</a> about this type of error.</p>
</div> </div>
<div class="modal" id="more-information-modal"> <div class="modal" id="more-information-modal">
<div class="modal-content"> <div class="modal-content">
<h4>{{ title }}</h4> <h2>About the "{{ title }}" error</h2>
<p>The request is larger than the server is willing or able to process. Previously called "Request Entity Too Large".</p> <p>The request is larger than the server is willing or able to process. Previously called "Request Entity Too Large".</p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">

View File

@ -2,14 +2,14 @@
{% block page_content %} {% block page_content %}
<div class="container"> <div class="container">
<h1>{{ title }}</h1> <h1 id="title">{{ title }}</h1>
<p class="light">{{ request.path }}</p> <p class="light">{{ request.path }}</p>
<p>Alternatively, you can visit the <a href="{{ url_for('main.index') }}">Main Page</a> or read <a class="modal-trigger" href="#more-information-modal">more information</a> about this type of error.</p> <p>Alternatively, you can visit the <a href="{{ url_for('main.index') }}">Main Page</a> or read <a class="modal-trigger" href="#more-information-modal">more information</a> about this type of error.</p>
</div> </div>
<div class="modal" id="more-information-modal"> <div class="modal" id="more-information-modal">
<div class="modal-content"> <div class="modal-content">
<h4>{{ title }}</h4> <h2>About the "{{ title }}" error</h2>
<p>A generic error message, given when an unexpected condition was encountered and no more specific message is suitable.</p> <p>A generic error message, given when an unexpected condition was encountered and no more specific message is suitable.</p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">

View File

@ -4,7 +4,7 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col s12"> <div class="col s12">
<h1>{{ title }}</h1> <h1 id="title">{{ title }}</h1>
</div> </div>
<div class="col s12"> <div class="col s12">

View File

@ -4,7 +4,7 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col s12"> <div class="col s12">
<h1>{{ title }}</h1> <h1 id="title">{{ title }}</h1>
</div> </div>
<div class="col s12"> <div class="col s12">

View File

@ -5,7 +5,7 @@
<div class="section white"> <div class="section white">
<div class="row container"> <div class="row container">
<div class="col s12"> <div class="col s12">
<h2>nopaque</h2> <h1 id="title">{{ title }}</h1>
<p>From text to data to analysis</p> <p>From text to data to analysis</p>
</div> </div>
</div> </div>

View File

@ -4,8 +4,9 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col s12"> <div class="col s12">
<h1>{{ title }}</h1> <h1 id="title">{{ title }}</h1>
</div> </div>
<div class="col s12"> <div class="col s12">
<div class="card" id="beta-launch"> <div class="card" id="beta-launch">
<div class="card-content"> <div class="card-content">

View File

@ -4,7 +4,7 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col s12"> <div class="col s12">
<h1>{{ title }}</h1> <h1 id="title">{{ title }}</h1>
</div> </div>
<div class="col s12"> <div class="col s12">

View File

@ -4,7 +4,7 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col s12"> <div class="col s12">
<h1>{{ title }}</h1> <h1 id="title">{{ title }}</h1>
</div> </div>
<div class="col s12"> <div class="col s12">

View File

@ -1,6 +1,4 @@
<!-- Analysis init modal. User feedback showing that the analysis session is <!-- Analysis init modal. User feedback showing that the analysis session is loading. -->
loading. -->
<div class="modal no-autoinit" id="analysis-init-modal"> <div class="modal no-autoinit" id="analysis-init-modal">
<div class="modal-content"> <div class="modal-content">
<h4>Initializing your corpus analysis session...</h4> <h4>Initializing your corpus analysis session...</h4>
@ -14,4 +12,4 @@ loading. -->
</div> </div>
<p id="analysis-init-error" class="hide red-text"></p> <p id="analysis-init-error" class="hide red-text"></p>
</div> </div>
</div> </div>

View File

@ -1,5 +1,4 @@
<!-- Modal showing detailed context info for one match. --> <!-- Modal showing detailed context info for one match. -->
<div id="context-modal" class="modal"> <div id="context-modal" class="modal">
<div class="modal-content"> <div class="modal-content">
<form> <form>
@ -73,4 +72,4 @@
</a> </a>
<a href="#!" class="modal-close waves-effect waves-light red btn">Close</a> <a href="#!" class="modal-close waves-effect waves-light red btn">Close</a>
</div> </div>
</div> </div>

View File

@ -1,6 +1,4 @@
<!-- Export query results modal. Allos the user to download the results in <!-- Export query results modal. Allos the user to download the results in different file formats. WIP -->
different file formats. WIP -->
<div id="query-results-download-modal" <div id="query-results-download-modal"
class="modal modal-fixed-footer no-autoinit"> class="modal modal-fixed-footer no-autoinit">
<div class="modal-content"> <div class="modal-content">
@ -51,4 +49,4 @@ different file formats. WIP -->
<div class="modal-footer"> <div class="modal-footer">
<a href="#!" class="modal-close waves-effect waves-light red btn">Close</a> <a href="#!" class="modal-close waves-effect waves-light red btn">Close</a>
</div> </div>
</div> </div>

View File

@ -1,6 +1,4 @@
<!-- Modal showing the corpus files for the current query results including <!-- Modal showing the corpus files for the current query results including title ant match count per corpus file. -->
title ant match count per corpus file. -->
<div id="show-corpus-files-modal" class="modal bottom-sheet"> <div id="show-corpus-files-modal" class="modal bottom-sheet">
<div class="container"> <div class="container">
<div class="row"> <div class="row">

View File

@ -1,6 +1,4 @@
<!-- Modal showing the meta data for the current query results or the imported <!-- Modal showing the meta data for the current query results or the imported results -->
results -->
<div id="meta-data-modal" class="modal bottom-sheet"> <div id="meta-data-modal" class="modal bottom-sheet">
<div class="container"> <div class="container">
<div class="row"> <div class="row">

View File

@ -1,6 +1,4 @@
<!-- Modal to show all metadata of one text/corpus file. Used in conjunction <!-- Modal to show all metadata of one text/corpus file. Used in conjunction with the show_meta_data.html.j2 template. -->
with the show_meta_data.html.j2 template.-->
<div id="modal-text-details" class="modal modal-fixed-footer"> <div id="modal-text-details" class="modal modal-fixed-footer">
<div class="modal-content"> <div class="modal-content">
<h4>Bibliographic data</h4> <h4>Bibliographic data</h4>
@ -9,4 +7,4 @@ with the show_meta_data.html.j2 template.-->
<div class="modal-footer"> <div class="modal-footer">
<a href="#!" class="modal-close waves-effect waves-green red btn">Close</a> <a href="#!" class="modal-close waves-effect waves-green red btn">Close</a>
</div> </div>
</div> </div>

View File

@ -78,15 +78,14 @@
{% endif %} {% endif %}
<a href="{{ url_for('main.index') }}" class="brand-logo hide-on-med-and-down" style="height: 100%; overflow: hidden;"><img src="{{ url_for('static', filename='images/nopaque_-_logo_name_slogan.svg') }}" style="height: 128px; margin-top: -32px;"></a> <a href="{{ url_for('main.index') }}" class="brand-logo hide-on-med-and-down" style="height: 100%; overflow: hidden;"><img src="{{ url_for('static', filename='images/nopaque_-_logo_name_slogan.svg') }}" style="height: 128px; margin-top: -32px;"></a>
<a href="{{ url_for('main.index') }}" class="brand-logo hide-on-large-only" style="height: 100%; overflow: hidden;"><img src="{{ url_for('static', filename='images/nopaque_-_logo.svg') }}" style="height: 128px; margin-top: -32px;"></a> <a href="{{ url_for('main.index') }}" class="brand-logo hide-on-large-only" style="height: 100%; overflow: hidden;"><img src="{{ url_for('static', filename='images/nopaque_-_logo.svg') }}" style="height: 128px; margin-top: -32px;"></a>
<ul class="right hide-on-med-and-down"> <ul class="right">
<li{% if request.path == url_for('main.news') %} class="active"{% endif %}><a href="{{ url_for('main.news') }}"><i class="material-icons left">notifications</i>News</a></li> <li class="hide-on-med-and-down{% if request.path == url_for('main.news') %} active{% endif %}"><a href="{{ url_for('main.news') }}"><i class="material-icons left">notifications</i>News</a></li>
{% if current_user.is_anonymous %} {% if current_user.is_anonymous %}
<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> <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>
<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> <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>
{% else %} {% else %}
<li{% if request.path == url_for('main.dashboard', _anchor='corpora') %} class="active"{% endif %}><a href="{{ url_for('main.dashboard', _anchor='corpora') }}"><i class="material-icons left">book</i>My Corpora</a></li> <li class="hide-on-med-and-down{% if request.path == url_for('main.dashboard') %} active{% endif %}"><a href="{{ url_for('main.dashboard') }}"><i class="material-icons left">dashboard</i>Dashboard</a></li>
<li{% if request.path == url_for('main.dashboard', _anchor='jobs') %} class="active"{% endif %}><a href="{{ url_for('main.dashboard', _anchor='jobs') }}"><i class="material-icons left">work</i>My Jobs</a></li> <li class="hide-on-med-and-down"><a class="dropdown-trigger no-autoinit" data-target="nav-more-dropdown" href="#!" id="nav-more-dropdown-trigger"><i class="material-icons">more_vert</i></a></li>
<li><a class="dropdown-trigger no-autoinit" data-target="nav-more-dropdown" href="#!" id="nav-more-dropdown-trigger"><i class="material-icons">more_vert</i></a></li>
{% endif %} {% endif %}
</ul> </ul>
</div> </div>
@ -98,7 +97,7 @@
{% if current_user.is_authenticated %} {% if current_user.is_authenticated %}
<ul class="dropdown-content" id="nav-more-dropdown"> <ul class="dropdown-content" id="nav-more-dropdown">
<li><a href="{{ url_for('profile.settings') }}"><i class="material-icons left">settings</i>Settings</a></li> <li><a href="{{ url_for('settings.index') }}"><i class="material-icons left">settings</i>Settings</a></li>
<li class="divider" tabindex="-1"></li> <li class="divider" tabindex="-1"></li>
<li><a href="{{ url_for('auth.logout') }}">Log out</a></li> <li><a href="{{ url_for('auth.logout') }}">Log out</a></li>
</ul> </ul>
@ -126,17 +125,18 @@
<li style="background-color: {{ colors.ocr }}; border-left: 10px solid {{ colors.ocr_darken }}; margin-top: 5px;"><a href="{{ url_for('services.service', service='ocr') }}"><i class="material-icons">find_in_page</i>OCR</a></li> <li style="background-color: {{ colors.ocr }}; border-left: 10px solid {{ colors.ocr_darken }}; margin-top: 5px;"><a href="{{ url_for('services.service', service='ocr') }}"><i class="material-icons">find_in_page</i>OCR</a></li>
<li style="background-color: {{ colors.nlp }}; border-left: 10px solid {{ colors.nlp_darken }}; margin-top: 5px;"><a href="{{ url_for('services.service', service='nlp') }}"><i class="material-icons">format_textdirection_l_to_r</i>NLP</a></li> <li style="background-color: {{ colors.nlp }}; border-left: 10px solid {{ colors.nlp_darken }}; margin-top: 5px;"><a href="{{ url_for('services.service', service='nlp') }}"><i class="material-icons">format_textdirection_l_to_r</i>NLP</a></li>
<li style="background-color: {{ colors.corpus_analysis }}; border-left: 10px solid {{ colors.corpus_analysis_darken }}; margin-top: 5px;"><a href="{{ url_for('services.service', service='corpus_analysis') }}"><i class="material-icons">search</i>Corpus analysis</a></li> <li style="background-color: {{ colors.corpus_analysis }}; border-left: 10px solid {{ colors.corpus_analysis_darken }}; margin-top: 5px;"><a href="{{ url_for('services.service', service='corpus_analysis') }}"><i class="material-icons">search</i>Corpus analysis</a></li>
<li class="hide-on-large-only"><div class="divider"></div></li> <li><div class="divider"></div></li>
<li class="hide-on-large-only"><a href="{{ url_for('profile.settings') }}"><i class="material-icons">settings</i>Settings</a></li> <li><a class="subheader">Account</a></li>
<li class="hide-on-large-only"><a href="{{ url_for('auth.logout') }}">Log out</a></li> <li><a href="{{ url_for('settings.index') }}"><i class="material-icons">settings</i>Settings</a></li>
<li><a href="{{ url_for('auth.logout') }}">Log out</a></li>
{% else %} {% else %}
<li class="hide-on-large-only"><a href="{{ url_for('auth.register') }}"><i class="material-icons">assignment</i>Register</a></li> <li><a href="{{ url_for('auth.register') }}"><i class="material-icons">assignment</i>Register</a></li>
<li class="hide-on-large-only"><a href="{{ url_for('auth.login') }}"><i class="material-icons">login</i>Log in</a></li> <li><a href="{{ url_for('auth.login') }}"><i class="material-icons">login</i>Log in</a></li>
{% endif %} {% endif %}
{% if current_user.is_administrator() %} {% if current_user.is_administrator() %}
<li><div class="divider"></div></li> <li><div class="divider"></div></li>
<li><a class="subheader">Administration</a></li> <li><a class="subheader">Administration</a></li>
<li><a href="{{ url_for('admin.index') }}"><i class="material-icons">build</i>Administration tools</a></li> <li><a href="{{ url_for('admin.users') }}"><i class="material-icons">build</i>Administration tools</a></li>
{% endif %} {% endif %}
</ul> </ul>
{% endblock sidenav %} {% endblock sidenav %}

View File

@ -1,136 +0,0 @@
{% extends "nopaque.html.j2" %}
{% block page_content %}
<div class="col s12 m4">
<h3>General settings</h3>
</div>
<div class="col s12 m8">
<br class="hide-on-small-only">
<div class="card">
<form method="POST">
<div class="card-content">
{{ edit_general_settings_form.hidden_tag() }}
<div class="row">
<div class="col s9">
<p><i class="material-icons left">brightness_3</i>{{ edit_general_settings_form.dark_mode.label.text }}</p>
<p class="light">Activate dark mode to ease your eyes.</p>
</div>
<div class="col s3 right-align">
{{ M.render_field(edit_general_settings_form.dark_mode, label=False) }}
</div>
<div class="col s12"><p>&nbsp;</p></div>
<div class="col s12 divider"></div>
<div class="col s12"><p>&nbsp;</p></div>
<div class="col s12 m8">
<p><i class="material-icons left">notifications</i>Job status site notifications</p>
<p class="light">Receive site notifications about job status changes.</p>
</div>
<div class="col s12 m4 right-align" style="margin-top: -1rem;">
{{ M.render_field(edit_general_settings_form.job_status_site_notifications, label=False) }}
</div>
<div class="col s12"><p>&nbsp;</p></div>
<div class="col s12 divider"></div>
<div class="col s12"><p>&nbsp;</p></div>
<div class="col s12 m8">
<p><i class="material-icons left">notifications</i>Job status mail notifications</p>
<p class="light">Receive mail notifications about job status changes.</p>
</div>
<div class="col s12 m4 right-align" style="margin-top: -1rem;">
{{ M.render_field(edit_general_settings_form.job_status_mail_notifications, label=False) }}
</div>
<!--
Seperate each setting with the following two elements
<div class="col s12 divider"></div>
<div class="col s12"><p>&nbsp;</p></div>
-->
</div>
</div>
<div class="card-action right-align">
{{ M.render_field(edit_general_settings_form.save_settings, material_icon='send') }}
</div>
</form>
</div>
</div>
<div class="col s12"></div>
<div class="col s12 m4">
<h3>Change password</h3>
</div>
<div class="col s12 m8">
<br class="hide-on-small-only">
<div class="card">
<form method="POST">
<div class="card-content">
{{ edit_password_form.hidden_tag() }}
{{ M.render_field(edit_password_form.current_password, data_length='128', material_icon='vpn_key') }}
{{ M.render_field(edit_password_form.password, data_length='128', material_icon='vpn_key') }}
{{ M.render_field(edit_password_form.password_confirmation, data_length='128', material_icon='vpn_key') }}
</div>
<div class="card-action right-align">
{{ M.render_field(edit_password_form.save_password, material_icon='send') }}
</div>
</form>
</div>
</div>
<div class="col s12"></div>
<div class="col s12 m4">
<h3>Change email</h3>
</div>
<div class="col s12 m8">
<br class="hide-on-small-only">
<div class="card">
<form method="POST">
<div class="card-content">
{{ edit_email_form.hidden_tag() }}
{{ M.render_field(edit_email_form.email, class_='validate', material_icon='email', type='email') }}
</div>
<div class="card-action right-align">
{{ M.render_field(edit_email_form.save_email, material_icon='send') }}
</div>
</form>
</div>
</div>
<div class="col s12"></div>
<div class="col s12 m4">
<h3>Delete account</h3>
</div>
<div class="col s12 m8">
<br class="hide-on-small-only">
<div class="card">
<div class="card-content">
<p>Deleting an account has the following effects:</p>
<ul>
<li>All data associated with your corpora and jobs will be permanently deleted.</li>
<li>All settings will be permanently deleted.</li>
</ul>
</div>
<div class="card-action right-align">
<a href="#delete-account-modal" class="btn modal-trigger red waves-effect waves-light"><i class="material-icons left">delete</i>Delete</a>
</div>
</div>
</div>
<!-- Modals -->
<div class="modal" id="delete-account-modal">
<div class="modal-content">
<h4>Confirm deletion</h4>
<p>Do you really want to delete your account and all associated data? All associated corpora, jobs and files will be permanently deleted!</p>
</div>
<div class="modal-footer">
<a href="#!" class="btn modal-close waves-effect waves-light">Cancel</a>
<a href="{{ url_for('profile.delete') }}" class="btn red waves-effect waves-light"><i class="material-icons left">delete</i>Delete</a>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,5 @@
<div class="collection">
<a href="{{ url_for('.edit_general_settings') }}" class="collection-item{%if request.path == url_for('.edit_general_settings') %} active{% endif %}">Edit general settings</a>
<a href="{{ url_for('.change_password') }}" class="collection-item{%if request.path == url_for('.change_password') %} active{% endif %}">Change password</a>
<a href="{{ url_for('.edit_notification_settings') }}" class="collection-item{%if request.path == url_for('.edit_notification_settings') %} active{% endif %}">Edit notification settings</a>
</div>

View File

@ -0,0 +1,35 @@
{% extends 'nopaque.html.j2' %}
{% import 'materialize/wtf.html.j2' as wtf %}
{% block page_content %}
<div class="container">
<div class="row">
<div class="col s12">
<h1 id="title">Settings</h1>
</div>
<div class="col s12 m4">
{% include 'settings/_menu.html.j2' %}
</div>
<div class="col s12 m8">
<div class="card">
<form enctype="multipart/form-data" method="POST">
<div class="card-content">
<span class="card-title">{{ title }}</span>
{{ form.hidden_tag() }}
{{ wtf.render_field(form.password, material_icon='vpn_key') }}
{{ wtf.render_field(form.new_password, material_icon='vpn_key') }}
{{ wtf.render_field(form.new_password2, material_icon='vpn_key') }}
</div>
<div class="card-action">
<div class="right-align">
{{ wtf.render_field(form.submit, material_icon='send') }}
</div>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock page_content %}

View File

@ -0,0 +1,63 @@
{% extends 'nopaque.html.j2' %}
{% import 'materialize/wtf.html.j2' as wtf %}
{% block page_content %}
<div class="container">
<div class="row">
<div class="col s12">
<h1 id="title">Settings</h1>
</div>
<div class="col s12 m4">
{% include 'settings/_menu.html.j2' %}
</div>
<div class="col s12 m8">
<div class="card">
<form enctype="multipart/form-data" method="POST">
<div class="card-content">
<span class="card-title">{{ title }}</span>
{{ form.hidden_tag() }}
{{ wtf.render_field(form.username, data_length='64', material_icon='person') }}
{{ wtf.render_field(form.email, data_length='254', material_icon='email') }}
{{ wtf.render_field(form.dark_mode, material_icon='brightness_3') }}
</div>
<div class="card-action">
<div class="right-align">
{{ wtf.render_field(form.submit, material_icon='send') }}
</div>
</div>
</form>
</div>
<div class="card">
<div class="card-content">
<span class="card-title">Delete account</span>
<p>Deleting an account has the following effects:</p>
<ul>
<li>All data associated with your corpora and jobs will be permanently deleted.</li>
<li>All settings will be permanently deleted.</li>
</ul>
</div>
<div class="card-action right-align">
<a href="#delete-account-modal" class="btn modal-trigger red waves-effect waves-light"><i class="material-icons left">delete</i>Delete</a>
</div>
</div>
</div>
</div>
</div>
<!-- Modals -->
<div class="modal" id="delete-account-modal">
<div class="modal-content">
<h4>Confirm deletion</h4>
<p>Do you really want to delete your account and all associated data? All associated corpora, jobs and files will be permanently deleted!</p>
</div>
<div class="modal-footer">
<a href="#!" class="btn modal-close waves-effect waves-light">Cancel</a>
<a href="{{ url_for('.delete') }}" class="btn red waves-effect waves-light"><i class="material-icons left">delete</i>Delete</a>
</div>
</div>
{% endblock page_content %}

View File

@ -0,0 +1,34 @@
{% extends 'nopaque.html.j2' %}
{% import 'materialize/wtf.html.j2' as wtf %}
{% block page_content %}
<div class="container">
<div class="row">
<div class="col s12">
<h1 id="title">Settings</h1>
</div>
<div class="col s12 m4">
{% include 'settings/_menu.html.j2' %}
</div>
<div class="col s12 m8">
<div class="card">
<form enctype="multipart/form-data" method="POST">
<div class="card-content">
<span class="card-title">{{ title }}</span>
{{ form.hidden_tag() }}
{{ wtf.render_field(form.job_status_mail_notifications, material_icon='notifications') }}
{{ wtf.render_field(form.job_status_site_notifications, material_icon='feedback') }}
</div>
<div class="card-action">
<div class="right-align">
{{ wtf.render_field(form.submit, material_icon='send') }}
</div>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock page_content %}

View File

@ -31,6 +31,7 @@ class Config:
''' # General # ''' ''' # General # '''
ADMIN_EMAIL_ADRESS = os.environ.get('NOPAQUE_ADMIN_EMAIL_ADRESS') ADMIN_EMAIL_ADRESS = os.environ.get('NOPAQUE_ADMIN_EMAIL_ADRESS')
ALLOWED_USERNAME_REGEX = '^[A-Za-zÄÖÜäöüß0-9_.]*$'
CONTACT_EMAIL_ADRESS = os.environ.get('NOPAQUE_CONTACT_EMAIL_ADRESS') CONTACT_EMAIL_ADRESS = os.environ.get('NOPAQUE_CONTACT_EMAIL_ADRESS')
DATA_DIR = os.environ.get('NOPAQUE_DATA_DIR', '/mnt/nopaque') DATA_DIR = os.environ.get('NOPAQUE_DATA_DIR', '/mnt/nopaque')
SECRET_KEY = os.environ.get('NOPAQUE_SECRET_KEY', 'hard to guess string') SECRET_KEY = os.environ.get('NOPAQUE_SECRET_KEY', 'hard to guess string')