Selbst-Update: Ladezustand und echter Neustart-Knopf
Der erste echte Live-Test des Selbst-Updates gegen das private Gitea-Repo lief erfolgreich (mehrfach im Test-Container bestätigt) - deckte aber zwei UX-Lücken auf, die der Owner direkt gemeldet hat: - Prüfen/Installieren gaben während der 15-25 Sekunden dauernden Netzwerkaktion keine sichtbare Rückmeldung - nicht von einem Hänger zu unterscheiden. Jetzt ein Ladeindikator (Panel: wiederverwendetes .lade-spinner; companion-app: neues .dm-spinner-Äquivalent, gab es dort noch gar nicht). - Nach erfolgreicher Installation stand nur ein Hinweistext da, kein Weg zum eigentlich nötigen nächsten Schritt. Der Knopf wechselt jetzt zu "Installation abschließen - Jetzt neu starten" und stößt homeassistant.restart direkt an. Das separat gemeldete "Lädt"-Hängenbleiben war kein Bug: reproduziert durch Live-Test des neuen Neustart-Knopfs - eine echte HA-Verbindungsunterbrechung während eines Neustarts zeigt exakt denselben, bereits bestehenden Bootstrap-Ladebildschirm. Vermutlich derselbe Effekt durch den Docker-Neustart früher in dieser Sitzung. Verifiziert im Test-Container per Browser-Automatisierung: Neustart-Knopf ausgelöst, Verbindungsabbruch beobachtet, per docker logs bestätigt, dass Home Assistant tatsächlich neu gestartet ist und die neue Version aktiv wurde. Details in AGENTS.md, Abschnitt L. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2337,6 +2337,73 @@ entirely, not replaced with a space) and un-escapes `\,`/`\;`/`\n`/`\\` before u
|
|||||||
Verified: `tsc --noEmit` clean, all 134 companion-app tests pass (127 + 7 new), panel JS re-checked
|
Verified: `tsc --noEmit` clean, all 134 companion-app tests pass (127 + 7 new), panel JS re-checked
|
||||||
with `node --check`. Manifest bumped to `2026.8.24.5`, `npm run ota` rerun (both frontends changed).
|
with `node --check`. Manifest bumped to `2026.8.24.5`, `npm run ota` rerun (both frontends changed).
|
||||||
|
|
||||||
|
### L) Self-update UX: loading feedback and a real restart button (2026-08-24, same day)
|
||||||
|
|
||||||
|
**The self-update feature (section J) got its first genuine live test against the real private
|
||||||
|
Gitea repo this same day**, and it worked — confirmed directly in the test container's log across
|
||||||
|
several real attempts (`Update installiert: Version 2026.8.24.5`, no manual file copying involved).
|
||||||
|
That's the first real end-to-end confirmation beyond the unit tests and structural container-restart
|
||||||
|
checks section J could offer on its own.
|
||||||
|
|
||||||
|
That same live test exposed two real UX gaps, both raised directly by the owner:
|
||||||
|
|
||||||
|
1. **No feedback during or after the ~15–25 second install** (confirmed from the log's own
|
||||||
|
timestamps between a "Prüfung" and the matching "installiert" line). The button just sat there
|
||||||
|
with no state change — indistinguishable from a hang, and after success, the version still read
|
||||||
|
"2026.8.24.4 installiert" because that field only updates after a real restart, which nothing in
|
||||||
|
the UI was telling the user to do next beyond a paragraph of text.
|
||||||
|
2. **A separate, initially confusing "Lädt …" hang the owner saw and asked about.** Traced by
|
||||||
|
reproducing it directly: `.lade-spinner`/"Lädt …" is this project's own **existing** top-level
|
||||||
|
bootstrap loading screen (shown whenever `DATEN_GELADEN` is false — i.e., no live connection to
|
||||||
|
Home Assistant yet), not anything related to the update button specifically. Clicking the new
|
||||||
|
"Jetzt neu starten" button and watching it live in a browser reproduced the *exact* same screen —
|
||||||
|
because a `homeassistant.restart` call genuinely does drop the frontend's connection until HA
|
||||||
|
finishes rebooting, and this is the correct, expected screen for that. The most likely explanation
|
||||||
|
for the owner's *original* sighting (before this button existed): the Docker Desktop restart from
|
||||||
|
earlier in the same session dropped the connection the same way. Not a bug — but the fact that it
|
||||||
|
was indistinguishable from one was the actual, valid complaint: nothing signposted that a restart
|
||||||
|
was in progress versus something being stuck.
|
||||||
|
|
||||||
|
**Fixed with a three-state UI, researched against Apple HIG's guidance on indeterminate progress**
|
||||||
|
(unknown-duration network operations get a running indicator, not just a disabled control; on
|
||||||
|
completion the UI leads straight to the next required action instead of leaving the user to find it
|
||||||
|
themselves):
|
||||||
|
- **Prüfen/Installieren, in progress** — a small spinner + "Prüfe …"/"Installiere …", reusing the
|
||||||
|
panel's own pre-existing `.lade-spinner` (originally built, per its own comment, for the exact same
|
||||||
|
"don't let 'still loading' look like 'stuck'" reason at app bootstrap) and a new equivalent
|
||||||
|
`.dm-spinner`/`.dm-ladezeile` pair added to companion-app's `screens.css` (that side had no spinner
|
||||||
|
at all before, only a button-text swap).
|
||||||
|
- **On success** — the tile switches directly to "Installation abschließen - Jetzt neu starten",
|
||||||
|
which calls `homeassistant.restart` (a core HA service any admin could already trigger via
|
||||||
|
Settings → System → Restart — this button doesn't grant a new capability, it surfaces an existing
|
||||||
|
one at the point it's actually needed). Its own click shows "Home Assistant startet neu …";
|
||||||
|
no `.finally`-style cleanup after that call succeeds, since the connection normally drops before
|
||||||
|
the promise would ever resolve — only a genuine rejection (service refused, etc.) reaches the
|
||||||
|
catch block.
|
||||||
|
- **On a real request-level failure** (not the already-handled "no token"/"bad token" case from
|
||||||
|
section J, which returns normally with `fehler` set — this is Gitea unreachable, the websocket
|
||||||
|
itself failing, or any other exception that never made it back from Home Assistant at all) — this
|
||||||
|
was previously silent in companion-app (a `try/finally` with no `catch` re-throws into an
|
||||||
|
unhandled promise rejection). Added explicit `catch` blocks on all three actions in both frontends,
|
||||||
|
each surfacing its own message rather than one shared silently-stale error field.
|
||||||
|
|
||||||
|
- Panel: `INTEGRATION_UPDATE_LAEUFT` (`"pruefen" | "installieren" | "neustart" | null`), the
|
||||||
|
`data-update` click handler rewritten from `serviceRufen()` (fire-and-forget) to direct
|
||||||
|
`HASS.callService()` promise chains so the loading flag can wrap them; `vEinst()`'s tile rewritten
|
||||||
|
with the three states above.
|
||||||
|
- companion-app: `updateNeustartLaeuft`/`updateAktionFehler` state and `updateNeustartAusloesen()` in
|
||||||
|
`Einstellungen.tsx`; new `homeAssistantNeuStarten()` in `api/index.ts` (`dienstAufrufen("homeassistant",
|
||||||
|
"restart")` — note the domain isn't `DIENST_DOMAIN`, this is a core HA service, not one of this
|
||||||
|
integration's own).
|
||||||
|
|
||||||
|
Verified live end-to-end in the test container via browser automation: triggered the real restart
|
||||||
|
button, watched the connection drop into the bootstrap "Lädt …" screen exactly as reasoned above,
|
||||||
|
confirmed via `docker logs` that Home Assistant actually rebooted
|
||||||
|
(`Audi Dashboard 2026.8.24.6 eingerichtet` after restart, version now genuinely current — not just
|
||||||
|
the pre-restart file swap). companion-app: `tsc --noEmit` clean, full suite still green. Manifest
|
||||||
|
bumped to `2026.8.24.6` and `npm run ota` rerun per the standing rule in `VERSIONIERUNG.md` — both
|
||||||
|
frontends changed (new spinner/restart-button behavior is real, visible UI, not internal-only).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Working conventions (observed — keep them)
|
## Working conventions (observed — keep them)
|
||||||
|
|||||||
@@ -217,6 +217,15 @@ export class DataMetricApi {
|
|||||||
return this.rest.dienstAufrufen(DIENST_DOMAIN, "update_installieren");
|
return this.rest.dienstAufrufen(DIENST_DOMAIN, "update_installieren");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Startet Home Assistant komplett neu - der letzte Schritt nach
|
||||||
|
updateInstallieren(), damit die neue Fassung tatsächlich geladen wird
|
||||||
|
(ein reiner Reload reicht dafür nicht, siehe AGENTS.md Abschnitt J).
|
||||||
|
Kein audi_dashboard-Dienst, deshalb der Bereich "homeassistant" statt
|
||||||
|
DIENST_DOMAIN. */
|
||||||
|
homeAssistantNeuStarten(): Promise<unknown> {
|
||||||
|
return this.rest.dienstAufrufen("homeassistant", "restart");
|
||||||
|
}
|
||||||
|
|
||||||
/* ------------------------------------------- Import aus dem HA-Verlauf
|
/* ------------------------------------------- Import aus dem HA-Verlauf
|
||||||
Bewusst NICHT über die Warteschlange, anders als die übrigen
|
Bewusst NICHT über die Warteschlange, anders als die übrigen
|
||||||
schreibenden Vorgänge: der Import ist keine Eingabe, die man im Funkloch
|
schreibenden Vorgänge: der Import ist keine Eingabe, die man im Funkloch
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ export function Einstellungen({
|
|||||||
const [otaFehler, setzeOtaFehler] = useState<string | null>(null)
|
const [otaFehler, setzeOtaFehler] = useState<string | null>(null)
|
||||||
const [updatePruefenLaeuft, setzeUpdatePruefenLaeuft] = useState(false)
|
const [updatePruefenLaeuft, setzeUpdatePruefenLaeuft] = useState(false)
|
||||||
const [updateInstallierenLaeuft, setzeUpdateInstallierenLaeuft] = useState(false)
|
const [updateInstallierenLaeuft, setzeUpdateInstallierenLaeuft] = useState(false)
|
||||||
|
const [updateNeustartLaeuft, setzeUpdateNeustartLaeuft] = useState(false)
|
||||||
|
const [updateAktionFehler, setzeUpdateAktionFehler] = useState<string | null>(null)
|
||||||
const dateiwahl = useRef<HTMLInputElement | null>(null)
|
const dateiwahl = useRef<HTMLInputElement | null>(null)
|
||||||
|
|
||||||
const otaUpdateVerfuegbar = otaMoeglich() && buendelPasst(otaBuendel, eigeneVersion())
|
const otaUpdateVerfuegbar = otaMoeglich() && buendelPasst(otaBuendel, eigeneVersion())
|
||||||
@@ -82,12 +84,21 @@ export function Einstellungen({
|
|||||||
// danach über neuLaden() den neuen Stand von
|
// danach über neuLaden() den neuen Stand von
|
||||||
// sensor.audi_dashboard_app_version - der Fehlertext bei fehlendem Token
|
// sensor.audi_dashboard_app_version - der Fehlertext bei fehlendem Token
|
||||||
// kommt von dort mit, eine eigene "ist ein Token eingetragen?"-Prüfung
|
// kommt von dort mit, eine eigene "ist ein Token eingetragen?"-Prüfung
|
||||||
// gibt es bewusst nicht.
|
// gibt es bewusst nicht. updateAktionFehler fängt zusätzlich echte
|
||||||
|
// Verbindungsfehler ab (dienste.py behandelt AktualisierungsFehler selbst
|
||||||
|
// und liefert 200 zurück - hier landet nur, was gar nicht erst bei Home
|
||||||
|
// Assistant ankam), sonst bliebe ein solcher Fehlschlag ganz ohne
|
||||||
|
// Rückmeldung.
|
||||||
const updatePruefenAusloesen = async () => {
|
const updatePruefenAusloesen = async () => {
|
||||||
setzeUpdatePruefenLaeuft(true)
|
setzeUpdatePruefenLaeuft(true)
|
||||||
|
setzeUpdateAktionFehler(null)
|
||||||
try {
|
try {
|
||||||
await api.updatePruefen()
|
await api.updatePruefen()
|
||||||
await neuLaden()
|
await neuLaden()
|
||||||
|
} catch (fehler) {
|
||||||
|
setzeUpdateAktionFehler(
|
||||||
|
fehler instanceof Error ? fehler.message : "Die Prüfung konnte nicht ausgeführt werden.",
|
||||||
|
)
|
||||||
} finally {
|
} finally {
|
||||||
setzeUpdatePruefenLaeuft(false)
|
setzeUpdatePruefenLaeuft(false)
|
||||||
}
|
}
|
||||||
@@ -95,14 +106,39 @@ export function Einstellungen({
|
|||||||
|
|
||||||
const updateInstallierenAusloesen = async () => {
|
const updateInstallierenAusloesen = async () => {
|
||||||
setzeUpdateInstallierenLaeuft(true)
|
setzeUpdateInstallierenLaeuft(true)
|
||||||
|
setzeUpdateAktionFehler(null)
|
||||||
try {
|
try {
|
||||||
await api.updateInstallieren()
|
await api.updateInstallieren()
|
||||||
await neuLaden()
|
await neuLaden()
|
||||||
|
} catch (fehler) {
|
||||||
|
setzeUpdateAktionFehler(
|
||||||
|
fehler instanceof Error ? fehler.message : "Das Update konnte nicht installiert werden.",
|
||||||
|
)
|
||||||
} finally {
|
} finally {
|
||||||
setzeUpdateInstallierenLaeuft(false)
|
setzeUpdateInstallierenLaeuft(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Nach einer erfolgreichen Installation zeigt die Kachel direkt den
|
||||||
|
// nächsten nötigen Schritt statt den Nutzer selbst suchen zu lassen - ein
|
||||||
|
// reiner Reload reicht nicht, siehe AGENTS.md Abschnitt J. Kein try/finally
|
||||||
|
// um setzeUpdateNeustartLaeuft: bricht die Verbindung normalerweise durch
|
||||||
|
// den Neustart selbst ab, bevor der Aufruf überhaupt zurückkehrt.
|
||||||
|
const updateNeustartAusloesen = async () => {
|
||||||
|
setzeUpdateNeustartLaeuft(true)
|
||||||
|
setzeUpdateAktionFehler(null)
|
||||||
|
try {
|
||||||
|
await api.homeAssistantNeuStarten()
|
||||||
|
} catch (fehler) {
|
||||||
|
setzeUpdateAktionFehler(
|
||||||
|
fehler instanceof Error
|
||||||
|
? fehler.message
|
||||||
|
: "Home Assistant konnte nicht neu gestartet werden.",
|
||||||
|
)
|
||||||
|
setzeUpdateNeustartLaeuft(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!einstellungen || !fahrzeug) return null
|
if (!einstellungen || !fahrzeug) return null
|
||||||
const werte = entwurf ?? einstellungen
|
const werte = entwurf ?? einstellungen
|
||||||
|
|
||||||
@@ -443,15 +479,36 @@ export function Einstellungen({
|
|||||||
|
|
||||||
<Tile>
|
<Tile>
|
||||||
<span className="ads-eyebrow">Integration-Update</span>
|
<span className="ads-eyebrow">Integration-Update</span>
|
||||||
{integrationUpdate?.installiert ? (
|
{/* HIG "Loading": Netzwerk zu Gitea plus Entpacken dauert unbestimmt
|
||||||
|
lang, ohne verlässliche Fortschrittsangabe - ein laufender
|
||||||
|
Indikator statt eines nur deaktivierten Knopfes verhindert, dass
|
||||||
|
das wie ein Hänger aussieht. Nach Erfolg führt der Knopf direkt
|
||||||
|
zum nächsten nötigen Schritt (Neustart) statt den Nutzer selbst
|
||||||
|
suchen zu lassen - ein reiner Reload reicht nicht (AGENTS.md
|
||||||
|
Abschnitt J). */}
|
||||||
|
{updateInstallierenLaeuft ? (
|
||||||
|
<div className="dm-ladezeile">
|
||||||
|
<span className="dm-spinner" aria-hidden="true" />
|
||||||
|
<span className="dm-fussnote" style={{ margin: 0 }}>
|
||||||
|
Installiere …
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
) : updateNeustartLaeuft ? (
|
||||||
|
<div className="dm-ladezeile">
|
||||||
|
<span className="dm-spinner" aria-hidden="true" />
|
||||||
|
<span className="dm-fussnote" style={{ margin: 0 }}>
|
||||||
|
Home Assistant startet neu …
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
) : integrationUpdate?.installiert ? (
|
||||||
<>
|
<>
|
||||||
<p className="dm-fussnote">
|
<p className="dm-fussnote">
|
||||||
Version {integrationUpdate.version} installiert. Home Assistant neu starten, damit
|
Version {integrationUpdate.version} installiert - die neue Fassung wird erst nach
|
||||||
die neue Fassung geladen wird — Einstellungen → System → Neu starten.
|
einem Neustart geladen.
|
||||||
</p>
|
</p>
|
||||||
<div className="dm-knopfreihe">
|
<div className="dm-knopfreihe">
|
||||||
<ActionButton onClick={() => void updatePruefenAusloesen()} disabled={updatePruefenLaeuft}>
|
<ActionButton onClick={() => void updateNeustartAusloesen()}>
|
||||||
{updatePruefenLaeuft ? "Prüfe …" : "Erneut prüfen"}
|
Installation abschließen - Jetzt neu starten
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@@ -466,14 +523,18 @@ export function Einstellungen({
|
|||||||
nicht betroffen.
|
nicht betroffen.
|
||||||
</p>
|
</p>
|
||||||
<div className="dm-knopfreihe">
|
<div className="dm-knopfreihe">
|
||||||
<ActionButton
|
<ActionButton onClick={() => void updateInstallierenAusloesen()}>
|
||||||
onClick={() => void updateInstallierenAusloesen()}
|
Update installieren
|
||||||
disabled={updateInstallierenLaeuft}
|
|
||||||
>
|
|
||||||
{updateInstallierenLaeuft ? "Installiere …" : "Update installieren"}
|
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
) : updatePruefenLaeuft ? (
|
||||||
|
<div className="dm-ladezeile">
|
||||||
|
<span className="dm-spinner" aria-hidden="true" />
|
||||||
|
<span className="dm-fussnote" style={{ margin: 0 }}>
|
||||||
|
Prüfe …
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<p className="dm-fussnote">
|
<p className="dm-fussnote">
|
||||||
@@ -482,13 +543,18 @@ export function Einstellungen({
|
|||||||
: "Noch nicht geprüft."}
|
: "Noch nicht geprüft."}
|
||||||
</p>
|
</p>
|
||||||
<div className="dm-knopfreihe">
|
<div className="dm-knopfreihe">
|
||||||
<ActionButton onClick={() => void updatePruefenAusloesen()} disabled={updatePruefenLaeuft}>
|
<ActionButton onClick={() => void updatePruefenAusloesen()}>
|
||||||
{updatePruefenLaeuft ? "Prüfe …" : "Auf Update prüfen"}
|
Auf Update prüfen
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{integrationUpdate?.fehler && <p className="dm-fehler">{integrationUpdate.fehler}</p>}
|
{updateAktionFehler && <p className="dm-fehler">{updateAktionFehler}</p>}
|
||||||
|
{!updateAktionFehler &&
|
||||||
|
integrationUpdate?.fehler &&
|
||||||
|
!updatePruefenLaeuft &&
|
||||||
|
!updateInstallierenLaeuft &&
|
||||||
|
!updateNeustartLaeuft && <p className="dm-fehler">{integrationUpdate.fehler}</p>}
|
||||||
</Tile>
|
</Tile>
|
||||||
|
|
||||||
<Tile>
|
<Tile>
|
||||||
|
|||||||
@@ -609,6 +609,34 @@
|
|||||||
background: var(--tile-2);
|
background: var(--tile-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ Ladezustand */
|
||||||
|
|
||||||
|
/* HIG "Loading": ein unbestimmt langer Vorgang (Netzwerk, kein verlässlicher
|
||||||
|
Fortschritt) bekommt einen laufenden Indikator statt eines stumm
|
||||||
|
daliegenden Knopfes - sonst nicht von einem Hänger zu unterscheiden.
|
||||||
|
Deckungsgleich mit .lade-spinner im Panel. */
|
||||||
|
.dm-spinner {
|
||||||
|
display: inline-block;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
flex: none;
|
||||||
|
border: 2px solid var(--line);
|
||||||
|
border-top-color: var(--fg2);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: dm-spin 0.8s linear infinite;
|
||||||
|
}
|
||||||
|
@keyframes dm-spin {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.dm-ladezeile {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--sp-2);
|
||||||
|
margin-top: var(--sp-3);
|
||||||
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ Meldungen */
|
/* ------------------------------------------------------------ Meldungen */
|
||||||
|
|
||||||
.dm-fehler {
|
.dm-fehler {
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"version":"2026.8.24.5","sha256":"378a1cbc7f5daa3d8c5d74734a4c09f30557b243a8ce4d720240bf911fbeff76","bytes":232096,"gebaut":"2026-08-24T10:29:33Z"}
|
{"version":"2026.8.24.6","sha256":"e0d8c7a6f4cc7fddc59b252e02e4ebf50002f3bd4e317ddb315670ef13299d75","bytes":232382,"gebaut":"2026-08-24T10:44:09Z"}
|
||||||
Binary file not shown.
@@ -2426,16 +2426,32 @@ function vEinst() {
|
|||||||
<div class="tile"><span class="label">Integration-Update</span>
|
<div class="tile"><span class="label">Integration-Update</span>
|
||||||
${(() => {
|
${(() => {
|
||||||
const u = INTEGRATION_UPDATE;
|
const u = INTEGRATION_UPDATE;
|
||||||
|
const laeuft = INTEGRATION_UPDATE_LAEUFT;
|
||||||
// Ersetzt install.ps1 als laufenden Update-Weg (Windows Smart App
|
// Ersetzt install.ps1 als laufenden Update-Weg (Windows Smart App
|
||||||
// Control blockiert dessen Ausführung zuverlässig) - install.ps1
|
// Control blockiert dessen Ausführung zuverlässig) - install.ps1
|
||||||
// bleibt nur noch für die Erstinstallation nötig, bevor die
|
// bleibt nur noch für die Erstinstallation nötig, bevor die
|
||||||
// Integration überhaupt läuft und sich selbst aktualisieren kann.
|
// Integration überhaupt läuft und sich selbst aktualisieren kann.
|
||||||
|
//
|
||||||
|
// HIG "Loading": Netzwerk zu Gitea plus Entpacken dauert unbestimmt
|
||||||
|
// lang, ohne verlässliche Fortschrittsangabe - ein laufender
|
||||||
|
// Indikator statt eines stumm daliegenden Knopfes verhindert, dass
|
||||||
|
// das wie ein Hänger aussieht (derselbe Grund wie beim
|
||||||
|
// Erstladen-Spinner, .lade-spinner). Nach Erfolg führt der Knopf
|
||||||
|
// direkt zum nächsten nötigen Schritt (Neustart), statt den Nutzer
|
||||||
|
// selbst suchen zu lassen.
|
||||||
|
const spinner = (text) => `<div style="display:flex;align-items:center;gap:10px;margin-top:8px">
|
||||||
|
<span class="lade-spinner" aria-hidden="true"></span>
|
||||||
|
<span class="label" style="margin:0">${esc(text)}</span>
|
||||||
|
</div>`;
|
||||||
|
if (laeuft === "installieren") return spinner("Installiere …");
|
||||||
|
if (laeuft === "neustart") return spinner("Home Assistant startet neu …");
|
||||||
if (u && u.installiert) {
|
if (u && u.installiert) {
|
||||||
return `<span style="display:block;margin-top:8px;color:var(--fg2);font-size:13.5px;line-height:1.6">
|
return `<span style="display:block;margin-top:8px;color:var(--fg2);font-size:13.5px;line-height:1.6">
|
||||||
Version ${esc(u.version)} installiert. Home Assistant neu starten, damit die neue
|
Version ${esc(u.version)} installiert - die neue Fassung wird erst nach einem Neustart
|
||||||
Fassung geladen wird - Einstellungen -> System -> Neu starten.</span>
|
geladen.</span>
|
||||||
<button class="aktion" style="margin-top:14px" data-update="pruefen">Erneut prüfen</button>`;
|
<button class="aktion" style="margin-top:14px" data-update="neustart">Installation abschließen - Jetzt neu starten</button>`;
|
||||||
}
|
}
|
||||||
|
if (laeuft === "pruefen") return spinner("Prüfe …");
|
||||||
if (u && u.verfuegbar) {
|
if (u && u.verfuegbar) {
|
||||||
return `<dl class="rows"><div class="row" style="border-bottom:0"><dt>Verfügbare Version</dt><dd>${esc(u.version)}</dd></div></dl>
|
return `<dl class="rows"><div class="row" style="border-bottom:0"><dt>Verfügbare Version</dt><dd>${esc(u.version)}</dd></div></dl>
|
||||||
<span style="display:block;margin-top:8px;color:var(--fg2);font-size:13.5px;line-height:1.6">
|
<span style="display:block;margin-top:8px;color:var(--fg2);font-size:13.5px;line-height:1.6">
|
||||||
@@ -2448,7 +2464,7 @@ function vEinst() {
|
|||||||
${u ? `Integration ist aktuell${u.geprueft_am ? ` (geprüft ${dedat(new Date(u.geprueft_am))})` : ""}.` : "Noch nicht geprüft."}</span>
|
${u ? `Integration ist aktuell${u.geprueft_am ? ` (geprüft ${dedat(new Date(u.geprueft_am))})` : ""}.` : "Noch nicht geprüft."}</span>
|
||||||
<button class="aktion" style="margin-top:14px" data-update="pruefen">Auf Update prüfen</button>`;
|
<button class="aktion" style="margin-top:14px" data-update="pruefen">Auf Update prüfen</button>`;
|
||||||
})()}
|
})()}
|
||||||
${INTEGRATION_UPDATE && INTEGRATION_UPDATE.fehler
|
${INTEGRATION_UPDATE && INTEGRATION_UPDATE.fehler && !INTEGRATION_UPDATE_LAEUFT
|
||||||
? `<span class="label" style="margin-top:10px;color:var(--red)">${esc(INTEGRATION_UPDATE.fehler)}</span>`
|
? `<span class="label" style="margin-top:10px;color:var(--red)">${esc(INTEGRATION_UPDATE.fehler)}</span>`
|
||||||
: ""}
|
: ""}
|
||||||
</div>`;
|
</div>`;
|
||||||
@@ -3156,6 +3172,12 @@ let APP_VERSION = null;
|
|||||||
// geprüft wurde. Ersetzt install.ps1 als Update-Weg, das Windows Smart App
|
// geprüft wurde. Ersetzt install.ps1 als Update-Weg, das Windows Smart App
|
||||||
// Control zuverlässig blockiert; siehe vVersion()/Einstellungen-Ansicht.
|
// Control zuverlässig blockiert; siehe vVersion()/Einstellungen-Ansicht.
|
||||||
let INTEGRATION_UPDATE = null;
|
let INTEGRATION_UPDATE = null;
|
||||||
|
// "pruefen" | "installieren" | "neustart" | null - während einer dieser drei
|
||||||
|
// Aktionen läuft, siehe vEinst()'s Integration-Update-Kachel. Eigens statt
|
||||||
|
// über serviceRufen() (das feuert-und-vergisst), weil diese drei Aktionen
|
||||||
|
// unbestimmt lange dauern (Netzwerk zu Gitea, Entpacken) und die Oberfläche
|
||||||
|
// das sichtbar machen soll - siehe .lade-spinner's eigener Kommentar dazu.
|
||||||
|
let INTEGRATION_UPDATE_LAEUFT = null;
|
||||||
|
|
||||||
/* Auswahl für "Modell" unter Einrichten - setzt nur CONFIG.fahrzeugtitel
|
/* Auswahl für "Modell" unter Einrichten - setzt nur CONFIG.fahrzeugtitel
|
||||||
(Zeile "Modell" in der Identität, Kopfzeilen, Kalender-Einträge, und den
|
(Zeile "Modell" in der Identität, Kopfzeilen, Kalender-Einträge, und den
|
||||||
@@ -4515,8 +4537,33 @@ function ereignisseVerdrahten() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const upd = e.target.closest("[data-update]");
|
const upd = e.target.closest("[data-update]");
|
||||||
if (upd && upd.dataset.update === "pruefen") { serviceRufen("update_pruefen", {}); return; }
|
if (upd) {
|
||||||
if (upd && upd.dataset.update === "installieren") { serviceRufen("update_installieren", {}); return; }
|
const aktion = upd.dataset.update;
|
||||||
|
// Eigene Promise-Kette statt serviceRufen() (das feuert-und-vergisst) -
|
||||||
|
// diese drei Aktionen brauchen einen sichtbaren Ladezustand, siehe
|
||||||
|
// INTEGRATION_UPDATE_LAEUFT's Kommentar an der Deklaration.
|
||||||
|
if (aktion === "pruefen" || aktion === "installieren") {
|
||||||
|
INTEGRATION_UPDATE_LAEUFT = aktion; render();
|
||||||
|
HASS.callService(DOMAIN, `update_${aktion}`, {})
|
||||||
|
.catch((err) => {
|
||||||
|
hinweis(
|
||||||
|
aktion === "pruefen" ? "Prüfung fehlgeschlagen" : "Installation fehlgeschlagen",
|
||||||
|
err && err.message ? err.message : "Der Dienst konnte nicht ausgeführt werden.",
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.finally(() => { INTEGRATION_UPDATE_LAEUFT = null; render(); });
|
||||||
|
} else if (aktion === "neustart") {
|
||||||
|
INTEGRATION_UPDATE_LAEUFT = "neustart"; render();
|
||||||
|
HASS.callService("homeassistant", "restart", {}).catch((err) => {
|
||||||
|
// Bricht die Verbindung normalerweise durch den Neustart selbst ab,
|
||||||
|
// bevor diese Zeile überhaupt liefe - nur ein echter Fehlschlag
|
||||||
|
// (Dienst abgelehnt, o.ä.) kommt hier tatsächlich an.
|
||||||
|
hinweis("Neustart fehlgeschlagen", err && err.message ? err.message : "Home Assistant konnte nicht neu gestartet werden.");
|
||||||
|
INTEGRATION_UPDATE_LAEUFT = null; render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
const cs = e.target.closest("[data-csv]");
|
const cs = e.target.closest("[data-csv]");
|
||||||
if (cs) {
|
if (cs) {
|
||||||
const w = cs.dataset.csv;
|
const w = cs.dataset.csv;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"domain": "audi_dashboard",
|
"domain": "audi_dashboard",
|
||||||
"name": "Audi Dashboard",
|
"name": "Audi Dashboard",
|
||||||
"version": "2026.8.24.5",
|
"version": "2026.8.24.6",
|
||||||
"documentation": "https://gitea.nothaft.cloud/paul/audi-app/src/branch/main/README.md",
|
"documentation": "https://gitea.nothaft.cloud/paul/audi-app/src/branch/main/README.md",
|
||||||
"issue_tracker": "https://gitea.nothaft.cloud/paul/audi-app/issues",
|
"issue_tracker": "https://gitea.nothaft.cloud/paul/audi-app/issues",
|
||||||
"codeowners": ["@paul"],
|
"codeowners": ["@paul"],
|
||||||
|
|||||||
Reference in New Issue
Block a user