diff --git a/package.json b/package.json
index a44aad0..3c71f54 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "site-gateway",
- "version": "0.11.113",
+ "version": "0.11.114",
"private": true,
"description": "Site Gateway: simple self-hosted website publishing, reverse proxying, and automatic HTTPS.",
"type": "module",
diff --git a/src/public/app.js b/src/public/app.js
index 571d000..cbe1246 100644
--- a/src/public/app.js
+++ b/src/public/app.js
@@ -338,8 +338,8 @@ function renderPerformance() {
$("#performance-sparkline").innerHTML = points.length ? `${gridLines}` : "";
if (!points.length) $("#performance-sparkline-labels").innerHTML = 'No request data for this window yet.';
const routes = data.routes || [];
- const countCell = (count, errors, breakdown) => { if (!errors) return `${count.toLocaleString()}`; if (!breakdown?.length) return `${count.toLocaleString()} · ${errors.toLocaleString()}`; const rows = breakdown.map(item => `
${escapeHtml(item.status)}${item.count.toLocaleString()}
`).join(""); return `${count.toLocaleString()} · ${errors.toLocaleString()}
${rows}
`; };
- $("#performance-rows").innerHTML = routes.length ? routes.map(route => `| ${escapeHtml(route.host)} | ${countCell(route.hourRequests, route.hourErrors)} | ${countCell(route.dayRequests, route.dayErrors, route.errorBreakdown)} | ${route.dayAvgMs == null ? "—" : `${route.dayAvgMs} ms`} |
`).join("") : '| No requests have been logged yet. |
';
+ const countCell = (count, errors, breakdown, host) => { if (!errors) return `${count.toLocaleString()}`; if (!breakdown?.length) return `${count.toLocaleString()} · ${errors.toLocaleString()}`; return `${count.toLocaleString()} · `; };
+ $("#performance-rows").innerHTML = routes.length ? routes.map(route => `| ${escapeHtml(route.host)} | ${countCell(route.hourRequests, route.hourErrors)} | ${countCell(route.dayRequests, route.dayErrors, route.errorBreakdown, route.host)} | ${route.dayAvgMs == null ? "—" : `${route.dayAvgMs} ms`} |
`).join("") : '| No requests have been logged yet. |
';
if (selected) $(`#performance-rows tr.row-highlight`)?.scrollIntoView({ block: "nearest" });
}
@@ -513,6 +513,10 @@ $("#refresh-logs").addEventListener("click", () => loadFeatureView().catch(error
$("#log-host").addEventListener("change", () => loadFeatureView().catch(error => toast(error.message)));
$("#performance-host").addEventListener("change", () => loadFeatureView().catch(error => toast(error.message)));
$("#performance-range").addEventListener("change", () => loadFeatureView().catch(error => toast(error.message)));
+
+// --- Performance: themed error-breakdown popup, replacing the old hover tooltip ------------
+function showErrorBreakdown(host, total, breakdown) { let dialog = document.querySelector("#error-breakdown-dialog"); if (!dialog) { dialog = document.createElement("dialog"); dialog.id = "error-breakdown-dialog"; document.body.append(dialog); } const rows = breakdown.map(item => `${escapeHtml(item.status)}${item.count.toLocaleString()}
`).join(""); dialog.innerHTML = ``; dialog.showModal(); }
+$("#performance-rows").addEventListener("click", event => { const button = event.target.closest("[data-error-breakdown]"); if (!button) return; let breakdown = []; try { breakdown = JSON.parse(button.dataset.errorBreakdown || "[]"); } catch { breakdown = []; } showErrorBreakdown(button.dataset.errorHost || "", Number(button.dataset.errorTotal) || 0, breakdown); });
$("#log-status").addEventListener("change", renderLogs);
$("#event-severity").addEventListener("change", renderLogs);
$("#event-category").addEventListener("change", renderLogs);
diff --git a/src/public/index.html b/src/public/index.html
index b623e9a..e31c6d0 100644
--- a/src/public/index.html
+++ b/src/public/index.html
@@ -7,7 +7,7 @@
Site Gateway
-
+
-
+