Audit quick wins: null guard, API error toasts, aria-labels on suppress buttons

- app.js: guard events array with || [] before .filter() to prevent crash on null
- app.js: show warning toast when /api/network or /api/status fail (was silent)
- app.js: add aria-label to all dynamically-generated suppress buttons
- index.html: add aria-label to server-rendered suppress buttons

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-29 17:50:00 -04:00
parent cabdbc24ad
commit c025da85c1
2 changed files with 8 additions and 6 deletions
+7 -5
View File
@@ -45,14 +45,14 @@ async function refreshAll() {
updateUnifiTable(net.unifi || []); updateUnifiTable(net.unifi || []);
updateTopology(net.hosts || {}); updateTopology(net.hosts || {});
} else { } else {
console.warn('Network API failed:', netResult.reason); showToast('Network data unavailable', 'warning');
} }
if (statusResult.status === 'fulfilled') { if (statusResult.status === 'fulfilled') {
const status = statusResult.value; const status = statusResult.value;
updateEventsTable(status.events || [], status.total_active); updateEventsTable(status.events || [], status.total_active);
updateStatusBar(status.summary || {}, status.last_check || '', status.daemon_ok); updateStatusBar(status.summary || {}, status.last_check || '', status.daemon_ok);
} else { } else {
console.warn('Status API failed:', statusResult.reason); showToast('Status data unavailable', 'warning');
} }
} finally { } finally {
if (refreshBtn) refreshBtn.classList.remove('is-loading'); if (refreshBtn) refreshBtn.classList.remove('is-loading');
@@ -153,7 +153,8 @@ function updateUnifiTable(devices) {
? `<button class="lt-btn lt-btn-ghost lt-btn-sm btn-suppress" ? `<button class="lt-btn lt-btn-ghost lt-btn-sm btn-suppress"
data-sup-type="unifi_device" data-sup-type="unifi_device"
data-sup-name="${lt.escHtml(d.name)}" data-sup-name="${lt.escHtml(d.name)}"
data-sup-detail="">🔕 Suppress</button>` data-sup-detail=""
aria-label="Suppress alerts for ${lt.escHtml(d.name)}">🔕 Suppress</button>`
: ''; : '';
return ` return `
<tr class="${statusClass}"> <tr class="${statusClass}">
@@ -171,7 +172,7 @@ function updateEventsTable(events, totalActive) {
const wrap = document.getElementById('events-table-wrap'); const wrap = document.getElementById('events-table-wrap');
if (!wrap) return; if (!wrap) return;
const active = events.filter(e => e.severity !== 'info'); const active = (events || []).filter(e => e.severity !== 'info');
if (!active.length) { if (!active.length) {
wrap.innerHTML = '<p class="empty-state">No active alerts ✔</p>'; wrap.innerHTML = '<p class="empty-state">No active alerts ✔</p>';
return; return;
@@ -207,7 +208,8 @@ function updateEventsTable(events, totalActive) {
<button class="lt-btn lt-btn-ghost lt-btn-sm btn-suppress" <button class="lt-btn lt-btn-ghost lt-btn-sm btn-suppress"
data-sup-type="${lt.escHtml(supType)}" data-sup-type="${lt.escHtml(supType)}"
data-sup-name="${lt.escHtml(e.target_name)}" data-sup-name="${lt.escHtml(e.target_name)}"
data-sup-detail="${lt.escHtml(e.target_detail||'')}">🔕</button> data-sup-detail="${lt.escHtml(e.target_detail||'')}"
aria-label="Suppress alert for ${lt.escHtml(e.target_name)}">🔕</button>
</td> </td>
</tr>`; </tr>`;
}).join(''); }).join('');
+1 -1
View File
@@ -336,7 +336,7 @@
data-sup-type="{{ 'unifi_device' if e.event_type == 'unifi_device_offline' else 'interface' if e.event_type == 'interface_down' else 'host' }}" data-sup-type="{{ 'unifi_device' if e.event_type == 'unifi_device_offline' else 'interface' if e.event_type == 'interface_down' else 'host' }}"
data-sup-name="{{ e.target_name }}" data-sup-name="{{ e.target_name }}"
data-sup-detail="{{ e.target_detail or '' }}" data-sup-detail="{{ e.target_detail or '' }}"
title="Suppress">🔕</button> title="Suppress" aria-label="Suppress alert for {{ e.target_name }}">🔕</button>
</td> </td>
</tr> </tr>
{% endif %} {% endif %}