Dasselbe <input type="time"> wie von/bis daneben, nur als Dauer gelesen -
"03:30" tippt sich leichter als "210" und steht in derselben Zeile wie die
beiden Uhrzeiten darueber. Dass es keine Uhrzeit meint, sagt der Text
ringsum ("erst nach ... am Stueck"); die Kopfzeile des Rahmens schreibt es
beim Tippen aus.
Die Obergrenze ist damit 23:59 statt 24 Stunden - genau das, was ein
Zeitfeld hergibt.
Dabei aufgefallen: haltezeitKurz() rundete auf Zehntelstunden und machte aus
23:59 die Auskunft "24 Std.". Eine Haltezeit, die laenger aussieht als sie
ist, ist genau die falsche Auskunft - krumme Werte stehen jetzt als
"23 Std. 59 Min." da, halbe behalten ihr Komma.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
521 lines
24 KiB
PHP
521 lines
24 KiB
PHP
<?php
|
||
/**
|
||
* Endpunkt fuer die Automatiken: Editor, Uebersicht, Speichern, Loeschen.
|
||
*
|
||
* Alles laeuft ueber ?action=... , damit Formular und Uebersicht dieselbe
|
||
* Modell-Schicht (restricted/automations.php) benutzen und nicht zwei
|
||
* Wahrheiten ueber denselben Datensatz entstehen.
|
||
*
|
||
* 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"
|
||
* POST ?action=save JSON-Rumpf, legt an oder ueberschreibt
|
||
* GET ?action=followers&id=N JSON: wer haengt an dieser Automatik?
|
||
* GET ?action=werte&ids=1,2,3 JSON: was diese Messwerte gerade sagen
|
||
* POST ?action=delete {"id": N, "mode": "abhaengen"|"mitloeschen"}
|
||
* POST ?action=toggle {"id": N} - pausieren / fortsetzen
|
||
*/
|
||
|
||
require_once("../helper.php");
|
||
require_once("../restricted/automations.php");
|
||
|
||
if (!checkLogin()) {
|
||
http_response_code(403);
|
||
exit;
|
||
}
|
||
|
||
function jsonAntwort($data, $code = 200)
|
||
{
|
||
http_response_code($code);
|
||
header("Content-Type: application/json; charset=utf-8");
|
||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||
exit;
|
||
}
|
||
|
||
$action = $_GET["action"] ?? "editor";
|
||
$floor = in_array($_GET["floor"] ?? "", $floors, true) ? $_GET["floor"] : "";
|
||
|
||
// --- Schreibende Aufrufe -------------------------------------------------
|
||
if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||
$body = json_decode(file_get_contents("php://input"), true);
|
||
if (!is_array($body)) {
|
||
jsonAntwort(["error" => "Konnte die Anfrage nicht lesen."], 400);
|
||
}
|
||
try {
|
||
switch ($action) {
|
||
case "save":
|
||
$id = saveAutomation($body);
|
||
jsonAntwort(["id" => $id]);
|
||
case "delete":
|
||
// Der Modus kommt aus der Rueckfrage im Browser. Unbekanntes
|
||
// faellt auf das schonendere "abhaengen" zurueck - lieber eine
|
||
// pausierte Automatik zu viel als eine geloeschte zu viel.
|
||
$modus = ($body["mode"] ?? "") === "mitloeschen"
|
||
? "mitloeschen" : "abhaengen";
|
||
deleteAutomation(intval($body["id"] ?? 0), $modus);
|
||
jsonAntwort(["ok" => true]);
|
||
case "toggle":
|
||
jsonAntwort(["enabled" => toggleAutomation(intval($body["id"] ?? 0))]);
|
||
default:
|
||
jsonAntwort(["error" => "Unbekannte Aktion."], 400);
|
||
}
|
||
} catch (InvalidArgumentException $e) {
|
||
jsonAntwort(["error" => $e->getMessage()], 400);
|
||
} catch (Throwable $e) {
|
||
jsonAntwort(["error" => "Speichern fehlgeschlagen: " . $e->getMessage()], 500);
|
||
}
|
||
}
|
||
|
||
// --- Wer haengt an dieser Automatik? -------------------------------------
|
||
// Wird vor dem Loeschen gefragt, damit die Rueckfrage im Browser die Namen
|
||
// nennen kann und nicht nur eine Zahl.
|
||
if ($action === "followers") {
|
||
jsonAntwort(["followers" => automationFollowers(intval($_GET["id"] ?? 0))]);
|
||
}
|
||
|
||
// --- Was sagen diese Messwerte gerade? -----------------------------------
|
||
// Der offene Editor fragt das im Takt nach, damit neben jeder Bedingung der
|
||
// laufende Wert steht. Absichtlich ein eigener, winziger Endpunkt: den
|
||
// ganzen Geraetekatalog neu zu holen waeren je Runde ueber hundert
|
||
// Kilobyte fuer ein paar Zahlen.
|
||
if ($action === "werte") {
|
||
$ids = array_slice(explode(",", $_GET["ids"] ?? ""), 0, 200);
|
||
jsonAntwort(["werte" => stateValues($ids)]);
|
||
}
|
||
|
||
// --- Zeitleiste ----------------------------------------------------------
|
||
// Alle Automatiken eines Tages mit ihrer Lage, siehe restricted/zeitleiste.php.
|
||
if ($action === "zeitleiste") {
|
||
require_once("../restricted/zeitleiste.php");
|
||
try {
|
||
jsonAntwort(zeitleiste(strval($_GET["datum"] ?? date("Y-m-d"))));
|
||
} catch (Throwable $e) {
|
||
jsonAntwort(["error" => "Zeitleiste nicht abrufbar: " . $e->getMessage()], 500);
|
||
}
|
||
}
|
||
|
||
// --- Uebersichtstabelle --------------------------------------------------
|
||
if ($action === "list") {
|
||
$tage = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"];
|
||
$list = listAutomations($floor);
|
||
if (!$list) {
|
||
echo "<p class='text-body-secondary mb-2'>Für diese Etage ist noch nichts eingerichtet.</p>";
|
||
} else {
|
||
echo "<div class='table-responsive'><table class='table table-hover align-middle'>";
|
||
// Je schmaler der Schirm, desto weniger Spalten: auf dem Handy bleiben
|
||
// Name, Ausloeser und die Knoepfe. Sonst muss man seitwaerts scrollen,
|
||
// um eine Automatik ueberhaupt pausieren zu koennen.
|
||
echo "<thead><tr><th>Name</th><th>Auslöser</th>"
|
||
. "<th class='d-none d-sm-table-cell'>Aktion</th>"
|
||
. "<th class='d-none d-md-table-cell'>Wann</th>"
|
||
. "<th class='d-none d-md-table-cell'>Zuletzt</th><th></th></tr></thead><tbody>";
|
||
|
||
/*
|
||
* Eine Automatik, die von einer anderen ausgeloest wird, steht unter
|
||
* ihr statt alphabetisch irgendwo dazwischen - sonst liest man
|
||
* "Rollladen Magdalena morgens" und sieht nicht, woran es haengt.
|
||
*
|
||
* Eingeklappt, weil die Uebersicht sonst mit jeder Verkettung laenger
|
||
* wird, ohne mehr zu sagen: der Ausloeser ist das, was man sucht. Der
|
||
* Zaehler am Pfeil verraet, dass da noch etwas ist.
|
||
*
|
||
* Gruppiert wird nur die Darstellung. Jeder Nachfolger bleibt eine
|
||
* vollwertige Automatik mit eigener Pause, eigenen Rahmenbedingungen
|
||
* und eigener Zeile im Protokoll - sonst waere zu klaeren, wem die
|
||
* Pause gehoert und was das Protokoll zeigt.
|
||
*/
|
||
$nachFolgern = [];
|
||
foreach ($list as $a) {
|
||
foreach ($a["triggers"] as $davor) {
|
||
$nachFolgern[intval($davor)][] = intval($a["id"]);
|
||
}
|
||
}
|
||
$vorhanden = [];
|
||
foreach ($list as $a) {
|
||
$vorhanden[intval($a["id"])] = $a;
|
||
}
|
||
// Wessen Ausloeser nicht in dieser Etage steht, faengt selbst oben an.
|
||
$wurzeln = [];
|
||
foreach ($list as $a) {
|
||
$hatVater = false;
|
||
foreach ($a["triggers"] as $davor) {
|
||
if (isset($vorhanden[intval($davor)])) {
|
||
$hatVater = true;
|
||
}
|
||
}
|
||
if (!$hatVater) {
|
||
$wurzeln[] = intval($a["id"]);
|
||
}
|
||
}
|
||
$reihe = [];
|
||
$einordnen = function ($id, $tiefe, $vater) use (&$einordnen, &$reihe, $nachFolgern, $vorhanden) {
|
||
if (!isset($vorhanden[$id]) || isset($reihe[$id])) {
|
||
return;
|
||
}
|
||
$reihe[$id] = ["auto" => $vorhanden[$id], "tiefe" => $tiefe,
|
||
"vater" => $vater, "kinder" => 0];
|
||
foreach ($nachFolgern[$id] ?? [] as $kind) {
|
||
$einordnen($kind, $tiefe + 1, $id);
|
||
}
|
||
};
|
||
foreach ($wurzeln as $id) {
|
||
$einordnen($id, 0, 0);
|
||
}
|
||
// Was durch einen Kreis nicht erreicht wurde, kommt trotzdem mit.
|
||
foreach ($list as $a) {
|
||
$einordnen(intval($a["id"]), 0, 0);
|
||
}
|
||
// Gezaehlt wird erst jetzt, und nur was wirklich eingeordnet wurde:
|
||
// wer zwei Ausloeser hat, steht nur unter dem ersten. Aus der rohen
|
||
// Nachfolgerliste gezaehlt verspraeche der zweite Pfeil eine Zeile,
|
||
// die er nicht aufklappen kann.
|
||
foreach ($reihe as $eintrag) {
|
||
if ($eintrag["vater"]) {
|
||
$reihe[$eintrag["vater"]]["kinder"]++;
|
||
}
|
||
}
|
||
|
||
foreach ($reihe as $eintrag) {
|
||
$a = $eintrag["auto"];
|
||
$tiefe = $eintrag["tiefe"];
|
||
$kinder = $eintrag["kinder"];
|
||
$vater = $eintrag["vater"];
|
||
// Nur die aktiven Tage nennen. Sieben Haekchen untereinander
|
||
// haben die Zeile frueher unnoetig hoch gemacht.
|
||
$aktiveTage = [];
|
||
for ($i = 0; $i < 7; $i++) {
|
||
if ($a["weekdays"] & (1 << $i)) {
|
||
$aktiveTage[] = $tage[$i];
|
||
}
|
||
}
|
||
// Kalendertage auf "immer" gehoeren zur Tagesangabe, nicht in
|
||
// die Nachbemerkung: sie fuegen Tage hinzu. Ohne angehakten
|
||
// Wochentag tragen sie die Automatik sogar allein - "nur
|
||
// Feiertage" ist damit eine gueltige Einstellung.
|
||
$dazu = [];
|
||
if ($a["on_vacation"] == 2) {
|
||
$dazu[] = "Ferientage";
|
||
}
|
||
if ($a["on_holiday"] == 2) {
|
||
$dazu[] = "Feiertage";
|
||
}
|
||
if (count($aktiveTage) === 7) {
|
||
$wann = "täglich";
|
||
} elseif ($aktiveTage) {
|
||
$wann = implode(" ", $aktiveTage);
|
||
if ($dazu) {
|
||
$wann .= " + " . implode(" + ", $dazu);
|
||
}
|
||
} elseif ($dazu) {
|
||
$wann = "nur " . implode(" + ", $dazu);
|
||
} else {
|
||
$wann = "nie – kein Tag ausgewählt";
|
||
}
|
||
// Vorabend: alle Tagesangaben meinen morgen. Vorne, damit auch
|
||
// "nicht an Feiertagen" weiter hinten als Folgetag gelesen wird.
|
||
if ($a["next_day"]) {
|
||
$wann = "Vorabend von " . $wann;
|
||
}
|
||
if ($a["window_from"] !== "00:00" || $a["window_to"] !== "23:59") {
|
||
$wann .= ", " . $a["window_from"] . "–" . $a["window_to"];
|
||
}
|
||
if ($a["on_holiday"] == 0) {
|
||
$wann .= ", nicht an Feiertagen";
|
||
}
|
||
if ($a["on_vacation"] == 0) {
|
||
$wann .= ", nicht in den Ferien";
|
||
}
|
||
if ($a["once_per_day"]) {
|
||
$wann .= ", nur einmal am Tag";
|
||
}
|
||
// Vor der Sperre, weil es auch zeitlich davor liegt: erst muss
|
||
// die Bedingung stehen bleiben, dann wird ausgeloest.
|
||
if (!empty($a["hold_secs"])) {
|
||
$wann .= ", erst nach "
|
||
. haltezeitKurz($a["hold_secs"]) . " ununterbrochen";
|
||
}
|
||
if ($a["lockout_secs"] > 0) {
|
||
$wann .= ", frühestens alle "
|
||
. ($a["lockout_secs"] >= 60
|
||
? intdiv($a["lockout_secs"], 60) . " Min."
|
||
: $a["lockout_secs"] . " Sek.");
|
||
}
|
||
|
||
$zuletzt = $a["last_run"] ? date("d.m. H:i", strtotime($a["last_run"])) : "–";
|
||
$pause = $a["enabled"] ? "bi-pause-fill" : "bi-play-fill";
|
||
$titel = $a["enabled"] ? "Pausieren" : "Fortsetzen";
|
||
$id = intval($a["id"]);
|
||
|
||
$klassen = [];
|
||
if (!$a["enabled"]) {
|
||
$klassen[] = "opacity-50";
|
||
}
|
||
if ($tiefe > 0) {
|
||
// Eingeklappt starten: die Zeile gehoert dem Ausloeser, und
|
||
// der steht direkt darueber.
|
||
$klassen[] = "auto-folge";
|
||
$klassen[] = "auto-von-" . $vater;
|
||
$klassen[] = "d-none";
|
||
}
|
||
$klasse = $klassen ? " class='" . implode(" ", $klassen) . "'" : "";
|
||
|
||
// Der Pfeil klappt die Nachfolger auf. Er steht nur da, wo es
|
||
// welche gibt - ein Pfeil ohne Inhalt waere eine Einladung ins
|
||
// Leere.
|
||
$griff = "";
|
||
if ($kinder > 0) {
|
||
$griff = "<button type='button' class='btn btn-link btn-sm p-0 me-1 text-decoration-none'"
|
||
. " title='Abhängige Automatiken zeigen'"
|
||
. " data-gruppe='" . $id . "'"
|
||
. " onclick='toggleAutomationGroup(" . $id . ", this)'>"
|
||
. "<i class='bi bi-chevron-right'></i> " . $kinder . "</button>";
|
||
}
|
||
$einzug = $tiefe > 0
|
||
? "<span class='text-body-secondary me-1'>"
|
||
. str_repeat(" ", $tiefe)
|
||
. "<i class='bi bi-arrow-return-right'></i></span> "
|
||
: "";
|
||
|
||
echo "<tr data-auto='" . $id . "'" . $klasse . ">";
|
||
echo "<td class='text-nowrap'>" . $einzug . $griff
|
||
. htmlspecialchars($a["name"]) . "</td>";
|
||
echo "<td>" . htmlspecialchars($a["conditionText"]) . "</td>";
|
||
echo "<td class='d-none d-sm-table-cell'>" . htmlspecialchars($a["actionText"]) . "</td>";
|
||
echo "<td class='text-nowrap d-none d-md-table-cell'>" . htmlspecialchars($wann) . "</td>";
|
||
echo "<td class='text-nowrap d-none d-md-table-cell'>" . $zuletzt . "</td>";
|
||
echo "<td class='text-nowrap'>"
|
||
. "<button type='button' class='btn btn-info btn-sm' title='" . $titel . "'"
|
||
. " onclick='toggleAutomation(" . $id . ")'><i class='bi " . $pause . "'></i></button> "
|
||
. "<button type='button' class='btn btn-warning btn-sm' title='Bearbeiten'"
|
||
. " onclick=\"openAutoActionModal('?action=editor&id=" . $id . "')\"><i class='bi bi-pencil-square'></i></button> "
|
||
. "<button type='button' class='btn btn-danger btn-sm' title='Löschen'"
|
||
. " onclick='deleteAutomation(" . $id . ", \"" . htmlspecialchars($a["name"], ENT_QUOTES) . "\")'><i class='bi bi-trash3'></i></button>"
|
||
. "</td></tr>";
|
||
}
|
||
echo "</tbody></table></div>";
|
||
}
|
||
exit;
|
||
}
|
||
|
||
// --- Editor --------------------------------------------------------------
|
||
$id = intval($_GET["id"] ?? 0);
|
||
$auto = $id > 0 ? loadAutomation($id) : null;
|
||
if (!$auto) {
|
||
$auto = emptyAutomation($floor);
|
||
}
|
||
|
||
// Geraetekatalog und Datensatz reisen im selben Dokument mit. Frueher holte
|
||
// der Editor die Geraeteliste und je Zeile noch einmal deren Details nach -
|
||
// zwei zusaetzliche Anfragen pro Bedingung, und die Dropdowns fuellten sich
|
||
// asynchron, weshalb ein gespeicherter Wert nicht zuverlaessig ankam.
|
||
$nutzdaten = json_encode([
|
||
"automation" => $auto,
|
||
"devices" => deviceCatalog(),
|
||
"lockouts" => lockoutChoices(),
|
||
"ferien" => kalendertagChoices("Ferien", "in den Ferien"),
|
||
"feiertage" => kalendertagChoices("Feiertage", "an Feiertagen"),
|
||
"tagvorlagen" => tagVorlagen(),
|
||
// Nur fuer die Reihenfolge der Raum-Gruppen: rooms.php gibt sie vor, und
|
||
// Etage fuer Etage liest sich besser als alphabetisch.
|
||
"rooms" => raumListe(),
|
||
], JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
|
||
|
||
?>
|
||
<script type="application/json" id="autoActionData"><?= $nutzdaten ?></script>
|
||
<form id="autoActionForm" class="p-2">
|
||
<div class="row g-2 mb-2">
|
||
<div class="col-8">
|
||
<div class="form-floating">
|
||
<input type="text" class="form-control" id="autoName" placeholder="Name" maxlength="100">
|
||
<label for="autoName">Name der Automatik</label>
|
||
</div>
|
||
</div>
|
||
<div class="col-4">
|
||
<div class="form-floating">
|
||
<!-- Ohne Etage waere die Automatik in keinem der drei Reiter zu
|
||
sehen und nur noch in der Datenbank auffindbar. Reihenfolge wie
|
||
die Reiter, damit der Rueckfall auf den ersten Eintrag dieselbe
|
||
Etage trifft wie die Startseite. -->
|
||
<select class="form-select" id="autoFloor">
|
||
<?php foreach ($floors as $etage): ?>
|
||
<option value="<?= $etage ?>" title="<?= htmlspecialchars(floorLabel($etage)) ?>"><?= $etage ?></option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
<label for="autoFloor">Etage</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!--
|
||
Drei Baender statt eines Akkordeons: Wenn - Wann - Dann.
|
||
|
||
Das Akkordeon schloss beim Oeffnen die anderen Faecher, Ausloeser und
|
||
Aktion waren also nie gleichzeitig zu sehen - ausgerechnet bei einer
|
||
Regel, die genau daraus besteht. Und das mittlere Fach hiess
|
||
"Bedingungen", enthielt aber die *Rahmen*bedingungen; die echten
|
||
Bedingungen standen im ersten.
|
||
|
||
Gebaut aus .card mit farbigem Rand und .card-header - kein eigenes
|
||
Bauteil. Die Ueberschriften tragen .card-title und bekommen darueber die
|
||
Anzeigeschrift aus solar.css.
|
||
-->
|
||
|
||
<div class="card border-primary mb-2">
|
||
<div class="card-header d-flex flex-wrap align-items-center gap-2 py-2">
|
||
<i class="bi bi-lightning-charge-fill text-primary"></i>
|
||
<span class="card-title mb-0 fs-5 text-primary">Wenn</span>
|
||
<span class="small text-body-secondary">… das alles zusammenkommt</span>
|
||
<span class="flex-grow-1"></span>
|
||
<!-- Wie viele Bedingungen gerade zutreffen. Fuellt renderConditions(). -->
|
||
<span id="condStand" class="d-flex align-items-center gap-2"></span>
|
||
</div>
|
||
<div class="card-body p-2">
|
||
<!-- Ein Block ist eine UND-Gruppe, zwischen den Bloecken steht ODER.
|
||
Die Klammerung ist damit gezeichnet und nicht bloss vereinbart. -->
|
||
<div id="condBlocks"></div>
|
||
<button class="btn btn-outline-secondary btn-sm mt-2" type="button" id="btnAddGroup">
|
||
<i class="bi bi-plus-lg"></i> oder stattdessen …
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card mb-2">
|
||
<!--
|
||
Der Rahmen steht zusammengefaltet da, solange er nur Vorgaben enthaelt -
|
||
und das ist der Normalfall. Frueher nahm er die meiste Flaeche fuer den
|
||
seltensten Inhalt.
|
||
-->
|
||
<!--
|
||
Zugeklappt liest man hier, was gilt; aufgeklappt greift man zu. Die drei
|
||
Vorlagen tauschen deshalb mit der Kurzfassung den Platz, statt eine
|
||
eigene Zeile im Rumpf zu bekommen - das war eine Zeile mehr und eine
|
||
Ueberschrift ("An welchen Tagen?"), die neben dem Wort "Wann" ohnehin
|
||
nichts Neues sagte.
|
||
|
||
ACHTUNG beim Umbauen: die Vorlagen duerfen NICHT innerhalb des Elements
|
||
stehen, das data-bs-toggle traegt. Bootstrap haengt seinen Umschalter
|
||
nicht an dieses Element, sondern ans Dokument - und dort in der
|
||
Capture-Phase, die vor jedem Klick auf einen Knopf darin laeuft. Ein
|
||
stopPropagation() im Knopf kommt deshalb grundsaetzlich zu spaet, und
|
||
jede Vorlage waere zugleich ein Zuklappen. Deshalb traegt nur der linke
|
||
Teil den Umschalter, die Vorlagen stehen daneben.
|
||
-->
|
||
<div class="card-header d-flex flex-wrap align-items-center gap-2 py-2">
|
||
<div class="d-flex align-items-center gap-2 flex-grow-1" id="rahmenKopf"
|
||
role="button" data-bs-toggle="collapse" data-bs-target="#rahmenBody"
|
||
aria-expanded="false">
|
||
<i class="bi bi-calendar-week text-warning"></i>
|
||
<span class="card-title mb-0 fs-5 text-warning">Wann</span>
|
||
<span id="rahmenKurz" class="text-body-secondary"></span>
|
||
<span class="flex-grow-1"></span>
|
||
<span class="small text-body-secondary text-nowrap" id="rahmenAendern">ändern <i class="bi bi-chevron-down"></i></span>
|
||
</div>
|
||
<div class="btn-group btn-group-sm" role="group" id="tagVorlagen" hidden></div>
|
||
<i class="bi bi-chevron-up text-body-secondary" id="rahmenZu" role="button"
|
||
data-bs-toggle="collapse" data-bs-target="#rahmenBody" hidden></i>
|
||
</div>
|
||
<div id="rahmenBody" class="collapse">
|
||
<div class="card-body">
|
||
<div class="btn-group btn-group-sm flex-wrap mb-2" role="group" id="weekdayGroup"></div>
|
||
<div class="row g-2 mb-2">
|
||
<div class="col-sm-6 d-flex align-items-center gap-2">
|
||
<span class="text-body-secondary small" style="min-width:5rem">Ferien</span>
|
||
<div class="btn-group btn-group-sm" role="group" id="ferienGroup"></div>
|
||
</div>
|
||
<div class="col-sm-6 d-flex align-items-center gap-2">
|
||
<span class="text-body-secondary small" style="min-width:5rem">Feiertage</span>
|
||
<div class="btn-group btn-group-sm" role="group" id="feiertagGroup"></div>
|
||
</div>
|
||
</div>
|
||
<!-- Vorabend: fuer alles, was abends fuer morgen geschieht - "Kinder
|
||
ins Bett, wenn morgen Schule ist" ist dann Mo-Fr, Ferien nie,
|
||
Feiertage nie, wie der Wecker. Uhrzeit und Zeitfenster bleiben
|
||
beim heutigen Tag. -->
|
||
<div class="form-check mb-2">
|
||
<input class="form-check-input" type="checkbox" id="nextDay">
|
||
<label class="form-check-label" for="nextDay">
|
||
Am Vorabend – Wochentage, Ferien und Feiertage gelten für den folgenden Tag
|
||
</label>
|
||
</div>
|
||
<!--
|
||
.callout ist ein AdminLTE-Bauteil: farbiger Balken links, getoenter
|
||
Grund, und die Toene kommen aus Bootstraps *-bg-subtle-Variablen -
|
||
es zieht also mit dem Theme mit, ohne dass hier eine Regel steht.
|
||
Als .alert stand der Satz vorher in einem eingesunkenen Kasten mit
|
||
Rahmen ringsum und las sich wie eine Warnung; er ist aber das
|
||
Ergebnis der Schalter darueber. Die Polsterung stellen Hilfsklassen
|
||
kleiner - .callout ist auf ganze Absaetze ausgelegt, hier steht ein
|
||
Satz.
|
||
-->
|
||
<div class="callout callout-primary py-2 px-3 mt-0 mb-3" id="rahmenSummary"></div>
|
||
|
||
<div class="row g-2 mb-3">
|
||
<div class="col-sm-6">
|
||
<div class="input-group input-group-sm">
|
||
<span class="input-group-text">von</span>
|
||
<input type="time" class="form-control" id="tSpanFrom" value="00:00">
|
||
<span class="input-group-text">bis</span>
|
||
<input type="time" class="form-control" id="tSpanTo" value="23:59">
|
||
</div>
|
||
</div>
|
||
<div class="col-sm-6">
|
||
<div class="input-group input-group-sm">
|
||
<span class="input-group-text">Sperre</span>
|
||
<select class="form-select" id="lockoutSecs"></select>
|
||
</div>
|
||
</div>
|
||
<!--
|
||
Haltezeit. Sie steht neben der Sperre, weil beide dasselbe Mittel
|
||
benutzen - Zeit - und trotzdem entgegengesetzt wirken: die Sperre
|
||
bremst die Wiederholung, die Haltezeit den ersten Lauf.
|
||
|
||
Frei einstellbar statt aus einer Auswahlliste: Wonach man sucht,
|
||
hängt am Messwert - eine offene Tür fällt nach zwei Minuten auf,
|
||
ein laufender Wasserhahn erst nach Stunden.
|
||
|
||
Dasselbe Zeitfeld wie von/bis darüber, nur als Dauer gelesen. Dass
|
||
es keine Uhrzeit meint, sagt der Text ringsum ("erst nach … am
|
||
Stück"); die Zeile über dem Rahmen schreibt es beim Tippen aus
|
||
("erst nach 3,5 Std.").
|
||
-->
|
||
<div class="col-sm-6">
|
||
<div class="input-group input-group-sm">
|
||
<span class="input-group-text">Auslösen erst nach</span>
|
||
<input type="time" class="form-control" id="holdZeit" value="00:00"
|
||
title="Stunden und Minuten, die die Bedingung ohne Unterbrechung erfüllt sein muss. 00:00 = sofort.">
|
||
<span class="input-group-text">am Stück</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-check">
|
||
<input class="form-check-input" type="checkbox" id="oncePerDay">
|
||
<label class="form-check-label" for="oncePerDay">
|
||
Nur einmal am Tag – danach bis Mitternacht gesperrt
|
||
</label>
|
||
</div>
|
||
<div class="form-check">
|
||
<input class="form-check-input" type="checkbox" id="runOnce">
|
||
<label class="form-check-label" for="runOnce">
|
||
Am Ende des Zeitraums auf jeden Fall ausführen, falls der Auslöser nie zutraf
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card border-success mb-0">
|
||
<div class="card-header d-flex flex-wrap align-items-center gap-2 py-2">
|
||
<i class="bi bi-play-fill text-success"></i>
|
||
<span class="card-title mb-0 fs-5 text-success">Dann</span>
|
||
<span class="small text-body-secondary">… passiert das hier</span>
|
||
</div>
|
||
<div class="card-body p-2">
|
||
<div id="actionList"></div>
|
||
<button class="btn btn-outline-success btn-sm mt-2" type="button" id="btnAddActor">
|
||
<i class="bi bi-plus-lg"></i> und noch etwas tun
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</form>
|