summaryrefslogtreecommitdiff
path: root/web/app.js
diff options
context:
space:
mode:
Diffstat (limited to 'web/app.js')
-rw-r--r--web/app.js94
1 files changed, 94 insertions, 0 deletions
diff --git a/web/app.js b/web/app.js
index bca41fa..77a2d9d 100644
--- a/web/app.js
+++ b/web/app.js
@@ -74,6 +74,24 @@ function formatDate(iso) {
});
}
+// Returns formatted string for changestats, e.g. "5 files, +127 -43".
+// Returns empty string for null/undefined input.
+export function formatChangestats(stats) {
+ if (stats == null) return '';
+ return `${stats.files_changed} files, +${stats.lines_added} -${stats.lines_removed}`;
+}
+
+// Returns a <span class="changestats-badge"> element for the given stats,
+// or null if stats is null/undefined.
+// Accepts an optional doc parameter for testability (defaults to document).
+export function renderChangestatsBadge(stats, doc = (typeof document !== 'undefined' ? document : null)) {
+ if (stats == null || doc == null) return null;
+ const span = doc.createElement('span');
+ span.className = 'changestats-badge';
+ span.textContent = formatChangestats(stats);
+ return span;
+}
+
function createTaskCard(task) {
const card = document.createElement('div');
card.className = 'task-card';
@@ -118,6 +136,13 @@ function createTaskCard(task) {
card.appendChild(desc);
}
+ // Changestats badge for COMPLETED/READY tasks
+ const CHANGESTATS_STATES = new Set(['COMPLETED', 'READY']);
+ if (CHANGESTATS_STATES.has(task.state) && task.changestats != null) {
+ const csBadge = renderChangestatsBadge(task.changestats);
+ if (csBadge) card.appendChild(csBadge);
+ }
+
// Footer: action buttons based on state
// Interrupted states (CANCELLED, FAILED, BUDGET_EXCEEDED) show both Resume and Restart.
// TIMED_OUT shows Resume only. Others show a single action.
@@ -339,6 +364,46 @@ export function setTaskFilterTab(tab) {
localStorage.setItem('taskFilterTab', tab);
}
+// ── Tab badge counts ───────────────────────────────────────────────────────────
+
+/**
+ * Computes badge counts for the 'interrupted', 'ready', and 'running' tabs.
+ * Returns { interrupted: N, ready: N, running: N }.
+ */
+export function computeTabBadgeCounts(tasks) {
+ let interrupted = 0;
+ let ready = 0;
+ let running = 0;
+ for (const t of tasks) {
+ if (INTERRUPTED_STATES.has(t.state)) interrupted++;
+ if (t.state === 'READY') ready++;
+ if (t.state === 'RUNNING') running++;
+ }
+ return { interrupted, ready, running };
+}
+
+/**
+ * Updates the badge count spans inside the tab buttons for
+ * 'interrupted', 'ready', and 'running'.
+ * Badge is hidden (display:none) when count is zero.
+ */
+export function updateTabBadges(tasks, doc = (typeof document !== 'undefined' ? document : null)) {
+ if (!doc) return;
+ const counts = computeTabBadgeCounts(tasks);
+ for (const [tab, count] of Object.entries(counts)) {
+ const btn = doc.querySelector(`.tab[data-tab="${tab}"]`);
+ if (!btn) continue;
+ let badge = btn.querySelector('.tab-count-badge');
+ if (!badge) {
+ badge = doc.createElement('span');
+ badge.className = 'tab-count-badge';
+ btn.appendChild(badge);
+ }
+ badge.textContent = String(count);
+ badge.hidden = count === 0;
+ }
+}
+
// ── Stats computations ─────────────────────────────────────────────────────────
/**
@@ -961,6 +1026,8 @@ async function poll() {
const tasks = await fetchTasks();
if (isUserEditing()) return;
+ updateTabBadges(tasks);
+
const activeTab = getActiveTab();
switch (activeTab) {
case 'queue':
@@ -1648,6 +1715,33 @@ function renderTaskPanel(task, executions) {
exitEl.textContent = `exit: ${exec.ExitCode ?? '—'}`;
row.appendChild(exitEl);
+ if (exec.Changestats != null) {
+ const csBadge = renderChangestatsBadge(exec.Changestats);
+ if (csBadge) row.appendChild(csBadge);
+ }
+
+ if (exec.Commits && exec.Commits.length > 0) {
+ const commitList = document.createElement('div');
+ commitList.className = 'execution-commits';
+ for (const commit of exec.Commits) {
+ const item = document.createElement('div');
+ item.className = 'commit-item';
+
+ const hash = document.createElement('span');
+ hash.className = 'commit-hash';
+ hash.textContent = commit.hash.slice(0, 7);
+ item.appendChild(hash);
+
+ const msg = document.createElement('span');
+ msg.className = 'commit-msg';
+ msg.textContent = commit.message;
+ item.appendChild(msg);
+
+ commitList.appendChild(item);
+ }
+ row.appendChild(commitList);
+ }
+
const logsBtn = document.createElement('button');
logsBtn.className = 'btn-view-logs';
logsBtn.textContent = 'View Logs';