mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-18 14:56:37 +00:00
Improve cloud dashboard (#11422)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
parent
68bee4dd58
commit
f398692e75
@ -51,11 +51,13 @@ export interface CloudStatusLoggedIn {
|
|||||||
google_registered: boolean;
|
google_registered: boolean;
|
||||||
google_entities: EntityFilter;
|
google_entities: EntityFilter;
|
||||||
google_domains: string[];
|
google_domains: string[];
|
||||||
|
alexa_registered: boolean;
|
||||||
alexa_entities: EntityFilter;
|
alexa_entities: EntityFilter;
|
||||||
prefs: CloudPreferences;
|
prefs: CloudPreferences;
|
||||||
remote_domain: string | undefined;
|
remote_domain: string | undefined;
|
||||||
remote_connected: boolean;
|
remote_connected: boolean;
|
||||||
remote_certificate: undefined | CertificateInformation;
|
remote_certificate: undefined | CertificateInformation;
|
||||||
|
http_use_ssl: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CloudStatus = CloudStatusNotLoggedIn | CloudStatusLoggedIn;
|
export type CloudStatus = CloudStatusNotLoggedIn | CloudStatusLoggedIn;
|
||||||
|
@ -8,3 +8,6 @@ export interface GoogleEntity {
|
|||||||
|
|
||||||
export const fetchCloudGoogleEntities = (hass: HomeAssistant) =>
|
export const fetchCloudGoogleEntities = (hass: HomeAssistant) =>
|
||||||
hass.callWS<GoogleEntity[]>({ type: "cloud/google_assistant/entities" });
|
hass.callWS<GoogleEntity[]>({ type: "cloud/google_assistant/entities" });
|
||||||
|
|
||||||
|
export const syncCloudGoogleEntities = (hass: HomeAssistant) =>
|
||||||
|
hass.callApi("POST", "cloud/google_actions/sync");
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
|
import "@material/mwc-list/mwc-list-item";
|
||||||
|
import type { ActionDetail } from "@material/mwc-list";
|
||||||
import "@polymer/paper-item/paper-item-body";
|
import "@polymer/paper-item/paper-item-body";
|
||||||
|
import { mdiDotsVertical } from "@mdi/js";
|
||||||
import { LitElement, css, html, PropertyValues } from "lit";
|
import { LitElement, css, html, PropertyValues } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { formatDateTime } from "../../../../common/datetime/format_date_time";
|
import { formatDateTime } from "../../../../common/datetime/format_date_time";
|
||||||
@ -7,6 +10,8 @@ import { fireEvent } from "../../../../common/dom/fire_event";
|
|||||||
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
|
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
|
||||||
import "../../../../components/buttons/ha-call-api-button";
|
import "../../../../components/buttons/ha-call-api-button";
|
||||||
import "../../../../components/ha-card";
|
import "../../../../components/ha-card";
|
||||||
|
import "../../../../components/ha-button-menu";
|
||||||
|
import "../../../../components/ha-icon-button";
|
||||||
import {
|
import {
|
||||||
cloudLogout,
|
cloudLogout,
|
||||||
CloudStatusLoggedIn,
|
CloudStatusLoggedIn,
|
||||||
@ -21,9 +26,10 @@ import "./cloud-google-pref";
|
|||||||
import "./cloud-remote-pref";
|
import "./cloud-remote-pref";
|
||||||
import "./cloud-tts-pref";
|
import "./cloud-tts-pref";
|
||||||
import "./cloud-webhooks";
|
import "./cloud-webhooks";
|
||||||
|
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||||
|
|
||||||
@customElement("cloud-account")
|
@customElement("cloud-account")
|
||||||
export class CloudAccount extends LitElement {
|
export class CloudAccount extends SubscribeMixin(LitElement) {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ type: Boolean }) public isWide = false;
|
@property({ type: Boolean }) public isWide = false;
|
||||||
@ -43,6 +49,23 @@ export class CloudAccount extends LitElement {
|
|||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
header="Home Assistant Cloud"
|
header="Home Assistant Cloud"
|
||||||
>
|
>
|
||||||
|
<ha-button-menu
|
||||||
|
slot="toolbar-icon"
|
||||||
|
corner="BOTTOM_START"
|
||||||
|
@action=${this._handleMenuAction}
|
||||||
|
activatable
|
||||||
|
>
|
||||||
|
<ha-icon-button
|
||||||
|
slot="trigger"
|
||||||
|
.label=${this.hass.localize("ui.common.menu")}
|
||||||
|
.path=${mdiDotsVertical}
|
||||||
|
></ha-icon-button>
|
||||||
|
|
||||||
|
<mwc-list-item>
|
||||||
|
${this.hass.localize("ui.panel.config.cloud.account.sign_out")}
|
||||||
|
</mwc-list-item>
|
||||||
|
</ha-button-menu>
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<ha-config-section .isWide=${this.isWide}>
|
<ha-config-section .isWide=${this.isWide}>
|
||||||
<span slot="header">Home Assistant Cloud</span>
|
<span slot="header">Home Assistant Cloud</span>
|
||||||
@ -115,11 +138,6 @@ export class CloudAccount extends LitElement {
|
|||||||
)}
|
)}
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
</a>
|
</a>
|
||||||
<mwc-button @click=${this._handleLogout}
|
|
||||||
>${this.hass.localize(
|
|
||||||
"ui.panel.config.cloud.account.sign_out"
|
|
||||||
)}</mwc-button
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</ha-card>
|
</ha-card>
|
||||||
</ha-config-section>
|
</ha-config-section>
|
||||||
@ -200,6 +218,33 @@ export class CloudAccount extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override hassSubscribe() {
|
||||||
|
const googleCheck = () => {
|
||||||
|
if (!this.cloudStatus?.google_registered) {
|
||||||
|
fireEvent(this, "ha-refresh-cloud-status");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return [
|
||||||
|
this.hass.connection.subscribeEvents(() => {
|
||||||
|
if (!this.cloudStatus?.alexa_registered) {
|
||||||
|
fireEvent(this, "ha-refresh-cloud-status");
|
||||||
|
}
|
||||||
|
}, "alexa_smart_home"),
|
||||||
|
this.hass.connection.subscribeEvents(
|
||||||
|
googleCheck,
|
||||||
|
"google_assistant_command"
|
||||||
|
),
|
||||||
|
this.hass.connection.subscribeEvents(
|
||||||
|
googleCheck,
|
||||||
|
"google_assistant_query"
|
||||||
|
),
|
||||||
|
this.hass.connection.subscribeEvents(
|
||||||
|
googleCheck,
|
||||||
|
"google_assistant_sync"
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
private async _fetchSubscriptionInfo() {
|
private async _fetchSubscriptionInfo() {
|
||||||
this._subscription = await fetchCloudSubscriptionInfo(this.hass);
|
this._subscription = await fetchCloudSubscriptionInfo(this.hass);
|
||||||
if (
|
if (
|
||||||
@ -211,10 +256,13 @@ export class CloudAccount extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _handleLogout() {
|
private async _handleMenuAction(ev: CustomEvent<ActionDetail>) {
|
||||||
|
switch (ev.detail.index) {
|
||||||
|
case 0:
|
||||||
await cloudLogout(this.hass);
|
await cloudLogout(this.hass);
|
||||||
fireEvent(this, "ha-refresh-cloud-status");
|
fireEvent(this, "ha-refresh-cloud-status");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_computeRTLDirection(hass) {
|
_computeRTLDirection(hass) {
|
||||||
return computeRTLDirection(hass);
|
return computeRTLDirection(hass);
|
||||||
@ -237,7 +285,7 @@ export class CloudAccount extends LitElement {
|
|||||||
}
|
}
|
||||||
.card-actions {
|
.card-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
flex-direction: row-reverse;
|
||||||
}
|
}
|
||||||
.card-actions a {
|
.card-actions a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
@ -10,7 +10,7 @@ import { CloudStatusLoggedIn, updateCloudPref } from "../../../../data/cloud";
|
|||||||
import type { HomeAssistant } from "../../../../types";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
|
|
||||||
export class CloudAlexaPref extends LitElement {
|
export class CloudAlexaPref extends LitElement {
|
||||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property() public cloudStatus?: CloudStatusLoggedIn;
|
@property() public cloudStatus?: CloudStatusLoggedIn;
|
||||||
|
|
||||||
@ -21,6 +21,7 @@ export class CloudAlexaPref extends LitElement {
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const alexa_registered = this.cloudStatus.alexa_registered;
|
||||||
const { alexa_enabled, alexa_report_state } = this.cloudStatus!.prefs;
|
const { alexa_enabled, alexa_report_state } = this.cloudStatus!.prefs;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
@ -36,7 +37,22 @@ export class CloudAlexaPref extends LitElement {
|
|||||||
></ha-switch>
|
></ha-switch>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
|
<p>
|
||||||
${this.hass!.localize("ui.panel.config.cloud.account.alexa.info")}
|
${this.hass!.localize("ui.panel.config.cloud.account.alexa.info")}
|
||||||
|
</p>
|
||||||
|
${!alexa_enabled
|
||||||
|
? ""
|
||||||
|
: !alexa_registered
|
||||||
|
? html`
|
||||||
|
<ha-alert
|
||||||
|
.title=${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.account.alexa.not_configured_title"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.account.alexa.not_configured_text"
|
||||||
|
)}
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
@ -61,8 +77,9 @@ export class CloudAlexaPref extends LitElement {
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
${alexa_enabled
|
</ha-alert>
|
||||||
? html`
|
`
|
||||||
|
: html`
|
||||||
<div class="state-reporting">
|
<div class="state-reporting">
|
||||||
<h3>
|
<h3>
|
||||||
${this.hass!.localize(
|
${this.hass!.localize(
|
||||||
@ -81,10 +98,11 @@ export class CloudAlexaPref extends LitElement {
|
|||||||
"ui.panel.config.cloud.account.alexa.info_state_reporting"
|
"ui.panel.config.cloud.account.alexa.info_state_reporting"
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
`
|
`}
|
||||||
: ""}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
|
${alexa_registered
|
||||||
|
? html`
|
||||||
<mwc-button
|
<mwc-button
|
||||||
@click=${this._handleSync}
|
@click=${this._handleSync}
|
||||||
.disabled=${!alexa_enabled || this._syncing}
|
.disabled=${!alexa_enabled || this._syncing}
|
||||||
@ -93,6 +111,8 @@ export class CloudAlexaPref extends LitElement {
|
|||||||
"ui.panel.config.cloud.account.alexa.sync_entities"
|
"ui.panel.config.cloud.account.alexa.sync_entities"
|
||||||
)}
|
)}
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
<div class="spacer"></div>
|
<div class="spacer"></div>
|
||||||
<a href="/config/cloud/alexa">
|
<a href="/config/cloud/alexa">
|
||||||
<mwc-button
|
<mwc-button
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@material/mwc-textfield/mwc-textfield";
|
||||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
import type { TextField } from "@material/mwc-textfield/mwc-textfield";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { property } from "lit/decorators";
|
import { property, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
import "../../../../components/buttons/ha-call-api-button";
|
|
||||||
import "../../../../components/ha-card";
|
|
||||||
import "../../../../components/ha-alert";
|
import "../../../../components/ha-alert";
|
||||||
|
import "../../../../components/ha-card";
|
||||||
import type { HaSwitch } from "../../../../components/ha-switch";
|
import type { HaSwitch } from "../../../../components/ha-switch";
|
||||||
import { CloudStatusLoggedIn, updateCloudPref } from "../../../../data/cloud";
|
import { CloudStatusLoggedIn, updateCloudPref } from "../../../../data/cloud";
|
||||||
|
import { syncCloudGoogleEntities } from "../../../../data/google_assistant";
|
||||||
import { showAlertDialog } from "../../../../dialogs/generic/show-dialog-box";
|
import { showAlertDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||||
import type { HomeAssistant } from "../../../../types";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
import { showSaveSuccessToast } from "../../../../util/toast-saved-success";
|
import { showSaveSuccessToast } from "../../../../util/toast-saved-success";
|
||||||
@ -16,13 +16,16 @@ import { showSaveSuccessToast } from "../../../../util/toast-saved-success";
|
|||||||
export class CloudGooglePref extends LitElement {
|
export class CloudGooglePref extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property() public cloudStatus?: CloudStatusLoggedIn;
|
@property({ attribute: false }) public cloudStatus?: CloudStatusLoggedIn;
|
||||||
|
|
||||||
|
@state() private _syncing = false;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (!this.cloudStatus) {
|
if (!this.cloudStatus) {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const google_registered = this.cloudStatus.google_registered;
|
||||||
const { google_enabled, google_report_state, google_secure_devices_pin } =
|
const { google_enabled, google_report_state, google_secure_devices_pin } =
|
||||||
this.cloudStatus.prefs;
|
this.cloudStatus.prefs;
|
||||||
|
|
||||||
@ -43,7 +46,9 @@ export class CloudGooglePref extends LitElement {
|
|||||||
<p>
|
<p>
|
||||||
${this.hass.localize("ui.panel.config.cloud.account.google.info")}
|
${this.hass.localize("ui.panel.config.cloud.account.google.info")}
|
||||||
</p>
|
</p>
|
||||||
${google_enabled && !this.cloudStatus.google_registered
|
${!google_enabled
|
||||||
|
? ""
|
||||||
|
: !google_registered
|
||||||
? html`
|
? html`
|
||||||
<ha-alert
|
<ha-alert
|
||||||
.title=${this.hass.localize(
|
.title=${this.hass.localize(
|
||||||
@ -80,9 +85,30 @@ export class CloudGooglePref extends LitElement {
|
|||||||
</ul>
|
</ul>
|
||||||
</ha-alert>
|
</ha-alert>
|
||||||
`
|
`
|
||||||
: ""}
|
: html`
|
||||||
${google_enabled
|
${this.cloudStatus.http_use_ssl
|
||||||
? html`
|
? html`
|
||||||
|
<ha-alert
|
||||||
|
alert-type="warning"
|
||||||
|
.title=${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.account.google.http_use_ssl_warning_title"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.account.google.http_use_ssl_warning_text"
|
||||||
|
)}
|
||||||
|
<a
|
||||||
|
href="https://www.nabucasa.com/config/google_assistant/#local-communication"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.common.learn_more"
|
||||||
|
)}</a
|
||||||
|
>
|
||||||
|
</ha-alert>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
|
||||||
<div class="state-reporting">
|
<div class="state-reporting">
|
||||||
<h3>
|
<h3>
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
@ -110,32 +136,33 @@ export class CloudGooglePref extends LitElement {
|
|||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.cloud.account.google.enter_pin_info"
|
"ui.panel.config.cloud.account.google.enter_pin_info"
|
||||||
)}
|
)}
|
||||||
<paper-input
|
<mwc-textfield
|
||||||
label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.panel.config.cloud.account.google.devices_pin"
|
"ui.panel.config.cloud.account.google.devices_pin"
|
||||||
)}
|
)}
|
||||||
id="google_secure_devices_pin"
|
.placeholder=${this.hass.localize(
|
||||||
placeholder=${this.hass.localize(
|
|
||||||
"ui.panel.config.cloud.account.google.enter_pin_hint"
|
"ui.panel.config.cloud.account.google.enter_pin_hint"
|
||||||
)}
|
)}
|
||||||
.value=${google_secure_devices_pin || ""}
|
.value=${google_secure_devices_pin || ""}
|
||||||
@change=${this._pinChanged}
|
@change=${this._pinChanged}
|
||||||
></paper-input>
|
></mwc-textfield>
|
||||||
</div>
|
</div>
|
||||||
`
|
`}
|
||||||
: ""}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
<ha-call-api-button
|
${google_registered
|
||||||
.hass=${this.hass}
|
? html`
|
||||||
.disabled=${!google_enabled}
|
<mwc-button
|
||||||
@hass-api-called=${this._syncEntitiesCalled}
|
@click=${this._handleSync}
|
||||||
path="cloud/google_actions/sync"
|
.disabled=${!google_enabled || this._syncing}
|
||||||
>
|
>
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.cloud.account.google.sync_entities"
|
"ui.panel.config.cloud.account.google.sync_entities"
|
||||||
)}
|
)}
|
||||||
</ha-call-api-button>
|
</mwc-button>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
<div class="spacer"></div>
|
||||||
<a href="/config/cloud/google-assistant">
|
<a href="/config/cloud/google-assistant">
|
||||||
<mwc-button>
|
<mwc-button>
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
@ -148,22 +175,29 @@ export class CloudGooglePref extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _syncEntitiesCalled(ev: CustomEvent) {
|
private async _handleSync() {
|
||||||
if (!ev.detail.success && ev.detail.response.status_code === 404) {
|
this._syncing = true;
|
||||||
this._syncFailed();
|
try {
|
||||||
}
|
await syncCloudGoogleEntities(this.hass!);
|
||||||
}
|
} catch (err: any) {
|
||||||
|
|
||||||
private async _syncFailed() {
|
|
||||||
showAlertDialog(this, {
|
showAlertDialog(this, {
|
||||||
title: this.hass.localize(
|
title: this.hass.localize(
|
||||||
"ui.panel.config.cloud.account.google.not_configured_title"
|
`ui.panel.config.cloud.account.google.${
|
||||||
|
err.status_code === 404
|
||||||
|
? "not_configured_title"
|
||||||
|
: "sync_failed_title"
|
||||||
|
}`
|
||||||
),
|
),
|
||||||
text: this.hass.localize(
|
text: this.hass.localize(
|
||||||
"ui.panel.config.cloud.account.google.not_configured_text"
|
`ui.panel.config.cloud.account.google.${
|
||||||
|
err.status_code === 404 ? "not_configured_text" : "sync_failed_text"
|
||||||
|
}`
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
fireEvent(this, "ha-refresh-cloud-status");
|
fireEvent(this, "ha-refresh-cloud-status");
|
||||||
|
} finally {
|
||||||
|
this._syncing = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _enableToggleChanged(ev) {
|
private async _enableToggleChanged(ev) {
|
||||||
@ -194,7 +228,7 @@ export class CloudGooglePref extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _pinChanged(ev) {
|
private async _pinChanged(ev) {
|
||||||
const input = ev.target as PaperInputElement;
|
const input = ev.target as TextField;
|
||||||
try {
|
try {
|
||||||
await updateCloudPref(this.hass, {
|
await updateCloudPref(this.hass, {
|
||||||
[input.id]: input.value || null,
|
[input.id]: input.value || null,
|
||||||
@ -207,7 +241,7 @@ export class CloudGooglePref extends LitElement {
|
|||||||
"ui.panel.config.cloud.account.google.enter_pin_error"
|
"ui.panel.config.cloud.account.google.enter_pin_error"
|
||||||
)} ${err.message}`
|
)} ${err.message}`
|
||||||
);
|
);
|
||||||
input.value = this.cloudStatus!.prefs.google_secure_devices_pin;
|
input.value = this.cloudStatus!.prefs.google_secure_devices_pin || "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,16 +259,13 @@ export class CloudGooglePref extends LitElement {
|
|||||||
right: auto;
|
right: auto;
|
||||||
left: 24px;
|
left: 24px;
|
||||||
}
|
}
|
||||||
ha-call-api-button {
|
mwc-textfield {
|
||||||
color: var(--primary-color);
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
paper-input {
|
|
||||||
width: 250px;
|
width: 250px;
|
||||||
|
display: block;
|
||||||
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
.card-actions {
|
.card-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
}
|
}
|
||||||
.card-actions a {
|
.card-actions a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
@ -245,6 +276,10 @@ export class CloudGooglePref extends LitElement {
|
|||||||
.secure_devices {
|
.secure_devices {
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
}
|
}
|
||||||
|
.spacer {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.state-reporting {
|
.state-reporting {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-top: 1.5em;
|
margin-top: 1.5em;
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
mdiCloseBox,
|
mdiCloseBox,
|
||||||
mdiCloseBoxMultiple,
|
mdiCloseBoxMultiple,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
|
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
@ -33,9 +34,14 @@ import {
|
|||||||
updateCloudAlexaEntityConfig,
|
updateCloudAlexaEntityConfig,
|
||||||
updateCloudPref,
|
updateCloudPref,
|
||||||
} from "../../../../data/cloud";
|
} from "../../../../data/cloud";
|
||||||
|
import {
|
||||||
|
EntityRegistryEntry,
|
||||||
|
subscribeEntityRegistry,
|
||||||
|
} from "../../../../data/entity_registry";
|
||||||
import { showDomainTogglerDialog } from "../../../../dialogs/domain-toggler/show-dialog-domain-toggler";
|
import { showDomainTogglerDialog } from "../../../../dialogs/domain-toggler/show-dialog-domain-toggler";
|
||||||
import "../../../../layouts/hass-loading-screen";
|
import "../../../../layouts/hass-loading-screen";
|
||||||
import "../../../../layouts/hass-subpage";
|
import "../../../../layouts/hass-subpage";
|
||||||
|
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||||
import { haStyle } from "../../../../resources/styles";
|
import { haStyle } from "../../../../resources/styles";
|
||||||
import type { HomeAssistant } from "../../../../types";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
|
|
||||||
@ -43,7 +49,7 @@ const DEFAULT_CONFIG_EXPOSE = true;
|
|||||||
const IGNORE_INTERFACES = ["Alexa.EndpointHealth"];
|
const IGNORE_INTERFACES = ["Alexa.EndpointHealth"];
|
||||||
|
|
||||||
@customElement("cloud-alexa")
|
@customElement("cloud-alexa")
|
||||||
class CloudAlexa extends LitElement {
|
class CloudAlexa extends SubscribeMixin(LitElement) {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property()
|
@property()
|
||||||
@ -53,9 +59,15 @@ class CloudAlexa extends LitElement {
|
|||||||
|
|
||||||
@state() private _entities?: AlexaEntity[];
|
@state() private _entities?: AlexaEntity[];
|
||||||
|
|
||||||
@property()
|
@state()
|
||||||
private _entityConfigs: CloudPreferences["alexa_entity_configs"] = {};
|
private _entityConfigs: CloudPreferences["alexa_entity_configs"] = {};
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private _entityCategories?: Record<
|
||||||
|
string,
|
||||||
|
EntityRegistryEntry["entity_category"]
|
||||||
|
>;
|
||||||
|
|
||||||
private _popstateSyncAttached = false;
|
private _popstateSyncAttached = false;
|
||||||
|
|
||||||
private _popstateReloadStatusAttached = false;
|
private _popstateReloadStatusAttached = false;
|
||||||
@ -72,7 +84,7 @@ class CloudAlexa extends LitElement {
|
|||||||
);
|
);
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (this._entities === undefined) {
|
if (this._entities === undefined || this._entityCategories === undefined) {
|
||||||
return html` <hass-loading-screen></hass-loading-screen> `;
|
return html` <hass-loading-screen></hass-loading-screen> `;
|
||||||
}
|
}
|
||||||
const emptyFilter = isEmptyFilter(this.cloudStatus.alexa_entities);
|
const emptyFilter = isEmptyFilter(this.cloudStatus.alexa_entities);
|
||||||
@ -99,10 +111,17 @@ class CloudAlexa extends LitElement {
|
|||||||
should_expose: null,
|
should_expose: null,
|
||||||
};
|
};
|
||||||
const isExposed = emptyFilter
|
const isExposed = emptyFilter
|
||||||
? this._configIsExposed(entity.entity_id, config)
|
? this._configIsExposed(
|
||||||
|
entity.entity_id,
|
||||||
|
config,
|
||||||
|
this._entityCategories![entity.entity_id]
|
||||||
|
)
|
||||||
: filterFunc(entity.entity_id);
|
: filterFunc(entity.entity_id);
|
||||||
const isDomainExposed = emptyFilter
|
const isDomainExposed = emptyFilter
|
||||||
? this._configIsDomainExposed(entity.entity_id)
|
? this._configIsDomainExposed(
|
||||||
|
entity.entity_id,
|
||||||
|
this._entityCategories![entity.entity_id]
|
||||||
|
)
|
||||||
: filterFunc(entity.entity_id);
|
: filterFunc(entity.entity_id);
|
||||||
if (isExposed) {
|
if (isExposed) {
|
||||||
selected++;
|
selected++;
|
||||||
@ -287,6 +306,23 @@ class CloudAlexa extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override hassSubscribe(): (
|
||||||
|
| UnsubscribeFunc
|
||||||
|
| Promise<UnsubscribeFunc>
|
||||||
|
)[] {
|
||||||
|
return [
|
||||||
|
subscribeEntityRegistry(this.hass.connection, (entries) => {
|
||||||
|
const categories = {};
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
categories[entry.entity_id] = entry.entity_category;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._entityCategories = categories;
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
private async _fetchData() {
|
private async _fetchData() {
|
||||||
const entities = await fetchCloudAlexaEntities(this.hass);
|
const entities = await fetchCloudAlexaEntities(this.hass);
|
||||||
entities.sort((a, b) => {
|
entities.sort((a, b) => {
|
||||||
@ -305,15 +341,26 @@ class CloudAlexa extends LitElement {
|
|||||||
fireEvent(this, "hass-more-info", { entityId });
|
fireEvent(this, "hass-more-info", { entityId });
|
||||||
}
|
}
|
||||||
|
|
||||||
private _configIsDomainExposed(entityId: string) {
|
private _configIsDomainExposed(
|
||||||
|
entityId: string,
|
||||||
|
entityCategory: EntityRegistryEntry["entity_category"] | undefined
|
||||||
|
) {
|
||||||
const domain = computeDomain(entityId);
|
const domain = computeDomain(entityId);
|
||||||
return this.cloudStatus.prefs.alexa_default_expose
|
return this.cloudStatus.prefs.alexa_default_expose
|
||||||
? this.cloudStatus.prefs.alexa_default_expose.includes(domain)
|
? !entityCategory &&
|
||||||
|
this.cloudStatus.prefs.alexa_default_expose.includes(domain)
|
||||||
: DEFAULT_CONFIG_EXPOSE;
|
: DEFAULT_CONFIG_EXPOSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _configIsExposed(entityId: string, config: AlexaEntityConfig) {
|
private _configIsExposed(
|
||||||
return config.should_expose ?? this._configIsDomainExposed(entityId);
|
entityId: string,
|
||||||
|
config: AlexaEntityConfig,
|
||||||
|
entityCategory: EntityRegistryEntry["entity_category"] | undefined
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
config.should_expose ??
|
||||||
|
this._configIsDomainExposed(entityId, entityCategory)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _exposeChanged(ev: CustomEvent<ActionDetail>) {
|
private async _exposeChanged(ev: CustomEvent<ActionDetail>) {
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
mdiCloseBox,
|
mdiCloseBox,
|
||||||
mdiCloseBoxMultiple,
|
mdiCloseBoxMultiple,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
|
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
@ -35,6 +36,10 @@ import {
|
|||||||
updateCloudGoogleEntityConfig,
|
updateCloudGoogleEntityConfig,
|
||||||
updateCloudPref,
|
updateCloudPref,
|
||||||
} from "../../../../data/cloud";
|
} from "../../../../data/cloud";
|
||||||
|
import {
|
||||||
|
EntityRegistryEntry,
|
||||||
|
subscribeEntityRegistry,
|
||||||
|
} from "../../../../data/entity_registry";
|
||||||
import {
|
import {
|
||||||
fetchCloudGoogleEntities,
|
fetchCloudGoogleEntities,
|
||||||
GoogleEntity,
|
GoogleEntity,
|
||||||
@ -42,6 +47,7 @@ import {
|
|||||||
import { showDomainTogglerDialog } from "../../../../dialogs/domain-toggler/show-dialog-domain-toggler";
|
import { showDomainTogglerDialog } from "../../../../dialogs/domain-toggler/show-dialog-domain-toggler";
|
||||||
import "../../../../layouts/hass-loading-screen";
|
import "../../../../layouts/hass-loading-screen";
|
||||||
import "../../../../layouts/hass-subpage";
|
import "../../../../layouts/hass-subpage";
|
||||||
|
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||||
import { haStyle } from "../../../../resources/styles";
|
import { haStyle } from "../../../../resources/styles";
|
||||||
import type { HomeAssistant } from "../../../../types";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
import { showToast } from "../../../../util/toast";
|
import { showToast } from "../../../../util/toast";
|
||||||
@ -49,7 +55,7 @@ import { showToast } from "../../../../util/toast";
|
|||||||
const DEFAULT_CONFIG_EXPOSE = true;
|
const DEFAULT_CONFIG_EXPOSE = true;
|
||||||
|
|
||||||
@customElement("cloud-google-assistant")
|
@customElement("cloud-google-assistant")
|
||||||
class CloudGoogleAssistant extends LitElement {
|
class CloudGoogleAssistant extends SubscribeMixin(LitElement) {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property() public cloudStatus!: CloudStatusLoggedIn;
|
@property() public cloudStatus!: CloudStatusLoggedIn;
|
||||||
@ -58,9 +64,15 @@ class CloudGoogleAssistant extends LitElement {
|
|||||||
|
|
||||||
@state() private _entities?: GoogleEntity[];
|
@state() private _entities?: GoogleEntity[];
|
||||||
|
|
||||||
@property()
|
@state()
|
||||||
private _entityConfigs: CloudPreferences["google_entity_configs"] = {};
|
private _entityConfigs: CloudPreferences["google_entity_configs"] = {};
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private _entityCategories?: Record<
|
||||||
|
string,
|
||||||
|
EntityRegistryEntry["entity_category"]
|
||||||
|
>;
|
||||||
|
|
||||||
private _popstateSyncAttached = false;
|
private _popstateSyncAttached = false;
|
||||||
|
|
||||||
private _popstateReloadStatusAttached = false;
|
private _popstateReloadStatusAttached = false;
|
||||||
@ -77,7 +89,7 @@ class CloudGoogleAssistant extends LitElement {
|
|||||||
);
|
);
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (this._entities === undefined) {
|
if (this._entities === undefined || this._entityCategories === undefined) {
|
||||||
return html` <hass-loading-screen></hass-loading-screen> `;
|
return html` <hass-loading-screen></hass-loading-screen> `;
|
||||||
}
|
}
|
||||||
const emptyFilter = isEmptyFilter(this.cloudStatus.google_entities);
|
const emptyFilter = isEmptyFilter(this.cloudStatus.google_entities);
|
||||||
@ -105,10 +117,17 @@ class CloudGoogleAssistant extends LitElement {
|
|||||||
should_expose: null,
|
should_expose: null,
|
||||||
};
|
};
|
||||||
const isExposed = emptyFilter
|
const isExposed = emptyFilter
|
||||||
? this._configIsExposed(entity.entity_id, config)
|
? this._configIsExposed(
|
||||||
|
entity.entity_id,
|
||||||
|
config,
|
||||||
|
this._entityCategories![entity.entity_id]
|
||||||
|
)
|
||||||
: filterFunc(entity.entity_id);
|
: filterFunc(entity.entity_id);
|
||||||
const isDomainExposed = emptyFilter
|
const isDomainExposed = emptyFilter
|
||||||
? this._configIsDomainExposed(entity.entity_id)
|
? this._configIsDomainExposed(
|
||||||
|
entity.entity_id,
|
||||||
|
this._entityCategories![entity.entity_id]
|
||||||
|
)
|
||||||
: filterFunc(entity.entity_id);
|
: filterFunc(entity.entity_id);
|
||||||
if (isExposed) {
|
if (isExposed) {
|
||||||
selected++;
|
selected++;
|
||||||
@ -311,15 +330,43 @@ class CloudGoogleAssistant extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _configIsDomainExposed(entityId: string) {
|
protected override hassSubscribe(): (
|
||||||
|
| UnsubscribeFunc
|
||||||
|
| Promise<UnsubscribeFunc>
|
||||||
|
)[] {
|
||||||
|
return [
|
||||||
|
subscribeEntityRegistry(this.hass.connection, (entries) => {
|
||||||
|
const categories = {};
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
categories[entry.entity_id] = entry.entity_category;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._entityCategories = categories;
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private _configIsDomainExposed(
|
||||||
|
entityId: string,
|
||||||
|
entityCategory: EntityRegistryEntry["entity_category"] | undefined
|
||||||
|
) {
|
||||||
const domain = computeDomain(entityId);
|
const domain = computeDomain(entityId);
|
||||||
return this.cloudStatus.prefs.google_default_expose
|
return this.cloudStatus.prefs.google_default_expose
|
||||||
? this.cloudStatus.prefs.google_default_expose.includes(domain)
|
? !entityCategory &&
|
||||||
|
this.cloudStatus.prefs.google_default_expose.includes(domain)
|
||||||
: DEFAULT_CONFIG_EXPOSE;
|
: DEFAULT_CONFIG_EXPOSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _configIsExposed(entityId: string, config: GoogleEntityConfig) {
|
private _configIsExposed(
|
||||||
return config.should_expose ?? this._configIsDomainExposed(entityId);
|
entityId: string,
|
||||||
|
config: GoogleEntityConfig,
|
||||||
|
entityCategory: EntityRegistryEntry["entity_category"] | undefined
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
config.should_expose ??
|
||||||
|
this._configIsDomainExposed(entityId, entityCategory)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _fetchData() {
|
private async _fetchData() {
|
||||||
|
@ -2115,25 +2115,30 @@
|
|||||||
"sync_entities_error": "Failed to sync entities:",
|
"sync_entities_error": "Failed to sync entities:",
|
||||||
"state_reporting_error": "Unable to {enable_disable} report state.",
|
"state_reporting_error": "Unable to {enable_disable} report state.",
|
||||||
"enable": "enable",
|
"enable": "enable",
|
||||||
"disable": "disable"
|
"disable": "disable",
|
||||||
|
"not_configured_title": "Alexa is not activated",
|
||||||
|
"not_configured_text": "Before you can use Alexa, you need to activate the Home Assistant skill for Alexa in the Alexa app."
|
||||||
},
|
},
|
||||||
"google": {
|
"google": {
|
||||||
"title": "Google Assistant",
|
"title": "Google Assistant",
|
||||||
"info": "With the Google Assistant integration for Home Assistant Cloud you'll be able to control all your Home Assistant devices via any Google Assistant-enabled device.",
|
"info": "With the Google Assistant integration for Home Assistant Cloud you'll be able to control all your Home Assistant devices via any Google Assistant-enabled device.",
|
||||||
|
"http_use_ssl_warning_title": "Local communication unavailable",
|
||||||
|
"http_use_ssl_warning_text": "Google devices will not be able to talk locally with Home Assistant because you have configured an SSL certificate for your HTTP integration.",
|
||||||
"enable_ha_skill": "Activate the Home Assistant Cloud skill for Google Assistant",
|
"enable_ha_skill": "Activate the Home Assistant Cloud skill for Google Assistant",
|
||||||
"config_documentation": "Configuration documentation",
|
"config_documentation": "Configuration documentation",
|
||||||
"enable_state_reporting": "Enable State Reporting",
|
"enable_state_reporting": "Enable State Reporting",
|
||||||
"info_state_reporting": "If you enable state reporting, Home Assistant will send all state changes of exposed entities to Google. This allows you to always see the latest states in the Google app.",
|
"info_state_reporting": "If you enable state reporting, Home Assistant will send all state changes of exposed entities to Google. This speeds up voice commands and allows you to always see the latest states in the Google app.",
|
||||||
"security_devices": "Security Devices",
|
"security_devices": "Security Devices",
|
||||||
"enter_pin_info": "Please enter a PIN to interact with security devices. Security devices are doors, garage doors and locks. You will be asked to say/enter this PIN when interacting with such devices via Google Assistant.",
|
"enter_pin_info": "Please enter a PIN to interact with security devices. Security devices are doors, garage doors and locks. You will be asked to say/enter this PIN when interacting with such devices via Google Assistant.",
|
||||||
"devices_pin": "Security Devices PIN",
|
"devices_pin": "Security Devices PIN",
|
||||||
"enter_pin_hint": "Enter a PIN to use security devices",
|
"enter_pin_hint": "Enter a PIN to use security devices",
|
||||||
"sync_entities": "Sync Entities to Google",
|
"sync_entities": "Sync Entities to Google",
|
||||||
"sync_entities_404_message": "Failed to sync your entities to Google, ask Google 'Hey Google, sync my devices' to sync your entities.",
|
|
||||||
"manage_entities": "Manage Entities",
|
"manage_entities": "Manage Entities",
|
||||||
"enter_pin_error": "Unable to store PIN:",
|
"enter_pin_error": "Unable to store PIN:",
|
||||||
"not_configured_title": "Google Assistant is not activated",
|
"not_configured_title": "Google Assistant is not activated",
|
||||||
"not_configured_text": "Before you can use Google Assistant, you need to activate the Home Assistant Cloud skill for Google Assistant in the Google Home app."
|
"not_configured_text": "Before you can use Google Assistant, you need to activate the Home Assistant Cloud skill for Google Assistant in the Google Home app.",
|
||||||
|
"sync_failed_title": "Syncing failed",
|
||||||
|
"sync_failed_text": "Syncing your entities failed, try again or check the logs."
|
||||||
},
|
},
|
||||||
"webhooks": {
|
"webhooks": {
|
||||||
"title": "Webhooks",
|
"title": "Webhooks",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user