From a40dc58e355ddb03012e8ebaae0eeb9536957462 Mon Sep 17 00:00:00 2001 From: mokusone <14061880+mokusone@users.noreply.github.com> Date: Thu, 14 May 2026 23:34:00 +0200 Subject: [PATCH] feat(filter): add cleanupExcludedOnStart option to delete stale objects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When enabled, on each adapter start the adapter scans all existing hass.0.entities.* channels and deletes those whose entity_id matches any of the configured excludePatterns. This complements the existing filter (which only prevents new objects from being created) by also cleaning up objects that were synced before the filter was activated or before the patterns were extended. Safety: - Channels (and any of their sub-states) holding common.custom config (history/influxdb/sql) are kept and a warning is logged so the user can decide manually. - Existing deleteStaleObjects() 50% sanity-guard is intentionally NOT applied here — the user explicitly opted in to the cleanup. - Per-id deletion only logs when verboseFilterLog is also active. Default is false (opt-in). Adds new admin checkbox + i18n keys for all 11 languages (machine translations for non-en/de still pending). --- admin/i18n/de.json | 6 ++- admin/i18n/en.json | 6 ++- admin/i18n/es.json | 6 ++- admin/i18n/fr.json | 14 +++--- admin/i18n/it.json | 6 ++- admin/i18n/nl.json | 6 ++- admin/i18n/pl.json | 6 ++- admin/i18n/pt.json | 6 ++- admin/i18n/ru.json | 6 ++- admin/i18n/uk.json | 6 ++- admin/i18n/zh-cn.json | 6 ++- admin/jsonConfig.json | 6 +++ io-package.json | 3 +- src/main.ts | 102 ++++++++++++++++++++++++++++++++++++++++++ 14 files changed, 158 insertions(+), 27 deletions(-) diff --git a/admin/i18n/de.json b/admin/i18n/de.json index f0b008d..23b0c5f 100644 --- a/admin/i18n/de.json +++ b/admin/i18n/de.json @@ -8,5 +8,7 @@ "Exclude patterns (one per line)": "Filter-Patterns (eines pro Zeile)", "Glob patterns, * wildcard. Lines starting with # are comments. Empty = filter off.": "Glob-Patterns mit * als Wildcard. Zeilen mit # sind Kommentare. Leer = Filter aus.", "Verbose filter logging": "Ausführliches Filter-Logging", - "When enabled, log each excluded entity_id individually during the initial adapter sync (requires loglevel info or debug).": "Wenn aktiv: jede ausgeschlossene entity_id wird beim initialen Adapter-Sync einzeln geloggt (benötigt Loglevel info oder debug)." -} \ No newline at end of file + "When enabled, log each excluded entity_id individually during the initial adapter sync (requires loglevel info or debug).": "Wenn aktiv: jede ausgeschlossene entity_id wird beim initialen Adapter-Sync einzeln geloggt (benötigt Loglevel info oder debug).", + "Cleanup excluded entities on adapter start": "Ausgefilterte Entitäten beim Adapter-Start löschen", + "When enabled, on each adapter start delete existing objects under entities.* whose entity_id matches the exclude patterns. Objects with custom adapter config (history/influxdb/sql) are kept and logged.": "Wenn aktiv: bei jedem Adapter-Start werden vorhandene Objekte unter entities.*, deren entity_id auf die Filter-Patterns matched, gelöscht. Objekte mit Custom-Adapter-Konfiguration (history/influxdb/sql) bleiben erhalten und werden geloggt." +} diff --git a/admin/i18n/en.json b/admin/i18n/en.json index 307dd24..8724b5b 100644 --- a/admin/i18n/en.json +++ b/admin/i18n/en.json @@ -8,5 +8,7 @@ "Exclude patterns (one per line)": "Exclude patterns (one per line)", "Glob patterns, * wildcard. Lines starting with # are comments. Empty = filter off.": "Glob patterns, * wildcard. Lines starting with # are comments. Empty = filter off.", "Verbose filter logging": "Verbose filter logging", - "When enabled, log each excluded entity_id individually during the initial adapter sync (requires loglevel info or debug).": "When enabled, log each excluded entity_id individually during the initial adapter sync (requires loglevel info or debug)." -} \ No newline at end of file + "When enabled, log each excluded entity_id individually during the initial adapter sync (requires loglevel info or debug).": "When enabled, log each excluded entity_id individually during the initial adapter sync (requires loglevel info or debug).", + "Cleanup excluded entities on adapter start": "Cleanup excluded entities on adapter start", + "When enabled, on each adapter start delete existing objects under entities.* whose entity_id matches the exclude patterns. Objects with custom adapter config (history/influxdb/sql) are kept and logged.": "When enabled, on each adapter start delete existing objects under entities.* whose entity_id matches the exclude patterns. Objects with custom adapter config (history/influxdb/sql) are kept and logged." +} diff --git a/admin/i18n/es.json b/admin/i18n/es.json index 712e053..c02c44e 100644 --- a/admin/i18n/es.json +++ b/admin/i18n/es.json @@ -8,5 +8,7 @@ "Exclude patterns (one per line)": "Patrones de exclusión (uno por línea)", "Glob patterns, * wildcard. Lines starting with # are comments. Empty = filter off.": "Patrones glob, * como comodín. Las líneas que empiezan con # son comentarios. Vacío = filtro desactivado.", "Verbose filter logging": "Registro detallado del filtro", - "When enabled, log each excluded entity_id individually during the initial adapter sync (requires loglevel info or debug).": "Cuando está activado, cada entity_id excluido se registra individualmente durante la sincronización inicial del adaptador (requiere loglevel info o debug)." -} \ No newline at end of file + "When enabled, log each excluded entity_id individually during the initial adapter sync (requires loglevel info or debug).": "Cuando está activado, cada entity_id excluido se registra individualmente durante la sincronización inicial del adaptador (requiere loglevel info o debug).", + "Cleanup excluded entities on adapter start": "Cleanup excluded entities on adapter start", + "When enabled, on each adapter start delete existing objects under entities.* whose entity_id matches the exclude patterns. Objects with custom adapter config (history/influxdb/sql) are kept and logged.": "When enabled, on each adapter start delete existing objects under entities.* whose entity_id matches the exclude patterns. Objects with custom adapter config (history/influxdb/sql) are kept and logged." +} diff --git a/admin/i18n/fr.json b/admin/i18n/fr.json index 9a042de..efe89e3 100644 --- a/admin/i18n/fr.json +++ b/admin/i18n/fr.json @@ -1,12 +1,14 @@ { - "Home assistant IP": "IP de l'assistant à domicile ", - "Home assistant WS Port": "Port WS de l'assistant domestique ", - "Password repeat": "Répéter le mot de passe ", + "Home assistant IP": "IP de l'assistant à domicile ", + "Home assistant WS Port": "Port WS de l'assistant domestique ", + "Password repeat": "Répéter le mot de passe ", "Password": "Mot de passe", - "Passwords missmatch!": "Les mots de passe ne correspondent pas !", + "Passwords missmatch!": "Les mots de passe ne correspondent pas !", "Secure": "HTTPS ?", "Exclude patterns (one per line)": "Modèles d'exclusion (un par ligne)", "Glob patterns, * wildcard. Lines starting with # are comments. Empty = filter off.": "Modèles glob, * comme joker. Les lignes commençant par # sont des commentaires. Vide = filtre désactivé.", "Verbose filter logging": "Journalisation détaillée du filtre", - "When enabled, log each excluded entity_id individually during the initial adapter sync (requires loglevel info or debug).": "Lorsqu'activé, chaque entity_id exclu est journalisé individuellement pendant la synchronisation initiale de l'adaptateur (nécessite loglevel info ou debug)." -} \ No newline at end of file + "When enabled, log each excluded entity_id individually during the initial adapter sync (requires loglevel info or debug).": "Lorsqu'activé, chaque entity_id exclu est journalisé individuellement pendant la synchronisation initiale de l'adaptateur (nécessite loglevel info ou debug).", + "Cleanup excluded entities on adapter start": "Cleanup excluded entities on adapter start", + "When enabled, on each adapter start delete existing objects under entities.* whose entity_id matches the exclude patterns. Objects with custom adapter config (history/influxdb/sql) are kept and logged.": "When enabled, on each adapter start delete existing objects under entities.* whose entity_id matches the exclude patterns. Objects with custom adapter config (history/influxdb/sql) are kept and logged." +} diff --git a/admin/i18n/it.json b/admin/i18n/it.json index d8c812b..0bc1bf3 100644 --- a/admin/i18n/it.json +++ b/admin/i18n/it.json @@ -8,5 +8,7 @@ "Exclude patterns (one per line)": "Pattern di esclusione (uno per riga)", "Glob patterns, * wildcard. Lines starting with # are comments. Empty = filter off.": "Pattern glob, * come wildcard. Le righe che iniziano con # sono commenti. Vuoto = filtro disattivato.", "Verbose filter logging": "Logging dettagliato del filtro", - "When enabled, log each excluded entity_id individually during the initial adapter sync (requires loglevel info or debug).": "Se attivato, registra ogni entity_id escluso individualmente durante la sincronizzazione iniziale dell'adattatore (richiede loglevel info o debug)." -} \ No newline at end of file + "When enabled, log each excluded entity_id individually during the initial adapter sync (requires loglevel info or debug).": "Se attivato, registra ogni entity_id escluso individualmente durante la sincronizzazione iniziale dell'adattatore (richiede loglevel info o debug).", + "Cleanup excluded entities on adapter start": "Cleanup excluded entities on adapter start", + "When enabled, on each adapter start delete existing objects under entities.* whose entity_id matches the exclude patterns. Objects with custom adapter config (history/influxdb/sql) are kept and logged.": "When enabled, on each adapter start delete existing objects under entities.* whose entity_id matches the exclude patterns. Objects with custom adapter config (history/influxdb/sql) are kept and logged." +} diff --git a/admin/i18n/nl.json b/admin/i18n/nl.json index 8c69b91..2fa44e1 100644 --- a/admin/i18n/nl.json +++ b/admin/i18n/nl.json @@ -8,5 +8,7 @@ "Exclude patterns (one per line)": "Uitsluitpatronen (één per regel)", "Glob patterns, * wildcard. Lines starting with # are comments. Empty = filter off.": "Glob-patronen, * als wildcard. Regels die beginnen met # zijn commentaar. Leeg = filter uit.", "Verbose filter logging": "Uitgebreide filterlogging", - "When enabled, log each excluded entity_id individually during the initial adapter sync (requires loglevel info or debug).": "Indien ingeschakeld, wordt elke uitgesloten entity_id afzonderlijk gelogd tijdens de initiële adapter-synchronisatie (vereist loglevel info of debug)." -} \ No newline at end of file + "When enabled, log each excluded entity_id individually during the initial adapter sync (requires loglevel info or debug).": "Indien ingeschakeld, wordt elke uitgesloten entity_id afzonderlijk gelogd tijdens de initiële adapter-synchronisatie (vereist loglevel info of debug).", + "Cleanup excluded entities on adapter start": "Cleanup excluded entities on adapter start", + "When enabled, on each adapter start delete existing objects under entities.* whose entity_id matches the exclude patterns. Objects with custom adapter config (history/influxdb/sql) are kept and logged.": "When enabled, on each adapter start delete existing objects under entities.* whose entity_id matches the exclude patterns. Objects with custom adapter config (history/influxdb/sql) are kept and logged." +} diff --git a/admin/i18n/pl.json b/admin/i18n/pl.json index 5c79d78..dba6e9d 100644 --- a/admin/i18n/pl.json +++ b/admin/i18n/pl.json @@ -8,5 +8,7 @@ "Exclude patterns (one per line)": "Wzorce wykluczeń (jeden na linię)", "Glob patterns, * wildcard. Lines starting with # are comments. Empty = filter off.": "Wzorce glob, * jako symbol wieloznaczny. Linie zaczynające się od # to komentarze. Puste = filtr wyłączony.", "Verbose filter logging": "Szczegółowe logowanie filtra", - "When enabled, log each excluded entity_id individually during the initial adapter sync (requires loglevel info or debug).": "Gdy włączone, każde wykluczone entity_id jest logowane osobno podczas początkowej synchronizacji adaptera (wymaga poziomu logowania info lub debug)." -} \ No newline at end of file + "When enabled, log each excluded entity_id individually during the initial adapter sync (requires loglevel info or debug).": "Gdy włączone, każde wykluczone entity_id jest logowane osobno podczas początkowej synchronizacji adaptera (wymaga poziomu logowania info lub debug).", + "Cleanup excluded entities on adapter start": "Cleanup excluded entities on adapter start", + "When enabled, on each adapter start delete existing objects under entities.* whose entity_id matches the exclude patterns. Objects with custom adapter config (history/influxdb/sql) are kept and logged.": "When enabled, on each adapter start delete existing objects under entities.* whose entity_id matches the exclude patterns. Objects with custom adapter config (history/influxdb/sql) are kept and logged." +} diff --git a/admin/i18n/pt.json b/admin/i18n/pt.json index 8c67662..c693c1a 100644 --- a/admin/i18n/pt.json +++ b/admin/i18n/pt.json @@ -8,5 +8,7 @@ "Exclude patterns (one per line)": "Padrões de exclusão (um por linha)", "Glob patterns, * wildcard. Lines starting with # are comments. Empty = filter off.": "Padrões glob, * como wildcard. Linhas começando com # são comentários. Vazio = filtro desligado.", "Verbose filter logging": "Registro detalhado do filtro", - "When enabled, log each excluded entity_id individually during the initial adapter sync (requires loglevel info or debug).": "Quando ativado, cada entity_id excluído é registrado individualmente durante a sincronização inicial do adaptador (requer loglevel info ou debug)." -} \ No newline at end of file + "When enabled, log each excluded entity_id individually during the initial adapter sync (requires loglevel info or debug).": "Quando ativado, cada entity_id excluído é registrado individualmente durante a sincronização inicial do adaptador (requer loglevel info ou debug).", + "Cleanup excluded entities on adapter start": "Cleanup excluded entities on adapter start", + "When enabled, on each adapter start delete existing objects under entities.* whose entity_id matches the exclude patterns. Objects with custom adapter config (history/influxdb/sql) are kept and logged.": "When enabled, on each adapter start delete existing objects under entities.* whose entity_id matches the exclude patterns. Objects with custom adapter config (history/influxdb/sql) are kept and logged." +} diff --git a/admin/i18n/ru.json b/admin/i18n/ru.json index 7f3f987..b4a9107 100644 --- a/admin/i18n/ru.json +++ b/admin/i18n/ru.json @@ -8,5 +8,7 @@ "Exclude patterns (one per line)": "Шаблоны исключения (по одному на строку)", "Glob patterns, * wildcard. Lines starting with # are comments. Empty = filter off.": "Glob-шаблоны, * — подстановочный знак. Строки, начинающиеся с #, являются комментариями. Пусто = фильтр выключен.", "Verbose filter logging": "Подробное логирование фильтра", - "When enabled, log each excluded entity_id individually during the initial adapter sync (requires loglevel info or debug).": "Когда включено, каждый исключённый entity_id логируется отдельно во время начальной синхронизации адаптера (требуется loglevel info или debug)." -} \ No newline at end of file + "When enabled, log each excluded entity_id individually during the initial adapter sync (requires loglevel info or debug).": "Когда включено, каждый исключённый entity_id логируется отдельно во время начальной синхронизации адаптера (требуется loglevel info или debug).", + "Cleanup excluded entities on adapter start": "Cleanup excluded entities on adapter start", + "When enabled, on each adapter start delete existing objects under entities.* whose entity_id matches the exclude patterns. Objects with custom adapter config (history/influxdb/sql) are kept and logged.": "When enabled, on each adapter start delete existing objects under entities.* whose entity_id matches the exclude patterns. Objects with custom adapter config (history/influxdb/sql) are kept and logged." +} diff --git a/admin/i18n/uk.json b/admin/i18n/uk.json index aa15ae0..ecc21c0 100644 --- a/admin/i18n/uk.json +++ b/admin/i18n/uk.json @@ -8,5 +8,7 @@ "Exclude patterns (one per line)": "Шаблони виключення (по одному в рядку)", "Glob patterns, * wildcard. Lines starting with # are comments. Empty = filter off.": "Glob-шаблони, * — підстановочний знак. Рядки, що починаються з #, є коментарями. Порожньо = фільтр вимкнено.", "Verbose filter logging": "Детальне логування фільтра", - "When enabled, log each excluded entity_id individually during the initial adapter sync (requires loglevel info or debug).": "Коли увімкнено, кожен виключений entity_id логується окремо під час початкової синхронізації адаптера (потрібен loglevel info або debug)." -} \ No newline at end of file + "When enabled, log each excluded entity_id individually during the initial adapter sync (requires loglevel info or debug).": "Коли увімкнено, кожен виключений entity_id логується окремо під час початкової синхронізації адаптера (потрібен loglevel info або debug).", + "Cleanup excluded entities on adapter start": "Cleanup excluded entities on adapter start", + "When enabled, on each adapter start delete existing objects under entities.* whose entity_id matches the exclude patterns. Objects with custom adapter config (history/influxdb/sql) are kept and logged.": "When enabled, on each adapter start delete existing objects under entities.* whose entity_id matches the exclude patterns. Objects with custom adapter config (history/influxdb/sql) are kept and logged." +} diff --git a/admin/i18n/zh-cn.json b/admin/i18n/zh-cn.json index 926adaf..cb0785f 100644 --- a/admin/i18n/zh-cn.json +++ b/admin/i18n/zh-cn.json @@ -8,5 +8,7 @@ "Exclude patterns (one per line)": "排除模式(每行一个)", "Glob patterns, * wildcard. Lines starting with # are comments. Empty = filter off.": "Glob 模式,* 通配符。以 # 开头的行是注释。为空 = 过滤器关闭。", "Verbose filter logging": "详细过滤器日志", - "When enabled, log each excluded entity_id individually during the initial adapter sync (requires loglevel info or debug).": "启用后,在适配器初始同步期间单独记录每个被排除的 entity_id(需要日志级别 info 或 debug)。" -} \ No newline at end of file + "When enabled, log each excluded entity_id individually during the initial adapter sync (requires loglevel info or debug).": "启用后,在适配器初始同步期间单独记录每个被排除的 entity_id(需要日志级别 info 或 debug)。", + "Cleanup excluded entities on adapter start": "Cleanup excluded entities on adapter start", + "When enabled, on each adapter start delete existing objects under entities.* whose entity_id matches the exclude patterns. Objects with custom adapter config (history/influxdb/sql) are kept and logged.": "When enabled, on each adapter start delete existing objects under entities.* whose entity_id matches the exclude patterns. Objects with custom adapter config (history/influxdb/sql) are kept and logged." +} diff --git a/admin/jsonConfig.json b/admin/jsonConfig.json index c320740..89b49a3 100644 --- a/admin/jsonConfig.json +++ b/admin/jsonConfig.json @@ -55,6 +55,12 @@ "label": "Verbose filter logging", "help": "When enabled, log each excluded entity_id individually during the initial adapter sync (requires loglevel info or debug).", "sm": 12 + }, + "cleanupExcludedOnStart": { + "type": "checkbox", + "label": "Cleanup excluded entities on adapter start", + "help": "When enabled, on each adapter start delete existing objects under entities.* whose entity_id matches the exclude patterns. Objects with custom adapter config (history/influxdb/sql) are kept and logged.", + "sm": 12 } } } diff --git a/io-package.json b/io-package.json index dd74f9c..938c86c 100644 --- a/io-package.json +++ b/io-package.json @@ -254,7 +254,8 @@ "password": "", "secure": false, "excludePatterns": "# Glob patterns, one per line. Lines starting with # are comments.\n# Example: filter out all entities prefixed with `iob_` regardless of domain:\n# *.iob_*", - "verboseFilterLog": false + "verboseFilterLog": false, + "cleanupExcludedOnStart": false }, "protectedNative": [ "password" diff --git a/src/main.ts b/src/main.ts index 2826c08..875dbba 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,6 +9,7 @@ interface HassAdapterConfig { secure: boolean; excludePatterns: string; verboseFilterLog: boolean; + cleanupExcludedOnStart: boolean; } interface HassEntity { @@ -683,6 +684,105 @@ class HassAdapter extends Adapter { this.initialSyncCompleted = true; } + private async cleanupExcludedObjects(): Promise { + if (!this.config.cleanupExcludedOnStart) { + return; + } + if (this.excludePatterns.length === 0) { + this.log.info('Cleanup skipped: no exclude patterns configured'); + return; + } + + let allObjects: Record; + try { + allObjects = (await this.getAdapterObjectsAsync()) as Record; + } catch (err) { + this.log.error(`Cleanup: failed to load adapter objects: ${err}`); + return; + } + + const prefix = `${this.namespace}.entities.`; + const channelsToDelete: { id: string; entityId: string }[] = []; + + for (const id in allObjects) { + if (!Object.prototype.hasOwnProperty.call(allObjects, id) || !id.startsWith(prefix)) { + continue; + } + const obj = allObjects[id]; + if (obj.type !== 'channel') { + continue; + } + const entityId = (obj.native as Record)?.entity_id; + if (typeof entityId !== 'string') { + continue; + } + if (isExcluded(entityId, this.excludePatterns)) { + channelsToDelete.push({ id, entityId }); + } + } + + if (channelsToDelete.length === 0) { + this.log.info('Cleanup: no existing objects matched exclude patterns'); + return; + } + + let deletedCount = 0; + let keptForCustomCount = 0; + + for (const { id, entityId } of channelsToDelete) { + const channelCustom = (allObjects[id].common as { custom?: Record } | undefined) + ?.custom; + let hasCustom = !!(channelCustom && Object.keys(channelCustom).length); + if (!hasCustom) { + const subPrefix = `${id}.`; + for (const subId in allObjects) { + if (!subId.startsWith(subPrefix)) { + continue; + } + const subCustom = ( + allObjects[subId].common as { custom?: Record } | undefined + )?.custom; + if (subCustom && Object.keys(subCustom).length) { + hasCustom = true; + break; + } + } + } + + if (hasCustom) { + keptForCustomCount++; + this.log.warn( + `Cleanup: keeping "${id}" (entity ${entityId}) — has custom adapter config (history/influxdb/sql); remove it manually if you really want to drop it`, + ); + continue; + } + + try { + await this.delObjectAsync(id, { recursive: true }); + delete this.hassObjects[id]; + const subPrefix = `${id}.`; + for (const cachedId of Object.keys(this.hassObjects)) { + if (cachedId.startsWith(subPrefix)) { + delete this.hassObjects[cachedId]; + } + } + deletedCount++; + if (this.config.verboseFilterLog) { + this.log.info(`Cleanup: deleted "${id}" (entity ${entityId})`); + } + } catch (err) { + this.log.error(`Cleanup: failed to delete "${id}": ${err}`); + } + } + + this.log.info( + `Cleanup: deleted ${deletedCount} excluded entit${deletedCount === 1 ? 'y' : 'ies'}` + + (keptForCustomCount > 0 + ? `, kept ${keptForCustomCount} with custom config (see warnings above)` + : ''), + ); + } + private async main(): Promise { this.config.host ||= '127.0.0.1'; this.config.port = parseInt(String(this.config.port), 10) || 8123; @@ -701,6 +801,8 @@ class HassAdapter extends Adapter { ); } + await this.cleanupExcludedObjects(); + await this.setStateAsync('info.connection', false, true); this.hass = new HASS(this.config, this.log);