From 5d8da63a00849b0017dd315e470b30c10cdd1a51 Mon Sep 17 00:00:00 2001
From: Reid 'arrdem' McKenzie <me@arrdem.com>
Date: Tue, 23 May 2023 00:15:16 -0600
Subject: [PATCH] Adding printers sorta works now

---
 .../src/python/tentacles/__main__.py          |  3 +-
 .../python/tentacles/blueprints/printer_ui.py | 21 +++++++++-
 .../src/python/tentacles/blueprints/util.py   | 11 ++++++
 .../tentacles/src/python/tentacles/schema.sql |  7 ++--
 .../python/tentacles/static/css/style.scss    | 39 ++++++++++++++++++-
 .../tentacles/src/python/tentacles/store.py   |  8 ++--
 .../tentacles/templates/add_printer.html.j2   |  9 +++--
 .../python/tentacles/templates/index.html.j2  | 15 -------
 .../tentacles/templates/printers.html.j2      | 28 +++++++++++++
 9 files changed, 110 insertions(+), 31 deletions(-)
 create mode 100644 projects/tentacles/src/python/tentacles/templates/printers.html.j2

diff --git a/projects/tentacles/src/python/tentacles/__main__.py b/projects/tentacles/src/python/tentacles/__main__.py
index 926dc8a..b610dc8 100644
--- a/projects/tentacles/src/python/tentacles/__main__.py
+++ b/projects/tentacles/src/python/tentacles/__main__.py
@@ -65,7 +65,8 @@ def serve(hostname: str, port: int, config: Path):
     print(app.config)
 
     # Before first request
-    app.before_first_request(create_j2_request_global)
+    with app.app_context():
+        create_j2_request_global()
 
     # Before request
     app.before_request(open_db)
diff --git a/projects/tentacles/src/python/tentacles/blueprints/printer_ui.py b/projects/tentacles/src/python/tentacles/blueprints/printer_ui.py
index 27e6da0..ed16f7b 100644
--- a/projects/tentacles/src/python/tentacles/blueprints/printer_ui.py
+++ b/projects/tentacles/src/python/tentacles/blueprints/printer_ui.py
@@ -20,28 +20,45 @@ from flask import (
     flash,
 )
 
-from .util import is_logged_in
+from .util import is_logged_in, requires_admin
 
 log = logging.getLogger(__name__)
 BLUEPRINT = Blueprint("printer", __name__)
 
 
+@requires_admin
 @BLUEPRINT.route("/printers")
 def printers():
     return render_template("printers.html.j2")
 
 
+@requires_admin
 @BLUEPRINT.route("/printers/add", methods=["get", "post"])
 def add_printer():
     if not is_logged_in(request):
         return redirect("/")
 
     elif request.method == "POST":
-        pass
+        try:
+            assert request.form["name"]
+            assert request.form["url"]
+            assert request.form["api_key"]
+            request.db.try_create_printer(
+                request.form["name"],
+                request.form["url"],
+                request.form["api_key"],
+            )
+            flash("Printer created")
+            return redirect("/printers")
+
+        except Exception as e:
+            log.exception("Failed to create printer")
+            flash(f"Unable to create printer", category="error")
 
     return render_template("add_printer.html.j2")
 
 
+@requires_admin
 @BLUEPRINT.route("/printers/delete")
 def delete_printer():
     return render_template("delete_printer.html.j2")
diff --git a/projects/tentacles/src/python/tentacles/blueprints/util.py b/projects/tentacles/src/python/tentacles/blueprints/util.py
index 25b2725..ed70660 100644
--- a/projects/tentacles/src/python/tentacles/blueprints/util.py
+++ b/projects/tentacles/src/python/tentacles/blueprints/util.py
@@ -27,3 +27,14 @@ def is_logged_in(request: Request) -> bool:
 
 def salt(password: str) -> str:
     return "$SALT$" + current_app.config["SECRET_KEY"] + password
+
+
+def requires_admin(f):
+    def _helper(*args, **kwargs):
+        if not request.is_admin:
+            flash("Sorry, admins only", category="error")
+            redirect("/")
+        else:
+            return f(*args, **kwargs)
+
+    return _helper
diff --git a/projects/tentacles/src/python/tentacles/schema.sql b/projects/tentacles/src/python/tentacles/schema.sql
index 3d7aa3a..261d4d2 100644
--- a/projects/tentacles/src/python/tentacles/schema.sql
+++ b/projects/tentacles/src/python/tentacles/schema.sql
@@ -5,8 +5,8 @@ CREATE TABLE IF NOT EXISTS groups (
 );
 
 INSERT OR IGNORE INTO groups (id, name, priority) VALUES (0, 'root', 20);
-INSERT OR IGNORE INTO groups (id, name, priority) VALUES (0, 'users', 10);
-INSERT OR IGNORE INTO groups (id, name, priority) VALUES (0, 'guests', 0);
+INSERT OR IGNORE INTO groups (id, name, priority) VALUES (1, 'users', 10);
+INSERT OR IGNORE INTO groups (id, name, priority) VALUES (2, 'guests', 0);
 
 CREATE TABLE IF NOT EXISTS user_statuses (
    id INTEGER PRIMARY KEY AUTOINCREMENT
@@ -42,7 +42,7 @@ CREATE TABLE IF NOT EXISTS user_keys (
 
 ----------------------------------------------------------------------------------------------------
 -- Printers represent physical devices (really octoprint API targets) that jobs can go to
-CREATE TABLE IF NOT EXISTs printer_statuses (
+CREATE TABLE IF NOT EXISTS printer_statuses (
    id INTEGER PRIMARY KEY AUTOINCREMENT
  , name TEXT
  , UNIQUE(name)
@@ -56,6 +56,7 @@ INSERT OR IGNORE INTO printer_statuses (id, name) values (3, 'running');
 
 CREATE TABLE IF NOT EXISTS printers (
    id INTEGER PRIMARY KEY AUTOINCREMENT
+ , name TEXT
  , url TEXT
  , api_key TEXT
  , status_id INTEGER
diff --git a/projects/tentacles/src/python/tentacles/static/css/style.scss b/projects/tentacles/src/python/tentacles/static/css/style.scss
index 70af198..4b3bb49 100644
--- a/projects/tentacles/src/python/tentacles/static/css/style.scss
+++ b/projects/tentacles/src/python/tentacles/static/css/style.scss
@@ -65,8 +65,8 @@ html, body {
 }
 
 .content, .footer {
-    padding-left: 10%;
-    padding-right: 10%;
+  padding-left: 10%;
+  padding-right: 10%;
 }
 
 .content {
@@ -85,6 +85,15 @@ a {
   padding: 0;
 }
 
+ul {
+  list-style: none;
+
+  .decorated {
+    list-style: auto;
+    padding: 1em;
+  }
+}
+
 nav {
   background-color: $beige;
   box-shadow: 0px 10px 0px $red,
@@ -272,3 +281,29 @@ $navbar_padding: 10px;
     border-color: $secondary_red;
   }
 }
+
+form {
+  display: flex;
+  flex-direction: column;
+
+  .form-input {
+    display: flex;
+    flex-direction: row;
+
+    .form-label {
+      width: 200px;
+      margin-right: 10px;
+    }
+
+    input {
+      margin-right: auto;
+      width: 400px;
+    }
+  }
+}
+
+.button {
+  border: 1px solid;
+  border-radius: 1em;
+  padding: 0.5em;
+}
diff --git a/projects/tentacles/src/python/tentacles/store.py b/projects/tentacles/src/python/tentacles/store.py
index 197fcd6..83824b6 100644
--- a/projects/tentacles/src/python/tentacles/store.py
+++ b/projects/tentacles/src/python/tentacles/store.py
@@ -229,16 +229,16 @@ class Store(object):
 
     @fmap(one)
     @requires_conn
-    def try_create_printer(self, url, api_key):
+    def try_create_printer(self, name: str, url: str, api_key: str):
         self._conn.execute(
-            "INSERT INTO printers (url, api_key, status_id) VALUES (?, ?, 0) RETURNING id",
-            [url, api_key],
+            "INSERT INTO printers (name, url, api_key, status_id) VALUES (?1, ?2, ?3, 0) RETURNING id",
+            [name, url, api_key],
         ).fetchone()
 
     @requires_conn
     def list_printers(self):
         return self._conn.execute(
-            "SELECT id, url, last_poll_date, s.name as status FROM printers p INNER JOIN printer_stauses s ON p.status_id = s.id"
+            "SELECT p.id, p.name, p.url, p.last_poll_date, s.name as status FROM printers p INNER JOIN printer_statuses s ON p.status_id = s.id"
         ).fetchall()
 
     @requires_conn
diff --git a/projects/tentacles/src/python/tentacles/templates/add_printer.html.j2 b/projects/tentacles/src/python/tentacles/templates/add_printer.html.j2
index 479d7ba..56b094a 100644
--- a/projects/tentacles/src/python/tentacles/templates/add_printer.html.j2
+++ b/projects/tentacles/src/python/tentacles/templates/add_printer.html.j2
@@ -2,10 +2,11 @@
 {% block content %}
 <h1>Add printer</h1>
 <form method="post" id="form">
-  <p>Hostname: <input type="text" name="hostname" /></p>
-  <p>API key: <input type="text" name="key" /></p>
-  <p><input id="test" type="button" value="Test" enabled="false" /></p>
-  <p><input id="submit" type="submit" value="Add" onclick="maybeSubmit();" /></p>
+  <span class="form-input"><span class="form-label">Printer name</span><input type="text" name="name" /></span>
+  <span class="form-input"><span class="form-label">Printer API URL</span><input type="text" name="url" /></span>
+  <span class="form-input"><span class="form-label">API key</span><input type="text" name="api_key" /></span>
+  <span><input id="test" type="button" value="Test" enabled="false" /></span>
+  <span><input id="submit" type="submit" value="Add" onclick="maybeSubmit();" /></span>
   <input type="hidden" name="tested" value="false" />
 </form>
 
diff --git a/projects/tentacles/src/python/tentacles/templates/index.html.j2 b/projects/tentacles/src/python/tentacles/templates/index.html.j2
index 265a51d..bfc48a1 100644
--- a/projects/tentacles/src/python/tentacles/templates/index.html.j2
+++ b/projects/tentacles/src/python/tentacles/templates/index.html.j2
@@ -1,20 +1,5 @@
 {% extends "base.html.j2" %}
 {% block content %}
-  <div class="panel printers">
-    <h2>Printers</h2>
-    {% with printers = request.db.list_printers() %}
-    {% if printers %}
-    <ul>
-      {% for printer in printers %}
-      <li></li>
-      {% endfor %}
-    </ul>
-    {% else %}
-      No printers available. {% if request.is_admin %}<a href="/printers/add">Configure one!</a>{% else %}Ask the admin to configure one!{% endif %}
-    {% endif %}
-    {% endwith %}
-  </div>
-
   <div class="panel queue">
     <h2>Queue</h2>
     {% with jobs = request.db.list_jobs(uid=request.uid) %}
diff --git a/projects/tentacles/src/python/tentacles/templates/printers.html.j2 b/projects/tentacles/src/python/tentacles/templates/printers.html.j2
new file mode 100644
index 0000000..2bdf710
--- /dev/null
+++ b/projects/tentacles/src/python/tentacles/templates/printers.html.j2
@@ -0,0 +1,28 @@
+{% extends "base.html.j2" %}
+{% block content %}
+  <div class="panel printers">
+    <h2>Printers</h2>
+    {% with printers = request.db.list_printers() %}
+    {% if printers %}
+    <ul>
+      {% for printer in printers %}
+      {% with id, name, url, last_poll, status = printer %}
+      <li class="printer">
+        <span class="printer-name">{{name}}</span>
+        <span class="printer-url">{{url}}</span>
+        <span class="printer-status">{{status}}</span>
+        <span class="printer-date">{{last_poll}}</span>
+        {# FIXME: How should these action buttons work? #}
+        <a class="button" href="/printers/action?id={{id}}">Test</a>
+        <a class="button" href="/printers/edit?id={{id}}">Edit</a>
+        <a class="button" href="/printers/delete?id={{id}}">Remove</a>
+      </li>
+      {% endwith %}
+      {% endfor %}
+    </ul>
+    {% else %}
+      No printers available. {% if request.is_admin %}<a href="/printers/add">Configure one!</a>{% else %}Ask the admin to configure one!{% endif %}
+    {% endif %}
+    {% endwith %}
+  </div>
+{% endblock %}