Many things

Signed-off-by: AKP <tom@tdpain.net>
This commit is contained in:
akp 2022-11-08 23:26:11 +00:00
parent 8028d82e94
commit f2f1f9b5e6
No known key found for this signature in database
GPG key ID: AA5726202C8879B7
11 changed files with 246 additions and 54 deletions

View file

@ -4,6 +4,7 @@ from typing import *
import requests
from bs4 import BeautifulSoup
from cachetools import cached, TTLCache
class ScraperError(ValueError):
@ -52,6 +53,7 @@ class CircuitScraper:
return CircuitScraper._base_url + f"/?site={site_id}"
@staticmethod
@cached(cache=TTLCache(maxsize=64, ttl=30))
def get_site_machine_states(site_id: str) -> List[Machine]:
site_url = CircuitScraper._get_site_url(site_id)
@ -120,3 +122,13 @@ class CircuitScraper:
machines.append(machine)
return machines
@staticmethod
def get_machine(site_id: str, machine_number: str) -> Optional[Machine]:
all_machines = CircuitScraper.get_site_machine_states(site_id)
res: Optional[Machine] = None
for machine in all_machines:
if machine.number == machine_number:
res = machine
break
return res

View file

@ -0,0 +1,26 @@
const show = (e) => { e.style.display = "block" }
const hide = (e) => { e.style.display = "none" }
const workingIndicator = document.getElementById("working")
const successIndicator = document.getElementById("success")
const errorIndicator = document.getElementById("error")
const errorIndicatorText = document.getElementById("error_text")
function showError(text) {
errorIndicatorText.innerText = text
show(errorIndicator)
}
function showSuccessHTML(html) {
const elem = document.createElement("p")
elem.innerHTML = html
showSuccessElement(elem)
}
function showSuccessElement(elem) {
successIndicator.appendChild(elem)
show(successIndicator)
}
const state = {}

View file

@ -0,0 +1,19 @@
const selectMachineBlock = document.getElementById("select_machine")
const selectMachineSelection = document.getElementById("select_machine__selection")
function addMachineToSelection(machineObject) {
const elem = document.createElement("option")
elem.value = machineObject["number"]
elem.innerText = `${machineObject["type"]} ${machineObject["number"]}`
selectMachineSelection.appendChild(elem)
}
{
const nextButton = document.getElementById("select_machine__next_button")
nextButton.addEventListener("click", () => {
state.machine = selectMachineSelection.options[selectMachineSelection.selectedIndex].value
hide(selectMachineBlock)
show(submitBlock)
})
}

View file

@ -0,0 +1,51 @@
const selectSiteBlock = document.getElementById("select_site");
{
const nextButton = document.getElementById("select_site__next_button")
const siteIDInput = document.getElementById("select_site__site_id_input")
nextButton.addEventListener("click", () => {
hide(errorIndicator)
hide(selectSiteBlock)
if (siteIDInput.value === "") {
showError("Missing site ID")
show(selectSiteBlock)
return
}
show(workingIndicator)
hide(selectSiteBlock)
fetch(`/api/v1/machines/${siteIDInput.value.trim()}`)
.then(async (response) => {
hide(workingIndicator)
const responseBody = await response.json()
if (!response.ok) {
showError(responseBody.message)
show(selectSiteBlock)
return
}
let numberInUse = 0
for (let i = 0; i < responseBody.length; i += 1) {
const machineObject = responseBody[i]
if (machineObject["state"] === "IN_USE") {
addMachineToSelection(machineObject)
numberInUse += 1
}
}
if (numberInUse === 0) {
showError("No machines in use.")
return
}
state.siteID = siteIDInput.value.trim()
hide(selectSiteBlock)
show(selectMachineBlock)
})
})
}

View file

@ -1,17 +0,0 @@
{
const block = document.getElementById("select_site");
const nextButton = document.getElementById("select_site__next_button")
const siteIDInput = document.getElementById("select_site__site_id_input")
nextButton.addEventListener("click", function () {
hideErrorIndicator()
if (siteIDInput.value === "") {
showError("Missing site ID")
return
}
showWorkingIndicator()
hideWorkingIndicator()
})
}

View file

@ -0,0 +1,44 @@
const submitBlock = document.getElementById("submit")
{
const nextButton = document.getElementById("submit__next_button")
const ntfyTopicInput = document.getElementById("submit__ntfy_topic")
nextButton.addEventListener("click", () => {
hide(errorIndicator)
hide(submitBlock)
show(workingIndicator)
const formValues = new FormData()
formValues.set("machine_number", state.machine)
{
const v = ntfyTopicInput.value.trim()
if (v !== "") {
formValues.set("ntfy_topic", ntfyTopicInput.value.trim())
}
}
fetch(`/api/v1/machines/${state.siteID}/watch`, {
method: "POST",
body: formValues,
})
.then(async (response) => {
hide(workingIndicator)
const responseBody = await response.json()
if (!response.ok) {
showError(responseBody.message)
show(submitBlock)
return
}
showSuccessHTML(`Successfully registered using ntfy topic <code>${responseBody["ntfy_topic"]}</code>`)
const anchor = document.createElement("a")
anchor.href = `https://ntfy.sh/${responseBody["ntfy_topic"]}`
anchor.innerText = "Click here to go to the ntfy topic"
anchor.target = "_blank"
showSuccessElement(anchor)
})
})
}

View file

@ -1,25 +0,0 @@
const workingIndicator = document.getElementById("working")
function showWorkingIndicator() {
workingIndicator.style.display = "block";
}
function hideWorkingIndicator() {
workingIndicator.style.display = "none";
}
const errorIndicator = document.getElementById("error")
const errorIndicatorText = document.getElementById("error_text")
function showErrorIndicator() {
errorIndicator.style.display = "block"
}
function hideErrorIndicator() {
errorIndicator.style.display = "none"
}
function showError(text) {
errorIndicatorText.innerText = text
showErrorIndicator()
}

View file

@ -9,12 +9,6 @@
</head>
<body>
<nav class="navbar navbar-dark bg-dark">
<div class="container-fluid">
<span class="navbar-brand mb-0 h1">CircuitBodge</span>
</div>
</nav>
<div class="container pt-4">
<h2>CircuitBodge</h2>
@ -36,14 +30,30 @@
</div>
<div id="select_machine" style="display:none;">
<div class="mb-3">
<label for="select_machine__selection" class="form-label">Select a machine</label>
<select class="form-select" id="select_machine__selection"></select>
</div>
<button type="button" class="btn btn-primary" id="select_machine__next_button">Next</button>
</div>
<div id="submit" style="display:none;">
<div class="mb-3">
<label for="submit__ntfy_topic" class="form-label">ntfy.sh topic</label>
<input type="text" class="form-control" id="submit__ntfy_topic" placeholder="Randomly generated">
</div>
<button type="button" class="btn btn-primary" id="submit__next_button">Next</button>
</div>
<div id="success" style="display:none;">
<p id="success__text"></p>
</div>
</div>
<script src="/static/util.js"></script>
<script src="/static/siteSelect.js"></script>
<script src="/static/main.js"></script>
<script src="/static/selectMachine.js"></script>
<script src="/static/selectSite.js"></script>
<script src="/static/submit.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3" crossorigin="anonymous"></script>
</body>

View file

@ -5,8 +5,15 @@ from threading import Lock
from typing import *
import flask
import requests
from circuit_scraper import CircuitScraper, Machine, ScraperError, MachineState
from circuit_scraper import (
CircuitScraper,
Machine,
ScraperError,
MachineState,
MachineType,
)
def new(debug=False) -> flask.Flask:
@ -82,11 +89,54 @@ def _api_register_watcher(site_id: str) -> Union[any, Tuple[any, int]]:
if machine_number is None:
return flask.jsonify({"ok": False, "message": "missing machine_number"}), 400
machine = CircuitScraper.get_machine(site_id, machine_number)
if machine is None:
return (
flask.jsonify(
{
"ok": False,
"message": "invalid site_id and machine_number combination",
}
),
400,
)
if machine.state != MachineState.InUse:
return (
flask.jsonify(
{
"ok": False,
"message": "Machine not in use",
}
),
400,
)
job_lock.acquire()
ws = WatchState(site_id, ntfy_topic, machine_number)
jobs.append(ws)
job_lock.release()
time_remaining_text: str
if machine.minutes_remaining < 0:
time_remaining_text = (
f"It was due to finish {machine.minutes_remaining} minutes ago."
)
else:
time_remaining_text = (
f"It's due to finish in {machine.minutes_remaining} minutes."
)
requests.post(
f"https://ntfy.sh/{ws.ntfy_topic}",
data=f"Topic registered to {machine.type.value.lower()} {ws.machine_number} at site {site_id}. "
+ time_remaining_text,
headers={
"Title": "Machine registered",
"Tags": "white_check_mark",
},
)
return flask.jsonify(ws), 200
@ -142,8 +192,20 @@ def _api_run_watcher() -> Union[any, Tuple[any, int]]:
continue
if target_machine.state == MachineState.Completed:
# TODO: Notify ntfy
print("DONE " + str(ws))
requests.post(
f"https://ntfy.sh/{ws.ntfy_topic}",
data=f"{target_machine.type} {target_machine.number} is finished!",
headers={
"Title": (
"Washing"
if target_machine.type == MachineType.Washer
else "Drying"
)
+ " completed!",
"Priority": "urgent",
"Tags": "tada",
},
)
completed_items.append(i)
continue

11
poetry.lock generated
View file

@ -13,6 +13,14 @@ soupsieve = ">1.2"
html5lib = ["html5lib"]
lxml = ["lxml"]
[[package]]
name = "cachetools"
version = "5.2.0"
description = "Extensible memoizing collections and decorators"
category = "main"
optional = false
python-versions = "~=3.7"
[[package]]
name = "certifi"
version = "2022.9.24"
@ -192,10 +200,11 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "flake8
[metadata]
lock-version = "1.1"
python-versions = "^3.8"
content-hash = "28f817d195ff20de983a656a216fbdaac203af52239ca31ed0bf9676eee92bb6"
content-hash = "00993ecc54f6327f8e58e6b91ce934b966e97c8db4bdb2c7a973c4101b20cb1c"
[metadata.files]
beautifulsoup4 = []
cachetools = []
certifi = []
charset-normalizer = []
click = []

View file

@ -10,6 +10,7 @@ python = "^3.8"
requests = "^2.28.1"
beautifulsoup4 = "^4.11.1"
Flask = "^2.2.2"
cachetools = "^5.2.0"
[tool.poetry.dev-dependencies]