diff --git a/src/panels/config/integrations/integration-panels/bluetooth/bluetooth-advertisement-monitor.ts b/src/panels/config/integrations/integration-panels/bluetooth/bluetooth-advertisement-monitor.ts index 07f7f98a1c..361eaced72 100644 --- a/src/panels/config/integrations/integration-panels/bluetooth/bluetooth-advertisement-monitor.ts +++ b/src/panels/config/integrations/integration-panels/bluetooth/bluetooth-advertisement-monitor.ts @@ -1,8 +1,9 @@ +import type { UnsubscribeFunc } from "home-assistant-js-websocket"; import type { CSSResultGroup, TemplateResult } from "lit"; import { html, LitElement } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; -import type { UnsubscribeFunc } from "home-assistant-js-websocket"; +import { storage } from "../../../../../common/decorators/storage"; import type { HASSDomEvent } from "../../../../../common/dom/fire_event"; import type { LocalizeFunc } from "../../../../../common/translations/localize"; import type { @@ -11,9 +12,6 @@ import type { } from "../../../../../components/data-table/ha-data-table"; import "../../../../../components/ha-fab"; import "../../../../../components/ha-icon-button"; -import "../../../../../layouts/hass-tabs-subpage-data-table"; -import { haStyle } from "../../../../../resources/styles"; -import type { HomeAssistant, Route } from "../../../../../types"; import type { BluetoothDeviceData, BluetoothScannersDetails, @@ -22,6 +20,10 @@ import { subscribeBluetoothAdvertisements, subscribeBluetoothScannersDetails, } from "../../../../../data/bluetooth"; +import type { DeviceRegistryEntry } from "../../../../../data/device_registry"; +import "../../../../../layouts/hass-tabs-subpage-data-table"; +import { haStyle } from "../../../../../resources/styles"; +import type { HomeAssistant, Route } from "../../../../../types"; import { showBluetoothDeviceInfoDialog } from "./show-dialog-bluetooth-device-info"; @customElement("bluetooth-advertisement-monitor") @@ -38,6 +40,22 @@ export class BluetoothAdvertisementMonitorPanel extends LitElement { @state() private _scanners: BluetoothScannersDetails = {}; + @state() private _sourceDevices: Record = {}; + + @storage({ + key: "bluetooth-advertisement-table-grouping", + state: false, + subscribe: false, + }) + private _activeGrouping?: string = "source"; + + @storage({ + key: "bluetooth-advertisement-table-collapsed", + state: false, + subscribe: false, + }) + private _activeCollapsed: string[] = []; + private _unsub_advertisements?: UnsubscribeFunc; private _unsub_scanners?: UnsubscribeFunc; @@ -57,6 +75,19 @@ export class BluetoothAdvertisementMonitorPanel extends LitElement { this._scanners = scanners; } ); + + const devices = Object.values(this.hass.devices); + const bluetoothDevices = devices.filter((device) => + device.connections.find((connection) => connection[0] === "bluetooth") + ); + this._sourceDevices = Object.fromEntries( + bluetoothDevices.map((device) => { + const connection = device.connections.find( + (c) => c[0] === "bluetooth" + )!; + return [connection[1], device]; + }) + ); } } @@ -91,14 +122,28 @@ export class BluetoothAdvertisementMonitorPanel extends LitElement { filterable: true, sortable: true, }, + device: { + title: localize("ui.panel.config.bluetooth.device"), + filterable: true, + sortable: true, + template: (data) => html`${data.device || "-"}`, + }, source: { title: localize("ui.panel.config.bluetooth.source"), filterable: true, sortable: true, + groupable: true, + }, + source_address: { + title: localize("ui.panel.config.bluetooth.source_address"), + filterable: true, + sortable: true, + defaultHidden: true, }, rssi: { title: localize("ui.panel.config.bluetooth.rssi"), type: "numeric", + maxWidth: "60px", sortable: true, }, }; @@ -108,11 +153,22 @@ export class BluetoothAdvertisementMonitorPanel extends LitElement { ); private _dataWithNamedSourceAndIds = memoizeOne((data) => - data.map((row) => ({ - ...row, - id: row.address, - source: this._scanners[row.source]?.name || row.source, - })) + data.map((row) => { + const device = this._sourceDevices[row.address]; + const scannerDevice = this._sourceDevices[row.source]; + const scanner = this._scanners[row.source]; + return { + ...row, + id: row.address, + source_address: row.source, + source: + scannerDevice?.name_by_user || + scannerDevice?.name || + scanner?.name || + row.source, + device: device?.name_by_user || device?.name || undefined, + }; + }) ); protected render(): TemplateResult { @@ -124,11 +180,23 @@ export class BluetoothAdvertisementMonitorPanel extends LitElement { .columns=${this._columns(this.hass.localize)} .data=${this._dataWithNamedSourceAndIds(this._data)} @row-click=${this._handleRowClicked} + .initialGroupColumn=${this._activeGrouping} + .initialCollapsedGroups=${this._activeCollapsed} + @grouping-changed=${this._handleGroupingChanged} + @collapsed-changed=${this._handleCollapseChanged} clickable > `; } + private _handleGroupingChanged(ev: CustomEvent) { + this._activeGrouping = ev.detail.value; + } + + private _handleCollapseChanged(ev: CustomEvent) { + this._activeCollapsed = ev.detail.value; + } + private _handleRowClicked(ev: HASSDomEvent) { const entry = this._data.find((ent) => ent.address === ev.detail.id); showBluetoothDeviceInfoDialog(this, { diff --git a/src/panels/config/integrations/integration-panels/bluetooth/dialog-bluetooth-device-info.ts b/src/panels/config/integrations/integration-panels/bluetooth/dialog-bluetooth-device-info.ts index 2aeb28cb77..635da34b03 100644 --- a/src/panels/config/integrations/integration-panels/bluetooth/dialog-bluetooth-device-info.ts +++ b/src/panels/config/integrations/integration-panels/bluetooth/dialog-bluetooth-device-info.ts @@ -53,8 +53,6 @@ class DialogBluetoothDeviceInfo extends LitElement implements HassDialog { return html`