Setup-Menü: Wertvorschau, Unavailable-Warnung, Duplikat-Check, Reset, Neustart-Hinweis

Fünf Erweiterungen des bestehenden Setup-Popups: Live-Wert neben jedem
Such-Kandidaten, Warnhinweis bei unavailable/unknown/fehlender Entität,
Duplikat-Check mit Bestätigung vor dem Speichern, Zurücksetzen-Button je
Feld (Standardwerte-Snapshot in entitaeten.py, vor jedem Override
genommen), und ein bestätigter "Jetzt neu starten"-Knopf nach dem
Speichern, falls sich eines der drei trigger-gebundenen Felder geändert
hat (neuer Service audi_dashboard_neustart). Im Docker-Testcontainer
Feld für Feld verifiziert.
This commit is contained in:
2026-08-12 18:48:35 +02:00
parent 3548f773c9
commit d5562c71ef
6 changed files with 173 additions and 18 deletions
+102 -14
View File
@@ -1911,6 +1911,7 @@ let smartdealPopupOffen = false;
an den Service geht - Abbrechen verwirft sie einfach ohne Nebeneffekt. */
let setupOffen = false;
let setupZuordnung = null; // {KEY: "entity.id"} bzw. {KEY: [4 IDs]} für Listenfelder
let setupZuordnungStart = null; // Schnappschuss von setupZuordnung beim Öffnen - für den Neustart-Hinweis (welche trigger-gebundenen Felder haben sich geändert)
let setupNurPassend = true; // globaler Schalter: Kandidaten auf passende Domain/Einheit einschränken
let setupSucheOffen = null; // Feld-Id ("KEY" oder "KEY:idx") des offenen Dropdowns, sonst null
let setupSuchtext = "";
@@ -1986,11 +1987,79 @@ function setupOeffnen() {
}
}
setupZuordnung = zuordnung;
setupZuordnungStart = JSON.parse(JSON.stringify(zuordnung));
setupSucheOffen = null; setupSuchtext = ""; setupSpeichertLaeuft = false;
setupOffen = true; render();
}
function setupSchliessen() {
setupOffen = false; setupSucheOffen = null; setupZuordnung = null; render();
setupOffen = false; setupSucheOffen = null; setupZuordnung = null; setupZuordnungStart = null; render();
}
/* Standardwert (aus einstellungen.py, vor jedem Override) für ein Feld bzw.
eine Listenposition - Grundlage für den Zurücksetzen-Button. */
function setupStandardwert(key, idx) {
const standard = (ENTITAETEN && ENTITAETEN.standardwerte) || {};
const wert = standard[key];
if (idx == null) return wert || "";
return (wert || [])[idx] || "";
}
/* Entity-IDs, die im Arbeitsstand mehr als einer Rolle zugeordnet sind. */
function setupDuplikate() {
const zaehler = {};
for (const feld of setupKatalog()) {
const werte = feld.liste ? (setupZuordnung[feld.key] || []) : [setupZuordnung[feld.key]];
for (const id of werte) { if (id) zaehler[id] = (zaehler[id] || 0) + 1; }
}
return Object.keys(zaehler).filter((id) => zaehler[id] > 1);
}
/* Labels der trigger-gebundenen Felder (ZUENDUNG/KM/TANK), deren Wert sich
seit dem Öffnen des Popups geändert hat - für den Neustart-Hinweis nach
dem Speichern. */
function setupGeaenderteTriggerFelder() {
if (!setupZuordnungStart) return [];
return setupKatalog()
.filter((f) => f.trigger_gebunden && JSON.stringify(setupZuordnung[f.key]) !== JSON.stringify(setupZuordnungStart[f.key]))
.map((f) => f.label);
}
function setupSpeichernAusfuehren() {
setupSpeichertLaeuft = true; render();
(async () => {
try {
await HASS.callService("pyscript", "audi_dashboard_entitaeten_schreiben", { zuordnung_json: JSON.stringify(setupZuordnung) });
} catch (err) {
setupSpeichertLaeuft = false; hinweis("Speichern fehlgeschlagen", err.message); return;
}
const geaendert = setupGeaenderteTriggerFelder();
setupOffen = false; setupSucheOffen = null; setupZuordnung = null; setupZuordnungStart = null; setupSpeichertLaeuft = false; render();
if (geaendert.length) {
bestaetigen("Neustart nötig", `Wirkt für ${geaendert.join(", ")} erst nach einem Neustart von Home Assistant.`, "Jetzt neu starten", setupNeustartAusloesen, false);
}
})();
}
function setupNeustartAusloesen() {
(async () => {
try {
await HASS.callService("pyscript", "audi_dashboard_neustart", {});
hinweis("Neustart ausgelöst", "Home Assistant startet neu - die App lädt sich danach automatisch neu.");
} catch (err) {
hinweis("Neustart fehlgeschlagen", err.message);
}
})();
}
/* Warnhinweis, falls der aktuell gewählte Sensor keinen brauchbaren Wert
liefert - sichtbar, bevor gespeichert wird, statt es erst hinterher in
der App zu bemerken. */
function entitaetStatusWarnung(wert) {
if (!wert) return "";
const zustand = HASS.states[wert] ? HASS.states[wert].state : null;
if (zustand === null) return `<div class="setup-feld-warnung">Entität nicht gefunden.</div>`;
if (zustand === "unavailable" || zustand === "unknown") return `<div class="setup-feld-warnung">Aktuell „${esc(zustand)}" - kein Wert vom Sensor.</div>`;
return "";
}
function vReifen() {
const r = CAR.reifen;
@@ -2639,22 +2708,33 @@ const LUPE_SVG = `<svg viewBox="0 0 20 20" width="16" height="16" fill="none" st
function entitaetZeilenMarkup(feldId, key, suchtext, aktuellerWert) {
const kandidaten = entitaetKandidaten(key, suchtext, aktuellerWert);
if (!kandidaten.length) return `<div class="entitaet-leer">Keine passende Entität gefunden</div>`;
return kandidaten.map((e) => `
return kandidaten.map((e) => {
const zustand = HASS.states[e.id] ? HASS.states[e.id].state : null;
const unbekannt = zustand === null || zustand === "unavailable" || zustand === "unknown";
const wertAnzeige = unbekannt ? (zustand === null ? "nicht gefunden" : zustand) : `${zustand}${e.unit ? " " + e.unit : ""}`;
return `
<button type="button" class="entitaet-eintrag ${e.id === aktuellerWert ? "aktiv" : ""}" data-setup-wahl="${esc(feldId)}" data-setup-entity="${esc(e.id)}">
<span class="entitaet-eintrag-name">${esc(e.name)}</span>
<span class="entitaet-eintrag-id">${esc(e.id)}${e.unit ? " · " + esc(e.unit) : ""}</span>
</button>`).join("");
<span class="entitaet-eintrag-id">${esc(e.id)}</span>
<span class="entitaet-eintrag-wert ${unbekannt ? "unbekannt" : ""}">${esc(wertAnzeige)}</span>
</button>`;
}).join("");
}
const RESET_SVG = `<svg viewBox="0 0 20 20" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M16 10a6 6 0 1 1-2-4.5"/><path d="M16 3v4h-4"/></svg>`;
function entitaetCombo(key, idx) {
const feldId = idx == null ? key : `${key}:${idx}`;
const wert = idx == null ? setupZuordnung[key] : (setupZuordnung[key] || [])[idx];
const offen = setupSucheOffen === feldId;
const anzeige = offen ? setupSuchtext : entitaetName(wert);
const standard = setupStandardwert(key, idx);
const zeigeReset = wert !== standard;
return `<div class="entitaet-combo">
<span class="entitaet-combo-icon">${LUPE_SVG}</span>
<input type="text" class="entitaet-combo-input" autocomplete="off" spellcheck="false"
placeholder="Entität suchen …" value="${esc(anzeige)}" data-setup-suche="${esc(feldId)}">
${zeigeReset ? `<button type="button" class="entitaet-combo-reset" title="Auf Standard zurücksetzen" data-setup-reset="${esc(feldId)}">${RESET_SVG}</button>` : ""}
${offen ? `<div class="entitaet-liste" data-setup-liste="${esc(feldId)}">${entitaetZeilenMarkup(feldId, key, setupSuchtext, wert)}</div>` : ""}
</div>`;
}
@@ -2672,11 +2752,12 @@ function setupFeldzeile(feld) {
<div class="setup-unterfeld">
<span class="setup-unterfeld-label">${esc(pos)}</span>
${entitaetCombo(feld.key, i)}
${entitaetStatusWarnung((setupZuordnung[feld.key] || [])[i])}
</div>`).join("")}
</div>
</div>`;
}
return `<div class="setup-feldzeile">${kopf}${entitaetCombo(feld.key, null)}</div>`;
return `<div class="setup-feldzeile">${kopf}${entitaetCombo(feld.key, null)}${entitaetStatusWarnung(setupZuordnung[feld.key])}</div>`;
}
const SETUP_GRUPPEN = [
@@ -3074,17 +3155,24 @@ function ereignisseVerdrahten() {
setupSucheOffen = null; setupSuchtext = ""; render();
return;
}
const reset = e.target.closest("[data-setup-reset]");
if (reset) {
const feldId = reset.dataset.setupReset;
const teile = feldId.split(":");
if (teile.length === 2) setupZuordnung[teile[0]][Number(teile[1])] = setupStandardwert(teile[0], Number(teile[1]));
else setupZuordnung[feldId] = setupStandardwert(feldId, null);
render();
return;
}
if (e.target.closest("[data-setup-speichern]")) {
if (setupSpeichertLaeuft) return;
setupSpeichertLaeuft = true; render();
(async () => {
try {
await HASS.callService("pyscript", "audi_dashboard_entitaeten_schreiben", { zuordnung_json: JSON.stringify(setupZuordnung) });
} catch (err) {
setupSpeichertLaeuft = false; hinweis("Speichern fehlgeschlagen", err.message); return;
}
setupOffen = false; setupSucheOffen = null; setupZuordnung = null; setupSpeichertLaeuft = false; render();
})();
const duplikate = setupDuplikate();
if (duplikate.length) {
const namen = duplikate.map(entitaetName).join(", ");
bestaetigen("Doppelt zugeordnet", `${namen} ${duplikate.length === 1 ? "ist" : "sind"} mehr als einer Rolle zugeordnet. Trotzdem speichern?`, "Trotzdem speichern", setupSpeichernAusfuehren, false);
return;
}
setupSpeichernAusfuehren();
return;
}
// Klick aufs (noch geschlossene) Suchfeld öffnet dessen Dropdown; ein