Z-Wave: Prevent closing the Add Device dialog when user input is required (#20999)

* prevent closing the Z-Wave Add node dialog when user input is required

* ask user for confirmation before leaving page during bootstrapping

* fix: no non-null assertion
This commit is contained in:
AlCalzone 2024-06-12 13:48:40 +02:00 committed by GitHub
parent c7b4e8f37c
commit 9a3f7df25e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 348 additions and 296 deletions

View File

@ -3,6 +3,7 @@ import { mdiAlertCircle, mdiCheckCircle, mdiQrcodeScan } from "@mdi/js";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import { fireEvent } from "../../../../../common/dom/fire_event";
import "../../../../../components/ha-alert";
import type { HaCheckbox } from "../../../../../components/ha-checkbox";
@ -60,7 +61,8 @@ class DialogZWaveJSAddNode extends LitElement {
| "finished"
| "provisioned"
| "validate_dsk_enter_pin"
| "grant_security_classes";
| "grant_security_classes"
| "waiting_for_device";
@state() private _device?: ZWaveJSAddNodeDevice;
@ -86,6 +88,11 @@ class DialogZWaveJSAddNode extends LitElement {
private _qrProcessing = false;
public connectedCallback(): void {
super.connectedCallback();
window.addEventListener("beforeunload", this._onBeforeUnload);
}
public disconnectedCallback(): void {
super.disconnectedCallback();
this._unsubscribe();
@ -106,14 +113,22 @@ class DialogZWaveJSAddNode extends LitElement {
return nothing;
}
// Prevent accidentally closing the dialog in certain stages
const preventClose = this._shouldPreventClose();
const heading = this.hass.localize(
"ui.panel.config.zwave_js.add_node.title"
);
return html`
<ha-dialog
open
@closed=${this.closeDialog}
.heading=${createCloseHeading(
this.hass,
this.hass.localize("ui.panel.config.zwave_js.add_node.title")
)}
.heading=${preventClose
? heading
: createCloseHeading(this.hass, heading)}
scrimClickAction=${ifDefined(preventClose ? "" : undefined)}
escapeKeyAction=${ifDefined(preventClose ? "" : undefined)}
>
${this._status === "loading"
? html`<div style="display: flex; justify-content: center;">
@ -122,6 +137,15 @@ class DialogZWaveJSAddNode extends LitElement {
indeterminate
></ha-circular-progress>
</div>`
: this._status === "waiting_for_device"
? html`<div class="flex-container">
<ha-circular-progress indeterminate></ha-circular-progress>
<p>
${this.hass.localize(
"ui.panel.config.zwave_js.add_node.waiting_for_device"
)}
</p>
</div>`
: this._status === "choose_strategy"
? html`<h3>Choose strategy</h3>
<div class="flex-column">
@ -129,8 +153,8 @@ class DialogZWaveJSAddNode extends LitElement {
.label=${html`<b>Secure if possible</b>
<div class="secondary">
Requires user interaction during inclusion. Fast and
secure with S2 when supported. Fallback to legacy S0 or
no encryption when necessary.
secure with S2 when supported. Fallback to legacy S0
or no encryption when necessary.
</div>`}
>
<ha-radio
@ -146,8 +170,8 @@ class DialogZWaveJSAddNode extends LitElement {
<ha-formfield
.label=${html`<b>Legacy Secure</b>
<div class="secondary">
Uses the older S0 security that is secure, but slow due
to a lot of overhead. Allows securely including S2
Uses the older S0 security that is secure, but slow
due to a lot of overhead. Allows securely including S2
capable devices which fail to be included with S2.
</div>`}
>
@ -190,7 +214,10 @@ class DialogZWaveJSAddNode extends LitElement {
.localize=${this.hass.localize}
@qr-code-scanned=${this._qrCodeScanned}
></ha-qr-scanner>
<mwc-button slot="secondaryAction" @click=${this._startOver}>
<mwc-button
slot="secondaryAction"
@click=${this._startOver}
>
${this.hass.localize(
"ui.panel.config.zwave_js.common.back"
)}
@ -228,7 +255,8 @@ class DialogZWaveJSAddNode extends LitElement {
: this._status === "grant_security_classes"
? html`
<h3>
The device has requested the following security classes:
The device has requested the following security
classes:
</h3>
${this._error
? html`<ha-alert alert-type="error"
@ -471,7 +499,7 @@ class DialogZWaveJSAddNode extends LitElement {
: ""}
<a
href=${`/config/devices/device/${
this._device!.id
this._device?.id
}`}
>
<mwc-button>
@ -529,6 +557,15 @@ class DialogZWaveJSAddNode extends LitElement {
`;
}
private _shouldPreventClose(): boolean {
return (
this._status === "started_specific" ||
this._status === "validate_dsk_enter_pin" ||
this._status === "grant_security_classes" ||
this._status === "waiting_for_device"
);
}
private _chooseInclusionStrategy(): void {
this._unsubscribe();
this._status = "choose_strategy";
@ -639,7 +676,7 @@ class DialogZWaveJSAddNode extends LitElement {
}
private async _validateDskAndEnterPin(): Promise<void> {
this._status = "loading";
this._status = "waiting_for_device";
this._error = undefined;
try {
await zwaveValidateDskAndEnterPin(
@ -656,7 +693,7 @@ class DialogZWaveJSAddNode extends LitElement {
}
private async _grantSecurityClasses(): Promise<void> {
this._status = "loading";
this._status = "waiting_for_device";
this._error = undefined;
try {
await zwaveGrantSecurityClasses(
@ -719,6 +756,12 @@ class DialogZWaveJSAddNode extends LitElement {
this._addNodeTimeoutHandle = undefined;
}
if (message.event === "node found") {
// The user may have to enter a PIN. Until then prevent accidentally
// closing the dialog
this._status = "waiting_for_device";
}
if (message.event === "validate dsk and enter pin") {
this._status = "validate_dsk_enter_pin";
this._dsk = message.dsk;
@ -775,6 +818,13 @@ class DialogZWaveJSAddNode extends LitElement {
}, 90000);
}
private _onBeforeUnload = (event: BeforeUnloadEvent) => {
if (this._shouldPreventClose()) {
event.preventDefault();
}
event.returnValue = true;
};
private _unsubscribe(): void {
if (this._subscribed) {
this._subscribed.then((unsub) => unsub());
@ -791,6 +841,7 @@ class DialogZWaveJSAddNode extends LitElement {
clearTimeout(this._addNodeTimeoutHandle);
}
this._addNodeTimeoutHandle = undefined;
window.removeEventListener("beforeunload", this._onBeforeUnload);
}
public closeDialog(): void {

View File

@ -4863,7 +4863,8 @@
"provisioning_finished": "The device has been added. Once you power it on, it will become available.",
"view_device": "View Device",
"interview_started": "The device is being interviewed. This may take some time.",
"interview_failed": "The device interview failed. Additional information may be available in the logs."
"interview_failed": "The device interview failed. Additional information may be available in the logs.",
"waiting_for_device": "Communicating with the device. Please wait."
},
"provisioned": {
"dsk": "DSK",