diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts
index f62a33fd7a..bda6105400 100644
--- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts
+++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts
@@ -61,7 +61,7 @@ export const handleChangeEvent = (element: TriggerElement, ev: CustomEvent) => {
if (!name) {
return;
}
- const newVal = ev.detail.value;
+ const newVal = (ev.target as any)?.value;
if ((element.trigger[name] || "") === newVal) {
return;
diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-webhook.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-webhook.ts
index 4d90ec0219..fecd09535d 100644
--- a/src/panels/config/automation/trigger/types/ha-automation-trigger-webhook.ts
+++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-webhook.ts
@@ -1,39 +1,133 @@
-import "@polymer/paper-input/paper-input";
-import { html, LitElement } from "lit";
-import { customElement, property } from "lit/decorators";
-import { WebhookTrigger } from "../../../../../data/automation";
+import "../../../../../components/ha-icon-button";
+import "../../../../../components/ha-textfield";
+import { UnsubscribeFunc } from "home-assistant-js-websocket";
+import { mdiContentCopy } from "@mdi/js";
+import { css, html, LitElement, PropertyValues } from "lit";
+import { customElement, property, state } from "lit/decorators";
+import { fireEvent } from "../../../../../common/dom/fire_event";
+import { slugify } from "../../../../../common/string/slugify";
+import { copyToClipboard } from "../../../../../common/util/copy-clipboard";
+import type { HaTextField } from "../../../../../components/ha-textfield";
+import { showToast } from "../../../../../util/toast";
+import {
+ WebhookTrigger,
+ AutomationConfig,
+} from "../../../../../data/automation";
import { HomeAssistant } from "../../../../../types";
import { handleChangeEvent } from "../ha-automation-trigger-row";
+const DEFAULT_WEBHOOK_ID = "";
+
@customElement("ha-automation-trigger-webhook")
export class HaWebhookTrigger extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public trigger!: WebhookTrigger;
+ @state() private _config?: AutomationConfig;
+
+ private _unsub?: UnsubscribeFunc;
+
public static get defaultConfig() {
return {
- webhook_id: "",
+ webhook_id: DEFAULT_WEBHOOK_ID,
};
}
+ connectedCallback() {
+ super.connectedCallback();
+ const details = {
+ callback: (config) => {
+ this._config = config;
+ },
+ };
+ fireEvent(this, "subscribe-automation-config", details);
+ this._unsub = (details as any).unsub;
+ }
+
+ disconnectedCallback() {
+ super.disconnectedCallback();
+ if (this._unsub) {
+ this._unsub();
+ }
+ }
+
+ private _generateWebhookId(): string {
+ // The webhook_id should be treated like a password. Generate a default
+ // value that would be hard for someone to guess. This generates a
+ // 144-bit random value. The output is a 24 character url-safe string.
+ const randomBytes = crypto.getRandomValues(new Uint8Array(18));
+ const base64Str = btoa(String.fromCharCode(...randomBytes));
+ const urlSafeId = base64Str.replace(/\+/g, "-").replace(/\//g, "_");
+
+ // Include the automation name to give the user context about what the
+ // webhook_id is used for.
+ const urlSafeAlias = slugify(this._config?.alias || "", "-");
+
+ return `${urlSafeAlias}-${urlSafeId}`;
+ }
+
+ public willUpdate(changedProperties: PropertyValues) {
+ super.willUpdate(changedProperties);
+ if (changedProperties.has("trigger")) {
+ if (this.trigger.webhook_id === DEFAULT_WEBHOOK_ID) {
+ this.trigger.webhook_id = this._generateWebhookId();
+ }
+ }
+ }
+
protected render() {
const { webhook_id: webhookId } = this.trigger;
+
return html`
-
+ .helper=${this.hass.localize(
+ "ui.panel.config.automation.editor.triggers.type.webhook.webhook_id_helper"
+ )}
+ .iconTrailing=${true}
+ .value=${webhookId || ""}
+ @input=${this._valueChanged}
+ >
+
+
`;
}
private _valueChanged(ev: CustomEvent): void {
handleChangeEvent(this, ev);
}
+
+ private async _copyUrl(ev): Promise {
+ const inputElement = ev.target.parentElement as HaTextField;
+ const url = this.hass.hassUrl(`/api/webhook/${inputElement.value}`);
+
+ await copyToClipboard(url);
+ showToast(this, {
+ message: this.hass.localize("ui.common.copied_clipboard"),
+ });
+ }
+
+ static styles = css`
+ ha-textfield {
+ display: block;
+ }
+
+ ha-textfield > ha-icon-button {
+ --mdc-icon-button-size: 24px;
+ --mdc-icon-size: 18px;
+ }
+ `;
}
declare global {
diff --git a/src/translations/en.json b/src/translations/en.json
index 6668462050..2583b4d528 100755
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -1678,8 +1678,10 @@
"seconds": "Seconds"
},
"webhook": {
+ "copy_url": "Copy URL to Clipboard",
"label": "Webhook",
- "webhook_id": "Webhook ID"
+ "webhook_id": "Webhook ID",
+ "webhook_id_helper": "Treat this ID like a password: keep it secret, and make it hard to guess."
},
"zone": {
"label": "Zone",