feat(dashboard): show client-mode clusters on endpoint view - #952
Conversation
Endpoint cluster lists were derived purely from cached attribute keys, so client-mode clusters (bound to a remote device, no local attribute storage) never appeared even though the Descriptor's ClientList carries them. Merge ClientList cluster ids into the endpoint listing with a CLIENT badge, and note in the cluster detail view why a client-mode cluster has no attributes to show.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
@lboue Hm, honestly I do not really see the benefit of having the Client clusters also in the Cluster list, You can not click on then and basically nothing happens with them. |
Per review feedback on the endpoint view: client-mode clusters were unclickable dead entries in the Clusters list. Show them instead as chips in a panel near the top, only when the endpoint has a Binding cluster, and mark clusters already covered by an existing binding as "Bound".
…-clusters # Conflicts: # CHANGELOG.md
|
@Apollon77 Good points, thanks. Reworked in fd7ce21:
Verified manually against the same Thermostat Controller endpoint ( |
…er chip --md-sys-color-tertiary-container is never defined in public/index.html (light or dark), so the "Bound" chip rendered with no background/text color at all. Switch to --md-sys-color-primary-container, which is defined for both themes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
npm run format now wraps these template expressions differently since the formatting rules changed upstream (merged from main); re-running the required format step touched these files with no logic change. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Local node_modules had a stale oxfmt 0.59.0 binary that formatted differently from the ^0.61.0 pinned in package.json, so CI's format-verify (installing fresh from the lockfile) flagged 31 files that passed locally. Reinstalling and reformatting syncs the tree with the pinned version. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Explicit attribute reads are not fabric-filtered, so the cached binding attribute can hold other fabrics' entries. boundClientClusterIds now filters on the node's CurrentFabricIndex, mirroring freshOurBindings and the ACL helpers; without it a second ecosystem's binding — or its cluster-less entry — marked client clusters Bound as soon as the binding editor's read landed. A cluster-less entry now unions with the cluster-specific ones instead of replacing them, and an entry targeting neither Group nor Endpoint no longer expands to every client cluster. Drops the client-only notice from the cluster view: nothing links to a client-mode cluster page, so it was unreachable except by hand-typed URL. Extracts the duplicated info-panel/chip CSS into shared-styles, adds the light-theme --md-sys-color-on-secondary-container the chips rely on, widens the endpoint view's guard dependencies so the cluster list tracks the viewed endpoint, and states Bound/Not bound as text rather than colour alone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds dashboard visibility for client-mode clusters (Descriptor ClientList) on the endpoint page, and refactors shared “info panel” styling so both endpoint/cluster views can reuse it. This improves UX for controller-style endpoints that only consume clusters via bindings (and therefore have no local attribute cache for those clusters).
Changes:
- Add
boundClientClusterIds()to compute which client clusters are already covered by Binding entries (with new unit tests). - Add reusable
infoPanelStylesand apply it to cluster/endpoint views. - Update endpoint view rendering/guarding and add a “Client Clusters” chip panel (plus related theme variable and changelog entry).
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/dashboard/test/BindingUtilTest.ts | Adds unit tests for boundClientClusterIds() behavior across fabric filtering and wildcard bindings. |
| packages/dashboard/src/util/shared-styles.ts | Introduces reusable infoPanelStyles for bordered info panels and chips. |
| packages/dashboard/src/util/binding.ts | Adds BINDING_CLUSTER_ID constant and boundClientClusterIds() helper; refactors list parsing. |
| packages/dashboard/src/pages/matter-endpoint-view.ts | Adds “Client Clusters” panel and refactors cluster list rendering/guarding; uses shared panel styles. |
| packages/dashboard/src/pages/matter-cluster-view.ts | Switches inline panel/chip CSS to shared infoPanelStyles. |
| packages/dashboard/src/pages/cluster-commands/clusters/binding-commands.ts | Uses BINDING_CLUSTER_ID for consistency and tweaks doc comment. |
| packages/dashboard/public/index.html | Adds missing theme token --md-sys-color-on-secondary-container for chip text color in light theme. |
| CHANGELOG.md | Documents the new endpoint “Client Clusters” panel and a claimed cluster list refresh fix. |
| export function boundClientClusterIds(node: MatterNode, endpoint: number): Set<number> { | ||
| const fabricIndex = nodeFabricIndex(node); | ||
| const bindings = readBindings(node, endpoint).filter( | ||
| b => fabricIndex === undefined || b.fabricIndex === fabricIndex, | ||
| ); | ||
| const bound = new Set(bindings.map(b => b.cluster).filter((c): c is number => c !== undefined)); | ||
| if (bindings.some(b => b.cluster === undefined && (b.group !== undefined || b.endpoint !== undefined))) { | ||
| for (const cluster of sourceClientClusters(node, endpoint)) bound.add(cluster); | ||
| } | ||
| return bound; | ||
| } |
| export const BINDING_CLUSTER_ID = 30; | ||
|
|
||
| const BINDING_KEY_RE = /^(\d+)\/30\/0$/; | ||
|
|
| ${guard( | ||
| [nodeId, this.endpoint, this.node.attributes, Object.keys(this.node.attributes).length], | ||
| () => | ||
| endpointClusters.map(cluster => { | ||
| return html` | ||
| <md-list-item type="link" href=${`#node/${nodeId}/${this.endpoint}/${cluster}`}> | ||
| <div slot="headline"> | ||
| ${clusters[cluster]?.label ?? "Custom/Unknown Cluster"} | ||
| </div> | ||
| <div slot="supporting-text">ClusterId ${cluster} (${formatHex(cluster)})</div> | ||
| <ha-svg-icon slot="end" .path=${mdiChevronRight}></ha-svg-icon> | ||
| </md-list-item> | ||
| `; | ||
| }), | ||
| )} |
| - Enhancement: Introduces Websocket Schema version 13 (backward compatible) | ||
| - (MindFreeze) Adds Websocket command `get_network_topology` and event `network_topology_updated` to expose the Thread and WiFi network details for external visualization | ||
| - Feature: (lboue) Dashboard endpoint view shows a "Client Clusters" panel listing the Descriptor's ClientList clusters when the endpoint has a Binding cluster, marking each as "Bound" or "Not bound" | ||
| - Fix: Dashboard endpoint view refreshes its cluster list when a cluster appears or disappears on the node, or the viewed endpoint changes |
Type of change
Description
The endpoint cluster list in the dashboard was derived purely from cached attribute keys
(
node.attributes), so client-mode clusters (an endpoint binds to a cluster hosted on anotherdevice rather than hosting it itself) never appeared, even though the Descriptor's
ClientListattribute records them. Client clusters have no local attribute storage, so they were silently
invisible with no explicit filter excluding them — it was an emergent effect of the data
collection method.
Update after review: the first version of this PR merged
ClientListcluster ids directlyinto the Clusters list with a
CLIENTbadge, but as @Apollon77 pointed out,those entries weren't clickable and did nothing when tapped. Reworked per that feedback:
matter-endpoint-view.tsshows a "Client Clusters" chip panel near the top of thepage — but only when the endpoint also has a Binding cluster, since without one there's
nothing to do with a client cluster anyway.
util/binding.ts's newboundClientClusterIds, cross-referencing the Binding cluster'sbindingattribute againstthe Descriptor's
ClientList) are visually marked "Bound" vs. the rest.matter-cluster-view.ts) still shows a short notice explaining why aclient-mode cluster's attribute list is empty, for anyone navigating there directly.
Backing evidence
Not applicable — this is a feature addition, not a fix for a defect.
Checklist
npm testpassesnpm run format-verifyandnpm run lintpassManually verified against a running server: endpoint 38 (Thermostat Controller) has
ServerList = [29, 57, 30]andClientList = [513](Thermostat). After this change, theClusters list no longer shows the Thermostat client cluster; instead, because the endpoint has
the Binding cluster (30), a "Client Clusters" panel appears showing the Thermostat cluster as a
chip, marked "Bound" once a binding entry targets it.
No new automated tests were added for this UI change; verification was manual (see above) plus
the existing dashboard test suite (153/153 passing).
🤖 Generated with Claude Code