Many things
Signed-off-by: AKP <tom@tdpain.net>
This commit is contained in:
parent
8028d82e94
commit
f2f1f9b5e6
11 changed files with 246 additions and 54 deletions
|
@ -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
|
||||
|
|
26
circuit-laundry-notifier/static/main.js
Normal file
26
circuit-laundry-notifier/static/main.js
Normal 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 = {}
|
19
circuit-laundry-notifier/static/selectMachine.js
Normal file
19
circuit-laundry-notifier/static/selectMachine.js
Normal 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)
|
||||
})
|
||||
}
|
51
circuit-laundry-notifier/static/selectSite.js
Normal file
51
circuit-laundry-notifier/static/selectSite.js
Normal 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)
|
||||
})
|
||||
})
|
||||
}
|
|
@ -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()
|
||||
})
|
||||
}
|
44
circuit-laundry-notifier/static/submit.js
Normal file
44
circuit-laundry-notifier/static/submit.js
Normal 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)
|
||||
})
|
||||
})
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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
11
poetry.lock
generated
|
@ -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 = []
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
|
Reference in a new issue