mirror of
https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
synced 2024-11-15 01:05:42 +00:00
Change the Subscription Logic for Socket.IO Data exchange
This commit is contained in:
parent
5771e156ce
commit
4e5957eea2
@ -11,8 +11,6 @@ from flask import (
|
|||||||
send_from_directory
|
send_from_directory
|
||||||
)
|
)
|
||||||
from flask_login import current_user, login_required
|
from flask_login import current_user, login_required
|
||||||
from werkzeug.utils import secure_filename
|
|
||||||
from zipfile import ZipFile
|
|
||||||
from . import bp
|
from . import bp
|
||||||
from . import tasks
|
from . import tasks
|
||||||
from .forms import (
|
from .forms import (
|
||||||
@ -24,7 +22,6 @@ from .forms import (
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
import glob
|
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,21 +1,25 @@
|
|||||||
class App {
|
class App {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.data = {users: {}};
|
this.data = {
|
||||||
this.promises = {users: {}};
|
promises: {getUser: {}, subscribeUser: {}},
|
||||||
|
users: {},
|
||||||
|
};
|
||||||
this.socket = io({transports: ['websocket'], upgrade: false});
|
this.socket = io({transports: ['websocket'], upgrade: false});
|
||||||
this.socket.on('PATCH', (patch) => {this.data = jsonpatch.applyPatch(this.data, patch).newDocument;});
|
this.socket.on('PATCH', (patch) => {
|
||||||
|
const re = new RegExp(`^/users/(${Object.keys(this.data.users).join('|')})`);
|
||||||
|
const filteredPatch = patch.filter(operation => re.test(operation.path));
|
||||||
|
|
||||||
|
jsonpatch.applyPatch(this.data, filteredPatch);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get users() {
|
getUser(userId) {
|
||||||
return this.data.users;
|
if (userId in this.data.promises.getUser) {
|
||||||
|
return this.data.promises.getUser[userId];
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribeUser(userId) {
|
this.data.promises.getUser[userId] = new Promise((resolve, reject) => {
|
||||||
if (userId in this.promises.users) {
|
this.socket.emit('GET /users/<user_id>', userId, (response) => {
|
||||||
return this.promises.users[userId];
|
|
||||||
}
|
|
||||||
this.promises.users[userId] = new Promise((resolve, reject) => {
|
|
||||||
this.socket.emit('SUBSCRIBE /users/<user_id>', userId, response => {
|
|
||||||
if (response.code === 200) {
|
if (response.code === 200) {
|
||||||
this.data.users[userId] = response.payload;
|
this.data.users[userId] = response.payload;
|
||||||
resolve(this.data.users[userId]);
|
resolve(this.data.users[userId]);
|
||||||
@ -24,7 +28,26 @@ class App {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return this.promises.users[userId];
|
|
||||||
|
return this.data.promises.getUser[userId];
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribeUser(userId) {
|
||||||
|
if (userId in this.data.promises.subscribeUser) {
|
||||||
|
return this.data.promises.subscribeUser[userId];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.data.promises.subscribeUser[userId] = new Promise((resolve, reject) => {
|
||||||
|
this.socket.emit('SUBSCRIBE /users/<user_id>', userId, (response) => {
|
||||||
|
if (response.code === 200) {
|
||||||
|
resolve(response);
|
||||||
|
} else {
|
||||||
|
reject(response);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.data.promises.subscribeUser[userId];
|
||||||
}
|
}
|
||||||
|
|
||||||
flash(message, category) {
|
flash(message, category) {
|
||||||
|
@ -1,10 +1,18 @@
|
|||||||
class JobStatusNotifier {
|
class JobStatusNotifier {
|
||||||
constructor(userId) {
|
constructor(userId) {
|
||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
|
this.isInitialized = false;
|
||||||
|
app.subscribeUser(this.userId).then((response) => {
|
||||||
app.socket.on('PATCH', (patch) => {this.onPATCH(patch);});
|
app.socket.on('PATCH', (patch) => {this.onPATCH(patch);});
|
||||||
|
});
|
||||||
|
app.getUser(this.userId).then((user) => {
|
||||||
|
this.isInitialized = true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onPATCH(patch) {
|
onPATCH(patch) {
|
||||||
|
if (!this.isInitialized) {return;}
|
||||||
|
|
||||||
let filteredPatch;
|
let filteredPatch;
|
||||||
let jobId;
|
let jobId;
|
||||||
let match;
|
let match;
|
||||||
@ -13,11 +21,11 @@ class JobStatusNotifier {
|
|||||||
|
|
||||||
re = new RegExp(`^/users/${this.userId}/jobs/([A-Za-z0-9]*)/status$`);
|
re = new RegExp(`^/users/${this.userId}/jobs/([A-Za-z0-9]*)/status$`);
|
||||||
filteredPatch = patch
|
filteredPatch = patch
|
||||||
.filter(operation => operation.op === 'replace')
|
.filter((operation) => {return operation.op === 'replace';})
|
||||||
.filter(operation => re.test(operation.path));
|
.filter((operation) => {return re.test(operation.path);});
|
||||||
for (operation of filteredPatch) {
|
for (operation of filteredPatch) {
|
||||||
[match, jobId] = operation.path.match(re);
|
[match, jobId] = operation.path.match(re);
|
||||||
app.flash(`[<a href="/jobs/${jobId}">${app.users[this.userId].jobs[jobId].title}</a>] New status: <span class="job-status-text" data-job-status="${operation.value}"></span>`, 'job');
|
app.flash(`[<a href="/jobs/${jobId}">${app.data.users[this.userId].jobs[jobId].title}</a>] New status: <span class="job-status-text" data-job-status="${operation.value}"></span>`, 'job');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,8 @@ class CorpusDisplay extends RessourceDisplay {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init(user) {
|
init(user) {
|
||||||
let corpus;
|
const corpus = user.corpora[this.corpusId];
|
||||||
|
|
||||||
corpus = user.corpora[this.corpusId];
|
|
||||||
this.setCreationDate(corpus.creation_date);
|
this.setCreationDate(corpus.creation_date);
|
||||||
this.setDescription(corpus.description);
|
this.setDescription(corpus.description);
|
||||||
this.setLastEditedDate(corpus.last_edited_date);
|
this.setLastEditedDate(corpus.last_edited_date);
|
||||||
@ -17,12 +16,15 @@ class CorpusDisplay extends RessourceDisplay {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onPATCH(patch) {
|
onPATCH(patch) {
|
||||||
|
if (!this.isInitialized) {return;}
|
||||||
|
|
||||||
let filteredPatch;
|
let filteredPatch;
|
||||||
let operation;
|
let operation;
|
||||||
let re;
|
let re;
|
||||||
|
|
||||||
re = new RegExp(`^/users/${this.userId}/corpora/${this.corpusId}`);
|
re = new RegExp(`^/users/${this.userId}/corpora/${this.corpusId}`);
|
||||||
filteredPatch = patch.filter(operation => re.test(operation.path));
|
filteredPatch = patch.filter(operation => re.test(operation.path));
|
||||||
|
|
||||||
for (operation of filteredPatch) {
|
for (operation of filteredPatch) {
|
||||||
switch(operation.op) {
|
switch(operation.op) {
|
||||||
case 'replace':
|
case 'replace':
|
||||||
@ -55,7 +57,7 @@ class CorpusDisplay extends RessourceDisplay {
|
|||||||
setNumTokens(numTokens) {
|
setNumTokens(numTokens) {
|
||||||
this.setElements(
|
this.setElements(
|
||||||
this.displayElement.querySelectorAll('.corpus-token-ratio'),
|
this.displayElement.querySelectorAll('.corpus-token-ratio'),
|
||||||
`${numTokens}/${app.users[this.userId].corpora[this.corpusId].max_num_tokens}`
|
`${numTokens}/${app.data.users[this.userId].corpora[this.corpusId].max_num_tokens}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,7 +79,7 @@ class CorpusDisplay extends RessourceDisplay {
|
|||||||
}
|
}
|
||||||
elements = this.displayElement.querySelectorAll('.corpus-build-trigger');
|
elements = this.displayElement.querySelectorAll('.corpus-build-trigger');
|
||||||
for (element of elements) {
|
for (element of elements) {
|
||||||
if (['UNPREPARED', 'FAILED'].includes(status) && Object.values(app.users[this.userId].corpora[this.corpusId].files).length > 0) {
|
if (['UNPREPARED', 'FAILED'].includes(status) && Object.values(app.data.users[this.userId].corpora[this.corpusId].files).length > 0) {
|
||||||
element.classList.remove('disabled');
|
element.classList.remove('disabled');
|
||||||
} else {
|
} else {
|
||||||
element.classList.add('disabled');
|
element.classList.add('disabled');
|
||||||
|
@ -5,9 +5,8 @@ class JobDisplay extends RessourceDisplay {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init(user) {
|
init(user) {
|
||||||
let job;
|
const job = user.jobs[this.jobId];
|
||||||
|
|
||||||
job = user.jobs[this.jobId];
|
|
||||||
this.setCreationDate(job.creation_date);
|
this.setCreationDate(job.creation_date);
|
||||||
this.setEndDate(job.creation_date);
|
this.setEndDate(job.creation_date);
|
||||||
this.setDescription(job.description);
|
this.setDescription(job.description);
|
||||||
@ -19,12 +18,15 @@ class JobDisplay extends RessourceDisplay {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onPATCH(patch) {
|
onPATCH(patch) {
|
||||||
|
if (!this.isInitialized) {return;}
|
||||||
|
|
||||||
let filteredPatch;
|
let filteredPatch;
|
||||||
let operation;
|
let operation;
|
||||||
let re;
|
let re;
|
||||||
|
|
||||||
re = new RegExp(`^/users/${this.userId}/jobs/${this.jobId}`);
|
re = new RegExp(`^/users/${this.userId}/jobs/${this.jobId}`);
|
||||||
filteredPatch = patch.filter(operation => re.test(operation.path));
|
filteredPatch = patch.filter(operation => re.test(operation.path));
|
||||||
|
|
||||||
for (operation of filteredPatch) {
|
for (operation of filteredPatch) {
|
||||||
switch(operation.op) {
|
switch(operation.op) {
|
||||||
case 'replace':
|
case 'replace':
|
||||||
|
@ -2,8 +2,16 @@ class RessourceDisplay {
|
|||||||
constructor(displayElement) {
|
constructor(displayElement) {
|
||||||
this.displayElement = displayElement;
|
this.displayElement = displayElement;
|
||||||
this.userId = this.displayElement.dataset.userId;
|
this.userId = this.displayElement.dataset.userId;
|
||||||
|
this.isInitialized = false;
|
||||||
|
if (this.userId) {
|
||||||
|
app.subscribeUser(this.userId).then((response) => {
|
||||||
app.socket.on('PATCH', (patch) => {this.onPATCH(patch);});
|
app.socket.on('PATCH', (patch) => {this.onPATCH(patch);});
|
||||||
app.subscribeUser(this.userId).then((user) => {this.init(user);});
|
});
|
||||||
|
app.getUser(this.userId).then((user) => {
|
||||||
|
this.init(user);
|
||||||
|
this.isInitialized = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init(user) {throw 'Not implemented';}
|
init(user) {throw 'Not implemented';}
|
||||||
|
@ -65,7 +65,7 @@ class CorpusFileList extends RessourceList {
|
|||||||
<div class="modal">
|
<div 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 file <b>${app.users[this.userId].corpora[this.corpusId].files[corpusFileId].filename}</b>? It will be permanently deleted!</p>
|
<p>Do you really want to delete the corpus file <b>${app.data.users[this.userId].corpora[this.corpusId].files[corpusFileId].filename}</b>? It will be permanently deleted!</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<a href="#!" class="btn modal-close waves-effect waves-light">Cancel</a>
|
<a href="#!" class="btn modal-close waves-effect waves-light">Cancel</a>
|
||||||
@ -97,6 +97,8 @@ class CorpusFileList extends RessourceList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onPATCH(patch) {
|
onPATCH(patch) {
|
||||||
|
if (!this.isInitialized) {return;}
|
||||||
|
|
||||||
let corpusFileId;
|
let corpusFileId;
|
||||||
let filteredPatch;
|
let filteredPatch;
|
||||||
let match;
|
let match;
|
||||||
|
@ -60,7 +60,7 @@ class CorpusList extends RessourceList {
|
|||||||
<div class="modal">
|
<div 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 <b>${app.users[this.userId].corpora[corpusId].title}</b>? All files will be permanently deleted!</p>
|
<p>Do you really want to delete the corpus <b>${app.data.users[this.userId].corpora[corpusId].title}</b>? All files will be permanently deleted!</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<a href="#!" class="btn modal-close waves-effect waves-light">Cancel</a>
|
<a href="#!" class="btn modal-close waves-effect waves-light">Cancel</a>
|
||||||
@ -89,6 +89,8 @@ class CorpusList extends RessourceList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onPATCH(patch) {
|
onPATCH(patch) {
|
||||||
|
if (!this.isInitialized) {return;}
|
||||||
|
|
||||||
let corpusId;
|
let corpusId;
|
||||||
let filteredPatch;
|
let filteredPatch;
|
||||||
let match;
|
let match;
|
||||||
|
@ -36,7 +36,6 @@ class JobList extends RessourceList {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
constructor(listElement, options = {}) {
|
constructor(listElement, options = {}) {
|
||||||
super(listElement, {...JobList.options, ...options});
|
super(listElement, {...JobList.options, ...options});
|
||||||
}
|
}
|
||||||
@ -66,7 +65,7 @@ class JobList extends RessourceList {
|
|||||||
<div class="modal">
|
<div class="modal">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<h4>Confirm job deletion</h4>
|
<h4>Confirm job deletion</h4>
|
||||||
<p>Do you really want to delete the job <b>${app.users[this.userId].jobs[jobId].title}</b>? All files will be permanently deleted!</p>
|
<p>Do you really want to delete the job <b>${app.data.users[this.userId].jobs[jobId].title}</b>? All files will be permanently deleted!</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<a href="#!" class="btn modal-close waves-effect waves-light">Cancel</a>
|
<a href="#!" class="btn modal-close waves-effect waves-light">Cancel</a>
|
||||||
@ -95,6 +94,8 @@ class JobList extends RessourceList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onPATCH(patch) {
|
onPATCH(patch) {
|
||||||
|
if (!this.isInitialized) {return;}
|
||||||
|
|
||||||
let filteredPatch;
|
let filteredPatch;
|
||||||
let jobId;
|
let jobId;
|
||||||
let match;
|
let match;
|
||||||
|
@ -58,6 +58,8 @@ class JobResultList extends RessourceList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onPATCH(patch) {
|
onPATCH(patch) {
|
||||||
|
if (!this.isInitialized) {return;}
|
||||||
|
|
||||||
let filteredPatch;
|
let filteredPatch;
|
||||||
let operation;
|
let operation;
|
||||||
let re;
|
let re;
|
||||||
|
@ -90,12 +90,15 @@ class RessourceList {
|
|||||||
this.listjs.list.style.cursor = 'pointer';
|
this.listjs.list.style.cursor = 'pointer';
|
||||||
this.userId = this.listjs.listContainer.dataset.userId;
|
this.userId = this.listjs.listContainer.dataset.userId;
|
||||||
this.listjs.list.addEventListener('click', event => this.onclick(event));
|
this.listjs.list.addEventListener('click', event => this.onclick(event));
|
||||||
|
this.isInitialized = false;
|
||||||
if (this.userId) {
|
if (this.userId) {
|
||||||
|
app.subscribeUser(this.userId).then((response) => {
|
||||||
app.socket.on('PATCH', (patch) => {this.onPATCH(patch);});
|
app.socket.on('PATCH', (patch) => {this.onPATCH(patch);});
|
||||||
app.subscribeUser(this.userId).then(
|
});
|
||||||
(user) => {this.init(user);},
|
app.getUser(this.userId).then((user) => {
|
||||||
(error) => {throw JSON.stringify(error);}
|
this.init(user);
|
||||||
);
|
this.isInitialized = true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ class UserList extends RessourceList {
|
|||||||
'id-1': user.id,
|
'id-1': user.id,
|
||||||
'username': user.username,
|
'username': user.username,
|
||||||
'email': user.email,
|
'email': user.email,
|
||||||
'last-seen': new Date(user.last_seen).toLocaleString("en-US"),
|
'last-seen': new Date(user.last_seen).toLocaleString('en-US'),
|
||||||
'member-since': user.member_since,
|
'member-since': user.member_since,
|
||||||
'role': user.role.name
|
'role': user.role.name
|
||||||
};
|
};
|
||||||
|
@ -32,11 +32,7 @@
|
|||||||
const jobStatusNotifier = new JobStatusNotifier(currentUserId);
|
const jobStatusNotifier = new JobStatusNotifier(currentUserId);
|
||||||
|
|
||||||
// Initialize components for current user
|
// Initialize components for current user
|
||||||
app.subscribeUser(currentUserId)
|
app.subscribeUser(currentUserId).catch((error) => {throw JSON.stringify(error);});
|
||||||
.then(
|
|
||||||
(user) => {return;},
|
|
||||||
(error) => {throw JSON.stringify(error);}
|
|
||||||
);
|
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
// Disable all option elements with no value
|
// Disable all option elements with no value
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
{% set breadcrumbs %}
|
{% set breadcrumbs %}
|
||||||
<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
|
<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
|
||||||
<li class="tab"><a href="{{ url_for('main.dashboard', _anchor='jobs') }}" target="_self">My jobs</a></li>
|
<li class="tab"><a href="{{ url_for('main.dashboard', _anchor='jobs') }}" target="_self">My jobs</a></li>
|
||||||
<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
|
|
||||||
{% if request.path == url_for('.job', job_id=job.id) %}
|
{% if request.path == url_for('.job', job_id=job.id) %}
|
||||||
|
<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
|
||||||
<li class="tab"><a class="active" href="{{ url_for('.job', job_id=job.id) }}" target="_self">{{ job.title }}</a></li>
|
<li class="tab"><a class="active" href="{{ url_for('.job', job_id=job.id) }}" target="_self">{{ job.title }}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endset %}
|
{% endset %}
|
||||||
|
@ -7,10 +7,28 @@
|
|||||||
<h1 id="title">{{ title }}</h1>
|
<h1 id="title">{{ title }}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col s12">
|
<div class="col s12 nopaque-ressource-list no-autoinit" data-ressource-type="User" id="users">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<table class="" id="users"></table>
|
<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>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Id</th>
|
||||||
|
<th>Username</th>
|
||||||
|
<th>Email</th>
|
||||||
|
<th>Last seen</th>
|
||||||
|
<th>Role</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="list"></tbody>
|
||||||
|
</table>
|
||||||
|
<ul class="pagination"></ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -18,52 +36,10 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endblock page_content %}
|
{% endblock page_content %}
|
||||||
|
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
<script src="https://unpkg.com/gridjs/dist/gridjs.umd.js"></script>
|
|
||||||
<script>
|
<script>
|
||||||
const updateUrl = (prev, query) => {
|
let userList = new UserList(document.querySelector('#users'));
|
||||||
return prev + (prev.indexOf('?') >= 0 ? '&' : '?') + new URLSearchParams(query).toString();
|
userList.init({{ dict_users|tojson }});
|
||||||
};
|
|
||||||
|
|
||||||
new gridjs.Grid({
|
|
||||||
columns: [
|
|
||||||
{ id: 'username', name: 'Username' },
|
|
||||||
{ id: 'email', name: 'Email' },
|
|
||||||
],
|
|
||||||
server: {
|
|
||||||
url: '/users/api_users',
|
|
||||||
then: results => results.data,
|
|
||||||
total: results => results.total,
|
|
||||||
},
|
|
||||||
search: {
|
|
||||||
enabled: true,
|
|
||||||
server: {
|
|
||||||
url: (prev, search) => {
|
|
||||||
return updateUrl(prev, {search});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
sort: {
|
|
||||||
enabled: true,
|
|
||||||
multiColumn: true,
|
|
||||||
server: {
|
|
||||||
url: (prev, columns) => {
|
|
||||||
const columnIds = ['username', 'email'];
|
|
||||||
const sort = columns.map(col => (col.direction === 1 ? '+' : '-') + columnIds[col.index]);
|
|
||||||
return updateUrl(prev, {sort});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
pagination: {
|
|
||||||
enabled: true,
|
|
||||||
server: {
|
|
||||||
url: (prev, page, limit) => {
|
|
||||||
return updateUrl(prev, {offset: page * limit, limit: limit});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}).render(document.getElementById('users'));
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock scripts %}
|
{% endblock scripts %}
|
||||||
|
@ -5,6 +5,19 @@ from flask_login import current_user
|
|||||||
from flask_socketio import join_room, leave_room
|
from flask_socketio import join_room, leave_room
|
||||||
|
|
||||||
|
|
||||||
|
@socketio.on('GET /users/<user_id>')
|
||||||
|
@socketio_login_required
|
||||||
|
def subscribe_user(user_hashid):
|
||||||
|
user_id = hashids.decode(user_hashid)
|
||||||
|
user = User.query.get(user_id)
|
||||||
|
if user is None:
|
||||||
|
return {'code': 404, 'msg': 'Not found'}
|
||||||
|
if not (user == current_user or current_user.is_administrator):
|
||||||
|
return {'code': 403, 'msg': 'Forbidden'}
|
||||||
|
dict_user = user.to_dict(backrefs=True, relationships=True)
|
||||||
|
return {'code': 200, 'msg': 'OK', 'payload': dict_user}
|
||||||
|
|
||||||
|
|
||||||
@socketio.on('SUBSCRIBE /users/<user_id>')
|
@socketio.on('SUBSCRIBE /users/<user_id>')
|
||||||
@socketio_login_required
|
@socketio_login_required
|
||||||
def subscribe_user(user_hashid):
|
def subscribe_user(user_hashid):
|
||||||
@ -14,9 +27,10 @@ def subscribe_user(user_hashid):
|
|||||||
return {'code': 404, 'msg': 'Not found'}
|
return {'code': 404, 'msg': 'Not found'}
|
||||||
if not (user == current_user or current_user.is_administrator):
|
if not (user == current_user or current_user.is_administrator):
|
||||||
return {'code': 403, 'msg': 'Forbidden'}
|
return {'code': 403, 'msg': 'Forbidden'}
|
||||||
dict_user = user.to_dict(backrefs=True, relationships=True)
|
# dict_user = user.to_dict(backrefs=True, relationships=True)
|
||||||
join_room(f'/users/{user.hashid}')
|
join_room(f'/users/{user.hashid}')
|
||||||
return {'code': 200, 'msg': 'OK', 'payload': dict_user}
|
# return {'code': 200, 'msg': 'OK', 'payload': dict_user}
|
||||||
|
return {'code': 200, 'msg': 'OK'}
|
||||||
|
|
||||||
|
|
||||||
@socketio.on('UNSUBSCRIBE /users/<user_id>')
|
@socketio.on('UNSUBSCRIBE /users/<user_id>')
|
||||||
|
@ -1,50 +1,17 @@
|
|||||||
|
from app.decorators import admin_required
|
||||||
from app.models import User
|
from app.models import User
|
||||||
from flask import render_template, request, url_for
|
from flask import render_template, request
|
||||||
|
from flask_login import login_required
|
||||||
from . import bp
|
from . import bp
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/')
|
@bp.route('/')
|
||||||
|
@login_required
|
||||||
|
@admin_required
|
||||||
def users():
|
def users():
|
||||||
|
dict_users = [u.to_dict(backrefs=True, relationships=False) for u in User.query.all()]
|
||||||
return render_template(
|
return render_template(
|
||||||
'users/users.html.j2',
|
'users/users.html.j2',
|
||||||
title='Users'
|
title='Users',
|
||||||
|
dict_users=dict_users
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/api_users')
|
|
||||||
def api_users():
|
|
||||||
query = User.query
|
|
||||||
|
|
||||||
# search filter
|
|
||||||
search = request.args.get('search')
|
|
||||||
if search:
|
|
||||||
query = query.filter(User.username.like(f'%{search}%') | User.email.like(f'%{search}%'))
|
|
||||||
total = query.count()
|
|
||||||
|
|
||||||
# sorting
|
|
||||||
sort = request.args.get('sort')
|
|
||||||
if sort:
|
|
||||||
order = []
|
|
||||||
for s in sort.split(','):
|
|
||||||
direction = s[0]
|
|
||||||
name = s[1:]
|
|
||||||
if name not in ['username', 'email']:
|
|
||||||
name = 'username'
|
|
||||||
col = getattr(User, name)
|
|
||||||
if direction == '-':
|
|
||||||
col = col.desc()
|
|
||||||
order.append(col)
|
|
||||||
if order:
|
|
||||||
query = query.order_by(*order)
|
|
||||||
|
|
||||||
# pagination
|
|
||||||
offset = request.args.get('offset', type=int, default=-1)
|
|
||||||
limit = request.args.get('limit', type=int, default=-1)
|
|
||||||
if offset != -1 and limit != -1:
|
|
||||||
query = query.offset(offset).limit(limit)
|
|
||||||
|
|
||||||
# response
|
|
||||||
return {
|
|
||||||
'data': [user.to_dict() for user in query],
|
|
||||||
'total': total
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user