20230928.0 (#18052)

This commit is contained in:
Paul Bottein 2023-09-28 20:15:04 +02:00 committed by GitHub
commit 47022d3a04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 722 additions and 526 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -182,7 +182,7 @@
"@types/luxon": "3.3.2",
"@types/mocha": "10.0.1",
"@types/qrcode": "1.5.2",
"@types/serve-handler": "6.1.1",
"@types/serve-handler": "6.1.2",
"@types/sortablejs": "1.15.2",
"@types/tar": "6.1.6",
"@types/ua-parser-js": "0.7.37",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 992 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 561 B

After

Width:  |  Height:  |  Size: 477 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 850 B

After

Width:  |  Height:  |  Size: 597 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 686 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "home-assistant-frontend"
version = "20230926.0"
version = "20230928.0"
license = {text = "Apache-2.0"}
description = "The Home Assistant frontend"
readme = "README.md"

View File

@ -138,41 +138,54 @@ const tryDescribeTrigger = (
// Numeric State Trigger
if (trigger.platform === "numeric_state" && trigger.entity_id) {
let base = "When";
const stateObj = hass.states[trigger.entity_id];
const entity = stateObj ? computeStateName(stateObj) : trigger.entity_id;
if (trigger.attribute) {
base += ` ${computeAttributeNameDisplay(
hass.localize,
stateObj,
hass.entities,
trigger.attribute
)} from`;
const attribute = trigger.attribute
? computeAttributeNameDisplay(
hass.localize,
stateObj,
hass.entities,
trigger.attribute
)
: undefined;
const duration = trigger.for ? describeDuration(trigger.for) : undefined;
if (trigger.above && trigger.below) {
return hass.localize(
`${triggerTranslationBaseKey}.numeric_state.description.above-below`,
{
attribute: attribute,
entity: entity,
above: trigger.above,
below: trigger.below,
duration: duration,
}
);
}
base += ` ${entity} is`;
if (trigger.above !== undefined) {
base += ` above ${trigger.above}`;
if (trigger.above) {
return hass.localize(
`${triggerTranslationBaseKey}.numeric_state.description.above`,
{
attribute: attribute,
entity: entity,
above: trigger.above,
duration: duration,
}
);
}
if (trigger.below !== undefined && trigger.above !== undefined) {
base += " and";
if (trigger.below) {
return hass.localize(
`${triggerTranslationBaseKey}.numeric_state.description.below`,
{
attribute: attribute,
entity: entity,
below: trigger.below,
duration: duration,
}
);
}
if (trigger.below !== undefined) {
base += ` below ${trigger.below}`;
}
if (trigger.for) {
const duration = describeDuration(trigger.for);
if (duration) {
base += ` for ${duration}`;
}
}
return base;
}
// State Trigger
@ -825,29 +838,49 @@ const tryDescribeCondition = (
// Numeric State Condition
if (condition.condition === "numeric_state" && condition.entity_id) {
let base = "Confirm";
const stateObj = hass.states[condition.entity_id];
const entity = stateObj ? computeStateName(stateObj) : condition.entity_id;
if ("attribute" in condition) {
base += ` ${condition.attribute} from`;
const attribute = condition.attribute
? computeAttributeNameDisplay(
hass.localize,
stateObj,
hass.entities,
condition.attribute
)
: undefined;
if (condition.above && condition.below) {
return hass.localize(
`${conditionsTranslationBaseKey}.numeric_state.description.above-below`,
{
attribute: attribute,
entity: entity,
above: condition.above,
below: condition.below,
}
);
}
base += ` ${entity} is`;
if ("above" in condition) {
base += ` above ${condition.above}`;
if (condition.above) {
return hass.localize(
`${conditionsTranslationBaseKey}.numeric_state.description.above`,
{
attribute: attribute,
entity: entity,
above: condition.above,
}
);
}
if ("below" in condition && "above" in condition) {
base += " and";
if (condition.below) {
return hass.localize(
`${conditionsTranslationBaseKey}.numeric_state.description.below`,
{
attribute: attribute,
entity: entity,
below: condition.below,
}
);
}
if ("below" in condition) {
base += ` below ${condition.below}`;
}
return base;
}
// Time condition

View File

@ -192,7 +192,7 @@ export interface ZWaveJSController {
supported_function_types: number[];
suc_node_id: number;
supports_timers: boolean;
is_heal_network_active: boolean;
is_rebuilding_routes: boolean;
inclusion_state: InclusionState;
nodes: ZWaveJSNodeStatus[];
}
@ -278,9 +278,9 @@ export interface ZWaveJSRefreshNodeStatusMessage {
stage?: string;
}
export interface ZWaveJSHealNetworkStatusMessage {
export interface ZWaveJSRebuildRoutesStatusMessage {
event: string;
heal_node_status: { [key: number]: string };
rebuild_routes_status: { [key: number]: string };
}
export interface ZWaveJSControllerStatisticsUpdatedMessage {
@ -651,12 +651,12 @@ export const reinterviewZwaveNode = (
}
);
export const healZwaveNode = (
export const rebuildZwaveNodeRoutes = (
hass: HomeAssistant,
device_id: string
): Promise<boolean> =>
hass.callWS({
type: "zwave_js/heal_node",
type: "zwave_js/rebuild_node_routes",
device_id,
});
@ -673,33 +673,33 @@ export const removeFailedZwaveNode = (
}
);
export const healZwaveNetwork = (
export const rebuildZwaveNetworkRoutes = (
hass: HomeAssistant,
entry_id: string
): Promise<UnsubscribeFunc> =>
hass.callWS({
type: "zwave_js/begin_healing_network",
type: "zwave_js/begin_rebuilding_routes",
entry_id,
});
export const stopHealZwaveNetwork = (
export const stopRebuildingZwaveNetworkRoutes = (
hass: HomeAssistant,
entry_id: string
): Promise<UnsubscribeFunc> =>
hass.callWS({
type: "zwave_js/stop_healing_network",
type: "zwave_js/stop_rebuilding_routes",
entry_id,
});
export const subscribeHealZwaveNetworkProgress = (
export const subscribeRebuildZwaveNetworkRoutesProgress = (
hass: HomeAssistant,
entry_id: string,
callbackFunction: (message: ZWaveJSHealNetworkStatusMessage) => void
callbackFunction: (message: ZWaveJSRebuildRoutesStatusMessage) => void
): Promise<UnsubscribeFunc> =>
hass.connection.subscribeMessage(
(message: any) => callbackFunction(message),
{
type: "zwave_js/subscribe_heal_network_progress",
type: "zwave_js/subscribe_rebuild_routes_progress",
entry_id,
}
);

View File

@ -53,9 +53,8 @@ class DialogLightColorFavorite extends LitElement {
): Promise<void> {
this._entry = dialogParams.entry;
this._dialogParams = dialogParams;
this._color = dialogParams.initialColor ?? this._computeCurrentColor();
this._updateModes();
this._loadCurrentColorAndMode(dialogParams.add, dialogParams.defaultMode);
await this.updateComplete;
}
public closeDialog(): void {
@ -82,19 +81,20 @@ class DialogLightColorFavorite extends LitElement {
}
this._modes = modes;
if (this._color) {
this._mode = "color_temp_kelvin" in this._color ? "color_temp" : "color";
} else {
this._mode = this._modes[0];
}
}
private _loadCurrentColorAndMode(
add?: boolean,
defaultMode?: LightPickerMode
) {
private _computeCurrentColor() {
const attributes = this.stateObj!.attributes;
const color_mode = attributes.color_mode;
let currentColor: LightColor | undefined;
let currentMode: LightPickerMode | undefined;
if (color_mode === LightColorMode.XY) {
currentMode = "color";
// XY color not supported for favorites. Try to grab the hs or rgb instead.
if (attributes.hs_color) {
currentColor = { hs_color: attributes.hs_color };
@ -105,21 +105,16 @@ class DialogLightColorFavorite extends LitElement {
color_mode === LightColorMode.COLOR_TEMP &&
attributes.color_temp_kelvin
) {
currentMode = LightColorMode.COLOR_TEMP;
currentColor = {
color_temp_kelvin: attributes.color_temp_kelvin,
};
} else if (attributes[color_mode + "_color"]) {
currentMode = "color";
currentColor = {
[color_mode + "_color"]: attributes[color_mode + "_color"],
} as LightColor;
}
if (add) {
this._color = currentColor;
}
this._mode = defaultMode ?? currentMode ?? this._modes[0];
return currentColor;
}
private _colorChanged(ev: CustomEvent) {
@ -230,7 +225,10 @@ class DialogLightColorFavorite extends LitElement {
<ha-button slot="secondaryAction" dialogAction="cancel">
${this.hass.localize("ui.common.cancel")}
</ha-button>
<ha-button slot="primaryAction" @click=${this._save}
<ha-button
slot="primaryAction"
@click=${this._save}
.disabled=${!this._color}
>${this.hass.localize("ui.common.save")}</ha-button
>
</ha-dialog>

View File

@ -1,12 +1,12 @@
import { mdiCheck, mdiMinus, mdiPlus } from "@mdi/js";
import {
css,
CSSResultGroup,
html,
LitElement,
nothing,
PropertyValues,
TemplateResult,
css,
html,
nothing,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
@ -19,18 +19,17 @@ import {
updateEntityRegistryEntry,
} from "../../../../data/entity_registry";
import {
computeDefaultFavoriteColors,
LightColor,
LightEntity,
computeDefaultFavoriteColors,
} from "../../../../data/light";
import { actionHandler } from "../../../../panels/lovelace/common/directives/action-handler-directive";
import {
loadSortable,
SortableInstance,
loadSortable,
} from "../../../../resources/sortable.ondemand";
import { HomeAssistant } from "../../../../types";
import { showConfirmationDialog } from "../../../generic/show-dialog-box";
import type { LightPickerMode } from "./dialog-light-color-favorite";
import "./ha-favorite-color-button";
import { showLightColorFavoriteDialog } from "./show-dialog-light-color-favorite";
@ -141,7 +140,6 @@ export class HaMoreInfoLightFavoriteColors extends LitElement {
private _add = async () => {
const color = await showLightColorFavoriteDialog(this, {
entry: this.entry!,
add: true,
title: this.hass.localize(
"ui.dialogs.more_info_control.light.favorite_color.add_title"
),
@ -156,13 +154,9 @@ export class HaMoreInfoLightFavoriteColors extends LitElement {
// Make sure the current favorite color is set
fireEvent(this, "favorite-color-edit-started");
await this._apply(index);
const defaultMode: LightPickerMode =
"color_temp_kelvin" in this._favoriteColors[index]
? "color_temp"
: "color";
const color = await showLightColorFavoriteDialog(this, {
entry: this.entry!,
defaultMode,
initialColor: this._favoriteColors[index],
title: this.hass.localize(
"ui.dialogs.more_info_control.light.favorite_color.edit_title"
),

View File

@ -1,13 +1,11 @@
import { fireEvent } from "../../../../common/dom/fire_event";
import { ExtEntityRegistryEntry } from "../../../../data/entity_registry";
import { LightColor } from "../../../../data/light";
import type { LightPickerMode } from "./dialog-light-color-favorite";
export interface LightColorFavoriteDialogParams {
entry: ExtEntityRegistryEntry;
title: string;
defaultMode?: LightPickerMode;
add?: boolean;
initialColor?: LightColor;
submit?: (color?: LightColor) => void;
cancel?: () => void;
}

View File

@ -271,6 +271,7 @@ export const provideHass = (
},
dockedSidebar: "auto",
vibrate: true,
debugConnection: false,
suspendWhenHidden: false,
moreInfoEntityId: null as any,
// @ts-ignore

View File

@ -15,7 +15,7 @@ import {
} from "../../../../../../data/zwave_js";
import { showConfirmationDialog } from "../../../../../../dialogs/generic/show-dialog-box";
import type { HomeAssistant } from "../../../../../../types";
import { showZWaveJSHealNodeDialog } from "../../../../integrations/integration-panels/zwave_js/show-dialog-zwave_js-heal-node";
import { showZWaveJSRebuildNodeRoutesDialog } from "../../../../integrations/integration-panels/zwave_js/show-dialog-zwave_js-rebuild-node-routes";
import { showZWaveJSNodeStatisticsDialog } from "../../../../integrations/integration-panels/zwave_js/show-dialog-zwave_js-node-statistics";
import { showZWaveJSReinterviewNodeDialog } from "../../../../integrations/integration-panels/zwave_js/show-dialog-zwave_js-reinterview-node";
import { showZWaveJSRemoveFailedNodeDialog } from "../../../../integrations/integration-panels/zwave_js/show-dialog-zwave_js-remove-failed-node";
@ -69,10 +69,12 @@ export const getZwaveDeviceActions = async (
}),
},
{
label: hass.localize("ui.panel.config.zwave_js.device_info.heal_node"),
label: hass.localize(
"ui.panel.config.zwave_js.device_info.rebuild_routes"
),
icon: mdiHospitalBox,
action: () =>
showZWaveJSHealNodeDialog(el, {
showZWaveJSRebuildNodeRoutesDialog(el, {
device,
}),
},

View File

@ -8,18 +8,18 @@ import { fireEvent } from "../../../../../common/dom/fire_event";
import { createCloseHeading } from "../../../../../components/ha-dialog";
import {
fetchZwaveNetworkStatus,
healZwaveNetwork,
stopHealZwaveNetwork,
subscribeHealZwaveNetworkProgress,
ZWaveJSHealNetworkStatusMessage,
rebuildZwaveNetworkRoutes,
stopRebuildingZwaveNetworkRoutes,
subscribeRebuildZwaveNetworkRoutesProgress,
ZWaveJSRebuildRoutesStatusMessage,
ZWaveJSNetwork,
} from "../../../../../data/zwave_js";
import { haStyleDialog } from "../../../../../resources/styles";
import { HomeAssistant } from "../../../../../types";
import { ZWaveJSHealNetworkDialogParams } from "./show-dialog-zwave_js-heal-network";
import { ZWaveJSRebuildNetworkRoutesDialogParams } from "./show-dialog-zwave_js-rebuild-network-routes";
@customElement("dialog-zwave_js-heal-network")
class DialogZWaveJSHealNetwork extends LitElement {
@customElement("dialog-zwave_js-rebuild-network-routes")
class DialogZWaveJSRebuildNetworkRoutes extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private entry_id?: string;
@ -34,7 +34,7 @@ class DialogZWaveJSHealNetwork extends LitElement {
private _subscribed?: Promise<UnsubscribeFunc>;
public showDialog(params: ZWaveJSHealNetworkDialogParams): void {
public showDialog(params: ZWaveJSRebuildNetworkRoutesDialogParams): void {
this._progress_total = 0;
this.entry_id = params.entry_id;
this._fetchData();
@ -61,7 +61,9 @@ class DialogZWaveJSHealNetwork extends LitElement {
@closed=${this.closeDialog}
.heading=${createCloseHeading(
this.hass,
this.hass.localize("ui.panel.config.zwave_js.heal_network.title")
this.hass.localize(
"ui.panel.config.zwave_js.rebuild_network_routes.title"
)
)}
>
${!this._status
@ -74,7 +76,7 @@ class DialogZWaveJSHealNetwork extends LitElement {
<div class="status">
<p>
${this.hass.localize(
"ui.panel.config.zwave_js.heal_network.introduction"
"ui.panel.config.zwave_js.rebuild_network_routes.introduction"
)}
</p>
</div>
@ -82,13 +84,16 @@ class DialogZWaveJSHealNetwork extends LitElement {
<p>
<em>
${this.hass.localize(
"ui.panel.config.zwave_js.heal_network.traffic_warning"
"ui.panel.config.zwave_js.rebuild_network_routes.traffic_warning"
)}
</em>
</p>
<mwc-button slot="primaryAction" @click=${this._startHeal}>
<mwc-button
slot="primaryAction"
@click=${this._startRebuildingRoutes}
>
${this.hass.localize(
"ui.panel.config.zwave_js.heal_network.start_heal"
"ui.panel.config.zwave_js.rebuild_network_routes.start_rebuilding_routes"
)}
</mwc-button>
`
@ -99,13 +104,13 @@ class DialogZWaveJSHealNetwork extends LitElement {
<p>
<b>
${this.hass.localize(
"ui.panel.config.zwave_js.heal_network.in_progress"
"ui.panel.config.zwave_js.rebuild_network_routes.in_progress"
)}
</b>
</p>
<p>
${this.hass.localize(
"ui.panel.config.zwave_js.heal_network.run_in_background"
"ui.panel.config.zwave_js.rebuild_network_routes.run_in_background"
)}
</p>
</div>
@ -114,9 +119,12 @@ class DialogZWaveJSHealNetwork extends LitElement {
<mwc-linear-progress indeterminate> </mwc-linear-progress>
`
: ""}
<mwc-button slot="secondaryAction" @click=${this._stopHeal}>
<mwc-button
slot="secondaryAction"
@click=${this._stopRebuildingRoutes}
>
${this.hass.localize(
"ui.panel.config.zwave_js.heal_network.stop_heal"
"ui.panel.config.zwave_js.rebuild_network_routes.stop_rebuilding_routes"
)}
</mwc-button>
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
@ -134,7 +142,7 @@ class DialogZWaveJSHealNetwork extends LitElement {
<div class="status">
<p>
${this.hass.localize(
"ui.panel.config.zwave_js.heal_network.healing_failed"
"ui.panel.config.zwave_js.rebuild_network_routes.rebuilding_routes_failed"
)}
</p>
</div>
@ -154,7 +162,7 @@ class DialogZWaveJSHealNetwork extends LitElement {
<div class="status">
<p>
${this.hass.localize(
"ui.panel.config.zwave_js.heal_network.healing_complete"
"ui.panel.config.zwave_js.rebuild_network_routes.rebuilding_routes_complete"
)}
</p>
</div>
@ -174,7 +182,7 @@ class DialogZWaveJSHealNetwork extends LitElement {
<div class="status">
<p>
${this.hass.localize(
"ui.panel.config.zwave_js.heal_network.healing_cancelled"
"ui.panel.config.zwave_js.rebuild_network_routes.rebuilding_routes_cancelled"
)}
</p>
</div>
@ -205,9 +213,9 @@ class DialogZWaveJSHealNetwork extends LitElement {
const network: ZWaveJSNetwork = await fetchZwaveNetworkStatus(this.hass!, {
entry_id: this.entry_id!,
});
if (network.controller.is_heal_network_active) {
if (network.controller.is_rebuilding_routes) {
this._status = "started";
this._subscribed = subscribeHealZwaveNetworkProgress(
this._subscribed = subscribeRebuildZwaveNetworkRoutesProgress(
this.hass,
this.entry_id!,
this._handleMessage.bind(this)
@ -215,33 +223,33 @@ class DialogZWaveJSHealNetwork extends LitElement {
}
}
private _startHeal(): void {
private _startRebuildingRoutes(): void {
if (!this.hass) {
return;
}
healZwaveNetwork(this.hass, this.entry_id!);
rebuildZwaveNetworkRoutes(this.hass, this.entry_id!);
this._status = "started";
this._subscribed = subscribeHealZwaveNetworkProgress(
this._subscribed = subscribeRebuildZwaveNetworkRoutesProgress(
this.hass,
this.entry_id!,
this._handleMessage.bind(this)
);
}
private _stopHeal(): void {
private _stopRebuildingRoutes(): void {
if (!this.hass) {
return;
}
stopHealZwaveNetwork(this.hass, this.entry_id!);
stopRebuildingZwaveNetworkRoutes(this.hass, this.entry_id!);
this._unsubscribe();
this._status = "cancelled";
}
private _handleMessage(message: ZWaveJSHealNetworkStatusMessage): void {
if (message.event === "heal network progress") {
private _handleMessage(message: ZWaveJSRebuildRoutesStatusMessage): void {
if (message.event === "rebuild routes progress") {
let finished = 0;
let in_progress = 0;
for (const status of Object.values(message.heal_node_status)) {
for (const status of Object.values(message.rebuild_routes_status)) {
if (status === "pending") {
in_progress++;
}
@ -249,11 +257,11 @@ class DialogZWaveJSHealNetwork extends LitElement {
finished++;
}
}
this._progress_total = Object.keys(message.heal_node_status).length;
this._progress_total = Object.keys(message.rebuild_routes_status).length;
this._progress_finished = finished / this._progress_total;
this._progress_in_progress = in_progress / this._progress_total;
}
if (message.event === "heal network done") {
if (message.event === "rebuild routes done") {
this._unsubscribe();
this._status = "finished";
}
@ -306,6 +314,6 @@ class DialogZWaveJSHealNetwork extends LitElement {
declare global {
interface HTMLElementTagNameMap {
"dialog-zwave_js-heal-network": DialogZWaveJSHealNetwork;
"dialog-zwave_js-rebuild-network-routes": DialogZWaveJSRebuildNetworkRoutes;
}
}

View File

@ -11,15 +11,15 @@ import {
} from "../../../../../data/device_registry";
import {
fetchZwaveNetworkStatus,
healZwaveNode,
rebuildZwaveNodeRoutes,
ZWaveJSNetwork,
} from "../../../../../data/zwave_js";
import { haStyleDialog } from "../../../../../resources/styles";
import { HomeAssistant } from "../../../../../types";
import { ZWaveJSHealNodeDialogParams } from "./show-dialog-zwave_js-heal-node";
import { ZWaveJSRebuildNodeRoutesDialogParams } from "./show-dialog-zwave_js-rebuild-node-routes";
@customElement("dialog-zwave_js-heal-node")
class DialogZWaveJSHealNode extends LitElement {
@customElement("dialog-zwave_js-rebuild-node-routes")
class DialogZWaveJSRebuildNodeRoutes extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private device?: DeviceRegistryEntry;
@ -28,7 +28,7 @@ class DialogZWaveJSHealNode extends LitElement {
@state() private _error?: string;
public showDialog(params: ZWaveJSHealNodeDialogParams): void {
public showDialog(params: ZWaveJSRebuildNodeRoutesDialogParams): void {
this.device = params.device;
this._fetchData();
}
@ -52,7 +52,9 @@ class DialogZWaveJSHealNode extends LitElement {
@closed=${this.closeDialog}
.heading=${createCloseHeading(
this.hass,
this.hass.localize("ui.panel.config.zwave_js.heal_node.title")
this.hass.localize(
"ui.panel.config.zwave_js.rebuild_node_routes.title"
)
)}
>
${!this._status
@ -65,7 +67,7 @@ class DialogZWaveJSHealNode extends LitElement {
<div class="status">
<p>
${this.hass.localize(
"ui.panel.config.zwave_js.heal_node.introduction",
"ui.panel.config.zwave_js.rebuild_node_routes.introduction",
{
device: html`<em
>${computeDeviceName(this.device, this.hass!)}</em
@ -78,13 +80,16 @@ class DialogZWaveJSHealNode extends LitElement {
<p>
<em>
${this.hass.localize(
"ui.panel.config.zwave_js.heal_node.traffic_warning"
"ui.panel.config.zwave_js.rebuild_node_routes.traffic_warning"
)}
</em>
</p>
<mwc-button slot="primaryAction" @click=${this._startHeal}>
<mwc-button
slot="primaryAction"
@click=${this._startRebuildingRoutes}
>
${this.hass.localize(
"ui.panel.config.zwave_js.heal_node.start_heal"
"ui.panel.config.zwave_js.rebuild_node_routes.start_rebuilding_routes"
)}
</mwc-button>
`
@ -96,7 +101,7 @@ class DialogZWaveJSHealNode extends LitElement {
<div class="status">
<p>
${this.hass.localize(
"ui.panel.config.zwave_js.heal_node.in_progress",
"ui.panel.config.zwave_js.rebuild_node_routes.in_progress",
{
device: html`<em
>${computeDeviceName(this.device, this.hass!)}</em
@ -121,7 +126,7 @@ class DialogZWaveJSHealNode extends LitElement {
<div class="status">
<p>
${this.hass.localize(
"ui.panel.config.zwave_js.heal_node.healing_failed",
"ui.panel.config.zwave_js.rebuild_node_routes.rebuilding_routes_failed",
{
device: html`<em
>${computeDeviceName(this.device, this.hass!)}</em
@ -134,7 +139,7 @@ class DialogZWaveJSHealNode extends LitElement {
? html` <em>${this._error}</em> `
: `
${this.hass.localize(
"ui.panel.config.zwave_js.heal_node.healing_failed_check_logs"
"ui.panel.config.zwave_js.rebuild_node_routes.rebuilding_routes_failed_check_logs"
)}
`}
</p>
@ -155,7 +160,7 @@ class DialogZWaveJSHealNode extends LitElement {
<div class="status">
<p>
${this.hass.localize(
"ui.panel.config.zwave_js.heal_node.healing_complete",
"ui.panel.config.zwave_js.rebuild_node_routes.rebuilding_routes_complete",
{
device: html`<em
>${computeDeviceName(this.device, this.hass!)}</em
@ -170,7 +175,7 @@ class DialogZWaveJSHealNode extends LitElement {
</mwc-button>
`
: ``}
${this._status === "network-healing"
${this._status === "rebuilding-routes"
? html`
<div class="flex-container">
<ha-svg-icon
@ -180,7 +185,7 @@ class DialogZWaveJSHealNode extends LitElement {
<div class="status">
<p>
${this.hass.localize(
"ui.panel.config.zwave_js.heal_node.network_heal_in_progress"
"ui.panel.config.zwave_js.rebuild_node_routes.routes_rebuild_in_progress"
)}
</p>
</div>
@ -201,18 +206,18 @@ class DialogZWaveJSHealNode extends LitElement {
const network: ZWaveJSNetwork = await fetchZwaveNetworkStatus(this.hass!, {
device_id: this.device!.id,
});
if (network.controller.is_heal_network_active) {
this._status = "network-healing";
if (network.controller.is_rebuilding_routes) {
this._status = "rebuilding-routes";
}
}
private async _startHeal(): Promise<void> {
private async _startRebuildingRoutes(): Promise<void> {
if (!this.hass) {
return;
}
this._status = "started";
try {
this._status = (await healZwaveNode(this.hass, this.device!.id))
this._status = (await rebuildZwaveNodeRoutes(this.hass, this.device!.id))
? "finished"
: "failed";
} catch (err: any) {
@ -258,6 +263,6 @@ class DialogZWaveJSHealNode extends LitElement {
declare global {
interface HTMLElementTagNameMap {
"dialog-zwave_js-heal-node": DialogZWaveJSHealNode;
"dialog-zwave_js-rebuild-node-routes": DialogZWaveJSRebuildNodeRoutes;
}
}

View File

@ -1,19 +0,0 @@
import { fireEvent } from "../../../../../common/dom/fire_event";
export interface ZWaveJSHealNetworkDialogParams {
entry_id: string;
}
export const loadHealNetworkDialog = () =>
import("./dialog-zwave_js-heal-network");
export const showZWaveJSHealNetworkDialog = (
element: HTMLElement,
healNetworkDialogParams: ZWaveJSHealNetworkDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-zwave_js-heal-network",
dialogImport: loadHealNetworkDialog,
dialogParams: healNetworkDialogParams,
});
};

View File

@ -1,19 +0,0 @@
import { fireEvent } from "../../../../../common/dom/fire_event";
import { DeviceRegistryEntry } from "../../../../../data/device_registry";
export interface ZWaveJSHealNodeDialogParams {
device: DeviceRegistryEntry;
}
export const loadHealNodeDialog = () => import("./dialog-zwave_js-heal-node");
export const showZWaveJSHealNodeDialog = (
element: HTMLElement,
healNodeDialogParams: ZWaveJSHealNodeDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-zwave_js-heal-node",
dialogImport: loadHealNodeDialog,
dialogParams: healNodeDialogParams,
});
};

View File

@ -0,0 +1,19 @@
import { fireEvent } from "../../../../../common/dom/fire_event";
export interface ZWaveJSRebuildNetworkRoutesDialogParams {
entry_id: string;
}
export const loadRebuildNetworkRoutesDialog = () =>
import("./dialog-zwave_js-rebuild-network-routes");
export const showZWaveJSRebuildNetworkRoutesDialog = (
element: HTMLElement,
rebuildNetworkRoutesDialogParams: ZWaveJSRebuildNetworkRoutesDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-zwave_js-rebuild-network-routes",
dialogImport: loadRebuildNetworkRoutesDialog,
dialogParams: rebuildNetworkRoutesDialogParams,
});
};

View File

@ -0,0 +1,20 @@
import { fireEvent } from "../../../../../common/dom/fire_event";
import { DeviceRegistryEntry } from "../../../../../data/device_registry";
export interface ZWaveJSRebuildNodeRoutesDialogParams {
device: DeviceRegistryEntry;
}
export const loadRebuildNodeRoutesDialog = () =>
import("./dialog-zwave_js-rebuild-node-routes");
export const showZWaveJSRebuildNodeRoutesDialog = (
element: HTMLElement,
rebuildNodeRoutesDialogParams: ZWaveJSRebuildNodeRoutesDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-zwave_js-rebuild-node-routes",
dialogImport: loadRebuildNodeRoutesDialog,
dialogParams: rebuildNodeRoutesDialogParams,
});
};

View File

@ -52,7 +52,7 @@ import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant, Route } from "../../../../../types";
import "../../../ha-config-section";
import { showZWaveJSAddNodeDialog } from "./show-dialog-zwave_js-add-node";
import { showZWaveJSHealNetworkDialog } from "./show-dialog-zwave_js-heal-network";
import { showZWaveJSRebuildNetworkRoutesDialog } from "./show-dialog-zwave_js-rebuild-network-routes";
import { showZWaveJSRemoveNodeDialog } from "./show-dialog-zwave_js-remove-node";
import { configTabs } from "./zwave_js-config-router";
@ -430,11 +430,11 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
)}
</mwc-button>
<mwc-button
@click=${this._healNetworkClicked}
@click=${this._rebuildNetworkRoutesClicked}
.disabled=${this._status === "disconnected"}
>
${this.hass.localize(
"ui.panel.config.zwave_js.common.heal_network"
"ui.panel.config.zwave_js.common.rebuild_network_routes"
)}
</mwc-button>
<mwc-button @click=${this._openOptionFlow}>
@ -612,8 +612,8 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
});
}
private async _healNetworkClicked() {
showZWaveJSHealNetworkDialog(this, {
private async _rebuildNetworkRoutesClicked() {
showZWaveJSRebuildNetworkRoutesDialog(this, {
entry_id: this.configEntryId!,
});
}

View File

@ -1,4 +1,11 @@
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
import {
css,
CSSResultGroup,
html,
LitElement,
nothing,
PropertyValues,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { LocalizeKeys } from "../../../../common/translations/localize";
@ -6,6 +13,7 @@ import "../../../../components/ha-form/ha-form";
import { AssistPipeline } from "../../../../data/assist_pipeline";
import { HomeAssistant } from "../../../../types";
import { fetchWakeWordInfo, WakeWord } from "../../../../data/wake_word";
import { documentationUrl } from "../../../../util/documentation-url";
@customElement("assist-pipeline-detail-wakeword")
export class AssistPipelineDetailWakeWord extends LitElement {
@ -67,7 +75,12 @@ export class AssistPipelineDetailWakeWord extends LitElement {
}
}
private _hasWakeWorkEntities = memoizeOne((states: HomeAssistant["states"]) =>
Object.keys(states).some((entityId) => entityId.startsWith("wake_word."))
);
protected render() {
const hasWakeWorkEntities = this._hasWakeWorkEntities(this.hass.states);
return html`
<div class="section">
<div class="content">
@ -83,11 +96,25 @@ export class AssistPipelineDetailWakeWord extends LitElement {
)}
</p>
</div>
${!hasWakeWorkEntities
? html`${this.hass.localize(
`ui.panel.config.voice_assistants.assistants.pipeline.detail.steps.wakeword.no_wake_words`
)}
<a
href=${documentationUrl(this.hass, "/docs/assist/")}
target="_blank"
rel="noreferrer noopener"
>${this.hass.localize(
`ui.panel.config.voice_assistants.assistants.pipeline.detail.steps.wakeword.no_wake_words_link`
)}</a
>`
: nothing}
<ha-form
.schema=${this._schema(this._wakeWords)}
.data=${this.data}
.hass=${this.hass}
.computeLabel=${this._computeLabel}
.disabled=${!hasWakeWorkEntities}
></ha-form>
</div>
</div>
@ -129,6 +156,9 @@ export class AssistPipelineDetailWakeWord extends LitElement {
margin-top: 0;
margin-bottom: 0;
}
a {
color: var(--primary-color);
}
`;
}
}

View File

@ -25,6 +25,7 @@ import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box
import type { HomeAssistant } from "../../../types";
import { brandsUrl } from "../../../util/brands-url";
import { showVoiceAssistantPipelineDetailDialog } from "./show-dialog-voice-assistant-pipeline-detail";
import { documentationUrl } from "../../../util/documentation-url";
export class AssistPref extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@ -73,9 +74,9 @@ export class AssistPref extends LitElement {
</h1>
<div class="header-actions">
<a
href="https://www.home-assistant.io/docs/assist/"
href=${documentationUrl(this.hass, "/docs/assist/")}
target="_blank"
rel="noreferrer"
rel="noreferrer noopener"
class="icon-link"
>
<ha-icon-button

View File

@ -307,9 +307,7 @@ export class DialogVoiceAssistantPipelineDetail extends LitElement {
return [
haStyleDialog,
css`
assist-pipeline-detail-config,
assist-pipeline-detail-conversation,
assist-pipeline-detail-stt {
.content > *:not(:last-child) {
margin-bottom: 16px;
display: block;
}

View File

@ -0,0 +1,52 @@
import { CSSResultGroup, LitElement, css, html } from "lit";
import { customElement, property } from "lit/decorators";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import "./ha-debug-connection-row";
@customElement("developer-tools-debug")
class HaPanelDevDebug extends SubscribeMixin(LitElement) {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean }) public narrow!: boolean;
protected render() {
return html`
<div class="content">
<ha-card
.header=${this.hass.localize(
"ui.panel.developer-tools.tabs.debug.title"
)}
class="form"
>
<div class="card-content">
<ha-debug-connection-row
.hass=${this.hass}
.narrow=${this.narrow}
></ha-debug-connection-row>
</ha-card>
</div>
`;
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
.content {
padding: 28px 20px 16px;
display: block;
max-width: 600px;
margin: 0 auto;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"developer-tools-debug": HaPanelDevDebug;
}
}

View File

@ -0,0 +1,50 @@
import { html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../components/ha-settings-row";
import "../../../components/ha-switch";
import type { HaSwitch } from "../../../components/ha-switch";
import type { HomeAssistant } from "../../../types";
import { storeState } from "../../../util/ha-pref-storage";
@customElement("ha-debug-connection-row")
class HaDebugConnectionRow extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public narrow!: boolean;
protected render(): TemplateResult {
return html`
<ha-settings-row .narrow=${this.narrow}>
<span slot="heading">
${this.hass.localize(
"ui.panel.developer-tools.tabs.debug.debug_connection.title"
)}
</span>
<span slot="description">
${this.hass.localize(
"ui.panel.developer-tools.tabs.debug.debug_connection.description"
)}
</span>
<ha-switch
.checked=${this.hass.debugConnection}
@change=${this._checkedChanged}
></ha-switch>
</ha-settings-row>
`;
}
private async _checkedChanged(ev: Event) {
const debugConnection = (ev.target as HaSwitch).checked;
if (debugConnection === this.hass.debugConnection) {
return;
}
this.hass.debugConnection = debugConnection;
storeState(this.hass);
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-debug-connection-row": HaDebugConnectionRow;
}
}

View File

@ -49,6 +49,10 @@ class DeveloperToolsRouter extends HassRouterPage {
tag: "developer-tools-assist",
load: () => import("./assist/developer-tools-assist"),
},
debug: {
tag: "developer-tools-debug",
load: () => import("./debug/developer-tools-debug"),
},
},
};

View File

@ -1,219 +0,0 @@
import "@material/mwc-button";
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { load } from "js-yaml";
import "../../../components/ha-code-editor";
import "../../../components/ha-textfield";
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
import { EventsMixin } from "../../../mixins/events-mixin";
import LocalizeMixin from "../../../mixins/localize-mixin";
import "../../../styles/polymer-ha-style";
import { documentationUrl } from "../../../util/documentation-url";
import "./event-subscribe-card";
import "./events-list";
const ERROR_SENTINEL = {};
/*
* @appliesMixin EventsMixin
* @appliesMixin LocalizeMixin
*/
class HaPanelDevEvent extends EventsMixin(LocalizeMixin(PolymerElement)) {
static get template() {
return html`
<style include="ha-style iron-flex iron-positioning"></style>
<style>
.content {
padding: 16px;
padding: max(16px, env(safe-area-inset-top))
max(16px, env(safe-area-inset-right))
max(16px, env(safe-area-inset-bottom))
max(16px, env(safe-area-inset-left));
max-width: 1200px;
margin: auto;
}
:host {
-ms-user-select: initial;
-webkit-user-select: initial;
-moz-user-select: initial;
@apply --paper-font-body1;
display: block;
}
.inputs {
max-width: 400px;
}
mwc-button {
margin-top: 8px;
}
ha-textfield {
display: block;
}
.code-editor {
margin-right: 16px;
margin-inline-start: initial;
margin-inline-end: 16px;
direction: var(--direction);
}
.header {
@apply --paper-font-title;
}
event-subscribe-card {
display: block;
margin: 16px 16px 0 0;
margin-inline-start: initial;
margin-inline-end: 16px;
direction: var(--direction);
}
a {
color: var(--primary-color);
}
</style>
<div class$="[[computeFormClasses(narrow)]]">
<div class="flex">
<p>
[[localize( 'ui.panel.developer-tools.tabs.events.description' )]]
<a
href="[[_computeDocumentationUrl(hass)]]"
target="_blank"
rel="noreferrer"
>
[[localize( 'ui.panel.developer-tools.tabs.events.documentation'
)]]
</a>
</p>
<div class="inputs">
<ha-textfield
label="[[localize(
'ui.panel.developer-tools.tabs.events.type'
)]]"
autofocus
required
value="[[eventType]]"
on-change="eventTypeChanged"
></ha-textfield>
<p>[[localize( 'ui.panel.developer-tools.tabs.events.data' )]]</p>
</div>
<div class="code-editor">
<ha-code-editor
mode="yaml"
value="[[eventData]]"
error="[[!validJSON]]"
on-value-changed="_yamlChanged"
dir="ltr"
></ha-code-editor>
</div>
<mwc-button on-click="fireEvent" raised disabled="[[!validJSON]]"
>[[localize( 'ui.panel.developer-tools.tabs.events.fire_event'
)]]</mwc-button
>
<event-subscribe-card hass="[[hass]]"></event-subscribe-card>
</div>
<div>
<div class="header">
[[localize( 'ui.panel.developer-tools.tabs.events.active_listeners'
)]]
</div>
<events-list
on-event-selected="eventSelected"
hass="[[hass]]"
></events-list>
</div>
</div>
`;
}
static get properties() {
return {
hass: {
type: Object,
},
eventType: {
type: String,
value: "",
},
eventData: {
type: String,
value: "",
},
parsedJSON: {
type: Object,
computed: "_computeParsedEventData(eventData)",
},
validJSON: {
type: Boolean,
computed: "_computeValidJSON(parsedJSON)",
},
};
}
eventSelected(ev) {
this.eventType = ev.detail.eventType;
}
eventTypeChanged(ev) {
this.eventType = ev.target.value;
}
_computeParsedEventData(eventData) {
try {
return eventData.trim() ? load(eventData) : {};
} catch (err) {
return ERROR_SENTINEL;
}
}
_computeDocumentationUrl(hass) {
return documentationUrl(hass, "/docs/configuration/events/");
}
_computeValidJSON(parsedJSON) {
return parsedJSON !== ERROR_SENTINEL;
}
_yamlChanged(ev) {
this.eventData = ev.detail.value;
}
fireEvent() {
if (!this.eventType) {
showAlertDialog(this, {
text: this.hass.localize(
"ui.panel.developer-tools.tabs.events.alert_event_type"
),
});
return;
}
this.hass
.callApi("POST", "events/" + this.eventType, this.parsedJSON)
.then(() => {
this.fire("hass-notification", {
message: this.hass.localize(
"ui.panel.developer-tools.tabs.events.notification_event_fired",
"type",
this.eventType
),
});
});
}
computeFormClasses(narrow) {
return narrow ? "content" : "content layout horizontal";
}
}
customElements.define("developer-tools-event", HaPanelDevEvent);

View File

@ -0,0 +1,184 @@
import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit";
import { customElement, property, state } from "lit/decorators";
import "@material/mwc-button";
import "../../../components/ha-yaml-editor";
import "../../../components/ha-textfield";
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
import { documentationUrl } from "../../../util/documentation-url";
import "./event-subscribe-card";
import "./events-list";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import { fireEvent } from "../../../common/dom/fire_event";
@customElement("developer-tools-event")
class HaPanelDevEvent extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean }) public narrow!: boolean;
@state() private _eventType: string = "";
@state() private _eventData: object = {};
@state() private _isValid: boolean = true;
protected render(): TemplateResult {
return html`
<div class=${this.narrow ? "content" : "content layout horizontal"}>
<div class="flex">
<p>
${this.hass.localize(
"ui.panel.developer-tools.tabs.events.description"
)}
<a
href=${documentationUrl(this.hass, "/docs/configuration/events/")}
target="_blank"
rel="noreferrer"
>
${this.hass.localize(
"ui.panel.developer-tools.tabs.events.documentation"
)}
</a>
</p>
<div class="inputs">
<ha-textfield
.label=${this.hass.localize(
"ui.panel.developer-tools.tabs.events.type"
)}
autofocus
required
.value=${this._eventType}
@change=${this._eventTypeChanged}
></ha-textfield>
<p>
${this.hass.localize("ui.panel.developer-tools.tabs.events.data")}
</p>
</div>
<div class="code-editor">
<ha-yaml-editor
.value=${this._eventData}
.error=${!this._isValid}
@value-changed=${this._yamlChanged}
></ha-yaml-editor>
</div>
<mwc-button
@click=${this._fireEvent}
raised
.disabled=${!this._isValid}
>${this.hass.localize(
"ui.panel.developer-tools.tabs.events.fire_event"
)}</mwc-button
>
<event-subscribe-card .hass=${this.hass}></event-subscribe-card>
</div>
<div>
<div class="header">
${this.hass.localize(
"ui.panel.developer-tools.tabs.events.active_listeners"
)}
</div>
<events-list
@event-selected=${this._eventSelected}
.hass=${this.hass}
></events-list>
</div>
</div>
`;
}
private _eventSelected(ev) {
this._eventType = ev.detail.eventType;
}
private _eventTypeChanged(ev) {
this._eventType = ev.target.value;
}
private _yamlChanged(ev) {
this._eventData = ev.detail.value;
this._isValid = ev.detail.isValid;
}
private async _fireEvent() {
if (!this._eventType) {
showAlertDialog(this, {
text: this.hass.localize(
"ui.panel.developer-tools.tabs.events.alert_event_type"
),
});
return;
}
await this.hass.callApi(
"POST",
`events/${this._eventType}`,
this._eventData
);
fireEvent(this, "hass-notification", {
message: this.hass.localize(
"ui.panel.developer-tools.tabs.events.notification_event_fired",
{ type: this._eventType }
),
});
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
.content {
padding: 16px;
padding: max(16px, env(safe-area-inset-top))
max(16px, env(safe-area-inset-right))
max(16px, env(safe-area-inset-bottom))
max(16px, env(safe-area-inset-left));
max-width: 1200px;
margin: auto;
}
:host {
-ms-user-select: initial;
-webkit-user-select: initial;
-moz-user-select: initial;
@apply --paper-font-body1;
display: block;
}
.inputs {
max-width: 400px;
}
mwc-button {
margin-top: 8px;
}
ha-textfield {
display: block;
}
.header {
@apply --paper-font-title;
}
event-subscribe-card {
display: block;
margin: 16px 16px 0 0;
margin-inline-start: initial;
margin-inline-end: 16px;
direction: var(--direction);
}
a {
color: var(--primary-color);
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"developer-tools-event": HaPanelDevEvent;
}
}

View File

@ -1,73 +0,0 @@
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { stringCompare } from "../../../common/string/compare";
import { EventsMixin } from "../../../mixins/events-mixin";
import LocalizeMixin from "../../../mixins/localize-mixin";
/*
* @appliesMixin EventsMixin
* @appliesMixin LocalizeMixin
*/
class EventsList extends EventsMixin(LocalizeMixin(PolymerElement)) {
static get template() {
return html`
<style>
ul {
margin: 0;
padding: 0;
}
li {
list-style: none;
line-height: 2em;
}
a {
color: var(--primary-color);
}
</style>
<ul>
<template is="dom-repeat" items="[[events]]" as="event">
<li>
<a href="#" on-click="eventSelected">{{event.event}}</a>
<span>
[[localize(
"ui.panel.developer-tools.tabs.events.count_listeners", "count",
event.listener_count )]]</span
>
</li>
</template>
</ul>
`;
}
static get properties() {
return {
hass: {
type: Object,
},
events: {
type: Array,
},
};
}
connectedCallback() {
super.connectedCallback();
this.hass.callApi("GET", "events").then((events) => {
this.events = events.sort((e1, e2) =>
stringCompare(e1.event, e2.event, this.hass.locale.language)
);
});
}
eventSelected(ev) {
ev.preventDefault();
this.fire("event-selected", { eventType: ev.model.event.event });
}
}
customElements.define("events-list", EventsList);

View File

@ -0,0 +1,84 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import { stringCompare } from "../../../common/string/compare";
import { fireEvent } from "../../../common/dom/fire_event";
import { HomeAssistant } from "../../../types";
interface EventListenerCount {
event: string;
listener_count: number;
}
@customElement("events-list")
class EventsList extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public events: EventListenerCount[] = [];
protected render(): TemplateResult {
return html`
<ul>
${this.events.map(
(event) => html`
<li>
<a href="#" @click=${this._eventSelected} .event=${event.event}
>${event.event}</a
>
<span>
${this.hass.localize(
"ui.panel.developer-tools.tabs.events.count_listeners",
{
count: event.listener_count,
}
)}</span
>
</li>
`
)}
</ul>
`;
}
protected async firstUpdated() {
const events = await this.hass.callApi<EventListenerCount[]>(
"GET",
"events"
);
this.events = events.sort((e1, e2) =>
stringCompare(e1.event, e2.event, this.hass.locale.language)
);
}
private _eventSelected(ev: Event) {
ev.preventDefault();
const event: string = (ev.currentTarget! as any).event;
fireEvent(this, "event-selected", { eventType: event });
}
static get styles(): CSSResultGroup {
return css`
ul {
margin: 0;
padding: 0;
}
li {
list-style: none;
line-height: 2em;
}
a {
color: var(--primary-color);
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"events-list": EventsList;
}
interface HASSDomEvents {
"event-selected": { eventType: string };
}
}

View File

@ -1,9 +1,14 @@
import { mdiDotsVertical } from "@mdi/js";
import "@polymer/paper-tabs/paper-tab";
import "@polymer/paper-tabs/paper-tabs";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import type { ActionDetail } from "@material/mwc-list";
import { navigate } from "../../common/navigate";
import "../../components/ha-menu-button";
import "../../components/ha-button-menu";
import "../../components/ha-icon-button";
import "../../components/ha-list-item";
import { haStyle } from "../../resources/styles";
import { HomeAssistant, Route } from "../../types";
import "./developer-tools-router";
@ -34,6 +39,16 @@ class PanelDeveloperTools extends LitElement {
<div class="main-title">
${this.hass.localize("panel.developer_tools")}
</div>
<ha-button-menu slot="actionItems" @action=${this._handleMenuAction}>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-list-item>
${this.hass.localize("ui.panel.developer-tools.tabs.debug.title")}
</ha-list-item>
</ha-button-menu>
</div>
<paper-tabs
scrollable
@ -85,6 +100,14 @@ class PanelDeveloperTools extends LitElement {
}
}
private async _handleMenuAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:
navigate(`/developer-tools/debug`);
break;
}
}
private get _page() {
return this.route.path.substr(1);
}

View File

@ -73,6 +73,7 @@ export const connectionMixin = <T extends Constructor<HassBaseEl>>(
translationMetadata,
dockedSidebar: "docked",
vibrate: true,
debugConnection: false,
suspendWhenHidden: true,
enableShortcuts: true,
moreInfoEntityId: null,
@ -84,7 +85,7 @@ export const connectionMixin = <T extends Constructor<HassBaseEl>>(
target,
notifyOnError = true
) => {
if (__DEV__) {
if (__DEV__ || this.hass?.debugConnection) {
// eslint-disable-next-line no-console
console.log(
"Calling service",
@ -109,7 +110,7 @@ export const connectionMixin = <T extends Constructor<HassBaseEl>>(
) {
return { context: { id: "" } };
}
if (__DEV__) {
if (__DEV__ || this.hass?.debugConnection) {
// eslint-disable-next-line no-console
console.error(
"Error calling service",
@ -146,7 +147,7 @@ export const connectionMixin = <T extends Constructor<HassBaseEl>>(
) => fetchWithAuth(auth, `${auth.data.hassUrl}${path}`, init),
// For messages that do not get a response
sendWS: (msg) => {
if (__DEV__) {
if (__DEV__ || this.hass?.debugConnection) {
// eslint-disable-next-line no-console
console.log("Sending", msg);
}
@ -154,14 +155,14 @@ export const connectionMixin = <T extends Constructor<HassBaseEl>>(
},
// For messages that expect a response
callWS: <R>(msg) => {
if (__DEV__) {
if (__DEV__ || this.hass?.debugConnection) {
// eslint-disable-next-line no-console
console.log("Sending", msg);
}
const resp = conn.sendMessagePromise<R>(msg);
if (__DEV__) {
if (__DEV__ || this.hass?.debugConnection) {
resp.then(
// eslint-disable-next-line no-console
(result) => console.log("Received", result),

View File

@ -2196,7 +2196,9 @@
},
"wakeword": {
"title": "Wake word",
"description": "If a device supports wake words, you can activate Assist by saying this word."
"description": "If a device supports wake words, you can activate Assist by saying this word.",
"no_wake_words": "It looks like you don't have a wake word engine setup yet.",
"no_wake_words_link": "Find out more about wake words."
}
},
"no_cloud_message": "You should have an active cloud subscription to use cloud speech services.",
@ -2446,7 +2448,12 @@
"mode_below": "Below mode",
"value_template": "Value template",
"type_value": "Fixed number",
"type_input": "Numeric value of another entity"
"type_input": "Numeric value of another entity",
"description": {
"above": "When {attribute, select, \n undefined {} \n other {{attribute} from }\n }{entity} is above {above}{duration, select, \n undefined {} \n other { for {duration}}\n }",
"below": "When {attribute, select, \n undefined {} \n other {{attribute} from }\n }{entity} is below {below}{duration, select, \n undefined {} \n other { for {duration}}\n }",
"above-below": "When {attribute, select, \n undefined {} \n other {{attribute} from }\n }{entity} is above {above} and below {below}{duration, select, \n undefined {} \n other { for {duration}}\n }"
}
},
"persistent_notification": {
"label": "Persistent notification",
@ -2595,7 +2602,12 @@
"below": "[%key:ui::panel::config::automation::editor::triggers::type::numeric_state::below%]",
"mode_above": "[%key:ui::panel::config::automation::editor::triggers::type::numeric_state::mode_above%]",
"mode_below": "[%key:ui::panel::config::automation::editor::triggers::type::numeric_state::mode_below%]",
"value_template": "[%key:ui::panel::config::automation::editor::triggers::type::numeric_state::value_template%]"
"value_template": "[%key:ui::panel::config::automation::editor::triggers::type::numeric_state::value_template%]",
"description": {
"above": "When {attribute, select, \n undefined {} \n other {{attribute} from }\n }{entity} is above {above}",
"below": "When {attribute, select, \n undefined {} \n other {{attribute} from }\n }{entity} is below {below}",
"above-below": "When {attribute, select, \n undefined {} \n other {{attribute} from }\n }{entity} is above {above} and below {below}"
}
},
"or": {
"label": "Or",
@ -3922,7 +3934,7 @@
"add_node": "Add device",
"remove_node": "Remove device",
"reconfigure_server": "Re-configure server",
"heal_network": "Heal Network",
"rebuild_network_routes": "Rebuild network routes",
"in_progress_inclusion_exclusion": "Z-Wave JS is searching for devices",
"cancel_inclusion_exclusion": "Stop searching"
},
@ -3982,7 +3994,7 @@
"node_ready": "Ready",
"device_config": "Configure",
"reinterview_device": "Re-interview",
"heal_node": "Heal",
"rebuild_routes": "Rebuild routes",
"remove_failed": "Remove failed",
"update_firmware": "Update",
"highest_security": "Highest security",
@ -4164,28 +4176,28 @@
"interview_failed": "The device interview failed. Additional information may be available in the logs.",
"interview_complete": "Device interview complete."
},
"heal_network": {
"title": "Heal your Z-Wave network",
"introduction": "Start a network heal on your Z-Wave network. A network heal will cause all devices to re-calculate their routes back to the controller and is recommended if you have recently moved devices or your controller.",
"traffic_warning": "The healing process generates a large amount of traffic on the Z-Wave network. This may cause devices to respond slowly (or not at all) while the heal is in progress.",
"start_heal": "Start healing",
"in_progress": "Network healing is in progress. This will take some time.",
"run_in_background": "You can close this dialog and the network healing will continue in the background.",
"stop_heal": "Stop Healing",
"healing_complete": "Network healing is complete.",
"healing_failed": "Healing failed. Additional information may be available in the logs.",
"healing_cancelled": "Network healing has been cancelled."
"rebuild_network_routes": {
"title": "Rebuild routes for your Z-Wave network",
"introduction": "Start rebuilding routes on your Z-Wave network. Rebuilding routes will cause all devices to re-calculate their routes back to the controller and is recommended if you have recently moved devices or your controller.",
"traffic_warning": "The rebuilding process generates a large amount of traffic on the Z-Wave network. This may cause devices to respond slowly (or not at all) while the rebuild is in progress.",
"start_rebuilding_routes": "Start rebuilding routes",
"in_progress": "Rebuild of network routes is in progress. This will take some time.",
"run_in_background": "You can close this dialog and the rebuild of network routes will continue in the background.",
"stop_rebuilding_routes": "Stop rebuilding routes",
"rebuilding_routes_complete": "Routes have been rebuilt.",
"rebuilding_routes_failed": "Route rebuilding failed. Additional information may be available in the logs.",
"rebuilding_routes_cancelled": "Rebuilding network routes has been cancelled."
},
"heal_node": {
"title": "Heal a Z-Wave device",
"rebuild_node_routes": {
"title": "Rebuild routes for a Z-Wave device",
"introduction": "Tell {device} to update its routes back to the controller. This can help with communication issues if you have recently moved the device or your controller.",
"traffic_warning": "The healing process generates a large amount of traffic on the Z-Wave network. This may cause devices to respond slowly (or not at all) while the heal is in progress.",
"start_heal": "Heal Device",
"healing_failed": "{device} could not be healed.",
"healing_failed_check_logs": "Additional information may be available in the logs.",
"healing_complete": "{device} has been healed.",
"in_progress": "{device} healing is in progress.",
"network_heal_in_progress": "A Z-Wave network heal is already in progress. Please wait for it to finish before healing an individual device."
"traffic_warning": "The route rebuilding process generates a large amount of traffic on the Z-Wave network. This may cause devices to respond slowly (or not at all) while the rebuilding is in progress.",
"start_rebuilding_routes": "Rebuild Routes for Device",
"rebuilding_routes_failed": "{device} routes could not be rebuild.",
"rebuilding_routes_failed_check_logs": "Additional information may be available in the logs.",
"rebuilding_routes_complete": "{device} routes have been rebuilt.",
"in_progress": "{device} routes rebuild is in progress.",
"routes_rebuild_in_progress": "A Z-Wave routes rebuild is already in progress. Please wait for it to finish before rebuilding routes for an individual device."
},
"update_firmware": {
"title": "Update device firmware",
@ -5562,6 +5574,13 @@
"no_match": "No intent matched",
"language": "[%key:ui::components::language-picker::language%]"
},
"debug": {
"title": "Debug tools",
"debug_connection": {
"title": "Debug connection",
"description": "Observe requests to the server and responses from the server in browser console."
}
},
"events": {
"title": "Events",
"description": "Fire an event on the event bus.",

View File

@ -230,6 +230,7 @@ export interface HomeAssistant {
suspendWhenHidden: boolean;
enableShortcuts: boolean;
vibrate: boolean;
debugConnection: boolean;
dockedSidebar: "docked" | "always_hidden" | "auto";
defaultPanel: string;
moreInfoEntityId: string | null;

View File

@ -5,6 +5,7 @@ const STORED_STATE = [
"selectedTheme",
"selectedLanguage",
"vibrate",
"debugConnection",
"suspendWhenHidden",
"enableShortcuts",
"defaultPanel",

View File

@ -4539,12 +4539,12 @@ __metadata:
languageName: node
linkType: hard
"@types/serve-handler@npm:6.1.1":
version: 6.1.1
resolution: "@types/serve-handler@npm:6.1.1"
"@types/serve-handler@npm:6.1.2":
version: 6.1.2
resolution: "@types/serve-handler@npm:6.1.2"
dependencies:
"@types/node": "*"
checksum: f519f83b18d7dea80f188f387a56dfe30fe944196849c470902fabf9db344c083c470f67e3362ad3f2294512950bc55924282c5c7a911ec8d507c37c6861d8ce
checksum: 6b206cba18bf77b938e340e1015c631d92b492918ba78dfdff57590a25da8c4bc63fba90f43d763f854c5aa523b243c283ba84d14cfeb1841795337c8b978bd6
languageName: node
linkType: hard
@ -9072,9 +9072,9 @@ __metadata:
linkType: hard
"get-func-name@npm:^2.0.0":
version: 2.0.0
resolution: "get-func-name@npm:2.0.0"
checksum: 8d82e69f3e7fab9e27c547945dfe5cc0c57fc0adf08ce135dddb01081d75684a03e7a0487466f478872b341d52ac763ae49e660d01ab83741f74932085f693c3
version: 2.0.2
resolution: "get-func-name@npm:2.0.2"
checksum: 3f62f4c23647de9d46e6f76d2b3eafe58933a9b3830c60669e4180d6c601ce1b4aa310ba8366143f55e52b139f992087a9f0647274e8745621fa2af7e0acf13b
languageName: node
linkType: hard
@ -9722,7 +9722,7 @@ __metadata:
"@types/luxon": 3.3.2
"@types/mocha": 10.0.1
"@types/qrcode": 1.5.2
"@types/serve-handler": 6.1.1
"@types/serve-handler": 6.1.2
"@types/sortablejs": 1.15.2
"@types/tar": 6.1.6
"@types/ua-parser-js": 0.7.37