// Overview tab - stats, pipelines, queue, activity, resources, cost, machines
import { store } from "../core/state";
import {
escapeHtml,
fmtNum,
formatDuration,
formatTime,
animateValue,
getBadgeClass,
getTypeShort,
} from "../core/helpers";
import { renderPipelineSVG } from "../components/charts/pipeline-rail";
import { renderCostTicker } from "../components/header";
import { switchTab } from "../core/router";
import * as api from "../core/api";
import { fetchPipelineDetail } from "./pipelines";
import { icon } from "../design/icons";
import type {
FleetState,
View,
PipelineInfo,
QueueItem,
EventItem,
} from "../types/api";
function renderStats(data: FleetState): void {
const d = data.daemon || ({} as any);
const m = data.metrics || ({} as any);
const firstRender = store.get("firstRender");
const statusEl = document.getElementById("stat-status");
const statusDot = document.getElementById("status-dot");
if (statusEl && statusDot) {
if (d.running) {
statusEl.textContent = "OPERATIONAL";
statusEl.className = "stat-value status-green";
statusDot.className = "pulse-dot operational";
} else {
statusEl.textContent = "OFFLINE";
statusEl.className = "stat-value status-rose";
statusDot.className = "pulse-dot offline";
}
}
const active = data.pipelines ? data.pipelines.length : 0;
const max = d.maxParallel || 0;
const activeEl = document.getElementById("stat-active");
if (activeEl) {
if (firstRender && active > 0) {
animateValue(activeEl, 0, active, 600, " / " + fmtNum(max));
} else {
activeEl.textContent = fmtNum(active) + " / " + fmtNum(max);
}
}
const barPct = max > 0 ? Math.min((active / max) * 100, 100) : 0;
const bar = document.getElementById("stat-active-bar");
if (bar) bar.style.width = barPct + "%";
const queued = data.queue ? data.queue.length : 0;
const queueEl = document.getElementById("stat-queue");
if (queueEl) {
queueEl.textContent = fmtNum(queued);
queueEl.className =
queued > 0 ? "stat-value status-amber" : "stat-value status-green";
}
const queueSub = document.getElementById("stat-queue-sub");
if (queueSub)
queueSub.textContent = queued === 1 ? "issue waiting" : "issues waiting";
const completed = m.completed ?? 0;
const completedEl = document.getElementById("stat-completed");
if (completedEl) {
if (firstRender && completed > 0) {
animateValue(completedEl, 0, completed, 800, "");
} else {
completedEl.textContent = fmtNum(completed);
}
}
const failed = m.failed ?? 0;
const failedSub = document.getElementById("stat-failed-sub");
if (failedSub) {
failedSub.textContent = fmtNum(failed) + " failed";
failedSub.className =
failed > 0 ? "stat-subtitle failed-some" : "stat-subtitle failed-none";
}
}
function renderOverviewPipelines(data: FleetState): void {
const container = document.getElementById("active-pipelines");
if (!container) return;
const firstRender = store.get("firstRender");
if (!data.pipelines || data.pipelines.length === 0) {
container.innerHTML = `
${icon("clock", 32)}
No active pipelines
`;
return;
}
let html = "";
for (let idx = 0; idx < data.pipelines.length; idx++) {
const p = data.pipelines[idx];
const maxIter = p.maxIterations || 20;
const curIter = p.iteration || 0;
const iterPct = maxIter > 0 ? Math.min((curIter / maxIter) * 100, 100) : 0;
const linesText =
p.linesWritten != null ? fmtNum(p.linesWritten) + " lines" : "";
const testsText =
p.testsPassing === true
? 'Tests \u2713'
: p.testsPassing === false
? 'Tests \u2717'
: "";
const metaParts = [linesText, testsText].filter(Boolean);
const animDelay = firstRender
? ` style="animation-delay:${idx * 0.05}s"`
: "";
html +=
`` +
`` +
`
${renderPipelineSVG(p)}
` +
`
` +
`
Iteration ${curIter}/${maxIter}` +
`
` +
`
${metaParts.join(" \u00b7 ")}
` +
(p.worktree
? `
WORKTREE: ${escapeHtml(p.worktree)}
`
: "") +
`
`;
}
container.innerHTML = html;
container.querySelectorAll(".pipeline-card").forEach((card) => {
card.addEventListener("click", () => {
const issue = card.getAttribute("data-issue");
if (issue) {
switchTab("pipelines");
fetchPipelineDetail(Number(issue));
}
});
});
}
function renderQueue(data: FleetState): void {
const container = document.getElementById("queue-list");
if (!container) return;
if (!data.queue || data.queue.length === 0) {
container.innerHTML = '';
return;
}
let html = "";
for (let i = 0; i < data.queue.length; i++) {
const q = data.queue[i];
const costEst =
q.estimated_cost != null
? ` ~$${q.estimated_cost.toFixed(2)}`
: "";
html +=
`` +
`#${q.issue}` +
`${escapeHtml(q.title)}` +
`${q.score != null ? q.score : "\u2014"}${costEst}
`;
html += ``;
if (q.factors) {
html += renderScoringFactors(
q.factors as unknown as Record
,
);
}
html += ``;
html += ` `;
}
container.innerHTML = html;
// Fetch detailed queue data for triage reasoning
let detailedData: Array> | null = null;
api
.fetchQueueDetailed()
.then((d) => {
detailedData = d.items || [];
})
.catch(() => {});
container.querySelectorAll(".queue-row").forEach((row) => {
row.addEventListener("click", () => {
const idx = row.getAttribute("data-queue-idx");
const detail = document.getElementById("queue-detail-" + idx);
if (!detail) return;
const isHidden = detail.style.display === "none";
detail.style.display = isHidden ? "" : "none";
if (isHidden && detailedData) {
const issue = row.getAttribute("data-issue");
const reasoningEl = document.getElementById("queue-reasoning-" + idx);
if (reasoningEl && !reasoningEl.innerHTML) {
const match = detailedData.find((d) => String(d.issue) === issue);
if (match) {
let rHtml = "";
if (match.triage_reason || match.reason)
rHtml += `Triage: ${escapeHtml(String(match.triage_reason || match.reason))}
`;
if (match.complexity_estimate)
rHtml += `Complexity: ${escapeHtml(String(match.complexity_estimate))}
`;
if (match.labels)
rHtml += `Labels: ${escapeHtml(String(match.labels))}
`;
if (match.age_hours)
rHtml += `Age: ${Number(match.age_hours).toFixed(1)}h
`;
reasoningEl.innerHTML = rHtml;
}
}
}
});
});
}
function renderScoringFactors(factors: Record): string {
const keys = [
"complexity",
"impact",
"priority",
"age",
"dependency",
"memory",
];
let html = '';
for (const k of keys) {
const val = Number(factors[k] ?? 0);
const pct = Math.max(0, Math.min(100, val));
html +=
`
` +
`
${escapeHtml(k)}` +
`
` +
`
${pct}`;
}
html += "
";
return html;
}
function renderOverviewActivity(data: FleetState): void {
const container = document.getElementById("activity-feed");
if (!container) return;
if (!data.events || data.events.length === 0) {
container.innerHTML =
'';
return;
}
const events = data.events.slice(-10).reverse();
let html = "";
for (const ev of events) {
const typeRaw = String(ev.type || "unknown");
const typeShort = getTypeShort(typeRaw);
const badgeClass = getBadgeClass(typeRaw);
const skip: Record = {
ts: true,
type: true,
timestamp: true,
};
const dparts: string[] = [];
for (const [key, val] of Object.entries(ev)) {
if (!skip[key]) dparts.push(key + "=" + val);
}
const detail = dparts.join(" ");
html +=
`` +
`${formatTime(ev.ts || ev.timestamp)}` +
`${escapeHtml(typeShort)}` +
`${escapeHtml(detail)}
`;
}
container.innerHTML = html;
}
function renderResources(data: FleetState): void {
const s = data.scale || ({} as any);
const m = data.metrics || ({} as any);
const active = data.pipelines ? data.pipelines.length : 0;
const cores = m.cpuCores || s.cpuCores || 0;
const maxByCpu = s.maxByCpu ?? null;
const maxByMem = s.maxByMem ?? null;
const maxByBudget = s.maxByBudget ?? null;
const cpuBar = document.getElementById("res-cpu-bar");
const cpuInfo = document.getElementById("res-cpu-info");
if (cpuBar && cpuInfo) {
if (maxByCpu != null) {
const pct = maxByCpu > 0 ? Math.min((active / maxByCpu) * 100, 100) : 0;
cpuBar.style.width = pct + "%";
cpuBar.className = "resource-bar-fill";
cpuInfo.textContent = maxByCpu + " max (" + cores + " cores)";
} else {
cpuBar.style.width = "0%";
cpuInfo.textContent = "\u2014";
}
}
const memBar = document.getElementById("res-mem-bar");
const memInfo = document.getElementById("res-mem-info");
if (memBar && memInfo) {
if (maxByMem != null) {
const pct = maxByMem > 0 ? Math.min((active / maxByMem) * 100, 100) : 0;
memBar.style.width = pct + "%";
memBar.className =
maxByMem <= 1
? "resource-bar-fill critical"
: maxByMem <= 2
? "resource-bar-fill warning"
: "resource-bar-fill";
const memGb = s.availMemGb != null ? s.availMemGb + "GB free" : "";
memInfo.textContent =
maxByMem + " max" + (memGb ? " (" + memGb + ")" : "");
} else {
memBar.style.width = "0%";
memInfo.textContent = "\u2014";
}
}
const budgetBar = document.getElementById("res-budget-bar");
const budgetInfo = document.getElementById("res-budget-info");
if (budgetBar && budgetInfo) {
if (maxByBudget != null) {
const pct =
maxByBudget > 0 ? Math.min((active / maxByBudget) * 100, 100) : 0;
budgetBar.style.width = pct + "%";
budgetBar.className = "resource-bar-fill";
budgetInfo.textContent = maxByBudget + " max";
} else {
budgetBar.style.width = "0%";
budgetInfo.textContent = "unlimited";
}
}
const constraintEl = document.getElementById("resource-constraint");
if (constraintEl) {
if (maxByMem != null && maxByCpu != null) {
const minFactor = Math.min(
maxByCpu || Infinity,
maxByMem || Infinity,
maxByBudget ?? Infinity,
);
if (minFactor === maxByMem && maxByMem <= 2) {
constraintEl.innerHTML =
'MEM-BOUND';
} else if (maxByBudget != null && minFactor === maxByBudget) {
constraintEl.innerHTML =
'BUDGET-BOUND';
} else {
constraintEl.innerHTML =
'NOMINAL';
}
} else {
constraintEl.innerHTML =
'NOMINAL';
}
}
}
function renderMachines(data: FleetState): void {
const section = document.getElementById("machines-section");
if (!section) return;
const machines = data.machines || [];
if (machines.length === 0) {
section.style.display = "none";
return;
}
section.style.display = "";
const grid = document.getElementById("machines-grid");
if (!grid) return;
let html = "";
for (const m of machines) {
const statusCls =
m.status === "online"
? "machine-online"
: m.status === "degraded"
? "machine-degraded"
: "machine-offline";
html +=
`` +
`` +
`
` +
`${escapeHtml(m.host)}` +
`${m.active_workers}/${m.max_workers} workers` +
`
`;
}
grid.innerHTML = html;
}
export const overviewView: View = {
init() {},
render(data: FleetState) {
renderStats(data);
renderOverviewPipelines(data);
renderQueue(data);
renderOverviewActivity(data);
renderResources(data);
renderCostTicker(data);
renderMachines(data);
},
destroy() {},
};