a11y: aria-pressed for all pill groups, aria-label on search inputs and buttons
Lint / Python (flake8) (push) Successful in 46s
Lint / JS (eslint) (push) Successful in 10s
Security / Python Security (bandit) (push) Successful in 51s
Test / Python Tests (pytest) (push) Successful in 1m8s
Lint / Notify on failure (push) Has been skipped
Lint / Deploy (push) Successful in 4s

- Add role="group" + aria-label to duration-pills and sev-pills containers
- Add aria-pressed to severity filter, duration, and refresh-interval pills
- Keep aria-pressed in sync with JS (setDuration, applyRefreshPillUI, modal reset)
- Add aria-label to events-search, host-search, links-search inputs
- Add aria-label to host and UniFi device suppress buttons in templates
- Replace dynamic style color strings in links.html stat cards with TDS
  utility classes (lt-text-red/green/amber) via downCls/errCls variables

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-10 23:34:16 -04:00
parent a3c0818fef
commit 68f59c49a2
5 changed files with 68 additions and 45 deletions
+10 -4
View File
@@ -276,9 +276,12 @@ function openSuppressModal(type, name, detail) {
updateSuppressForm();
lt.modal.open('suppress-modal');
document.querySelectorAll('#suppress-modal .pill').forEach(p => p.classList.remove('active'));
document.querySelectorAll('#suppress-modal .pill').forEach(p => {
p.classList.remove('active');
p.setAttribute('aria-pressed', 'false');
});
const manualPill = document.querySelector('#suppress-modal .pill-manual');
if (manualPill) manualPill.classList.add('active');
if (manualPill) { manualPill.classList.add('active'); manualPill.setAttribute('aria-pressed', 'true'); }
const hint = document.getElementById('duration-hint');
if (hint) hint.textContent = 'Suppression will persist until manually removed.';
}
@@ -297,8 +300,11 @@ function updateSuppressForm() {
function setDuration(mins, el) {
document.getElementById('sup-expires').value = mins || '';
document.querySelectorAll('#suppress-modal .pill').forEach(p => p.classList.remove('active'));
if (el) el.classList.add('active');
document.querySelectorAll('#suppress-modal .pill').forEach(p => {
p.classList.remove('active');
p.setAttribute('aria-pressed', 'false');
});
if (el) { el.classList.add('active'); el.setAttribute('aria-pressed', 'true'); }
const hint = document.getElementById('duration-hint');
if (hint) {
if (mins) {
+15 -13
View File
@@ -231,12 +231,12 @@
</div>
<div class="lt-form-group lt-form-group--last">
<label class="lt-label">Duration</label>
<div class="duration-pills">
<button type="button" class="pill" data-duration="30">30 min</button>
<button type="button" class="pill" data-duration="60">1 hr</button>
<button type="button" class="pill" data-duration="240">4 hr</button>
<button type="button" class="pill" data-duration="480">8 hr</button>
<button type="button" class="pill pill-manual active" data-duration="">Manual &#x221E;</button>
<div class="duration-pills" role="group" aria-label="Select suppression duration">
<button type="button" class="pill" data-duration="30" aria-pressed="false">30 min</button>
<button type="button" class="pill" data-duration="60" aria-pressed="false">1 hr</button>
<button type="button" class="pill" data-duration="240" aria-pressed="false">4 hr</button>
<button type="button" class="pill" data-duration="480" aria-pressed="false">8 hr</button>
<button type="button" class="pill pill-manual active" data-duration="" aria-pressed="true">Manual &#x221E;</button>
</div>
<input type="hidden" id="sup-expires" name="expires_minutes" value="">
<div class="lt-field-hint" id="duration-hint">Persists until manually removed.</div>
@@ -286,12 +286,12 @@
<div class="lt-modal-body">
<div class="lt-form-group">
<label class="lt-label">Auto-refresh interval</label>
<div class="duration-pills" id="settings-refresh-pills">
<button type="button" class="pill" data-refresh-interval="15">15 s</button>
<button type="button" class="pill" data-refresh-interval="30">30 s</button>
<button type="button" class="pill" data-refresh-interval="60">1 min</button>
<button type="button" class="pill" data-refresh-interval="300">5 min</button>
<button type="button" class="pill" data-refresh-interval="0">Off</button>
<div class="duration-pills" id="settings-refresh-pills" role="group" aria-label="Select auto-refresh interval">
<button type="button" class="pill" data-refresh-interval="15" aria-pressed="false">15 s</button>
<button type="button" class="pill" data-refresh-interval="30" aria-pressed="false">30 s</button>
<button type="button" class="pill" data-refresh-interval="60" aria-pressed="false">1 min</button>
<button type="button" class="pill" data-refresh-interval="300" aria-pressed="false">5 min</button>
<button type="button" class="pill" data-refresh-interval="0" aria-pressed="false">Off</button>
</div>
<div class="lt-field-hint" id="settings-refresh-hint"></div>
</div>
@@ -381,7 +381,9 @@
function applyRefreshPillUI(interval) {
document.querySelectorAll('#settings-refresh-pills .pill').forEach(function(p) {
p.classList.toggle('active', parseInt(p.dataset.refreshInterval) === interval);
var isActive = parseInt(p.dataset.refreshInterval) === interval;
p.classList.toggle('active', isActive);
p.setAttribute('aria-pressed', isActive ? 'true' : 'false');
});
var hint = document.getElementById('settings-refresh-hint');
if (hint) {
+20 -11
View File
@@ -73,12 +73,13 @@
<div class="lt-toolbar-left">
<div class="lt-search">
<input type="search" class="lt-input lt-search-input" id="events-search"
placeholder="Filter by target, type, description…" autocomplete="off">
placeholder="Filter by target, type, description…" autocomplete="off"
aria-label="Filter active alerts">
</div>
<div class="sev-pills">
<button type="button" class="pill active" data-sev="">All</button>
<button type="button" class="pill" data-sev="critical">Critical</button>
<button type="button" class="pill" data-sev="warning">Warning</button>
<div class="sev-pills" role="group" aria-label="Filter by severity">
<button type="button" class="pill active" data-sev="" aria-pressed="true">All</button>
<button type="button" class="pill" data-sev="critical" aria-pressed="false">Critical</button>
<button type="button" class="pill" data-sev="warning" aria-pressed="false">Warning</button>
</div>
</div>
</div>
@@ -316,7 +317,7 @@
<div class="lt-toolbar-left">
<div class="lt-search">
<input type="search" class="lt-input lt-search-input lt-search-input--sm" id="host-search"
placeholder="Filter hosts…" autocomplete="off">
placeholder="Filter hosts…" autocomplete="off" aria-label="Filter hosts">
</div>
</div>
</div>
@@ -357,7 +358,7 @@
data-sup-type="host"
data-sup-name="{{ name }}"
data-sup-detail=""
title="Suppress alerts for this host">
aria-label="Suppress alerts for {{ name }}">
🔕 Suppress
</button>
<a href="{{ url_for('links_page') }}#{{ name }}"
@@ -416,7 +417,8 @@
<button class="lt-btn lt-btn-ghost lt-btn-sm btn-suppress"
data-sup-type="unifi_device"
data-sup-name="{{ d.name }}"
data-sup-detail="">
data-sup-detail=""
aria-label="Suppress alerts for {{ d.name }}">
🔕 Suppress
</button>
{% endif %}
@@ -540,8 +542,12 @@
document.querySelector('.sev-pills')?.addEventListener('click', e => {
const pill = e.target.closest('.pill[data-sev]');
if (!pill) return;
document.querySelectorAll('.sev-pills .pill').forEach(p => p.classList.remove('active'));
document.querySelectorAll('.sev-pills .pill').forEach(p => {
p.classList.remove('active');
p.setAttribute('aria-pressed', 'false');
});
pill.classList.add('active');
pill.setAttribute('aria-pressed', 'true');
_filterSev = pill.dataset.sev;
applyEventsFilter();
});
@@ -563,9 +569,12 @@
document.querySelectorAll('.lt-stat-card[data-stat-filter]').forEach(card => {
card.addEventListener('click', () => {
const sev = card.dataset.statFilter;
document.querySelectorAll('.sev-pills .pill').forEach(p => p.classList.remove('active'));
document.querySelectorAll('.sev-pills .pill').forEach(p => {
p.classList.remove('active');
p.setAttribute('aria-pressed', 'false');
});
const matchPill = document.querySelector(`.sev-pills .pill[data-sev="${sev}"]`);
if (matchPill) matchPill.classList.add('active');
if (matchPill) { matchPill.classList.add('active'); matchPill.setAttribute('aria-pressed', 'true'); }
_filterSev = sev;
applyEventsFilter();
document.getElementById('events-table-wrap')?.scrollIntoView({ behavior: 'smooth', block: 'start' });
+8 -7
View File
@@ -17,7 +17,8 @@
<div class="lt-toolbar-left">
<div class="lt-search">
<input type="search" class="lt-input lt-search-input" id="links-search"
placeholder="Filter by host or switch name…" autocomplete="off">
placeholder="Filter by host or switch name…" autocomplete="off"
aria-label="Filter by host or switch name">
</div>
</div>
<div class="lt-toolbar-right">
@@ -407,8 +408,8 @@ function buildLinkSummary(hosts, unifiSwitches) {
}
const allTotal = totalIfaces + swTotal;
const allDown = downIfaces + swDown;
const downColor = allDown > 0 ? 'var(--red)' : 'var(--green)';
const errColor = errIfaces > 0 ? 'var(--amber)' : 'var(--green)';
const downCls = allDown > 0 ? 'lt-text-red' : 'lt-text-green';
const errCls = errIfaces > 0 ? 'lt-text-amber' : 'lt-text-green';
const downCardCls = allDown > 0 ? ' lt-stat-card--alert' : '';
const poeCard = totalPoe > 0 ? `
<div class="lt-stat-card">
@@ -428,16 +429,16 @@ function buildLinkSummary(hosts, unifiSwitches) {
</div>
</div>
<div class="lt-stat-card${downCardCls}">
<span class="lt-stat-icon" aria-hidden="true" style="color:${downColor}">●</span>
<span class="lt-stat-icon ${downCls}" aria-hidden="true">●</span>
<div class="lt-stat-info">
<span class="lt-stat-value" style="color:${downColor}">${allDown}</span>
<span class="lt-stat-value ${downCls}">${allDown}</span>
<span class="lt-stat-label">Ports Down</span>
</div>
</div>
<div class="lt-stat-card">
<span class="lt-stat-icon" aria-hidden="true" style="color:${errColor}">▲</span>
<span class="lt-stat-icon ${errCls}" aria-hidden="true">▲</span>
<div class="lt-stat-info">
<span class="lt-stat-value" style="color:${errColor}">${errIfaces}</span>
<span class="lt-stat-value ${errCls}">${errIfaces}</span>
<span class="lt-stat-label">With Errors</span>
</div>
</div>
+15 -10
View File
@@ -58,12 +58,12 @@
<div class="form-row form-row-align">
<div class="lt-form-group">
<label class="lt-label">Duration</label>
<div class="duration-pills">
<button type="button" class="pill" data-duration="30">30 min</button>
<button type="button" class="pill" data-duration="60">1 hr</button>
<button type="button" class="pill" data-duration="240">4 hr</button>
<button type="button" class="pill" data-duration="480">8 hr</button>
<button type="button" class="pill pill-manual active" data-duration="">Manual ∞</button>
<div class="duration-pills" role="group" aria-label="Select suppression duration">
<button type="button" class="pill" data-duration="30" aria-pressed="false">30 min</button>
<button type="button" class="pill" data-duration="60" aria-pressed="false">1 hr</button>
<button type="button" class="pill" data-duration="240" aria-pressed="false">4 hr</button>
<button type="button" class="pill" data-duration="480" aria-pressed="false">8 hr</button>
<button type="button" class="pill pill-manual active" data-duration="" aria-pressed="true">Manual ∞</button>
</div>
<input type="hidden" id="s-expires" name="expires_minutes" value="">
<div class="lt-field-hint" id="s-dur-hint">Persists until manually removed.</div>
@@ -110,7 +110,8 @@
<td class="ts-cell">{{ s.created_at }}</td>
<td class="ts-cell">{% if s.expires_at %}{{ s.expires_at }}{% else %}<em>manual</em>{% endif %}</td>
<td>
<button class="lt-btn lt-btn-danger lt-btn-sm" data-action="remove-sup" data-sup-id="{{ s.id }}">Remove</button>
<button class="lt-btn lt-btn-danger lt-btn-sm" data-action="remove-sup" data-sup-id="{{ s.id }}"
aria-label="Remove suppression for {{ s.target_name or 'global' }}">Remove</button>
</td>
</tr>
{% endfor %}
@@ -221,8 +222,11 @@
function setDur(mins, el) {
document.getElementById('s-expires').value = mins || '';
document.querySelectorAll('.duration-pills .pill').forEach(p => p.classList.remove('active'));
if (el) el.classList.add('active');
document.querySelectorAll('.duration-pills .pill').forEach(p => {
p.classList.remove('active');
p.setAttribute('aria-pressed', 'false');
});
if (el) { el.classList.add('active'); el.setAttribute('aria-pressed', 'true'); }
const hint = document.getElementById('s-dur-hint');
if (mins) {
const h = Math.floor(mins/60), m = mins%60;
@@ -251,7 +255,8 @@
<td>${lt.escHtml(s.suppressed_by)}</td>
<td class="ts-cell">${lt.escHtml(s.created_at || '')}</td>
<td class="ts-cell">${s.expires_at ? lt.escHtml(s.expires_at) : '<em>manual</em>'}</td>
<td><button class="lt-btn lt-btn-danger lt-btn-sm" data-action="remove-sup" data-sup-id="${s.id}">Remove</button></td>
<td><button class="lt-btn lt-btn-danger lt-btn-sm" data-action="remove-sup" data-sup-id="${s.id}"
aria-label="Remove suppression for ${lt.escHtml(s.target_name || 'global')}">Remove</button></td>
</tr>`).join('');
wrap.innerHTML = `
<div class="lt-frame">