Grotti 2.10.0: Orca-Senden, Slice-Ordner, ED-Strom, Hygiene.

Slice-Watch mit Ordnern/Filtern, Orca-Nachbearbeitung, Cockpit-Verlauf
mit ED, Zeitzone im Browser. Tasten-Grenze und README-API angeglichen,
toter Code entfernt, Requirements gepinnt.
This commit is contained in:
Dominic Gundel committed 2026-10-18 00:41:15 +02:00
1 parent efd0801cd1
commit 9e22cebb81
48 files changed
+2259 -422

No files matched your search

+67 -3
View File
@@ -190,7 +190,11 @@ def create_app(cfg):
if not user:
if wants_json() or path.startswith("/api/"):
return jsonify({"error": "Anmeldung nötig.", "code": "auth"}), 401
return redirect(url_for("login_page", next=path))
# Query behalten (z. B. /dateien?file=12 nach Orca-Upload)
next_path = path
if request.query_string:
next_path = path + "?" + request.query_string.decode("utf-8", "replace")
return redirect(url_for("login_page", next=next_path))
g.user = user
ensure_csrf()
@@ -490,6 +494,7 @@ def create_app(cfg):
"Anleitung-Oberflaeche",
"Anleitung-Wand",
"Anleitung-Dateien",
"Anleitung-Orca",
"Anleitung-ACE",
"Anleitung-Warteschlange",
"Anleitung-Material",
@@ -510,6 +515,7 @@ def create_app(cfg):
"Anleitung-Oberflaeche": "Oberfläche",
"Anleitung-Wand": "Druckerwand",
"Anleitung-Dateien": "Dateien senden",
"Anleitung-Orca": "Orca an Grotti",
"Anleitung-ACE": "ACE und Spulen",
"Anleitung-Warteschlange": "Warteschlange",
"Anleitung-Material": "Material und mehr",
@@ -695,8 +701,15 @@ def create_app(cfg):
alerts = data.get("alerts") if isinstance(data.get("alerts"), dict) else None
auto_queue = data.get("auto_queue") if "auto_queue" in data else None
fqdn = data.get("fqdn") if "fqdn" in data else None
watch_dir = data.get("watch_dir") if "watch_dir" in data else None
return jsonify(grotti_config.save_settings(
cfg, energy=energy, telegram=telegram, alerts=alerts, auto_queue=auto_queue, fqdn=fqdn,
cfg,
energy=energy,
telegram=telegram,
alerts=alerts,
auto_queue=auto_queue,
fqdn=fqdn,
watch_dir=watch_dir,
))
@app.post("/api/v1/telegram/test")
@@ -906,7 +919,7 @@ def create_app(cfg):
@app.get("/api/v1/dateien")
def file_list():
library.sync_watch(conn(), cfg, force=False)
return jsonify(store.list_files(conn()))
return jsonify(store.list_files(conn(), watch_dir=cfg.get("watch_dir")))
@app.post("/api/v1/dateien/neu-lesen")
def file_rescan():
@@ -914,6 +927,31 @@ def create_app(cfg):
result["metadaten"] = library.refresh_file_meta(conn(), force=True)
return jsonify(result)
@app.get("/api/v1/dateien/ordner")
def file_folders():
return jsonify({"folders": library.list_folders(cfg)})
@app.post("/api/v1/dateien/ordner")
def file_folder_create():
data = body()
folder = library.create_folder(cfg, data.get("folder"))
return jsonify({"folder": folder, "folders": library.list_folders(cfg)}), 201
@app.delete("/api/v1/dateien/ordner")
def file_folder_delete():
data = body()
folder = library.delete_folder(cfg, data.get("folder"))
return jsonify({"folder": folder, "folders": library.list_folders(cfg)})
@app.post("/api/v1/dateien/<int:file_id>/verschieben")
def file_move(file_id):
data = body()
folder = data.get("folder")
if folder is None:
folder = ""
moved = library.move_file(conn(), cfg, file_id, folder)
return jsonify(moved)
@app.post("/api/v1/dateien")
def file_upload():
upload = request.files.get("file")
@@ -1799,6 +1837,28 @@ def create_app(cfg):
def run_list():
return jsonify(store.list_runs(conn()))
@app.get("/api/v1/laeufe/aktuell")
def run_aktuell():
"""Aktueller oder letzter Temperatur-/ED-Verlauf fürs Cockpit (host=)."""
host = require_host(request.args.get("host", ""))
run = store.latest_run_for_host(conn(), host)
if not run:
return jsonify({"run": None, "samples": [], "energy": None})
run_id = run["id"]
samples = store.list_run_samples(conn(), run_id)
from grotti.cost import energy_estimate
energy = energy_estimate(run.get("duration_s"), cfg["energy"], samples)
if len(samples) > 180:
step = max(1, len(samples) // 120)
samples = samples[::step]
return jsonify({
"run": run,
"samples": samples,
"energy": energy,
"sample_count": int(run.get("_sample_count") or len(samples)),
})
@app.get("/api/v1/laeufe/<int:run_id>")
def run_get(run_id):
row = store.get_run_row(conn(), run_id)
@@ -1814,6 +1874,9 @@ def create_app(cfg):
file_row = store.get_file_row(conn(), run["file_id"]) if run.get("file_id") else None
project = store.get_project(conn(), run["project_id"]) if run.get("project_id") else None
samples = store.list_run_samples(conn(), run_id)
from grotti.cost import energy_estimate
energy = energy_estimate(run.get("duration_s"), cfg["energy"], samples)
# Anzeige auf ca. 1 Punkt / Minute verdichten
if len(samples) > 180:
step = max(1, len(samples) // 120)
@@ -1827,6 +1890,7 @@ def create_app(cfg):
"events": store.list_run_events(conn(), run_id),
"file": store.file_out(file_row) if file_row else None,
"project": project,
"energy": energy,
})
@app.post("/api/v1/laeufe/abgleich")