|
|
|
@@ -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,8 +338,9 @@ function renderPerformance() {
|
|
|
|
|
$("#performance-sparkline").innerHTML = points.length ? `${gridLines}<path d="${areaPath}" fill="var(--green)" opacity="0.12" stroke="none" /><path d="${smoothLine}" fill="none" stroke="var(--green)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />` : "";
|
|
|
|
|
if (!points.length) $("#performance-sparkline-labels").innerHTML = '<span class="axis-label" style="left:0;width:100%;top:45%;text-align:center">No request data for this window yet.</span>';
|
|
|
|
|
const routes = data.routes || [];
|
|
|
|
|
const countCell = (count, errors, breakdown) => { if (!errors) return `${count.toLocaleString()}`; if (!breakdown?.length) return `${count.toLocaleString()} <span class="count-divider">·</span> <span class="http-status bad">${errors.toLocaleString()}</span>`; const rows = breakdown.map(item => `<div class="error-breakdown-row"><span>${escapeHtml(item.status)}</span><span>${item.count.toLocaleString()}</span></div>`).join(""); return `${count.toLocaleString()} <span class="count-divider">·</span> <details class="error-breakdown"><summary class="http-status bad">${errors.toLocaleString()}</summary><div class="error-breakdown-body">${rows}</div></details>`; };
|
|
|
|
|
$("#performance-rows").innerHTML = routes.length ? routes.map(route => `<tr class="${selected && route.host === selected ? "row-highlight" : ""}"><td title="${escapeHtml(route.host)}">${escapeHtml(route.host)}</td><td>${countCell(route.hourRequests, route.hourErrors)}</td><td>${countCell(route.dayRequests, route.dayErrors, route.errorBreakdown)}</td><td>${route.dayAvgMs == null ? "—" : `${route.dayAvgMs} ms`}</td></tr>`).join("") : '<tr><td colspan="4" class="quiet-state">No requests have been logged yet.</td></tr>';
|
|
|
|
|
state.performanceErrorBreakdowns = {};
|
|
|
|
|
const countCell = (count, errors, breakdown, host) => { if (!errors) return `${count.toLocaleString()}`; if (!breakdown?.length) return `${count.toLocaleString()} <span class="count-divider">·</span> <span class="http-status bad">${errors.toLocaleString()}</span>`; state.performanceErrorBreakdowns[host] = { total: errors, breakdown }; return `${count.toLocaleString()} <span class="count-divider">·</span> <button type="button" class="http-status bad count-link-button" data-error-host="${escapeHtml(host)}">${errors.toLocaleString()}</button>`; };
|
|
|
|
|
$("#performance-rows").innerHTML = routes.length ? routes.map(route => `<tr class="${selected && route.host === selected ? "row-highlight" : ""}"><td title="${escapeHtml(route.host)}">${escapeHtml(route.host)}</td><td>${countCell(route.hourRequests, route.hourErrors)}</td><td>${countCell(route.dayRequests, route.dayErrors, route.errorBreakdown, route.host)}</td><td>${route.dayAvgMs == null ? "—" : `${route.dayAvgMs} ms`}</td></tr>`).join("") : '<tr><td colspan="4" class="quiet-state">No requests have been logged yet.</td></tr>';
|
|
|
|
|
if (selected) $(`#performance-rows tr.row-highlight`)?.scrollIntoView({ block: "nearest" });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -513,6 +514,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 => `<div class="error-breakdown-row"><span>${escapeHtml(item.status)}</span><span>${item.count.toLocaleString()}</span></div>`).join(""); dialog.innerHTML = `<form method="dialog" class="dialog-card compact"><div class="dialog-heading"><div><p class="eyebrow">Performance · Last 24h</p><h2>${escapeHtml(host)}</h2></div></div><p class="muted">${total.toLocaleString()} error response${total === 1 ? "" : "s"} in the last 24 hours, by status code.</p><div class="error-breakdown-list">${rows}</div><div class="dialog-actions"><button value="cancel" class="button secondary">Close</button></div></form>`; 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);
|
|
|
|
|