feat: stacked area chart, framework breakdown on active sessions, tool bar visualization
This commit is contained in:
+41
-15
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user