180180 border-radius : var (--radius-sm ); padding : 3px 7px ; font-size : 13px ;
181181 background : var (--surface ); color : var (--text ); text-align : center; }
182182
183+ .ignore-tag { display : inline-flex; align-items : center; gap : 4px ;
184+ background : var (--surface2 ); border : 0.5px solid var (--border2 );
185+ border-radius : 99px ; padding : 2px 9px ; font-size : 11px ; color : var (--text2 ); }
186+ .ignore-tag-x { cursor : pointer; opacity : 0.5 ; font-size : 13px ; line-height : 1 ; }
187+ .ignore-tag-x : hover { opacity : 1 ; }
188+ .ctrl-bar input [type = text ] { border : 0.5px solid var (--border2 );
189+ border-radius : var (--radius-sm ); padding : 3px 8px ; font-size : 12px ;
190+ background : var (--surface ); color : var (--text ); width : 120px ; }
191+ .ctrl-bar button .btn-add { font-size : 11px ; padding : 3px 9px ; }
192+ tr .ignored { opacity : 0.35 ; }
193+ tr .ignored td { color : var (--text3 ); }
194+
183195 @media (max-width : 600px ) {
184196 .stat-row { grid-template-columns : 1fr 1fr ; }
185197 .topbar-sub { display : none; }
196208 < svg viewBox ="0 0 24 24 "> < path d ="M19.36 2.72 20.78 4.14l-1.4 1.41 1.41 1.41-4.24 4.24-1.41-1.41-6.89 6.9a4 4 0 0 1-1.2 4.71l-2.83 2.12-1.41-1.41 2.12-2.83a2 2 0 0 0 .27-2.1l-.27-.42L3 15.36l1.41-1.41 1.42 1.42 5.65-5.66-1.41-1.41 4.24-4.24 1.41 1.41z "/> </ svg >
197209 </ div >
198210 < div >
199- < div class ="topbar-title "> Orphan Entity Cleaner < span style ="font-size:11px;font-weight:400;color:var(--text2);margin-left:6px "> v1.5.6 </ span > </ div >
211+ < div class ="topbar-title "> Orphan Entity Cleaner < span style ="font-size:11px;font-weight:400;color:var(--text2);margin-left:6px "> v2.0.0 </ span > </ div >
200212 < div class ="topbar-sub "> Home Assistant Integration — orphan entity cleanup</ div >
201213 </ div >
202214 < div class ="topbar-spacer "> </ div >
218230 < div class ="ctrl-sep "> </ div >
219231 < span style ="color:var(--text2) "> Min age (h)</ span >
220232 < input type ="number " id ="min-age " value ="24 " min ="1 " max ="720 ">
233+ < div class ="ctrl-sep "> </ div >
234+ < span style ="color:var(--text2) "> Ignore platforms</ span >
235+ < input type ="text " id ="ignore-input " placeholder ="es. tuya, zha… ">
236+ < button class ="btn-add " onclick ="ignoreAdd() "> + Add</ button >
237+ < div id ="ignore-tags " style ="display:flex;gap:5px;flex-wrap:wrap;align-items:center; "> </ div >
221238 </ div >
222239
223240 <!-- Toolbar -->
@@ -282,8 +299,31 @@ <h2>Confirm deletion</h2>
282299</ div >
283300
284301< script >
285- let allOrphans = [ ] ;
286- let selected = new Set ( ) ;
302+ let allOrphans = [ ] ;
303+ let selected = new Set ( ) ;
304+ let ignorePlatforms = new Set ( ) ;
305+
306+ function ignoreAdd ( ) {
307+ const inp = document . getElementById ( 'ignore-input' ) ;
308+ const val = inp . value . trim ( ) . toLowerCase ( ) . replace ( / [ ^ a - z 0 - 9 _ ] / g, '' ) ;
309+ if ( ! val ) return ;
310+ if ( ignorePlatforms . has ( val ) ) { inp . value = '' ; return ; }
311+ ignorePlatforms . add ( val ) ;
312+ inp . value = '' ;
313+ renderIgnoreTags ( ) ;
314+ renderTable ( filteredOrphans ( ) ) ;
315+ }
316+ function ignoreRemove ( val ) {
317+ ignorePlatforms . delete ( val ) ;
318+ renderIgnoreTags ( ) ;
319+ renderTable ( filteredOrphans ( ) ) ;
320+ }
321+ function renderIgnoreTags ( ) {
322+ const c = document . getElementById ( 'ignore-tags' ) ;
323+ c . innerHTML = [ ...ignorePlatforms ] . map ( v =>
324+ `<span class="ignore-tag">${ v } <span class="ignore-tag-x" onclick="ignoreRemove('${ v } ')">×</span></span>`
325+ ) . join ( '' ) ;
326+ }
287327
288328function addLog ( msg , type = 'info' ) {
289329 const box = document . getElementById ( 'log-box' ) ;
@@ -304,22 +344,27 @@ <h2>Confirm deletion</h2>
304344function filteredOrphans ( ) {
305345 const q = ( document . getElementById ( 'filter' ) . value || '' ) . toLowerCase ( ) ;
306346 const heur = document . getElementById ( 'heur-on' ) ?. checked ;
307- return allOrphans . filter ( e =>
308- ( heur || e . method !== 'heuristic' ) &&
309- ( ! q || e . entity_id . includes ( q ) || e . platform . includes ( q ) )
310- ) ;
347+ return allOrphans
348+ . filter ( e => ( heur || e . method !== 'heuristic' ) &&
349+ ( ! q || e . entity_id . includes ( q ) || e . platform . includes ( q ) ) )
350+ . map ( e => ( { ...e , _ignored : ignorePlatforms . has ( e . platform ) } ) ) ;
351+ }
352+ function activeOrphans ( ) {
353+ return filteredOrphans ( ) . filter ( e => ! e . _ignored ) ;
311354}
312- function selAll ( ) { filteredOrphans ( ) . forEach ( e => selected . add ( e . entity_id ) ) ; renderTable ( filteredOrphans ( ) ) ; updateSelCount ( ) ; }
355+ function selAll ( ) { activeOrphans ( ) . forEach ( e => selected . add ( e . entity_id ) ) ; renderTable ( filteredOrphans ( ) ) ; updateSelCount ( ) ; }
313356function deselAll ( ) { selected . clear ( ) ; renderTable ( filteredOrphans ( ) ) ; updateSelCount ( ) ; }
314357function toggleRow ( id , chk ) { chk ? selected . add ( id ) : selected . delete ( id ) ; updateSelCount ( ) ; }
315358function applyFilter ( ) {
316359 const items = filteredOrphans ( ) ;
317360 renderTable ( items ) ;
318361 const q = document . getElementById ( 'filter' ) . value ;
319- document . getElementById ( 'tbl-meta' ) . textContent = q ? items . length + ' of ' + allOrphans . length : '' ;
362+ const active = items . filter ( e => ! e . _ignored ) . length ;
363+ document . getElementById ( 'tbl-meta' ) . textContent = q ? active + ' of ' + allOrphans . length : '' ;
320364}
321365function renderTable ( items ) {
322366 const wrap = document . getElementById ( 'tbl-wrap' ) ;
367+ const activeItems = items . filter ( e => ! e . _ignored ) ;
323368 if ( ! items . length ) {
324369 wrap . innerHTML = `<div class="empty">
325370 <svg viewBox="0 0 24 24"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>
@@ -328,6 +373,17 @@ <h2>Confirm deletion</h2>
328373 return ;
329374 }
330375 const rows = items . map ( e => {
376+ if ( e . _ignored ) {
377+ const dis = e . disabled_by ? '<span class="badge badge-disabled">disabled</span>' : '' ;
378+ const age = e . age_hours != null ? e . age_hours + 'h' : '—' ;
379+ return `<tr class="ignored">
380+ <td class="td-chk"></td>
381+ <td><span class="eid">${ e . entity_id } </span>${ dis } </td>
382+ <td><span class="platform-badge">${ e . platform } </span></td>
383+ <td class="td-method"><span style="color:var(--text3)">— ignored</span></td>
384+ <td class="td-age"><span class="age-text">—</span></td>
385+ </tr>` ;
386+ }
331387 const chk = selected . has ( e . entity_id ) ? 'checked' : '' ;
332388 const badge = e . method === 'timestamp'
333389 ? '<span class="badge badge-ts">orphaned_timestamp</span>'
@@ -436,6 +492,7 @@ <h2>Confirm deletion</h2>
436492 }
437493}
438494document . addEventListener ( 'keydown' , e => { if ( e . key === 'Escape' ) closeModal ( ) ; } ) ;
495+ document . getElementById ( 'ignore-input' ) . addEventListener ( 'keydown' , e => { if ( e . key === 'Enter' ) ignoreAdd ( ) ; } ) ;
439496</ script >
440497</ body >
441498</ html >
0 commit comments