diff --git a/package.json b/package.json index 1897f07..bd74dfe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "site-gateway", - "version": "0.11.115", + "version": "0.11.116", "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 cbe1246..21c47d6 100644 --- a/src/public/app.js +++ b/src/public/app.js @@ -8,7 +8,7 @@ // --- Shared DOM shortcut and app state ---------------------------------------- const $ = selector => document.querySelector(selector); -const state = { sites: [], proxies: [], redirects: [], streams: [], accessLists: [], groups: [], backups: [], settings: null, dashboard: null, certificates: null, readiness: null, logs: null, users: [], user: null, config: null, view: "overview", loaded: false, pendingDelete: null, pendingReplace: null, editing: null, iconTarget: null, passwordTarget: null, healthTimer: null, updateCheckTimer: null, loadedVersion: null, updateAvailable: false }; +const state = { sites: [], proxies: [], redirects: [], streams: [], accessLists: [], groups: [], backups: [], settings: null, dashboard: null, certificates: null, readiness: null, logs: null, users: [], user: null, config: null, view: "overview", loaded: false, pendingDelete: null, pendingReplace: null, editing: null, iconTarget: null, passwordTarget: null, healthTimer: null, updateCheckTimer: null, loadedVersion: null, updateAvailable: false, performanceErrorBreakdowns: {} }; // One-time DOM patches: move the Access List field into the create/settings // forms (features.js owns the Access List data, this file owns these forms). @@ -338,7 +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, host) => { if (!errors) return `${count.toLocaleString()}`; if (!breakdown?.length) return `${count.toLocaleString()} · ${errors.toLocaleString()}`; return `${count.toLocaleString()} · `; }; + state.performanceErrorBreakdowns = {}; + const countCell = (count, errors, breakdown, host) => { if (!errors) return `${count.toLocaleString()}`; if (!breakdown?.length) return `${count.toLocaleString()} · ${errors.toLocaleString()}`; state.performanceErrorBreakdowns[host] = { total: errors, breakdown }; 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" }); } @@ -515,8 +516,8 @@ $("#performance-host").addEventListener("change", () => loadFeatureView().catch( $("#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 = `

Performance · Last 24h

${escapeHtml(host)}

${total.toLocaleString()} error response${total === 1 ? "" : "s"} in the last 24 hours, by status code.

${rows}
`; 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); }); +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 = `

Performance · Last 24h

${escapeHtml(host)}

${total.toLocaleString()} error response${total === 1 ? "" : "s"} in the last 24 hours, by status code.

${rows}
`; dialog.showModal(); } +$("#performance-rows").addEventListener("click", event => { const button = event.target.closest("[data-error-host]"); if (!button) return; const entry = state.performanceErrorBreakdowns[button.dataset.errorHost]; if (!entry) return; showErrorBreakdown(button.dataset.errorHost, entry.total, entry.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 84b95f1..d8e205b 100644 --- a/src/public/index.html +++ b/src/public/index.html @@ -430,6 +430,6 @@
- +