Fix workflows scroll, add CPU/mem/disk sparklines during job runs

This commit is contained in:
zyphlar
2026-04-18 22:50:54 -07:00
parent 3e1b7aa089
commit 32cd6cba59
2 changed files with 148 additions and 0 deletions
+130
View File
@@ -109,6 +109,7 @@
/* Scrollable workflow area */
.workflows {
flex: 1;
min-height: 0;
overflow-y: auto;
padding: 14px 16px;
display: flex;
@@ -300,6 +301,37 @@
#status.error { background: #3a1e1e; color: #e07070; }
#status.info { background: #1e2e3a; color: #70b8e0; }
#sys-stats {
flex-shrink: 0;
display: none;
padding: 6px 14px 8px;
background: #141414;
border-bottom: 1px solid #2a2a2a;
}
.stat-row {
display: flex;
align-items: center;
gap: 8px;
margin-top: 4px;
}
.stat-label {
font-size: 10px;
font-weight: 700;
color: #666;
width: 30px;
letter-spacing: 0.3px;
}
.stat-val {
font-size: 10px;
color: #888;
width: 34px;
text-align: right;
font-family: 'Courier New', monospace;
}
#logs {
flex: 1;
overflow-y: auto;
@@ -479,6 +511,23 @@
<button id="cancelButton" class="btn-cancel" onclick="cancelJob()">Cancel</button>
</div>
<div id="status"></div>
<div id="sys-stats">
<div class="stat-row">
<span class="stat-label">CPU</span>
<canvas id="cpu-canvas" width="180" height="22"></canvas>
<span class="stat-val" id="cpu-val"></span>
</div>
<div class="stat-row">
<span class="stat-label">MEM</span>
<canvas id="mem-canvas" width="180" height="22"></canvas>
<span class="stat-val" id="mem-val"></span>
</div>
<div class="stat-row">
<span class="stat-label">DISK</span>
<canvas id="disk-canvas" width="180" height="22"></canvas>
<span class="stat-val" id="disk-val"></span>
</div>
</div>
<div id="logs"><span class="log-placeholder">Run a step to see output here.</span></div>
</div>
@@ -570,6 +619,7 @@
currentJobId = data.job_id;
if (logCheckInterval) clearInterval(logCheckInterval);
logCheckInterval = setInterval(checkJobStatus, 1000);
startStatsPolling();
})
.catch(err => {
showStatus(`Error: ${err.message}`, 'error');
@@ -593,6 +643,7 @@
enableButtons();
document.getElementById('cancelButton').classList.remove('active');
currentJobId = null;
stopStatsPolling();
} else {
showStatus(`Cancel failed: ${data.error}`, 'error');
}
@@ -613,6 +664,7 @@
enableButtons();
document.getElementById('cancelButton').classList.remove('active');
currentJobId = null;
stopStatsPolling();
}
})
.catch(err => console.error('Status check error:', err));
@@ -660,6 +712,84 @@
function enableButtons() {
document.querySelectorAll('.btn-run').forEach(b => b.disabled = false);
}
/* ── System stats sparklines ── */
const MAX_SAMPLES = 60;
const statHistory = { cpu: [], mem: [], disk: [] };
let statsInterval = null;
const sparkColors = { cpu: '#e07070', mem: '#70b8e0', disk: '#f0a830' };
function drawSparkline(canvasId, data, color) {
const canvas = document.getElementById(canvasId);
if (!canvas) return;
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
ctx.clearRect(0, 0, W, H);
// Background
ctx.fillStyle = '#1e1e1e';
ctx.fillRect(0, 0, W, H);
if (data.length < 2) return;
const step = W / (MAX_SAMPLES - 1);
const startX = W - (data.length - 1) * step;
// Fill
ctx.beginPath();
ctx.moveTo(startX, H);
data.forEach((v, i) => {
ctx.lineTo(startX + i * step, H - (v / 100) * (H - 2) - 1);
});
ctx.lineTo(startX + (data.length - 1) * step, H);
ctx.closePath();
ctx.fillStyle = color + '33';
ctx.fill();
// Line
ctx.beginPath();
data.forEach((v, i) => {
const x = startX + i * step;
const y = H - (v / 100) * (H - 2) - 1;
i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
});
ctx.strokeStyle = color;
ctx.lineWidth = 1.5;
ctx.stroke();
}
function pollSystemStats() {
fetch('/api/system-stats')
.then(r => r.json())
.then(data => {
if (data.error) return;
['cpu', 'mem', 'disk'].forEach(k => {
statHistory[k].push(data[k]);
if (statHistory[k].length > MAX_SAMPLES) statHistory[k].shift();
});
document.getElementById('cpu-val').textContent = data.cpu.toFixed(0) + '%';
document.getElementById('mem-val').textContent = data.mem.toFixed(0) + '%';
document.getElementById('disk-val').textContent = data.disk.toFixed(0) + '%';
drawSparkline('cpu-canvas', statHistory.cpu, sparkColors.cpu);
drawSparkline('mem-canvas', statHistory.mem, sparkColors.mem);
drawSparkline('disk-canvas', statHistory.disk, sparkColors.disk);
})
.catch(() => {});
}
function startStatsPolling() {
document.getElementById('sys-stats').style.display = 'block';
statHistory.cpu = []; statHistory.mem = []; statHistory.disk = [];
pollSystemStats();
statsInterval = setInterval(pollSystemStats, 2000);
}
function stopStatsPolling() {
clearInterval(statsInterval);
statsInterval = null;
document.getElementById('sys-stats').style.display = 'none';
}
</script>
</body>
</html>