Automatiken als Ausloeser: Editor, Uebersicht, Loeschen

Gegenstueck zur Runner-Seite. Der Editor selbst braucht nichts: das
gerechnete Geraet "Automatiken" fuehrt jede Automatik als Messwert, und
der Geraetewaehler listet ohnehin Geraete und deren Messwerte. Neu sind
nur die Operatoren des Datentyps `elapsed` ("+", "ab +", "vor +") und
sein Eingabefeld.

Drei Stellen wissen trotzdem davon:

pruefeKreis() lehnt beim Speichern ab, was sich mittelbar selbst
ausloesen wuerde. Im Betrieb waere ein Kreis kaum zu bemerken - die
Sperrzeit begrenzt ihn auf eine Ausloesung je lockout_secs, und im
Protokoll sieht das aus wie eine Automatik, die halt oft laeuft.

deleteAutomation() fragt vorher. fk_cond_state steht auf ON DELETE
CASCADE, mit dem Messwert verschwaende also still die Bedingung des
Nachfolgers: aus "zehn Minuten nach dem Wecker UND es ist hell" wuerde
ein blosses "es ist hell", und der Rollladen fuehre ab morgen jeden Tag
bei Sonnenaufgang hoch. Bei der letzten Bedingung faengt der Runner das
ab, bei einer von zweien niemand. Zur Wahl stehen Abhaengen (Bedingung
raus, Nachfolger pausiert) und Mitloeschen; automatisch mitzuloeschen
waere die falsche Vorgabe.

Die Uebersicht rueckt Nachfolger unter ihren Ausloeser ein und zeigt sie
eingeklappt. Das ist nur die Darstellung - jeder Nachfolger bleibt eine
vollwertige Automatik mit eigener Pause, eigenen Rahmenbedingungen und
eigener Zeile im Protokoll. Sobald "gruppiert" auch "gehoert dazu"
hiesse, waere zu klaeren, wem die Pause gehoert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-09 22:51:42 +02:00
co-authored by Claude Opus 5
parent c6a879b424
commit b607d4a064
4 changed files with 474 additions and 11 deletions
+120 -2
View File
@@ -15,6 +15,12 @@
*
* Der Geraetekatalog kommt zusammen mit dem Datensatz im selben Dokument
* (siehe ajax/AutoAction.php), es gibt also keine Nachladerei je Zeile.
*
* Eine Automatik kann eine andere ausloesen. Dafuer steht hier auffallend
* wenig: das gerechnete Geraet "Automatiken" fuehrt jede Automatik als
* Messwert, und damit ist es fuer den Editor ein Geraet wie jedes andere -
* er listet ohnehin Geraete und deren Messwerte. Nur die Uebersicht weiss
* davon (toggleAutomationGroup) und das Loeschen (deleteAutomation).
*/
let autoModel = null;
@@ -800,6 +806,43 @@ function refreshAutomations(floor) {
});
}
/**
* Die von einer Automatik ausgeloesten Zeilen zeigen oder verbergen.
*
* Sie stehen schon in der Tabelle, nur mit d-none - das spart eine zweite
* Anfrage und haelt die Reihenfolge stabil, wenn zwischendurch etwas
* pausiert wird.
*/
function toggleAutomationGroup(id, knopf) {
const zeilen = Array.from(document.querySelectorAll(".auto-von-" + id));
if (!zeilen.length) return;
const aufklappen = zeilen[0].classList.contains("d-none");
if (aufklappen) {
zeilen.forEach(zeile => zeile.classList.remove("d-none"));
} else {
// Beim Zuklappen muessen auch die Enkel verschwinden. Sonst blieben sie
// sichtbar stehen, ohne dass noch zu sehen waere, woran sie haengen.
automationGruppeVerbergen(id);
}
pfeilStellen(knopf, aufklappen);
}
function automationGruppeVerbergen(id) {
document.querySelectorAll(".auto-von-" + id).forEach(zeile => {
zeile.classList.add("d-none");
const griff = zeile.querySelector("button[data-gruppe]");
if (griff) {
pfeilStellen(griff, false);
automationGruppeVerbergen(Number(griff.dataset.gruppe));
}
});
}
function pfeilStellen(knopf, offen) {
const pfeil = knopf && knopf.querySelector("i");
if (pfeil) pfeil.className = offen ? "bi bi-chevron-down" : "bi bi-chevron-right";
}
function toggleAutomation(id) {
automationRequest("toggle", { id: id }).then(antwort => {
if (antwort.error) alert(antwort.error);
@@ -807,12 +850,87 @@ function toggleAutomation(id) {
});
}
/**
* Loeschen - und vorher fragen, was mit den abhaengigen Automatiken wird.
*
* Ohne diese Frage verschwaende mit dem Ausloeser still die Bedingung des
* Nachfolgers (fk_cond_state steht auf ON DELETE CASCADE). Aus "zehn Minuten
* nach dem Wecker UND es ist hell" wuerde dann ein blosses "es ist hell" -
* eine andere Regel, die niemand angeordnet hat.
*
* Drei Antworten, deshalb kein confirm(): mitloeschen, abhaengen, abbrechen.
*/
function deleteAutomation(id, name) {
if (!confirm("Automatik \"" + name + "\" wirklich löschen?")) return;
automationRequest("delete", { id: id }).then(antwort => {
fetch("./ajax/AutoAction.php?action=followers&id=" + id, {
headers: { "Requested-With-Ajax": "ajax" }
})
.then(antwort => antwort.json())
.then(daten => {
const folgen = (daten && daten.followers) || [];
if (!folgen.length) {
if (confirm("Automatik \"" + name + "\" wirklich löschen?")) {
automationLoeschen(id, "abhaengen");
}
return;
}
frageNachfolger(id, name, folgen);
})
.catch(() => {
// Auch ohne Auskunft loeschbar - dann eben mit der schonenden Variante.
if (confirm("Automatik \"" + name + "\" wirklich löschen?")) {
automationLoeschen(id, "abhaengen");
}
});
}
function automationLoeschen(id, modus) {
automationRequest("delete", { id: id, mode: modus }).then(antwort => {
if (antwort.error) alert(antwort.error);
else refreshAutomations(null);
});
}
/** Der Dialog mit den drei Antworten. Wird bei Bedarf gebaut. */
function frageNachfolger(id, name, folgen) {
const alt = document.getElementById("modalAutoLoeschen");
if (alt) alt.remove();
const liste = folgen
.map(f => "<li>" + textSicher(f.name)
+ (f.enabled ? "" : " <span class='text-body-secondary'>(pausiert)</span>")
+ "</li>")
.join("");
const wieViele = folgen.length === 1
? "Eine Automatik wird" : folgen.length + " Automatiken werden";
const huelle = document.createElement("div");
huelle.innerHTML =
"<div class='modal fade' id='modalAutoLoeschen' tabindex='-1'>"
+ "<div class='modal-dialog modal-dialog-centered'><div class='modal-content'>"
+ "<div class='modal-header'><h5 class='modal-title'>„" + textSicher(name) + "“ löschen</h5>"
+ "<button type='button' class='btn-close' data-bs-dismiss='modal'></button></div>"
+ "<div class='modal-body'><p>" + wieViele + " von dieser hier ausgelöst:</p>"
+ "<ul>" + liste + "</ul>"
+ "<p class='mb-0 text-body-secondary small'>Beim Abhängen verlieren sie nur diese "
+ "eine Bedingung. Damit sie danach nicht mit einer schwächeren Regel weiterlaufen, "
+ "werden sie pausiert.</p></div>"
+ "<div class='modal-footer'>"
+ "<button type='button' class='btn btn-secondary' data-bs-dismiss='modal'>Abbrechen</button>"
+ "<button type='button' class='btn btn-warning' id='btnAutoAbhaengen'>Abhängen und pausieren</button>"
+ "<button type='button' class='btn btn-danger' id='btnAutoMit'>Alle löschen</button>"
+ "</div></div></div></div>";
const knoten = huelle.firstChild;
document.body.appendChild(knoten);
const dialog = new bootstrap.Modal(knoten);
const waehlen = modus => {
dialog.hide();
automationLoeschen(id, modus);
};
knoten.querySelector("#btnAutoAbhaengen").addEventListener("click", () => waehlen("abhaengen"));
knoten.querySelector("#btnAutoMit").addEventListener("click", () => waehlen("mitloeschen"));
knoten.addEventListener("hidden.bs.modal", () => knoten.remove());
dialog.show();
}
document.addEventListener("DOMContentLoaded", () => refreshAutomations(null));