Skip to content

Replace Dashboard's Database Table with an HTMX Grid #612

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 114 additions & 33 deletions apps/_dashboard/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
)
from py4web.core import Fixture, Reloader, Session, dumps, error_logger, safely
from py4web.utils.factories import ActionFactory
from py4web.utils.grid import Grid, AttributesPluginHtmx

from .diff2kryten import diff2kryten
from .utils import *
Expand All @@ -52,7 +53,8 @@ def get_commits(project):
commits = []
for line in output.split("\n"):
if line.startswith("commit "):
commit = {"code": line[7:], "message": "", "author": "", "date": ""}
commit = {"code": line[7:], "message": "",
"author": "", "date": ""}
commits.append(commit)
elif line.startswith("Author: "):
commit["author"] = line[8:]
Expand Down Expand Up @@ -138,7 +140,104 @@ def logout():
@action("dbadmin")
@action.uses(Logged(session), "dbadmin.html")
def dbadmin():
return dict(languages=dumps(getattr(T.local, "language", {})))
args = dict(request.query)
app = args.get('app', None)
dbname = args.get('dbname', None)
tablename = args.get('tablename', None)
error = (app is None or dbname is None or tablename is None)
gridURL = URL("dbadmin_grid", app, dbname, tablename)
return dict(languages=dumps(getattr(T.local, "language", {})), gridURL=gridURL, error=error)

@action("dbadmin_grid/<app>/<dbname>/<tablename>", method=["GET", "POST"])
@action("dbadmin_grid/<app>/<dbname>/<tablename>/<path:path>", method=["GET", "POST"])
@action.uses(Logged(session), "dbadminGrid.html")
def dbadmin_grid(app=None, dbname=None, tablename=None, path=None):
from py4web.core import Reloader, DAL
from yatl.helpers import A, INPUT

if MODE != "full":
raise HTTP(403)

module = Reloader.MODULES[app]

databases = [
name for name in dir(module) if isinstance(getattr(module, name), DAL)
]
if dbname not in databases:
raise HTTP(406)

db = getattr(module, dbname)

grid_param = dict(
rows_per_page=20,
include_action_button_text=True,
search_button_text=None,
formstyle=FormStyleFuture,
grid_class_style=GridClassStyleFuture,
auto_process=False,
)

if tablename not in db:
raise HTTP(406)

table = getattr(db, tablename)

for field in table:
field.readable = True
field.writable = True

query = table.id > 0
orderby = [table.id]
columns = [field for field in table]
columns = columns[:6]

def genericSearch(field):
query = lambda value: field == value
if field.type.lower() == "string":
query = lambda value: field.contains(value)
return query

search_queries = [[f"By {field.label}", genericSearch(field)] for field in table]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this for now will do but we need something better


grid = Grid(
path,
query,
columns=columns,
search_queries=search_queries,
orderby=orderby,
show_id=True,
T=T,
**grid_param
)

grid.attributes_plugin = AttributesPluginHtmx("#panel")
attrs = {
"_hx-get": URL("dbadmin_grid", app, dbname, tablename),
"_hx-target": "#panel",
"_class": "btn"
}
grid.param.new_sidecar = A("Cancel", **attrs)
grid.param.edit_sidecar = A("Cancel", **attrs)

grid.formatters_by_type["date"] = (
lambda value: value.strftime("%m/%d/%Y") if value else ""
)

grid.formatters_by_type["time"] = (
lambda value: value.strftime("%H:%M:%S") if value else ""
)

grid.formatters_by_type["datetime"] = (
lambda value: value.strftime("%m/%d/%Y %H:%M:%S") if value else ""
)

grid.formatters_by_type["boolean"] = (
lambda value: INPUT(_type="checkbox", _checked=value, _disabled="disabled") if isinstance(value, bool) else ""
)

grid.process()

return dict(grid=grid)

@action("info")
@session_secured
Expand Down Expand Up @@ -226,7 +325,8 @@ def walk(path):
"dirs": list(
sorted(
[
{"name": dir, "content": store[os.path.join(root, dir)]}
{"name": dir,
"content": store[os.path.join(root, dir)]}
for dir in dirs
if dir[0] != "." and dir[:2] != "__"
],
Expand Down Expand Up @@ -268,15 +368,17 @@ def packed(path):
appname = sanitize(appname)
app_dir = os.path.join(FOLDER, appname)
store = io.BytesIO()
zip = zipfile.ZipFile(store, mode="w", compression=zipfile.ZIP_DEFLATED)
zip = zipfile.ZipFile(
store, mode="w", compression=zipfile.ZIP_DEFLATED)
for root, dirs, files in os.walk(app_dir, topdown=False):
if not root.startswith("."):
for name in files:
if not (
name.endswith("~") or name.endswith(".pyc") or name[:1] in "#."
name.endswith("~") or name.endswith(
".pyc") or name[:1] in "#."
):
filename = os.path.join(root, name)
short = filename[len(app_dir + os.path.sep) :]
short = filename[len(app_dir + os.path.sep):]
print("added", filename, short)
zip.write(filename, short)
zip.close()
Expand All @@ -288,7 +390,8 @@ def packed(path):
@session_secured
def tickets():
"""Returns most recent tickets grouped by path+error"""
tickets = safely(error_logger.database_logger.get) if MODE != "DEMO" else None
tickets = safely(
error_logger.database_logger.get) if MODE != "DEMO" else None
return {"payload": tickets or []}

@action("clear")
Expand All @@ -304,7 +407,8 @@ def error_ticket(ticket_uuid):
if MODE != "demo":
return dict(
ticket=safely(
lambda: error_logger.database_logger.get(ticket_uuid=ticket_uuid)
lambda: error_logger.database_logger.get(
ticket_uuid=ticket_uuid)
)
)
else:
Expand All @@ -317,7 +421,6 @@ def api(path):
args = path.split("/")
app_name = args[0]
from py4web.core import Reloader, DAL
from pydal.restapi import RestAPI, Policy

if MODE != "full":
raise HTTP(403)
Expand Down Expand Up @@ -347,29 +450,6 @@ def tables(name):
{"name": name, "tables": tables(name)} for name in databases
]
}
elif len(args) > 2 and args[1] in databases:
db = getattr(module, args[1])
id = args[3] if len(args) == 4 else None
policy = Policy()
for table in db:
policy.set(
table._tablename,
"GET",
authorize=True,
allowed_patterns=["**"],
allow_lookup=True,
fields=table.fields,
)
policy.set(table._tablename, "PUT", authorize=True, fields=table.fields)
policy.set(
table._tablename, "POST", authorize=True, fields=table.fields
)
policy.set(table._tablename, "DELETE", authorize=True)
data = action.uses(db, T)(
lambda: RestAPI(db, policy)(
request.method, args[2], id, request.query, request.json
)
)()
else:
data = {}
if "code" in data:
Expand Down Expand Up @@ -511,7 +591,8 @@ def swapbranch(project):
raise HTTP(400)

branch = (
request.forms.get("branches") if request.forms.get("branches") else "master"
request.forms.get("branches") if request.forms.get(
"branches") else "master"
)
# swap branches then go back to gitlog so new commits load
checkout(project, branch)
Expand Down
33 changes: 31 additions & 2 deletions apps/_dashboard/static/css/future.css
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ td.middle {
tbody {
border-bottom: 1px solid #33BFFF;
}
.btn,
button {
background-color: black;
color: #33BFFF;
Expand All @@ -72,6 +73,9 @@ button.btn-app {
padding: 4px 20px 6px 10px;
text-align: left;
}

.btn-filled,
.btn:hover,
button:hover {
border: 2px solid #139FDF;
box-shadow: inset 0 0 0 50px #33BFFF;
Expand Down Expand Up @@ -133,8 +137,7 @@ input[type=number],
input[type=date],
input[type=time],
input[type=datetime-local],
input[type=file],
select,
.input,
textarea {
background-color: black;
border: 0;
Expand All @@ -148,6 +151,7 @@ input[type=number],
input[type=date],
input[type=time],
input[type=datetime-local],
.input,
select,
textarea {
border-bottom: 2px solid #33BFFF;
Expand Down Expand Up @@ -284,4 +288,29 @@ label {
}
.field-referenced {
margin-right: 1em;
}

.field:not(:last-child) {
margin-bottom: .75rem;
}

.label:not(:last-child) {
margin-bottom: .5em;
}

select {
cursor: pointer;
background-color: black;
font-size: 20px;
color: #33BFFF;
}
.gridPanel {
background: transparent;
text-align: left;
vertical-align: top;
border: 0;
margin: 5px 15px 5px 5px;
padding: 30px 15px 2px 15px;
position: relative;
text-overflow: ellipsis;
}
4 changes: 0 additions & 4 deletions apps/_dashboard/static/js/dbadmin.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
var app = Q.app();
var params = new URLSearchParams(window.location.search);
app.data.loading = 0;
app.data.app = params.get('app');
app.data.dbname = params.get('dbname');
app.data.tablename = params.get('tablename');
app.data.url = '/_dashboard/rest/{app}/{dbname}/{tablename}'.format(app.data);
app.data.filter = params.get('filter') || '';
app.data.order = params.get('order') || '';
app.start();
17 changes: 12 additions & 5 deletions apps/_dashboard/templates/dbadmin.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,32 @@
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css">
</head>
<body>
<div id="vue" class="my-effects">
<!--div class="loading" v-if="loading>0"></div-->
<div id="vue">
<div class="panel">
<label>
App: {{app}}
Database: {{dbname}}
Table: {{tablename}}
</label>
</div>
<div class="panel">
<mtable :url="url" :filter="filter" :order="order" :editable="true" :create="true" :deletable="true" :render="{}"></mtable>
<div id="panel" class="gridPanel">
[[if error:]]
<div>
<p class="error"> An error has occured, please make sure that the query parameters: "app", "dbname", and "tablename" are all filled out.</p>
<a class="btn" href="[[=URL('index')]]">Return</a>
</div>
[[else:]]
<div hx-get="[[=gridURL]]" hx-trigger="load" hx-target="#panel">
</div>
[[pass]]
</div>
</div>
</body>
<script src="https://unpkg.com/htmx.org@1.3.2"></script>
<script src="js/sugar.min.js"></script>
<script src="js/vue.min.js"></script>
<script src="js/axios.min.js"></script>
<script src="js/utils.js"></script>
<script src="components/mtable.js"></script>
<script>T.languages = [[=languages]];</script>
<script src="js/dbadmin.js"></script>
</html>
1 change: 1 addition & 0 deletions apps/_dashboard/templates/dbadminGrid.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[[=grid.render()]]
Loading