mirror of
https://github.com/home-assistant/frontend.git
synced 2025-04-27 23:07:20 +00:00
Allow downloading device diagnostics (#11370)
This commit is contained in:
parent
f7fc83ac12
commit
81faae6f74
@ -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<DiagnosticInfo> =>
|
||||
hass.callWS<DiagnosticInfo>({
|
||||
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}`;
|
||||
|
@ -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`
|
||||
<a href=${link} @click=${this._signUrl}>
|
||||
<mwc-button>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.devices.download_diagnostics`
|
||||
)}
|
||||
</mwc-button>
|
||||
</a>
|
||||
`;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
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`
|
||||
<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`
|
||||
<hass-tabs-subpage
|
||||
.hass=${this.hass}
|
||||
@ -291,7 +418,7 @@ export class HaConfigDevicePage extends LitElement {
|
||||
? html`
|
||||
<img
|
||||
src=${brandsUrl({
|
||||
domain: integrations[0],
|
||||
domain: integrations[0].domain,
|
||||
type: "logo",
|
||||
darkOptimized: this.hass.themes?.darkMode,
|
||||
})}
|
||||
@ -312,56 +439,16 @@ export class HaConfigDevicePage extends LitElement {
|
||||
.devices=${this.devices}
|
||||
.device=${device}
|
||||
>
|
||||
${
|
||||
device.disabled_by
|
||||
? 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>`
|
||||
: ""}
|
||||
`
|
||||
: 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)}
|
||||
${deviceInfo}
|
||||
${
|
||||
deviceActions.length
|
||||
? html`
|
||||
<div class="card-actions" slot="actions">
|
||||
${deviceActions}
|
||||
</div>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
</ha-device-info-card>
|
||||
</div>
|
||||
<div class="column">
|
||||
@ -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`
|
||||
<div class="card-actions" slot="actions">
|
||||
<ha-device-actions-mqtt
|
||||
.hass=${this.hass}
|
||||
.device=${device}
|
||||
></ha-device-actions-mqtt>
|
||||
</div>
|
||||
deviceActions.push(html`
|
||||
<ha-device-actions-mqtt
|
||||
.hass=${this.hass}
|
||||
.device=${device}
|
||||
></ha-device-actions-mqtt>
|
||||
`);
|
||||
}
|
||||
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`
|
||||
<ha-device-info-ozw
|
||||
.hass=${this.hass}
|
||||
.device=${device}
|
||||
></ha-device-info-ozw>
|
||||
<div class="card-actions" slot="actions">
|
||||
<ha-device-actions-ozw
|
||||
.hass=${this.hass}
|
||||
.device=${device}
|
||||
></ha-device-actions-ozw>
|
||||
</div>
|
||||
`);
|
||||
deviceActions.push(html`
|
||||
<ha-device-actions-ozw
|
||||
.hass=${this.hass}
|
||||
.device=${device}
|
||||
></ha-device-actions-ozw>
|
||||
`);
|
||||
}
|
||||
if (integrations.includes("tasmota")) {
|
||||
if (domains.includes("tasmota")) {
|
||||
import(
|
||||
"./device-detail/integration-elements/tasmota/ha-device-actions-tasmota"
|
||||
);
|
||||
templates.push(html`
|
||||
<div class="card-actions" slot="actions">
|
||||
<ha-device-actions-tasmota
|
||||
.hass=${this.hass}
|
||||
.device=${device}
|
||||
></ha-device-actions-tasmota>
|
||||
</div>
|
||||
deviceActions.push(html`
|
||||
<ha-device-actions-tasmota
|
||||
.hass=${this.hass}
|
||||
.device=${device}
|
||||
></ha-device-actions-tasmota>
|
||||
`);
|
||||
}
|
||||
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`
|
||||
<ha-device-info-zha
|
||||
.hass=${this.hass}
|
||||
.device=${device}
|
||||
></ha-device-info-zha>
|
||||
<div class="card-actions" slot="actions">
|
||||
<ha-device-actions-zha
|
||||
.hass=${this.hass}
|
||||
.device=${device}
|
||||
></ha-device-actions-zha>
|
||||
</div>
|
||||
`);
|
||||
deviceActions.push(html`
|
||||
<ha-device-actions-zha
|
||||
.hass=${this.hass}
|
||||
.device=${device}
|
||||
></ha-device-actions-zha>
|
||||
`);
|
||||
}
|
||||
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`
|
||||
<ha-device-info-zwave_js
|
||||
.hass=${this.hass}
|
||||
.device=${device}
|
||||
></ha-device-info-zwave_js>
|
||||
<div class="card-actions" slot="actions">
|
||||
<ha-device-actions-zwave_js
|
||||
.hass=${this.hass}
|
||||
.device=${device}
|
||||
></ha-device-actions-zwave_js>
|
||||
</div>
|
||||
`);
|
||||
deviceActions.push(html`
|
||||
<ha-device-actions-zwave_js
|
||||
.hass=${this.hass}
|
||||
.device=${device}
|
||||
></ha-device-actions-zwave_js>
|
||||
`);
|
||||
}
|
||||
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,
|
||||
|
@ -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",
|
||||
|
Loading…
x
Reference in New Issue
Block a user