Updated URL Logic for admin package

This commit is contained in:
Patrick Jentsch 2019-11-15 11:45:04 +01:00
parent 74324ac0be
commit f22bff4ed0
13 changed files with 402 additions and 414 deletions

View File

@ -5,64 +5,51 @@ from app.tables import AdminUserItem, AdminUserTable
from app.profile.background_functions import delete_user_ from app.profile.background_functions import delete_user_
from flask import current_app, flash, redirect, render_template, url_for from flask import current_app, flash, redirect, render_template, url_for
from flask_login import login_required from flask_login import login_required
from threading import Thread
from . import admin from . import admin
from .forms import EditProfileAdminForm from .forms import EditProfileAdminForm
import threading
@admin.route('/overview', methods=['GET', 'POST']) @admin.route('/board')
@login_required @login_required
@admin_required @admin_required
def for_admins_only(): def board():
users = User.query.order_by(User.username).all() users = User.query.all()
items = [AdminUserItem(u.username, u.email, u.role_id, u.confirmed, u.id) items = [AdminUserItem(u.username, u.email, u.role_id, u.confirmed, u.id)
for u in users] for u in users]
# Convert table object to html string # Convert table object to html string
table = AdminUserTable(items).__html__() table = AdminUserTable(items).__html__()
# Add class "list" to tbody element. Needed for "List.js" # Add class "list" to tbody element. Needed for "List.js"
table = table.replace('tbody', 'tbody class="list"', 1) table = table.replace('tbody', 'tbody class="list"', 1)
return render_template('admin/admin.html.j2', return render_template('admin/board.html.j2', table=table,
table=table, title='Admin board')
title='Administration tools')
@admin.route('/overview/admin_user_page/<int:user_id>', @admin.route('/user/<int:user_id>')
methods=['GET', 'POST'])
@login_required @login_required
@admin_required @admin_required
def admin_user_page(user_id): def user(user_id):
selected_user = User.query.get_or_404(user_id) user = User.query.get_or_404(user_id)
title = 'Administration of user {} with ID: {}'.format( return render_template('admin/user.html.j2', title='Administrate user',
selected_user.username, user=user)
selected_user.id
)
registration_date = selected_user.registration_date.strftime(
'%A, %e %B %H:%M'
)
return render_template('admin/admin_user_page.html.j2',
registration_date=registration_date,
selected_user=selected_user,
title=title)
@admin.route('/overview/admin_user_page/delete/<int:user_id>', @admin.route('/user/<int:user_id>/delete')
methods=['GET', 'POST'])
@login_required @login_required
@admin_required @admin_required
def admin_delete_user(user_id): def delete_user(user_id):
delete_thread = threading.Thread(target=delete_user_, user = User.query.get_or_404(user_id)
args=(current_app._get_current_object(), thread = Thread(target=delete_user_,
user_id)) args=(current_app._get_current_object(), user.id))
delete_thread.start() thread.start()
flash('User {} has been deleted!'.format(user_id)) flash('User has been deleted!')
return redirect(url_for('admin.for_admins_only')) return redirect(url_for('admin.board'))
@admin.route('/overview/admin_user_page/edit_profile_admin/<int:user_id>', @admin.route('/user/<int:user_id>/edit', methods=['GET', 'POST'])
methods=['GET', 'POST'])
@login_required @login_required
@admin_required @admin_required
def edit_profile_admin(user_id): def edit_user(user_id):
user = User.query.get_or_404(user_id) user = User.query.get_or_404(user_id)
form = EditProfileAdminForm(user=user) form = EditProfileAdminForm(user=user)
if form.validate_on_submit(): if form.validate_on_submit():
@ -73,16 +60,10 @@ def edit_profile_admin(user_id):
db.session.add(user) db.session.add(user)
db.session.commit() db.session.commit()
flash('The profile has been updated.') flash('The profile has been updated.')
return redirect(url_for('admin.edit_profile_admin', user_id=user.id)) return redirect(url_for('admin.edit_user', user_id=user.id))
form.email.data = user.email form.email.data = user.email
form.username.data = user.username form.username.data = user.username
form.confirmed.data = user.confirmed form.confirmed.data = user.confirmed
form.role.data = user.role_id form.role.data = user.role_id
title = 'Edit profile of user {} with ID {}'.format( return render_template('admin/edit_user.html.j2', form=form,
user.username, title='Edit user', user=user)
user.id
)
return render_template('admin/edit_profile_admin.html.j2',
form=form,
title=title,
user=user)

View File

@ -3,6 +3,7 @@ from app.models import Corpus, CorpusFile
from flask import (abort, current_app, flash, redirect, request, from flask import (abort, current_app, flash, redirect, request,
render_template, url_for, send_from_directory) render_template, url_for, send_from_directory)
from flask_login import current_user, login_required from flask_login import current_user, login_required
from threading import Thread
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
from . import corpora from . import corpora
from .background_functions import (delete_corpus_, delete_corpus_file_, from .background_functions import (delete_corpus_, delete_corpus_file_,
@ -10,7 +11,6 @@ from .background_functions import (delete_corpus_, delete_corpus_file_,
from .forms import (AddCorpusFileForm, AddCorpusForm, EditCorpusFileForm, from .forms import (AddCorpusFileForm, AddCorpusForm, EditCorpusFileForm,
QueryDownloadForm, QueryForm) QueryDownloadForm, QueryForm)
import os import os
import threading
@corpora.route('/add', methods=['GET', 'POST']) @corpora.route('/add', methods=['GET', 'POST'])
@ -71,9 +71,8 @@ def delete_corpus(corpus_id):
corpus = Corpus.query.get_or_404(corpus_id) corpus = Corpus.query.get_or_404(corpus_id)
if not (corpus.creator == current_user or current_user.is_administrator()): if not (corpus.creator == current_user or current_user.is_administrator()):
abort(403) abort(403)
thread = threading.Thread(target=delete_corpus_, thread = Thread(target=delete_corpus_,
args=(current_app._get_current_object(), args=(current_app._get_current_object(), corpus.id))
corpus.id))
thread.start() thread.start()
flash('Corpus deleted!') flash('Corpus deleted!')
return redirect(url_for('main.dashboard')) return redirect(url_for('main.dashboard'))
@ -106,9 +105,9 @@ def add_corpus_file(corpus_id):
title=add_corpus_file_form.title.data) title=add_corpus_file_form.title.data)
db.session.add(corpus_file) db.session.add(corpus_file)
db.session.commit() db.session.commit()
thread = threading.Thread(target=edit_corpus_file_, thread = Thread(target=edit_corpus_file_,
args=(current_app._get_current_object(), args=(current_app._get_current_object(),
corpus_file.id)) corpus_file.id))
thread.start() thread.start()
flash('Corpus file added!') flash('Corpus file added!')
return redirect(url_for('corpora.corpus', corpus_id=corpus_id)) return redirect(url_for('corpora.corpus', corpus_id=corpus_id))
@ -126,9 +125,8 @@ def delete_corpus_file(corpus_id, corpus_file_id):
if not (corpus_file.corpus.creator == current_user if not (corpus_file.corpus.creator == current_user
or current_user.is_administrator()): or current_user.is_administrator()):
abort(403) abort(403)
thread = threading.Thread(target=delete_corpus_file_, thread = Thread(target=delete_corpus_file_,
args=(current_app._get_current_object(), args=(current_app._get_current_object(), corpus_file.id))
corpus_file.id))
thread.start() thread.start()
flash('Corpus file deleted!') flash('Corpus file deleted!')
return redirect(url_for('corpora.corpus', corpus_id=corpus_id)) return redirect(url_for('corpora.corpus', corpus_id=corpus_id))
@ -165,9 +163,9 @@ def edit_corpus_file(corpus_id, corpus_file_id):
corpus_file.publishing_year = edit_corpus_file_form.publishing_year.data corpus_file.publishing_year = edit_corpus_file_form.publishing_year.data
corpus_file.title = edit_corpus_file_form.title.data corpus_file.title = edit_corpus_file_form.title.data
db.session.commit() db.session.commit()
thread = threading.Thread(target=edit_corpus_file_, thread = Thread(target=edit_corpus_file_,
args=(current_app._get_current_object(), args=(current_app._get_current_object(),
corpus_file.id)) corpus_file.id))
thread.start() thread.start()
flash('Corpus file edited!') flash('Corpus file edited!')
return redirect(url_for('corpora.corpus', corpus_id=corpus_id)) return redirect(url_for('corpora.corpus', corpus_id=corpus_id))

View File

@ -13,7 +13,7 @@ def send_email(to, subject, template, **kwargs):
msg = Message('[Opaque] {}'.format(subject), recipients=[to]) msg = Message('[Opaque] {}'.format(subject), recipients=[to])
msg.body = render_template(template + '.txt.j2', **kwargs) msg.body = render_template(template + '.txt.j2', **kwargs)
msg.html = render_template(template + '.html.j2', **kwargs) msg.html = render_template(template + '.html.j2', **kwargs)
thr = Thread(target=send_async_email, thread = Thread(target=send_async_email,
args=[current_app._get_current_object(), msg]) args=(current_app._get_current_object(), msg))
thr.start() thread.start()
return thr return thread

View File

@ -2,10 +2,10 @@ from app.models import Job, JobInput, JobResult
from flask import (abort, current_app, flash, redirect, render_template, from flask import (abort, current_app, flash, redirect, render_template,
send_from_directory, url_for) send_from_directory, url_for)
from flask_login import current_user, login_required from flask_login import current_user, login_required
from threading import Thread
from . import jobs from . import jobs
from .background_functions import delete_job_ from .background_functions import delete_job_
import os import os
import threading
@jobs.route('/<int:job_id>') @jobs.route('/<int:job_id>')
@ -23,10 +23,9 @@ def delete_job(job_id):
job = Job.query.get_or_404(job_id) job = Job.query.get_or_404(job_id)
if not (job.creator == current_user or current_user.is_administrator()): if not (job.creator == current_user or current_user.is_administrator()):
abort(403) abort(403)
delete_thread = threading.Thread(target=delete_job_, thread = Thread(target=delete_job_,
args=(current_app._get_current_object(), args=(current_app._get_current_object(), job_id))
job_id)) thread.start()
delete_thread.start()
flash('Job has been deleted!') flash('Job has been deleted!')
return redirect(url_for('main.dashboard')) return redirect(url_for('main.dashboard'))

View File

@ -21,7 +21,7 @@ class AdminUserTable(Table):
id = Col('User Id', column_html_attrs={'class': 'id'}, id = Col('User Id', column_html_attrs={'class': 'id'},
th_html_attrs={'class': 'sort', th_html_attrs={'class': 'sort',
'data-sort': 'id'}) 'data-sort': 'id'})
url = LinkCol('Profile', 'admin.admin_user_page', url = LinkCol('Profile', 'admin.user',
url_kwargs=dict(user_id='id'), url_kwargs=dict(user_id='id'),
anchor_attrs={'class': 'waves-effect waves-light btn-small'}) anchor_attrs={'class': 'waves-effect waves-light btn-small'})

View File

@ -1,28 +0,0 @@
{% extends "full_width.html.j2" %}
{% block page_content %}
<div class="col s12">
<div class="card">
<div class="card-content">
<span class="card-title">User list</span>
<div id="users">
<div class="input-field">
<i class="material-icons prefix">search</i>
<input id="search-corpus" class="search" type="text"></input>
<label for="search-corpus">Search users</label>
</div>
{{ table }}
<ul class="pagination"></ul>
</div>
</div>
</div>
</div>
<script type="text/javascript">
var options = {
valueNames: ['username', 'email', 'role', 'confirmed', 'id'],
page: 10,
pagination: true
};
var userList = new List('users', options);
</script>
{% endblock %}

View File

@ -1,104 +0,0 @@
{% extends "limited_width.html.j2" %}
{% block page_content %}
<div class="col s12 m6">
<div class="card large">
<div class="card-content">
<span class="card-title">User information</span>
<ul>
<li>Username: {{selected_user.username}}</li>
<li>Email: {{selected_user.email}}</li>
<li>ID: {{selected_user.id}}</li>
<li>Registration date: {{registration_date}}</li>
<li>Confirmed status: {{selected_user.confirmed}}</li>
<li>Role ID: {{selected_user.role_id}}</li>
<li>Permissions as Int: {{selected_user.role.permissions}}</li>
<li>Role name: {{selected_user.role.name}}</li>
</ul>
<div class="card-action">
<a href="{{url_for('admin.edit_profile_admin', user_id=selected_user.id)}}" class="waves-effect waves-light btn"><i class="material-icons left">edit</i>Edit user</a>
<a href="#modal-confirm-delete" class="waves-effect waves-light btn red modal-trigger"><i class="material-icons left">delete</i>Delete User</a>
<!-- Modal Strucutre -->
<div id="modal-confirm-delete" class="modal">
<div class="modal-content">
<h4>Confirm deletion</h4>
<p>Do you really want to delete the current selected user ({{selected_user.username}})?
All associated jobs and job files will be permanently deleted.</p>
</div>
<div class="modal-footer">
<a href="{{url_for('admin.admin_delete_user', user_id=selected_user.id)}}" class="modal-close waves-effect waves-green btn red"><i class="material-icons left">delete</i>Delete User</a>
<a href="#!" class="modal-close waves-effect waves-green btn cancel">Cancel</a>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
socket.emit('subscribe_foreign_user_ressources', {{ selected_user.id }});
</script>
<div class="col s12 m6">
<div id="job-foreign-list">
<div class="card">
<div class="card-content">
<span class="card-title">User Jobs</span>
<div class="row">
<div class="col s12">
<div class="input-field">
<i class="material-icons prefix">search</i>
<input id="search-job" class="search" type="text"></input>
<label for="search-job">Search job</label>
</div>
</div>
<div class="col s12">
<ul class="pagination"></ul>
</div>
</div>
</div>
</div>
<div class="collection list"></div>
</div>
</div>
<script>
var jobList = new JobList("job-foreign-list", foreignJobsSubscribers, {
item: '<div><span class="title"></span><span class="description"></span></div>',
page: 4,
pagination: true,
valueNames: ["description", "title", {data: ["id"]}]
});
jobList.on("filterComplete", List.updatePagination);
jobList.on("searchComplete", List.updatePagination);
</script>
<div class="col s12 m6">
<div id="corpus-foreign-list">
<div class="card">
<div class="card-content">
<span class="card-title">User Corpora</span>
<div class="row">
<div class="col s12">
<div class="input-field">
<i class="material-icons prefix">search</i>
<input id="search-job" class="search" type="text"></input>
<label for="search-job">Search corpus</label>
</div>
</div>
<div class="col s12">
<ul class="pagination"></ul>
</div>
</div>
</div>
</div>
<div class="collection list"></div>
</div>
</div>
<script>
var corpusList = new CorpusList("corpus-foreign-list", foreignCorporaSubscribers, {
item: '<div><span class="title"></span><span class="description"></span></div>',
page: 4,
pagination: true,
valueNames: ["description", "title", {data: ["id"]}]
});
corpusList.on("filterComplete", List.updatePagination);
corpusList.on("searchComplete", List.updatePagination);
</script>
{% endblock %}

View File

@ -0,0 +1,26 @@
{% extends "full_width.html.j2" %}
{% block page_content %}
<div class="col s12">
<div id="user-list">
<div class="card">
<div class="card-content">
<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="text"></input>
<label for="search-user">Search user</label>
</div>
{{ table }}
<ul class="pagination"></ul>
</div>
</div>
</div>
</div>
<script>
var options = {page: 10, pagination: true,
valueNames: ['username', 'email', 'role', 'confirmed', 'id']};
var userList = new List('user-list', options);
</script>
{% endblock %}

View File

@ -1,6 +1,12 @@
{% extends "limited_width.html.j2" %} {% extends "limited_width.html.j2" %}
{% block page_content %} {% block page_content %}
<div class="col s12 m4">
<h3 id="title">{{ user.username }}</h3>
<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>
<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>
</div>
<div class="col s12 m8"> <div class="col s12 m8">
<div class="card"> <div class="card">
<form method="POST"> <form method="POST">
@ -11,7 +17,7 @@
{{ form.username() }} {{ form.username() }}
{{ form.username.label }} {{ form.username.label }}
{% for error in form.username.errors %} {% for error in form.username.errors %}
<span class="helper-text red-text">{{ error }}</span> <span class="helper-text red-text">{{ error }}</span>
{% endfor %} {% endfor %}
</div> </div>
<div class="input-field"> <div class="input-field">
@ -19,7 +25,7 @@
{{ form.email() }} {{ form.email() }}
{{ form.email.label }} {{ form.email.label }}
{% for error in form.email.errors %} {% for error in form.email.errors %}
<span class="helper-text red-text">{{ error }}</span> <span class="helper-text red-text">{{ error }}</span>
{% endfor %} {% endfor %}
</div> </div>
<div class="input-field"> <div class="input-field">
@ -27,7 +33,7 @@
{{ form.role() }} {{ form.role() }}
{{ form.role.label }} {{ form.role.label }}
{% for error in form.role.errors %} {% for error in form.role.errors %}
<span class="helper-text red-text">{{ error }}</span> <span class="helper-text red-text">{{ error }}</span>
{% endfor %} {% endfor %}
</div> </div>
<div class="switch"> <div class="switch">
@ -36,22 +42,21 @@
Confirmed status: Confirmed status:
Off Off
{% if form.confirmed.data == True %} {% if form.confirmed.data == True %}
<input type="checkbox" id="{{form.confirmed.name}}" name="{{form.confirmed.name}}" checked="checked"> <input type="checkbox" id="{{ form.confirmed.name }}" name="{{ form.confirmed.name }}" checked="checked">
{% else %} {% else %}
<input type="checkbox" id="{{form.confirmed.name}}" name="{{form.confirmed.name}}"> <input type="checkbox" id="{{ form.confirmed.name }}" name="{{ form.confirmed.name }}">
{% endif %} {% endif %}
<span class="lever"></span> <span class="lever"></span>
On On
</label> </label>
{% for error in form.confirmed.errors %} {% for error in form.confirmed.errors %}
<span class="helper-text red-text">{{ error }}</span> <span class="helper-text red-text">{{ error }}</span>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
<div class="card-action right-align"> <div class="card-action right-align">
{{ form.submit(class='btn') }} <button class="btn waves-effect waves-light" id="submit" name="submit" type="submit">Submit<i class="material-icons right">send</i></button>
</div> </div>
</div>
</form> </form>
</div> </div>
</div> </div>

View File

@ -0,0 +1,115 @@
{% extends "limited_width.html.j2" %}
{% block page_content %}
<div class="col s12 m4">
<h3 id="title">{{ user.username }}</h3>
<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>
<a class="waves-effect waves-light btn" href="{{ url_for('admin.board') }}"><i class="material-icons left">arrow_back</i>Back to admin board</a>
</div>
<div class="col s12 m8">
<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>Registration date: {{ user.registration_date.strftime('%m/%d/%Y, %H:%M:%S %p') }}</li>
<li>Confirmed status: {{ user.confirmed }}</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('admin.edit_user', user_id=user.id) }}" class="waves-effect waves-light btn"><i class="material-icons left">edit</i>Edit user</a>
<a data-target="delete-user-modal" class="waves-effect waves-light btn red modal-trigger"><i class="material-icons left">delete</i>Delete user</a>
</div>
</div>
</div>
<div class="col s12"></div>
<div class="col s12 m6">
<div id="corpus-list">
<div class="card">
<div class="card-content">
<span class="card-title">Corpora</span>
<div class="row">
<div class="col s12">
<div class="input-field">
<i class="material-icons prefix">search</i>
<input id="search-corpus" class="search" type="text"></input>
<label for="search-corpus">Search corpus</label>
</div>
</div>
<div class="col s12">
<ul class="pagination"></ul>
</div>
</div>
</div>
</div>
<div class="collection list"></div>
</div>
</div>
<div class="col s12 m6">
<div id="job-list">
<div class="card">
<div class="card-content">
<span class="card-title">Jobs</span>
<div class="row">
<div class="col s12">
<div class="input-field">
<i class="material-icons prefix">search</i>
<input id="search-job" class="search" type="text"></input>
<label for="search-job">Search job</label>
</div>
</div>
<div class="col s12">
<ul class="pagination"></ul>
</div>
</div>
</div>
</div>
<div class="collection list"></div>
</div>
</div>
<!-- Modals -->
<div id="delete-user-modal" class="modal">
<div class="modal-content">
<h4>Confirm user deletion</h4>
<p>Do you really want to delete the user {{ user.username }}? All associated data will be permanently deleted!</p>
</div>
<div class="modal-footer">
<a href="#!" class="modal-close waves-effect waves-green btn cancel">Cancel</a>
<a href="{{ url_for('admin.delete_user', user_id=user.id) }}" class="modal-close waves-effect waves-green btn red">Confirm<i class="material-icons right">send</i></a>
</div>
</div>
<script>
socket.emit('subscribe_foreign_user_ressources', {{ user.id }});
var corpusList = new CorpusList("corpus-list", foreignCorporaSubscribers, {
item: '<div><span class="title"></span><span class="description"></span></div>',
page: 4,
pagination: true,
valueNames: ["description", "title", {data: ["id"]}]
});
corpusList.on("filterComplete", List.updatePagination);
corpusList.on("searchComplete", List.updatePagination);
var jobList = new JobList("job-list", foreignJobsSubscribers, {
item: '<div><span class="title"></span><span class="description"></span></div>',
page: 4,
pagination: true,
valueNames: ["description", "title", {data: ["id"]}]
});
jobList.on("filterComplete", List.updatePagination);
jobList.on("searchComplete", List.updatePagination);
</script>
{% endblock %}

View File

@ -37,7 +37,6 @@
var jobsSubscribers = []; var jobsSubscribers = [];
var socket = io(); var socket = io();
socket.emit('subscribe_user_ressources');
socket.on('init-corpora', function(msg) { socket.on('init-corpora', function(msg) {
corpora = JSON.parse(msg); corpora = JSON.parse(msg);
for (let subscriber of corporaSubscribers) {subscriber._init(corpora);} for (let subscriber of corporaSubscribers) {subscriber._init(corpora);}
@ -141,7 +140,7 @@
{% 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.for_admins_only') }}"><i class="material-icons">build</i>Administration tools</a></li> <li><a href="{{ url_for('admin.board') }}"><i class="material-icons">build</i>Administration tools</a></li>
{% endif %} {% endif %}
<div class="hide-on-large-only"> <div class="hide-on-large-only">
<li><div class="divider"></div></li> <li><div class="divider"></div></li>
@ -170,28 +169,31 @@
</div> </div>
</footer> </footer>
<script type="text/javascript" src="{{ url_for('static', filename='js/materialize.min.js') }}"></script>
<script> <script>
M.AutoInit(); socket.emit('subscribe_user_ressources');
M.CharacterCounter.init(document.querySelectorAll('input[data-length][type="text"]')) </script>
M.Dropdown.init( <script src="{{ url_for('static', filename='js/materialize.min.js') }}"></script>
document.getElementById("nav-notifications"), <script>
{"alignment": "right", "constrainWidth": false, "coverTrigger": false} M.AutoInit();
); M.CharacterCounter.init(document.querySelectorAll('input[data-length][type="text"]'))
M.Dropdown.init( M.Dropdown.init(
document.getElementById("nav-account"), document.getElementById("nav-notifications"),
{"alignment": "right", "constrainWidth": false, "coverTrigger": false} {"alignment": "right", "constrainWidth": false, "coverTrigger": false}
); );
// Highlight current navigation entry M.Dropdown.init(
var entry; document.getElementById("nav-account"),
for (entry of document.querySelectorAll("#slide-out a:not(.subheader)")) { {"alignment": "right", "constrainWidth": false, "coverTrigger": false}
if (entry.href === window.location.href) { );
entry.parentNode.classList.add("active"); // Highlight current navigation entry
} var entry;
for (entry of document.querySelectorAll("#slide-out a:not(.subheader)")) {
if (entry.href === window.location.href) {
entry.parentNode.classList.add("active");
} }
{% for message in get_flashed_messages() %} }
M.toast({html: '{{ message }}'}) {% for message in get_flashed_messages() %}
{% endfor %} M.toast({html: '{{ message }}'})
{% endfor %}
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,83 +1,6 @@
{% extends "limited_width.html.j2" %} {% extends "limited_width.html.j2" %}
{% block page_content %} {% block page_content %}
<script>
{% if corpus.creator == current_user %}
var foreignCorpusFlag = false;
{% else %}
var foreignCorpusFlag = true;
socket.emit('subscribe_foreign_user_ressources', {{ corpus.user_id }});
{% endif %}
class InformationUpdater {
constructor(corpusId) {
this.corpusId = corpusId;
if (foreignCorpusFlag) {
foreignCorporaSubscribers.push(this);
} else {
corporaSubscribers.push(this);
}
}
_init() {
if (foreignCorpusFlag) {
this.corpus = foreignCorpora[this.corpusId];
} else {
this.corpus = corpora[this.corpusId];
}
// Status
this.setStatus(this.corpus.status);
}
_update(patch) {
var pathArray;
for (let operation of patch) {
/* "/corpusId/valueName" -> ["corpusId", "valueName"] */
pathArray = operation.path.split("/").slice(1);
if (pathArray[0] != this.corpusId) {continue;}
switch(operation.op) {
case "add":
location.reload();
break;
case "delete":
location.reload();
break;
case "replace":
if (pathArray[1] === "status") {
this.setStatus(operation.value);
}
break;
default:
break;
}
}
}
setStatus(status) {
var statusElement;
statusElement = document.getElementById("status");
statusElement.classList.remove(...Object.values(CorpusList.STATUS_COLORS));
statusElement.classList.add(CorpusList.STATUS_COLORS[status] || CorpusList.STATUS_COLORS['default']);
statusElement.innerText = status;
var analyseBtn = document.getElementById('analyse');
if (status === 'prepared' || status === 'analysing') {
analyseBtn.classList.remove('hide', 'disabled');
} else if (status === 'start analysis' || status === 'stop analysis') {
analyseBtn.classList.remove('hide');
analyseBtn.classList.add('disabled');
}
if (status === 'prepared' || status === 'preparable' || status === 'preparing' || status === 'start analysis' || status === 'analysing' || status === 'stop analysis') {
var prepareBtn = document.getElementById('prepare');
prepareBtn.classList.add('hide');
}
}
}
var informationUpdater = new InformationUpdater({{ corpus.id }});
</script>
<div class="col s12 m4"> <div class="col s12 m4">
<h3 id="title">{{ corpus.title }}</h3> <h3 id="title">{{ corpus.title }}</h3>
<p id="description">{{ corpus.description }}</p> <p id="description">{{ corpus.description }}</p>
@ -146,11 +69,12 @@
</div> </div>
</div> </div>
<!-- Modals --> <!-- Modals -->
<div id="delete-corpus-modal" class="modal"> <div id="delete-corpus-modal" class="modal">
<div class="modal-content"> <div class="modal-content">
<h4>Confirm corpus deletion</h4> <h4>Confirm corpus deletion</h4>
<p>Do you really want to delete the Corpus {{corpus.title}}? All files will be permanently deleted!</p> <p>Do you really want to delete the corpus {{corpus.title}}? All files will be permanently deleted!</p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<a href="#!" class="modal-close waves-effect waves-green btn cancel">Cancel</a> <a href="#!" class="modal-close waves-effect waves-green btn cancel">Cancel</a>
@ -169,5 +93,78 @@
<a class="modal-close waves-effect waves-green btn red" href="{{ url_for('corpora.delete_corpus_file', corpus_file_id=file.id, corpus_id=corpus.id) }}">Confirm<i class="material-icons right">send</i></a> <a class="modal-close waves-effect waves-green btn red" href="{{ url_for('corpora.delete_corpus_file', corpus_file_id=file.id, corpus_id=corpus.id) }}">Confirm<i class="material-icons right">send</i></a>
</div> </div>
</div> </div>
<script>
class InformationUpdater {
constructor(corpusId, foreignCorpusFlag) {
this.corpusId = corpusId;
this.foreignCorpusFlag = foreignCorpusFlag;
if (this.foreignCorpusFlag) {
foreignCorporaSubscribers.push(this);
} else {
corporaSubscribers.push(this);
}
}
_init() {
var corpus = this.foreignCorpusFlag ? foreignCorpora[this.corpusId] : corpora[this.corpusId];
// Status
this.setStatus(corpus.status);
}
_update(patch) {
var pathArray;
for (let operation of patch) {
/* "/corpusId/valueName" -> ["corpusId", "valueName"] */
pathArray = operation.path.split("/").slice(1);
if (pathArray[0] != this.corpusId) {continue;}
switch(operation.op) {
case "add":
location.reload();
break;
case "delete":
location.reload();
break;
case "replace":
if (pathArray[1] === "status") {
this.setStatus(operation.value);
}
break;
default:
break;
}
}
}
setStatus(status) {
var statusElement;
statusElement = document.getElementById("status");
statusElement.classList.remove(...Object.values(CorpusList.STATUS_COLORS));
statusElement.classList.add(CorpusList.STATUS_COLORS[status] || CorpusList.STATUS_COLORS['default']);
statusElement.innerText = status;
var analyseBtn = document.getElementById('analyse');
if (status === 'prepared' || status === 'analysing') {
analyseBtn.classList.remove('hide', 'disabled');
} else if (status === 'start analysis' || status === 'stop analysis') {
analyseBtn.classList.remove('hide');
analyseBtn.classList.add('disabled');
}
if (status === 'prepared' || status === 'preparable' || status === 'preparing' || status === 'start analysis' || status === 'analysing' || status === 'stop analysis') {
var prepareBtn = document.getElementById('prepare');
prepareBtn.classList.add('hide');
}
}
}
{% if corpus.creator == current_user %}
var informationUpdater = new InformationUpdater({{ corpus.id }}, false);
{% else %}
var informationUpdater = new InformationUpdater({{ corpus.id }}, true);
socket.emit('subscribe_foreign_user_ressources', {{ corpus.user_id }});
{% endif %}
</script>
{% endfor %} {% endfor %}
{% endblock %} {% endblock %}

View File

@ -1,114 +1,6 @@
{% extends "limited_width.html.j2" %} {% extends "limited_width.html.j2" %}
{% block page_content %} {% block page_content %}
<script>
{% if job.creator == current_user %}
var foreignJobFlag = false;
{% else %}
var foreignJobFlag = true;
socket.emit('subscribe_foreign_user_ressources', {{ job.user_id }});
{% endif %}
class InformationUpdater {
constructor(jobId) {
this.jobId = jobId;
if (foreignJobFlag) {
foreignJobsSubscribers.push(this);
} else {
jobsSubscribers.push(this);
}
}
_init() {
if (foreignJobFlag) {
this.job = foreignJobs[this.jobId];
} else {
this.job = jobs[this.jobId];
}
// End date
this.setEndDate(this.job.end_date);
// Status
this.setStatus(this.job.status);
// End date
if (this.job.end_date) {this.setEndDate(this.job.end_date);}
// Input results
for (let input of this.job.inputs) {
for (let result of input.results) {
this.setResult(result);
}
}
}
_update(patch) {
var pathArray;
for (let operation of patch) {
/* "/jobId/valueName" -> ["jobId", "valueName"] */
pathArray = operation.path.split("/").slice(1);
if (pathArray[0] != this.jobId) {continue;}
switch(operation.op) {
case "add":
if (pathArray[1] === "inputs" && pathArray[3] === "results") {
this.setResult(operation.value);
}
break;
case "delete":
location.reload();
break;
case "replace":
if (pathArray[1] === "end_date") {
this.setEndDate(operation.value);
} else if (pathArray[1] === "status") {
this.setStatus(operation.value);
}
break;
default:
break;
}
}
}
setEndDate(timestamp) {
var end_date;
if (timestamp === null) {
end_date = "N.a.";
} else {
end_date = new Date(timestamp * 1000).toLocaleString("en-US");
}
document.getElementById("end-date").value = end_date;
M.updateTextFields();
}
setResult(result) {
var resultsElement, resultDownloadButtonElement,
resultDownloadButtonIconElement;
resultsElement = document.getElementById(`input-${result.job_input_id}-results`);
resultDownloadButtonElement = document.createElement("a");
resultDownloadButtonElement.classList.add("waves-effect", "waves-light", "btn-small");
resultDownloadButtonElement.href = `/jobs/${this.jobId}/results/${result.id}/download`;
resultDownloadButtonElement.innerText = result.filename.split(".").reverse()[0];
resultDownloadButtonElement.setAttribute("download", "");
resultDownloadButtonIconElement = document.createElement("i");
resultDownloadButtonIconElement.classList.add("material-icons", "left");
resultDownloadButtonIconElement.innerText = "file_download";
resultDownloadButtonElement.prepend(resultDownloadButtonIconElement);
resultsElement.append(resultDownloadButtonElement);
resultsElement.append(" ");
}
setStatus(status) {
var statusElement;
statusElement = document.getElementById("status");
statusElement.classList.remove(...Object.values(JobList.STATUS_COLORS));
statusElement.classList.add(JobList.STATUS_COLORS[status] || JobList.STATUS_COLORS['default']);
statusElement.innerText = status;
}
}
var informationUpdater = new InformationUpdater({{ job.id }});
</script>
<div class="col s12 m4"> <div class="col s12 m4">
<h3 id="title">{{ job.title }}</h3> <h3 id="title">{{ job.title }}</h3>
<p id="description">{{ job.description }}</p> <p id="description">{{ job.description }}</p>
@ -210,6 +102,7 @@
</div> </div>
</div> </div>
<!-- Modals --> <!-- Modals -->
<div id="delete-job-modal" class="modal"> <div id="delete-job-modal" class="modal">
<div class="modal-content"> <div class="modal-content">
@ -221,4 +114,108 @@
<a class="modal-close waves-effect waves-green btn red" href="{{ url_for('jobs.delete_job', job_id=job.id) }}">Confirm<i class="material-icons right">send</i></a> <a class="modal-close waves-effect waves-green btn red" href="{{ url_for('jobs.delete_job', job_id=job.id) }}">Confirm<i class="material-icons right">send</i></a>
</div> </div>
</div> </div>
<script>
class InformationUpdater {
constructor(jobId, foreignJobFlag) {
this.jobId = jobId;
this.foreignJobFlag = foreignJobFlag;
if (this.foreignJobFlag) {
foreignJobsSubscribers.push(this);
} else {
jobsSubscribers.push(this);
}
}
_init() {
var job = this.foreignJobFlag ? foreignJobs[this.jobId] : jobs[this.jobId];
// End date
this.setEndDate(job.end_date);
// Status
this.setStatus(job.status);
// End date
if (job.end_date) {this.setEndDate(job.end_date);}
// Input results
for (let input of job.inputs) {
for (let result of input.results) {
this.setResult(result);
}
}
}
_update(patch) {
var pathArray;
for (let operation of patch) {
/* "/jobId/valueName" -> ["jobId", "valueName"] */
pathArray = operation.path.split("/").slice(1);
if (pathArray[0] != this.jobId) {continue;}
switch(operation.op) {
case "add":
if (pathArray[1] === "inputs" && pathArray[3] === "results") {
this.setResult(operation.value);
}
break;
case "delete":
location.reload();
break;
case "replace":
if (pathArray[1] === "end_date") {
this.setEndDate(operation.value);
} else if (pathArray[1] === "status") {
this.setStatus(operation.value);
}
break;
default:
break;
}
}
}
setEndDate(timestamp) {
var end_date;
if (timestamp === null) {
end_date = "N.a.";
} else {
end_date = new Date(timestamp * 1000).toLocaleString("en-US");
}
document.getElementById("end-date").value = end_date;
M.updateTextFields();
}
setResult(result) {
var resultsElement, resultDownloadButtonElement,
resultDownloadButtonIconElement;
resultsElement = document.getElementById(`input-${result.job_input_id}-results`);
resultDownloadButtonElement = document.createElement("a");
resultDownloadButtonElement.classList.add("waves-effect", "waves-light", "btn-small");
resultDownloadButtonElement.href = `/jobs/${this.jobId}/results/${result.id}/download`;
resultDownloadButtonElement.innerText = result.filename.split(".").reverse()[0];
resultDownloadButtonElement.setAttribute("download", "");
resultDownloadButtonIconElement = document.createElement("i");
resultDownloadButtonIconElement.classList.add("material-icons", "left");
resultDownloadButtonIconElement.innerText = "file_download";
resultDownloadButtonElement.prepend(resultDownloadButtonIconElement);
resultsElement.append(resultDownloadButtonElement);
resultsElement.append(" ");
}
setStatus(status) {
var statusElement;
statusElement = document.getElementById("status");
statusElement.classList.remove(...Object.values(JobList.STATUS_COLORS));
statusElement.classList.add(JobList.STATUS_COLORS[status] || JobList.STATUS_COLORS['default']);
statusElement.innerText = status;
}
}
{% if job.creator == current_user %}
var informationUpdater = new InformationUpdater({{ job.id }}, false);
{% else %}
var informationUpdater = new InformationUpdater({{ job.id }}, true);
socket.emit('subscribe_foreign_user_ressources', {{ job.user_id }});
{% endif %}
</script>
{% endblock %} {% endblock %}