Add hiding diagnostic entities (#153)

Config and diagnostic entities are now hidden by default.

Their visibility can be defined in the configuration with entries
`hide_config_entities` and `hide_diagnostic_entities` for all or individual domains.
This commit is contained in:
w531t4
2025-03-23 02:33:00 -04:00
committed by GitHub
parent 0c2649e08e
commit 711140d13f
7 changed files with 83 additions and 46 deletions

View File

@@ -6,6 +6,7 @@ import {DeviceRegistryEntry} from "./types/homeassistant/data/device_registry";
import {AreaRegistryEntry} from "./types/homeassistant/data/area_registry"; import {AreaRegistryEntry} from "./types/homeassistant/data/area_registry";
import {generic} from "./types/strategy/generic"; import {generic} from "./types/strategy/generic";
import setupCustomLocalize from "./localize"; import setupCustomLocalize from "./localize";
import {applyEntityCategoryFilters} from "./utillties/filters";
import StrategyArea = generic.StrategyArea; import StrategyArea = generic.StrategyArea;
/** /**
@@ -228,7 +229,6 @@ class Helper {
* @static * @static
*/ */
static getCountTemplate(domain: string, operator: string, value: string): string { static getCountTemplate(domain: string, operator: string, value: string): string {
// noinspection JSMismatchedCollectionQueryUpdate (False positive per 17-04-2023)
/** /**
* Array of entity state-entries, filtered by domain. * Array of entity state-entries, filtered by domain.
* *
@@ -246,22 +246,14 @@ class Helper {
console.warn("Helper class should be initialized before calling this method!"); console.warn("Helper class should be initialized before calling this method!");
} }
// Get the ID of the devices which are linked to the given area. // Get the state of entities which are linked to the given area.
for (const area of this.#areas) { for (const area of this.#areas) {
const areaDeviceIds = this.#devices.filter((device) => { let entities = this.getDeviceEntities(area, domain);
return device.area_id === area.area_id;
}).map((device) => {
return device.id;
});
// Get the entities of which all conditions of the callback function are met. @see areaFilterCallback. // Exclude hidden Config and Diagnostic entities.
const newStates = this.#entities.filter( entities = applyEntityCategoryFilters(entities, domain);
this.#areaFilterCallback, {
area: area, const newStates = entities.map((entity) => `states['${entity.entity_id}']`);
domain: domain,
areaDeviceIds: areaDeviceIds,
})
.map((entity) => `states['${entity.entity_id}']`);
states.push(...newStates); states.push(...newStates);
} }

View File

@@ -21,7 +21,8 @@ export const getConfigurationDefaults = (localize: Function): StrategyDefaults =
debug: false, debug: false,
domains: { domains: {
_: { _: {
hide_config_entities: false, hide_config_entities: true,
hide_diagnostic_entities: true,
}, },
default: { default: {
title: localize("generic.miscellaneous"), title: localize("generic.miscellaneous"),

View File

@@ -6,6 +6,7 @@ import {LovelaceCardConfig, LovelaceConfig, LovelaceViewConfig} from "./types/ho
import {StackCardConfig} from "./types/homeassistant/lovelace/cards/types"; import {StackCardConfig} from "./types/homeassistant/lovelace/cards/types";
import {EntityCardConfig} from "./types/lovelace-mushroom/cards/entity-card-config"; import {EntityCardConfig} from "./types/lovelace-mushroom/cards/entity-card-config";
import {HassServiceTarget} from "home-assistant-js-websocket"; import {HassServiceTarget} from "home-assistant-js-websocket";
import {applyEntityCategoryFilters} from "./utillties/filters";
import StrategyArea = generic.StrategyArea; import StrategyArea = generic.StrategyArea;
/** /**
@@ -112,10 +113,10 @@ class MushroomStrategy extends HTMLTemplateElement {
try { try {
domainCards = await import(`./cards/${className}`).then(cardModule => { domainCards = await import(`./cards/${className}`).then(cardModule => {
let domainCards: EntityCardConfig[] = []; let domainCards: EntityCardConfig[] = [];
const entities = Helper.getDeviceEntities(area, domain); let entities = Helper.getDeviceEntities(area, domain);
let configEntityHidden =
Helper.strategyOptions.domains[domain ?? "_"].hide_config_entities // Exclude hidden Config and Diagnostic entities.
|| Helper.strategyOptions.domains["_"].hide_config_entities; entities = applyEntityCategoryFilters(entities, domain);
// Set the target for controller cards to entities without an area. // Set the target for controller cards to entities without an area.
if (area.area_id === "undisclosed") { if (area.area_id === "undisclosed") {
@@ -175,11 +176,6 @@ class MushroomStrategy extends HTMLTemplateElement {
deviceOptions = Helper.strategyOptions.card_options?.[entity.device_id]; deviceOptions = Helper.strategyOptions.card_options?.[entity.device_id];
} }
// Don't include the config-entity if hidden in the strategy options.
if (entity.entity_category === "config" && configEntityHidden) {
continue;
}
domainCards.push(new cardModule[className](entity, cardOptions).getCard()); domainCards.push(new cardModule[className](entity, cardOptions).getCard());
} }
@@ -219,10 +215,13 @@ class MushroomStrategy extends HTMLTemplateElement {
if (!Helper.strategyOptions.domains.default.hidden) { if (!Helper.strategyOptions.domains.default.hidden) {
// Create cards for any other domain. // Create cards for any other domain.
// Collect entities of the current area and unexposed domains. // Collect entities of the current area and unexposed domains.
const miscellaneousEntities = Helper.getDeviceEntities(area).filter( let miscellaneousEntities = Helper.getDeviceEntities(area).filter(
entity => !exposedDomainIds.includes(entity.entity_id.split(".", 1)[0]) entity => !exposedDomainIds.includes(entity.entity_id.split(".", 1)[0])
); );
// Exclude hidden Config and Diagnostic entities.
miscellaneousEntities = applyEntityCategoryFilters(miscellaneousEntities, "default");
// Create a column of miscellaneous entity cards. // Create a column of miscellaneous entity cards.
if (miscellaneousEntities.length) { if (miscellaneousEntities.length) {
let miscellaneousCards: (StackCardConfig | EntityCardConfig)[] = []; let miscellaneousCards: (StackCardConfig | EntityCardConfig)[] = [];
@@ -237,11 +236,6 @@ class MushroomStrategy extends HTMLTemplateElement {
let cardOptions = Helper.strategyOptions.card_options?.[entity.entity_id]; let cardOptions = Helper.strategyOptions.card_options?.[entity.entity_id];
let deviceOptions = Helper.strategyOptions.card_options?.[entity.device_id ?? "null"]; let deviceOptions = Helper.strategyOptions.card_options?.[entity.device_id ?? "null"];
// Don't include the config-entity if hidden in the strategy options
if (entity.entity_category === "config" && Helper.strategyOptions.domains["_"].hide_config_entities) {
continue;
}
miscellaneousCards.push(new cardModule.MiscellaneousCard(entity, cardOptions).getCard()); miscellaneousCards.push(new cardModule.MiscellaneousCard(entity, cardOptions).getCard());
} }

View File

@@ -38,11 +38,14 @@ export namespace generic {
* @property {boolean} [hidden] True if the entity should be hidden from the dashboard. * @property {boolean} [hidden] True if the entity should be hidden from the dashboard.
* @property {boolean} [hide_config_entities] True if the entity's categorie is "config" and should be hidden from the * @property {boolean} [hide_config_entities] True if the entity's categorie is "config" and should be hidden from the
* dashboard. * dashboard.
* @property {boolean} [hide_diagnostic_entities] True if the entity's categorie is "diagnostic" and should be hidden
* from the dashboard.
*/ */
export interface DomainConfig extends Partial<cards.ControllerCardConfig> { export interface DomainConfig extends Partial<cards.ControllerCardConfig> {
hidden?: boolean; hidden?: boolean;
order?: number; order?: number;
hide_config_entities?: boolean hide_config_entities?: boolean
hide_diagnostic_entities?: boolean
} }
/** /**

52
src/utillties/filters.ts Normal file
View File

@@ -0,0 +1,52 @@
import {EntityRegistryEntry} from "../types/homeassistant/data/entity_registry";
import {Helper} from "../Helper";
/**
* Filter an array of entities by property/value pair
*
* @param entities The array of entities to filter.
* @param property The property to filter on.
* @param value The value to match.
* @param exclude Whether to exclude entities with the given property/value pair (default: true).
*
* @returns A new list of entities filtered by the given property/value pair.
*/
export function filterEntitiesByPropertyValue(
entities: EntityRegistryEntry[],
property: keyof EntityRegistryEntry,
value: any,
exclude: boolean = true
) {
return entities.filter(entity => exclude ? entity[property] !== value : entity[property] === value);
}
export function applyEntityCategoryFilters(entities: EntityRegistryEntry[], domain: string) {
if (!Helper.isInitialized()) {
throw new Error("The Helper module must be initialized before using this one.");
}
const domainOptions = {
...Helper.strategyOptions.domains["_"],
...Helper.strategyOptions.domains[domain],
};
let filteredEntityCategory = [];
if (domainOptions.hide_config_entities) {
entities = filterEntitiesByPropertyValue(entities, "entity_category", "config");
filteredEntityCategory.push("Config");
}
if (domainOptions.hide_diagnostic_entities) {
entities = filterEntitiesByPropertyValue(entities, "entity_category", "diagnostic");
filteredEntityCategory.push("Diagnostic");
}
if (Helper.debug && filteredEntityCategory.length > 0) {
console.warn(filteredEntityCategory.join(" & ") + " entities are filtered out.");
}
return entities;
}

View File

@@ -5,6 +5,7 @@ import {LovelaceCardConfig, LovelaceViewConfig} from "../types/homeassistant/dat
import {cards} from "../types/strategy/cards"; import {cards} from "../types/strategy/cards";
import {TitleCardConfig} from "../types/lovelace-mushroom/cards/title-card-config"; import {TitleCardConfig} from "../types/lovelace-mushroom/cards/title-card-config";
import {HassServiceTarget} from "home-assistant-js-websocket"; import {HassServiceTarget} from "home-assistant-js-websocket";
import {applyEntityCategoryFilters} from "../utillties/filters";
import abstractCardConfig = cards.AbstractCardConfig; import abstractCardConfig = cards.AbstractCardConfig;
/** /**
@@ -42,24 +43,22 @@ abstract class AbstractView {
* @private * @private
* @readonly * @readonly
*/ */
readonly #domain?: string; readonly #domain: string;
/** /**
* Class constructor. * Class constructor.
* *
* @param {string} [domain] The domain which the view is representing. * @param {string} domain The domain which the view is representing.
* *
* @throws {Error} If trying to instantiate this class. * @throws {Error} If trying to instantiate this class.
* @throws {Error} If the Helper module isn't initialized. * @throws {Error} If the Helper module isn't initialized.
*/ */
protected constructor(domain: string = "") { protected constructor(domain: string) {
if (!Helper.isInitialized()) { if (!Helper.isInitialized()) {
throw new Error("The Helper module must be initialized before using this one."); throw new Error("The Helper module must be initialized before using this one.");
} }
if (domain) { this.#domain = domain;
this.#domain = domain;
}
} }
/** /**
@@ -69,14 +68,10 @@ abstract class AbstractView {
*/ */
async createViewCards(): Promise<(StackCardConfig | TitleCardConfig)[]> { async createViewCards(): Promise<(StackCardConfig | TitleCardConfig)[]> {
const viewCards: LovelaceCardConfig[] = []; const viewCards: LovelaceCardConfig[] = [];
const configEntityHidden =
Helper.strategyOptions.domains[this.#domain ?? "_"].hide_config_entities
|| Helper.strategyOptions.domains["_"].hide_config_entities;
// Create cards for each area. // Create cards for each area.
for (const area of Helper.areas) { for (const area of Helper.areas) {
const areaCards: abstractCardConfig[] = []; const areaCards: abstractCardConfig[] = [];
const entities = Helper.getDeviceEntities(area, this.#domain ?? "");
const className = Helper.sanitizeClassName(this.#domain + "Card"); const className = Helper.sanitizeClassName(this.#domain + "Card");
const cardModule = await import(`../cards/${className}`); const cardModule = await import(`../cards/${className}`);
@@ -85,6 +80,10 @@ abstract class AbstractView {
area_id: [area.area_id], area_id: [area.area_id],
}; };
let entities = Helper.getDeviceEntities(area, this.#domain);
// Exclude hidden Config and Diagnostic entities.
entities = applyEntityCategoryFilters(entities, this.#domain);
// Set the target for controller cards to entities without an area. // Set the target for controller cards to entities without an area.
if (area.area_id === "undisclosed") { if (area.area_id === "undisclosed") {
target = { target = {
@@ -101,10 +100,6 @@ abstract class AbstractView {
continue; continue;
} }
if (entity.entity_category === "config" && configEntityHidden) {
continue;
}
areaCards.push(new cardModule[className](entity, cardOptions).getCard()); areaCards.push(new cardModule[className](entity, cardOptions).getCard());
} }

View File

@@ -39,7 +39,7 @@ class HomeView extends AbstractView {
* @param {views.ViewConfig} [options={}] Options for the view. * @param {views.ViewConfig} [options={}] Options for the view.
*/ */
constructor(options: views.ViewConfig = {}) { constructor(options: views.ViewConfig = {}) {
super(); super("home");
this.config = Object.assign(this.config, this.#defaultConfig, options); this.config = Object.assign(this.config, this.#defaultConfig, options);
} }