From ebe0caba838ae3e331ae78ac0e6d5cb8b797ea26 Mon Sep 17 00:00:00 2001 From: Charles Garwood Date: Wed, 28 Apr 2021 13:00:13 -0400 Subject: [PATCH] MVP Z-Wave JS Log Viewer (#9008) * Add element to subscribe to ZJS logs * set log level and adjust styling * review comments * add ZWaveJS to function names * use flexbox * Review comments * import Co-authored-by: Paulus Schoutsen Co-authored-by: Bram Kragten --- src/data/zwave_js.ts | 49 +++++- .../zwave_js/zwave_js-config-router.ts | 11 +- .../zwave_js/zwave_js-logs.ts | 157 ++++++++++++++++++ src/translations/en.json | 9 +- 4 files changed, 221 insertions(+), 5 deletions(-) create mode 100644 src/panels/config/integrations/integration-panels/zwave_js/zwave_js-logs.ts diff --git a/src/data/zwave_js.ts b/src/data/zwave_js.ts index e58d0c4331..af5f30436e 100644 --- a/src/data/zwave_js.ts +++ b/src/data/zwave_js.ts @@ -173,9 +173,9 @@ export const reinterviewNode = ( ); }; -export const getIdentifiersFromDevice = function ( +export const getIdentifiersFromDevice = ( device: DeviceRegistryEntry -): ZWaveJSNodeIdentifiers | undefined { +): ZWaveJSNodeIdentifiers | undefined => { if (!device) { return undefined; } @@ -193,3 +193,48 @@ export const getIdentifiersFromDevice = function ( home_id: identifiers[0], }; }; + +export interface ZWaveJSLogMessage { + timestamp: string; + level: string; + primary_tags: string; + message: string | string[]; +} + +export const subscribeZWaveJSLogs = ( + hass: HomeAssistant, + entry_id: string, + callback: (message: ZWaveJSLogMessage) => void +) => + hass.connection.subscribeMessage(callback, { + type: "zwave_js/subscribe_logs", + entry_id, + }); + +export interface ZWaveJSLogConfig { + level: string; + enabled: boolean; + filename: string; + log_to_file: boolean; + force_console: boolean; +} + +export const fetchZWaveJSLogConfig = ( + hass: HomeAssistant, + entry_id: string +): Promise => + hass.callWS({ + type: "zwave_js/get_log_config", + entry_id, + }); + +export const setZWaveJSLogLevel = ( + hass: HomeAssistant, + entry_id: string, + level: string +): Promise => + hass.callWS({ + type: "zwave_js/update_log_config", + entry_id, + config: { level }, + }); diff --git a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-router.ts b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-router.ts index a30eedaebf..2973f17c3b 100644 --- a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-router.ts +++ b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-router.ts @@ -7,7 +7,7 @@ import { HomeAssistant } from "../../../../../types"; import { navigate } from "../../../../../common/navigate"; import { PageNavigation } from "../../../../../layouts/hass-tabs-subpage"; -import { mdiServerNetwork } from "@mdi/js"; +import { mdiServerNetwork, mdiMathLog } from "@mdi/js"; export const configTabs: PageNavigation[] = [ { @@ -15,6 +15,11 @@ export const configTabs: PageNavigation[] = [ path: `/config/zwave_js/dashboard`, iconPath: mdiServerNetwork, }, + { + translationKey: "ui.panel.config.zwave_js.navigation.logs", + path: `/config/zwave_js/logs`, + iconPath: mdiMathLog, + }, ]; @customElement("zwave_js-config-router") @@ -41,6 +46,10 @@ class ZWaveJSConfigRouter extends HassRouterPage { tag: "zwave_js-node-config", load: () => import("./zwave_js-node-config"), }, + logs: { + tag: "zwave_js-logs", + load: () => import("./zwave_js-logs"), + }, }, }; diff --git a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-logs.ts b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-logs.ts new file mode 100644 index 0000000000..730f79b483 --- /dev/null +++ b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-logs.ts @@ -0,0 +1,157 @@ +import { UnsubscribeFunc } from "home-assistant-js-websocket"; +import { + css, + html, + property, + customElement, + LitElement, + CSSResultArray, + internalProperty, + query, +} from "lit-element"; +import "@polymer/paper-listbox/paper-listbox"; +import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; +import { + fetchZWaveJSLogConfig, + setZWaveJSLogLevel, + subscribeZWaveJSLogs, + ZWaveJSLogConfig, +} from "../../../../../data/zwave_js"; +import { SubscribeMixin } from "../../../../../mixins/subscribe-mixin"; +import { HomeAssistant, Route } from "../../../../../types"; +import { configTabs } from "./zwave_js-config-router"; +import "../../../../../layouts/hass-tabs-subpage"; +import { haStyle } from "../../../../../resources/styles"; + +@customElement("zwave_js-logs") +class ZWaveJSLogs extends SubscribeMixin(LitElement) { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ type: Object }) public route!: Route; + + @property({ type: Boolean }) public narrow!: boolean; + + @property() public configEntryId!: string; + + @internalProperty() private _logConfig?: ZWaveJSLogConfig; + + @query("textarea", true) private _textarea?: HTMLTextAreaElement; + + public hassSubscribe(): Array> { + return [ + subscribeZWaveJSLogs(this.hass, this.configEntryId, (log) => { + if (!this.hasUpdated) { + return; + } + if (Array.isArray(log.message)) { + for (const line of log.message) { + this._textarea!.value += `${line}\n`; + } + } else { + this._textarea!.value += `${log.message}\n`; + } + }), + ]; + } + + protected render() { + return html` + +
+ +
+

+ ${this.hass.localize("ui.panel.config.zwave_js.logs.title")} +

+
+
+ ${this._logConfig + ? html` + + + Error + Warn + Info + Verbose + Debug + Silly + + + ` + : ""} +
+
+ +
+
+ `; + } + + protected firstUpdated(changedProps) { + super.firstUpdated(changedProps); + this._fetchData(); + } + + private async _fetchData() { + if (!this.configEntryId) { + return; + } + this._logConfig = await fetchZWaveJSLogConfig( + this.hass!, + this.configEntryId + ); + } + + private _dropdownSelected(ev) { + if (ev.target === undefined || this._logConfig === undefined) { + return; + } + if (this._logConfig.level === ev.target.selected) { + return; + } + setZWaveJSLogLevel(this.hass!, this.configEntryId, ev.target.selected); + } + + static get styles(): CSSResultArray { + return [ + haStyle, + css` + .container { + display: flex; + flex-direction: column; + height: 100%; + box-sizing: border-box; + padding: 16px; + } + textarea { + flex-grow: 1; + padding: 16px; + } + ha-card { + margin: 16px 0; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "zwave_js-logs": ZWaveJSLogs; + } +} diff --git a/src/translations/en.json b/src/translations/en.json index cf4a17345d..ccc0c3a785 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2562,7 +2562,8 @@ }, "zwave_js": { "navigation": { - "network": "Network" + "network": "Network", + "logs": "Logs" }, "common": { "network": "Network", @@ -2649,6 +2650,10 @@ "in_progress": "The device is being interviewed. This may take some time.", "interview_failed": "The device interview failed. Additional information may be available in the logs.", "interview_complete": "Device interview complete." + }, + "logs": { + "title": "Z-Wave JS Logs", + "log_level": "Log Level" } } }, @@ -3936,4 +3941,4 @@ } } } -} +} \ No newline at end of file