feat: Printer edit flow; dashboard webcams
This commit is contained in:
parent
de7f91947f
commit
d79a2a578f
10 changed files with 182 additions and 26 deletions
|
@ -116,14 +116,21 @@ def serve(hostname: str, port: int, config: Path, trace: bool):
|
|||
if config:
|
||||
with open(config, "rb") as fp:
|
||||
app.config.update(tomllib.load(fp))
|
||||
|
||||
print(app.config)
|
||||
|
||||
# Run migrations once at startup rather than when connecting
|
||||
with closing(db_factory(app)) as db:
|
||||
db.migrate()
|
||||
|
||||
# Configuring cherrypy is kinda awful
|
||||
cherrypy.server.unsubscribe()
|
||||
server = cherrypy._cpserver.Server()
|
||||
cherrypy.config.update(
|
||||
{
|
||||
"environment": "production",
|
||||
"engine.autoreload.on": False,
|
||||
"log.screen.on": True,
|
||||
}
|
||||
)
|
||||
cherrypy.tree.graft(app, "/")
|
||||
|
@ -135,7 +142,6 @@ def serve(hostname: str, port: int, config: Path, trace: bool):
|
|||
server.subscribe()
|
||||
|
||||
# Spawn the worker thread(s)
|
||||
|
||||
Worker(cherrypy.engine, app, db_factory, poll_printers, frequency=5).start()
|
||||
Worker(cherrypy.engine, app, db_factory, assign_jobs, frequency=5).start()
|
||||
Worker(cherrypy.engine, app, db_factory, push_jobs, frequency=5).start()
|
||||
|
|
|
@ -103,6 +103,26 @@ def handle_add_printer():
|
|||
return render_template("printers.html.j2")
|
||||
|
||||
|
||||
@BLUEPRINT.route("/admin/printers/edit", methods=["GET"])
|
||||
@requires_admin
|
||||
def get_edit_printers():
|
||||
pid = int(request.args.get("id", "-1"))
|
||||
if row := ctx.db.fetch_printer(pid=pid):
|
||||
return render_template("edit_printer.html.j2", printer=row)
|
||||
else:
|
||||
flash("No such printer", category="error")
|
||||
return redirect("/admin"), 404
|
||||
|
||||
|
||||
@BLUEPRINT.route("/admin/printers/edit", methods=["POST"])
|
||||
@requires_admin
|
||||
def handle_edit_printers():
|
||||
args = request.form.copy()
|
||||
args["id"] = int(args["id"])
|
||||
ctx.db.edit_printer(**request.form)
|
||||
return redirect("/admin")
|
||||
|
||||
|
||||
@BLUEPRINT.route("/admin/files", methods=["POST"])
|
||||
@requires_admin
|
||||
def manipulate_files():
|
||||
|
|
|
@ -82,7 +82,22 @@ class Db(Queries):
|
|||
self._conn.row_factory = self._factory
|
||||
self._conn.isolation_level = None # Disable automagical transactions
|
||||
self._cursor = self._conn.cursor()
|
||||
self.create_tables()
|
||||
|
||||
def migrate(self):
|
||||
self.migration_0000_create_migrations()
|
||||
existing_migrations = {it.name for it in self.list_migrations()}
|
||||
for query in sorted(
|
||||
(
|
||||
q
|
||||
for q in _queries.available_queries
|
||||
if q.startswith("migration_") and q not in existing_migrations
|
||||
)
|
||||
):
|
||||
log.warn("Applying migration %s", query)
|
||||
getattr(self, query)()
|
||||
digest = sha3_256()
|
||||
digest.update(getattr(_queries, query).sql.encode())
|
||||
self.record_migration(name=query, fingerprint=digest.hexdigest())
|
||||
|
||||
def begin(self):
|
||||
self._conn.execute("BEGIN")
|
||||
|
|
|
@ -1,4 +1,31 @@
|
|||
-- name: create_tables#
|
||||
-- name: migration-0000-create_migrations
|
||||
CREATE TABLE IF NOT EXISTS migrations (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||
, name TEXT
|
||||
, fingerprint TEXT
|
||||
, executed_at TEXT DEFAULT (datetime('now'))
|
||||
, UNIQUE(name)
|
||||
);
|
||||
|
||||
-- name: list-migrations
|
||||
SELECT
|
||||
*
|
||||
FROM migrations
|
||||
ORDER BY
|
||||
datetime(executed_at) ASC
|
||||
;
|
||||
|
||||
-- name: record-migration!
|
||||
INSERT INTO migrations (
|
||||
name
|
||||
, fingerprint
|
||||
)
|
||||
VALUES (
|
||||
:name
|
||||
, :fingerprint
|
||||
);
|
||||
|
||||
-- name: migration-0001-create_tables#
|
||||
-- Initialize the core db tables. Arguably migration 0.
|
||||
----------------------------------------------------------------------------------------------------
|
||||
-- User structures
|
||||
|
@ -347,6 +374,7 @@ SELECT
|
|||
p.id
|
||||
, p.name
|
||||
, p.url
|
||||
, p.stream_url
|
||||
, p.api_key
|
||||
, p.last_poll_date
|
||||
, s.name as status
|
||||
|
@ -360,6 +388,7 @@ SELECT
|
|||
p.id
|
||||
, p.name
|
||||
, p.url
|
||||
, p.stream_url
|
||||
, p.api_key
|
||||
, p.last_poll_date
|
||||
, s.name as status
|
||||
|
@ -388,6 +417,17 @@ WHERE
|
|||
id = :pid
|
||||
;
|
||||
|
||||
-- name: edit-printer
|
||||
UPDATE printers
|
||||
SET
|
||||
name = :name
|
||||
, url = :url
|
||||
, stream_url = :stream_url
|
||||
, api_key = :api_key
|
||||
WHERE
|
||||
id = :id
|
||||
;
|
||||
|
||||
----------------------------------------------------------------------------------------------------
|
||||
-- Files
|
||||
----------------------------------------------------------------------------------------------------
|
||||
|
|
|
@ -59,6 +59,8 @@
|
|||
.columns:first-child {
|
||||
margin-left: 0; }
|
||||
|
||||
.equal.columns {}
|
||||
|
||||
.one.column,
|
||||
.one.columns { width: 4.66666666667%; }
|
||||
.two.columns { width: 13.3333333333%; }
|
||||
|
|
|
@ -54,6 +54,10 @@ label {
|
|||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.u-flex1 {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.file, .printer, .key, .job {
|
||||
flex-direction: column;
|
||||
|
@ -65,3 +69,20 @@ label {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.row.webcams {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
img {
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.webcam {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.webcam:last-child {
|
||||
padding-right: 0px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
{% extends "base.html.j2" %}
|
||||
{% block content %}
|
||||
<h1>Edit printer</h1>
|
||||
<div class="row">
|
||||
<form method="post">
|
||||
<div class="row">
|
||||
<div class="twelve columns">
|
||||
<label for="name">Printer name</label>
|
||||
<input type="text" name="name" value="{{ printer.name }}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="twelve columns">
|
||||
<label for="url">Printer base URL</label>
|
||||
<input type="text" name="url" value="{{ printer.url }}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="twelve columns">
|
||||
<label for="url">Printer stream URL</label>
|
||||
<input type="text" name="stream_url" value="{{ printer.stream_url or ''}}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="twelve columns">
|
||||
<label for="api_key">API key</label>
|
||||
<input type="text" name="api_key" value="{{ printer.api_key }}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<input type="hidden" name="id" value="{{ printer.id }}" />
|
||||
<input id="submit" type="submit" value="Submit" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,5 +1,9 @@
|
|||
{% extends "base.html.j2" %}
|
||||
{% block content %}
|
||||
<div class="row twelve columns mb-2">
|
||||
{% include "streams.html.j2" %}
|
||||
</div>
|
||||
|
||||
<div class="row twelve columns mb-2">
|
||||
{% include "jobs_list.html.j2" %}
|
||||
</div>
|
||||
|
|
|
@ -3,22 +3,22 @@
|
|||
{% with printers = ctx.db.list_printers() %}
|
||||
{% if printers %}
|
||||
{% for printer in printers %}
|
||||
{% with id, name, url, _api_key, last_poll, status = printer %}
|
||||
<div class="printer row u-flex">
|
||||
<div class="details six columns">
|
||||
<span class="printer-name">{{name}}</span>
|
||||
<span class="printer-url"><code>{{url}}</code></span>
|
||||
<span class="printer-status">{{status}}</span>
|
||||
<span class="printer-date">{{last_poll}}</span>
|
||||
<span class="printer-name">{{printer.name}}</span>
|
||||
<span class="printer-url"><code>{{printer.url}}</code></span>
|
||||
<span class="printer-status">{{printer.status}}</span>
|
||||
<span class="printer-date">{{printer.last_poll_date}}</span>
|
||||
</div>
|
||||
{# FIXME: How should these action buttons work? #}
|
||||
<div class="controls u-flex u-ml-auto">
|
||||
<a class="button" href="/printers/test?id={{id}}">Test</a>
|
||||
<a class="button" href="/printers/edit?id={{id}}">Edit</a>
|
||||
<a class="button" href="/printers/delete?id={{id}}">Remove</a>
|
||||
{% if ctx.is_admin %}
|
||||
<a class="button" href="/admin/printers/test?id={{printer.id}}">Test</a>
|
||||
<a class="button" href="/admin/printers/edit?id={{printer.id}}">Edit</a>
|
||||
<a class="button" href="/admin/printers/delete?id={{printer.id}}">Remove</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
{% if ctx.is_admin %}
|
||||
<a class="button" href="/admin/printers">Add a printer</a>
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
{% import "macros.html.j2" as macros %}
|
||||
<h2>Webcams</h2>
|
||||
{% with printers = ctx.db.list_printers() %}
|
||||
<div class="webcams row">
|
||||
{% for printer in printers if printer.stream_url %}
|
||||
<div class="u-flex1 webcam" style="max-width: calc(100% / {{printers|length}})">
|
||||
<label>{{ printer.name }}</label>
|
||||
<img id="printer_{{printer.id}}_stream" src="{{ printer.stream_url }}" style="max-width: 100%;" />
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endwith %}
|
Loading…
Reference in a new issue