Migrate inspector and links pages to TDS lt.* APIs
Lint / Python (flake8) (push) Failing after 1m22s
Lint / JS (eslint) (push) Successful in 10s
Security / Python Security (bandit) (push) Failing after 49s
Lint / Notify on failure (push) Has been cancelled
Lint / Deploy (push) Has been cancelled
Test / Python Tests (pytest) (push) Has been cancelled

- Add escHtml alias (lt.escHtml) to both pages so existing template strings work without touching 40+ call sites
- Replace raw fetch() with lt.api.get/post in loadInspector, loadLinks, runDiagnostic, pollDiagnostic
- Replace setInterval(load*, 60000) with lt.autoRefresh.start() for intelligent polling
- Add lt.toast.error() to catch blocks for user-visible error feedback

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-18 23:59:19 -04:00
parent c1c3905179
commit a17b1382bc
2 changed files with 19 additions and 31 deletions
+8 -17
View File
@@ -24,6 +24,8 @@
{% block scripts %} {% block scripts %}
<script> <script>
const escHtml = s => lt.escHtml(s);
// ── Switch layout config ───────────────────────────────────────────────── // ── Switch layout config ─────────────────────────────────────────────────
// keys match the model field returned by the UniFi API // keys match the model field returned by the UniFi API
// rows: array of rows, each row is an array of port_idx values // rows: array of rows, each row is an array of port_idx values
@@ -451,18 +453,17 @@ function renderInspector(data) {
// ── Fetch and render ───────────────────────────────────────────────────── // ── Fetch and render ─────────────────────────────────────────────────────
async function loadInspector() { async function loadInspector() {
try { try {
const resp = await fetch('/api/links'); const data = await lt.api.get('/api/links');
if (!resp.ok) throw new Error('API error');
const data = await resp.json();
renderInspector(data); renderInspector(data);
} catch (e) { } catch (e) {
document.getElementById('inspector-main').innerHTML = document.getElementById('inspector-main').innerHTML =
'<p class="empty-state">Failed to load inspector data.</p>'; '<p class="empty-state">Failed to load inspector data.</p>';
lt.toast.error('Failed to load inspector data');
} }
} }
loadInspector(); loadInspector();
setInterval(loadInspector, 60000); lt.autoRefresh.start(loadInspector, 60000);
// ── Link Diagnostics ───────────────────────────────────────────────── // ── Link Diagnostics ─────────────────────────────────────────────────
let _diagPollTimer = null; let _diagPollTimer = null;
@@ -478,22 +479,13 @@ function runDiagnostic(swName, portIdx) {
statusEl.textContent = 'Submitting to Pulse...'; statusEl.textContent = 'Submitting to Pulse...';
resultsEl.innerHTML = ''; resultsEl.innerHTML = '';
fetch('/api/diagnose', { lt.api.post('/api/diagnose', {switch_name: swName, port_idx: portIdx})
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({switch_name: swName, port_idx: portIdx}),
})
.then(r => r.json())
.then(resp => { .then(resp => {
if (resp.error) {
statusEl.textContent = 'Error: ' + resp.error;
return;
}
statusEl.textContent = 'Collecting diagnostics via Pulse...'; statusEl.textContent = 'Collecting diagnostics via Pulse...';
pollDiagnostic(resp.job_id, statusEl, resultsEl); pollDiagnostic(resp.job_id, statusEl, resultsEl);
}) })
.catch(e => { .catch(e => {
statusEl.textContent = 'Request failed: ' + e; statusEl.textContent = 'Error: ' + (e.message || 'Request failed');
}); });
} }
@@ -506,8 +498,7 @@ function pollDiagnostic(jobId, statusEl, resultsEl) {
statusEl.textContent = 'Timed out waiting for results.'; statusEl.textContent = 'Timed out waiting for results.';
return; return;
} }
fetch(`/api/diagnose/${jobId}`) lt.api.get(`/api/diagnose/${jobId}`)
.then(r => r.json())
.then(resp => { .then(resp => {
if (resp.status === 'done') { if (resp.status === 'done') {
clearInterval(_diagPollTimer); clearInterval(_diagPollTimer);
+5 -8
View File
@@ -20,6 +20,8 @@
{% block scripts %} {% block scripts %}
<script> <script>
const escHtml = s => lt.escHtml(s);
// ── Formatting helpers ──────────────────────────────────────────── // ── Formatting helpers ────────────────────────────────────────────
function fmtRate(bytesPerSec) { function fmtRate(bytesPerSec) {
if (bytesPerSec === null || bytesPerSec === undefined) return ''; if (bytesPerSec === null || bytesPerSec === undefined) return '';
@@ -480,13 +482,7 @@ function checkLinksStale(updatedStr) {
// ── Fetch + render ──────────────────────────────────────────────── // ── Fetch + render ────────────────────────────────────────────────
async function loadLinks() { async function loadLinks() {
try { try {
const resp = await fetch('/api/links'); const data = await lt.api.get('/api/links');
if (!resp.ok) {
document.getElementById('links-container').innerHTML =
'<div class="error-state">Failed to load link statistics.</div>';
return;
}
const data = await resp.json();
if (!data.hosts && !data.unifi_switches) { if (!data.hosts && !data.unifi_switches) {
document.getElementById('links-container').innerHTML = document.getElementById('links-container').innerHTML =
'<div class="link-no-data">No link data yet — monitor has not completed a full cycle.</div>'; '<div class="link-no-data">No link data yet — monitor has not completed a full cycle.</div>';
@@ -501,10 +497,11 @@ async function loadLinks() {
} catch (e) { } catch (e) {
document.getElementById('links-container').innerHTML = document.getElementById('links-container').innerHTML =
'<div class="error-state">Network error loading link statistics.</div>'; '<div class="error-state">Network error loading link statistics.</div>';
lt.toast.error('Failed to load link statistics');
} }
} }
loadLinks(); loadLinks();
setInterval(loadLinks, 60000); lt.autoRefresh.start(loadLinks, 60000);
</script> </script>
{% endblock %} {% endblock %}