feat: stacked area chart, framework breakdown on active sessions, tool bar visualization
This commit is contained in:
+37
-11
@@ -943,6 +943,7 @@
|
|||||||
<div class="summary-card">
|
<div class="summary-card">
|
||||||
<div class="summary-card-label">Active Sessions</div>
|
<div class="summary-card-label">Active Sessions</div>
|
||||||
<div class="summary-card-value" id="dash-active">-</div>
|
<div class="summary-card-value" id="dash-active">-</div>
|
||||||
|
<div class="summary-card-sub" id="dash-active-sub"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="summary-card">
|
<div class="summary-card">
|
||||||
<div class="summary-card-label">Runs Today</div>
|
<div class="summary-card-label">Runs Today</div>
|
||||||
@@ -1133,6 +1134,14 @@
|
|||||||
if (errEl) {
|
if (errEl) {
|
||||||
errEl.classList.toggle('has-errors', s.errors_today > 0);
|
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() {
|
async function loadTimeseries() {
|
||||||
@@ -1154,11 +1163,15 @@
|
|||||||
function buildChartData() {
|
function buildChartData() {
|
||||||
const ts = dashboardState.timeseries;
|
const ts = dashboardState.timeseries;
|
||||||
if (!ts || !ts.series || ts.series.length === 0) return null;
|
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 [
|
return [
|
||||||
ts.series.map(b => Math.floor(new Date(b.ts).getTime() / 1000)),
|
ts.series.map(b => Math.floor(new Date(b.ts).getTime() / 1000)),
|
||||||
ts.series.map(b => b.runs),
|
runs,
|
||||||
ts.series.map(b => b.tools),
|
tools,
|
||||||
ts.series.map(b => b.errors),
|
errors,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1211,22 +1224,26 @@
|
|||||||
{
|
{
|
||||||
label: 'Runs',
|
label: 'Runs',
|
||||||
stroke: '#34d399',
|
stroke: '#34d399',
|
||||||
width: 2,
|
width: 1.5,
|
||||||
fill: 'rgba(52, 211, 153, 0.08)',
|
fill: 'rgba(52, 211, 153, 0.15)',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Tools',
|
label: 'Tools',
|
||||||
stroke: '#22d3ee',
|
stroke: '#22d3ee',
|
||||||
width: 2,
|
width: 1.5,
|
||||||
fill: 'rgba(34, 211, 238, 0.08)',
|
fill: 'rgba(34, 211, 238, 0.15)',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Errors',
|
label: 'Errors',
|
||||||
stroke: '#f87171',
|
stroke: '#f87171',
|
||||||
width: 2,
|
width: 1.5,
|
||||||
fill: 'rgba(248, 113, 113, 0.08)',
|
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);
|
dashboardChart = new uPlot(opts, data, container);
|
||||||
@@ -1354,12 +1371,21 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
list.innerHTML = topTools.map(([name, count]) => `
|
const maxCount = topTools[0][1];
|
||||||
|
list.innerHTML = topTools.map(([name, count]) => {
|
||||||
|
const pct = maxCount > 0 ? (count / maxCount * 100) : 0;
|
||||||
|
return `
|
||||||
<li>
|
<li>
|
||||||
|
<div class="stat-list-header">
|
||||||
<span class="stat-list-name">${escapeHTML(name)}</span>
|
<span class="stat-list-name">${escapeHTML(name)}</span>
|
||||||
<span class="stat-list-count">${count}</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>
|
</li>
|
||||||
`).join('');
|
`;
|
||||||
|
}).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
route();
|
route();
|
||||||
|
|||||||
@@ -889,6 +889,27 @@ tr.clickable:hover td:first-child {
|
|||||||
border-radius: 4px;
|
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 {
|
.event-icon {
|
||||||
width: 18px;
|
width: 18px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
|
|||||||
Reference in New Issue
Block a user