Skip to content

Commit fb3f467

Browse files
authored
Merge PR #485: Polecat status dashboard
Polecats: status summary card
2 parents 9c3c54a + dbd334c commit fb3f467

1 file changed

Lines changed: 70 additions & 2 deletions

File tree

client/dashboard.js

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,8 @@ class Dashboard {
115115
<div class="dashboard-summary-title">Status</div>
116116
<div id="dashboard-status-summary" class="dashboard-summary-body">Loading…</div>
117117
</div>
118-
<div class="dashboard-summary-card">
119-
<div class="dashboard-summary-title">Telemetry</div>
118+
<div class="dashboard-summary-card">
119+
<div class="dashboard-summary-title">Telemetry</div>
120120
<div id="dashboard-telemetry-summary" class="dashboard-summary-body">Loading…</div>
121121
<div class="dashboard-summary-actions">
122122
<button class="dashboard-topbar-btn" id="dashboard-open-telemetry-details" title="View trends and histograms">📈 Details</button>
@@ -127,6 +127,13 @@ class Dashboard {
127127
<button class="dashboard-topbar-btn" id="dashboard-export-telemetry-json" title="Download telemetry JSON export">⬇ JSON</button>
128128
</div>
129129
</div>
130+
<div class="dashboard-summary-card">
131+
<div class="dashboard-summary-title">Polecats</div>
132+
<div id="dashboard-polecats-summary" class="dashboard-summary-body">Loading…</div>
133+
<div class="dashboard-summary-actions">
134+
<button class="dashboard-topbar-btn" id="dashboard-open-polecats-card" title="Open Polecats panel">🐾 Manage</button>
135+
</div>
136+
</div>
130137
<div class="dashboard-summary-card">
131138
<div class="dashboard-summary-title">Projects</div>
132139
<div id="dashboard-projects-summary" class="dashboard-summary-body">Loading…</div>
@@ -194,6 +201,7 @@ class Dashboard {
194201
async loadDashboardProcessSummary() {
195202
const statusEl = document.getElementById('dashboard-status-summary');
196203
const telemetryEl = document.getElementById('dashboard-telemetry-summary');
204+
const polecatsEl = document.getElementById('dashboard-polecats-summary');
197205
const projectsEl = document.getElementById('dashboard-projects-summary');
198206
const adviceEl = document.getElementById('dashboard-advice-summary');
199207
const readinessEl = document.getElementById('dashboard-readiness-summary');
@@ -209,6 +217,10 @@ class Dashboard {
209217
document.getElementById('dashboard-open-polecats')?.addEventListener('click', (e) => {
210218
e.preventDefault();
211219
this.showPolecatOverlay().catch(() => {});
220+
});
221+
document.getElementById('dashboard-open-polecats-card')?.addEventListener('click', (e) => {
222+
e.preventDefault();
223+
this.showPolecatOverlay().catch(() => {});
212224
});
213225
document.getElementById('dashboard-open-tests')?.addEventListener('click', (e) => {
214226
e.preventDefault();
@@ -271,6 +283,12 @@ class Dashboard {
271283
} catch {}
272284
});
273285

286+
try {
287+
this.updatePolecatSummary(polecatsEl);
288+
} catch {
289+
// ignore
290+
}
291+
274292
const escapeHtml = (value) => String(value ?? '')
275293
.replace(/&/g, '&amp;')
276294
.replace(/</g, '&lt;')
@@ -467,6 +485,56 @@ class Dashboard {
467485
}
468486
}
469487

488+
updatePolecatSummary(targetEl = null) {
489+
const el = targetEl || document.getElementById('dashboard-polecats-summary');
490+
if (!el) return;
491+
492+
const sessionsMap = this.orchestrator?.sessions;
493+
const sessions = sessionsMap && typeof sessionsMap.values === 'function'
494+
? Array.from(sessionsMap.values())
495+
: [];
496+
497+
if (!sessions.length) {
498+
el.textContent = 'No sessions.';
499+
return;
500+
}
501+
502+
const byType = { claude: 0, codex: 0, server: 0, other: 0 };
503+
const byStatus = {};
504+
505+
for (const s of sessions) {
506+
const type = String(s?.type || '').trim().toLowerCase();
507+
if (type === 'claude') byType.claude += 1;
508+
else if (type === 'codex') byType.codex += 1;
509+
else if (type === 'server') byType.server += 1;
510+
else byType.other += 1;
511+
512+
const status = String(s?.status || 'idle').trim().toLowerCase() || 'idle';
513+
byStatus[status] = (byStatus[status] || 0) + 1;
514+
}
515+
516+
const total = sessions.length;
517+
const busy = byStatus.busy || 0;
518+
const waiting = byStatus.waiting || 0;
519+
const idle = byStatus.idle || 0;
520+
const otherStatuses = Object.entries(byStatus)
521+
.filter(([k]) => k !== 'busy' && k !== 'waiting' && k !== 'idle')
522+
.sort((a, b) => (b[1] || 0) - (a[1] || 0))
523+
.map(([k, v]) => `${k}:${v}`)
524+
.slice(0, 4)
525+
.join(' • ');
526+
527+
el.innerHTML = `
528+
<div>Total <strong>${total}</strong></div>
529+
<div style="opacity:0.85; margin-top:4px;">
530+
Types: claude ${byType.claude} • codex ${byType.codex} • server ${byType.server}${byType.other ? ` • other ${byType.other}` : ''}
531+
</div>
532+
<div style="opacity:0.85; margin-top:4px;">
533+
Status: busy ${busy} • waiting ${waiting} • idle ${idle}${otherStatuses ? ` • ${otherStatuses}` : ''}
534+
</div>
535+
`;
536+
}
537+
470538
downloadTelemetryCsv(lookbackHours) {
471539
const hours = Number(lookbackHours);
472540
const safe = Number.isFinite(hours) && hours > 0 ? hours : 24;

0 commit comments

Comments
 (0)