diff --git a/src/panels/lovelace/common/confirm-action.ts b/src/panels/lovelace/common/confirm-action.ts new file mode 100644 index 0000000000..2b783cb3f3 --- /dev/null +++ b/src/panels/lovelace/common/confirm-action.ts @@ -0,0 +1,25 @@ +import type { ConfirmationRestrictionConfig } from "../../../data/lovelace/config/action"; +import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box"; +import { HomeAssistant } from "../../../types"; + +export const confirmAction = async ( + node: HTMLElement, + hass: HomeAssistant, + config: ConfirmationRestrictionConfig, + action: string +): Promise => { + if ( + config.exemptions && + config.exemptions.some((e) => e.user === hass!.user?.id) + ) { + return true; + } + + return showConfirmationDialog(node, { + text: + config.text || + hass.localize("ui.panel.lovelace.cards.actions.action_confirmation", { + action, + }), + }); +}; diff --git a/src/panels/lovelace/editor/structs/action-struct.ts b/src/panels/lovelace/editor/structs/action-struct.ts index d1f8d90c48..ed918e6d1f 100644 --- a/src/panels/lovelace/editor/structs/action-struct.ts +++ b/src/panels/lovelace/editor/structs/action-struct.ts @@ -16,7 +16,7 @@ const actionConfigStructUser = object({ user: string(), }); -const actionConfigStructConfirmation = union([ +export const actionConfigStructConfirmation = union([ boolean(), object({ text: optional(string()), diff --git a/src/panels/lovelace/editor/structs/entities-struct.ts b/src/panels/lovelace/editor/structs/entities-struct.ts index 837f621b19..870e097b85 100644 --- a/src/panels/lovelace/editor/structs/entities-struct.ts +++ b/src/panels/lovelace/editor/structs/entities-struct.ts @@ -1,6 +1,9 @@ import { union, object, string, optional, boolean, enums } from "superstruct"; import { TIMESTAMP_RENDERING_FORMATS } from "../../components/types"; -import { actionConfigStruct } from "./action-struct"; +import { + actionConfigStruct, + actionConfigStructConfirmation, +} from "./action-struct"; export const entitiesConfigStruct = union([ object({ @@ -14,6 +17,7 @@ export const entitiesConfigStruct = union([ tap_action: optional(actionConfigStruct), hold_action: optional(actionConfigStruct), double_tap_action: optional(actionConfigStruct), + confirmation: optional(actionConfigStructConfirmation), }), string(), ]); diff --git a/src/panels/lovelace/entity-rows/hui-button-entity-row.ts b/src/panels/lovelace/entity-rows/hui-button-entity-row.ts index 1b9d5cf583..c649b2664b 100644 --- a/src/panels/lovelace/entity-rows/hui-button-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-button-entity-row.ts @@ -14,6 +14,7 @@ import { hasConfigOrEntityChanged } from "../common/has-changed"; import "../components/hui-generic-entity-row"; import { createEntityNotFoundWarning } from "../components/hui-warning"; import { ActionRowConfig, LovelaceRow } from "./types"; +import { confirmAction } from "../common/confirm-action"; @customElement("hui-button-entity-row") class HuiButtonEntityRow extends LitElement implements LovelaceRow { @@ -69,11 +70,21 @@ class HuiButtonEntityRow extends LitElement implements LovelaceRow { `; } - private _pressButton(ev): void { + private async _pressButton(ev): Promise { ev.stopPropagation(); - this.hass.callService("button", "press", { - entity_id: this._config!.entity, - }); + if ( + !this._config?.confirmation || + (await confirmAction( + this, + this.hass, + this._config.confirmation, + this.hass.localize("ui.card.button.press") + )) + ) { + this.hass.callService("button", "press", { + entity_id: this._config!.entity, + }); + } } } diff --git a/src/panels/lovelace/entity-rows/hui-input-button-entity-row.ts b/src/panels/lovelace/entity-rows/hui-input-button-entity-row.ts index 97b2a08dba..84697b89e5 100644 --- a/src/panels/lovelace/entity-rows/hui-input-button-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-input-button-entity-row.ts @@ -14,6 +14,7 @@ import { hasConfigOrEntityChanged } from "../common/has-changed"; import "../components/hui-generic-entity-row"; import { createEntityNotFoundWarning } from "../components/hui-warning"; import { ActionRowConfig, LovelaceRow } from "./types"; +import { confirmAction } from "../common/confirm-action"; @customElement("hui-input-button-entity-row") class HuiInputButtonEntityRow extends LitElement implements LovelaceRow { @@ -69,11 +70,21 @@ class HuiInputButtonEntityRow extends LitElement implements LovelaceRow { `; } - private _pressButton(ev): void { + private async _pressButton(ev): Promise { ev.stopPropagation(); - this.hass.callService("input_button", "press", { - entity_id: this._config!.entity, - }); + if ( + !this._config?.confirmation || + (await confirmAction( + this, + this.hass, + this._config.confirmation, + this.hass.localize("ui.card.button.press") + )) + ) { + this.hass.callService("input_button", "press", { + entity_id: this._config!.entity, + }); + } } } diff --git a/src/panels/lovelace/entity-rows/hui-lock-entity-row.ts b/src/panels/lovelace/entity-rows/hui-lock-entity-row.ts index 128a80808a..454225cd6e 100644 --- a/src/panels/lovelace/entity-rows/hui-lock-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-lock-entity-row.ts @@ -13,16 +13,17 @@ import { HomeAssistant } from "../../../types"; import { hasConfigOrEntityChanged } from "../common/has-changed"; import "../components/hui-generic-entity-row"; import { createEntityNotFoundWarning } from "../components/hui-warning"; -import { EntityConfig, LovelaceRow } from "./types"; +import { ConfirmableRowConfig, LovelaceRow } from "./types"; import { callProtectedLockService } from "../../../data/lock"; +import { confirmAction } from "../common/confirm-action"; @customElement("hui-lock-entity-row") class HuiLockEntityRow extends LitElement implements LovelaceRow { @property({ attribute: false }) public hass?: HomeAssistant; - @state() private _config?: EntityConfig; + @state() private _config?: ConfirmableRowConfig; - public setConfig(config: EntityConfig): void { + public setConfig(config: ConfirmableRowConfig): void { if (!config) { throw new Error("Invalid configuration"); } @@ -73,15 +74,21 @@ class HuiLockEntityRow extends LitElement implements LovelaceRow { `; } - private _callService(ev): void { + private async _callService(ev): Promise { ev.stopPropagation(); const stateObj = this.hass!.states[this._config!.entity]; - callProtectedLockService( - this, - this.hass!, - stateObj, - stateObj.state === "locked" ? "unlock" : "lock" - ); + const action = stateObj.state === "locked" ? "unlock" : "lock"; + if ( + !this._config?.confirmation || + (await confirmAction( + this, + this.hass!, + this._config.confirmation, + this.hass!.localize(`ui.card.lock.${action}`) + )) + ) { + callProtectedLockService(this, this.hass!, stateObj, action); + } } } diff --git a/src/panels/lovelace/entity-rows/hui-scene-entity-row.ts b/src/panels/lovelace/entity-rows/hui-scene-entity-row.ts index 5d1646d9b2..27ddae5b70 100644 --- a/src/panels/lovelace/entity-rows/hui-scene-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-scene-entity-row.ts @@ -16,6 +16,7 @@ import { hasConfigOrEntityChanged } from "../common/has-changed"; import "../components/hui-generic-entity-row"; import { createEntityNotFoundWarning } from "../components/hui-warning"; import { ActionRowConfig, LovelaceRow } from "./types"; +import { confirmAction } from "../common/confirm-action"; @customElement("hui-scene-entity-row") class HuiSceneEntityRow extends LitElement implements LovelaceRow { @@ -73,9 +74,19 @@ class HuiSceneEntityRow extends LitElement implements LovelaceRow { `; } - private _callService(ev: Event): void { + private async _callService(ev: Event): Promise { ev.stopPropagation(); - activateScene(this.hass, this._config!.entity); + if ( + !this._config?.confirmation || + (await confirmAction( + this, + this.hass, + this._config.confirmation, + this._config.action_name || this.hass.localize("ui.card.scene.activate") + )) + ) { + activateScene(this.hass, this._config!.entity); + } } } diff --git a/src/panels/lovelace/entity-rows/hui-script-entity-row.ts b/src/panels/lovelace/entity-rows/hui-script-entity-row.ts index 87602c939f..e884f40507 100644 --- a/src/panels/lovelace/entity-rows/hui-script-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-script-entity-row.ts @@ -16,6 +16,7 @@ import "../components/hui-generic-entity-row"; import { createEntityNotFoundWarning } from "../components/hui-warning"; import { ActionRowConfig, LovelaceRow } from "./types"; import { showMoreInfoDialog } from "../../../dialogs/more-info/show-ha-more-info-dialog"; +import { confirmAction } from "../common/confirm-action"; @customElement("hui-script-entity-row") class HuiScriptEntityRow extends LitElement implements LovelaceRow { @@ -91,12 +92,20 @@ class HuiScriptEntityRow extends LitElement implements LovelaceRow { this._callService("turn_off"); } - private _runScript(ev): void { + private async _runScript(ev): Promise { ev.stopPropagation(); if (hasScriptFields(this.hass!, this._config!.entity)) { showMoreInfoDialog(this, { entityId: this._config!.entity }); - } else { + } else if ( + !this._config?.confirmation || + (await confirmAction( + this, + this.hass!, + this._config.confirmation, + this._config.action_name || this.hass!.localize("ui.card.script.run") + )) + ) { this._callService("turn_on"); } } diff --git a/src/panels/lovelace/entity-rows/types.ts b/src/panels/lovelace/entity-rows/types.ts index d5c2053418..b8dd9c8d4c 100644 --- a/src/panels/lovelace/entity-rows/types.ts +++ b/src/panels/lovelace/entity-rows/types.ts @@ -1,4 +1,7 @@ -import type { ActionConfig } from "../../../data/lovelace/config/action"; +import type { + ActionConfig, + ConfirmationRestrictionConfig, +} from "../../../data/lovelace/config/action"; import type { HomeAssistant } from "../../../types"; import type { LegacyStateFilter } from "../common/evaluate-filter"; import type { Condition } from "../common/validate-condition"; @@ -11,7 +14,12 @@ export interface EntityConfig { icon?: string; image?: string; } -export interface ActionRowConfig extends EntityConfig { + +export interface ConfirmableRowConfig extends EntityConfig { + confirmation?: ConfirmationRestrictionConfig; +} + +export interface ActionRowConfig extends ConfirmableRowConfig { action_name?: string; } export interface EntityFilterEntityConfig extends EntityConfig {