clementine-agent 1.18.87 → 1.18.88
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/dashboard.js +116 -1
- package/package.json +1 -1
package/dist/cli/dashboard.js
CHANGED
|
@@ -15207,6 +15207,40 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
15207
15207
|
/* ── Recent history row hover (Tasks page bottom zone) ── */
|
|
15208
15208
|
.history-row { transition: background 0.12s ease; }
|
|
15209
15209
|
.history-row:hover { background: var(--bg-hover); }
|
|
15210
|
+
/* PRD §12 / 1.18.88: Health Strip — six glanceable tiles above the
|
|
15211
|
+
Tasks pane. Always visible, refreshes on SSE + 30s poll. */
|
|
15212
|
+
.health-strip {
|
|
15213
|
+
display: grid;
|
|
15214
|
+
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
|
15215
|
+
gap: 10px;
|
|
15216
|
+
margin-bottom: 18px;
|
|
15217
|
+
}
|
|
15218
|
+
.health-tile {
|
|
15219
|
+
background: var(--bg-secondary);
|
|
15220
|
+
border: 1px solid var(--border);
|
|
15221
|
+
border-radius: var(--radius);
|
|
15222
|
+
padding: 12px 14px;
|
|
15223
|
+
display: flex;
|
|
15224
|
+
flex-direction: column;
|
|
15225
|
+
gap: 4px;
|
|
15226
|
+
}
|
|
15227
|
+
.health-tile-label {
|
|
15228
|
+
font-size: 10px;
|
|
15229
|
+
color: var(--text-muted);
|
|
15230
|
+
text-transform: uppercase;
|
|
15231
|
+
letter-spacing: 0.05em;
|
|
15232
|
+
font-weight: 500;
|
|
15233
|
+
}
|
|
15234
|
+
.health-tile-value {
|
|
15235
|
+
font-size: 20px;
|
|
15236
|
+
font-weight: 600;
|
|
15237
|
+
color: var(--text-primary);
|
|
15238
|
+
line-height: 1.2;
|
|
15239
|
+
}
|
|
15240
|
+
.health-tile-sub {
|
|
15241
|
+
font-size: 11px;
|
|
15242
|
+
color: var(--text-muted);
|
|
15243
|
+
}
|
|
15210
15244
|
/* PRD Phase 1.2: "Run task once" running-state pulse on the Last run tab. */
|
|
15211
15245
|
@keyframes pulse {
|
|
15212
15246
|
0%, 100% { opacity: 0.4; transform: scale(0.85); }
|
|
@@ -23739,6 +23773,78 @@ function renderRunningCard(item) {
|
|
|
23739
23773
|
+ '</div></div>';
|
|
23740
23774
|
}
|
|
23741
23775
|
|
|
23776
|
+
// ── PRD §12 / 1.18.88: Health Strip ───────────────────────────────────
|
|
23777
|
+
// Six glanceable tiles above the Tasks pane: 24h runs / success rate /
|
|
23778
|
+
// P50 latency / P95 latency / active runs / top failure category.
|
|
23779
|
+
// Computed client-side from /api/cron/runs (already fetched by refreshCron).
|
|
23780
|
+
async function refreshHealthStrip() {
|
|
23781
|
+
var strip = document.getElementById('health-strip');
|
|
23782
|
+
if (!strip) return;
|
|
23783
|
+
var runs = [];
|
|
23784
|
+
try {
|
|
23785
|
+
var r = await apiFetch('/api/cron/runs?limit=200');
|
|
23786
|
+
var d = await r.json();
|
|
23787
|
+
runs = (d && d.runs) || [];
|
|
23788
|
+
} catch (e) { /* leave the strip empty if fetch fails */ }
|
|
23789
|
+
// Filter to last 24h.
|
|
23790
|
+
var cutoff = Date.now() - 24 * 60 * 60 * 1000;
|
|
23791
|
+
var last24 = runs.filter(function(rn) { return rn.startedAt && new Date(rn.startedAt).getTime() >= cutoff; });
|
|
23792
|
+
// Success / failure split.
|
|
23793
|
+
var ok = last24.filter(function(rn) { return rn.status === 'ok'; }).length;
|
|
23794
|
+
var failed = last24.filter(function(rn) { return rn.status === 'error' || rn.status === 'timeout' || rn.status === 'lost'; }).length;
|
|
23795
|
+
var successRate = last24.length > 0 ? Math.round((ok / last24.length) * 100) : null;
|
|
23796
|
+
// Latency distribution (terminal runs only — exclude in-progress).
|
|
23797
|
+
var durations = last24
|
|
23798
|
+
.filter(function(rn) { return rn.durationMs != null && rn.status !== 'running' && rn.status !== 'lost'; })
|
|
23799
|
+
.map(function(rn) { return rn.durationMs; })
|
|
23800
|
+
.sort(function(a, b) { return a - b; });
|
|
23801
|
+
function pct(arr, p) {
|
|
23802
|
+
if (arr.length === 0) return null;
|
|
23803
|
+
var idx = Math.min(arr.length - 1, Math.floor(arr.length * p / 100));
|
|
23804
|
+
return arr[idx];
|
|
23805
|
+
}
|
|
23806
|
+
var p50 = pct(durations, 50);
|
|
23807
|
+
var p95 = pct(durations, 95);
|
|
23808
|
+
// Active runs come from /api/build/operations runningNow which refreshCron
|
|
23809
|
+
// has already loaded into the cronJobsData side-channel; tap operations
|
|
23810
|
+
// summary if it's hanging around in the DOM, otherwise fetch it ourselves.
|
|
23811
|
+
var activeRuns = 0;
|
|
23812
|
+
try {
|
|
23813
|
+
var ops = await apiFetch('/api/build/operations?hours=1&limit=10').then(function(rr) { return rr.json(); });
|
|
23814
|
+
activeRuns = ((ops && ops.runningNow) || []).length;
|
|
23815
|
+
} catch (e) { /* fall back to 0 */ }
|
|
23816
|
+
// Top failure category for the day.
|
|
23817
|
+
var catCounts = {};
|
|
23818
|
+
for (var i = 0; i < last24.length; i++) {
|
|
23819
|
+
var c = last24[i].failureCategory;
|
|
23820
|
+
if (c) catCounts[c] = (catCounts[c] || 0) + 1;
|
|
23821
|
+
}
|
|
23822
|
+
var topCat = null, topCount = 0;
|
|
23823
|
+
Object.keys(catCounts).forEach(function(k) {
|
|
23824
|
+
if (catCounts[k] > topCount) { topCat = k; topCount = catCounts[k]; }
|
|
23825
|
+
});
|
|
23826
|
+
// Render six tiles.
|
|
23827
|
+
function tile(label, value, sub, color) {
|
|
23828
|
+
return '<div class="health-tile">'
|
|
23829
|
+
+ '<div class="health-tile-label">' + esc(label) + '</div>'
|
|
23830
|
+
+ '<div class="health-tile-value"' + (color ? ' style="color:' + color + '"' : '') + '>' + (value === null || value === undefined ? '—' : esc(String(value))) + '</div>'
|
|
23831
|
+
+ (sub ? '<div class="health-tile-sub">' + esc(sub) + '</div>' : '')
|
|
23832
|
+
+ '</div>';
|
|
23833
|
+
}
|
|
23834
|
+
var srColor = successRate === null ? null
|
|
23835
|
+
: successRate >= 95 ? 'var(--green)'
|
|
23836
|
+
: successRate >= 80 ? 'var(--yellow)'
|
|
23837
|
+
: 'var(--red)';
|
|
23838
|
+
var html = '';
|
|
23839
|
+
html += tile('Runs · 24h', last24.length, ok + ' ok · ' + failed + ' failed');
|
|
23840
|
+
html += tile('Success rate', successRate === null ? '—' : (successRate + '%'), null, srColor);
|
|
23841
|
+
html += tile('P50 latency', p50 === null ? '—' : formatDurationMs(p50), 'median run time');
|
|
23842
|
+
html += tile('P95 latency', p95 === null ? '—' : formatDurationMs(p95), '95th percentile');
|
|
23843
|
+
html += tile('Running now', activeRuns, activeRuns === 0 ? 'idle' : 'live');
|
|
23844
|
+
html += tile('Top failure', topCat ? _runListCategoryLabel(topCat) : '—', topCat ? topCount + ' run' + (topCount === 1 ? '' : 's') : 'no failures', topCat ? _runListCategoryColor(topCat) : null);
|
|
23845
|
+
strip.innerHTML = html;
|
|
23846
|
+
}
|
|
23847
|
+
|
|
23742
23848
|
// ── PRD Phase 3: Run list ──────────────────────────────────────────────
|
|
23743
23849
|
// Single sortable/filterable table of every CronRunEntry across all tasks.
|
|
23744
23850
|
// Filters: status, task name, time window. Browser-local saved views.
|
|
@@ -24375,7 +24481,11 @@ async function refreshCron() {
|
|
|
24375
24481
|
var visibleRunning = ownerScoped ? (ops.runningNow || []).filter(function(i) { return buildOpsOwnerMatches(i.owner || ''); }) : (ops.runningNow || []);
|
|
24376
24482
|
var ownerFilter = getBuildOwnerFilter();
|
|
24377
24483
|
|
|
24378
|
-
|
|
24484
|
+
// PRD §12 / 1.18.88: Health Strip placeholder. The runs payload from
|
|
24485
|
+
// /api/cron/runs (already fetched alongside ops) feeds the metrics.
|
|
24486
|
+
// Render an empty shell first; refreshHealthStrip fills it in.
|
|
24487
|
+
var html = '<div id="health-strip" class="health-strip"></div>';
|
|
24488
|
+
html += renderOperationsSummary(ops);
|
|
24379
24489
|
|
|
24380
24490
|
// ── Zone 1 — Running now (promoted to top, primary "what's live" view) ──
|
|
24381
24491
|
if (visibleRunning.length > 0) {
|
|
@@ -24424,6 +24534,11 @@ async function refreshCron() {
|
|
|
24424
24534
|
html += renderRecentHistoryList(historyData);
|
|
24425
24535
|
|
|
24426
24536
|
panel.innerHTML = html;
|
|
24537
|
+
// PRD §12 / 1.18.88: populate the Health Strip after the panel renders.
|
|
24538
|
+
// Fire-and-forget — the strip lives at the top, fills in async.
|
|
24539
|
+
if (typeof refreshHealthStrip === 'function') {
|
|
24540
|
+
refreshHealthStrip().catch(function() { /* non-fatal */ });
|
|
24541
|
+
}
|
|
24427
24542
|
panel.onclick = function(ev) {
|
|
24428
24543
|
var target = ev.target;
|
|
24429
24544
|
while (target && target.id !== 'panel-cron') {
|