mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-16 13:56:35 +00:00
Merge pull request #10846 from home-assistant/dev
This commit is contained in:
commit
cdc3d11181
@ -120,7 +120,7 @@ class UpdateAvailableCard extends LitElement {
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
const changelog = changelogUrl(this._updateType, this._version);
|
const changelog = changelogUrl(this._updateType, this._version_latest);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-card
|
<ha-card
|
||||||
|
2
setup.py
2
setup.py
@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="home-assistant-frontend",
|
name="home-assistant-frontend",
|
||||||
version="20211206.0",
|
version="20211209.0",
|
||||||
description="The Home Assistant frontend",
|
description="The Home Assistant frontend",
|
||||||
url="https://github.com/home-assistant/frontend",
|
url="https://github.com/home-assistant/frontend",
|
||||||
author="The Home Assistant Authors",
|
author="The Home Assistant Authors",
|
||||||
|
@ -122,14 +122,20 @@ class HaDurationInput extends LitElement {
|
|||||||
value %= 60;
|
value %= 60;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const newValue: HaDurationData = {
|
||||||
|
hours,
|
||||||
|
minutes,
|
||||||
|
seconds: this._seconds,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.enableMillisecond || this._milliseconds) {
|
||||||
|
newValue.milliseconds = this._milliseconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
newValue[unit] = value;
|
||||||
|
|
||||||
fireEvent(this, "value-changed", {
|
fireEvent(this, "value-changed", {
|
||||||
value: {
|
value: newValue,
|
||||||
hours,
|
|
||||||
minutes,
|
|
||||||
seconds: this._seconds,
|
|
||||||
milliseconds: this._milliseconds,
|
|
||||||
...{ [unit]: value },
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import "@material/mwc-select/mwc-select";
|
import "@material/mwc-select/mwc-select";
|
||||||
import type { Select } from "@material/mwc-select/mwc-select";
|
import { mdiCamera } from "@mdi/js";
|
||||||
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
|
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import type QrScanner from "qr-scanner";
|
import type QrScanner from "qr-scanner";
|
||||||
@ -8,6 +8,7 @@ import { fireEvent } from "../common/dom/fire_event";
|
|||||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||||
import { LocalizeFunc } from "../common/translations/localize";
|
import { LocalizeFunc } from "../common/translations/localize";
|
||||||
import "./ha-alert";
|
import "./ha-alert";
|
||||||
|
import "./ha-button-menu";
|
||||||
|
|
||||||
@customElement("ha-qr-scanner")
|
@customElement("ha-qr-scanner")
|
||||||
class HaQrScanner extends LitElement {
|
class HaQrScanner extends LitElement {
|
||||||
@ -58,29 +59,37 @@ class HaQrScanner extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`${this._cameras && this._cameras.length > 1
|
return html`${this._error
|
||||||
? html`<mwc-select
|
|
||||||
.label=${this.localize(
|
|
||||||
"ui.panel.config.zwave_js.add_node.select_camera"
|
|
||||||
)}
|
|
||||||
fixedMenuPosition
|
|
||||||
naturalMenuWidth
|
|
||||||
@closed=${stopPropagation}
|
|
||||||
@selected=${this._cameraChanged}
|
|
||||||
>
|
|
||||||
${this._cameras!.map(
|
|
||||||
(camera) => html`
|
|
||||||
<mwc-list-item .value=${camera.id}>${camera.label}</mwc-list-item>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
</mwc-select>`
|
|
||||||
: ""}
|
|
||||||
${this._error
|
|
||||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||||
: ""}
|
: ""}
|
||||||
${navigator.mediaDevices
|
${navigator.mediaDevices
|
||||||
? html`<video></video>
|
? html`<video></video>
|
||||||
<div id="canvas-container"></div>`
|
<div id="canvas-container">
|
||||||
|
${this._cameras && this._cameras.length > 1
|
||||||
|
? html`<ha-button-menu
|
||||||
|
corner="BOTTOM_START"
|
||||||
|
fixed
|
||||||
|
@closed=${stopPropagation}
|
||||||
|
>
|
||||||
|
<ha-icon-button
|
||||||
|
slot="trigger"
|
||||||
|
.label=${this.localize(
|
||||||
|
"ui.panel.config.zwave_js.add_node.select_camera"
|
||||||
|
)}
|
||||||
|
.path=${mdiCamera}
|
||||||
|
></ha-icon-button>
|
||||||
|
${this._cameras!.map(
|
||||||
|
(camera) => html`
|
||||||
|
<mwc-list-item
|
||||||
|
.value=${camera.id}
|
||||||
|
@click=${this._cameraChanged}
|
||||||
|
>${camera.label}</mwc-list-item
|
||||||
|
>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</ha-button-menu>`
|
||||||
|
: ""}
|
||||||
|
</div>`
|
||||||
: html`<ha-alert alert-type="warning"
|
: html`<ha-alert alert-type="warning"
|
||||||
>${!window.isSecureContext
|
>${!window.isSecureContext
|
||||||
? "You can only use your camera to scan a QR core when using HTTPS."
|
? "You can only use your camera to scan a QR core when using HTTPS."
|
||||||
@ -135,16 +144,23 @@ class HaQrScanner extends LitElement {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private _cameraChanged(ev: CustomEvent): void {
|
private _cameraChanged(ev: CustomEvent): void {
|
||||||
this._qrScanner?.setCamera((ev.target as Select).value);
|
this._qrScanner?.setCamera((ev.target as any).value);
|
||||||
}
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
canvas {
|
canvas {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
mwc-select {
|
#canvas-container {
|
||||||
width: 100%;
|
position: relative;
|
||||||
margin-bottom: 16px;
|
}
|
||||||
|
ha-button-menu {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 8px;
|
||||||
|
right: 8px;
|
||||||
|
background: #727272b2;
|
||||||
|
color: white;
|
||||||
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,7 @@ import {
|
|||||||
PersistentNotification,
|
PersistentNotification,
|
||||||
subscribeNotifications,
|
subscribeNotifications,
|
||||||
} from "../data/persistent_notification";
|
} from "../data/persistent_notification";
|
||||||
|
import { getExternalConfig } from "../external_app/external_config";
|
||||||
import { actionHandler } from "../panels/lovelace/common/directives/action-handler-directive";
|
import { actionHandler } from "../panels/lovelace/common/directives/action-handler-directive";
|
||||||
import { haStyleScrollbar } from "../resources/styles";
|
import { haStyleScrollbar } from "../resources/styles";
|
||||||
import type { HomeAssistant, PanelInfo, Route } from "../types";
|
import type { HomeAssistant, PanelInfo, Route } from "../types";
|
||||||
@ -266,6 +267,11 @@ class HaSidebar extends LitElement {
|
|||||||
subscribeNotifications(this.hass.connection, (notifications) => {
|
subscribeNotifications(this.hass.connection, (notifications) => {
|
||||||
this._notifications = notifications;
|
this._notifications = notifications;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Temporary workaround for a bug in Android. Can be removed in Home Assistant 2022.2
|
||||||
|
if (this.hass.auth.external) {
|
||||||
|
getExternalConfig(this.hass.auth.external);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updated(changedProps) {
|
protected updated(changedProps) {
|
||||||
|
@ -37,6 +37,25 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const clearUrlParams = () => {
|
||||||
|
// Clear auth data from url if we have been able to establish a connection
|
||||||
|
if (location.search.includes("auth_callback=1")) {
|
||||||
|
const searchParams = new URLSearchParams(location.search);
|
||||||
|
// https://github.com/home-assistant/home-assistant-js-websocket/blob/master/lib/auth.ts
|
||||||
|
// Remove all data from QueryCallbackData type
|
||||||
|
searchParams.delete("auth_callback");
|
||||||
|
searchParams.delete("code");
|
||||||
|
searchParams.delete("state");
|
||||||
|
searchParams.delete("storeToken");
|
||||||
|
const search = searchParams.toString();
|
||||||
|
history.replaceState(
|
||||||
|
null,
|
||||||
|
"",
|
||||||
|
`${location.pathname}${search ? `?${search}` : ""}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const authProm = isExternal
|
const authProm = isExternal
|
||||||
? () =>
|
? () =>
|
||||||
import("../external_app/external_auth").then(({ createExternalAuth }) =>
|
import("../external_app/external_auth").then(({ createExternalAuth }) =>
|
||||||
@ -52,23 +71,7 @@ const authProm = isExternal
|
|||||||
const connProm = async (auth) => {
|
const connProm = async (auth) => {
|
||||||
try {
|
try {
|
||||||
const conn = await createConnection({ auth });
|
const conn = await createConnection({ auth });
|
||||||
// Clear auth data from url if we have been able to establish a connection
|
clearUrlParams();
|
||||||
if (location.search.includes("auth_callback=1")) {
|
|
||||||
const searchParams = new URLSearchParams(location.search);
|
|
||||||
// https://github.com/home-assistant/home-assistant-js-websocket/blob/master/lib/auth.ts
|
|
||||||
// Remove all data from QueryCallbackData type
|
|
||||||
searchParams.delete("auth_callback");
|
|
||||||
searchParams.delete("code");
|
|
||||||
searchParams.delete("state");
|
|
||||||
searchParams.delete("storeToken");
|
|
||||||
const search = searchParams.toString();
|
|
||||||
history.replaceState(
|
|
||||||
null,
|
|
||||||
"",
|
|
||||||
`${location.pathname}${search ? `?${search}` : ""}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { auth, conn };
|
return { auth, conn };
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (err !== ERR_INVALID_AUTH) {
|
if (err !== ERR_INVALID_AUTH) {
|
||||||
@ -85,6 +88,7 @@ const connProm = async (auth) => {
|
|||||||
}
|
}
|
||||||
auth = await authProm();
|
auth = await authProm();
|
||||||
const conn = await createConnection({ auth });
|
const conn = await createConnection({ auth });
|
||||||
|
clearUrlParams();
|
||||||
return { auth, conn };
|
return { auth, conn };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
* Auth class that connects to a native app for authentication.
|
* Auth class that connects to a native app for authentication.
|
||||||
*/
|
*/
|
||||||
import { Auth } from "home-assistant-js-websocket";
|
import { Auth } from "home-assistant-js-websocket";
|
||||||
import { ExternalMessaging, InternalMessage } from "./external_messaging";
|
import { ExternalMessaging, EMMessage } from "./external_messaging";
|
||||||
|
|
||||||
const CALLBACK_SET_TOKEN = "externalAuthSetToken";
|
const CALLBACK_SET_TOKEN = "externalAuthSetToken";
|
||||||
const CALLBACK_REVOKE_TOKEN = "externalAuthRevokeToken";
|
const CALLBACK_REVOKE_TOKEN = "externalAuthRevokeToken";
|
||||||
@ -36,7 +36,7 @@ declare global {
|
|||||||
postMessage(payload: BasePayload);
|
postMessage(payload: BasePayload);
|
||||||
};
|
};
|
||||||
externalBus: {
|
externalBus: {
|
||||||
postMessage(payload: InternalMessage);
|
postMessage(payload: EMMessage);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { Connection } from "home-assistant-js-websocket";
|
||||||
import {
|
import {
|
||||||
externalForwardConnectionEvents,
|
externalForwardConnectionEvents,
|
||||||
externalForwardHaptics,
|
externalForwardHaptics,
|
||||||
@ -7,39 +8,50 @@ const CALLBACK_EXTERNAL_BUS = "externalBus";
|
|||||||
|
|
||||||
interface CommandInFlight {
|
interface CommandInFlight {
|
||||||
resolve: (data: any) => void;
|
resolve: (data: any) => void;
|
||||||
reject: (err: ExternalError) => void;
|
reject: (err: EMError) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InternalMessage {
|
export interface EMMessage {
|
||||||
id?: number;
|
id?: number;
|
||||||
type: string;
|
type: string;
|
||||||
payload?: unknown;
|
payload?: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ExternalError {
|
interface EMError {
|
||||||
code: string;
|
code: string;
|
||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ExternalMessageResult {
|
interface EMMessageResultSuccess {
|
||||||
id: number;
|
id: number;
|
||||||
type: "result";
|
type: "result";
|
||||||
success: true;
|
success: true;
|
||||||
result: unknown;
|
result: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ExternalMessageResultError {
|
interface EMMessageResultError {
|
||||||
id: number;
|
id: number;
|
||||||
type: "result";
|
type: "result";
|
||||||
success: false;
|
success: false;
|
||||||
error: ExternalError;
|
error: EMError;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExternalMessage = ExternalMessageResult | ExternalMessageResultError;
|
interface EMExternalMessageRestart {
|
||||||
|
id: number;
|
||||||
|
type: "command";
|
||||||
|
command: "restart";
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExternalMessage =
|
||||||
|
| EMMessageResultSuccess
|
||||||
|
| EMMessageResultError
|
||||||
|
| EMExternalMessageRestart;
|
||||||
|
|
||||||
export class ExternalMessaging {
|
export class ExternalMessaging {
|
||||||
public commands: { [msgId: number]: CommandInFlight } = {};
|
public commands: { [msgId: number]: CommandInFlight } = {};
|
||||||
|
|
||||||
|
public connection?: Connection;
|
||||||
|
|
||||||
public cache: Record<string, any> = {};
|
public cache: Record<string, any> = {};
|
||||||
|
|
||||||
public msgId = 0;
|
public msgId = 0;
|
||||||
@ -54,7 +66,7 @@ export class ExternalMessaging {
|
|||||||
* Send message to external app that expects a response.
|
* Send message to external app that expects a response.
|
||||||
* @param msg message to send
|
* @param msg message to send
|
||||||
*/
|
*/
|
||||||
public sendMessage<T>(msg: InternalMessage): Promise<T> {
|
public sendMessage<T>(msg: EMMessage): Promise<T> {
|
||||||
const msgId = ++this.msgId;
|
const msgId = ++this.msgId;
|
||||||
msg.id = msgId;
|
msg.id = msgId;
|
||||||
|
|
||||||
@ -69,7 +81,9 @@ export class ExternalMessaging {
|
|||||||
* Send message to external app without expecting a response.
|
* Send message to external app without expecting a response.
|
||||||
* @param msg message to send
|
* @param msg message to send
|
||||||
*/
|
*/
|
||||||
public fireMessage(msg: InternalMessage) {
|
public fireMessage(
|
||||||
|
msg: EMMessage | EMMessageResultSuccess | EMMessageResultError
|
||||||
|
) {
|
||||||
if (!msg.id) {
|
if (!msg.id) {
|
||||||
msg.id = ++this.msgId;
|
msg.id = ++this.msgId;
|
||||||
}
|
}
|
||||||
@ -82,6 +96,43 @@ export class ExternalMessaging {
|
|||||||
console.log("Receiving message from external app", msg);
|
console.log("Receiving message from external app", msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (msg.type === "command") {
|
||||||
|
if (!this.connection) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn("Received command without having connection set", msg);
|
||||||
|
this.fireMessage({
|
||||||
|
id: msg.id,
|
||||||
|
type: "result",
|
||||||
|
success: false,
|
||||||
|
error: {
|
||||||
|
code: "commands_not_init",
|
||||||
|
message: `Commands connection not set`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else if (msg.command === "restart") {
|
||||||
|
this.connection.socket.close();
|
||||||
|
this.fireMessage({
|
||||||
|
id: msg.id,
|
||||||
|
type: "result",
|
||||||
|
success: true,
|
||||||
|
result: null,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn("Received unknown command", msg.command, msg);
|
||||||
|
this.fireMessage({
|
||||||
|
id: msg.id,
|
||||||
|
type: "result",
|
||||||
|
success: false,
|
||||||
|
error: {
|
||||||
|
code: "unknown_command",
|
||||||
|
message: `Unknown command ${msg.command}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const pendingCmd = this.commands[msg.id];
|
const pendingCmd = this.commands[msg.id];
|
||||||
|
|
||||||
if (!pendingCmd) {
|
if (!pendingCmd) {
|
||||||
@ -99,7 +150,7 @@ export class ExternalMessaging {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _sendExternal(msg: InternalMessage) {
|
protected _sendExternal(msg: EMMessage) {
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log("Sending message to external app", msg);
|
console.log("Sending message to external app", msg);
|
||||||
|
@ -127,7 +127,9 @@ export class HassRouterPage extends ReactiveElement {
|
|||||||
|
|
||||||
// Update the url if we know where we're mounted.
|
// Update the url if we know where we're mounted.
|
||||||
if (route) {
|
if (route) {
|
||||||
navigate(`${route.prefix}/${result}`, { replace: true });
|
navigate(`${route.prefix}/${result}${location.search}`, {
|
||||||
|
replace: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,9 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
|
|||||||
const path = curPath();
|
const path = curPath();
|
||||||
|
|
||||||
if (["", "/"].includes(path)) {
|
if (["", "/"].includes(path)) {
|
||||||
navigate(`/${getStorageDefaultPanelUrlPath()}`, { replace: true });
|
navigate(`/${getStorageDefaultPanelUrlPath()}${location.search}`, {
|
||||||
|
replace: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
this._route = {
|
this._route = {
|
||||||
prefix: "",
|
prefix: "",
|
||||||
|
@ -138,7 +138,8 @@ export default class HaAutomationConditionEditor extends LitElement {
|
|||||||
if (!ev.detail.isValid) {
|
if (!ev.detail.isValid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fireEvent(this, "value-changed", { value: ev.detail.value });
|
// @ts-ignore
|
||||||
|
fireEvent(this, "value-changed", { value: ev.detail.value, yaml: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
|
@ -109,6 +109,7 @@ export default class HaAutomationConditionRow extends LitElement {
|
|||||||
: ""}
|
: ""}
|
||||||
<ha-automation-condition-editor
|
<ha-automation-condition-editor
|
||||||
@ui-mode-not-available=${this._handleUiModeNotAvailable}
|
@ui-mode-not-available=${this._handleUiModeNotAvailable}
|
||||||
|
@value-changed=${this._handleChangeEvent}
|
||||||
.yamlMode=${this._yamlMode}
|
.yamlMode=${this._yamlMode}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.condition=${this.condition}
|
.condition=${this.condition}
|
||||||
@ -127,6 +128,12 @@ export default class HaAutomationConditionRow extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _handleChangeEvent(ev: CustomEvent) {
|
||||||
|
if (ev.detail.yaml) {
|
||||||
|
this._warnings = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _handleAction(ev: CustomEvent<ActionDetail>) {
|
private _handleAction(ev: CustomEvent<ActionDetail>) {
|
||||||
switch (ev.detail.index) {
|
switch (ev.detail.index) {
|
||||||
case 0:
|
case 0:
|
||||||
|
@ -291,6 +291,7 @@ export default class HaAutomationTriggerRow extends LitElement {
|
|||||||
if (!ev.detail.isValid) {
|
if (!ev.detail.isValid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this._warnings = undefined;
|
||||||
fireEvent(this, "value-changed", { value: ev.detail.value });
|
fireEvent(this, "value-changed", { value: ev.detail.value });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,7 +131,7 @@ class HaConfigDashboard extends LitElement {
|
|||||||
border-bottom: var(--app-header-border-bottom);
|
border-bottom: var(--app-header-border-bottom);
|
||||||
--header-height: 55px;
|
--header-height: 55px;
|
||||||
}
|
}
|
||||||
ha-card:last-child {
|
:host(:not([narrow])) ha-card:last-child {
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
ha-config-section {
|
ha-config-section {
|
||||||
@ -152,7 +152,7 @@ class HaConfigDashboard extends LitElement {
|
|||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
:host([narrow]) ha-card {
|
:host([narrow]) ha-card {
|
||||||
background-color: var(--primary-background-color);
|
border-radius: 0;
|
||||||
box-shadow: unset;
|
box-shadow: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,8 +43,7 @@ class HaConfigNavigation extends LitElement {
|
|||||||
<paper-item-body two-line>
|
<paper-item-body two-line>
|
||||||
${page.name ||
|
${page.name ||
|
||||||
this.hass.localize(
|
this.hass.localize(
|
||||||
page.translationKey ||
|
`ui.panel.config.dashboard.${page.translationKey}.title`
|
||||||
`ui.panel.config.${page.component}.caption`
|
|
||||||
)}
|
)}
|
||||||
${page.component === "cloud" && (page.info as CloudStatus)
|
${page.component === "cloud" && (page.info as CloudStatus)
|
||||||
? page.info.logged_in
|
? page.info.logged_in
|
||||||
@ -68,7 +67,7 @@ class HaConfigNavigation extends LitElement {
|
|||||||
<div secondary>
|
<div secondary>
|
||||||
${page.description ||
|
${page.description ||
|
||||||
this.hass.localize(
|
this.hass.localize(
|
||||||
`ui.panel.config.${page.component}.description`
|
`ui.panel.config.dashboard.${page.translationKey}.description`
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
`}
|
`}
|
||||||
|
@ -49,80 +49,69 @@ export const configSections: { [name: string]: PageNavigation[] } = {
|
|||||||
dashboard: [
|
dashboard: [
|
||||||
{
|
{
|
||||||
path: "/config/integrations",
|
path: "/config/integrations",
|
||||||
name: "Devices & Services",
|
translationKey: "devices",
|
||||||
description: "Integrations, devices, entities and areas",
|
|
||||||
iconPath: mdiDevices,
|
iconPath: mdiDevices,
|
||||||
iconColor: "#0D47A1",
|
iconColor: "#0D47A1",
|
||||||
core: true,
|
core: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/config/automation",
|
path: "/config/automation",
|
||||||
name: "Automations & Scenes",
|
translationKey: "automations",
|
||||||
description: "Manage automations, scenes, scripts and helpers",
|
|
||||||
iconPath: mdiRobot,
|
iconPath: mdiRobot,
|
||||||
iconColor: "#518C43",
|
iconColor: "#518C43",
|
||||||
core: true,
|
core: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/config/blueprint",
|
path: "/config/blueprint",
|
||||||
name: "Blueprints",
|
translationKey: "blueprints",
|
||||||
description: "Manage blueprints",
|
|
||||||
iconPath: mdiPaletteSwatch,
|
iconPath: mdiPaletteSwatch,
|
||||||
iconColor: "#64B5F6",
|
iconColor: "#64B5F6",
|
||||||
component: "blueprint",
|
component: "blueprint",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/hassio",
|
path: "/hassio",
|
||||||
name: "Add-ons, Backups & Supervisor",
|
translationKey: "supervisor",
|
||||||
description: "Create backups, check logs or reboot your system",
|
|
||||||
iconPath: mdiHomeAssistant,
|
iconPath: mdiHomeAssistant,
|
||||||
iconColor: "#4084CD",
|
iconColor: "#4084CD",
|
||||||
component: "hassio",
|
component: "hassio",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/config/lovelace/dashboards",
|
path: "/config/lovelace/dashboards",
|
||||||
name: "Dashboards",
|
translationKey: "dashboards",
|
||||||
description: "Create customized sets of cards to control your home",
|
|
||||||
iconPath: mdiViewDashboard,
|
iconPath: mdiViewDashboard,
|
||||||
iconColor: "#B1345C",
|
iconColor: "#B1345C",
|
||||||
component: "lovelace",
|
component: "lovelace",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/config/energy",
|
path: "/config/energy",
|
||||||
name: "Energy",
|
translationKey: "energy",
|
||||||
description: "Monitor your energy production and consumption",
|
|
||||||
iconPath: mdiLightningBolt,
|
iconPath: mdiLightningBolt,
|
||||||
iconColor: "#F1C447",
|
iconColor: "#F1C447",
|
||||||
component: "energy",
|
component: "energy",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/config/tags",
|
path: "/config/tags",
|
||||||
name: "Tags",
|
translationKey: "tags",
|
||||||
description:
|
|
||||||
"Trigger automations when a NFC tag, QR code, etc. is scanned",
|
|
||||||
iconPath: mdiNfcVariant,
|
iconPath: mdiNfcVariant,
|
||||||
iconColor: "#616161",
|
iconColor: "#616161",
|
||||||
component: "tag",
|
component: "tag",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/config/person",
|
path: "/config/person",
|
||||||
name: "People & Zones",
|
translationKey: "people",
|
||||||
description: "Manage the people and zones that Home Assistant tracks",
|
|
||||||
iconPath: mdiAccount,
|
iconPath: mdiAccount,
|
||||||
iconColor: "#E48629",
|
iconColor: "#E48629",
|
||||||
components: ["person", "zone", "users"],
|
components: ["person", "zone", "users"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "#external-app-configuration",
|
path: "#external-app-configuration",
|
||||||
name: "Companion App",
|
translationKey: "companion",
|
||||||
description: "Location and notifications",
|
|
||||||
iconPath: mdiCellphoneCog,
|
iconPath: mdiCellphoneCog,
|
||||||
iconColor: "#8E24AA",
|
iconColor: "#8E24AA",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/config/core",
|
path: "/config/core",
|
||||||
name: "Settings",
|
translationKey: "settings",
|
||||||
description: "Basic settings, server controls, logs and info",
|
|
||||||
iconPath: mdiCog,
|
iconPath: mdiCog,
|
||||||
iconColor: "#4A5963",
|
iconColor: "#4A5963",
|
||||||
core: true,
|
core: true,
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
import type { TextField } from "@material/mwc-textfield/mwc-textfield";
|
|
||||||
import "@material/mwc-textfield/mwc-textfield";
|
import "@material/mwc-textfield/mwc-textfield";
|
||||||
import { mdiAlertCircle, mdiCheckCircle, mdiQrcodeScan } from "@mdi/js";
|
import { mdiAlertCircle, mdiCheckCircle, mdiQrcodeScan } from "@mdi/js";
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
@ -183,17 +182,9 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
.localize=${this.hass.localize}
|
.localize=${this.hass.localize}
|
||||||
@qr-code-scanned=${this._qrCodeScanned}
|
@qr-code-scanned=${this._qrCodeScanned}
|
||||||
></ha-qr-scanner>
|
></ha-qr-scanner>
|
||||||
<p>
|
<mwc-button slot="secondaryAction" @click=${this._startOver}>
|
||||||
If scanning doesn't work, you can enter the QR code value
|
${this.hass.localize("ui.panel.config.zwave_js.common.back")}
|
||||||
manually:
|
</mwc-button>`
|
||||||
</p>
|
|
||||||
<mwc-textfield
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.add_node.enter_qr_code"
|
|
||||||
)}
|
|
||||||
.disabled=${this._qrProcessing}
|
|
||||||
@keydown=${this._qrKeyDown}
|
|
||||||
></mwc-textfield>`
|
|
||||||
: this._status === "validate_dsk_enter_pin"
|
: this._status === "validate_dsk_enter_pin"
|
||||||
? html`
|
? html`
|
||||||
<p>
|
<p>
|
||||||
@ -274,7 +265,7 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
We have not found any device in inclusion mode. Make sure the
|
We have not found any device in inclusion mode. Make sure the
|
||||||
device is active and in inclusion mode.
|
device is active and in inclusion mode.
|
||||||
</p>
|
</p>
|
||||||
<mwc-button slot="primaryAction" @click=${this._startInclusion}>
|
<mwc-button slot="primaryAction" @click=${this._startOver}>
|
||||||
Retry
|
Retry
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
`
|
`
|
||||||
@ -373,7 +364,7 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
|
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
|
||||||
${this.hass.localize("ui.panel.config.zwave_js.common.close")}
|
${this.hass.localize("ui.common.close")}
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
`
|
`
|
||||||
: this._status === "failed"
|
: this._status === "failed"
|
||||||
@ -510,15 +501,6 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
this._status = "qr_scan";
|
this._status = "qr_scan";
|
||||||
}
|
}
|
||||||
|
|
||||||
private _qrKeyDown(ev: KeyboardEvent) {
|
|
||||||
if (this._qrProcessing) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (ev.key === "Enter") {
|
|
||||||
this._handleQrCodeScanned((ev.target as TextField).value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _qrCodeScanned(ev: CustomEvent): void {
|
private _qrCodeScanned(ev: CustomEvent): void {
|
||||||
if (this._qrProcessing) {
|
if (this._qrProcessing) {
|
||||||
return;
|
return;
|
||||||
@ -574,11 +556,7 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
}
|
}
|
||||||
} else if (provisioningInfo.version === 0) {
|
} else if (provisioningInfo.version === 0) {
|
||||||
this._inclusionStrategy = InclusionStrategy.Security_S2;
|
this._inclusionStrategy = InclusionStrategy.Security_S2;
|
||||||
// this._startInclusion(provisioningInfo);
|
this._startInclusion(provisioningInfo);
|
||||||
this._startInclusion(undefined, undefined, {
|
|
||||||
dsk: "34673-15546-46480-39591-32400-22155-07715-45994",
|
|
||||||
security_classes: [0, 1, 7],
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
this._error = "This QR code is not supported";
|
this._error = "This QR code is not supported";
|
||||||
this._status = "failed";
|
this._status = "failed";
|
||||||
@ -636,6 +614,11 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
ZWaveFeature.SmartStart
|
ZWaveFeature.SmartStart
|
||||||
)
|
)
|
||||||
).supported;
|
).supported;
|
||||||
|
this._supportsSmartStart = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _startOver(_ev: Event) {
|
||||||
|
this._startInclusion();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _startInclusion(
|
private _startInclusion(
|
||||||
|
@ -106,6 +106,7 @@ class DialogUserDetail extends LitElement {
|
|||||||
.dir=${computeRTLDirection(this.hass)}
|
.dir=${computeRTLDirection(this.hass)}
|
||||||
>
|
>
|
||||||
<ha-switch
|
<ha-switch
|
||||||
|
.disabled=${user.system_generated}
|
||||||
.checked=${this._localOnly}
|
.checked=${this._localOnly}
|
||||||
@change=${this._localOnlyChanged}
|
@change=${this._localOnlyChanged}
|
||||||
>
|
>
|
||||||
|
@ -18,6 +18,7 @@ import "../../../components/ha-code-editor";
|
|||||||
import "../../../components/ha-icon-button";
|
import "../../../components/ha-icon-button";
|
||||||
import "../../../components/ha-svg-icon";
|
import "../../../components/ha-svg-icon";
|
||||||
import "../../../components/ha-checkbox";
|
import "../../../components/ha-checkbox";
|
||||||
|
import "../../../components/ha-expansion-panel";
|
||||||
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||||
import { EventsMixin } from "../../../mixins/events-mixin";
|
import { EventsMixin } from "../../../mixins/events-mixin";
|
||||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||||
@ -40,6 +41,10 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
|||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ha-expansion-panel {
|
||||||
|
margin: 0 8px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.inputs {
|
.inputs {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
@ -135,72 +140,72 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<p>
|
|
||||||
[[localize('ui.panel.developer-tools.tabs.states.description1')]]<br />
|
|
||||||
[[localize('ui.panel.developer-tools.tabs.states.description2')]]
|
|
||||||
</p>
|
|
||||||
<div class="state-wrapper flex layout horizontal">
|
|
||||||
<div class="inputs">
|
|
||||||
<ha-entity-picker
|
|
||||||
autofocus
|
|
||||||
hass="[[hass]]"
|
|
||||||
value="{{_entityId}}"
|
|
||||||
on-change="entityIdChanged"
|
|
||||||
allow-custom-entity
|
|
||||||
></ha-entity-picker>
|
|
||||||
<paper-input
|
|
||||||
label="[[localize('ui.panel.developer-tools.tabs.states.state')]]"
|
|
||||||
required
|
|
||||||
autocapitalize="none"
|
|
||||||
autocomplete="off"
|
|
||||||
autocorrect="off"
|
|
||||||
spellcheck="false"
|
|
||||||
value="{{_state}}"
|
|
||||||
class="state-input"
|
|
||||||
></paper-input>
|
|
||||||
<p>
|
|
||||||
[[localize('ui.panel.developer-tools.tabs.states.state_attributes')]]
|
|
||||||
</p>
|
|
||||||
<ha-code-editor
|
|
||||||
mode="yaml"
|
|
||||||
value="[[_stateAttributes]]"
|
|
||||||
error="[[!validJSON]]"
|
|
||||||
on-value-changed="_yamlChanged"
|
|
||||||
></ha-code-editor>
|
|
||||||
<div class="button-row">
|
|
||||||
<mwc-button
|
|
||||||
on-click="handleSetState"
|
|
||||||
disabled="[[!validJSON]]"
|
|
||||||
raised
|
|
||||||
>[[localize('ui.panel.developer-tools.tabs.states.set_state')]]</mwc-button
|
|
||||||
>
|
|
||||||
<ha-icon-button
|
|
||||||
on-click="entityIdChanged"
|
|
||||||
label="[[localize('ui.common.refresh')]]"
|
|
||||||
path="[[refreshIcon()]]"
|
|
||||||
></ha-icon-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="info">
|
|
||||||
<template is="dom-if" if="[[_entity]]">
|
|
||||||
<p>
|
|
||||||
<b
|
|
||||||
>[[localize('ui.panel.developer-tools.tabs.states.last_changed')]]:</b
|
|
||||||
><br />[[lastChangedString(_entity)]]
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<b
|
|
||||||
>[[localize('ui.panel.developer-tools.tabs.states.last_updated')]]:</b
|
|
||||||
><br />[[lastUpdatedString(_entity)]]
|
|
||||||
</p>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h1>
|
<h1>
|
||||||
[[localize('ui.panel.developer-tools.tabs.states.current_entities')]]
|
[[localize('ui.panel.developer-tools.tabs.states.current_entities')]]
|
||||||
</h1>
|
</h1>
|
||||||
|
<ha-expansion-panel header="Set state">
|
||||||
|
<p>
|
||||||
|
[[localize('ui.panel.developer-tools.tabs.states.description1')]]<br />
|
||||||
|
[[localize('ui.panel.developer-tools.tabs.states.description2')]]
|
||||||
|
</p>
|
||||||
|
<div class="state-wrapper flex layout horizontal">
|
||||||
|
<div class="inputs">
|
||||||
|
<ha-entity-picker
|
||||||
|
autofocus
|
||||||
|
hass="[[hass]]"
|
||||||
|
value="{{_entityId}}"
|
||||||
|
on-change="entityIdChanged"
|
||||||
|
allow-custom-entity
|
||||||
|
></ha-entity-picker>
|
||||||
|
<paper-input
|
||||||
|
label="[[localize('ui.panel.developer-tools.tabs.states.state')]]"
|
||||||
|
required
|
||||||
|
autocapitalize="none"
|
||||||
|
autocomplete="off"
|
||||||
|
autocorrect="off"
|
||||||
|
spellcheck="false"
|
||||||
|
value="{{_state}}"
|
||||||
|
class="state-input"
|
||||||
|
></paper-input>
|
||||||
|
<p>
|
||||||
|
[[localize('ui.panel.developer-tools.tabs.states.state_attributes')]]
|
||||||
|
</p>
|
||||||
|
<ha-code-editor
|
||||||
|
mode="yaml"
|
||||||
|
value="[[_stateAttributes]]"
|
||||||
|
error="[[!validJSON]]"
|
||||||
|
on-value-changed="_yamlChanged"
|
||||||
|
></ha-code-editor>
|
||||||
|
<div class="button-row">
|
||||||
|
<mwc-button
|
||||||
|
on-click="handleSetState"
|
||||||
|
disabled="[[!validJSON]]"
|
||||||
|
raised
|
||||||
|
>[[localize('ui.panel.developer-tools.tabs.states.set_state')]]</mwc-button
|
||||||
|
>
|
||||||
|
<ha-icon-button
|
||||||
|
on-click="entityIdChanged"
|
||||||
|
label="[[localize('ui.common.refresh')]]"
|
||||||
|
path="[[refreshIcon()]]"
|
||||||
|
></ha-icon-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info">
|
||||||
|
<template is="dom-if" if="[[_entity]]">
|
||||||
|
<p>
|
||||||
|
<b
|
||||||
|
>[[localize('ui.panel.developer-tools.tabs.states.last_changed')]]:</b
|
||||||
|
><br />[[lastChangedString(_entity)]]
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b
|
||||||
|
>[[localize('ui.panel.developer-tools.tabs.states.last_updated')]]:</b
|
||||||
|
><br />[[lastUpdatedString(_entity)]]
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ha-expansion-panel>
|
||||||
<div class="table-wrapper">
|
<div class="table-wrapper">
|
||||||
<table class="entities">
|
<table class="entities">
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -698,7 +698,7 @@ class HUIRoot extends LitElement {
|
|||||||
|
|
||||||
private _navigateToView(path: string | number, replace?: boolean) {
|
private _navigateToView(path: string | number, replace?: boolean) {
|
||||||
if (!this.lovelace!.editMode) {
|
if (!this.lovelace!.editMode) {
|
||||||
navigate(`${this.route!.prefix}/${path}`, { replace });
|
navigate(`${this.route!.prefix}/${path}${location.search}`, { replace });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
navigate(`${this.route!.prefix}/${path}?${addSearchParam({ edit: "1" })}`, {
|
navigate(`${this.route!.prefix}/${path}?${addSearchParam({ edit: "1" })}`, {
|
||||||
|
15
src/state/external-mixin.ts
Normal file
15
src/state/external-mixin.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { Constructor } from "../types";
|
||||||
|
import { HassBaseEl } from "./hass-base-mixin";
|
||||||
|
|
||||||
|
export const ExternalMixin = <T extends Constructor<HassBaseEl>>(
|
||||||
|
superClass: T
|
||||||
|
) =>
|
||||||
|
class extends superClass {
|
||||||
|
protected hassConnected() {
|
||||||
|
super.hassConnected();
|
||||||
|
|
||||||
|
if (this.hass!.auth.external) {
|
||||||
|
this.hass!.auth.external.connection = this.hass!.connection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -6,6 +6,7 @@ import DisconnectToastMixin from "./disconnect-toast-mixin";
|
|||||||
import { hapticMixin } from "./haptic-mixin";
|
import { hapticMixin } from "./haptic-mixin";
|
||||||
import { HassBaseEl } from "./hass-base-mixin";
|
import { HassBaseEl } from "./hass-base-mixin";
|
||||||
import { loggingMixin } from "./logging-mixin";
|
import { loggingMixin } from "./logging-mixin";
|
||||||
|
import { ExternalMixin } from "./external-mixin";
|
||||||
import MoreInfoMixin from "./more-info-mixin";
|
import MoreInfoMixin from "./more-info-mixin";
|
||||||
import NotificationMixin from "./notification-mixin";
|
import NotificationMixin from "./notification-mixin";
|
||||||
import { panelTitleMixin } from "./panel-title-mixin";
|
import { panelTitleMixin } from "./panel-title-mixin";
|
||||||
@ -31,4 +32,5 @@ export class HassElement extends ext(HassBaseEl, [
|
|||||||
hapticMixin,
|
hapticMixin,
|
||||||
panelTitleMixin,
|
panelTitleMixin,
|
||||||
loggingMixin,
|
loggingMixin,
|
||||||
|
ExternalMixin,
|
||||||
]) {}
|
]) {}
|
||||||
|
@ -131,5 +131,7 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
|
|||||||
(themeMeta.getAttribute("default-content") as string);
|
(themeMeta.getAttribute("default-content") as string);
|
||||||
themeMeta.setAttribute("content", themeColor);
|
themeMeta.setAttribute("content", themeColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.hass!.auth.external?.fireMessage({ type: "theme-update" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -928,9 +928,47 @@
|
|||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"header": "Configure Home Assistant",
|
"header": "Configure Home Assistant",
|
||||||
"advanced_mode": {
|
"dashboard": {
|
||||||
"hint_enable": "Missing config options? Enable advanced mode on",
|
"devices": {
|
||||||
"link_profile_page": "your profile page"
|
"title": "Devices & Services",
|
||||||
|
"description": "Integrations, devices, entities and areas"
|
||||||
|
},
|
||||||
|
"automations": {
|
||||||
|
"title": "Automations & Scenes",
|
||||||
|
"description": "Manage automations, scenes, scripts and helpers"
|
||||||
|
},
|
||||||
|
"blueprints": {
|
||||||
|
"title": "Blueprints",
|
||||||
|
"description": "Manage blueprints"
|
||||||
|
},
|
||||||
|
"supervisor": {
|
||||||
|
"title": "Add-ons, Backups & Supervisor",
|
||||||
|
"description": "Create backups, check logs or reboot your system"
|
||||||
|
},
|
||||||
|
"dashboards": {
|
||||||
|
"title": "Dashboards",
|
||||||
|
"description": "Create customized sets of cards to control your home"
|
||||||
|
},
|
||||||
|
"energy": {
|
||||||
|
"title": "Energy",
|
||||||
|
"description": "Monitor your energy production and consumption"
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"title": "Tags",
|
||||||
|
"description": "Trigger automations when a NFC tag, QR code, etc. is scanned"
|
||||||
|
},
|
||||||
|
"people": {
|
||||||
|
"title": "People & Zones",
|
||||||
|
"description": "Manage the people and zones that Home Assistant tracks"
|
||||||
|
},
|
||||||
|
"companion": {
|
||||||
|
"title": "Companion App",
|
||||||
|
"description": "Location and notifications"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"title": "Settings",
|
||||||
|
"description": "Basic settings, server controls, logs and info"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"editor": {
|
"editor": {
|
||||||
@ -2813,7 +2851,7 @@
|
|||||||
"node_id": "Device ID",
|
"node_id": "Device ID",
|
||||||
"home_id": "Home ID",
|
"home_id": "Home ID",
|
||||||
"source": "Source",
|
"source": "Source",
|
||||||
"close": "Close",
|
"back": "Back",
|
||||||
"add_node": "Add device",
|
"add_node": "Add device",
|
||||||
"remove_node": "Remove device",
|
"remove_node": "Remove device",
|
||||||
"reconfigure_server": "Re-configure Server",
|
"reconfigure_server": "Re-configure Server",
|
||||||
|
@ -2,16 +2,16 @@ import { assert } from "chai";
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
ExternalMessaging,
|
ExternalMessaging,
|
||||||
InternalMessage,
|
EMMessage,
|
||||||
} from "../../src/external_app/external_messaging";
|
} from "../../src/external_app/external_messaging";
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
global.__DEV__ = true;
|
global.__DEV__ = true;
|
||||||
|
|
||||||
class MockExternalMessaging extends ExternalMessaging {
|
class MockExternalMessaging extends ExternalMessaging {
|
||||||
public mockSent: InternalMessage[] = [];
|
public mockSent: EMMessage[] = [];
|
||||||
|
|
||||||
protected _sendExternal(msg: InternalMessage) {
|
protected _sendExternal(msg: EMMessage) {
|
||||||
this.mockSent.push(msg);
|
this.mockSent.push(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user