Skip to content

Commit f5075f3

Browse files
author
Franz646
committed
feat: dry run mode — simulate deletion without removing entities — v2.4.2
- Dry run toggle in toolbar, activates amber warning banner - 'Delete selected' becomes 'Simulate' in dry run mode - Save & Delete hidden in dry run mode - Simulated entities highlighted with 'would delete' badge for 6 seconds - Results logged in the activity log
1 parent 88bfd68 commit f5075f3

3 files changed

Lines changed: 70 additions & 3 deletions

File tree

custom_components/orphan_cleaner/const.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,4 @@
4747
PANEL_ICON = "mdi:broom"
4848

4949
# Versione corrente (usata per cache-busting)
50-
VERSION = "2.4.1"
50+
VERSION = "2.4.2"

custom_components/orphan_cleaner/frontend/panel.html

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,13 @@
220220
border-radius:var(--radius-sm); color:#2ecc71; font-size:11px; padding:3px 10px; cursor:pointer; }
221221
.btn-save-list:hover { background:rgba(46,204,113,.25); }
222222
.save-ok { font-size:11px; color:#2ecc71; opacity:0; transition:opacity .4s; margin-left:4px; }
223+
.dry-run-wrap { display:flex; align-items:center; gap:6px; font-size:12px;
224+
color:var(--text2); white-space:nowrap; }
225+
.dry-run-wrap input[type=checkbox] { accent-color:#f59e0b; width:14px; height:14px; cursor:pointer; }
226+
.dry-banner { display:none; background:rgba(245,158,11,.12); border:.5px solid rgba(245,158,11,.4);
227+
border-radius:var(--radius-sm); padding:7px 14px; margin-bottom:8px;
228+
font-size:12px; color:#f59e0b; }
229+
.dry-banner.show { display:block; }
223230
.btn-save-del { background:#7a5200; border:none;
224231
border-radius:var(--radius-sm); color:#f5c842; font-size:13px; font-weight:500;
225232
padding:7px 13px; cursor:pointer; white-space:nowrap; }
@@ -242,7 +249,7 @@
242249
<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>
243250
</div>
244251
<div>
245-
<div class="topbar-title">Orphan Entity Cleaner <span style="font-size:11px;font-weight:400;color:var(--text2);margin-left:6px">v2.4.1</span></div>
252+
<div class="topbar-title">Orphan Entity Cleaner <span style="font-size:11px;font-weight:400;color:var(--text2);margin-left:6px">v2.4.2</span></div>
246253
<div class="topbar-sub">Home Assistant Integration — orphan entity cleanup</div>
247254
</div>
248255
<div class="topbar-spacer"></div>
@@ -308,9 +315,15 @@
308315
<button id="btn-desel" onclick="deselAll()" disabled>Deselect</button>
309316
<input class="search" type="text" placeholder="Filter by entity_id or platform…"
310317
id="filter" oninput="applyFilter()" disabled>
318+
<label class="dry-run-wrap" title="Simulate deletion without removing anything">
319+
<input type="checkbox" id="dry-run-toggle" onchange="onDryRunToggle()"> Dry run
320+
</label>
311321
<button class="btn-danger" id="btn-del" onclick="confirmDelete()" disabled>Delete selected</button>
312322
<button class="btn-save-del" id="btn-save-del" onclick="confirmSaveDelete()" disabled title="Save a backup JSON before deleting">Save &amp; Delete</button>
313323
</div>
324+
<div class="dry-banner" id="dry-banner">
325+
🔍 <strong>Dry run mode</strong> — deletion will be simulated, nothing will actually be removed.
326+
</div>
314327

315328
<!-- Table card -->
316329
<div class="card">
@@ -534,11 +547,65 @@ <h2>Confirm deletion</h2>
534547

535548
function confirmDelete() {
536549
if (!selected.size) return;
550+
if (isDryRun()) { doDryRun(); return; }
537551
document.getElementById('modal-msg').innerHTML =
538552
'You are about to delete <strong>'+selected.size+' entities</strong> from the Home Assistant registry.<br>'
539553
+'This operation is <strong>irreversible</strong>. Continue?';
540554
document.getElementById('overlay').classList.add('show');
541555
}
556+
function onDryRunToggle() {
557+
const dry = document.getElementById('dry-run-toggle').checked;
558+
document.getElementById('dry-banner').classList.toggle('show', dry);
559+
document.getElementById('btn-del').textContent = dry ? 'Simulate' : 'Delete selected';
560+
document.getElementById('btn-save-del').style.display = dry ? 'none' : '';
561+
}
562+
563+
function isDryRun() {
564+
return document.getElementById('dry-run-toggle').checked;
565+
}
566+
567+
async function doDryRun() {
568+
const ids = [...selected];
569+
addLog('🔍 Dry run: simulating deletion of '+ids.length+' entities…', 'info');
570+
setStatus('Dry run in progress…');
571+
try {
572+
const r = await fetch('/api/orphan_cleaner/delete', {
573+
method: 'POST', headers: {'Content-Type':'application/json'},
574+
body: JSON.stringify({ entity_ids: ids, dry_run: true })
575+
});
576+
if (!r.ok) throw new Error('HTTP '+r.status);
577+
const d = await r.json();
578+
addLog('🔍 Dry run complete: '+d.deleted_count+' would be deleted, '+d.failed_count+' would fail.', 'ok');
579+
setStatus('Dry run complete — nothing was deleted.');
580+
ids.forEach(id => {
581+
document.querySelectorAll('tr').forEach(row => {
582+
const eid = row.querySelector('.eid');
583+
if (eid && eid.textContent === id) {
584+
row.style.outline = '1.5px solid #f59e0b';
585+
row.style.opacity = '0.55';
586+
if (!row.querySelector('.badge-dry')) {
587+
const b = document.createElement('span');
588+
b.className = 'badge-dry';
589+
b.textContent = 'would delete';
590+
b.style.cssText = 'background:#78350f;color:#fcd34d;border-radius:4px;padding:1px 6px;font-size:10px;margin-left:5px;';
591+
eid.after(b);
592+
}
593+
}
594+
});
595+
});
596+
setTimeout(() => {
597+
document.querySelectorAll('tr').forEach(row => {
598+
row.style.outline = '';
599+
row.style.opacity = '';
600+
row.querySelectorAll('.badge-dry').forEach(b => b.remove());
601+
});
602+
}, 6000);
603+
} catch(err) {
604+
addLog('Dry run error: '+err.message, 'err');
605+
setStatus('Dry run failed.', 'err');
606+
}
607+
}
608+
542609
function showDiff() {
543610
const banner = document.getElementById('diff-banner');
544611
if (!previousOrphans.length) { banner.classList.remove('show'); return; }

custom_components/orphan_cleaner/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@
1313
"iot_class": "local_push",
1414
"issue_tracker": "https://github.com/Franz646/orphan-cleaner/issues",
1515
"requirements": [],
16-
"version": "2.4.1"
16+
"version": "2.4.2"
1717
}

0 commit comments

Comments
 (0)