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 83cac24c77..25cffcf1db 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
@@ -1,4 +1,4 @@
-import { mdiServerNetwork, mdiMathLog } from "@mdi/js";
+import { mdiServerNetwork, mdiMathLog, mdiNetwork } from "@mdi/js";
import { customElement, property } from "lit/decorators";
import type { RouterOptions } from "../../../../../layouts/hass-router-page";
import { HassRouterPage } from "../../../../../layouts/hass-router-page";
@@ -18,6 +18,11 @@ export const configTabs: PageNavigation[] = [
path: `/config/zwave_js/logs`,
iconPath: mdiMathLog,
},
+ {
+ translationKey: "ui.panel.config.zwave_js.navigation.visualization",
+ path: `/config/zwave_js/visualization`,
+ iconPath: mdiNetwork,
+ },
];
@customElement("zwave_js-config-router")
@@ -60,6 +65,10 @@ class ZWaveJSConfigRouter extends HassRouterPage {
tag: "zwave_js-provisioned",
load: () => import("./zwave_js-provisioned"),
},
+ visualization: {
+ tag: "zwave_js-network-visualization",
+ load: () => import("./zwave_js-network-visualization"),
+ },
},
initialLoad: () => this._fetchConfigEntries(),
};
diff --git a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-network-visualization.ts b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-network-visualization.ts
new file mode 100644
index 0000000000..d6b8d5a68c
--- /dev/null
+++ b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-network-visualization.ts
@@ -0,0 +1,111 @@
+import { customElement, property, state } from "lit/decorators";
+import { css, html, LitElement, nothing } from "lit";
+import memoizeOne from "memoize-one";
+import type { HomeAssistant, Route } from "../../../../../types";
+import { configTabs } from "./zwave_js-config-router";
+import { SubscribeMixin } from "../../../../../mixins/subscribe-mixin";
+import type {
+ NetworkData,
+ NetworkLink,
+ NetworkNode,
+} from "../../../../../components/chart/ha-network-graph";
+import "../../../../../components/chart/ha-network-graph";
+import "../../../../../layouts/hass-tabs-subpage";
+import { fetchZwaveNetworkStatus } from "../../../../../data/zwave_js";
+import { colorVariables } from "../../../../../resources/theme/color.globals";
+
+@customElement("zwave_js-network-visualization")
+export class ZWaveJSNetworkVisualization extends SubscribeMixin(LitElement) {
+ public hass!: HomeAssistant;
+
+ @property({ attribute: false }) public route!: Route;
+
+ @property({ attribute: "is-wide", type: Boolean }) public isWide = false;
+
+ @property({ type: Boolean }) public narrow = false;
+
+ @property({ attribute: false }) public configEntryId!: string;
+
+ @state() private _data: NetworkData | null = null;
+
+ protected async firstUpdated() {
+ this._data = await this._getNetworkData();
+ }
+
+ protected render() {
+ if (!this._data) {
+ return nothing;
+ }
+ return html`
+
+
+ `;
+ }
+
+ private _getNetworkData = memoizeOne(async (): Promise => {
+ const nodes: NetworkNode[] = [];
+ const links: NetworkLink[] = [];
+ const categories = [
+ {
+ name: this.hass.localize(
+ "ui.panel.config.zwave_js.visualization.controller"
+ ),
+ symbol: "roundRect",
+ itemStyle: {
+ color: colorVariables["primary-color"],
+ },
+ },
+ {
+ name: this.hass.localize("ui.panel.config.zwave_js.visualization.node"),
+ symbol: "circle",
+ itemStyle: {
+ color: colorVariables["cyan-color"],
+ },
+ },
+ ];
+ const network = await fetchZwaveNetworkStatus(this.hass!, {
+ entry_id: this.configEntryId,
+ });
+ network.controller.nodes.forEach((node) => {
+ nodes.push({
+ id: String(node.node_id),
+ label: String(node.node_id),
+ fixed: node.is_controller_node,
+ polarDistance: node.is_controller_node ? 0 : 0.5,
+ category: node.is_controller_node ? 0 : 1,
+ });
+ });
+
+ return { nodes, links, categories };
+ });
+
+ private _handleChartClick(_e: CustomEvent) {
+ // @TODO
+ }
+
+ static get styles() {
+ return [
+ css`
+ ha-network-graph {
+ height: 100%;
+ }
+ `,
+ ];
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "zwave_js-network-visualization": ZWaveJSNetworkVisualization;
+ }
+}
diff --git a/src/translations/en.json b/src/translations/en.json
index b352f0a58e..915d0fac6a 100644
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -5792,7 +5792,8 @@
"zwave_js": {
"navigation": {
"network": "Network",
- "logs": "Logs"
+ "logs": "Logs",
+ "visualization": "Visualization"
},
"common": {
"network": "Network",
@@ -6246,6 +6247,10 @@
"log_level_changed": "Log Level changed to: {level}",
"download_logs": "Download logs"
},
+ "visualization": {
+ "controller": "Controller",
+ "node": "Node"
+ },
"node_installer": {
"header": "Installer Settings",
"introduction": "Configure your device installer settings.",