mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-21 00:06:35 +00:00
Update entity naming in entities config page (#24966)
Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com>
This commit is contained in:
parent
e3122e8e4d
commit
a6c9702ab2
@ -1,3 +1,4 @@
|
|||||||
|
import memoizeOne from "memoize-one";
|
||||||
import type { DeviceRegistryEntry } from "../../data/device_registry";
|
import type { DeviceRegistryEntry } from "../../data/device_registry";
|
||||||
import type {
|
import type {
|
||||||
EntityRegistryDisplayEntry,
|
EntityRegistryDisplayEntry,
|
||||||
@ -5,6 +6,7 @@ import type {
|
|||||||
} from "../../data/entity_registry";
|
} from "../../data/entity_registry";
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import { computeStateName } from "./compute_state_name";
|
import { computeStateName } from "./compute_state_name";
|
||||||
|
import { getDuplicates } from "../string/get_duplicates";
|
||||||
|
|
||||||
export const computeDeviceName = (
|
export const computeDeviceName = (
|
||||||
device: DeviceRegistryEntry
|
device: DeviceRegistryEntry
|
||||||
@ -36,3 +38,13 @@ export const fallbackDeviceName = (
|
|||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getDuplicatedDeviceNames = memoizeOne(
|
||||||
|
(devices: HomeAssistant["devices"]): Set<string> => {
|
||||||
|
const names = Object.values(devices)
|
||||||
|
.map((device) => computeDeviceName(device))
|
||||||
|
.filter((name): name is string => name !== undefined);
|
||||||
|
|
||||||
|
return getDuplicates(names);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
14
src/common/string/get_duplicates.ts
Normal file
14
src/common/string/get_duplicates.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
export function getDuplicates(array: string[]): Set<string> {
|
||||||
|
const duplicates = new Set<string>();
|
||||||
|
const seen = new Set<string>();
|
||||||
|
|
||||||
|
for (const item of array) {
|
||||||
|
if (seen.has(item)) {
|
||||||
|
duplicates.add(item);
|
||||||
|
} else {
|
||||||
|
seen.add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return duplicates;
|
||||||
|
}
|
@ -497,7 +497,7 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
|
|||||||
: "",
|
: "",
|
||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
title: localize("ui.panel.config.devices.data_table.name"),
|
title: localize("ui.panel.config.devices.data_table.device"),
|
||||||
main: true,
|
main: true,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
|
@ -25,15 +25,18 @@ import { computeCssColor } from "../../../common/color/compute-color";
|
|||||||
import { formatShortDateTimeWithConditionalYear } from "../../../common/datetime/format_date_time";
|
import { formatShortDateTimeWithConditionalYear } from "../../../common/datetime/format_date_time";
|
||||||
import { storage } from "../../../common/decorators/storage";
|
import { storage } from "../../../common/decorators/storage";
|
||||||
import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
import { computeAreaName } from "../../../common/entity/compute_area_name";
|
||||||
import {
|
import {
|
||||||
isDeletableEntity,
|
computeDeviceName,
|
||||||
deleteEntity,
|
getDuplicatedDeviceNames,
|
||||||
} from "../../../common/entity/delete_entity";
|
} from "../../../common/entity/compute_device_name";
|
||||||
import type { Helper } from "../helpers/const";
|
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||||
import { isHelperDomain } from "../helpers/const";
|
import { computeEntityEntryName } from "../../../common/entity/compute_entity_name";
|
||||||
import { HELPERS_CRUD } from "../../../data/helpers_crud";
|
|
||||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||||
|
import {
|
||||||
|
deleteEntity,
|
||||||
|
isDeletableEntity,
|
||||||
|
} from "../../../common/entity/delete_entity";
|
||||||
import {
|
import {
|
||||||
PROTOCOL_INTEGRATIONS,
|
PROTOCOL_INTEGRATIONS,
|
||||||
protocolIntegrationPicked,
|
protocolIntegrationPicked,
|
||||||
@ -53,7 +56,6 @@ import "../../../components/data-table/ha-data-table-labels";
|
|||||||
import "../../../components/ha-alert";
|
import "../../../components/ha-alert";
|
||||||
import "../../../components/ha-button-menu";
|
import "../../../components/ha-button-menu";
|
||||||
import "../../../components/ha-check-list-item";
|
import "../../../components/ha-check-list-item";
|
||||||
import "../../../components/ha-md-divider";
|
|
||||||
import "../../../components/ha-filter-devices";
|
import "../../../components/ha-filter-devices";
|
||||||
import "../../../components/ha-filter-domains";
|
import "../../../components/ha-filter-domains";
|
||||||
import "../../../components/ha-filter-floor-areas";
|
import "../../../components/ha-filter-floor-areas";
|
||||||
@ -62,6 +64,7 @@ import "../../../components/ha-filter-labels";
|
|||||||
import "../../../components/ha-filter-states";
|
import "../../../components/ha-filter-states";
|
||||||
import "../../../components/ha-icon";
|
import "../../../components/ha-icon";
|
||||||
import "../../../components/ha-icon-button";
|
import "../../../components/ha-icon-button";
|
||||||
|
import "../../../components/ha-md-divider";
|
||||||
import "../../../components/ha-md-menu-item";
|
import "../../../components/ha-md-menu-item";
|
||||||
import "../../../components/ha-sub-menu";
|
import "../../../components/ha-sub-menu";
|
||||||
import "../../../components/ha-svg-icon";
|
import "../../../components/ha-svg-icon";
|
||||||
@ -78,17 +81,15 @@ import type {
|
|||||||
EntityRegistryEntry,
|
EntityRegistryEntry,
|
||||||
UpdateEntityRegistryEntryResult,
|
UpdateEntityRegistryEntryResult,
|
||||||
} from "../../../data/entity_registry";
|
} from "../../../data/entity_registry";
|
||||||
import {
|
import { updateEntityRegistryEntry } from "../../../data/entity_registry";
|
||||||
computeEntityRegistryName,
|
|
||||||
updateEntityRegistryEntry,
|
|
||||||
} from "../../../data/entity_registry";
|
|
||||||
import type { IntegrationManifest } from "../../../data/integration";
|
|
||||||
import {
|
|
||||||
fetchIntegrationManifests,
|
|
||||||
domainToName,
|
|
||||||
} from "../../../data/integration";
|
|
||||||
import type { EntitySources } from "../../../data/entity_sources";
|
import type { EntitySources } from "../../../data/entity_sources";
|
||||||
import { fetchEntitySourcesWithCache } from "../../../data/entity_sources";
|
import { fetchEntitySourcesWithCache } from "../../../data/entity_sources";
|
||||||
|
import { HELPERS_CRUD } from "../../../data/helpers_crud";
|
||||||
|
import type { IntegrationManifest } from "../../../data/integration";
|
||||||
|
import {
|
||||||
|
domainToName,
|
||||||
|
fetchIntegrationManifests,
|
||||||
|
} from "../../../data/integration";
|
||||||
import type { LabelRegistryEntry } from "../../../data/label_registry";
|
import type { LabelRegistryEntry } from "../../../data/label_registry";
|
||||||
import {
|
import {
|
||||||
createLabelRegistryEntry,
|
createLabelRegistryEntry,
|
||||||
@ -106,6 +107,8 @@ import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
|||||||
import { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
import type { HomeAssistant, Route } from "../../../types";
|
import type { HomeAssistant, Route } from "../../../types";
|
||||||
import { configSections } from "../ha-panel-config";
|
import { configSections } from "../ha-panel-config";
|
||||||
|
import type { Helper } from "../helpers/const";
|
||||||
|
import { isHelperDomain } from "../helpers/const";
|
||||||
import "../integrations/ha-integration-overflow-menu";
|
import "../integrations/ha-integration-overflow-menu";
|
||||||
import { showAddIntegrationDialog } from "../integrations/show-add-integration-dialog";
|
import { showAddIntegrationDialog } from "../integrations/show-add-integration-dialog";
|
||||||
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
|
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
|
||||||
@ -124,6 +127,8 @@ export interface EntityRow extends StateEntity {
|
|||||||
restored: boolean;
|
restored: boolean;
|
||||||
status: string | undefined;
|
status: string | undefined;
|
||||||
area?: string;
|
area?: string;
|
||||||
|
device?: string;
|
||||||
|
device_full?: string;
|
||||||
localized_platform: string;
|
localized_platform: string;
|
||||||
domain: string;
|
domain: string;
|
||||||
label_entries: LabelRegistryEntry[];
|
label_entries: LabelRegistryEntry[];
|
||||||
@ -304,11 +309,10 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
|||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
main: true,
|
main: true,
|
||||||
title: localize("ui.panel.config.entities.picker.headers.name"),
|
title: localize("ui.panel.config.entities.picker.headers.entity"),
|
||||||
sortable: true,
|
sortable: true,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
direction: "asc",
|
direction: "asc",
|
||||||
flex: 2,
|
|
||||||
extraTemplate: (entry) =>
|
extraTemplate: (entry) =>
|
||||||
entry.label_entries.length
|
entry.label_entries.length
|
||||||
? html`
|
? html`
|
||||||
@ -318,10 +322,29 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
|||||||
`
|
`
|
||||||
: nothing,
|
: nothing,
|
||||||
},
|
},
|
||||||
|
device: {
|
||||||
|
title: localize("ui.panel.config.entities.picker.headers.device"),
|
||||||
|
sortable: true,
|
||||||
|
template: (entry) => entry.device || "—",
|
||||||
|
},
|
||||||
|
device_full: {
|
||||||
|
title: localize("ui.panel.config.entities.picker.headers.device"),
|
||||||
|
filterable: true,
|
||||||
|
groupable: true,
|
||||||
|
hidden: true,
|
||||||
|
},
|
||||||
|
area: {
|
||||||
|
title: localize("ui.panel.config.entities.picker.headers.area"),
|
||||||
|
sortable: true,
|
||||||
|
filterable: true,
|
||||||
|
groupable: true,
|
||||||
|
template: (entry) => entry.area || "—",
|
||||||
|
},
|
||||||
entity_id: {
|
entity_id: {
|
||||||
title: localize("ui.panel.config.entities.picker.headers.entity_id"),
|
title: localize("ui.panel.config.entities.picker.headers.entity_id"),
|
||||||
sortable: true,
|
sortable: true,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
|
defaultHidden: true,
|
||||||
},
|
},
|
||||||
localized_platform: {
|
localized_platform: {
|
||||||
title: localize("ui.panel.config.entities.picker.headers.integration"),
|
title: localize("ui.panel.config.entities.picker.headers.integration"),
|
||||||
@ -336,12 +359,6 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
|||||||
filterable: true,
|
filterable: true,
|
||||||
groupable: true,
|
groupable: true,
|
||||||
},
|
},
|
||||||
area: {
|
|
||||||
title: localize("ui.panel.config.entities.picker.headers.area"),
|
|
||||||
sortable: true,
|
|
||||||
filterable: true,
|
|
||||||
groupable: true,
|
|
||||||
},
|
|
||||||
disabled_by: {
|
disabled_by: {
|
||||||
title: localize("ui.panel.config.entities.picker.headers.disabled_by"),
|
title: localize("ui.panel.config.entities.picker.headers.disabled_by"),
|
||||||
hidden: true,
|
hidden: true,
|
||||||
@ -620,12 +637,16 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const duplicatedDevicesNames = getDuplicatedDeviceNames(devices);
|
||||||
|
|
||||||
for (const entry of filteredEntities) {
|
for (const entry of filteredEntities) {
|
||||||
const entity = this.hass.states[entry.entity_id];
|
const entity = this.hass.states[entry.entity_id];
|
||||||
const unavailable = entity?.state === UNAVAILABLE;
|
const unavailable = entity?.state === UNAVAILABLE;
|
||||||
const restored = entity?.attributes.restored === true;
|
const restored = entity?.attributes.restored === true;
|
||||||
const areaId = entry.area_id ?? devices[entry.device_id!]?.area_id;
|
const deviceId = entry.device_id;
|
||||||
|
const areaId = entry.area_id || devices[deviceId!]?.area_id;
|
||||||
const area = areaId ? areas[areaId] : undefined;
|
const area = areaId ? areas[areaId] : undefined;
|
||||||
|
const device = deviceId ? devices[deviceId] : undefined;
|
||||||
const hidden = !!entry.hidden_by;
|
const hidden = !!entry.hidden_by;
|
||||||
const disabled = !!entry.disabled_by;
|
const disabled = !!entry.disabled_by;
|
||||||
const readonly = entry.readonly;
|
const readonly = entry.readonly;
|
||||||
@ -651,17 +672,30 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
|||||||
(lbl) => labelReg!.find((label) => label.label_id === lbl)!
|
(lbl) => labelReg!.find((label) => label.label_id === lbl)!
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const entityName = computeEntityEntryName(
|
||||||
|
entry as EntityRegistryEntry,
|
||||||
|
this.hass
|
||||||
|
);
|
||||||
|
|
||||||
|
const deviceName = device ? computeDeviceName(device) : undefined;
|
||||||
|
const areaName = area ? computeAreaName(area) : undefined;
|
||||||
|
|
||||||
|
const deviceFullName = deviceName
|
||||||
|
? duplicatedDevicesNames.has(deviceName) && areaName
|
||||||
|
? `${deviceName} (${areaName})`
|
||||||
|
: deviceName
|
||||||
|
: undefined;
|
||||||
|
|
||||||
result.push({
|
result.push({
|
||||||
...entry,
|
...entry,
|
||||||
entity,
|
entity,
|
||||||
name: computeEntityRegistryName(
|
name: entityName || deviceName || entry.entity_id,
|
||||||
this.hass!,
|
device: deviceName,
|
||||||
entry as EntityRegistryEntry
|
area: areaName,
|
||||||
),
|
device_full: deviceFullName,
|
||||||
unavailable,
|
unavailable,
|
||||||
restored,
|
restored,
|
||||||
localized_platform: domainToName(localize, entry.platform),
|
localized_platform: domainToName(localize, entry.platform),
|
||||||
area: area ? area.name : "—",
|
|
||||||
domain: domainToName(localize, computeDomain(entry.entity_id)),
|
domain: domainToName(localize, computeDomain(entry.entity_id)),
|
||||||
status: restored
|
status: restored
|
||||||
? localize("ui.panel.config.entities.picker.status.not_provided")
|
? localize("ui.panel.config.entities.picker.status.not_provided")
|
||||||
@ -792,7 +826,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
selectable
|
selectable
|
||||||
.selected=${this._selected.length}
|
.selected=${this._selected.length}
|
||||||
.initialGroupColumn=${this._activeGrouping}
|
.initialGroupColumn=${this._activeGrouping ?? "device_full"}
|
||||||
.initialCollapsedGroups=${this._activeCollapsed}
|
.initialCollapsedGroups=${this._activeCollapsed}
|
||||||
.initialSorting=${this._activeSorting}
|
.initialSorting=${this._activeSorting}
|
||||||
.columnOrder=${this._activeColumnOrder}
|
.columnOrder=${this._activeColumnOrder}
|
||||||
|
@ -4970,7 +4970,7 @@
|
|||||||
"disabled": "Disabled",
|
"disabled": "Disabled",
|
||||||
"data_table": {
|
"data_table": {
|
||||||
"icon": "Icon",
|
"icon": "Icon",
|
||||||
"name": "Name",
|
"device": "Device",
|
||||||
"manufacturer": "Manufacturer",
|
"manufacturer": "Manufacturer",
|
||||||
"model": "Model",
|
"model": "Model",
|
||||||
"area": "Area",
|
"area": "Area",
|
||||||
@ -5017,8 +5017,9 @@
|
|||||||
},
|
},
|
||||||
"headers": {
|
"headers": {
|
||||||
"state_icon": "State icon",
|
"state_icon": "State icon",
|
||||||
"name": "Name",
|
"entity": "Entity",
|
||||||
"entity_id": "Entity ID",
|
"entity_id": "Entity ID",
|
||||||
|
"device": "Device",
|
||||||
"integration": "Integration",
|
"integration": "Integration",
|
||||||
"area": "Area",
|
"area": "Area",
|
||||||
"disabled_by": "Disabled by",
|
"disabled_by": "Disabled by",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user