mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 09:46:36 +00:00
Show yaml setup integrations in the UI (#21447)
* Show yaml setup integrations in the UI * Update en.json * Move config entry logic to memoize function
This commit is contained in:
parent
bbb64870a1
commit
d96ddf968c
@ -6,12 +6,12 @@ import {
|
||||
mdiSofa,
|
||||
} from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
nothing,
|
||||
PropertyValues,
|
||||
css,
|
||||
html,
|
||||
nothing,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
@ -20,7 +20,7 @@ import { fireEvent } from "../common/dom/fire_event";
|
||||
import { caseInsensitiveStringCompare } from "../common/string/compare";
|
||||
import { Blueprints, fetchBlueprints } from "../data/blueprint";
|
||||
import { ConfigEntry, getConfigEntries } from "../data/config_entries";
|
||||
import { findRelated, ItemType, RelatedResult } from "../data/search";
|
||||
import { ItemType, RelatedResult, findRelated } from "../data/search";
|
||||
import { haStyle } from "../resources/styles";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { brandsUrl } from "../util/brands-url";
|
||||
@ -109,6 +109,26 @@ export class HaRelatedItems extends LitElement {
|
||||
)
|
||||
);
|
||||
|
||||
private _getConfigEntries = memoizeOne(
|
||||
(
|
||||
relatedConfigEntries: string[] | undefined,
|
||||
entries: ConfigEntry[] | undefined
|
||||
) => {
|
||||
const configEntries =
|
||||
relatedConfigEntries && entries
|
||||
? relatedConfigEntries.map((entryId) =>
|
||||
entries!.find((configEntry) => configEntry.entry_id === entryId)
|
||||
)
|
||||
: undefined;
|
||||
|
||||
const configEntryDomains = new Set(
|
||||
configEntries?.map((entry) => entry?.domain)
|
||||
);
|
||||
|
||||
return { configEntries, configEntryDomains };
|
||||
}
|
||||
);
|
||||
|
||||
protected render() {
|
||||
if (!this._related) {
|
||||
return nothing;
|
||||
@ -128,22 +148,25 @@ export class HaRelatedItems extends LitElement {
|
||||
</mwc-list>
|
||||
`;
|
||||
}
|
||||
|
||||
const { configEntries, configEntryDomains } = this._getConfigEntries(
|
||||
this._related.config_entry,
|
||||
this._entries
|
||||
);
|
||||
|
||||
return html`
|
||||
${this._related.config_entry && this._entries
|
||||
${configEntries || this._related.integration
|
||||
? html`<h3>
|
||||
${this.hass.localize("ui.components.related-items.integration")}
|
||||
</h3>
|
||||
<mwc-list
|
||||
>${this._related.config_entry.map((relatedConfigEntryId) => {
|
||||
const entry: ConfigEntry | undefined = this._entries!.find(
|
||||
(configEntry) => configEntry.entry_id === relatedConfigEntryId
|
||||
);
|
||||
>${configEntries?.map((entry) => {
|
||||
if (!entry) {
|
||||
return nothing;
|
||||
}
|
||||
return html`
|
||||
<a
|
||||
href=${`/config/integrations/integration/${entry.domain}#config_entry=${relatedConfigEntryId}`}
|
||||
href=${`/config/integrations/integration/${entry.domain}#config_entry=${entry.entry_id}`}
|
||||
@click=${this._navigateAwayClose}
|
||||
>
|
||||
<ha-list-item hasMeta graphic="icon">
|
||||
@ -164,8 +187,34 @@ export class HaRelatedItems extends LitElement {
|
||||
</ha-list-item>
|
||||
</a>
|
||||
`;
|
||||
})}</mwc-list
|
||||
>`
|
||||
})}
|
||||
${this._related.integration
|
||||
?.filter((integration) => !configEntryDomains.has(integration))
|
||||
.map(
|
||||
(integration) =>
|
||||
html`<a
|
||||
href=${`/config/integrations/integration/${integration}`}
|
||||
@click=${this._navigateAwayClose}
|
||||
>
|
||||
<ha-list-item hasMeta graphic="icon">
|
||||
<img
|
||||
.src=${brandsUrl({
|
||||
domain: integration,
|
||||
type: "icon",
|
||||
useFallback: true,
|
||||
darkOptimized: this.hass.themes?.darkMode,
|
||||
})}
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
alt=${integration}
|
||||
slot="graphic"
|
||||
/>
|
||||
${this.hass.localize(`component.${integration}.title`)}
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
</a>`
|
||||
)}
|
||||
</mwc-list>`
|
||||
: nothing}
|
||||
${this._related.device
|
||||
? html`<h3>
|
||||
|
@ -32,7 +32,11 @@ export const fetchRepairsIssues = (conn: Connection) =>
|
||||
type: "repairs/list_issues",
|
||||
});
|
||||
|
||||
export const fetchRepairsIssueData = (conn: Connection, domain, issue_id) =>
|
||||
export const fetchRepairsIssueData = (
|
||||
conn: Connection,
|
||||
domain: string,
|
||||
issue_id: string
|
||||
) =>
|
||||
conn.sendMessagePromise<{ issue_data: { string: any } }>({
|
||||
type: "repairs/get_issue_data",
|
||||
domain,
|
||||
|
@ -8,6 +8,7 @@ export interface RelatedResult {
|
||||
device?: string[];
|
||||
entity?: string[];
|
||||
group?: string[];
|
||||
integration?: string[];
|
||||
scene?: string[];
|
||||
script?: string[];
|
||||
script_blueprint?: string[];
|
||||
|
@ -507,8 +507,30 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
)
|
||||
.map((entry) => entry.entry_id);
|
||||
|
||||
const filteredEntitiesByDomain = new Set<string>();
|
||||
|
||||
const entitySources = this._entitySources || {};
|
||||
|
||||
const entitiesByDomain = {};
|
||||
|
||||
for (const [entity, source] of Object.entries(entitySources)) {
|
||||
if (!(source.domain in entitiesByDomain)) {
|
||||
entitiesByDomain[source.domain] = [];
|
||||
}
|
||||
entitiesByDomain[source.domain].push(entity);
|
||||
}
|
||||
|
||||
for (const val of filter.value) {
|
||||
if (val in entitiesByDomain) {
|
||||
entitiesByDomain[val].forEach((item) =>
|
||||
filteredEntitiesByDomain.add(item)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
filteredEntities = filteredEntities.filter(
|
||||
(entity) =>
|
||||
filteredEntitiesByDomain.has(entity.entity_id) ||
|
||||
(filter.value as string[]).includes(entity.platform) ||
|
||||
(entity.config_entry_id &&
|
||||
entryIds.includes(entity.config_entry_id))
|
||||
@ -951,6 +973,9 @@ ${
|
||||
}
|
||||
|
||||
protected firstUpdated() {
|
||||
fetchEntitySourcesWithCache(this.hass).then((sources) => {
|
||||
this._entitySources = sources;
|
||||
});
|
||||
this._setFiltersFromUrl();
|
||||
if (Object.keys(this._filters).length) {
|
||||
return;
|
||||
@ -961,9 +986,6 @@ ${
|
||||
items: undefined,
|
||||
},
|
||||
};
|
||||
fetchEntitySourcesWithCache(this.hass).then((sources) => {
|
||||
this._entitySources = sources;
|
||||
});
|
||||
}
|
||||
|
||||
private _setFiltersFromUrl() {
|
||||
|
@ -108,6 +108,7 @@ import { documentationUrl } from "../../../util/documentation-url";
|
||||
import { fileDownload } from "../../../util/file_download";
|
||||
import { DataEntryFlowProgressExtended } from "./ha-config-integrations";
|
||||
import { showAddIntegrationDialog } from "./show-add-integration-dialog";
|
||||
import { fetchEntitySourcesWithCache } from "../../../data/entity_sources";
|
||||
|
||||
@customElement("ha-config-integration-page")
|
||||
class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
@ -140,6 +141,8 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
window.location.hash.substring(1)
|
||||
);
|
||||
|
||||
@state() private _domainEntities: Record<string, string[]> = {};
|
||||
|
||||
private _configPanel = memoizeOne(
|
||||
(domain: string, panels: HomeAssistant["panels"]): string | undefined =>
|
||||
Object.values(panels).find(
|
||||
@ -185,9 +188,25 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
this._extraConfigEntries = undefined;
|
||||
this._fetchManifest();
|
||||
this._fetchDiagnostics();
|
||||
this._fetchEntitySources();
|
||||
}
|
||||
}
|
||||
|
||||
private async _fetchEntitySources() {
|
||||
const entitySources = await fetchEntitySourcesWithCache(this.hass);
|
||||
|
||||
const entitiesByDomain = {};
|
||||
|
||||
for (const [entity, source] of Object.entries(entitySources)) {
|
||||
if (!(source.domain in entitiesByDomain)) {
|
||||
entitiesByDomain[source.domain] = [];
|
||||
}
|
||||
entitiesByDomain[source.domain].push(entity);
|
||||
}
|
||||
|
||||
this._domainEntities = entitiesByDomain;
|
||||
}
|
||||
|
||||
protected updated(changed: PropertyValues) {
|
||||
super.updated(changed);
|
||||
if (
|
||||
@ -245,6 +264,22 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
|
||||
const devices = this._getDevices(configEntries, this.hass.devices);
|
||||
const entities = this._getEntities(configEntries, this._entities);
|
||||
let numberOfEntities = entities.length;
|
||||
|
||||
if (
|
||||
this.domain in this._domainEntities &&
|
||||
numberOfEntities !== this._domainEntities[this.domain].length
|
||||
) {
|
||||
if (!numberOfEntities) {
|
||||
numberOfEntities = this._domainEntities[this.domain].length;
|
||||
} else {
|
||||
const entityIds = new Set(entities.map((entity) => entity.entity_id));
|
||||
for (const entityId of this._domainEntities[this.domain]) {
|
||||
entityIds.add(entityId);
|
||||
}
|
||||
numberOfEntities = entityIds.size;
|
||||
}
|
||||
}
|
||||
|
||||
const services = !devices.some((device) => device.entry_type !== "service");
|
||||
|
||||
@ -320,7 +355,7 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
</ha-list-item>
|
||||
</a>`
|
||||
: ""}
|
||||
${entities.length > 0
|
||||
${numberOfEntities > 0
|
||||
? html`<a
|
||||
href=${`/config/entities?historyBack=1&domain=${this.domain}`}
|
||||
>
|
||||
@ -331,7 +366,7 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.integrations.config_entry.entities`,
|
||||
{ count: entities.length }
|
||||
{ count: numberOfEntities }
|
||||
)}
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
@ -503,9 +538,15 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
</h1>
|
||||
${normalEntries.length === 0
|
||||
? html`<div class="card-content no-entries">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.integration_page.no_entries"
|
||||
)}
|
||||
${this.hass.config.components.find(
|
||||
(comp) => comp.split(".")[0] === this.domain
|
||||
)
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.integrations.integration_page.yaml_entry"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.integrations.integration_page.no_entries"
|
||||
)}
|
||||
</div>`
|
||||
: nothing}
|
||||
<ha-list-new>
|
||||
@ -683,7 +724,7 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
|
||||
const configPanel = this._configPanel(item.domain, this.hass.panels);
|
||||
|
||||
return html` <ha-list-item-new
|
||||
return html`<ha-list-item-new
|
||||
class=${classMap({
|
||||
config_entry: true,
|
||||
"state-not-loaded": item!.state === "not_loaded",
|
||||
@ -1323,6 +1364,17 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
private async _addIntegration() {
|
||||
if (!this._manifest?.config_flow) {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.yaml_only_title"
|
||||
),
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.yaml_only"
|
||||
),
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (this._manifest?.single_config_entry) {
|
||||
const entries = this._domainConfigEntries(
|
||||
this.domain,
|
||||
|
@ -73,8 +73,10 @@ import "./ha-integration-card";
|
||||
import type { HaIntegrationCard } from "./ha-integration-card";
|
||||
import "./ha-integration-overflow-menu";
|
||||
import { showAddIntegrationDialog } from "./show-add-integration-dialog";
|
||||
import { fetchEntitySourcesWithCache } from "../../../data/entity_sources";
|
||||
|
||||
export interface ConfigEntryExtended extends ConfigEntry {
|
||||
export interface ConfigEntryExtended extends Omit<ConfigEntry, "entry_id"> {
|
||||
entry_id?: string;
|
||||
localized_domain_name?: string;
|
||||
}
|
||||
|
||||
@ -114,6 +116,8 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
|
||||
@state()
|
||||
private _manifests: Record<string, IntegrationManifest> = {};
|
||||
|
||||
@state() private _domainEntities: Record<string, string[]> = {};
|
||||
|
||||
private _extraFetchedManifests?: Set<string>;
|
||||
|
||||
@state() private _showIgnored = false;
|
||||
@ -149,13 +153,56 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
|
||||
|
||||
private _filterConfigEntries = memoizeOne(
|
||||
(
|
||||
components: string[],
|
||||
manifests: Record<string, IntegrationManifest>,
|
||||
configEntries: ConfigEntryExtended[],
|
||||
localize: HomeAssistant["localize"],
|
||||
filter?: string
|
||||
): [
|
||||
[string, ConfigEntryExtended[]][],
|
||||
ConfigEntryExtended[],
|
||||
ConfigEntryExtended[],
|
||||
] => {
|
||||
const entryDomains = new Set(configEntries.map((entry) => entry.domain));
|
||||
|
||||
const domains = new Set<string>();
|
||||
|
||||
for (const component of components) {
|
||||
const componentDomain = component.split(".")[0];
|
||||
if (
|
||||
!entryDomains.has(componentDomain) &&
|
||||
manifests[componentDomain] &&
|
||||
(!manifests[componentDomain].integration_type ||
|
||||
["device", "hub", "service", "integration"].includes(
|
||||
manifests[componentDomain].integration_type!
|
||||
))
|
||||
) {
|
||||
domains.add(componentDomain);
|
||||
}
|
||||
}
|
||||
|
||||
const nonConfigEntry: ConfigEntryExtended[] = [...domains].map(
|
||||
(domain) => ({
|
||||
domain,
|
||||
localized_domain_name: domainToName(localize, domain),
|
||||
title: domain,
|
||||
source: "yaml",
|
||||
state: "loaded",
|
||||
supports_options: false,
|
||||
supports_remove_device: false,
|
||||
supports_unload: false,
|
||||
supports_reconfigure: false,
|
||||
pref_disable_new_entities: false,
|
||||
pref_disable_polling: false,
|
||||
disabled_by: null,
|
||||
reason: null,
|
||||
error_reason_translation_key: null,
|
||||
error_reason_translation_placeholders: null,
|
||||
})
|
||||
);
|
||||
|
||||
const allEntries = [...configEntries, ...nonConfigEntry];
|
||||
|
||||
let filteredConfigEntries: ConfigEntryExtended[];
|
||||
const ignored: ConfigEntryExtended[] = [];
|
||||
const disabled: ConfigEntryExtended[] = [];
|
||||
@ -167,12 +214,12 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
|
||||
minMatchCharLength: Math.min(filter.length, 2),
|
||||
threshold: 0.2,
|
||||
};
|
||||
const fuse = new Fuse(configEntries, options);
|
||||
const fuse = new Fuse(allEntries, options);
|
||||
filteredConfigEntries = fuse
|
||||
.search(filter)
|
||||
.map((result) => result.item);
|
||||
} else {
|
||||
filteredConfigEntries = configEntries;
|
||||
filteredConfigEntries = allEntries;
|
||||
}
|
||||
|
||||
for (const entry of filteredConfigEntries) {
|
||||
@ -232,6 +279,7 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
|
||||
protected firstUpdated(changed: PropertyValues) {
|
||||
super.firstUpdated(changed);
|
||||
this._fetchManifests();
|
||||
this._fetchEntitySources();
|
||||
if (this.route.path === "/add") {
|
||||
this._handleAdd();
|
||||
}
|
||||
@ -276,7 +324,13 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
|
||||
></hass-loading-screen>`;
|
||||
}
|
||||
const [integrations, ignoredConfigEntries, disabledConfigEntries] =
|
||||
this._filterConfigEntries(this.configEntries, this._filter);
|
||||
this._filterConfigEntries(
|
||||
this.hass.config.components,
|
||||
this._manifests,
|
||||
this.configEntries,
|
||||
this.hass.localize,
|
||||
this._filter
|
||||
);
|
||||
const configEntriesInProgress = this._filterConfigEntriesInProgress(
|
||||
this.configEntriesInProgress,
|
||||
this._filter
|
||||
@ -463,6 +517,7 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
|
||||
.items=${items}
|
||||
.manifest=${this._manifests[domain]}
|
||||
.entityRegistryEntries=${this._entityRegistryEntries}
|
||||
.domainEntities=${this._domainEntities[domain] || []}
|
||||
.supportsDiagnostics=${this._diagnosticHandlers
|
||||
? this._diagnosticHandlers[domain]
|
||||
: false}
|
||||
@ -552,6 +607,21 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
|
||||
await scanUSBDevices(this.hass);
|
||||
}
|
||||
|
||||
private async _fetchEntitySources() {
|
||||
const entitySources = await fetchEntitySourcesWithCache(this.hass);
|
||||
|
||||
const entitiesByDomain = {};
|
||||
|
||||
for (const [entity, source] of Object.entries(entitySources)) {
|
||||
if (!(source.domain in entitiesByDomain)) {
|
||||
entitiesByDomain[source.domain] = [];
|
||||
}
|
||||
entitiesByDomain[source.domain].push(entity);
|
||||
}
|
||||
|
||||
this._domainEntities = entitiesByDomain;
|
||||
}
|
||||
|
||||
private async _fetchManifests(integrations?: string[]) {
|
||||
const fetched = await fetchIntegrationManifests(this.hass, integrations);
|
||||
// Make a copy so we can keep track of previously loaded manifests
|
||||
|
@ -1,5 +1,5 @@
|
||||
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
|
||||
import { mdiCloud, mdiPackageVariant } from "@mdi/js";
|
||||
import { mdiCloud, mdiCodeBraces, mdiPackageVariant } from "@mdi/js";
|
||||
import {
|
||||
CSSResultGroup,
|
||||
LitElement,
|
||||
@ -46,6 +46,8 @@ export class HaIntegrationCard extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public logInfo?: IntegrationLogInfo;
|
||||
|
||||
@property({ attribute: false }) public domainEntities: string[] = [];
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const entryState = this._getState(this.items);
|
||||
|
||||
@ -100,9 +102,13 @@ export class HaIntegrationCard extends LitElement {
|
||||
|
||||
private _renderSingleEntry(): TemplateResult {
|
||||
const devices = this._getDevices(this.items, this.hass.devices);
|
||||
const entities = devices.length
|
||||
? []
|
||||
: this._getEntities(this.items, this.entityRegistryEntries);
|
||||
const entitiesCount = devices.length
|
||||
? 0
|
||||
: this._getEntityCount(
|
||||
this.items,
|
||||
this.entityRegistryEntries,
|
||||
this.domainEntities
|
||||
);
|
||||
|
||||
const services = !devices.some((device) => device.entry_type !== "service");
|
||||
|
||||
@ -123,25 +129,32 @@ export class HaIntegrationCard extends LitElement {
|
||||
)}
|
||||
</ha-button>
|
||||
</a>`
|
||||
: entities.length > 0
|
||||
: entitiesCount > 0
|
||||
? html`<a
|
||||
href=${`/config/entities?historyBack=1&domain=${this.domain}`}
|
||||
>
|
||||
<ha-button>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.integrations.config_entry.entities`,
|
||||
{ count: entities.length }
|
||||
{ count: entitiesCount }
|
||||
)}
|
||||
</ha-button>
|
||||
</a>`
|
||||
: html`<a href=${`/config/integrations/integration/${this.domain}`}>
|
||||
<ha-button>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.integrations.config_entry.entries`,
|
||||
{ count: this.items.length }
|
||||
)}
|
||||
</ha-button>
|
||||
</a>`}
|
||||
: this.items.find((itm) => itm.source !== "yaml")
|
||||
? html`<a
|
||||
href=${`/config/integrations/integration/${this.domain}`}
|
||||
>
|
||||
<ha-button>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.integrations.config_entry.entries`,
|
||||
{
|
||||
count: this.items.filter((itm) => itm.source !== "yaml")
|
||||
.length,
|
||||
}
|
||||
)}
|
||||
</ha-button>
|
||||
</a>`
|
||||
: html`<div class="spacer"></div>`}
|
||||
<div class="icons">
|
||||
${this.manifest && !this.manifest.is_built_in
|
||||
? html`<span class="icon custom">
|
||||
@ -169,6 +182,19 @@ export class HaIntegrationCard extends LitElement {
|
||||
>
|
||||
</div>`
|
||||
: nothing}
|
||||
${!this.manifest?.config_flow
|
||||
? html`<div class="icon yaml">
|
||||
<ha-svg-icon .path=${mdiCodeBraces}></ha-svg-icon>
|
||||
<simple-tooltip
|
||||
animation-delay="0"
|
||||
.position=${computeRTL(this.hass) ? "right" : "left"}
|
||||
offset="4"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.no_config_flow"
|
||||
)}</simple-tooltip
|
||||
>
|
||||
</div>`
|
||||
: nothing}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@ -190,19 +216,42 @@ export class HaIntegrationCard extends LitElement {
|
||||
}
|
||||
);
|
||||
|
||||
private _getEntities = memoizeOne(
|
||||
private _getEntityCount = memoizeOne(
|
||||
(
|
||||
configEntry: ConfigEntry[],
|
||||
entityRegistryEntries: EntityRegistryEntry[]
|
||||
): EntityRegistryEntry[] => {
|
||||
entityRegistryEntries: EntityRegistryEntry[],
|
||||
domainEntities: string[]
|
||||
): number => {
|
||||
if (!entityRegistryEntries) {
|
||||
return [];
|
||||
return domainEntities.length;
|
||||
}
|
||||
const entryIds = configEntry.map((entry) => entry.entry_id);
|
||||
return entityRegistryEntries.filter(
|
||||
|
||||
const entryIds = configEntry
|
||||
.map((entry) => entry.entry_id)
|
||||
.filter(Boolean);
|
||||
|
||||
if (!entryIds.length) {
|
||||
return domainEntities.length;
|
||||
}
|
||||
|
||||
const entityRegEntities = entityRegistryEntries.filter(
|
||||
(entity) =>
|
||||
entity.config_entry_id && entryIds.includes(entity.config_entry_id)
|
||||
);
|
||||
|
||||
if (entityRegEntities.length === domainEntities.length) {
|
||||
return domainEntities.length;
|
||||
}
|
||||
|
||||
const entityIds = new Set<string>(
|
||||
entityRegEntities.map((reg) => reg.entity_id)
|
||||
);
|
||||
|
||||
for (const entity of domainEntities) {
|
||||
entityIds.add(entity);
|
||||
}
|
||||
|
||||
return entityIds.size;
|
||||
}
|
||||
);
|
||||
|
||||
@ -308,6 +357,9 @@ export class HaIntegrationCard extends LitElement {
|
||||
.icon.custom {
|
||||
background: var(--warning-color);
|
||||
}
|
||||
.icon.yaml {
|
||||
background: var(--label-badge-grey);
|
||||
}
|
||||
.icon ha-svg-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
@ -316,6 +368,9 @@ export class HaIntegrationCard extends LitElement {
|
||||
simple-tooltip {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.spacer {
|
||||
height: 36px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@ -4270,6 +4270,7 @@
|
||||
"entries_system": "[%key:ui::panel::config::integrations::integration_page::entries%]",
|
||||
"entries_entity": "[%key:ui::panel::config::integrations::integration_page::entries%]",
|
||||
"no_entries": "No entries",
|
||||
"yaml_entry": "This integration was not setup via the UI, you have either set it up in YAML or it is a dependency set up by another integration. If you want to configure it, you will need to do so in your configuration.yaml file.",
|
||||
"attention_entries": "Needs attention",
|
||||
"add_entry": "Add entry",
|
||||
"add_device": "Add device",
|
||||
@ -4340,6 +4341,7 @@
|
||||
"custom_integration": "Custom integration",
|
||||
"depends_on_cloud": "Depends on the cloud",
|
||||
"yaml_only": "Needs manual configuration",
|
||||
"no_config_flow": "This integration was not set up from the UI",
|
||||
"disabled_polling": "Automatic polling for updated data disabled",
|
||||
"debug_logging_enabled": "Debug logging enabled",
|
||||
"state": {
|
||||
|
Loading…
x
Reference in New Issue
Block a user