@nockdev/hsa 1.2.5 → 1.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dashboard.html +65 -6
- package/logs.html +17 -2
- package/package.json +1 -1
package/dashboard.html
CHANGED
|
@@ -1157,6 +1157,33 @@
|
|
|
1157
1157
|
opacity: 0.4;
|
|
1158
1158
|
}
|
|
1159
1159
|
|
|
1160
|
+
/* ERROR BANNER */
|
|
1161
|
+
.error-banner {
|
|
1162
|
+
display: none;
|
|
1163
|
+
align-items: center;
|
|
1164
|
+
gap: 8px;
|
|
1165
|
+
padding: 8px 16px;
|
|
1166
|
+
background: var(--red-subtle);
|
|
1167
|
+
border-bottom: 1px solid rgba(248, 81, 73, 0.3);
|
|
1168
|
+
color: var(--red);
|
|
1169
|
+
font-size: 12px;
|
|
1170
|
+
font-family: var(--font-mono);
|
|
1171
|
+
grid-area: header;
|
|
1172
|
+
}
|
|
1173
|
+
.error-banner.visible {
|
|
1174
|
+
display: flex;
|
|
1175
|
+
}
|
|
1176
|
+
.error-banner .error-dismiss {
|
|
1177
|
+
margin-left: auto;
|
|
1178
|
+
cursor: pointer;
|
|
1179
|
+
opacity: 0.6;
|
|
1180
|
+
background: none;
|
|
1181
|
+
border: none;
|
|
1182
|
+
color: var(--red);
|
|
1183
|
+
font-size: 14px;
|
|
1184
|
+
}
|
|
1185
|
+
.error-banner .error-dismiss:hover { opacity: 1; }
|
|
1186
|
+
|
|
1160
1187
|
/* RESPONSIVE */
|
|
1161
1188
|
@media (max-width: 1200px) {
|
|
1162
1189
|
.kpi-grid {
|
|
@@ -1240,6 +1267,12 @@
|
|
|
1240
1267
|
</button>
|
|
1241
1268
|
</div>
|
|
1242
1269
|
</header>
|
|
1270
|
+
<!-- ERROR BANNER -->
|
|
1271
|
+
<div class="error-banner" id="errorBanner">
|
|
1272
|
+
<span>⚠</span>
|
|
1273
|
+
<span id="errorBannerMsg">Connection failed</span>
|
|
1274
|
+
<button class="error-dismiss" onclick="hideErrorBanner()">✕</button>
|
|
1275
|
+
</div>
|
|
1243
1276
|
|
|
1244
1277
|
<!-- CONTEXT SELECTOR BAR -->
|
|
1245
1278
|
<div class="context-bar">
|
|
@@ -1495,6 +1528,7 @@
|
|
|
1495
1528
|
const MAX_SSE_RETRIES = 5;
|
|
1496
1529
|
let sseReconnectTimer = null;
|
|
1497
1530
|
let fetchErrorCount = 0;
|
|
1531
|
+
let dashboardAbortCtrl = null;
|
|
1498
1532
|
const BASE_REFRESH_MS = 5000;
|
|
1499
1533
|
const MAX_REFRESH_MS = 30000;
|
|
1500
1534
|
|
|
@@ -1524,6 +1558,16 @@
|
|
|
1524
1558
|
function setStatus(status) {
|
|
1525
1559
|
const dot = document.getElementById("statusDot");
|
|
1526
1560
|
dot.className = "logo-dot " + (status === "connected" ? "" : status);
|
|
1561
|
+
if (status === "connected") hideErrorBanner();
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
function showErrorBanner(msg) {
|
|
1565
|
+
const banner = document.getElementById("errorBanner");
|
|
1566
|
+
document.getElementById("errorBannerMsg").textContent = msg || "Connection failed";
|
|
1567
|
+
banner.classList.add("visible");
|
|
1568
|
+
}
|
|
1569
|
+
function hideErrorBanner() {
|
|
1570
|
+
document.getElementById("errorBanner").classList.remove("visible");
|
|
1527
1571
|
}
|
|
1528
1572
|
|
|
1529
1573
|
function toggleAutoRefresh() {
|
|
@@ -1626,10 +1670,16 @@
|
|
|
1626
1670
|
FETCH & RENDER
|
|
1627
1671
|
================================================================ */
|
|
1628
1672
|
async function fetchDashboard() {
|
|
1673
|
+
// Cancel any in-flight request to prevent NS_BINDING_ABORTED
|
|
1674
|
+
if (dashboardAbortCtrl) dashboardAbortCtrl.abort();
|
|
1675
|
+
dashboardAbortCtrl = new AbortController();
|
|
1676
|
+
const ctrl = dashboardAbortCtrl;
|
|
1629
1677
|
try {
|
|
1630
1678
|
let url = endpoint + "/api/dashboard";
|
|
1631
1679
|
// Note: /api/dashboard doesn't support session filtering yet, handled client-side
|
|
1632
|
-
const
|
|
1680
|
+
const timeoutId = setTimeout(() => ctrl.abort(), 8000);
|
|
1681
|
+
const res = await fetch(url, { signal: ctrl.signal });
|
|
1682
|
+
clearTimeout(timeoutId);
|
|
1633
1683
|
if (!res.ok) throw new Error("HTTP " + res.status);
|
|
1634
1684
|
const data = await res.json();
|
|
1635
1685
|
setStatus("connected");
|
|
@@ -1639,10 +1689,13 @@
|
|
|
1639
1689
|
}
|
|
1640
1690
|
render(data);
|
|
1641
1691
|
} catch (err) {
|
|
1692
|
+
if (err.name === "AbortError") return; // Intentional cancel, ignore
|
|
1642
1693
|
setStatus("error");
|
|
1643
1694
|
fetchErrorCount++;
|
|
1644
1695
|
adjustRefreshInterval();
|
|
1645
|
-
|
|
1696
|
+
const msg = err.message || "Connection failed";
|
|
1697
|
+
console.warn("HSA fetch error:", msg);
|
|
1698
|
+
showErrorBanner(msg);
|
|
1646
1699
|
}
|
|
1647
1700
|
}
|
|
1648
1701
|
|
|
@@ -2247,7 +2300,7 @@
|
|
|
2247
2300
|
renderSidebar(allSessions);
|
|
2248
2301
|
populateContextSelectors(allSessions);
|
|
2249
2302
|
})
|
|
2250
|
-
.catch(() => {});
|
|
2303
|
+
.catch((err) => { console.warn("fetchSessions failed:", err.message || err); });
|
|
2251
2304
|
}
|
|
2252
2305
|
|
|
2253
2306
|
function connectSessionSSE() {
|
|
@@ -2406,9 +2459,15 @@
|
|
|
2406
2459
|
INIT
|
|
2407
2460
|
================================================================ */
|
|
2408
2461
|
document.getElementById("refreshToggle").classList.add("active");
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2462
|
+
// Fast health check first, then full data load
|
|
2463
|
+
(async () => {
|
|
2464
|
+
try {
|
|
2465
|
+
const res = await fetch(endpoint + "/api/health", { signal: AbortSignal.timeout(3000) });
|
|
2466
|
+
if (res.ok) { setStatus("connected"); hideErrorBanner(); }
|
|
2467
|
+
} catch { /* will fall through to connect() below */ }
|
|
2468
|
+
connect();
|
|
2469
|
+
startRefresh();
|
|
2470
|
+
})();
|
|
2412
2471
|
window.addEventListener("resize", drawCacheChart);
|
|
2413
2472
|
</script>
|
|
2414
2473
|
</body>
|
package/logs.html
CHANGED
|
@@ -628,6 +628,8 @@
|
|
|
628
628
|
let filterTimeout = null;
|
|
629
629
|
let allSessions = [];
|
|
630
630
|
let sseRetryMs = 1000;
|
|
631
|
+
let sseRetryCount = 0;
|
|
632
|
+
const MAX_SSE_RETRIES = 5;
|
|
631
633
|
let statsThrottleTimer = null;
|
|
632
634
|
|
|
633
635
|
// ── Init ──────────────────────────────────────────────────
|
|
@@ -651,7 +653,9 @@
|
|
|
651
653
|
|
|
652
654
|
setStatus("pending");
|
|
653
655
|
try {
|
|
654
|
-
const res = await fetch(baseUrl + "/api/logs?limit=200"
|
|
656
|
+
const res = await fetch(baseUrl + "/api/logs?limit=200", {
|
|
657
|
+
signal: AbortSignal.timeout(8000),
|
|
658
|
+
});
|
|
655
659
|
if (!res.ok) throw new Error(res.statusText);
|
|
656
660
|
const data = await res.json();
|
|
657
661
|
entries = data.entries || [];
|
|
@@ -758,6 +762,14 @@
|
|
|
758
762
|
eventSource.close();
|
|
759
763
|
eventSource = null;
|
|
760
764
|
}
|
|
765
|
+
sseRetryCount++;
|
|
766
|
+
if (sseRetryCount >= MAX_SSE_RETRIES) {
|
|
767
|
+
console.warn("SSE: Max retries reached, stopping log stream reconnect");
|
|
768
|
+
isStreaming = false;
|
|
769
|
+
document.getElementById("streamBtn").textContent = "▶ Stream";
|
|
770
|
+
document.getElementById("streamBtn").classList.remove("active");
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
761
773
|
// FE-01: Auto-reconnect with exponential backoff
|
|
762
774
|
setTimeout(() => {
|
|
763
775
|
if (isStreaming) startStream();
|
|
@@ -767,6 +779,7 @@
|
|
|
767
779
|
eventSource.onopen = () => {
|
|
768
780
|
setStatus("ok");
|
|
769
781
|
sseRetryMs = 1000;
|
|
782
|
+
sseRetryCount = 0;
|
|
770
783
|
};
|
|
771
784
|
isStreaming = true;
|
|
772
785
|
document.getElementById("streamBtn").textContent = "⏸ Pause";
|
|
@@ -804,7 +817,9 @@
|
|
|
804
817
|
document.getElementById("statErrors").textContent = s.errorCount;
|
|
805
818
|
document.getElementById("statAvg").textContent = s.avgDurationMs;
|
|
806
819
|
document.getElementById("statRate").textContent = s.callsPerMinute;
|
|
807
|
-
} catch {
|
|
820
|
+
} catch (err) {
|
|
821
|
+
console.warn("fetchStats error:", err.message || err);
|
|
822
|
+
}
|
|
808
823
|
}
|
|
809
824
|
|
|
810
825
|
// ── Filters ───────────────────────────────────────────────
|