2 Commits
v0.4 ... v0.6

Author SHA1 Message Date
a4d0803d20 Basic Table
All checks were successful
Build and push Docker image at git tag / build (push) Successful in 1m44s
2024-10-08 21:00:14 +02:00
e376956def Fix DB && Logs to Stdout in Docker
All checks were successful
Build and push Docker image at git tag / build (push) Successful in 1m54s
2024-10-08 14:11:57 +02:00
17 changed files with 284 additions and 32 deletions

View File

@ -17,6 +17,14 @@ server {
root /ums-agenten/plattform/web/public/; root /ums-agenten/plattform/web/public/;
index index.html; index index.html;
location = / {
server_name_in_redirect off;
port_in_redirect off;
absolute_redirect off;
return 303 /index;
}
location / { location / {
try_files $uri $uri/ @dynamic; try_files $uri $uri/ @dynamic;
} }

View File

@ -29,6 +29,11 @@ autostart=true
autorestart=true autorestart=true
priority=10 priority=10
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
[program:nginx] [program:nginx]
command=/usr/sbin/nginx -g 'daemon off;' command=/usr/sbin/nginx -g 'daemon off;'
autostart=true autostart=true

View File

@ -62,16 +62,16 @@ class DB():
self.db = sqlite3.connect( self.db = sqlite3.connect(
os.path.join(PERSIST_PATH, 'messages.db'), os.path.join(PERSIST_PATH, 'messages.db'),
check_same_thread=False check_same_thread=False
) )
self.db.row_factory = sqlite3.Row self.db.row_factory = sqlite3.Row
self.dblock = Lock()
atexit.register(lambda db : db.close(), self.db) atexit.register(lambda db : db.close(), self.db)
self.db_lock = Lock()
self._assure_tables() self._assure_tables()
def _assure_tables(self): def _assure_tables(self):
self.dblock.acquire() self.db_lock.acquire()
with self.db: with self.db:
self.db.execute("""CREATE TABLE IF NOT EXISTS Messages ( self.db.execute("""CREATE TABLE IF NOT EXISTS Messages (
count INTEGER PRIMARY KEY AUTOINCREMENT, count INTEGER PRIMARY KEY AUTOINCREMENT,
@ -82,11 +82,11 @@ class DB():
json BLOB, json BLOB,
processed BOOL DEFAULT FALSE processed BOOL DEFAULT FALSE
)""") )""")
self.dblock.release() self.db_lock.release()
@validate_call @validate_call
def add_message(self, sender:str, recipient:str, message:AgentMessage, processed:bool=False) -> int: def add_message(self, sender:str, recipient:str, message:AgentMessage, processed:bool=False) -> int:
self.dblock.acquire() self.db_lock.acquire()
with self.db: with self.db:
self.db.execute( self.db.execute(
"""INSERT INTO Messages ( """INSERT INTO Messages (
@ -101,20 +101,21 @@ class DB():
"processed" : processed "processed" : processed
}) })
new_count = self.db.execute("SELECT LAST_INSERT_ROWID() as last").fetchone() new_count = self.db.execute("SELECT LAST_INSERT_ROWID() as last").fetchone()
self.dblock.release() self.db_lock.release()
return new_count['last'] return new_count['last']
@validate_call @validate_call
def set_processed(self, count:int, processed:bool=True) -> bool: def set_processed(self, count:int, processed:bool=True) -> bool:
self.dblock.acquire() self.db_lock.acquire()
with self.db: with self.db:
try: try:
self.db.execute("UPDATE Messages SET processed = ? WHERE count = ?", (processed, count)) self.db.execute("UPDATE Messages SET processed = ? WHERE count = ?", (processed, count))
return True return True
except: except:
return False return False
self.dblock.release() finally:
self.db_lock.release()
def __iter__(self) -> Generator[RowObject, None, None]: def __iter__(self) -> Generator[RowObject, None, None]:
yield from self.iterate() yield from self.iterate()
@ -153,7 +154,7 @@ class DB():
with self.db: with self.db:
for row in self.db.execute( for row in self.db.execute(
"SELECT * FROM Messages {} 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)
@ -164,7 +165,7 @@ class DB():
sender=row['sender'], sender=row['sender'],
recipient=row['recipient'], recipient=row['recipient'],
time=int(datetime.strptime(row['time'], self._DB_TIME_FORMAT).timestamp()), time=int(datetime.strptime(row['time'], self._DB_TIME_FORMAT).timestamp()),
message=AgentMessage.model_construct(row['json']), message=AgentMessage.model_validate_json(row['json']),
processed=row['processed'] processed=row['processed']
) )

View File

@ -8,28 +8,57 @@
# 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
from urllib.parse import urlencode
from fastapi import APIRouter, Request from fastapi import APIRouter, Request
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():
_PREFIX = "/app"
def __init__(self, template:Jinja2Templates, db:DB): def __init__(self, template:Jinja2Templates, db:DB):
self.template = template self.template = template
self.db = db self.db = db
self.router = APIRouter( self.router = APIRouter(
prefix="/app", prefix=self._PREFIX,
tags=["app, gui"] tags=["app, gui"]
) )
self._add_routes() self._add_routes()
def _add_routes(self): def _add_routes(self):
@self.router.get("/", summary="Test") @self.router.get("/", response_class=RedirectResponse, summary="Redirect")
def corpus(request: Request): def index(request: Request) -> RedirectResponse:
return RedirectResponse(self._PREFIX + "/table")
@self.router.get("/table", response_class=HTMLResponse, summary="Table of messages")
def table(request: Request, limit:int=10, offset:int=0):
db_args = {
"limit" : limit,
"offset" : offset
}
print(self.db.by_count(3)) def pagination_link(**kwargs):
link_args = db_args.copy()
return {} link_args.update(kwargs)
return urlencode(link_args)
return self.template.TemplateResponse(
'table.html',
{"request" : request,
"db" : self.db, "db_args" : db_args,
"pagination_link" : pagination_link
}
)
@self.router.get("/new", response_class=HTMLResponse, summary="Add new riddle")
def new(request: Request):
return self.template.TemplateResponse(
'new.html',
{"request" : request}
)

View File

@ -10,7 +10,10 @@
import os import os
from datetime import datetime
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from ums.management.interface import Interface from ums.management.interface import Interface
@ -44,13 +47,21 @@ 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 _add_routers(self): def _add_routers(self):
interface_router = Interface(self.template, self.db) interface_router = Interface(self.template, self.db)
self.app.include_router(interface_router.router) self.app.include_router(interface_router.router)
def _add_routes(self): def _add_routes(self):
@self.app.get("/index", response_class=HTMLResponse, summary="Link list")
def index(request: Request):
return self.template.TemplateResponse(
'index.html',
{"request" : request}
)
@self.app.get("/test", summary="Test") @self.app.get("/test", summary="Test")
def huhu(request: Request) -> AgentMessage: def huhu(request: Request) -> AgentMessage:

View File

@ -1,14 +0,0 @@
<html>
<head>
<title>UMS-Agenten &ndash; Management</title>
</head>
<body>
<h1>UMS-Agenten &ndash; Management</h1>
<ul>
<li><a href="/app/" target="_blank">Web App (small GUI)</a></li>
<li><a href="/app/new" target="_blank">Add Riddle via GUI</a></li>
<li><a href="/api/" target="_blank">API Documentations</a></li>
<li><a href="/docs/" target="_blank">Code Documentation</a></li>
</ul>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

6
web/public/static/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
web/public/static/jquery.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

View File

48
web/templates/base.html Normal file
View File

@ -0,0 +1,48 @@
{#-
Agenten Plattform
(c) 2024 Magnus Bender
Institute of Humanities-Centered Artificial Intelligence (CHAI)
Universitaet Hamburg
https://www.chai.uni-hamburg.de/~bender
source code released under the terms of GNU Public License Version 3
https://www.gnu.org/licenses/gpl-3.0.txt
-#}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<link rel="stylesheet" href="/static/bootstrap.min.css" />
<link rel="stylesheet" href="/static/main.css" />
<script src="/static/jquery.min.js"></script>
<script src="/static/bootstrap.bundle.min.js"></script>
<script src="/static/main.js"></script>
<title>{{title}} &ndash; MGMT</title>
<!--
Agenten Plattform
(c) 2024 Magnus Bender
Institute of Humanities-Centered Artificial Intelligence (CHAI)
Universitaet Hamburg
https://www.chai.uni-hamburg.de/~bender
source code released under the terms of GNU Public License Version 3
https://www.gnu.org/licenses/gpl-3.0.txt
-->
{% block morehead %}
{% endblock %}
</head>
<body>
<div class="container">
<h1>{{title}} &ndash; MGMT</h1>
{% block maincontent %}
{% endblock %}
</div>
</body>
</html>

19
web/templates/index.html Normal file
View File

@ -0,0 +1,19 @@
{#-
Agenten Plattform
(c) 2024 Magnus Bender
Institute of Humanities-Centered Artificial Intelligence (CHAI)
Universitaet Hamburg
https://www.chai.uni-hamburg.de/~bender
source code released under the terms of GNU Public License Version 3
https://www.gnu.org/licenses/gpl-3.0.txt
-#}
{% extends "base.html" %}
{% set title = "Overview" %}
{% block maincontent %}
<ul>
<li><a href="/app/table" target="_blank">&nearr; Web App: Table</a></li>
<li><a href="/app/new" target="_blank">&nearr; Web App: New Riddle</a></li>
<li><a href="/api/" target="_blank">&nearr; Documentation: API </a></li>
<li><a href="/docs/" target="_blank">&nearr; Documentation: Code </a></li>
</ul>
{% endblock %}

19
web/templates/new.html Normal file
View File

@ -0,0 +1,19 @@
{#-
Agenten Plattform
(c) 2024 Magnus Bender
Institute of Humanities-Centered Artificial Intelligence (CHAI)
Universitaet Hamburg
https://www.chai.uni-hamburg.de/~bender
source code released under the terms of GNU Public License Version 3
https://www.gnu.org/licenses/gpl-3.0.txt
-#}
{% extends "base.html" %}
{% set title = "New" %}
{% block maincontent %}
<div class="float-end">
<a href="/app/table" class="btn btn-secondary">&larr; Back to Messages</a>
</div>
{% endblock %}

109
web/templates/table.html Normal file
View File

@ -0,0 +1,109 @@
{#-
Agenten Plattform
(c) 2024 Magnus Bender
Institute of Humanities-Centered Artificial Intelligence (CHAI)
Universitaet Hamburg
https://www.chai.uni-hamburg.de/~bender
source code released under the terms of GNU Public License Version 3
https://www.gnu.org/licenses/gpl-3.0.txt
-#}
{% extends "base.html" %}
{% set title = "Messages" %}
{% macro pagination() %}
<nav>
<ul class="pagination justify-content-center">
<li class="page-item {% if db_args.offset-db_args.limit < 0 %}disabled{% endif %}">
<a class="page-link" href="?{{ pagination_link(offset=db_args.offset-db_args.limit) }}">Previous</a>
</li>
<li class="page-item active" aria-current="page">
<span class="page-link">Offset: {{db_args.offset}}</span>
</li>
<li class="page-item">
<a class="page-link" href="?{{ pagination_link(offset=db_args.offset+db_args.limit) }}">Next</a>
</li>
</ul>
</nav>
{% endmacro %}
{% block maincontent %}
<div class="float-end">
<a href="/app/new" class="btn btn-secondary">&rarr; Add a Riddle</a>
</div>
{{pagination()}}
<table class="table table-striped">
<thead>
{% for item in db.iterate(**db_args) %}
{% if loop.index == 1 %}
<tr id="row_0">
{% for field in item.__fields__.keys() %}
<th>
{% 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_after" value="{{db_args.time_after}}" class="form-control" placeholder="After">
{% elif field not in ('message', 'count') %}
<input type="text" class="value_filter" name="filter_{{field}}" value="{{db_args[field]}}" class="form-control" placeholder="Filter">
{% endif %}
{{ field.title() }}
</th>
{% endfor %}
</tr>
</thead><tbody>
{% endif %}
<tr id="row_{{loop.index}}">
{% for field in item.__fields__.keys() %}
{% if field == "message" %}
<td>
<button type="button" class="btn btn-outline-secondary btn-outline" data-bs-toggle="modal" data-bs-target="#row_message_{{loop.index}}">
Show Message
</button>
<div class="modal fade" id="row_message_{{loop.index}}" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5">Content of Message</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<pre>{{ item[field].model_dump_json(indent=2)|string }}</pre>
</div>
</div>
</div>
</div>
</td>
{% elif field == "time" %}
<td ts="item[field]">{{ timestamp2date(item[field]) }}</td>
{% else %}
<td>{{ item[field] }}</td>
{% endif %}
{% endfor %}
</tr>
{% else %}
<div class="alert alert-warning" role="alert">
No items found, reset offset, limit, filter, ...!
</div>
{% endfor %}
</tbody>
</table>
<div class="float-end">
<div class="input-group">
<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;">
<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=25) }}" {% if db_args.limit == 25 %}selected{% endif %}>25</option>
<option value="?{{ pagination_link(limit=100) }}" {% if db_args.limit == 100 %}selected{% endif %}>100</option>
{% if db_args.limit not in (5, 10, 25, 100) %}
<option value="?{{ pagination_link(limit=db_args.limit) }}" selected>{{db_args.limit}}</option>
{% endif %}
</select>
</div>
</div>
{{pagination()}}
{% endblock %}