diff --git a/src/data/diagnostics.ts b/src/data/diagnostics.ts index d49554bf76..e533244232 100644 --- a/src/data/diagnostics.ts +++ b/src/data/diagnostics.ts @@ -1,9 +1,10 @@ import { HomeAssistant } from "../types"; -interface DiagnosticInfo { +export interface DiagnosticInfo { domain: string; handlers: { config_entry: boolean; + device: boolean; }; } @@ -14,5 +15,19 @@ export const fetchDiagnosticHandlers = ( type: "diagnostics/list", }); +export const fetchDiagnosticHandler = ( + hass: HomeAssistant, + domain: string +): Promise => + hass.callWS({ + type: "diagnostics/get", + domain, + }); + export const getConfigEntryDiagnosticsDownloadUrl = (entry_id: string) => `/api/diagnostics/config_entry/${entry_id}`; + +export const getDeviceDiagnosticsDownloadUrl = ( + entry_id: string, + device_id: string +) => `/api/diagnostics/config_entry/${entry_id}/device/${device_id}`; diff --git a/src/panels/config/devices/ha-config-device-page.ts b/src/panels/config/devices/ha-config-device-page.ts index 5bc90abded..30663f5894 100644 --- a/src/panels/config/devices/ha-config-device-page.ts +++ b/src/panels/config/devices/ha-config-device-page.ts @@ -3,6 +3,7 @@ import "@polymer/paper-tooltip/paper-tooltip"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { ifDefined } from "lit/directives/if-defined"; +import { until } from "lit/directives/until"; import memoizeOne from "memoize-one"; import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import { computeDomain } from "../../../common/entity/compute_domain"; @@ -17,6 +18,7 @@ import "../../../components/ha-icon-button"; import "../../../components/ha-icon-next"; import "../../../components/ha-svg-icon"; import { AreaRegistryEntry } from "../../../data/area_registry"; +import { getSignedPath } from "../../../data/auth"; import { ConfigEntry, disableConfigEntry, @@ -27,6 +29,11 @@ import { DeviceRegistryEntry, updateDeviceRegistryEntry, } from "../../../data/device_registry"; +import { + fetchDiagnosticHandler, + getDeviceDiagnosticsDownloadUrl, + getConfigEntryDiagnosticsDownloadUrl, +} from "../../../data/diagnostics"; import { EntityRegistryEntry, findBatteryChargingEntity, @@ -44,6 +51,7 @@ import "../../../layouts/hass-tabs-subpage"; import { haStyle } from "../../../resources/styles"; import { HomeAssistant, Route } from "../../../types"; import { brandsUrl } from "../../../util/brands-url"; +import { fileDownload } from "../../../util/file_download"; import "../ha-config-section"; import { configSections } from "../ha-panel-config"; import "./device-detail/ha-device-entities-card"; @@ -82,6 +90,10 @@ export class HaConfigDevicePage extends LitElement { @state() private _related?: RelatedResult; + @state() private _diagnosticDownloadLinks?: Promise< + (TemplateResult | string)[] + >; + private _device = memoizeOne( ( deviceId: string, @@ -91,10 +103,8 @@ export class HaConfigDevicePage extends LitElement { ); private _integrations = memoizeOne( - (device: DeviceRegistryEntry, entries: ConfigEntry[]): string[] => - entries - .filter((entry) => device.config_entries.includes(entry.entry_id)) - .map((entry) => entry.domain) + (device: DeviceRegistryEntry, entries: ConfigEntry[]): ConfigEntry[] => + entries.filter((entry) => device.config_entries.includes(entry.entry_id)) ); private _entities = memoizeOne( @@ -165,6 +175,63 @@ export class HaConfigDevicePage extends LitElement { findBatteryChargingEntity(this.hass, entities) ); + public willUpdate(changedProps) { + super.willUpdate(changedProps); + + if ( + changedProps.has("deviceId") || + changedProps.has("devices") || + changedProps.has("deviceId") || + changedProps.has("entries") + ) { + this._diagnosticDownloadLinks = undefined; + } + + if ( + this._diagnosticDownloadLinks || + !this.devices || + !this.deviceId || + !this.entries + ) { + return; + } + + this._diagnosticDownloadLinks = this._renderDiagnosticButtons(); + } + + private async _renderDiagnosticButtons(): Promise< + (TemplateResult | string)[] + > { + const result: TemplateResult[] = []; + const device = this._device(this.deviceId, this.devices); + + if (!device) { + return result; + } + + return Promise.all( + this._integrations(device, this.entries).map(async (entry) => { + const info = await fetchDiagnosticHandler(this.hass, entry.domain); + + if (!info.handlers.device && !info.handlers.config_entry) { + return ""; + } + const link = info.handlers.device + ? getDeviceDiagnosticsDownloadUrl(entry.entry_id, this.deviceId) + : getConfigEntryDiagnosticsDownloadUrl(entry.entry_id); + return html` + + + ${this.hass.localize( + `ui.panel.config.devices.download_diagnostics` + )} + + + `; + }) + ); + } + protected firstUpdated(changedProps) { super.firstUpdated(changedProps); loadDeviceRegistryDetailDialog(); @@ -214,6 +281,66 @@ export class HaConfigDevicePage extends LitElement { ? device.configuration_url!.replace("homeassistant://", "/") : device.configuration_url; + const deviceInfo: TemplateResult[] = []; + + if (device.disabled_by) { + deviceInfo.push( + html` + + ${this.hass.localize( + "ui.panel.config.devices.enabled_cause", + "cause", + this.hass.localize( + `ui.panel.config.devices.disabled_by.${device.disabled_by}` + ) + )} + + ${device.disabled_by === "user" + ? html`
+ + ${this.hass.localize("ui.common.enable")} + +
` + : ""} + ` + ); + } + + const deviceActions: TemplateResult[] = []; + + if (configurationUrl) { + deviceActions.push(html` + + + ${this.hass.localize( + `ui.panel.config.devices.open_configuration_url_${ + device.entry_type || "device" + }` + )} + + + + `); + } + + this._renderIntegrationInfo( + device, + integrations, + deviceInfo, + deviceActions + ); + + if (this._diagnosticDownloadLinks) { + deviceActions.push(html`${until(this._diagnosticDownloadLinks)}`); + } + return html` - ${ - device.disabled_by - ? html` - - ${this.hass.localize( - "ui.panel.config.devices.enabled_cause", - "cause", - this.hass.localize( - `ui.panel.config.devices.disabled_by.${device.disabled_by}` - ) - )} - - ${device.disabled_by === "user" - ? html`
- - ${this.hass.localize("ui.common.enable")} - -
` - : ""} - ` - : html`` - } - ${ - configurationUrl - ? html` - - ` - : "" - } - ${this._renderIntegrationInfo(device, integrations)} + ${deviceInfo} + ${ + deviceActions.length + ? html` +
+ ${deviceActions} +
+ ` + : "" + }
@@ -648,85 +735,84 @@ export class HaConfigDevicePage extends LitElement { private _renderIntegrationInfo( device, - integrations: string[] + integrations: ConfigEntry[], + deviceInfo: TemplateResult[], + deviceActions: TemplateResult[] ): TemplateResult[] { + const domains = integrations.map((int) => int.domain); const templates: TemplateResult[] = []; - if (integrations.includes("mqtt")) { + if (domains.includes("mqtt")) { import( "./device-detail/integration-elements/mqtt/ha-device-actions-mqtt" ); - templates.push(html` -
- -
+ deviceActions.push(html` + `); } - if (integrations.includes("ozw")) { + if (domains.includes("ozw")) { import("./device-detail/integration-elements/ozw/ha-device-actions-ozw"); import("./device-detail/integration-elements/ozw/ha-device-info-ozw"); - templates.push(html` + deviceInfo.push(html` -
- -
+ `); + deviceActions.push(html` + `); } - if (integrations.includes("tasmota")) { + if (domains.includes("tasmota")) { import( "./device-detail/integration-elements/tasmota/ha-device-actions-tasmota" ); - templates.push(html` -
- -
+ deviceActions.push(html` + `); } - if (integrations.includes("zha")) { + if (domains.includes("zha")) { import("./device-detail/integration-elements/zha/ha-device-actions-zha"); import("./device-detail/integration-elements/zha/ha-device-info-zha"); - templates.push(html` + deviceInfo.push(html` -
- -
+ `); + deviceActions.push(html` + `); } - if (integrations.includes("zwave_js")) { + if (domains.includes("zwave_js")) { import( "./device-detail/integration-elements/zwave_js/ha-device-info-zwave_js" ); import( "./device-detail/integration-elements/zwave_js/ha-device-actions-zwave_js" ); - templates.push(html` + deviceInfo.push(html` -
- -
+ `); + deviceActions.push(html` + `); } return templates; @@ -866,6 +952,16 @@ export class HaConfigDevicePage extends LitElement { }); } + private async _signUrl(ev) { + const anchor = ev.target.closest("a"); + ev.preventDefault(); + const signedUrl = await getSignedPath( + this.hass, + anchor.getAttribute("href") + ); + fileDownload(signedUrl.path); + } + static get styles(): CSSResultGroup { return [ haStyle, diff --git a/src/translations/en.json b/src/translations/en.json index 1e25eb8b9f..c95ec5fd6f 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2204,6 +2204,7 @@ "enabled_description": "Disabled devices will not be shown and entities belonging to the device will be disabled and not added to Home Assistant.", "open_configuration_url_device": "Visit device", "open_configuration_url_service": "Visit service", + "download_diagnostics": "Download diagnostics", "automation": { "automations": "Automations", "no_automations": "No automations",