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:
|
if config:
|
||||||
with open(config, "rb") as fp:
|
with open(config, "rb") as fp:
|
||||||
app.config.update(tomllib.load(fp))
|
app.config.update(tomllib.load(fp))
|
||||||
|
|
||||||
print(app.config)
|
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()
|
cherrypy.server.unsubscribe()
|
||||||
server = cherrypy._cpserver.Server()
|
server = cherrypy._cpserver.Server()
|
||||||
cherrypy.config.update(
|
cherrypy.config.update(
|
||||||
{
|
{
|
||||||
"environment": "production",
|
"environment": "production",
|
||||||
"engine.autoreload.on": False,
|
"engine.autoreload.on": False,
|
||||||
|
"log.screen.on": True,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
cherrypy.tree.graft(app, "/")
|
cherrypy.tree.graft(app, "/")
|
||||||
|
@ -135,7 +142,6 @@ def serve(hostname: str, port: int, config: Path, trace: bool):
|
||||||
server.subscribe()
|
server.subscribe()
|
||||||
|
|
||||||
# Spawn the worker thread(s)
|
# Spawn the worker thread(s)
|
||||||
|
|
||||||
Worker(cherrypy.engine, app, db_factory, poll_printers, frequency=5).start()
|
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, assign_jobs, frequency=5).start()
|
||||||
Worker(cherrypy.engine, app, db_factory, push_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")
|
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"])
|
@BLUEPRINT.route("/admin/files", methods=["POST"])
|
||||||
@requires_admin
|
@requires_admin
|
||||||
def manipulate_files():
|
def manipulate_files():
|
||||||
|
|
|
@ -82,7 +82,22 @@ class Db(Queries):
|
||||||
self._conn.row_factory = self._factory
|
self._conn.row_factory = self._factory
|
||||||
self._conn.isolation_level = None # Disable automagical transactions
|
self._conn.isolation_level = None # Disable automagical transactions
|
||||||
self._cursor = self._conn.cursor()
|
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):
|
def begin(self):
|
||||||
self._conn.execute("BEGIN")
|
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.
|
-- Initialize the core db tables. Arguably migration 0.
|
||||||
----------------------------------------------------------------------------------------------------
|
----------------------------------------------------------------------------------------------------
|
||||||
-- User structures
|
-- User structures
|
||||||
|
@ -347,6 +374,7 @@ SELECT
|
||||||
p.id
|
p.id
|
||||||
, p.name
|
, p.name
|
||||||
, p.url
|
, p.url
|
||||||
|
, p.stream_url
|
||||||
, p.api_key
|
, p.api_key
|
||||||
, p.last_poll_date
|
, p.last_poll_date
|
||||||
, s.name as status
|
, s.name as status
|
||||||
|
@ -360,6 +388,7 @@ SELECT
|
||||||
p.id
|
p.id
|
||||||
, p.name
|
, p.name
|
||||||
, p.url
|
, p.url
|
||||||
|
, p.stream_url
|
||||||
, p.api_key
|
, p.api_key
|
||||||
, p.last_poll_date
|
, p.last_poll_date
|
||||||
, s.name as status
|
, s.name as status
|
||||||
|
@ -388,6 +417,17 @@ WHERE
|
||||||
id = :pid
|
id = :pid
|
||||||
;
|
;
|
||||||
|
|
||||||
|
-- name: edit-printer
|
||||||
|
UPDATE printers
|
||||||
|
SET
|
||||||
|
name = :name
|
||||||
|
, url = :url
|
||||||
|
, stream_url = :stream_url
|
||||||
|
, api_key = :api_key
|
||||||
|
WHERE
|
||||||
|
id = :id
|
||||||
|
;
|
||||||
|
|
||||||
----------------------------------------------------------------------------------------------------
|
----------------------------------------------------------------------------------------------------
|
||||||
-- Files
|
-- Files
|
||||||
----------------------------------------------------------------------------------------------------
|
----------------------------------------------------------------------------------------------------
|
||||||
|
|
|
@ -59,6 +59,8 @@
|
||||||
.columns:first-child {
|
.columns:first-child {
|
||||||
margin-left: 0; }
|
margin-left: 0; }
|
||||||
|
|
||||||
|
.equal.columns {}
|
||||||
|
|
||||||
.one.column,
|
.one.column,
|
||||||
.one.columns { width: 4.66666666667%; }
|
.one.columns { width: 4.66666666667%; }
|
||||||
.two.columns { width: 13.3333333333%; }
|
.two.columns { width: 13.3333333333%; }
|
||||||
|
|
|
@ -54,6 +54,10 @@ label {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.u-flex1 {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 760px) {
|
@media (max-width: 760px) {
|
||||||
.file, .printer, .key, .job {
|
.file, .printer, .key, .job {
|
||||||
flex-direction: column;
|
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" %}
|
{% extends "base.html.j2" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<div class="row twelve columns mb-2">
|
||||||
|
{% include "streams.html.j2" %}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row twelve columns mb-2">
|
<div class="row twelve columns mb-2">
|
||||||
{% include "jobs_list.html.j2" %}
|
{% include "jobs_list.html.j2" %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,22 +3,22 @@
|
||||||
{% with printers = ctx.db.list_printers() %}
|
{% with printers = ctx.db.list_printers() %}
|
||||||
{% if printers %}
|
{% if printers %}
|
||||||
{% for printer in printers %}
|
{% for printer in printers %}
|
||||||
{% with id, name, url, _api_key, last_poll, status = printer %}
|
|
||||||
<div class="printer row u-flex">
|
<div class="printer row u-flex">
|
||||||
<div class="details six columns">
|
<div class="details six columns">
|
||||||
<span class="printer-name">{{name}}</span>
|
<span class="printer-name">{{printer.name}}</span>
|
||||||
<span class="printer-url"><code>{{url}}</code></span>
|
<span class="printer-url"><code>{{printer.url}}</code></span>
|
||||||
<span class="printer-status">{{status}}</span>
|
<span class="printer-status">{{printer.status}}</span>
|
||||||
<span class="printer-date">{{last_poll}}</span>
|
<span class="printer-date">{{printer.last_poll_date}}</span>
|
||||||
</div>
|
</div>
|
||||||
{# FIXME: How should these action buttons work? #}
|
{# FIXME: How should these action buttons work? #}
|
||||||
<div class="controls u-flex u-ml-auto">
|
<div class="controls u-flex u-ml-auto">
|
||||||
<a class="button" href="/printers/test?id={{id}}">Test</a>
|
{% if ctx.is_admin %}
|
||||||
<a class="button" href="/printers/edit?id={{id}}">Edit</a>
|
<a class="button" href="/admin/printers/test?id={{printer.id}}">Test</a>
|
||||||
<a class="button" href="/printers/delete?id={{id}}">Remove</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>
|
||||||
</div>
|
</div>
|
||||||
{% endwith %}
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if ctx.is_admin %}
|
{% if ctx.is_admin %}
|
||||||
<a class="button" href="/admin/printers">Add a printer</a>
|
<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