Overview
+
+ Cannot reach server
+
+
@@ -404,9 +446,11 @@
document.getElementById('empty').style.display = sess.length ? 'none' : 'block';
// update live chip
- const nRun = allSessions.filter(s => s.status === 'running').length;
- document.getElementById('live-label').textContent =
- nRun ? `${nRun} running` : 'Live';
+ if (!connState.hasError) {
+ const nRun = allSessions.filter(s => s.status === 'running').length;
+ document.getElementById('live-label').textContent =
+ nRun ? `${nRun} running` : 'Live';
+ }
const nodes = sess.map(s => ({
id: s.id,
@@ -577,10 +621,59 @@
.attr('fill', 'none').attr('stroke', '#6366f1').attr('stroke-width', 1.5);
}
+// ── connection state ──────────────────────────────────────────────────
+const connState = {
+ metricsError: null,
+ sessionsError: null,
+ get hasError() { return Boolean(this.metricsError || this.sessionsError); },
+ get errorMessage() { return this.metricsError || this.sessionsError || ''; }
+};
+
+function updateConnUI() {
+ const chip = document.getElementById('live-chip');
+ const label = document.getElementById('live-label');
+ const banner = document.getElementById('conn-banner');
+ const bannerMsg = document.getElementById('conn-banner-msg');
+
+ if (connState.hasError) {
+ if (chip) {
+ chip.classList.add('disconnected');
+ chip.title = connState.errorMessage;
+ }
+ if (label) label.textContent = 'Disconnected';
+ if (banner) banner.style.display = 'flex';
+ if (bannerMsg) bannerMsg.textContent = connState.errorMessage;
+ } else {
+ if (chip) {
+ chip.classList.remove('disconnected');
+ chip.title = '';
+ }
+ if (banner) banner.style.display = 'none';
+ const nRun = allSessions.filter(s => s.status === 'running').length;
+ if (label) label.textContent = nRun ? `${nRun} running` : 'Live';
+ }
+}
+
+function setEndpointStatus(endpoint, err) {
+ if (endpoint === 'metrics') {
+ connState.metricsError = err;
+ } else if (endpoint === 'sessions') {
+ connState.sessionsError = err;
+ }
+ updateConnUI();
+}
+
+async function refreshAll() {
+ await Promise.all([loadSessions(), loadMetrics()]);
+}
+
// ── sidebar metrics ───────────────────────────────────────────────────
async function loadMetrics() {
try {
const r = await fetch(BASE + '/api/v1/metrics?period=24h');
+ if (!r.ok) {
+ throw new Error(`Metrics request failed (HTTP ${r.status})`);
+ }
const d = await r.json();
document.getElementById('m-active').textContent = d.active_sessions ?? '—';
document.getElementById('m-total').textContent = d.sessions_count ?? '—';
@@ -602,17 +695,39 @@
`;
}).join('') || '
No model data yet
';
- } catch (_) {}
+
+ setEndpointStatus('metrics', null);
+ } catch (err) {
+ const msg = err && err.message ? err.message : 'Cannot reach server';
+ console.error('loadMetrics error:', err);
+ setEndpointStatus('metrics', msg);
+ }
}
// ── sessions ──────────────────────────────────────────────────────────
async function loadSessions() {
try {
const r = await fetch(BASE + '/api/v1/sessions?limit=80');
+ if (!r.ok) {
+ throw new Error(`Sessions request failed (HTTP ${r.status})`);
+ }
const d = await r.json();
allSessions = d.sessions || [];
+ setEndpointStatus('sessions', null);
render();
- } catch (_) {}
+ } catch (err) {
+ const msg = err && err.message ? err.message : 'Cannot reach server';
+ console.error('loadSessions error:', err);
+ setEndpointStatus('sessions', msg);
+ }
+}
+
+// ── retry button ──────────────────────────────────────────────────────
+const retryBtn = document.getElementById('conn-retry-btn');
+if (retryBtn) {
+ retryBtn.addEventListener('click', () => {
+ refreshAll();
+ });
}
// ── filter chips ──────────────────────────────────────────────────────
@@ -629,14 +744,14 @@
const cdEl = document.getElementById('hdr-cd');
setInterval(() => {
countdown--;
- if (countdown <= 0) { countdown = 30; loadSessions(); loadMetrics(); }
+ if (countdown <= 0) { countdown = 30; refreshAll(); }
cdEl.textContent = '↻ ' + countdown + 's';
}, 1000);
// ── keyboard ──────────────────────────────────────────────────────────
document.addEventListener('keydown', e => {
if (e.target.tagName === 'INPUT') return;
- if (e.key === 'r' || e.key === 'R') { countdown = 1; }
+ if (e.key === 'r' || e.key === 'R') { countdown = 1; refreshAll(); }
if (e.key === 'Escape') { window.__unpinTip(); }
if (e.key === 'f' || e.key === 'F') {
svg.transition().duration(420).call(
@@ -646,8 +761,7 @@
});
// ── boot ──────────────────────────────────────────────────────────────
-loadSessions();
-loadMetrics();
+refreshAll();
}());