diff --git a/src/panels/config/devices/ha-config-devices-dashboard.ts b/src/panels/config/devices/ha-config-devices-dashboard.ts index ce6bc77c9..a78239873 100644 --- a/src/panels/config/devices/ha-config-devices-dashboard.ts +++ b/src/panels/config/devices/ha-config-devices-dashboard.ts @@ -35,6 +35,8 @@ import { EntityRegistryEntry } from "../../../data/entity_registry"; import { ConfigEntry } from "../../../data/config_entries"; import { AreaRegistryEntry } from "../../../data/area_registry"; import { navigate } from "../../../common/navigate"; +import { LocalizeFunc } from "../../../common/translations/localize"; +import computeStateName from "../../../common/entity/compute_state_name"; interface DeviceRowData extends DeviceRegistryEntry { device?: DeviceRowData; @@ -43,6 +45,10 @@ interface DeviceRowData extends DeviceRegistryEntry { battery_entity?: string; } +interface DeviceEntityLookup { + [deviceId: string]: EntityRegistryEntry[]; +} + @customElement("ha-config-devices-dashboard") export class HaConfigDeviceDashboard extends LitElement { @property() public hass!: HomeAssistant; @@ -59,37 +65,73 @@ export class HaConfigDeviceDashboard extends LitElement { entries: ConfigEntry[], entities: EntityRegistryEntry[], areas: AreaRegistryEntry[], - domain: string + domain: string, + localize: LocalizeFunc ) => { + // Some older installations might have devices pointing at invalid entryIDs + // So we guard for that. + let outputDevices: DeviceRowData[] = devices; + + const deviceLookup: { [deviceId: string]: DeviceRegistryEntry } = {}; + for (const device of devices) { + deviceLookup[device.id] = device; + } + + const deviceEntityLookup: DeviceEntityLookup = {}; + for (const entity of entities) { + if (!entity.device_id) { + continue; + } + if (!(entity.device_id in deviceEntityLookup)) { + deviceEntityLookup[entity.device_id] = []; + } + deviceEntityLookup[entity.device_id].push(entity); + } + + const entryLookup: { [entryId: string]: ConfigEntry } = {}; + for (const entry of entries) { + entryLookup[entry.entry_id] = entry; + } + + const areaLookup: { [areaId: string]: AreaRegistryEntry } = {}; + for (const area of areas) { + areaLookup[area.area_id] = area; + } + if (domain) { - outputDevices = outputDevices.filter( - (device) => - entries.find((entry) => - device.config_entries.includes(entry.entry_id) - )!.domain === domain + outputDevices = outputDevices.filter((device) => + device.config_entries.find( + (entryId) => + entryId in entryLookup && entryLookup[entryId].domain === domain + ) ); } outputDevices = outputDevices.map((device) => { - const output = { ...device }; - output.name = device.name_by_user || device.name || "No name"; - - output.area = - !areas || !device || !device.area_id - ? "No area" - : areas.find((area) => area.area_id === device.area_id)!.name; - - output.integration = - !entries || !device || !device.config_entries - ? "No integration" - : entries.find((entry) => - device.config_entries.includes(entry.entry_id) - )!.domain; - - output.battery_entity = this._batteryEntity(device, entities); - - return output; + return { + ...device, + name: + device.name_by_user || + device.name || + this._fallbackDeviceName(device.id, deviceEntityLookup) || + "No name", + model: device.model || "", + manufacturer: device.manufacturer || "", + area: device.area_id ? areaLookup[device.area_id].name : "No area", + integration: device.config_entries.length + ? device.config_entries + .filter((entId) => entId in entryLookup) + .map( + (entId) => + localize( + `component.${entryLookup[entId].domain}.config.title` + ) || entryLookup[entId].domain + ) + .join(", ") + : "No integration", + battery_entity: this._batteryEntity(device.id, deviceEntityLookup), + }; }); return outputDevices; @@ -171,7 +213,7 @@ export class HaConfigDeviceDashboard extends LitElement { > ` : html` - n/a + - `; }, }, @@ -190,7 +232,8 @@ export class HaConfigDeviceDashboard extends LitElement { this.entries, this.entities, this.areas, - this.domain + this.domain, + this.hass.localize ).map((device: DeviceRowData) => { // We don't need a lot of this data for mobile view, but kept it for filtering... const data: DataTabelRowData = { @@ -214,10 +257,12 @@ export class HaConfigDeviceDashboard extends LitElement { `; } - private _batteryEntity(device, entities): string | undefined { - const batteryEntity = entities.find( + private _batteryEntity( + deviceId: string, + deviceEntityLookup: DeviceEntityLookup + ): string | undefined { + const batteryEntity = (deviceEntityLookup[deviceId] || []).find( (entity) => - entity.device_id === device.id && this.hass.states[entity.entity_id] && this.hass.states[entity.entity_id].attributes.device_class === "battery" ); @@ -225,6 +270,20 @@ export class HaConfigDeviceDashboard extends LitElement { return batteryEntity ? batteryEntity.entity_id : undefined; } + private _fallbackDeviceName( + deviceId: string, + deviceEntityLookup: DeviceEntityLookup + ): string | undefined { + for (const entity of deviceEntityLookup[deviceId] || []) { + const stateObj = this.hass.states[entity.entity_id]; + if (stateObj) { + return computeStateName(stateObj); + } + } + + return undefined; + } + private _handleRowClicked(ev: CustomEvent) { const deviceId = (ev.detail as RowClickedEvent).id; navigate(this, `/config/devices/device/${deviceId}`); diff --git a/src/panels/config/devices/ha-config-devices.ts b/src/panels/config/devices/ha-config-devices.ts index 363e83a3e..1e7c696ab 100644 --- a/src/panels/config/devices/ha-config-devices.ts +++ b/src/panels/config/devices/ha-config-devices.ts @@ -34,6 +34,7 @@ class HaConfigDevices extends HassRouterPage { routes: { dashboard: { tag: "ha-config-devices-dashboard", + cache: true, }, device: { tag: "ha-config-device-page", @@ -41,10 +42,10 @@ class HaConfigDevices extends HassRouterPage { }, }; - @property() private _configEntries?: ConfigEntry[]; - @property() private _entityRegistryEntries?: EntityRegistryEntry[]; - @property() private _deviceRegistryEntries?: DeviceRegistryEntry[]; - @property() private _areas?: AreaRegistryEntry[]; + @property() private _configEntries: ConfigEntry[] = []; + @property() private _entityRegistryEntries: EntityRegistryEntry[] = []; + @property() private _deviceRegistryEntries: DeviceRegistryEntry[] = []; + @property() private _areas: AreaRegistryEntry[] = []; private _unsubs?: UnsubscribeFunc[]; diff --git a/src/panels/config/integrations/ha-config-entries-dashboard.ts b/src/panels/config/integrations/ha-config-entries-dashboard.ts index 7b3cd7367..5c562375d 100644 --- a/src/panels/config/integrations/ha-config-entries-dashboard.ts +++ b/src/panels/config/integrations/ha-config-entries-dashboard.ts @@ -37,25 +37,24 @@ import { HomeAssistant } from "../../../types"; import { ConfigEntry } from "../../../data/config_entries"; import { fireEvent } from "../../../common/dom/fire_event"; import { EntityRegistryEntry } from "../../../data/entity_registry"; +import { DataEntryFlowProgress } from "../../../data/data_entry_flow"; @customElement("ha-config-entries-dashboard") export class HaConfigManagerDashboard extends LitElement { @property() public hass!: HomeAssistant; - @property() public isWide = false; - - @property() private entries = []; + @property() private configEntries!: ConfigEntry[]; /** * Entity Registry entries. */ - @property() private entities: EntityRegistryEntry[] = []; + @property() private entityRegistryEntries!: EntityRegistryEntry[]; /** * Current flows that are in progress and have not been started by a user. * For example, can be discovered devices that require more config. */ - @property() private progress = []; + @property() private configEntriesInProgress!: DataEntryFlowProgress[]; public connectedCallback() { super.connectedCallback(); @@ -67,7 +66,7 @@ export class HaConfigManagerDashboard extends LitElement { - ${this.progress.length + ${this.configEntriesInProgress.length ? html` - ${this.progress.map( + ${this.configEntriesInProgress.map( (flow) => html`
@@ -102,8 +101,8 @@ export class HaConfigManagerDashboard extends LitElement { )} - ${this.entities.length - ? this.entries.map( + ${this.entityRegistryEntries.length + ? this.configEntries.map( (item: any, idx) => html` `; @@ -175,11 +173,11 @@ export class HaConfigManagerDashboard extends LitElement { } private _getEntities(configEntry: ConfigEntry): HassEntity[] { - if (!this.entities) { + if (!this.entityRegistryEntries) { return []; } const states: HassEntity[] = []; - this.entities.forEach((entity) => { + this.entityRegistryEntries.forEach((entity) => { if ( entity.config_entry_id === configEntry.entry_id && entity.entity_id in this.hass.states @@ -217,21 +215,10 @@ export class HaConfigManagerDashboard extends LitElement { z-index: 1; } - ha-fab[is-wide] { - bottom: 24px; - right: 24px; - } - ha-fab[rtl] { right: auto; left: 16px; } - - ha-fab[rtl][is-wide] { - bottom: 24px; - right: auto; - left: 24px; - } `; } } diff --git a/src/panels/config/integrations/ha-config-integrations.ts b/src/panels/config/integrations/ha-config-integrations.ts index 39a240bbb..c471288ca 100644 --- a/src/panels/config/integrations/ha-config-integrations.ts +++ b/src/panels/config/integrations/ha-config-integrations.ts @@ -1,4 +1,5 @@ import "@polymer/app-route/app-route"; +import { property, customElement, PropertyValues } from "lit-element"; import "./ha-config-entries-dashboard"; import "./config-entry/ha-config-entry-page"; @@ -11,7 +12,6 @@ import { HassRouterPage, RouterOptions, } from "../../../layouts/hass-router-page"; -import { property, customElement, PropertyValues } from "lit-element"; import { HomeAssistant } from "../../../types"; import { ConfigEntry, getConfigEntries } from "../../../data/config_entries"; import { @@ -42,7 +42,6 @@ class HaConfigIntegrations extends HassRouterPage { protected routerOptions: RouterOptions = { defaultPage: "dashboard", - preloadAll: true, routes: { dashboard: { tag: "ha-config-entries-dashboard", @@ -53,11 +52,11 @@ class HaConfigIntegrations extends HassRouterPage { }, }; - @property() private _configEntries?: ConfigEntry[]; - @property() private _configEntriesInProgress?: DataEntryFlowProgress[]; - @property() private _entityRegistryEntries?: EntityRegistryEntry[]; - @property() private _deviceRegistryEntries?: DeviceRegistryEntry[]; - @property() private _areas?: AreaRegistryEntry[]; + @property() private _configEntries: ConfigEntry[] = []; + @property() private _configEntriesInProgress: DataEntryFlowProgress[] = []; + @property() private _entityRegistryEntries: EntityRegistryEntry[] = []; + @property() private _deviceRegistryEntries: DeviceRegistryEntry[] = []; + @property() private _areas: AreaRegistryEntry[] = []; private _unsubs?: UnsubscribeFunc[]; @@ -98,15 +97,14 @@ class HaConfigIntegrations extends HassRouterPage { protected updatePageEl(pageEl) { pageEl.hass = this.hass; + pageEl.entityRegistryEntries = this._entityRegistryEntries; + pageEl.configEntries = this._configEntries; + if (this._currentPage === "dashboard") { - pageEl.entities = this._entityRegistryEntries; - pageEl.entries = this._configEntries; - pageEl.progress = this._configEntriesInProgress; + pageEl.configEntriesInProgress = this._configEntriesInProgress; return; } - pageEl.entityRegistryEntries = this._entityRegistryEntries; - pageEl.configEntries = this._configEntries; pageEl.configEntryId = this.routeTail.path.substr(1); pageEl.deviceRegistryEntries = this._deviceRegistryEntries; pageEl.areas = this._areas;