feat: stacked area chart, framework breakdown on active sessions, tool bar visualization

This commit is contained in:
William Valentin
2026-03-14 11:34:15 -07:00
parent af127a45f1
commit ab7a83c50d
2 changed files with 62 additions and 15 deletions
+41 -15
View File
@@ -943,6 +943,7 @@
<div class="summary-card">
<div class="summary-card-label">Active Sessions</div>
<div class="summary-card-value" id="dash-active">-</div>
<div class="summary-card-sub" id="dash-active-sub"></div>
</div>
<div class="summary-card">
<div class="summary-card-label">Runs Today</div>
@@ -1133,6 +1134,14 @@
if (errEl) {
errEl.classList.toggle('has-errors', s.errors_today > 0);
}
const subEl = document.getElementById('dash-active-sub');
if (subEl && s.by_framework) {
const parts = Object.entries(s.by_framework)
.filter(([, v]) => v.runs > 0)
.map(([name, v]) => escapeHTML(name) + ' ' + v.runs);
subEl.textContent = parts.length > 0 ? parts.join(' / ') : '';
}
}
async function loadTimeseries() {
@@ -1154,11 +1163,15 @@
function buildChartData() {
const ts = dashboardState.timeseries;
if (!ts || !ts.series || ts.series.length === 0) return null;
// Stacked: errors on bottom, then tools, then runs on top
const errors = ts.series.map(b => b.errors);
const tools = ts.series.map((b, i) => b.tools + errors[i]);
const runs = ts.series.map((b, i) => b.runs + tools[i]);
return [
ts.series.map(b => Math.floor(new Date(b.ts).getTime() / 1000)),
ts.series.map(b => b.runs),
ts.series.map(b => b.tools),
ts.series.map(b => b.errors),
runs,
tools,
errors,
];
}
@@ -1211,22 +1224,26 @@
{
label: 'Runs',
stroke: '#34d399',
width: 2,
fill: 'rgba(52, 211, 153, 0.08)',
width: 1.5,
fill: 'rgba(52, 211, 153, 0.15)',
},
{
label: 'Tools',
stroke: '#22d3ee',
width: 2,
fill: 'rgba(34, 211, 238, 0.08)',
width: 1.5,
fill: 'rgba(34, 211, 238, 0.15)',
},
{
label: 'Errors',
stroke: '#f87171',
width: 2,
fill: 'rgba(248, 113, 113, 0.08)',
width: 1.5,
fill: 'rgba(248, 113, 113, 0.2)',
},
],
bands: [
{ series: [1, 2], fill: 'rgba(52, 211, 153, 0.15)' },
{ series: [2, 3], fill: 'rgba(34, 211, 238, 0.15)' },
],
};
dashboardChart = new uPlot(opts, data, container);
@@ -1354,12 +1371,21 @@
return;
}
list.innerHTML = topTools.map(([name, count]) => `
<li>
<span class="stat-list-name">${escapeHTML(name)}</span>
<span class="stat-list-count">${count}</span>
</li>
`).join('');
const maxCount = topTools[0][1];
list.innerHTML = topTools.map(([name, count]) => {
const pct = maxCount > 0 ? (count / maxCount * 100) : 0;
return `
<li>
<div class="stat-list-header">
<span class="stat-list-name">${escapeHTML(name)}</span>
<span class="stat-list-count">${count}</span>
</div>
<div class="stat-list-bar-track">
<div class="stat-list-bar-fill" style="width:${pct}%"></div>
</div>
</li>
`;
}).join('');
}
route();
+21
View File
@@ -889,6 +889,27 @@ tr.clickable:hover td:first-child {
border-radius: 4px;
}
.stat-list-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.stat-list-bar-track {
height: 3px;
background: var(--surface-2);
border-radius: 2px;
margin-top: 0.3rem;
overflow: hidden;
}
.stat-list-bar-fill {
height: 100%;
background: var(--accent);
border-radius: 2px;
transition: width 0.3s ease;
}
.event-icon {
width: 18px;
height: 18px;