mirror of
https://github.com/home-assistant/frontend.git
synced 2025-04-30 00:07:19 +00:00
Allow downloading device diagnostics (#11370)
This commit is contained in:
parent
f7fc83ac12
commit
81faae6f74
@ -1,9 +1,10 @@
|
|||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
interface DiagnosticInfo {
|
export interface DiagnosticInfo {
|
||||||
domain: string;
|
domain: string;
|
||||||
handlers: {
|
handlers: {
|
||||||
config_entry: boolean;
|
config_entry: boolean;
|
||||||
|
device: boolean;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -14,5 +15,19 @@ export const fetchDiagnosticHandlers = (
|
|||||||
type: "diagnostics/list",
|
type: "diagnostics/list",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const fetchDiagnosticHandler = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
domain: string
|
||||||
|
): Promise<DiagnosticInfo> =>
|
||||||
|
hass.callWS<DiagnosticInfo>({
|
||||||
|
type: "diagnostics/get",
|
||||||
|
domain,
|
||||||
|
});
|
||||||
|
|
||||||
export const getConfigEntryDiagnosticsDownloadUrl = (entry_id: string) =>
|
export const getConfigEntryDiagnosticsDownloadUrl = (entry_id: string) =>
|
||||||
`/api/diagnostics/config_entry/${entry_id}`;
|
`/api/diagnostics/config_entry/${entry_id}`;
|
||||||
|
|
||||||
|
export const getDeviceDiagnosticsDownloadUrl = (
|
||||||
|
entry_id: string,
|
||||||
|
device_id: string
|
||||||
|
) => `/api/diagnostics/config_entry/${entry_id}/device/${device_id}`;
|
||||||
|
@ -3,6 +3,7 @@ import "@polymer/paper-tooltip/paper-tooltip";
|
|||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { ifDefined } from "lit/directives/if-defined";
|
import { ifDefined } from "lit/directives/if-defined";
|
||||||
|
import { until } from "lit/directives/until";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||||
@ -17,6 +18,7 @@ import "../../../components/ha-icon-button";
|
|||||||
import "../../../components/ha-icon-next";
|
import "../../../components/ha-icon-next";
|
||||||
import "../../../components/ha-svg-icon";
|
import "../../../components/ha-svg-icon";
|
||||||
import { AreaRegistryEntry } from "../../../data/area_registry";
|
import { AreaRegistryEntry } from "../../../data/area_registry";
|
||||||
|
import { getSignedPath } from "../../../data/auth";
|
||||||
import {
|
import {
|
||||||
ConfigEntry,
|
ConfigEntry,
|
||||||
disableConfigEntry,
|
disableConfigEntry,
|
||||||
@ -27,6 +29,11 @@ import {
|
|||||||
DeviceRegistryEntry,
|
DeviceRegistryEntry,
|
||||||
updateDeviceRegistryEntry,
|
updateDeviceRegistryEntry,
|
||||||
} from "../../../data/device_registry";
|
} from "../../../data/device_registry";
|
||||||
|
import {
|
||||||
|
fetchDiagnosticHandler,
|
||||||
|
getDeviceDiagnosticsDownloadUrl,
|
||||||
|
getConfigEntryDiagnosticsDownloadUrl,
|
||||||
|
} from "../../../data/diagnostics";
|
||||||
import {
|
import {
|
||||||
EntityRegistryEntry,
|
EntityRegistryEntry,
|
||||||
findBatteryChargingEntity,
|
findBatteryChargingEntity,
|
||||||
@ -44,6 +51,7 @@ import "../../../layouts/hass-tabs-subpage";
|
|||||||
import { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
import { HomeAssistant, Route } from "../../../types";
|
import { HomeAssistant, Route } from "../../../types";
|
||||||
import { brandsUrl } from "../../../util/brands-url";
|
import { brandsUrl } from "../../../util/brands-url";
|
||||||
|
import { fileDownload } from "../../../util/file_download";
|
||||||
import "../ha-config-section";
|
import "../ha-config-section";
|
||||||
import { configSections } from "../ha-panel-config";
|
import { configSections } from "../ha-panel-config";
|
||||||
import "./device-detail/ha-device-entities-card";
|
import "./device-detail/ha-device-entities-card";
|
||||||
@ -82,6 +90,10 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
|
|
||||||
@state() private _related?: RelatedResult;
|
@state() private _related?: RelatedResult;
|
||||||
|
|
||||||
|
@state() private _diagnosticDownloadLinks?: Promise<
|
||||||
|
(TemplateResult | string)[]
|
||||||
|
>;
|
||||||
|
|
||||||
private _device = memoizeOne(
|
private _device = memoizeOne(
|
||||||
(
|
(
|
||||||
deviceId: string,
|
deviceId: string,
|
||||||
@ -91,10 +103,8 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
);
|
);
|
||||||
|
|
||||||
private _integrations = memoizeOne(
|
private _integrations = memoizeOne(
|
||||||
(device: DeviceRegistryEntry, entries: ConfigEntry[]): string[] =>
|
(device: DeviceRegistryEntry, entries: ConfigEntry[]): ConfigEntry[] =>
|
||||||
entries
|
entries.filter((entry) => device.config_entries.includes(entry.entry_id))
|
||||||
.filter((entry) => device.config_entries.includes(entry.entry_id))
|
|
||||||
.map((entry) => entry.domain)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
private _entities = memoizeOne(
|
private _entities = memoizeOne(
|
||||||
@ -165,6 +175,63 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
findBatteryChargingEntity(this.hass, entities)
|
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`
|
||||||
|
<a href=${link} @click=${this._signUrl}>
|
||||||
|
<mwc-button>
|
||||||
|
${this.hass.localize(
|
||||||
|
`ui.panel.config.devices.download_diagnostics`
|
||||||
|
)}
|
||||||
|
</mwc-button>
|
||||||
|
</a>
|
||||||
|
`;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
protected firstUpdated(changedProps) {
|
protected firstUpdated(changedProps) {
|
||||||
super.firstUpdated(changedProps);
|
super.firstUpdated(changedProps);
|
||||||
loadDeviceRegistryDetailDialog();
|
loadDeviceRegistryDetailDialog();
|
||||||
@ -214,6 +281,66 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
? device.configuration_url!.replace("homeassistant://", "/")
|
? device.configuration_url!.replace("homeassistant://", "/")
|
||||||
: device.configuration_url;
|
: device.configuration_url;
|
||||||
|
|
||||||
|
const deviceInfo: TemplateResult[] = [];
|
||||||
|
|
||||||
|
if (device.disabled_by) {
|
||||||
|
deviceInfo.push(
|
||||||
|
html`
|
||||||
|
<ha-alert alert-type="warning">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.devices.enabled_cause",
|
||||||
|
"cause",
|
||||||
|
this.hass.localize(
|
||||||
|
`ui.panel.config.devices.disabled_by.${device.disabled_by}`
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</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>`
|
||||||
|
: ""}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const deviceActions: TemplateResult[] = [];
|
||||||
|
|
||||||
|
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
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this._diagnosticDownloadLinks) {
|
||||||
|
deviceActions.push(html`${until(this._diagnosticDownloadLinks)}`);
|
||||||
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<hass-tabs-subpage
|
<hass-tabs-subpage
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@ -291,7 +418,7 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
? html`
|
? html`
|
||||||
<img
|
<img
|
||||||
src=${brandsUrl({
|
src=${brandsUrl({
|
||||||
domain: integrations[0],
|
domain: integrations[0].domain,
|
||||||
type: "logo",
|
type: "logo",
|
||||||
darkOptimized: this.hass.themes?.darkMode,
|
darkOptimized: this.hass.themes?.darkMode,
|
||||||
})}
|
})}
|
||||||
@ -312,56 +439,16 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
.devices=${this.devices}
|
.devices=${this.devices}
|
||||||
.device=${device}
|
.device=${device}
|
||||||
>
|
>
|
||||||
${
|
${deviceInfo}
|
||||||
device.disabled_by
|
${
|
||||||
? html`
|
deviceActions.length
|
||||||
<ha-alert alert-type="warning">
|
? html`
|
||||||
${this.hass.localize(
|
<div class="card-actions" slot="actions">
|
||||||
"ui.panel.config.devices.enabled_cause",
|
${deviceActions}
|
||||||
"cause",
|
</div>
|
||||||
this.hass.localize(
|
`
|
||||||
`ui.panel.config.devices.disabled_by.${device.disabled_by}`
|
: ""
|
||||||
)
|
}
|
||||||
)}
|
|
||||||
</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``
|
|
||||||
}
|
|
||||||
${
|
|
||||||
configurationUrl
|
|
||||||
? html`
|
|
||||||
<div class="card-actions" slot="actions">
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: ""
|
|
||||||
}
|
|
||||||
${this._renderIntegrationInfo(device, integrations)}
|
|
||||||
</ha-device-info-card>
|
</ha-device-info-card>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
@ -648,85 +735,84 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
|
|
||||||
private _renderIntegrationInfo(
|
private _renderIntegrationInfo(
|
||||||
device,
|
device,
|
||||||
integrations: string[]
|
integrations: ConfigEntry[],
|
||||||
|
deviceInfo: TemplateResult[],
|
||||||
|
deviceActions: TemplateResult[]
|
||||||
): TemplateResult[] {
|
): TemplateResult[] {
|
||||||
|
const domains = integrations.map((int) => int.domain);
|
||||||
const templates: TemplateResult[] = [];
|
const templates: TemplateResult[] = [];
|
||||||
if (integrations.includes("mqtt")) {
|
if (domains.includes("mqtt")) {
|
||||||
import(
|
import(
|
||||||
"./device-detail/integration-elements/mqtt/ha-device-actions-mqtt"
|
"./device-detail/integration-elements/mqtt/ha-device-actions-mqtt"
|
||||||
);
|
);
|
||||||
templates.push(html`
|
deviceActions.push(html`
|
||||||
<div class="card-actions" slot="actions">
|
<ha-device-actions-mqtt
|
||||||
<ha-device-actions-mqtt
|
.hass=${this.hass}
|
||||||
.hass=${this.hass}
|
.device=${device}
|
||||||
.device=${device}
|
></ha-device-actions-mqtt>
|
||||||
></ha-device-actions-mqtt>
|
|
||||||
</div>
|
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
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-actions-ozw");
|
||||||
import("./device-detail/integration-elements/ozw/ha-device-info-ozw");
|
import("./device-detail/integration-elements/ozw/ha-device-info-ozw");
|
||||||
templates.push(html`
|
deviceInfo.push(html`
|
||||||
<ha-device-info-ozw
|
<ha-device-info-ozw
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.device=${device}
|
.device=${device}
|
||||||
></ha-device-info-ozw>
|
></ha-device-info-ozw>
|
||||||
<div class="card-actions" slot="actions">
|
`);
|
||||||
<ha-device-actions-ozw
|
deviceActions.push(html`
|
||||||
.hass=${this.hass}
|
<ha-device-actions-ozw
|
||||||
.device=${device}
|
.hass=${this.hass}
|
||||||
></ha-device-actions-ozw>
|
.device=${device}
|
||||||
</div>
|
></ha-device-actions-ozw>
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
if (integrations.includes("tasmota")) {
|
if (domains.includes("tasmota")) {
|
||||||
import(
|
import(
|
||||||
"./device-detail/integration-elements/tasmota/ha-device-actions-tasmota"
|
"./device-detail/integration-elements/tasmota/ha-device-actions-tasmota"
|
||||||
);
|
);
|
||||||
templates.push(html`
|
deviceActions.push(html`
|
||||||
<div class="card-actions" slot="actions">
|
<ha-device-actions-tasmota
|
||||||
<ha-device-actions-tasmota
|
.hass=${this.hass}
|
||||||
.hass=${this.hass}
|
.device=${device}
|
||||||
.device=${device}
|
></ha-device-actions-tasmota>
|
||||||
></ha-device-actions-tasmota>
|
|
||||||
</div>
|
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
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-actions-zha");
|
||||||
import("./device-detail/integration-elements/zha/ha-device-info-zha");
|
import("./device-detail/integration-elements/zha/ha-device-info-zha");
|
||||||
templates.push(html`
|
deviceInfo.push(html`
|
||||||
<ha-device-info-zha
|
<ha-device-info-zha
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.device=${device}
|
.device=${device}
|
||||||
></ha-device-info-zha>
|
></ha-device-info-zha>
|
||||||
<div class="card-actions" slot="actions">
|
`);
|
||||||
<ha-device-actions-zha
|
deviceActions.push(html`
|
||||||
.hass=${this.hass}
|
<ha-device-actions-zha
|
||||||
.device=${device}
|
.hass=${this.hass}
|
||||||
></ha-device-actions-zha>
|
.device=${device}
|
||||||
</div>
|
></ha-device-actions-zha>
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
if (integrations.includes("zwave_js")) {
|
if (domains.includes("zwave_js")) {
|
||||||
import(
|
import(
|
||||||
"./device-detail/integration-elements/zwave_js/ha-device-info-zwave_js"
|
"./device-detail/integration-elements/zwave_js/ha-device-info-zwave_js"
|
||||||
);
|
);
|
||||||
import(
|
import(
|
||||||
"./device-detail/integration-elements/zwave_js/ha-device-actions-zwave_js"
|
"./device-detail/integration-elements/zwave_js/ha-device-actions-zwave_js"
|
||||||
);
|
);
|
||||||
templates.push(html`
|
deviceInfo.push(html`
|
||||||
<ha-device-info-zwave_js
|
<ha-device-info-zwave_js
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.device=${device}
|
.device=${device}
|
||||||
></ha-device-info-zwave_js>
|
></ha-device-info-zwave_js>
|
||||||
<div class="card-actions" slot="actions">
|
`);
|
||||||
<ha-device-actions-zwave_js
|
deviceActions.push(html`
|
||||||
.hass=${this.hass}
|
<ha-device-actions-zwave_js
|
||||||
.device=${device}
|
.hass=${this.hass}
|
||||||
></ha-device-actions-zwave_js>
|
.device=${device}
|
||||||
</div>
|
></ha-device-actions-zwave_js>
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
return templates;
|
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 {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
|
@ -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.",
|
"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_device": "Visit device",
|
||||||
"open_configuration_url_service": "Visit service",
|
"open_configuration_url_service": "Visit service",
|
||||||
|
"download_diagnostics": "Download diagnostics",
|
||||||
"automation": {
|
"automation": {
|
||||||
"automations": "Automations",
|
"automations": "Automations",
|
||||||
"no_automations": "No automations",
|
"no_automations": "No automations",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user