fix: diagnostic toggle hint, link_stats schema, pagination UX, rate-limit feedback
Lint / Python (flake8) (push) Successful in 46s
Lint / JS (eslint) (push) Successful in 8s
Security / Python Security (bandit) (push) Successful in 41s
Test / Python Tests (pytest) (push) Successful in 49s
Lint / Notify on failure (push) Has been skipped
Lint / Deploy (push) Successful in 3s
Lint / Python (flake8) (push) Successful in 46s
Lint / JS (eslint) (push) Successful in 8s
Security / Python Security (bandit) (push) Successful in 41s
Test / Python Tests (pytest) (push) Successful in 49s
Lint / Notify on failure (push) Has been skipped
Lint / Deploy (push) Successful in 3s
- inspector.html: collapsible section hint text now toggles between [expand]/[collapse] when clicked
- inspector.html: timeout and connection-loss during diagnostic poll now show a Retry button instead of a dead end
- inspector.html: 429 rate-limit response shows a clear human-readable message instead of generic error
- app.py: empty link_stats fallback now includes unifi_switches:{} for schema consistency with real data shape
- index.html: pagination overflow notice now says "export all as JSON" (opens in new tab) instead of misleadingly linking to raw API as navigation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -291,7 +291,7 @@ def api_links():
|
|||||||
return jsonify(json.loads(raw))
|
return jsonify(json.loads(raw))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f'Failed to parse link_stats JSON: {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')
|
@app.route('/api/events')
|
||||||
|
|||||||
@@ -92,7 +92,7 @@
|
|||||||
<div id="events-table-wrap">
|
<div id="events-table-wrap">
|
||||||
{% if events %}
|
{% if events %}
|
||||||
{% if total_active is defined and total_active > events|length %}
|
{% if total_active is defined and total_active > events|length %}
|
||||||
<div class="pagination-notice">Showing {{ events|length }} of {{ total_active }} active alerts — <a href="/api/events?limit=1000">view all via API</a></div>
|
<div class="pagination-notice">Showing {{ events|length }} of {{ total_active }} active alerts — use the search box to filter, or <a href="/api/events?limit=1000" target="_blank" rel="noopener">export all as JSON</a></div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="lt-table-wrap">
|
<div class="lt-table-wrap">
|
||||||
<table class="lt-table" id="events-table">
|
<table class="lt-table" id="events-table">
|
||||||
|
|||||||
@@ -487,7 +487,13 @@ document.addEventListener('click', e => {
|
|||||||
if (diagBtn) { runDiagnostic(diagBtn.dataset.sw, parseInt(diagBtn.dataset.idx, 10)); return; }
|
if (diagBtn) { runDiagnostic(diagBtn.dataset.sw, parseInt(diagBtn.dataset.idx, 10)); return; }
|
||||||
|
|
||||||
const toggleDiag = e.target.closest('[data-action="toggle-diag"]');
|
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 ─────────────────────────────────────────────────
|
// ── Link Diagnostics ─────────────────────────────────────────────────
|
||||||
@@ -510,7 +516,10 @@ function runDiagnostic(swName, portIdx) {
|
|||||||
pollDiagnostic(resp.job_id, statusEl, resultsEl);
|
pollDiagnostic(resp.job_id, statusEl, resultsEl);
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.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++;
|
attempts++;
|
||||||
if (attempts > 120) { // 2min timeout
|
if (attempts > 120) { // 2min timeout
|
||||||
clearInterval(_diagPollTimer);
|
clearInterval(_diagPollTimer);
|
||||||
statusEl.textContent = 'Timed out waiting for results.';
|
_diagPollTimer = null;
|
||||||
|
statusEl.innerHTML = 'Timed out waiting for results. '
|
||||||
|
+ '<button class="lt-btn lt-btn-ghost lt-btn-sm" id="diag-retry-btn">Retry</button>';
|
||||||
|
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;
|
return;
|
||||||
}
|
}
|
||||||
lt.api.get(`/api/diagnose/${jobId}`)
|
lt.api.get(`/api/diagnose/${jobId}`)
|
||||||
@@ -535,7 +550,12 @@ function pollDiagnostic(jobId, statusEl, resultsEl) {
|
|||||||
.catch(() => {
|
.catch(() => {
|
||||||
clearInterval(_diagPollTimer);
|
clearInterval(_diagPollTimer);
|
||||||
_diagPollTimer = null;
|
_diagPollTimer = null;
|
||||||
statusEl.textContent = 'Error: lost connection while collecting diagnostics.';
|
statusEl.innerHTML = 'Error: lost connection while collecting diagnostics. '
|
||||||
|
+ '<button class="lt-btn lt-btn-ghost lt-btn-sm" id="diag-retry-btn">Retry</button>';
|
||||||
|
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);
|
}, 2000);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user