Table works

This commit is contained in:
Magnus Bender 2024-10-09 15:13:40 +02:00
parent a4d0803d20
commit 4199b1c347
Signed by: bender
GPG Key ID: 5149A211831F2BD7
7 changed files with 183 additions and 47 deletions

View File

@ -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'],

View File

@ -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(

View File

@ -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)

View File

@ -0,0 +1,4 @@
.value_filter {
width: 150px;
}

View File

@ -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()
}

View File

@ -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}} &ndash; MGMT</title> <title>{{title}} &ndash; MGMT</title>
@ -44,5 +43,6 @@
{% block maincontent %} {% block maincontent %}
{% endblock %} {% endblock %}
</div> </div>
<script src="/static/main.js"></script>
</body> </body>
</html> </html>

View File

@ -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">&rarr; Add a Riddle</a> <a href="/app/new" class="btn btn-secondary">&rarr; 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 %}