Table works
This commit is contained in:
parent
a4d0803d20
commit
4199b1c347
@ -125,8 +125,8 @@ class DB():
|
|||||||
id:str|None=None, sender:str|None=None, recipient:str|None=None,
|
id:str|None=None, sender:str|None=None, recipient:str|None=None,
|
||||||
processed:bool|None=None,
|
processed:bool|None=None,
|
||||||
time_after:int|None=None, time_before:int|None=None,
|
time_after:int|None=None, time_before:int|None=None,
|
||||||
limit:int=20, offset:int=0
|
limit:int=20, offset:int=0, _count_only:bool=False
|
||||||
) -> Generator[RowObject, None, None]:
|
) -> Generator[RowObject|int, None, None]:
|
||||||
|
|
||||||
where = []
|
where = []
|
||||||
params = {
|
params = {
|
||||||
@ -153,12 +153,30 @@ class DB():
|
|||||||
where_clause = ""
|
where_clause = ""
|
||||||
|
|
||||||
with self.db:
|
with self.db:
|
||||||
|
if _count_only:
|
||||||
|
count = self.db.execute(
|
||||||
|
"SELECT COUNT(*) as count FROM Messages {}".format(where_clause),
|
||||||
|
params
|
||||||
|
).fetchone()
|
||||||
|
|
||||||
|
yield count['count']
|
||||||
|
else:
|
||||||
for row in self.db.execute(
|
for row in self.db.execute(
|
||||||
"SELECT * FROM Messages {} ORDER BY time DESC LIMIT :lim OFFSET :off".format(where_clause),
|
"SELECT * FROM Messages {} ORDER BY time DESC LIMIT :lim OFFSET :off".format(where_clause),
|
||||||
params
|
params
|
||||||
):
|
):
|
||||||
yield self._create_row_object(row)
|
yield self._create_row_object(row)
|
||||||
|
|
||||||
|
def __len__(self) -> int:
|
||||||
|
return self.len()
|
||||||
|
|
||||||
|
def len(self, **kwargs) -> int:
|
||||||
|
"""
|
||||||
|
See `DB.iterate` for possible values of `kwargs`.
|
||||||
|
"""
|
||||||
|
kwargs['_count_only'] = True
|
||||||
|
return next(self.iterate(**kwargs))
|
||||||
|
|
||||||
def _create_row_object(self, row:sqlite3.Row) -> RowObject:
|
def _create_row_object(self, row:sqlite3.Row) -> RowObject:
|
||||||
return RowObject(
|
return RowObject(
|
||||||
count=row['count'],
|
count=row['count'],
|
||||||
|
@ -8,12 +8,15 @@
|
|||||||
# source code released under the terms of GNU Public License Version 3
|
# source code released under the terms of GNU Public License Version 3
|
||||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
from fastapi import APIRouter, Request
|
from fastapi import APIRouter, Request
|
||||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
|
|
||||||
|
|
||||||
from ums.management.db import DB
|
from ums.management.db import DB
|
||||||
|
|
||||||
class Interface():
|
class Interface():
|
||||||
@ -37,12 +40,31 @@ class Interface():
|
|||||||
return RedirectResponse(self._PREFIX + "/table")
|
return RedirectResponse(self._PREFIX + "/table")
|
||||||
|
|
||||||
@self.router.get("/table", response_class=HTMLResponse, summary="Table of messages")
|
@self.router.get("/table", response_class=HTMLResponse, summary="Table of messages")
|
||||||
def table(request: Request, limit:int=10, offset:int=0):
|
def table(request: Request,
|
||||||
|
id:str|None=None, sender:str|None=None, recipient:str|None=None,
|
||||||
|
processed:bool|None=None,
|
||||||
|
time_after:int|str|None=None, time_before:int|str|None=None,
|
||||||
|
limit:int=10, offset:int=0, _count_only:bool=False
|
||||||
|
):
|
||||||
|
|
||||||
db_args = {
|
db_args = {
|
||||||
"limit" : limit,
|
"limit" : limit,
|
||||||
"offset" : offset
|
"offset" : offset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
convert_time = lambda t: self.template.env.globals["date2timestamp"](t) \
|
||||||
|
if not re.match(r'^\d+$', t) else int(t)
|
||||||
|
for v,n,f in (
|
||||||
|
(id,'id',str), (sender,'sender',str), (recipient,'recipient',str),
|
||||||
|
(processed,'processed', bool),
|
||||||
|
(time_after, 'time_after', convert_time), (time_before, 'time_before', convert_time)
|
||||||
|
):
|
||||||
|
if not v is None:
|
||||||
|
db_args[n] = f(v)
|
||||||
|
|
||||||
|
if _count_only:
|
||||||
|
return self.db.len(**db_args)
|
||||||
|
else:
|
||||||
def pagination_link(**kwargs):
|
def pagination_link(**kwargs):
|
||||||
link_args = db_args.copy()
|
link_args = db_args.copy()
|
||||||
link_args.update(kwargs)
|
link_args.update(kwargs)
|
||||||
@ -56,6 +78,20 @@ class Interface():
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@self.router.get("/table/total", summary="Total number of messages in table")
|
||||||
|
def table_total(request: Request,
|
||||||
|
id:str|None=None, sender:str|None=None, recipient:str|None=None,
|
||||||
|
processed:bool|None=None,
|
||||||
|
time_after:int|str|None=None, time_before:int|str|None=None,
|
||||||
|
limit:int=10, offset:int=0
|
||||||
|
) -> int:
|
||||||
|
|
||||||
|
kwargs = locals().copy()
|
||||||
|
del kwargs['table']
|
||||||
|
kwargs['_count_only'] = True
|
||||||
|
|
||||||
|
return table(**kwargs)
|
||||||
|
|
||||||
@self.router.get("/new", response_class=HTMLResponse, summary="Add new riddle")
|
@self.router.get("/new", response_class=HTMLResponse, summary="Add new riddle")
|
||||||
def new(request: Request):
|
def new(request: Request):
|
||||||
return self.template.TemplateResponse(
|
return self.template.TemplateResponse(
|
||||||
|
@ -16,6 +16,8 @@ from fastapi import FastAPI, Request
|
|||||||
from fastapi.responses import HTMLResponse
|
from fastapi.responses import HTMLResponse
|
||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
|
|
||||||
|
from jinja2.runtime import Undefined as JinjaUndefined
|
||||||
|
|
||||||
from ums.management.interface import Interface
|
from ums.management.interface import Interface
|
||||||
from ums.management.db import DB
|
from ums.management.db import DB
|
||||||
|
|
||||||
@ -23,6 +25,8 @@ from ums.utils import AgentMessage, RiddleData, RiddleDataType, RiddleSolution,
|
|||||||
|
|
||||||
class WebMain():
|
class WebMain():
|
||||||
|
|
||||||
|
_TIME_FORMAT = "%H:%M:%S %d.%m.%Y"
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._init_app()
|
self._init_app()
|
||||||
self._init_templates()
|
self._init_templates()
|
||||||
@ -47,8 +51,17 @@ class WebMain():
|
|||||||
directory=TEMPLATE_PATH,
|
directory=TEMPLATE_PATH,
|
||||||
auto_reload=True
|
auto_reload=True
|
||||||
)
|
)
|
||||||
self.template.env.globals["timestamp2date"] = lambda t: \
|
|
||||||
datetime.fromtimestamp(t).strftime("%H:%M:%S %d.%m.%Y")
|
def timestamp2date(t:int|JinjaUndefined) -> str:
|
||||||
|
return "" if isinstance(t, JinjaUndefined) \
|
||||||
|
else datetime.fromtimestamp(t).strftime(self._TIME_FORMAT)
|
||||||
|
self.template.env.globals["timestamp2date"] = timestamp2date
|
||||||
|
|
||||||
|
def date2timestamp(d:str|JinjaUndefined) -> int|str:
|
||||||
|
return "" if isinstance(d, JinjaUndefined) \
|
||||||
|
else int(datetime.strptime(d, self._TIME_FORMAT).timestamp())
|
||||||
|
self.template.env.globals["date2timestamp"] = date2timestamp
|
||||||
|
|
||||||
|
|
||||||
def _add_routers(self):
|
def _add_routers(self):
|
||||||
interface_router = Interface(self.template, self.db)
|
interface_router = Interface(self.template, self.db)
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
.value_filter {
|
||||||
|
width: 150px;
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
|
||||||
|
|
||||||
|
function pagination_link(name, value){
|
||||||
|
let link_args = db_args;
|
||||||
|
if (name && value){
|
||||||
|
link_args[name] = value;
|
||||||
|
}
|
||||||
|
else if(name){
|
||||||
|
delete link_args[name];
|
||||||
|
}
|
||||||
|
return '?' + $.param( link_args );
|
||||||
|
}
|
||||||
|
|
||||||
|
$(".value_filter").change((e)=>{
|
||||||
|
window.location = pagination_link($(e.target).attr('name').substr('filter_'.length), $(e.target).val())
|
||||||
|
});
|
||||||
|
|
||||||
|
function enable_auto_refresh(){
|
||||||
|
setInterval( () => {
|
||||||
|
if($('#autoRefresh').prop('checked')){
|
||||||
|
$.get(
|
||||||
|
'/app/table/total' + pagination_link(),
|
||||||
|
(v) => {
|
||||||
|
if( v != db_total ){
|
||||||
|
window.location = pagination_link()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
sessionStorage.setItem('auto_refresh', $('#autoRefresh').prop('checked'));
|
||||||
|
}
|
||||||
|
$("#autoRefresh").click(enable_auto_refresh);
|
||||||
|
if(sessionStorage.hasOwnProperty('auto_refresh') && sessionStorage.getItem('auto_refresh') === 'true'){
|
||||||
|
$("#autoRefresh").prop('checked', true);
|
||||||
|
enable_auto_refresh()
|
||||||
|
}
|
@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
<script src="/static/jquery.min.js"></script>
|
<script src="/static/jquery.min.js"></script>
|
||||||
<script src="/static/bootstrap.bundle.min.js"></script>
|
<script src="/static/bootstrap.bundle.min.js"></script>
|
||||||
<script src="/static/main.js"></script>
|
|
||||||
|
|
||||||
<title>{{title}} – MGMT</title>
|
<title>{{title}} – MGMT</title>
|
||||||
|
|
||||||
@ -44,5 +43,6 @@
|
|||||||
{% block maincontent %}
|
{% block maincontent %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
|
<script src="/static/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
@ -26,25 +26,35 @@
|
|||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
{% block maincontent %}
|
{% block maincontent %}
|
||||||
|
|
||||||
<div class="float-end">
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" role="switch" id="autoRefresh">
|
||||||
|
<label class="form-check-label" for="autoRefresh">Refresh for new messages</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
{{pagination()}}
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
<a href="/app/new" class="btn btn-secondary">→ Add a Riddle</a>
|
<a href="/app/new" class="btn btn-secondary">→ Add a Riddle</a>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{{pagination()}}
|
|
||||||
|
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
{% for item in db.iterate(**db_args) %}
|
{% for item in db.iterate(**db_args) %}
|
||||||
|
{% set field_names = ['id'] + item.__fields__.keys()|list %}
|
||||||
|
|
||||||
{% if loop.index == 1 %}
|
{% if loop.index == 1 %}
|
||||||
<tr id="row_0">
|
<tr id="row_0">
|
||||||
{% for field in item.__fields__.keys() %}
|
{% for field in field_names %}
|
||||||
<th>
|
<th>
|
||||||
{% if field == 'time' %}
|
{% if field == 'time' %}
|
||||||
<input type="text" class="value_filter" name="filter_time_before" value="{{db_args.time_before}}" class="form-control" placeholder="Before">
|
<input type="text" class="value_filter" name="filter_time_before" value="{{timestamp2date(db_args.time_before)}}" class="form-control" placeholder="Before"><br />
|
||||||
<input type="text" class="value_filter" name="filter_time_after" value="{{db_args.time_after}}" class="form-control" placeholder="After">
|
<input type="text" class="value_filter" name="filter_time_after" value="{{timestamp2date(db_args.time_after)}}" class="form-control" placeholder="After"><br />
|
||||||
{% elif field not in ('message', 'count') %}
|
{% elif field not in ('message', 'count') %}
|
||||||
<input type="text" class="value_filter" name="filter_{{field}}" value="{{db_args[field]}}" class="form-control" placeholder="Filter">
|
<input type="text" class="value_filter" name="filter_{{field}}" value="{{db_args[field]}}" class="form-control" placeholder="Filter"><br />
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ field.title() }}
|
{{ field.title() }}
|
||||||
</th>
|
</th>
|
||||||
@ -54,7 +64,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<tr id="row_{{loop.index}}">
|
<tr id="row_{{loop.index}}">
|
||||||
{% for field in item.__fields__.keys() %}
|
{% for field in field_names %}
|
||||||
{% if field == "message" %}
|
{% if field == "message" %}
|
||||||
<td>
|
<td>
|
||||||
<button type="button" class="btn btn-outline-secondary btn-outline" data-bs-toggle="modal" data-bs-target="#row_message_{{loop.index}}">
|
<button type="button" class="btn btn-outline-secondary btn-outline" data-bs-toggle="modal" data-bs-target="#row_message_{{loop.index}}">
|
||||||
@ -74,6 +84,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
{% elif field == "id" %}
|
||||||
|
<td>{{ item.message.id }}</td>
|
||||||
{% elif field == "time" %}
|
{% elif field == "time" %}
|
||||||
<td ts="item[field]">{{ timestamp2date(item[field]) }}</td>
|
<td ts="item[field]">{{ timestamp2date(item[field]) }}</td>
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -83,16 +95,27 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="alert alert-warning" role="alert">
|
<div class="alert alert-warning" role="alert">
|
||||||
No items found, reset offset, limit, filter, ...!
|
No items found, reset offset, limit, filter, ...!<br />
|
||||||
|
<a class="btn btn-warning" href="/app/table">Reset</a>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<div class="float-end">
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
{{pagination()}}
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="input-group">
|
||||||
|
<label class="input-group-text" for="total-items">Total items</label>
|
||||||
|
<input class="form-control" id="total-items" disabled value="{{ db.len(**db_args) }}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<label class="input-group-text" for="items-per-page">Items per page</label>
|
<label class="input-group-text" for="items-per-page">Items per page</label>
|
||||||
<select class="form-select" id="items-per-page" onchange="location = this.value;">
|
<select class="form-select" id="items-per-page" onchange="window.location = this.value;">
|
||||||
<option value="?{{ pagination_link(limit=5) }}" {% if db_args.limit == 5 %}selected{% endif %}>5</option>
|
<option value="?{{ pagination_link(limit=5) }}" {% if db_args.limit == 5 %}selected{% endif %}>5</option>
|
||||||
<option value="?{{ pagination_link(limit=10) }}" {% if db_args.limit == 10 %}selected{% endif %}>10</option>
|
<option value="?{{ pagination_link(limit=10) }}" {% if db_args.limit == 10 %}selected{% endif %}>10</option>
|
||||||
<option value="?{{ pagination_link(limit=25) }}" {% if db_args.limit == 25 %}selected{% endif %}>25</option>
|
<option value="?{{ pagination_link(limit=25) }}" {% if db_args.limit == 25 %}selected{% endif %}>25</option>
|
||||||
@ -103,7 +126,12 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{{pagination()}}
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
{% block morehead %}
|
||||||
|
<script>
|
||||||
|
const db_args = JSON.parse('{{ db_args|tojson }}');
|
||||||
|
const db_total = {{ db.len(**db_args) }};
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
Loading…
x
Reference in New Issue
Block a user