Performance tab: sparkline axis, per-route card, fix table overflow/truncation
This commit is contained in:
+16
-4
@@ -245,11 +245,23 @@ function renderPerformance() {
|
||||
$("#performance-trend-title").textContent = `Requests · last 6 hours${selected ? ` · ${selected}` : ""}`;
|
||||
const points = data.trend || [];
|
||||
const max = Math.max(1, ...points.map(point => point.count));
|
||||
const stepX = points.length > 1 ? 600 / (points.length - 1) : 600;
|
||||
const path = points.map((point, index) => `${index === 0 ? "M" : "L"}${(index * stepX).toFixed(1)},${(120 - (point.count / max) * 110 - 4).toFixed(1)}`).join(" ");
|
||||
$("#performance-sparkline").innerHTML = points.length ? `<polyline points="${points.map((point, index) => `${(index * stepX).toFixed(1)},${(120 - (point.count / max) * 110 - 4).toFixed(1)}`).join(" ")}" fill="none" stroke="var(--green)" stroke-width="2" /><path d="${path} L${(600).toFixed(1)},120 L0,120 Z" fill="var(--green)" opacity="0.12" stroke="none" />` : "";
|
||||
const left = 34, right = 8, top = 10, bottom = 20, width = 600, height = 140;
|
||||
const plotWidth = width - left - right, plotHeight = height - top - bottom;
|
||||
const xAt = index => left + (points.length > 1 ? (index / (points.length - 1)) * plotWidth : plotWidth);
|
||||
const yAt = count => top + plotHeight - (count / max) * plotHeight;
|
||||
const gridLines = [0, 0.5, 1].map(fraction => {
|
||||
const y = (top + plotHeight * (1 - fraction)).toFixed(1);
|
||||
const value = Math.round(max * fraction);
|
||||
return `<line x1="${left}" y1="${y}" x2="${width - right}" y2="${y}" stroke="var(--line)" stroke-width="1" /><text x="${left - 8}" y="${y}" text-anchor="end" dominant-baseline="middle" fill="var(--muted)" font-size="10">${value}</text>`;
|
||||
}).join("");
|
||||
const firstPoint = points[0], lastPoint = points[points.length - 1];
|
||||
const timeLabels = points.length ? `<text x="${left}" y="${height - 4}" fill="var(--muted)" font-size="10">${escapeHtml(formatTime(firstPoint.at))}</text><text x="${width - right}" y="${height - 4}" text-anchor="end" fill="var(--muted)" font-size="10">${escapeHtml(formatTime(lastPoint.at))}</text>` : "";
|
||||
const linePoints = points.map((point, index) => `${xAt(index).toFixed(1)},${yAt(point.count).toFixed(1)}`).join(" ");
|
||||
const areaPath = points.length ? `M${xAt(0).toFixed(1)},${(top + plotHeight).toFixed(1)} L${linePoints.replaceAll(" ", " L")} L${xAt(points.length - 1).toFixed(1)},${(top + plotHeight).toFixed(1)} Z` : "";
|
||||
$("#performance-sparkline").setAttribute("viewBox", `0 0 ${width} ${height}`);
|
||||
$("#performance-sparkline").innerHTML = points.length ? `${gridLines}<path d="${areaPath}" fill="var(--green)" opacity="0.12" stroke="none" /><polyline points="${linePoints}" fill="none" stroke="var(--green)" stroke-width="2" />${timeLabels}` : '<text x="10" y="70" fill="var(--muted)" font-size="12">No request data for this window yet.</text>';
|
||||
const routes = data.routes || [];
|
||||
$("#performance-rows").innerHTML = routes.length ? routes.map(route => `<tr class="${selected && route.host === selected ? "row-highlight" : ""}"><td>${escapeHtml(route.host)}</td><td>${route.hourRequests}</td><td>${route.dayRequests}</td><td>${route.dayErrors ? `<span class="http-status bad">${route.dayErrors}</span>` : "0"}</td><td>${route.dayAvgMs == null ? "—" : `${route.dayAvgMs} ms`}</td></tr>`).join("") : '<tr><td colspan="5" class="quiet-state">No requests have been logged yet.</td></tr>';
|
||||
$("#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>${route.hourRequests}</td><td>${route.dayRequests}</td><td>${route.dayErrors ? `<span class="http-status bad">${route.dayErrors}</span>` : "0"}</td><td>${route.dayAvgMs == null ? "—" : `${route.dayAvgMs} ms`}</td></tr>`).join("") : '<tr><td colspan="5" class="quiet-state">No requests have been logged yet.</td></tr>';
|
||||
if (selected) $(`#performance-rows tr.row-highlight`)?.scrollIntoView({ block: "nearest" });
|
||||
}
|
||||
|
||||
|
||||
@@ -145,10 +145,13 @@
|
||||
<p id="performance-summary" class="muted feature-note">No requests recorded yet. <span id="performance-last-checked">Not checked yet.</span></p>
|
||||
<section class="dashboard-panel">
|
||||
<div class="panel-heading"><div><p class="eyebrow">Trend</p><h2 id="performance-trend-title">Requests · last 6 hours</h2></div></div>
|
||||
<svg id="performance-sparkline" class="performance-sparkline" viewBox="0 0 600 120" preserveAspectRatio="none" aria-label="Request volume trend"></svg>
|
||||
<svg id="performance-sparkline" class="performance-sparkline" viewBox="0 0 600 140" aria-label="Request volume trend"></svg>
|
||||
</section>
|
||||
<section class="dashboard-panel">
|
||||
<div class="panel-heading"><div><p class="eyebrow">Per-route</p><h2>Throughput by domain</h2></div></div>
|
||||
<p class="muted feature-note">Requests, error rate, and average response time for each configured domain.</p>
|
||||
<div class="table-wrap performance-table-wrap"><table class="performance-table"><thead><tr><th>Domain</th><th>Last hour</th><th>Last 24h</th><th>Errors (24h)</th><th>Avg. response</th></tr></thead><tbody id="performance-rows"></tbody></table></div>
|
||||
</section>
|
||||
<div class="diagnostic-section-heading"><p class="eyebrow">Per-route</p><h2>Throughput by domain</h2><p class="muted">Requests, error rate, and average response time for each configured domain.</p></div>
|
||||
<div class="table-wrap diagnostic-list"><table class="log-table performance-table"><thead><tr><th>Domain</th><th>Last hour</th><th>Last 24h</th><th>Errors (24h)</th><th>Avg. response</th></tr></thead><tbody id="performance-rows"></tbody></table></div>
|
||||
</section>
|
||||
<section id="users-view" class="feature-view hidden">
|
||||
<div class="admin-tabs"><button class="tab-active" data-admin-tab="users">Users</button><button data-admin-tab="defaults">Gateway defaults</button><button data-admin-tab="backups">Backup & restore</button><button data-admin-tab="security">Security & updates</button><button data-admin-tab="danger" class="danger-tab">Danger Zone</button></div>
|
||||
|
||||
@@ -259,3 +259,13 @@ select{appearance:none!important;-webkit-appearance:none!important;background-re
|
||||
#dashboard-view>.dashboard-columns #dashboard-jobs{margin:0;align-self:start}
|
||||
#dashboard-view>.dashboard-columns #dashboard-jobs{align-self:stretch;height:auto}
|
||||
#access-list.loading { visibility: hidden; }
|
||||
.table-wrap.performance-table-wrap{overflow-x:hidden;overflow-y:auto;max-height:min(52vh,620px)}
|
||||
.performance-table{width:100%;border-collapse:collapse;font-size:.82rem;table-layout:fixed}
|
||||
.performance-table th,.performance-table td{padding:13px 15px;text-align:left;border-top:1px solid var(--line);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.performance-table thead th{border-top:0;color:var(--muted);font-size:.7rem;text-transform:uppercase;letter-spacing:.06em;white-space:normal}
|
||||
.performance-table th:nth-child(1),.performance-table td:nth-child(1){width:32%}
|
||||
.performance-table th:nth-child(2),.performance-table td:nth-child(2){width:14%}
|
||||
.performance-table th:nth-child(3),.performance-table td:nth-child(3){width:14%}
|
||||
.performance-table th:nth-child(4),.performance-table td:nth-child(4){width:19%;text-align:center}
|
||||
.performance-table th:nth-child(5),.performance-table td:nth-child(5){width:21%}
|
||||
@media(max-width:760px){.performance-table th:nth-child(2),.performance-table td:nth-child(2),.performance-table th:nth-child(3),.performance-table td:nth-child(3){display:none}.performance-table th:nth-child(1),.performance-table td:nth-child(1){width:46%}.performance-table th:nth-child(4),.performance-table td:nth-child(4){width:27%}.performance-table th:nth-child(5),.performance-table td:nth-child(5){width:27%}}
|
||||
|
||||
Reference in New Issue
Block a user