diff --git a/app.py b/app.py
index 6997b32..0a900e0 100644
--- a/app.py
+++ b/app.py
@@ -291,7 +291,7 @@ def api_links():
return jsonify(json.loads(raw))
except Exception as e:
logger.error(f'Failed to parse link_stats JSON: {e}')
- return jsonify({'hosts': {}, 'updated': None})
+ return jsonify({'hosts': {}, 'unifi_switches': {}, 'updated': None})
@app.route('/api/events')
diff --git a/templates/index.html b/templates/index.html
index cdc7f6f..47ee69b 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -92,7 +92,7 @@
{% if events %}
{% if total_active is defined and total_active > events|length %}
-
+
{% endif %}
diff --git a/templates/inspector.html b/templates/inspector.html
index 7002e65..ed7e79e 100644
--- a/templates/inspector.html
+++ b/templates/inspector.html
@@ -487,7 +487,13 @@ document.addEventListener('click', e => {
if (diagBtn) { runDiagnostic(diagBtn.dataset.sw, parseInt(diagBtn.dataset.idx, 10)); return; }
const toggleDiag = e.target.closest('[data-action="toggle-diag"]');
- if (toggleDiag) { toggleDiag.parentElement.classList.toggle('diag-open'); return; }
+ if (toggleDiag) {
+ const section = toggleDiag.parentElement;
+ const nowOpen = section.classList.toggle('diag-open');
+ const hint = toggleDiag.querySelector('.diag-toggle-hint');
+ if (hint) hint.textContent = nowOpen ? '[collapse]' : '[expand]';
+ return;
+ }
});
// ── Link Diagnostics ─────────────────────────────────────────────────
@@ -510,7 +516,10 @@ function runDiagnostic(swName, portIdx) {
pollDiagnostic(resp.job_id, statusEl, resultsEl);
})
.catch(e => {
- statusEl.textContent = 'Error: ' + (e.message || 'Request failed');
+ const msg = (e && e.status === 429)
+ ? 'Rate limit reached — max 5 diagnostics per minute. Please wait.'
+ : 'Error: ' + (e && e.message || 'Request failed');
+ statusEl.textContent = msg;
});
}
@@ -520,7 +529,13 @@ function pollDiagnostic(jobId, statusEl, resultsEl) {
attempts++;
if (attempts > 120) { // 2min timeout
clearInterval(_diagPollTimer);
- statusEl.textContent = 'Timed out waiting for results.';
+ _diagPollTimer = null;
+ statusEl.innerHTML = 'Timed out waiting for results. '
+ + '';
+ document.getElementById('diag-retry-btn')?.addEventListener('click', () => {
+ const sel = document.querySelector('.switch-port-block.selected');
+ if (sel) runDiagnostic(sel.dataset.switch, parseInt(sel.dataset.portIdx));
+ });
return;
}
lt.api.get(`/api/diagnose/${jobId}`)
@@ -535,7 +550,12 @@ function pollDiagnostic(jobId, statusEl, resultsEl) {
.catch(() => {
clearInterval(_diagPollTimer);
_diagPollTimer = null;
- statusEl.textContent = 'Error: lost connection while collecting diagnostics.';
+ statusEl.innerHTML = 'Error: lost connection while collecting diagnostics. '
+ + '';
+ document.getElementById('diag-retry-btn')?.addEventListener('click', () => {
+ const sel = document.querySelector('.switch-port-block.selected');
+ if (sel) runDiagnostic(sel.dataset.switch, parseInt(sel.dataset.portIdx));
+ });
});
}, 2000);
}