Einstellungsseite: Reiter, Kachelwerte und Geraeteliste
Die Einstellungen bestanden aus einer einzigen langen Spalte mit den Preistabellen. Sie bekommt Reiter, und darin zwei neue Flaechen: was die Kacheln der Startseite anzeigen, und welche Geraete es ueberhaupt gibt. Reiter gab es vorher dreimal in drei Ausfuehrungen - die Etagen ueber den Automatismen mit eigenem switchTab(), die Geraetegruppen im Raum-Modal mit eigener CSS-Klasse, und die Einstellungsseite noch einmal anders. Jetzt baut restricted/reiter.php alle drei; umgeschaltet wird von Bootstrap, den offenen Reiter merkt reiterMerken() in der Adresse. Kachelwerte: was auf einer Kachel steht, stand fest in rooms.php. Die neue Tabelle kachelWerte (solarLog_kacheln.sql) ueberschreibt das je Raum, die Maske waehlt die Messwerte aus demselben Katalog wie der Automatik-Editor - nach Etage sortiert, die Geraete des eigenen Raums oben. Fehlt die Tabelle, zeigt jede Kachel weiter ihre Vorgabe. Geraete: die Zuordnung Geraet -> Raum war ein Modal, erreichbar ueber ein Zahnrad neben den Automatismen. Sie ist eine Einstellung und steht jetzt dort, zusammen mit dem, was fehlte: seit wann ein Geraet nichts mehr meldet, in welchen Automatiken es steckt, und ein Loeschknopf. Letzterer ist noetig, weil der Suchlauf nichts loescht - er legt an und frischt auf, damit die Automatiken ihre Zustands-Ids behalten; ein abgebautes Geraet bliebe sonst fuer immer stehen. Geloescht wird nur, was in keiner Automatik vorkommt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,11 +9,9 @@
|
||||
* GET ?action=editor&id=N Editor fuer eine vorhandene Automatik
|
||||
* GET ?action=editor&floor=OG Editor fuer eine neue Automatik
|
||||
* GET ?action=list&floor=OG Tabelle fuer die Karte "Automatismen"
|
||||
* GET ?action=rooms Zuordnung Geraet -> Raum zum Bearbeiten
|
||||
* POST ?action=save JSON-Rumpf, legt an oder ueberschreibt
|
||||
* POST ?action=delete {"id": N}
|
||||
* POST ?action=toggle {"id": N} - pausieren / fortsetzen
|
||||
* POST ?action=saveRooms {"<actor_id>": "OG/Bad", ...}
|
||||
*/
|
||||
|
||||
require_once("../helper.php");
|
||||
@@ -51,8 +49,6 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
jsonAntwort(["ok" => true]);
|
||||
case "toggle":
|
||||
jsonAntwort(["enabled" => toggleAutomation(intval($body["id"] ?? 0))]);
|
||||
case "saveRooms":
|
||||
jsonAntwort(["gespeichert" => saveRooms($body["rooms"] ?? [])]);
|
||||
default:
|
||||
jsonAntwort(["error" => "Unbekannte Aktion."], 400);
|
||||
}
|
||||
@@ -130,73 +126,6 @@ if ($action === "list") {
|
||||
exit;
|
||||
}
|
||||
|
||||
// --- Geraete den Raeumen zuordnen ---------------------------------------
|
||||
if ($action === "rooms") {
|
||||
$labels = geraeteBeschriftungen();
|
||||
$raeume = geraeteRaeume();
|
||||
$vorschlag = raumVorschlaege();
|
||||
$auswahl = raumListe();
|
||||
|
||||
$res = meshDb()->query("SELECT id, type FROM actors");
|
||||
$geraete = [];
|
||||
while ($row = $res->fetch_assoc()) {
|
||||
$id = intval($row["id"]);
|
||||
$geraete[] = [
|
||||
"id" => $id,
|
||||
"label" => $labels[$id] ?? "?",
|
||||
"type" => $row["type"],
|
||||
"room" => $raeume[$id] ?? null,
|
||||
"tipp" => $vorschlag[$id] ?? null,
|
||||
];
|
||||
}
|
||||
// Unzugeordnete nach oben: das ist die Arbeit, die noch zu tun ist.
|
||||
usort($geraete, function ($a, $b) {
|
||||
$offen = (int)!empty($b["room"]) - (int)!empty($a["room"]);
|
||||
return $offen !== 0 ? $offen : strcoll($a["label"], $b["label"]);
|
||||
});
|
||||
|
||||
$offen = count(array_filter($geraete, function ($g) { return empty($g["room"]); }));
|
||||
$tipps = count(array_filter($geraete, function ($g) {
|
||||
return empty($g["room"]) && !empty($g["tipp"]);
|
||||
}));
|
||||
|
||||
echo "<div class='p-2'>";
|
||||
echo "<p class='small text-body-secondary'>Die Zuordnung ordnet die Geräteliste im Editor "
|
||||
. "nach Räumen statt nach Geräteart. Sie wird von Hand vergeben und bleibt bei "
|
||||
. "einem neuen Geräte-Suchlauf erhalten.</p>";
|
||||
if ($tipps) {
|
||||
echo "<div class='alert alert-secondary py-2 small d-flex justify-content-between align-items-center'>"
|
||||
. "<span>" . $tipps . " Gerät(e) verraten ihren Raum im MQTT-Topic.</span>"
|
||||
. "<button type='button' class='btn btn-outline-secondary btn-sm' id='btnRoomTipps'>"
|
||||
. "Vorschläge übernehmen</button></div>";
|
||||
}
|
||||
echo "<div class='table-responsive' style='max-height:60vh;overflow-y:auto'>";
|
||||
echo "<table class='table table-sm table-hover align-middle'><thead><tr>"
|
||||
. "<th>Gerät</th><th class='d-none d-sm-table-cell'>Art</th><th>Raum</th>"
|
||||
. "</tr></thead><tbody>";
|
||||
foreach ($geraete as $g) {
|
||||
echo "<tr" . (empty($g["room"]) ? " class='table-active'" : "") . ">";
|
||||
echo "<td>" . htmlspecialchars($g["label"]) . "</td>";
|
||||
echo "<td class='d-none d-sm-table-cell small text-body-secondary'>"
|
||||
. htmlspecialchars($g["type"]) . "</td>";
|
||||
echo "<td><select class='form-select form-select-sm' data-actor='" . $g["id"] . "'"
|
||||
. ($g["tipp"] ? " data-tipp='" . htmlspecialchars($g["tipp"], ENT_QUOTES) . "'" : "")
|
||||
. ">";
|
||||
echo "<option value=''" . (empty($g["room"]) ? " selected" : "") . ">–</option>";
|
||||
foreach ($auswahl as $raum) {
|
||||
echo "<option value='" . htmlspecialchars($raum["key"], ENT_QUOTES) . "'"
|
||||
. ($g["room"] === $raum["key"] ? " selected" : "") . ">"
|
||||
. htmlspecialchars($raum["label"]) . "</option>";
|
||||
}
|
||||
echo "</select></td></tr>";
|
||||
}
|
||||
echo "</tbody></table></div>";
|
||||
echo "<p class='small text-body-secondary mb-0'>" . $offen . " von " . count($geraete)
|
||||
. " Geräten noch ohne Raum.</p>";
|
||||
echo "</div>";
|
||||
exit;
|
||||
}
|
||||
|
||||
// --- Editor --------------------------------------------------------------
|
||||
$id = intval($_GET["id"] ?? 0);
|
||||
$auto = $id > 0 ? loadAutomation($id) : null;
|
||||
|
||||
+70
-2
@@ -4,14 +4,22 @@
|
||||
*
|
||||
* GET ?action=list&reihe=strom Zeitraeume einer Preistabelle
|
||||
* POST ?action=save {"reihe": "strom", "zeilen": [ ... ]}
|
||||
* GET ?action=kacheln Anzeigewerte aller Home-Kacheln
|
||||
* POST ?action=kacheln-save {"floor": "OG", "room": "bad",
|
||||
* "werte": [ ... ] oder null}
|
||||
* GET ?action=geraete Geraete mit Raum, Verwendung und Alter
|
||||
* POST ?action=geraete-raeume {"rooms": {"12": "OG/Bad", ...}}
|
||||
* POST ?action=geraet-loeschen {"id": 12}
|
||||
*
|
||||
* Ein reiner Verteiler: geprueft und geschrieben wird in
|
||||
* restricted/costs.php, damit Maske und Endpunkt dieselbe Wahrheit ueber
|
||||
* einen Zeitraum haben.
|
||||
* restricted/costs.php bzw. restricted/kacheln.php, damit Maske und Endpunkt
|
||||
* dieselbe Wahrheit ueber einen Zeitraum oder eine Kachel haben.
|
||||
*/
|
||||
|
||||
require_once("../helper.php");
|
||||
require_once("../restricted/costs.php");
|
||||
require_once("../restricted/kacheln.php");
|
||||
require_once("../restricted/automations.php");
|
||||
|
||||
if (!checkLogin()) {
|
||||
http_response_code(403);
|
||||
@@ -43,6 +51,41 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
"gespeichert" => $anzahl,
|
||||
"zeilen" => preiseLaden($body["reihe"]),
|
||||
]);
|
||||
|
||||
case "kacheln-save":
|
||||
/*
|
||||
* Fehlt "werte" oder steht dort null, faellt die Kachel auf
|
||||
* ihre Vorgabe zurueck - dann kommt diese zurueck, damit die
|
||||
* Maske zeigt, was jetzt gilt, statt zu leeren Zeilen.
|
||||
*/
|
||||
$floor = $body["floor"] ?? "";
|
||||
$room = $body["room"] ?? "";
|
||||
$werte = array_key_exists("werte", $body) ? $body["werte"] : null;
|
||||
$gespeichert = kachelWerteSpeichern($floor, $room, $werte);
|
||||
|
||||
$vorgabe = [];
|
||||
foreach (allRooms() as $r) {
|
||||
if ($r["floor"] === $floor && $r["id"] === $room) {
|
||||
$vorgabe = kachelVorgabe($r);
|
||||
}
|
||||
}
|
||||
jsonAntwort([
|
||||
"eigen" => $gespeichert !== null,
|
||||
"werte" => $gespeichert !== null ? $gespeichert : $vorgabe,
|
||||
]);
|
||||
case "geraete-raeume":
|
||||
jsonAntwort(["gespeichert" => saveRooms($body["rooms"] ?? [])]);
|
||||
|
||||
case "geraet-loeschen":
|
||||
/*
|
||||
* Loeschen ist endgueltig - abgelehnt wird es, wenn eine
|
||||
* Automatik auf das Geraet zeigt (siehe geraetLoeschen()).
|
||||
* Zurueck kommt die neue Liste, damit die Maske nicht raten
|
||||
* muss, was jetzt gilt.
|
||||
*/
|
||||
$name = geraetLoeschen($body["id"] ?? 0);
|
||||
jsonAntwort(["geloescht" => $name, "geraete" => geraeteListe()]);
|
||||
|
||||
default:
|
||||
jsonAntwort(["error" => "Unbekannte Aktion."], 400);
|
||||
}
|
||||
@@ -53,6 +96,31 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
}
|
||||
}
|
||||
|
||||
if ($action === "kacheln") {
|
||||
try {
|
||||
jsonAntwort([
|
||||
"kacheln" => kachelListe(),
|
||||
"symbole" => kachelSymbole(),
|
||||
// Auswahlliste je Geraet - dieselbe Quelle wie im Automatik-Editor.
|
||||
"katalog" => kachelKatalog(),
|
||||
// Fuer die Eingabe von Hand, wenn etwas nicht im Katalog steht.
|
||||
"topics" => kachelTopicVorschlaege(),
|
||||
"tabelleDa" => kachelTabelleDa(),
|
||||
"max" => KACHEL_MAX,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
jsonAntwort(["error" => "Kacheln nicht abrufbar: " . $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
if ($action === "geraete") {
|
||||
try {
|
||||
jsonAntwort(["geraete" => geraeteListe(), "raeume" => raumListe()]);
|
||||
} catch (Throwable $e) {
|
||||
jsonAntwort(["error" => "Geräte nicht abrufbar: " . $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
if ($action === "list") {
|
||||
try {
|
||||
jsonAntwort(["zeilen" => preiseLaden($_GET["reihe"] ?? "")]);
|
||||
|
||||
Reference in New Issue
Block a user