Add LDAP avatar photos, UX polish, and TDS component upgrades
Lint / Python (flake8) (push) Successful in 1m13s
Lint / JS (eslint) (push) Successful in 9s
Security / Python Security (bandit) (push) Failing after 45s
Test / Python Tests (pytest) (push) Successful in 57s
Lint / Notify on failure (push) Has been skipped
Lint / Deploy (push) Successful in 5s

- Add /api/avatar endpoint querying lldap for user jpegPhoto; disk cache
  with sentinel pattern avoids repeat LDAP hits for users without photos
- Add ldap3 dependency and ldap config block to config.json
- Wire lt-avatar img overlay in base.html with capture-phase error
  fallback (lt-avatar-img-err) to reveal initials when image is absent
- Fix lt-avatar CSS shim: position:relative + absolute inset on img
  (local base.css was missing these; added to style.css)
- Replace all empty-state paragraphs with proper lt-empty-state markup
  (icon + title + body) across index, suppressions, inspector, app.js
- Add lt-spinner--cyan next to refresh button; shows during refreshAll()
- Replace inspector panel-section-title with lt-divider throughout
- Add data-tooltip attributes to SFP DOM metrics, TX/RX/Carrier/Duplex/
  Auto-neg/Error labels in links.html and inspector panel
- Add tooltips to events table column headers (Sev, First Seen, Failures)
- Fix links.html host panel timestamp (was reading sample.updated which
  is always undefined; now uses data.updated)
- Fix UniFi status text casing (Online→ONLINE to match server render)
- Remove dead topo-status-* class manipulation from updateTopology()
- Always render alert-count-badge; toggle display:none when count is 0
- Fix double UniFi get_devices() call in monitor.py run loop
- Fix chip-critical animation (was using green pulse-glow; now red)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-30 21:09:56 -04:00
parent 29267c9933
commit 9d6583a08a
11 changed files with 286 additions and 83 deletions
+29 -14
View File
@@ -6,19 +6,23 @@
<!-- ── Status bar ──────────────────────────────────────────────────── -->
<div class="status-bar">
<div class="status-chips">
{% if not daemon_ok %}
<span class="chip chip-critical">⚠ MONITOR OFFLINE</span>
{% endif %}
{% if summary.critical %}
<span class="chip chip-critical">● {{ summary.critical }} CRITICAL</span>
{% endif %}
{% if summary.warning %}
<span class="chip chip-warning">● {{ summary.warning }} WARNING</span>
{% endif %}
{% if not summary.critical and not summary.warning %}
{% if daemon_ok and not summary.critical and not summary.warning %}
<span class="chip chip-ok">✔ ALL SYSTEMS NOMINAL</span>
{% endif %}
</div>
<div class="status-meta">
<span class="last-check" id="last-check">{{ last_check }}</span>
<button class="lt-btn lt-btn-ghost lt-btn-sm" data-action="refresh">↻ REFRESH</button>
<span class="last-check" id="last-check" data-tooltip="Last monitor poll timestamp" data-tooltip-pos="bottom">{{ last_check }}</span>
<span class="lt-spinner lt-spinner--sm lt-spinner--cyan" id="refresh-spinner" style="display:none"></span>
<button class="lt-btn lt-btn-ghost lt-btn-sm" data-action="refresh" data-tooltip="Refresh all data now" data-tooltip-pos="bottom">↻ REFRESH</button>
</div>
</div>
@@ -46,7 +50,7 @@
<!-- WAN wire: cyan → green gradient, labeled -->
<div class="topo-vc">
<div class="topo-vc-wire" style="background:linear-gradient(to bottom,var(--cyan),var(--cyan)); opacity:.55;"></div>
<div class="topo-vc-wire" style="background:linear-gradient(to bottom,var(--cyan),rgba(0,212,255,.3)); opacity:.7;"></div>
<span class="topo-vc-label">WAN · 10G SFP+</span>
</div>
@@ -215,7 +219,11 @@
</div>
</div>
{% else %}
<p class="empty-state">No host data yet monitor is initializing.</p>
<div class="lt-empty-state lt-empty-state--sm">
<div class="lt-empty-state-icon"></div>
<div class="lt-empty-state-title">No host data yet</div>
<div class="lt-empty-state-body">The monitor daemon may still be starting up.</div>
</div>
{% endfor %}
</div>
</section>
@@ -272,9 +280,8 @@
<section class="g-section">
<div class="g-section-header">
<h2 class="g-section-title">Active Alerts</h2>
{% if summary.critical or summary.warning %}
<span class="g-section-badge">{{ (summary.critical or 0) + (summary.warning or 0) }}</span>
{% endif %}
<span class="g-section-badge" id="alert-count-badge"
{% if not summary.critical and not summary.warning %}style="display:none"{% endif %}>{{ (summary.critical or 0) + (summary.warning or 0) }}</span>
<div class="g-section-actions">
<div class="events-filter-bar">
<input type="search" class="lt-input lt-input-sm" id="events-search"
@@ -297,14 +304,14 @@
<caption class="lt-sr-only">Active network alerts</caption>
<thead>
<tr>
<th>Sev</th>
<th data-tooltip="Alert severity level" data-tooltip-pos="bottom">Sev</th>
<th>Type</th>
<th>Target</th>
<th>Detail</th>
<th>Description</th>
<th>First Seen</th>
<th>Last Seen</th>
<th>Failures</th>
<th data-tooltip="When this alert was first raised" data-tooltip-pos="bottom">First Seen</th>
<th data-tooltip="Most recent check failure" data-tooltip-pos="bottom">Last Seen</th>
<th data-tooltip="Consecutive check failures since first seen" data-tooltip-pos="bottom">Failures</th>
<th>Ticket</th>
<th>Actions</th>
</tr>
@@ -341,13 +348,21 @@
</tr>
{% endif %}
{% else %}
<tr><td colspan="10" class="empty-state">No active alerts ✔</td></tr>
<tr><td colspan="10">
<div class="lt-empty-state lt-empty-state--sm">
<div class="lt-empty-state-icon"></div>
<div class="lt-empty-state-title">No active alerts</div>
</div>
</td></tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="empty-state">No active alerts ✔</p>
<div class="lt-empty-state lt-empty-state--sm">
<div class="lt-empty-state-icon"></div>
<div class="lt-empty-state-title">No active alerts</div>
</div>
{% endif %}
</div>
</section>