This is an automated email from the ASF dual-hosted git repository.
tn pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-atr-experiments.git
The following commit(s) were added to refs/heads/main by this push:
new 81ee8dd add page to display executed background tasks to the admin
area
81ee8dd is described below
commit 81ee8dd140bd6a63d96fb17d9b5e756c6e8f203e
Author: Thomas Neidhart <[email protected]>
AuthorDate: Wed Mar 5 10:37:46 2025 +0100
add page to display executed background tasks to the admin area
---
.pre-commit-config.yaml | 2 +-
atr/blueprints/admin/admin.py | 5 +++
atr/blueprints/admin/templates/tasks.html | 70 +++++++++++++++++++++++++++++++
atr/blueprints/api/api.py | 20 ++++++++-
atr/db/service.py | 13 +++++-
atr/static/README.txt | 1 +
atr/static/css/mermaid.min.css | 5 +++
atr/static/js/gridjs.production.min.js | 2 +
atr/templates/includes/sidebar.html | 5 +++
9 files changed, 120 insertions(+), 3 deletions(-)
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 32974f2..0c5bbb3 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -43,7 +43,7 @@ repos:
- id: stylelint
additional_dependencies: ['[email protected]',
'[email protected]']
files: "atr/static/css/.*\\.css$"
- exclude:
"atr/static/css/bootstrap\\.min\\.css$|atr/static/css/fontawesome\\.all\\.min\\.css$|atr/static/css/normalize\\.css$"
+ exclude:
"atr/static/css/bootstrap\\.min\\.css$|atr/static/css/fontawesome\\.all\\.min\\.css$|atr/static/css/normalize\\.css$|atr/static/css/mermaid\\.min\\.css$"
types_or: ['css']
args: ['--fix']
- repo: local
diff --git a/atr/blueprints/admin/admin.py b/atr/blueprints/admin/admin.py
index ca603e2..87709af 100644
--- a/atr/blueprints/admin/admin.py
+++ b/atr/blueprints/admin/admin.py
@@ -278,6 +278,11 @@ async def _update_pmcs() -> int:
return updated_count
[email protected]("/tasks")
+async def admin_tasks() -> str:
+ return await render_template("tasks.html")
+
+
@blueprint.route("/debug/database")
async def admin_debug_database() -> str:
"""Debug information about the database."""
diff --git a/atr/blueprints/admin/templates/tasks.html
b/atr/blueprints/admin/templates/tasks.html
new file mode 100644
index 0000000..8563686
--- /dev/null
+++ b/atr/blueprints/admin/templates/tasks.html
@@ -0,0 +1,70 @@
+{% extends "layouts/base-admin.html" %}
+
+{% block title %}
+ Executed Tasks ~ ATR
+{% endblock %}
+
+{% block stylesheets %}
+ {{ super() }}
+ <link rel="stylesheet"
+ href="{{ url_for('static', filename='css/mermaid.min.css') }}" />
+
+ <style>
+ .gridjs-pages button {
+ color: unset;
+ }
+ </style>
+{% endblock stylesheets %}
+
+{% block content %}
+ <h1>Executed Background Tasks</h1>
+
+ <div id="task-table"></div>
+{% endblock content %}
+
+{% block javascripts %}
+ {{ super() }}
+ <script src="{{ url_for('static', filename='js/gridjs.production.min.js')
}}"></script>
+
+ <script>
+ new gridjs.Grid({
+ columns: [
+ 'ID',
+ 'Task Type',
+ 'Task Status',
+ {
+ name: 'Added',
+ formatter: (cell) => `${new Date(cell).toISOString()}`
+ },
+ {
+ name: 'Started',
+ formatter: (cell) => `${new Date(cell).toISOString()}`
+ },
+ {
+ name: 'Completed',
+ formatter: (cell) => `${new Date(cell).toISOString()}`
+ }
+ ],
+ style: {
+ table: {
+ 'white-space': 'nowrap'
+ }
+ },
+ search: true,
+ pagination: {
+ limit: 15,
+ server: {
+ url: (prev, page, limit) => `${prev}?limit=${limit}&offset=${page *
limit}`
+ }
+ },
+ // sort: true,
+ server: {
+ url: '/api/tasks',
+ then: data => data.data.map(task => [
+ task.id, task.task_type, task.status, task.added, task.started,
task.completed
+ ]),
+ total: data => data.count
+ }
+ }).render(document.getElementById("task-table"));
+ </script>
+{% endblock javascripts %}
diff --git a/atr/blueprints/api/api.py b/atr/blueprints/api/api.py
index 82d9d3d..79fd3ec 100644
--- a/atr/blueprints/api/api.py
+++ b/atr/blueprints/api/api.py
@@ -16,9 +16,13 @@
# under the License.
from collections.abc import Mapping
+from dataclasses import dataclass
from typing import Any
-from atr.db.service import get_pmc_by_name
+from quart import Response, jsonify
+from quart_schema import validate_querystring
+
+from atr.db.service import get_pmc_by_name, get_tasks_paged
from . import blueprint
@@ -30,3 +34,17 @@ async def api_pmc(project_name: str) -> tuple[Mapping[str,
Any], int]:
return pmc.model_dump(), 200
else:
return {}, 404
+
+
+@dataclass
+class Pagination:
+ offset: int = 0
+ limit: int = 20
+
+
[email protected]("/tasks")
+@validate_querystring(Pagination)
+async def api_tasks(query_args: Pagination) -> Response:
+ paged_tasks, count = await get_tasks_paged(limit=query_args.limit,
offset=query_args.offset)
+ result = {"data": [x.model_dump(exclude={"result"}) for x in paged_tasks],
"count": count}
+ return jsonify(result)
diff --git a/atr/db/service.py b/atr/db/service.py
index 5a1b06a..6c8c2a1 100644
--- a/atr/db/service.py
+++ b/atr/db/service.py
@@ -17,9 +17,10 @@
from collections.abc import Sequence
+from sqlalchemy import func
from sqlmodel import select
-from atr.db.models import PMC
+from atr.db.models import PMC, Task
from . import get_session
@@ -37,3 +38,13 @@ async def get_pmcs() -> Sequence[PMC]:
statement = select(PMC)
pmcs = (await db_session.execute(statement)).scalars().all()
return pmcs
+
+
+async def get_tasks_paged(limit: int, offset: int) -> tuple[Sequence[Task],
int]:
+ """Returns a list of Tasks based on limit and offset values together with
the total count."""
+
+ async with get_session() as db_session:
+ statement =
select(Task).limit(limit).offset(offset).order_by(Task.id.desc()) # type:
ignore
+ tasks = (await db_session.execute(statement)).scalars().all()
+ count = (await
db_session.execute(select(func.count(Task.id)))).scalar_one() # type: ignore
+ return tasks, count
diff --git a/atr/static/README.txt b/atr/static/README.txt
index 24e9335..f6d7751 100644
--- a/atr/static/README.txt
+++ b/atr/static/README.txt
@@ -3,3 +3,4 @@ The following static resources are copied:
bootstrap 5.3.3
fontawesome-free 6.7.2
normalize.css 8.0.1
+gridjs 6.2.0
diff --git a/atr/static/css/mermaid.min.css b/atr/static/css/mermaid.min.css
new file mode 100644
index 0000000..718601d
--- /dev/null
+++ b/atr/static/css/mermaid.min.css
@@ -0,0 +1,5 @@
+.gridjs-footer button,.gridjs-head
button{background-color:transparent;background-image:none;border:none;cursor:pointer;margin:0;outline:none;padding:0}.gridjs-temp{position:relative}.gridjs-head{margin-bottom:5px;padding:5px
1px;width:100%}.gridjs-head::after{clear:both;content:"";display:block}.gridjs-head:empty{border:none;padding:0}.gridjs-container{color:#000000;display:inline-block;overflow:hidden;padding:2px;position:relative;z-index:0}.gridjs-footer{background-color:#ffffff;borde
[...]
+
+@supports (-moz-appearance:none){th.gridjs-th-fixed{box-shadow:0 0 0 1px
#e5e7eb}}th.gridjs-th:first-child{border-left:none}th.gridjs-th:last-child{border-right:none}.gridjs-tr{border:none}.gridjs-tr-selected
td{background-color:#ebf5ff}.gridjs-tr:last-child td{border-bottom:0}.gridjs
*,.gridjs ::after,.gridjs
::before{box-sizing:border-box}.gridjs-wrapper{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;border-color:#e5e7eb;border-radius:8px
8px 0 0;border-top-width: [...]
+
+@keyframes shimmer{100%{transform:translateX(100%)}}.gridjs-td
.gridjs-checkbox{cursor:pointer;display:block;margin:auto}.gridjs-resizable{bottom:0;position:absolute;right:0;top:0;width:5px}.gridjs-resizable:hover{background-color:#9bc2f7;cursor:ew-resize}
diff --git a/atr/static/js/gridjs.production.min.js
b/atr/static/js/gridjs.production.min.js
new file mode 100644
index 0000000..31761d7
--- /dev/null
+++ b/atr/static/js/gridjs.production.min.js
@@ -0,0 +1,2 @@
+!function(t,n){"object"==typeof exports&&"undefined"!=typeof
module?n(exports):"function"==typeof
define&&define.amd?define(["exports"],n):n((t||self).gridjs={})}(this,function(t){function
n(t,n){for(var e=0;e<n.length;e++){var
r=n[e];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in
r&&(r.writable=!0),Object.defineProperty(t,"symbol"==typeof(o=function(t,n){if("object"!=typeof
t||null===t)return t;var e=t[Symbol.toPrimitive];if(void 0!==e){var
r=e.call(t,"string");if("object"!=t [...]
+//# sourceMappingURL=gridjs.umd.js.map
diff --git a/atr/templates/includes/sidebar.html
b/atr/templates/includes/sidebar.html
index bcf9bdf..c29b70c 100644
--- a/atr/templates/includes/sidebar.html
+++ b/atr/templates/includes/sidebar.html
@@ -84,6 +84,11 @@
<a href="{{ url_for('admin.admin_projects_update') }}"
{% if request.endpoint == 'admin.admin_projects_update'
%}class="active"{% endif %}>Update projects</a>
</li>
+ <li>
+ <i class="fa-solid fa-list-check"></i>
+ <a href="{{ url_for('admin.admin_tasks') }}"
+ {% if request.endpoint == 'admin.admin_tasks'
%}class="active"{% endif %}>Background Tasks</a>
+ </li>
<li>
<i class="fa-solid fa-weight-scale"></i>
<a href="{{ url_for('admin.admin_performance') }}"
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]