mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-25 18:26:35 +00:00
Add the ability to enable debug logs in the config entry overflow (#14319)
This commit is contained in:
parent
b97a9ef311
commit
44502d2c8d
@ -1,5 +1,7 @@
|
|||||||
|
import { Connection, createCollection } from "home-assistant-js-websocket";
|
||||||
import { LocalizeFunc } from "../common/translations/localize";
|
import { LocalizeFunc } from "../common/translations/localize";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
|
import { debounce } from "../common/util/debounce";
|
||||||
|
|
||||||
export type IntegrationType =
|
export type IntegrationType =
|
||||||
| "device"
|
| "device"
|
||||||
@ -23,6 +25,7 @@ export interface IntegrationManifest {
|
|||||||
zeroconf?: string[];
|
zeroconf?: string[];
|
||||||
homekit?: { models: string[] };
|
homekit?: { models: string[] };
|
||||||
integration_type?: IntegrationType;
|
integration_type?: IntegrationType;
|
||||||
|
loggers?: string[];
|
||||||
quality_scale?: "gold" | "internal" | "platinum" | "silver";
|
quality_scale?: "gold" | "internal" | "platinum" | "silver";
|
||||||
iot_class:
|
iot_class:
|
||||||
| "assumed_state"
|
| "assumed_state"
|
||||||
@ -36,6 +39,24 @@ export interface IntegrationSetup {
|
|||||||
seconds?: number;
|
seconds?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IntegrationLogInfo {
|
||||||
|
domain: string;
|
||||||
|
level?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum LogSeverity {
|
||||||
|
CRITICAL = 50,
|
||||||
|
FATAL = 50,
|
||||||
|
ERROR = 40,
|
||||||
|
WARNING = 30,
|
||||||
|
WARN = 30,
|
||||||
|
INFO = 20,
|
||||||
|
DEBUG = 10,
|
||||||
|
NOTSET = 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IntegrationLogPersistance = "none" | "once" | "permanent";
|
||||||
|
|
||||||
export const integrationIssuesUrl = (
|
export const integrationIssuesUrl = (
|
||||||
domain: string,
|
domain: string,
|
||||||
manifest: IntegrationManifest
|
manifest: IntegrationManifest
|
||||||
@ -69,3 +90,46 @@ export const fetchIntegrationManifest = (
|
|||||||
|
|
||||||
export const fetchIntegrationSetups = (hass: HomeAssistant) =>
|
export const fetchIntegrationSetups = (hass: HomeAssistant) =>
|
||||||
hass.callWS<IntegrationSetup[]>({ type: "integration/setup_info" });
|
hass.callWS<IntegrationSetup[]>({ type: "integration/setup_info" });
|
||||||
|
|
||||||
|
export const fetchIntegrationLogInfo = (conn) =>
|
||||||
|
conn.sendMessagePromise({
|
||||||
|
type: "logger/log_info",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const setIntegrationLogLevel = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
integration: string,
|
||||||
|
level: string,
|
||||||
|
persistence: IntegrationLogPersistance
|
||||||
|
) =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "logger/integration_log_level",
|
||||||
|
integration,
|
||||||
|
level,
|
||||||
|
persistence,
|
||||||
|
});
|
||||||
|
|
||||||
|
const subscribeLogInfoUpdates = (conn, store) =>
|
||||||
|
conn.subscribeEvents(
|
||||||
|
debounce(
|
||||||
|
() =>
|
||||||
|
fetchIntegrationLogInfo(conn).then((log_infos) =>
|
||||||
|
store.setState(log_infos, true)
|
||||||
|
),
|
||||||
|
200,
|
||||||
|
true
|
||||||
|
),
|
||||||
|
"logging_changed"
|
||||||
|
);
|
||||||
|
|
||||||
|
export const subscribeLogInfo = (
|
||||||
|
conn: Connection,
|
||||||
|
onChange: (devices: IntegrationLogInfo[]) => void
|
||||||
|
) =>
|
||||||
|
createCollection<IntegrationLogInfo[]>(
|
||||||
|
"_integration_log_info",
|
||||||
|
fetchIntegrationLogInfo,
|
||||||
|
subscribeLogInfoUpdates,
|
||||||
|
conn,
|
||||||
|
onChange
|
||||||
|
);
|
||||||
|
@ -51,6 +51,8 @@ import {
|
|||||||
fetchIntegrationManifest,
|
fetchIntegrationManifest,
|
||||||
fetchIntegrationManifests,
|
fetchIntegrationManifests,
|
||||||
IntegrationManifest,
|
IntegrationManifest,
|
||||||
|
IntegrationLogInfo,
|
||||||
|
subscribeLogInfo,
|
||||||
} from "../../../data/integration";
|
} from "../../../data/integration";
|
||||||
import {
|
import {
|
||||||
getIntegrationDescriptions,
|
getIntegrationDescriptions,
|
||||||
@ -154,6 +156,10 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@state() private _diagnosticHandlers?: Record<string, boolean>;
|
@state() private _diagnosticHandlers?: Record<string, boolean>;
|
||||||
|
|
||||||
|
@state() private _logInfos?: {
|
||||||
|
[integration: string]: IntegrationLogInfo;
|
||||||
|
};
|
||||||
|
|
||||||
public hassSubscribe(): Array<UnsubscribeFunc | Promise<UnsubscribeFunc>> {
|
public hassSubscribe(): Array<UnsubscribeFunc | Promise<UnsubscribeFunc>> {
|
||||||
return [
|
return [
|
||||||
subscribeEntityRegistry(this.hass.connection, (entries) => {
|
subscribeEntityRegistry(this.hass.connection, (entries) => {
|
||||||
@ -230,6 +236,13 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
},
|
},
|
||||||
{ type: ["device", "hub", "service"] }
|
{ type: ["device", "hub", "service"] }
|
||||||
),
|
),
|
||||||
|
subscribeLogInfo(this.hass.connection, (log_infos) => {
|
||||||
|
const logInfoLookup: { [integration: string]: IntegrationLogInfo } = {};
|
||||||
|
for (const log_info of log_infos) {
|
||||||
|
logInfoLookup[log_info.domain] = log_info;
|
||||||
|
}
|
||||||
|
this._logInfos = logInfoLookup;
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -514,6 +527,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
.supportsDiagnostics=${this._diagnosticHandlers
|
.supportsDiagnostics=${this._diagnosticHandlers
|
||||||
? this._diagnosticHandlers[domain]
|
? this._diagnosticHandlers[domain]
|
||||||
: false}
|
: false}
|
||||||
|
.logInfo=${this._logInfos ? this._logInfos[domain] : null}
|
||||||
></ha-integration-card>`
|
></ha-integration-card>`
|
||||||
)
|
)
|
||||||
: this._filter &&
|
: this._filter &&
|
||||||
|
@ -5,6 +5,8 @@ import {
|
|||||||
mdiAlertCircle,
|
mdiAlertCircle,
|
||||||
mdiBookshelf,
|
mdiBookshelf,
|
||||||
mdiBug,
|
mdiBug,
|
||||||
|
mdiBugPlay,
|
||||||
|
mdiBugStop,
|
||||||
mdiChevronLeft,
|
mdiChevronLeft,
|
||||||
mdiCog,
|
mdiCog,
|
||||||
mdiDelete,
|
mdiDelete,
|
||||||
@ -47,11 +49,17 @@ import {
|
|||||||
ERROR_STATES,
|
ERROR_STATES,
|
||||||
RECOVERABLE_STATES,
|
RECOVERABLE_STATES,
|
||||||
} from "../../../data/config_entries";
|
} from "../../../data/config_entries";
|
||||||
|
import { getErrorLogDownloadUrl } from "../../../data/error_log";
|
||||||
import type { DeviceRegistryEntry } from "../../../data/device_registry";
|
import type { DeviceRegistryEntry } from "../../../data/device_registry";
|
||||||
import { getConfigEntryDiagnosticsDownloadUrl } from "../../../data/diagnostics";
|
import { getConfigEntryDiagnosticsDownloadUrl } from "../../../data/diagnostics";
|
||||||
import type { EntityRegistryEntry } from "../../../data/entity_registry";
|
import type { EntityRegistryEntry } from "../../../data/entity_registry";
|
||||||
import type { IntegrationManifest } from "../../../data/integration";
|
import type { IntegrationManifest } from "../../../data/integration";
|
||||||
import { integrationIssuesUrl } from "../../../data/integration";
|
import {
|
||||||
|
integrationIssuesUrl,
|
||||||
|
IntegrationLogInfo,
|
||||||
|
LogSeverity,
|
||||||
|
setIntegrationLogLevel,
|
||||||
|
} from "../../../data/integration";
|
||||||
import { showConfigEntrySystemOptionsDialog } from "../../../dialogs/config-entry-system-options/show-dialog-config-entry-system-options";
|
import { showConfigEntrySystemOptionsDialog } from "../../../dialogs/config-entry-system-options/show-dialog-config-entry-system-options";
|
||||||
import { showOptionsFlowDialog } from "../../../dialogs/config-flow/show-dialog-options-flow";
|
import { showOptionsFlowDialog } from "../../../dialogs/config-flow/show-dialog-options-flow";
|
||||||
import {
|
import {
|
||||||
@ -95,6 +103,8 @@ export class HaIntegrationCard extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public supportsDiagnostics = false;
|
@property({ type: Boolean }) public supportsDiagnostics = false;
|
||||||
|
|
||||||
|
@property() public logInfo?: IntegrationLogInfo;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
let item = this._selectededConfigEntry;
|
let item = this._selectededConfigEntry;
|
||||||
|
|
||||||
@ -137,6 +147,8 @@ export class HaIntegrationCard extends LitElement {
|
|||||||
.localizedDomainName=${item ? item.localized_domain_name : undefined}
|
.localizedDomainName=${item ? item.localized_domain_name : undefined}
|
||||||
.manifest=${this.manifest}
|
.manifest=${this.manifest}
|
||||||
.configEntry=${item}
|
.configEntry=${item}
|
||||||
|
.debugLoggingEnabled=${this.logInfo &&
|
||||||
|
this.logInfo.level === LogSeverity.DEBUG}
|
||||||
>
|
>
|
||||||
${this.items.length > 1
|
${this.items.length > 1
|
||||||
? html`
|
? html`
|
||||||
@ -398,6 +410,28 @@ export class HaIntegrationCard extends LitElement {
|
|||||||
</mwc-list-item>
|
</mwc-list-item>
|
||||||
</a>`
|
</a>`
|
||||||
: ""}
|
: ""}
|
||||||
|
${this.logInfo
|
||||||
|
? html`<mwc-list-item
|
||||||
|
@request-selected=${this.logInfo.level === LogSeverity.DEBUG
|
||||||
|
? this._handleDisableDebugLogging
|
||||||
|
: this._handleEnableDebugLogging}
|
||||||
|
graphic="icon"
|
||||||
|
>
|
||||||
|
${this.logInfo.level === LogSeverity.DEBUG
|
||||||
|
? this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.disable_debug_logging"
|
||||||
|
)
|
||||||
|
: this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.enable_debug_logging"
|
||||||
|
)}
|
||||||
|
<ha-svg-icon
|
||||||
|
slot="graphic"
|
||||||
|
.path=${this.logInfo.level === LogSeverity.DEBUG
|
||||||
|
? mdiBugStop
|
||||||
|
: mdiBugPlay}
|
||||||
|
></ha-svg-icon>
|
||||||
|
</mwc-list-item>`
|
||||||
|
: ""}
|
||||||
${this.manifest &&
|
${this.manifest &&
|
||||||
(this.manifest.is_built_in ||
|
(this.manifest.is_built_in ||
|
||||||
this.manifest.issue_tracker ||
|
this.manifest.issue_tracker ||
|
||||||
@ -501,6 +535,34 @@ export class HaIntegrationCard extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _handleEnableDebugLogging(ev: MouseEvent) {
|
||||||
|
const configEntry = ((ev.target as HTMLElement).closest("ha-card") as any)
|
||||||
|
.configEntry;
|
||||||
|
const integration = configEntry.domain;
|
||||||
|
await setIntegrationLogLevel(
|
||||||
|
this.hass,
|
||||||
|
integration,
|
||||||
|
LogSeverity[LogSeverity.DEBUG],
|
||||||
|
"once"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _handleDisableDebugLogging(ev: MouseEvent) {
|
||||||
|
const configEntry = ((ev.target as HTMLElement).closest("ha-card") as any)
|
||||||
|
.configEntry;
|
||||||
|
const integration = configEntry.domain;
|
||||||
|
await setIntegrationLogLevel(
|
||||||
|
this.hass,
|
||||||
|
integration,
|
||||||
|
LogSeverity[LogSeverity.NOTSET],
|
||||||
|
"once"
|
||||||
|
);
|
||||||
|
const timeString = new Date().toISOString().replace(/:/g, "-");
|
||||||
|
const logFileName = `home-assistant_${integration}_${timeString}.log`;
|
||||||
|
const signedUrl = await getSignedPath(this.hass, getErrorLogDownloadUrl);
|
||||||
|
fileDownload(signedUrl.path, logFileName);
|
||||||
|
}
|
||||||
|
|
||||||
private get _selectededConfigEntry(): ConfigEntryExtended | undefined {
|
private get _selectededConfigEntry(): ConfigEntryExtended | undefined {
|
||||||
return this.items.length === 1
|
return this.items.length === 1
|
||||||
? this.items[0]
|
? this.items[0]
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { mdiCloud, mdiPackageVariant, mdiSyncOff } from "@mdi/js";
|
import { mdiBugPlay, mdiCloud, mdiPackageVariant, mdiSyncOff } from "@mdi/js";
|
||||||
import "@polymer/paper-tooltip/paper-tooltip";
|
import "@polymer/paper-tooltip/paper-tooltip";
|
||||||
import { css, html, LitElement, TemplateResult } from "lit";
|
import { css, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
@ -24,6 +24,8 @@ export class HaIntegrationHeader extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public configEntry?: ConfigEntry;
|
@property({ attribute: false }) public configEntry?: ConfigEntry;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public debugLoggingEnabled?: boolean;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
let primary: string;
|
let primary: string;
|
||||||
let secondary: string | undefined;
|
let secondary: string | undefined;
|
||||||
@ -76,6 +78,15 @@ export class HaIntegrationHeader extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.debugLoggingEnabled) {
|
||||||
|
icons.push([
|
||||||
|
mdiBugPlay,
|
||||||
|
this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.debug_logging_enabled"
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${!this.banner ? "" : html`<div class="banner">${this.banner}</div>`}
|
${!this.banner ? "" : html`<div class="banner">${this.banner}</div>`}
|
||||||
<slot name="above-header"></slot>
|
<slot name="above-header"></slot>
|
||||||
|
@ -3011,10 +3011,12 @@
|
|||||||
"system_options": "System options",
|
"system_options": "System options",
|
||||||
"documentation": "Documentation",
|
"documentation": "Documentation",
|
||||||
"download_diagnostics": "Download diagnostics",
|
"download_diagnostics": "Download diagnostics",
|
||||||
|
"disable_debug_logging": "Disable debug logging",
|
||||||
"known_issues": "Known issues",
|
"known_issues": "Known issues",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"delete_confirm_title": "Delete {title}?",
|
"delete_confirm_title": "Delete {title}?",
|
||||||
"delete_confirm_text": "Its devices and entities will be permanently deleted.",
|
"delete_confirm_text": "Its devices and entities will be permanently deleted.",
|
||||||
|
"enable_debug_logging": "Enable debug logging",
|
||||||
"reload": "Reload",
|
"reload": "Reload",
|
||||||
"restart_confirm": "Restart Home Assistant to finish removing this integration",
|
"restart_confirm": "Restart Home Assistant to finish removing this integration",
|
||||||
"reload_confirm": "The integration was reloaded",
|
"reload_confirm": "The integration was reloaded",
|
||||||
@ -3049,6 +3051,7 @@
|
|||||||
"depends_on_cloud": "Depends on the cloud",
|
"depends_on_cloud": "Depends on the cloud",
|
||||||
"yaml_only": "Needs manual configuration",
|
"yaml_only": "Needs manual configuration",
|
||||||
"disabled_polling": "Automatic polling for updated data disabled",
|
"disabled_polling": "Automatic polling for updated data disabled",
|
||||||
|
"debug_logging_enabled": "Debug logging enabled",
|
||||||
"state": {
|
"state": {
|
||||||
"loaded": "Loaded",
|
"loaded": "Loaded",
|
||||||
"setup_error": "Failed to set up",
|
"setup_error": "Failed to set up",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user