mirror of
https://github.com/home-assistant/frontend.git
synced 2025-04-25 05:47:20 +00:00
Move Logbook and make device page better (#12763)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
parent
d7971c69ad
commit
e61aa266a6
@ -19,7 +19,6 @@ import { afterNextRender } from "../../../common/util/render-status";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-icon-next";
|
||||
import "../../logbook/ha-logbook";
|
||||
import {
|
||||
AreaRegistryEntry,
|
||||
deleteAreaRegistryEntry,
|
||||
@ -43,15 +42,16 @@ import { SceneEntity } from "../../../data/scene";
|
||||
import { ScriptEntity } from "../../../data/script";
|
||||
import { findRelated, RelatedResult } from "../../../data/search";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
import "../../logbook/ha-logbook";
|
||||
import { showEntityEditorDialog } from "../entities/show-dialog-entity-editor";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import {
|
||||
loadAreaRegistryDetailDialog,
|
||||
showAreaRegistryDetailDialog,
|
||||
} from "./show-dialog-area-registry-detail";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
|
||||
declare type NameAndEntity<EntityType extends HassEntity> = {
|
||||
name: string;
|
||||
@ -761,10 +761,10 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
border-radius: 50%;
|
||||
}
|
||||
ha-logbook {
|
||||
height: 800px;
|
||||
height: 400px;
|
||||
}
|
||||
:host([narrow]) ha-logbook {
|
||||
height: 400px;
|
||||
height: 235px;
|
||||
overflow: auto;
|
||||
}
|
||||
`,
|
||||
|
@ -0,0 +1,13 @@
|
||||
import type { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import type { DeviceAction } from "../../../ha-config-device-page";
|
||||
import { showMQTTDeviceDebugInfoDialog } from "./show-dialog-mqtt-device-debug-info";
|
||||
|
||||
export const getMQTTDeviceActions = (
|
||||
el: HTMLElement,
|
||||
device: DeviceRegistryEntry
|
||||
): DeviceAction[] => [
|
||||
{
|
||||
label: "MQTT Info",
|
||||
action: async () => showMQTTDeviceDebugInfoDialog(el, { device }),
|
||||
},
|
||||
];
|
@ -1,36 +0,0 @@
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import { haStyle } from "../../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../../types";
|
||||
import { showMQTTDeviceDebugInfoDialog } from "./show-dialog-mqtt-device-debug-info";
|
||||
|
||||
@customElement("ha-device-actions-mqtt")
|
||||
export class HaDeviceActionsMqtt extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public device!: DeviceRegistryEntry;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<mwc-button @click=${this._showDebugInfo}> MQTT Info </mwc-button>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _showDebugInfo(): Promise<void> {
|
||||
const device = this.device;
|
||||
await showMQTTDeviceDebugInfoDialog(this, { device });
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
import { navigate } from "../../../../../../common/navigate";
|
||||
import type { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import { fetchZHADevice } from "../../../../../../data/zha";
|
||||
import { showConfirmationDialog } from "../../../../../../dialogs/generic/show-dialog-box";
|
||||
import type { HomeAssistant } from "../../../../../../types";
|
||||
import { showZHAClusterDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-cluster";
|
||||
import { showZHADeviceChildrenDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-device-children";
|
||||
import { showZHADeviceZigbeeInfoDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-device-zigbee-info";
|
||||
import { showZHAReconfigureDeviceDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-reconfigure-device";
|
||||
import type { DeviceAction } from "../../../ha-config-device-page";
|
||||
|
||||
export const getZHADeviceActions = async (
|
||||
el: HTMLElement,
|
||||
hass: HomeAssistant,
|
||||
device: DeviceRegistryEntry
|
||||
): Promise<DeviceAction[]> => {
|
||||
const zigbeeConnection = device.connections.find(
|
||||
(conn) => conn[0] === "zigbee"
|
||||
);
|
||||
|
||||
if (!zigbeeConnection) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const zhaDevice = await fetchZHADevice(hass, zigbeeConnection[1]);
|
||||
|
||||
if (!zhaDevice) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const actions: DeviceAction[] = [];
|
||||
|
||||
if (zhaDevice.device_type !== "Coordinator") {
|
||||
actions.push({
|
||||
label: hass.localize("ui.dialogs.zha_device_info.buttons.reconfigure"),
|
||||
action: () => showZHAReconfigureDeviceDialog(el, { device: zhaDevice }),
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
zhaDevice.power_source === "Mains" &&
|
||||
(zhaDevice.device_type === "Router" ||
|
||||
zhaDevice.device_type === "Coordinator")
|
||||
) {
|
||||
actions.push(
|
||||
...[
|
||||
{
|
||||
label: hass.localize("ui.dialogs.zha_device_info.buttons.add"),
|
||||
action: () => navigate(`/config/zha/add/${zhaDevice!.ieee}`),
|
||||
},
|
||||
{
|
||||
label: hass.localize(
|
||||
"ui.dialogs.zha_device_info.buttons.device_children"
|
||||
),
|
||||
action: () => showZHADeviceChildrenDialog(el, { device: zhaDevice! }),
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
if (zhaDevice.device_type !== "Coordinator") {
|
||||
actions.push(
|
||||
...[
|
||||
{
|
||||
label: hass.localize(
|
||||
"ui.dialogs.zha_device_info.buttons.zigbee_information"
|
||||
),
|
||||
action: () =>
|
||||
showZHADeviceZigbeeInfoDialog(el, { device: zhaDevice }),
|
||||
},
|
||||
{
|
||||
label: hass.localize("ui.dialogs.zha_device_info.buttons.clusters"),
|
||||
action: () => showZHAClusterDialog(el, { device: zhaDevice }),
|
||||
},
|
||||
{
|
||||
label: hass.localize(
|
||||
"ui.dialogs.zha_device_info.buttons.view_in_visualization"
|
||||
),
|
||||
action: () =>
|
||||
navigate(`/config/zha/visualization/${zhaDevice!.device_reg_id}`),
|
||||
},
|
||||
{
|
||||
label: hass.localize("ui.dialogs.zha_device_info.buttons.remove"),
|
||||
classes: "warning",
|
||||
action: async () => {
|
||||
const confirmed = await showConfirmationDialog(el, {
|
||||
text: hass.localize(
|
||||
"ui.dialogs.zha_device_info.confirmations.remove"
|
||||
),
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
await hass.callService("zha", "remove", {
|
||||
ieee: zhaDevice.ieee,
|
||||
});
|
||||
|
||||
history.back();
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return actions;
|
||||
};
|
@ -1,155 +0,0 @@
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { navigate } from "../../../../../../common/navigate";
|
||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import { fetchZHADevice, ZHADevice } from "../../../../../../data/zha";
|
||||
import { showConfirmationDialog } from "../../../../../../dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../../types";
|
||||
import { showZHAClusterDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-cluster";
|
||||
import { showZHADeviceChildrenDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-device-children";
|
||||
import { showZHADeviceZigbeeInfoDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-device-zigbee-info";
|
||||
import { showZHAReconfigureDeviceDialog } from "../../../../integrations/integration-panels/zha/show-dialog-zha-reconfigure-device";
|
||||
|
||||
@customElement("ha-device-actions-zha")
|
||||
export class HaDeviceActionsZha extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public device!: DeviceRegistryEntry;
|
||||
|
||||
@state() private _zhaDevice?: ZHADevice;
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
if (changedProperties.has("device")) {
|
||||
const zigbeeConnection = this.device.connections.find(
|
||||
(conn) => conn[0] === "zigbee"
|
||||
);
|
||||
if (!zigbeeConnection) {
|
||||
return;
|
||||
}
|
||||
fetchZHADevice(this.hass, zigbeeConnection[1]).then((device) => {
|
||||
this._zhaDevice = device;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._zhaDevice) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
${this._zhaDevice.device_type !== "Coordinator"
|
||||
? html`
|
||||
<mwc-button @click=${this._onReconfigureNodeClick}>
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.buttons.reconfigure"
|
||||
)}
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
${this._zhaDevice.power_source === "Mains" &&
|
||||
(this._zhaDevice.device_type === "Router" ||
|
||||
this._zhaDevice.device_type === "Coordinator")
|
||||
? html`
|
||||
<mwc-button @click=${this._onAddDevicesClick}>
|
||||
${this.hass!.localize("ui.dialogs.zha_device_info.buttons.add")}
|
||||
</mwc-button>
|
||||
<mwc-button @click=${this._handleDeviceChildrenClicked}>
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.buttons.device_children"
|
||||
)}
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
${this._zhaDevice.device_type !== "Coordinator"
|
||||
? html`
|
||||
<mwc-button @click=${this._handleZigbeeInfoClicked}>
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.buttons.zigbee_information"
|
||||
)}
|
||||
</mwc-button>
|
||||
<mwc-button @click=${this._showClustersDialog}>
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.buttons.clusters"
|
||||
)}
|
||||
</mwc-button>
|
||||
<mwc-button @click=${this._onViewInVisualizationClick}>
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.buttons.view_in_visualization"
|
||||
)}
|
||||
</mwc-button>
|
||||
<mwc-button class="warning" @click=${this._removeDevice}>
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.zha_device_info.buttons.remove"
|
||||
)}
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
private async _showClustersDialog(): Promise<void> {
|
||||
await showZHAClusterDialog(this, { device: this._zhaDevice! });
|
||||
}
|
||||
|
||||
private async _onReconfigureNodeClick(): Promise<void> {
|
||||
if (!this.hass) {
|
||||
return;
|
||||
}
|
||||
showZHAReconfigureDeviceDialog(this, { device: this._zhaDevice! });
|
||||
}
|
||||
|
||||
private _onAddDevicesClick() {
|
||||
navigate(`/config/zha/add/${this._zhaDevice!.ieee}`);
|
||||
}
|
||||
|
||||
private _onViewInVisualizationClick() {
|
||||
navigate(`/config/zha/visualization/${this._zhaDevice!.device_reg_id}`);
|
||||
}
|
||||
|
||||
private async _handleZigbeeInfoClicked() {
|
||||
showZHADeviceZigbeeInfoDialog(this, { device: this._zhaDevice! });
|
||||
}
|
||||
|
||||
private async _handleDeviceChildrenClicked() {
|
||||
showZHADeviceChildrenDialog(this, { device: this._zhaDevice! });
|
||||
}
|
||||
|
||||
private async _removeDevice() {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.dialogs.zha_device_info.confirmations.remove"
|
||||
),
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.hass.callService("zha", "remove", {
|
||||
ieee: this._zhaDevice!.ieee,
|
||||
});
|
||||
|
||||
history.back();
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../../../../components/ha-expansion-panel";
|
||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import { fetchZHADevice, ZHADevice } from "../../../../../../data/zha";
|
||||
import { haStyle } from "../../../../../../resources/styles";
|
||||
@ -40,38 +41,39 @@ export class HaDeviceActionsZha extends LitElement {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
<h4>Zigbee info</h4>
|
||||
<div>IEEE: ${this._zhaDevice.ieee}</div>
|
||||
<div>Nwk: ${formatAsPaddedHex(this._zhaDevice.nwk)}</div>
|
||||
<div>Device Type: ${this._zhaDevice.device_type}</div>
|
||||
<div>
|
||||
LQI:
|
||||
${this._zhaDevice.lqi ||
|
||||
this.hass!.localize("ui.dialogs.zha_device_info.unknown")}
|
||||
</div>
|
||||
<div>
|
||||
RSSI:
|
||||
${this._zhaDevice.rssi ||
|
||||
this.hass!.localize("ui.dialogs.zha_device_info.unknown")}
|
||||
</div>
|
||||
<div>
|
||||
${this.hass!.localize("ui.dialogs.zha_device_info.last_seen")}:
|
||||
${this._zhaDevice.last_seen ||
|
||||
this.hass!.localize("ui.dialogs.zha_device_info.unknown")}
|
||||
</div>
|
||||
<div>
|
||||
${this.hass!.localize("ui.dialogs.zha_device_info.power_source")}:
|
||||
${this._zhaDevice.power_source ||
|
||||
this.hass!.localize("ui.dialogs.zha_device_info.unknown")}
|
||||
</div>
|
||||
${this._zhaDevice.quirk_applied
|
||||
? html`
|
||||
<div>
|
||||
${this.hass!.localize("ui.dialogs.zha_device_info.quirk")}:
|
||||
${this._zhaDevice.quirk_class}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<ha-expansion-panel header="Zigbee info">
|
||||
<div>IEEE: ${this._zhaDevice.ieee}</div>
|
||||
<div>Nwk: ${formatAsPaddedHex(this._zhaDevice.nwk)}</div>
|
||||
<div>Device Type: ${this._zhaDevice.device_type}</div>
|
||||
<div>
|
||||
LQI:
|
||||
${this._zhaDevice.lqi ||
|
||||
this.hass!.localize("ui.dialogs.zha_device_info.unknown")}
|
||||
</div>
|
||||
<div>
|
||||
RSSI:
|
||||
${this._zhaDevice.rssi ||
|
||||
this.hass!.localize("ui.dialogs.zha_device_info.unknown")}
|
||||
</div>
|
||||
<div>
|
||||
${this.hass!.localize("ui.dialogs.zha_device_info.last_seen")}:
|
||||
${this._zhaDevice.last_seen ||
|
||||
this.hass!.localize("ui.dialogs.zha_device_info.unknown")}
|
||||
</div>
|
||||
<div>
|
||||
${this.hass!.localize("ui.dialogs.zha_device_info.power_source")}:
|
||||
${this._zhaDevice.power_source ||
|
||||
this.hass!.localize("ui.dialogs.zha_device_info.unknown")}
|
||||
</div>
|
||||
${this._zhaDevice.quirk_applied
|
||||
? html`
|
||||
<div>
|
||||
${this.hass!.localize("ui.dialogs.zha_device_info.quirk")}:
|
||||
${this._zhaDevice.quirk_class}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</ha-expansion-panel>
|
||||
`;
|
||||
}
|
||||
|
||||
@ -86,6 +88,11 @@ export class HaDeviceActionsZha extends LitElement {
|
||||
word-break: break-all;
|
||||
margin-top: 2px;
|
||||
}
|
||||
ha-expansion-panel {
|
||||
--expansion-panel-summary-padding: 0;
|
||||
--expansion-panel-content-padding: 0;
|
||||
padding-top: 4px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@ -0,0 +1,69 @@
|
||||
import { getConfigEntries } from "../../../../../../data/config_entries";
|
||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import { fetchZwaveNodeStatus } from "../../../../../../data/zwave_js";
|
||||
import type { HomeAssistant } from "../../../../../../types";
|
||||
import { showZWaveJSHealNodeDialog } from "../../../../integrations/integration-panels/zwave_js/show-dialog-zwave_js-heal-node";
|
||||
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";
|
||||
import type { DeviceAction } from "../../../ha-config-device-page";
|
||||
|
||||
export const getZwaveDeviceActions = async (
|
||||
el: HTMLElement,
|
||||
hass: HomeAssistant,
|
||||
device: DeviceRegistryEntry
|
||||
): Promise<DeviceAction[]> => {
|
||||
const configEntries = await getConfigEntries(hass, {
|
||||
domain: "zwave_js",
|
||||
});
|
||||
|
||||
const configEntry = configEntries.find((entry) =>
|
||||
device.config_entries.includes(entry.entry_id)
|
||||
);
|
||||
|
||||
if (!configEntry) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const entryId = configEntry.entry_id;
|
||||
|
||||
const node = await fetchZwaveNodeStatus(hass, device.id);
|
||||
|
||||
if (!node || node.is_controller_node) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
label: hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.device_config"
|
||||
),
|
||||
href: `/config/zwave_js/node_config/${device.id}?config_entry=${entryId}`,
|
||||
},
|
||||
{
|
||||
label: hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.reinterview_device"
|
||||
),
|
||||
action: () =>
|
||||
showZWaveJSReinterviewNodeDialog(el, {
|
||||
device_id: device.id,
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: hass.localize("ui.panel.config.zwave_js.device_info.heal_node"),
|
||||
action: () =>
|
||||
showZWaveJSHealNodeDialog(el, {
|
||||
entry_id: entryId,
|
||||
device: device,
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.remove_failed"
|
||||
),
|
||||
action: () =>
|
||||
showZWaveJSRemoveFailedNodeDialog(el, {
|
||||
device_id: device.id,
|
||||
}),
|
||||
},
|
||||
];
|
||||
};
|
@ -1,138 +0,0 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import {
|
||||
fetchZwaveNodeStatus,
|
||||
ZWaveJSNodeStatus,
|
||||
} from "../../../../../../data/zwave_js";
|
||||
import { haStyle } from "../../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../../types";
|
||||
import { showZWaveJSReinterviewNodeDialog } from "../../../../integrations/integration-panels/zwave_js/show-dialog-zwave_js-reinterview-node";
|
||||
import { showZWaveJSHealNodeDialog } from "../../../../integrations/integration-panels/zwave_js/show-dialog-zwave_js-heal-node";
|
||||
import { showZWaveJSRemoveFailedNodeDialog } from "../../../../integrations/integration-panels/zwave_js/show-dialog-zwave_js-remove-failed-node";
|
||||
import { getConfigEntries } from "../../../../../../data/config_entries";
|
||||
|
||||
@customElement("ha-device-actions-zwave_js")
|
||||
export class HaDeviceActionsZWaveJS extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public device!: DeviceRegistryEntry;
|
||||
|
||||
@state() private _entryId?: string;
|
||||
|
||||
@state() private _node?: ZWaveJSNodeStatus;
|
||||
|
||||
public willUpdate(changedProperties: PropertyValues) {
|
||||
super.willUpdate(changedProperties);
|
||||
if (changedProperties.has("device")) {
|
||||
this._fetchNodeDetails();
|
||||
}
|
||||
}
|
||||
|
||||
protected async _fetchNodeDetails() {
|
||||
if (!this.device) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._node = undefined;
|
||||
|
||||
const configEntries = await getConfigEntries(this.hass, {
|
||||
domain: "zwave_js",
|
||||
});
|
||||
|
||||
const configEntry = configEntries.find((entry) =>
|
||||
this.device.config_entries.includes(entry.entry_id)
|
||||
);
|
||||
|
||||
if (!configEntry) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._entryId = configEntry.entry_id;
|
||||
|
||||
this._node = await fetchZwaveNodeStatus(this.hass, this.device.id);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._node) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
${!this._node.is_controller_node
|
||||
? html`
|
||||
<a
|
||||
.href=${`/config/zwave_js/node_config/${this.device.id}?config_entry=${this._entryId}`}
|
||||
>
|
||||
<mwc-button>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.device_config"
|
||||
)}
|
||||
</mwc-button>
|
||||
</a>
|
||||
<mwc-button @click=${this._reinterviewClicked}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.reinterview_device"
|
||||
)}
|
||||
</mwc-button>
|
||||
<mwc-button @click=${this._healNodeClicked}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.heal_node"
|
||||
)}
|
||||
</mwc-button>
|
||||
<mwc-button @click=${this._removeFailedNode}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.remove_failed"
|
||||
)}
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
private async _reinterviewClicked() {
|
||||
if (!this.device) {
|
||||
return;
|
||||
}
|
||||
showZWaveJSReinterviewNodeDialog(this, {
|
||||
device_id: this.device.id,
|
||||
});
|
||||
}
|
||||
|
||||
private async _healNodeClicked() {
|
||||
if (!this.device) {
|
||||
return;
|
||||
}
|
||||
showZWaveJSHealNodeDialog(this, {
|
||||
entry_id: this._entryId!,
|
||||
device: this.device,
|
||||
});
|
||||
}
|
||||
|
||||
private async _removeFailedNode() {
|
||||
if (!this.device) {
|
||||
return;
|
||||
}
|
||||
showZWaveJSRemoveFailedNodeDialog(this, {
|
||||
device_id: this.device.id,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
@ -7,16 +7,17 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import "../../../../../../components/ha-expansion-panel";
|
||||
import {
|
||||
ConfigEntry,
|
||||
getConfigEntries,
|
||||
} from "../../../../../../data/config_entries";
|
||||
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import {
|
||||
fetchZwaveNodeStatus,
|
||||
nodeStatus,
|
||||
ZWaveJSNodeStatus,
|
||||
SecurityClass,
|
||||
ZWaveJSNodeStatus,
|
||||
} from "../../../../../../data/zwave_js";
|
||||
import { haStyle } from "../../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../../types";
|
||||
@ -69,73 +70,76 @@ export class HaDeviceInfoZWaveJS extends LitElement {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
<h4>
|
||||
${this.hass.localize("ui.panel.config.zwave_js.device_info.zwave_info")}
|
||||
</h4>
|
||||
${this._multipleConfigEntries
|
||||
? html`
|
||||
<div>
|
||||
${this.hass.localize("ui.panel.config.zwave_js.common.source")}:
|
||||
${this._configEntry!.title}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<div>
|
||||
${this.hass.localize("ui.panel.config.zwave_js.common.node_id")}:
|
||||
${this._node.node_id}
|
||||
</div>
|
||||
${!this._node.is_controller_node
|
||||
? html`
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.node_status"
|
||||
)}:
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.zwave_js.node_status.${
|
||||
nodeStatus[this._node.status]
|
||||
}`
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.node_ready"
|
||||
)}:
|
||||
${this._node.ready
|
||||
? this.hass.localize("ui.common.yes")
|
||||
: this.hass.localize("ui.common.no")}
|
||||
</div>
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.highest_security"
|
||||
)}:
|
||||
${this._node.highest_security_class !== null
|
||||
? this.hass.localize(
|
||||
`ui.panel.config.zwave_js.security_classes.${
|
||||
SecurityClass[this._node.highest_security_class]
|
||||
}.title`
|
||||
)
|
||||
: this._node.is_secure === false
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.zwave_js.security_classes.none.title"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.unknown"
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.zwave_plus"
|
||||
)}:
|
||||
${this._node.zwave_plus_version
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.zwave_plus_version",
|
||||
"version",
|
||||
this._node.zwave_plus_version
|
||||
)
|
||||
: this.hass.localize("ui.common.no")}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<ha-expansion-panel
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.zwave_info"
|
||||
)}
|
||||
>
|
||||
${this._multipleConfigEntries
|
||||
? html`
|
||||
<div>
|
||||
${this.hass.localize("ui.panel.config.zwave_js.common.source")}:
|
||||
${this._configEntry!.title}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<div>
|
||||
${this.hass.localize("ui.panel.config.zwave_js.common.node_id")}:
|
||||
${this._node.node_id}
|
||||
</div>
|
||||
${!this._node.is_controller_node
|
||||
? html`
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.node_status"
|
||||
)}:
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.zwave_js.node_status.${
|
||||
nodeStatus[this._node.status]
|
||||
}`
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.node_ready"
|
||||
)}:
|
||||
${this._node.ready
|
||||
? this.hass.localize("ui.common.yes")
|
||||
: this.hass.localize("ui.common.no")}
|
||||
</div>
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.highest_security"
|
||||
)}:
|
||||
${this._node.highest_security_class !== null
|
||||
? this.hass.localize(
|
||||
`ui.panel.config.zwave_js.security_classes.${
|
||||
SecurityClass[this._node.highest_security_class]
|
||||
}.title`
|
||||
)
|
||||
: this._node.is_secure === false
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.zwave_js.security_classes.none.title"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.unknown"
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.zwave_plus"
|
||||
)}:
|
||||
${this._node.zwave_plus_version
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.zwave_js.device_info.zwave_plus_version",
|
||||
"version",
|
||||
this._node.zwave_plus_version
|
||||
)
|
||||
: this.hass.localize("ui.common.no")}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</ha-expansion-panel>
|
||||
`;
|
||||
}
|
||||
|
||||
@ -150,6 +154,11 @@ export class HaDeviceInfoZWaveJS extends LitElement {
|
||||
word-break: break-all;
|
||||
margin-top: 2px;
|
||||
}
|
||||
ha-expansion-panel {
|
||||
--expansion-panel-summary-padding: 0;
|
||||
--expansion-panel-content-padding: 0;
|
||||
padding-top: 4px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@ -1,4 +1,9 @@
|
||||
import { mdiOpenInNew, mdiPencil, mdiPlusCircle } from "@mdi/js";
|
||||
import {
|
||||
mdiDotsVertical,
|
||||
mdiOpenInNew,
|
||||
mdiPencil,
|
||||
mdiPlusCircle,
|
||||
} from "@mdi/js";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
@ -13,6 +18,7 @@ import { slugify } from "../../../common/string/slugify";
|
||||
import { groupBy } from "../../../common/util/group-by";
|
||||
import "../../../components/entity/ha-battery-icon";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-icon-next";
|
||||
import "../../../components/ha-svg-icon";
|
||||
@ -26,14 +32,14 @@ import {
|
||||
import {
|
||||
computeDeviceName,
|
||||
DeviceRegistryEntry,
|
||||
updateDeviceRegistryEntry,
|
||||
removeConfigEntryFromDevice,
|
||||
updateDeviceRegistryEntry,
|
||||
} from "../../../data/device_registry";
|
||||
import {
|
||||
fetchDiagnosticHandler,
|
||||
getDeviceDiagnosticsDownloadUrl,
|
||||
getConfigEntryDiagnosticsDownloadUrl,
|
||||
DiagnosticInfo,
|
||||
fetchDiagnosticHandler,
|
||||
getConfigEntryDiagnosticsDownloadUrl,
|
||||
getDeviceDiagnosticsDownloadUrl,
|
||||
} from "../../../data/diagnostics";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
@ -51,9 +57,10 @@ import {
|
||||
import "../../../layouts/hass-error-screen";
|
||||
import "../../../layouts/hass-tabs-subpage";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
import type { HomeAssistant, Route } from "../../../types";
|
||||
import { brandsUrl } from "../../../util/brands-url";
|
||||
import { fileDownload } from "../../../util/file_download";
|
||||
import "../../logbook/ha-logbook";
|
||||
import "../ha-config-section";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import "./device-detail/ha-device-entities-card";
|
||||
@ -63,12 +70,19 @@ import {
|
||||
loadDeviceRegistryDetailDialog,
|
||||
showDeviceRegistryDetailDialog,
|
||||
} from "./device-registry-detail/show-dialog-device-registry-detail";
|
||||
import "../../logbook/ha-logbook";
|
||||
|
||||
export interface EntityRegistryStateEntry extends EntityRegistryEntry {
|
||||
stateName?: string | null;
|
||||
}
|
||||
|
||||
export interface DeviceAction {
|
||||
href?: string;
|
||||
action?: (ev: any) => void;
|
||||
label: string;
|
||||
trailingIcon?: string;
|
||||
classes?: string;
|
||||
}
|
||||
|
||||
@customElement("ha-config-device-page")
|
||||
export class HaConfigDevicePage extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@ -94,11 +108,11 @@ export class HaConfigDevicePage extends LitElement {
|
||||
@state() private _related?: RelatedResult;
|
||||
|
||||
// If a number, it's the request ID so we make sure we don't show older info
|
||||
@state() private _diagnosticDownloadLinks?:
|
||||
| number
|
||||
| (TemplateResult | string)[];
|
||||
@state() private _diagnosticDownloadLinks?: number | DeviceAction[];
|
||||
|
||||
@state() private _deleteButtons?: (TemplateResult | string)[];
|
||||
@state() private _deleteButtons?: DeviceAction[];
|
||||
|
||||
@state() private _deviceActions?: DeviceAction[];
|
||||
|
||||
private _logbookTime = { recent: 86400 };
|
||||
|
||||
@ -196,15 +210,17 @@ export class HaConfigDevicePage extends LitElement {
|
||||
if (
|
||||
changedProps.has("deviceId") ||
|
||||
changedProps.has("devices") ||
|
||||
changedProps.has("deviceId") ||
|
||||
changedProps.has("entries")
|
||||
) {
|
||||
this._diagnosticDownloadLinks = undefined;
|
||||
this._deleteButtons = undefined;
|
||||
this._deviceActions = undefined;
|
||||
}
|
||||
|
||||
if (
|
||||
(this._diagnosticDownloadLinks && this._deleteButtons) ||
|
||||
(this._diagnosticDownloadLinks &&
|
||||
this._deleteButtons &&
|
||||
this._deviceActions) ||
|
||||
!this.devices ||
|
||||
!this.deviceId ||
|
||||
!this.entries
|
||||
@ -214,129 +230,10 @@ export class HaConfigDevicePage extends LitElement {
|
||||
|
||||
this._diagnosticDownloadLinks = Math.random();
|
||||
this._deleteButtons = []; // To prevent re-rendering if no delete buttons
|
||||
this._renderDiagnosticButtons(this._diagnosticDownloadLinks);
|
||||
this._renderDeleteButtons();
|
||||
}
|
||||
|
||||
private async _renderDiagnosticButtons(requestId: number): Promise<void> {
|
||||
if (!isComponentLoaded(this.hass, "diagnostics")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const device = this._device(this.deviceId, this.devices);
|
||||
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
|
||||
let links = await Promise.all(
|
||||
this._integrations(device, this.entries).map(
|
||||
async (entry): Promise<boolean | { link: string; domain: string }> => {
|
||||
if (entry.state !== "loaded") {
|
||||
return false;
|
||||
}
|
||||
let info: DiagnosticInfo;
|
||||
try {
|
||||
info = await fetchDiagnosticHandler(this.hass, entry.domain);
|
||||
} catch (err: any) {
|
||||
if (err.code === "not_found") {
|
||||
return false;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (!info.handlers.device && !info.handlers.config_entry) {
|
||||
return false;
|
||||
}
|
||||
return {
|
||||
link: info.handlers.device
|
||||
? getDeviceDiagnosticsDownloadUrl(entry.entry_id, this.deviceId)
|
||||
: getConfigEntryDiagnosticsDownloadUrl(entry.entry_id),
|
||||
domain: entry.domain,
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
links = links.filter(Boolean);
|
||||
|
||||
if (this._diagnosticDownloadLinks !== requestId) {
|
||||
return;
|
||||
}
|
||||
if (links.length > 0) {
|
||||
this._diagnosticDownloadLinks = (
|
||||
links as { link: string; domain: string }[]
|
||||
).map(
|
||||
(link) => html`
|
||||
<a href=${link.link} @click=${this._signUrl}>
|
||||
<mwc-button>
|
||||
${links.length > 1
|
||||
? this.hass.localize(
|
||||
`ui.panel.config.devices.download_diagnostics_integration`,
|
||||
{
|
||||
integration: domainToName(
|
||||
this.hass.localize,
|
||||
link.domain
|
||||
),
|
||||
}
|
||||
)
|
||||
: this.hass.localize(
|
||||
`ui.panel.config.devices.download_diagnostics`
|
||||
)}
|
||||
</mwc-button>
|
||||
</a>
|
||||
`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private _renderDeleteButtons() {
|
||||
const device = this._device(this.deviceId, this.devices);
|
||||
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
|
||||
const buttons: TemplateResult[] = [];
|
||||
this._integrations(device, this.entries).forEach((entry) => {
|
||||
if (entry.state !== "loaded" || !entry.supports_remove_device) {
|
||||
return;
|
||||
}
|
||||
buttons.push(html`
|
||||
<mwc-button
|
||||
class="warning"
|
||||
.entryId=${entry.entry_id}
|
||||
@click=${this._confirmDeleteEntry}
|
||||
>
|
||||
${buttons.length > 1
|
||||
? this.hass.localize(
|
||||
`ui.panel.config.devices.delete_device_integration`,
|
||||
{
|
||||
integration: domainToName(this.hass.localize, entry.domain),
|
||||
}
|
||||
)
|
||||
: this.hass.localize(`ui.panel.config.devices.delete_device`)}
|
||||
</mwc-button>
|
||||
`);
|
||||
});
|
||||
|
||||
if (buttons.length > 0) {
|
||||
this._deleteButtons = buttons;
|
||||
}
|
||||
}
|
||||
|
||||
private async _confirmDeleteEntry(e: MouseEvent): Promise<void> {
|
||||
const entryId = (e.currentTarget as any).entryId;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
text: this.hass.localize("ui.panel.config.devices.confirm_delete"),
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
await removeConfigEntryFromDevice(this.hass!, this.deviceId, entryId);
|
||||
this._deviceActions = [];
|
||||
this._getDiagnosticButtons(this._diagnosticDownloadLinks);
|
||||
this._getDeleteActions();
|
||||
this._getDeviceActions();
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps) {
|
||||
@ -381,16 +278,19 @@ export class HaConfigDevicePage extends LitElement {
|
||||
: undefined;
|
||||
const area = this._computeArea(this.areas, device);
|
||||
|
||||
const configurationUrlIsHomeAssistant =
|
||||
device.configuration_url?.startsWith("homeassistant://") || false;
|
||||
|
||||
const configurationUrl = configurationUrlIsHomeAssistant
|
||||
? device.configuration_url!.replace("homeassistant://", "/")
|
||||
: device.configuration_url;
|
||||
|
||||
const deviceInfo: TemplateResult[] = [];
|
||||
const deviceAlerts: TemplateResult[] = [];
|
||||
|
||||
const actions = [...(this._deviceActions || [])];
|
||||
if (Array.isArray(this._diagnosticDownloadLinks)) {
|
||||
actions.push(...this._diagnosticDownloadLinks);
|
||||
}
|
||||
if (this._deleteButtons) {
|
||||
actions.push(...this._deleteButtons);
|
||||
}
|
||||
|
||||
const firstDeviceAction = actions.shift();
|
||||
|
||||
if (device.disabled_by) {
|
||||
deviceInfo.push(
|
||||
html`
|
||||
@ -408,54 +308,19 @@ export class HaConfigDevicePage extends LitElement {
|
||||
)}
|
||||
</ha-alert>
|
||||
${device.disabled_by === "user"
|
||||
? html` <div class="card-actions" slot="actions">
|
||||
<mwc-button unelevated @click=${this._enableDevice}>
|
||||
${this.hass.localize("ui.common.enable")}
|
||||
</mwc-button>
|
||||
</div>`
|
||||
? html`
|
||||
<div class="card-actions" slot="actions">
|
||||
<mwc-button unelevated @click=${this._enableDevice}>
|
||||
${this.hass.localize("ui.common.enable")}
|
||||
</mwc-button>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
`
|
||||
);
|
||||
}
|
||||
|
||||
const deviceActions: (TemplateResult | string)[] = [];
|
||||
|
||||
if (configurationUrl) {
|
||||
deviceActions.push(html`
|
||||
<a
|
||||
href=${configurationUrl}
|
||||
rel="noopener noreferrer"
|
||||
.target=${configurationUrlIsHomeAssistant ? "_self" : "_blank"}
|
||||
>
|
||||
<mwc-button>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.devices.open_configuration_url_${
|
||||
device.entry_type || "device"
|
||||
}`
|
||||
)}
|
||||
<ha-svg-icon
|
||||
.path=${mdiOpenInNew}
|
||||
slot="trailingIcon"
|
||||
></ha-svg-icon>
|
||||
</mwc-button>
|
||||
</a>
|
||||
`);
|
||||
}
|
||||
|
||||
this._renderIntegrationInfo(
|
||||
device,
|
||||
integrations,
|
||||
deviceInfo,
|
||||
deviceActions,
|
||||
deviceAlerts
|
||||
);
|
||||
|
||||
if (Array.isArray(this._diagnosticDownloadLinks)) {
|
||||
deviceActions.push(...this._diagnosticDownloadLinks);
|
||||
}
|
||||
if (this._deleteButtons) {
|
||||
deviceActions.push(...this._deleteButtons);
|
||||
}
|
||||
this._renderIntegrationInfo(device, integrations, deviceInfo, deviceAlerts);
|
||||
|
||||
return html`
|
||||
<hass-tabs-subpage
|
||||
@ -479,10 +344,6 @@ export class HaConfigDevicePage extends LitElement {
|
||||
`
|
||||
: ""
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="container">
|
||||
<div class="header fullwidth">
|
||||
${
|
||||
@ -562,57 +423,70 @@ export class HaConfigDevicePage extends LitElement {
|
||||
>
|
||||
${deviceInfo}
|
||||
${
|
||||
deviceActions.length
|
||||
firstDeviceAction || actions.length
|
||||
? html`
|
||||
<div class="card-actions" slot="actions">
|
||||
${deviceActions}
|
||||
<div>
|
||||
<a href=${ifDefined(firstDeviceAction!.href)}>
|
||||
<mwc-button
|
||||
class=${ifDefined(firstDeviceAction!.classes)}
|
||||
.action=${firstDeviceAction!.action}
|
||||
@click=${this._deviceActionClicked}
|
||||
>
|
||||
${firstDeviceAction!.label}
|
||||
${firstDeviceAction!.trailingIcon
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
.path=${firstDeviceAction!.trailingIcon}
|
||||
slot="trailingIcon"
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: ""}
|
||||
</mwc-button>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
${actions.length
|
||||
? html`
|
||||
<ha-button-menu corner="BOTTOM_START">
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize(
|
||||
"ui.common.menu"
|
||||
)}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
${actions.map(
|
||||
(deviceAction) => html`
|
||||
<a href=${ifDefined(deviceAction.href)}>
|
||||
<mwc-list-item
|
||||
class=${ifDefined(
|
||||
deviceAction.classes
|
||||
)}
|
||||
.action=${deviceAction.action}
|
||||
@click=${this._deviceActionClicked}
|
||||
>
|
||||
${deviceAction.label}
|
||||
${deviceAction.trailingIcon
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
.path=${deviceAction.trailingIcon}
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: ""}
|
||||
</mwc-list-item>
|
||||
</a>
|
||||
`
|
||||
)}
|
||||
</ha-button-menu>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
</ha-device-info-card>
|
||||
</div>
|
||||
<div class="column">
|
||||
${["control", "sensor", "config", "diagnostic"].map((category) =>
|
||||
// Make sure we render controls if no other cards will be rendered
|
||||
entitiesByCategory[category].length > 0 ||
|
||||
(entities.length === 0 && category === "control")
|
||||
? html`
|
||||
<ha-device-entities-card
|
||||
.hass=${this.hass}
|
||||
.header=${this.hass.localize(
|
||||
`ui.panel.config.devices.entities.${category}`
|
||||
)}
|
||||
.deviceName=${deviceName}
|
||||
.entities=${entitiesByCategory[category]}
|
||||
.showHidden=${device.disabled_by !== null}
|
||||
>
|
||||
</ha-device-entities-card>
|
||||
`
|
||||
: ""
|
||||
)}
|
||||
${
|
||||
isComponentLoaded(this.hass, "logbook")
|
||||
? html`
|
||||
<ha-card outlined>
|
||||
<h1 class="card-header">
|
||||
${this.hass.localize("panel.logbook")}
|
||||
</h1>
|
||||
<ha-logbook
|
||||
.hass=${this.hass}
|
||||
.time=${this._logbookTime}
|
||||
.entityIds=${this._entityIds(entities)}
|
||||
.deviceIds=${this._deviceIdInList(this.deviceId)}
|
||||
virtualize
|
||||
narrow
|
||||
no-icon
|
||||
></ha-logbook>
|
||||
</ha-card>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
<div class="column">
|
||||
|
||||
${
|
||||
isComponentLoaded(this.hass, "automation")
|
||||
? html`
|
||||
@ -881,12 +755,227 @@ export class HaConfigDevicePage extends LitElement {
|
||||
`
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
<div class="column">
|
||||
${["control", "sensor", "config", "diagnostic"].map((category) =>
|
||||
// Make sure we render controls if no other cards will be rendered
|
||||
entitiesByCategory[category].length > 0 ||
|
||||
(entities.length === 0 && category === "control")
|
||||
? html`
|
||||
<ha-device-entities-card
|
||||
.hass=${this.hass}
|
||||
.header=${this.hass.localize(
|
||||
`ui.panel.config.devices.entities.${category}`
|
||||
)}
|
||||
.deviceName=${deviceName}
|
||||
.entities=${entitiesByCategory[category]}
|
||||
.showHidden=${device.disabled_by !== null}
|
||||
>
|
||||
</ha-device-entities-card>
|
||||
`
|
||||
: ""
|
||||
)}
|
||||
</div>
|
||||
<div class="column">
|
||||
${
|
||||
isComponentLoaded(this.hass, "logbook")
|
||||
? html`
|
||||
<ha-card outlined>
|
||||
<h1 class="card-header">
|
||||
${this.hass.localize("panel.logbook")}
|
||||
</h1>
|
||||
<ha-logbook
|
||||
.hass=${this.hass}
|
||||
.time=${this._logbookTime}
|
||||
.entityIds=${this._entityIds(entities)}
|
||||
.deviceIds=${this._deviceIdInList(this.deviceId)}
|
||||
virtualize
|
||||
narrow
|
||||
no-icon
|
||||
></ha-logbook>
|
||||
</ha-card>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</ha-config-section>
|
||||
</hass-tabs-subpage> `;
|
||||
}
|
||||
|
||||
private async _getDiagnosticButtons(requestId: number): Promise<void> {
|
||||
if (!isComponentLoaded(this.hass, "diagnostics")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const device = this._device(this.deviceId, this.devices);
|
||||
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
|
||||
let links = await Promise.all(
|
||||
this._integrations(device, this.entries).map(
|
||||
async (entry): Promise<boolean | { link: string; domain: string }> => {
|
||||
if (entry.state !== "loaded") {
|
||||
return false;
|
||||
}
|
||||
let info: DiagnosticInfo;
|
||||
try {
|
||||
info = await fetchDiagnosticHandler(this.hass, entry.domain);
|
||||
} catch (err: any) {
|
||||
if (err.code === "not_found") {
|
||||
return false;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (!info.handlers.device && !info.handlers.config_entry) {
|
||||
return false;
|
||||
}
|
||||
return {
|
||||
link: info.handlers.device
|
||||
? getDeviceDiagnosticsDownloadUrl(entry.entry_id, this.deviceId)
|
||||
: getConfigEntryDiagnosticsDownloadUrl(entry.entry_id),
|
||||
domain: entry.domain,
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
links = links.filter(Boolean);
|
||||
|
||||
if (this._diagnosticDownloadLinks !== requestId) {
|
||||
return;
|
||||
}
|
||||
if (links.length > 0) {
|
||||
this._diagnosticDownloadLinks = (
|
||||
links as { link: string; domain: string }[]
|
||||
).map((link) => ({
|
||||
href: link.link,
|
||||
action: (ev) => this._signUrl(ev),
|
||||
label:
|
||||
links.length > 1
|
||||
? this.hass.localize(
|
||||
`ui.panel.config.devices.download_diagnostics_integration`,
|
||||
{
|
||||
integration: domainToName(this.hass.localize, link.domain),
|
||||
}
|
||||
)
|
||||
: this.hass.localize(
|
||||
`ui.panel.config.devices.download_diagnostics`
|
||||
),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
private _getDeleteActions() {
|
||||
const device = this._device(this.deviceId, this.devices);
|
||||
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
|
||||
const buttons: DeviceAction[] = [];
|
||||
this._integrations(device, this.entries).forEach((entry) => {
|
||||
if (entry.state !== "loaded" || !entry.supports_remove_device) {
|
||||
return;
|
||||
}
|
||||
buttons.push({
|
||||
action: async () => {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
text: this.hass.localize("ui.panel.config.devices.confirm_delete"),
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
await removeConfigEntryFromDevice(
|
||||
this.hass!,
|
||||
this.deviceId,
|
||||
entry.entry_id
|
||||
);
|
||||
},
|
||||
classes: "warning",
|
||||
label:
|
||||
buttons.length > 1
|
||||
? this.hass.localize(
|
||||
`ui.panel.config.devices.delete_device_integration`,
|
||||
{
|
||||
integration: domainToName(this.hass.localize, entry.domain),
|
||||
}
|
||||
)
|
||||
: this.hass.localize(`ui.panel.config.devices.delete_device`),
|
||||
});
|
||||
});
|
||||
|
||||
if (buttons.length > 0) {
|
||||
this._deleteButtons = buttons;
|
||||
}
|
||||
}
|
||||
|
||||
private async _getDeviceActions() {
|
||||
const device = this._device(this.deviceId, this.devices);
|
||||
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
|
||||
const deviceActions: DeviceAction[] = [];
|
||||
|
||||
const configurationUrlIsHomeAssistant =
|
||||
device.configuration_url?.startsWith("homeassistant://") || false;
|
||||
|
||||
const configurationUrl = configurationUrlIsHomeAssistant
|
||||
? device.configuration_url!.replace("homeassistant://", "/")
|
||||
: device.configuration_url;
|
||||
|
||||
if (configurationUrl) {
|
||||
deviceActions.push({
|
||||
href: configurationUrl,
|
||||
label: this.hass.localize(
|
||||
`ui.panel.config.devices.open_configuration_url_${
|
||||
device.entry_type || "device"
|
||||
}`
|
||||
),
|
||||
trailingIcon: mdiOpenInNew,
|
||||
});
|
||||
}
|
||||
|
||||
const domains = this._integrations(device, this.entries).map(
|
||||
(int) => int.domain
|
||||
);
|
||||
|
||||
if (domains.includes("mqtt")) {
|
||||
const mqtt = await import(
|
||||
"./device-detail/integration-elements/mqtt/device-actions"
|
||||
);
|
||||
const actions = mqtt.getMQTTDeviceActions(this, device);
|
||||
deviceActions.push(...actions);
|
||||
}
|
||||
if (domains.includes("zha")) {
|
||||
const zha = await import(
|
||||
"./device-detail/integration-elements/zha/device-actions"
|
||||
);
|
||||
const actions = await zha.getZHADeviceActions(this, this.hass, device);
|
||||
deviceActions.push(...actions);
|
||||
}
|
||||
if (domains.includes("zwave_js")) {
|
||||
const zwave = await import(
|
||||
"./device-detail/integration-elements/zwave_js/device-actions"
|
||||
);
|
||||
const actions = await zwave.getZwaveDeviceActions(
|
||||
this,
|
||||
this.hass,
|
||||
device
|
||||
);
|
||||
deviceActions.push(...actions);
|
||||
}
|
||||
|
||||
this._deviceActions = deviceActions;
|
||||
}
|
||||
|
||||
private _computeEntityName(entity: EntityRegistryEntry) {
|
||||
if (entity.name) {
|
||||
return entity.name;
|
||||
@ -935,23 +1024,10 @@ export class HaConfigDevicePage extends LitElement {
|
||||
device: DeviceRegistryEntry,
|
||||
integrations: ConfigEntry[],
|
||||
deviceInfo: TemplateResult[],
|
||||
deviceActions: (string | TemplateResult)[],
|
||||
deviceAlerts: TemplateResult[]
|
||||
) {
|
||||
const domains = integrations.map((int) => int.domain);
|
||||
if (domains.includes("mqtt")) {
|
||||
import(
|
||||
"./device-detail/integration-elements/mqtt/ha-device-actions-mqtt"
|
||||
);
|
||||
deviceActions.push(html`
|
||||
<ha-device-actions-mqtt
|
||||
.hass=${this.hass}
|
||||
.device=${device}
|
||||
></ha-device-actions-mqtt>
|
||||
`);
|
||||
}
|
||||
if (domains.includes("zha")) {
|
||||
import("./device-detail/integration-elements/zha/ha-device-actions-zha");
|
||||
import("./device-detail/integration-elements/zha/ha-device-info-zha");
|
||||
deviceInfo.push(html`
|
||||
<ha-device-info-zha
|
||||
@ -959,12 +1035,6 @@ export class HaConfigDevicePage extends LitElement {
|
||||
.device=${device}
|
||||
></ha-device-info-zha>
|
||||
`);
|
||||
deviceActions.push(html`
|
||||
<ha-device-actions-zha
|
||||
.hass=${this.hass}
|
||||
.device=${device}
|
||||
></ha-device-actions-zha>
|
||||
`);
|
||||
}
|
||||
if (domains.includes("zwave_js")) {
|
||||
import(
|
||||
@ -973,9 +1043,6 @@ export class HaConfigDevicePage extends LitElement {
|
||||
import(
|
||||
"./device-detail/integration-elements/zwave_js/ha-device-info-zwave_js"
|
||||
);
|
||||
import(
|
||||
"./device-detail/integration-elements/zwave_js/ha-device-actions-zwave_js"
|
||||
);
|
||||
deviceAlerts.push(html`
|
||||
<ha-device-alerts-zwave_js
|
||||
.hass=${this.hass}
|
||||
@ -988,12 +1055,6 @@ export class HaConfigDevicePage extends LitElement {
|
||||
.device=${device}
|
||||
></ha-device-info-zwave_js>
|
||||
`);
|
||||
deviceActions.push(html`
|
||||
<ha-device-actions-zwave_js
|
||||
.hass=${this.hass}
|
||||
.device=${device}
|
||||
></ha-device-actions-zwave_js>
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1132,8 +1193,7 @@ export class HaConfigDevicePage extends LitElement {
|
||||
}
|
||||
|
||||
private async _signUrl(ev) {
|
||||
const anchor = ev.target.closest("a");
|
||||
ev.preventDefault();
|
||||
const anchor = ev.currentTarget.closest("a");
|
||||
const signedUrl = await getSignedPath(
|
||||
this.hass,
|
||||
anchor.getAttribute("href")
|
||||
@ -1141,6 +1201,16 @@ export class HaConfigDevicePage extends LitElement {
|
||||
fileDownload(signedUrl.path);
|
||||
}
|
||||
|
||||
private _deviceActionClicked(ev) {
|
||||
if (!ev.currentTarget.action) {
|
||||
return;
|
||||
}
|
||||
|
||||
ev.preventDefault();
|
||||
|
||||
(ev.currentTarget as any).action(ev);
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
@ -1171,9 +1241,6 @@ export class HaConfigDevicePage extends LitElement {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.show-more {
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-family: var(--paper-font-headline_-_font-family);
|
||||
@ -1282,7 +1349,19 @@ export class HaConfigDevicePage extends LitElement {
|
||||
:host([narrow]) ha-logbook {
|
||||
height: 235px;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-config-device-page": HaConfigDevicePage;
|
||||
}
|
||||
}
|
||||
|
@ -556,10 +556,6 @@ class HaLogbookRenderer extends LitElement {
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.narrow .date {
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.rtl .date {
|
||||
direction: rtl;
|
||||
}
|
||||
@ -590,6 +586,12 @@ class HaLogbookRenderer extends LitElement {
|
||||
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
button.link {
|
||||
color: var(--paper-item-icon-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.container {
|
||||
@ -607,7 +609,6 @@ class HaLogbookRenderer extends LitElement {
|
||||
|
||||
.narrow .entry {
|
||||
line-height: 1.5;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.narrow .icon-message state-badge {
|
||||
|
@ -158,6 +158,7 @@ export const buttonLinkStyle = css`
|
||||
text-align: left;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
`;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user