diff --git a/projects/tentacles/src/python/gcode.py b/projects/tentacles/src/python/gcode.py index 4131aef..f1f5d9c 100644 --- a/projects/tentacles/src/python/gcode.py +++ b/projects/tentacles/src/python/gcode.py @@ -1,10 +1,11 @@ #!/usr/bin/env python3 from pathlib import Path -from attrs import define +import re from typing import Optional, Tuple -import re +from attrs import define + OPTION_PATTERN = re.compile("; (?P[a-z0-9_]+) = (?P.*?)\n") diff --git a/projects/tentacles/src/python/tentacles/db.py b/projects/tentacles/src/python/tentacles/db.py index 5dab149..b267fd3 100644 --- a/projects/tentacles/src/python/tentacles/db.py +++ b/projects/tentacles/src/python/tentacles/db.py @@ -12,9 +12,12 @@ from time import sleep from types import GeneratorType, new_class from typing import Optional -from aiosql.aiosql import _make_driver_adapter as get_adapter -from aiosql.query_loader import QueryLoader +from aiosql.aiosql import ( + _make_driver_adapter as get_adapter, +) from aiosql.queries import Queries +from aiosql.query_loader import QueryLoader + _sqlite = get_adapter("sqlite3") _loader = QueryLoader(_sqlite, None) @@ -211,6 +214,10 @@ class Db(Queries): super().refresh_key(kid=kid, expiration=(datetime.now() + ttl).isoformat()) + def start_job(self, *, jid: int): + super().start_job(jid=jid) + super().update_job_status(jid=jid, status="running") + def finish_job(self, *, jid: int, state: str, message: Optional[str] = None): super().finish_job(jid=jid, state=state, message=message) diff --git a/projects/tentacles/src/python/tentacles/sql/jobs.sql b/projects/tentacles/src/python/tentacles/sql/jobs.sql index 4dcf0b0..777b7f6 100644 --- a/projects/tentacles/src/python/tentacles/sql/jobs.sql +++ b/projects/tentacles/src/python/tentacles/sql/jobs.sql @@ -79,6 +79,10 @@ SELECT , fa.nozzle_diameter , fa.filament_id , (SELECT name FROM filament WHERE id = fa.filament_id) AS filament_name + , (SELECT name FROM job_statuses WHERE id = j.status_id) AS status + , j.started_at + , j.cancelled_at + , j.finished_at FROM jobs j INNER JOIN files f ON j.file_id = f.id @@ -86,6 +90,7 @@ LEFT JOIN file_analysis fa ON fa.file_id = f.id WHERE finished_at IS NULL + AND cancelled_at IS NULL AND (:uid IS NULL OR j.user_id = :uid) AND f.id IS NOT NULL ; @@ -103,12 +108,17 @@ LIMIT 1 -- name: list-job-history SELECT - * + j.* + , fa.id as analysis_id , (SELECT name FROM job_statuses WHERE id = j.status_id) AS `status` FROM jobs j +INNER JOIN files f + ON j.file_id = f.id +LEFT JOIN file_analysis fa + ON fa.file_id = f.id WHERE finished_at IS NOT NULL - AND (:uid IS NULL OR user_id = :uid) + AND (:uid IS NULL OR j.user_id = :uid) ORDER BY datetime(finished_at) DESC LIMIT 25 @@ -201,3 +211,10 @@ WHERE user_id = :uid AND file_id = :fid ; + +-- name: update-job-status! +UPDATE jobs + SET status_id = (SELECT id FROM job_statuses WHERE name = :status) +WHERE + id = :jid +; diff --git a/projects/tentacles/src/python/tentacles/static/css/style.scss b/projects/tentacles/src/python/tentacles/static/css/style.scss index 934724c..1055d33 100644 --- a/projects/tentacles/src/python/tentacles/static/css/style.scss +++ b/projects/tentacles/src/python/tentacles/static/css/style.scss @@ -54,6 +54,10 @@ label { margin-bottom: 20px; } +.mr-auto { + margin-right: auto; +} + .u-flex1 { flex: 1; } diff --git a/projects/tentacles/src/python/tentacles/static/css/tirefire/_dots.scss b/projects/tentacles/src/python/tentacles/static/css/tirefire/_dots.scss index 74b82be..e9b4a4d 100644 --- a/projects/tentacles/src/python/tentacles/static/css/tirefire/_dots.scss +++ b/projects/tentacles/src/python/tentacles/static/css/tirefire/_dots.scss @@ -22,6 +22,10 @@ background-color: $secondary_green; } +.dot.analyzing { + background-color: $black; +} + .dot.queued { background-color: $secondary_blue; } @@ -43,7 +47,7 @@ background-color: $red; } -.dot--basic { +.dot--active { animation: blink 2s infinite; } diff --git a/projects/tentacles/src/python/tentacles/templates/files_list.html.j2 b/projects/tentacles/src/python/tentacles/templates/files_list.html.j2 index eafd93a..88f0085 100644 --- a/projects/tentacles/src/python/tentacles/templates/files_list.html.j2 +++ b/projects/tentacles/src/python/tentacles/templates/files_list.html.j2 @@ -6,6 +6,8 @@
{{ file.filename }} +
+
{{ file.print_successes }} {{ file.print_failures }}
diff --git a/projects/tentacles/src/python/tentacles/templates/jobs_history.html.j2 b/projects/tentacles/src/python/tentacles/templates/jobs_history.html.j2 index 68441ed..b12a713 100644 --- a/projects/tentacles/src/python/tentacles/templates/jobs_history.html.j2 +++ b/projects/tentacles/src/python/tentacles/templates/jobs_history.html.j2 @@ -5,7 +5,7 @@ {% for job in jobs %}
-
+
{{ctx.db.fetch_file(ctx.uid, job.file_id).filename or "it's a secret"}}
@@ -25,7 +25,7 @@
-
+
diff --git a/projects/tentacles/src/python/tentacles/templates/jobs_list.html.j2 b/projects/tentacles/src/python/tentacles/templates/jobs_list.html.j2 index 47baa55..058f1bd 100644 --- a/projects/tentacles/src/python/tentacles/templates/jobs_list.html.j2 +++ b/projects/tentacles/src/python/tentacles/templates/jobs_list.html.j2 @@ -18,7 +18,7 @@ {% if job.started_at %}
- {{ (datetime.now() - datetime.fromisoformat(job.started_at)) }} + {{ (datetime.utcnow() - datetime.fromisoformat(job.started_at)) }}
{% else %}
@@ -34,7 +34,7 @@
-
+
diff --git a/projects/tentacles/src/python/tentacles/templates/macros.html.j2 b/projects/tentacles/src/python/tentacles/templates/macros.html.j2 index 8d238f8..f6eafc1 100644 --- a/projects/tentacles/src/python/tentacles/templates/macros.html.j2 +++ b/projects/tentacles/src/python/tentacles/templates/macros.html.j2 @@ -33,10 +33,19 @@ {% endmacro %} {% macro job_state(job) %} -{{ 'queued' if (not job.finished_at and not job.printer_id and not job.cancelled_at) else - 'running' if (not job.finished_at and job.printer_id and not job.cancelled_at) else - 'cancelling' if (not job.finished_at and job.cancelled_at) else - job.status }} +{{ job.status if job.finished_at else + 'cancelling' if job.cancelled_at else + 'analyzing' if not job.analysis_id else + 'queued' if (not job.printer_id and not job.started_at) else + 'running' if job.printer_id else + job.status +}} +{% endmacro %} + +{% macro job_active(job) %} +{{ + 'dot--active' if not job.finished_at else '' +}} {% endmacro %} {# #################################################################################################### #} diff --git a/projects/tentacles/src/python/tentacles/workers.py b/projects/tentacles/src/python/tentacles/workers.py index 5990575..0b97635 100644 --- a/projects/tentacles/src/python/tentacles/workers.py +++ b/projects/tentacles/src/python/tentacles/workers.py @@ -7,10 +7,11 @@ Supporting the core app with asynchronous maintenance tasks. Mostly related to monitoring and managing Printer state. """ -import os from contextlib import closing +from datetime import datetime, timedelta from functools import cache import logging +import os from pathlib import Path from pprint import pformat from typing import Callable @@ -18,9 +19,8 @@ from urllib import parse as urlparse from cherrypy.process.plugins import Monitor from fastmail import FastMailSMTP +from flask import Flask as App, render_template from gcode import analyze_gcode_file -from flask import Flask as App -from flask import render_template from octorest import OctoRest as _OR from requests import Response from requests.exceptions import ( @@ -256,8 +256,12 @@ def pull_jobs(app: App, db: Db) -> None: printer_state = {"disconnected": True, "error": True} if job_state.get("progress", {}).get("completion", 0.0) == 100.0: - log.info(f"Job {job.id} has succeeded") - db.finish_job(jid=job.id, state="success") + # Debounce jobs which JUST started from immediately completing due to a data race + start_date = datetime.fromisoformat(job.started_at) + runtime = datetime.utcnow() - start_date + if runtime >= timedelta(seconds=15): # 3 polling cycles + log.info(f"Job {job.id} has succeeded") + db.finish_job(jid=job.id, state="success") elif printer_state.get("error"): log.warn(f"Job {job.id} has failed") diff --git a/projects/tentacles/test/python/conftest.py b/projects/tentacles/test/python/conftest.py index 3121717..fcb2b08 100644 --- a/projects/tentacles/test/python/conftest.py +++ b/projects/tentacles/test/python/conftest.py @@ -6,6 +6,7 @@ import logging import pytest from tentacles.db import Db + # FIXME: Should this be an autouse fixture? Maybe. Doesn't buy much tho. logging.addLevelName(logging.DEBUG - 5, "TRACE") logging.TRACE = logging.DEBUG - 5 diff --git a/projects/tentacles/test/python/test_gcode.py b/projects/tentacles/test/python/test_gcode.py index 5f1164a..16d8f78 100644 --- a/projects/tentacles/test/python/test_gcode.py +++ b/projects/tentacles/test/python/test_gcode.py @@ -3,10 +3,10 @@ import re from gcode import ( - parse_prusa_config_str, - OPTION_PATTERN, analyze_gcode_str, GcodeAnalysis, + OPTION_PATTERN, + parse_prusa_config_str, )