diff --git a/src/tui/app.rs b/src/tui/app.rs index f536ed78e..a5a3a99b6 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -683,7 +683,9 @@ impl App { workflows: Vec::new(), workflows_all: Vec::new(), workflows_state: TableState::default(), - workflows_sort: WorkflowsSort::None, + // Default to newest-first to match the dash. Users can cycle + // through Asc / unsorted with the ID column shortcut. + workflows_sort: WorkflowsSort::IdDesc, workflows_offset: 0, workflows_has_more: false, jobs: Vec::new(), diff --git a/torc-dash/static/css/style.css b/torc-dash/static/css/style.css index 3fc12c0b2..ec1278800 100644 --- a/torc-dash/static/css/style.css +++ b/torc-dash/static/css/style.css @@ -623,6 +623,7 @@ body { .status-running { background: #cce5ff; color: #004085; } .status-completed { background: #d4edda; color: #155724; } .status-failed { background: #f8d7da; color: #721c24; } +.status-pending_failed { background: #f8d7da; color: #721c24; } .status-canceled { background: #e2d9f3; color: #4a235a; } .status-terminated { background: #d6d8db; color: #383d41; } .status-disabled { background: #fdfdfe; color: #818182; } diff --git a/torc-dash/static/js/app-debugging.js b/torc-dash/static/js/app-debugging.js index 33270f3fe..b488f243c 100644 --- a/torc-dash/static/js/app-debugging.js +++ b/torc-dash/static/js/app-debugging.js @@ -151,8 +151,6 @@ Object.assign(TorcDashboard.prototype, { return; } - const statusNames = ['Uninitialized', 'Blocked', 'Ready', 'Pending', 'Running', 'Completed', 'Failed', 'Canceled', 'Terminated', 'Disabled']; - container.innerHTML = `
| ${this.escapeHtml(job.name || '-')} | -${statusNames[job.status] || '-'} | +${this.formatJobStatus(job.status) || '-'} | ${result?.return_code ?? '-'} | ${result?.stdoutPath ? this.escapeHtml(this.truncate(result.stdoutPath, 40)) : '-'} |
${result?.stderrPath ? this.escapeHtml(this.truncate(result.stderrPath, 40)) : '-'} |
diff --git a/torc-dash/static/js/app-details.js b/torc-dash/static/js/app-details.js
index 017dbf2b0..282759abc 100644
--- a/torc-dash/static/js/app-details.js
+++ b/torc-dash/static/js/app-details.js
@@ -400,15 +400,13 @@ Object.assign(TorcDashboard.prototype, {
},
renderTableBodyRows(items, tabType, jobNameMap) {
- const statusNames = ['Uninitialized', 'Blocked', 'Ready', 'Pending', 'Running', 'Completed', 'Failed', 'Canceled', 'Terminated', 'Disabled'];
-
switch (tabType) {
case 'jobs':
return items.map(job => `
|
${job.id ?? '-'} |
${this.escapeHtml(job.name || '-')} | -${statusNames[job.status] || job.status} | +${this.formatJobStatus(job.status)} | ${this.escapeHtml(this.truncate(job.command || '-', 80))} |
${result.run_id ?? '-'} | ${result.attempt_id ?? 1} | ${result.return_code ?? '-'} | -${statusNames[result.status] || result.status} | +${this.formatJobStatus(result.status)} | ${result.exec_time_minutes != null ? result.exec_time_minutes.toFixed(2) : '-'} | ${this.formatBytes(result.peak_memory_bytes)} | -${result.avg_cpu_percent != null ? result.avg_cpu_percent.toFixed(1) : '-'} | +${result.peak_cpu_percent != null ? result.peak_cpu_percent.toFixed(1) : '-'} | `).join(''); @@ -570,14 +568,10 @@ Object.assign(TorcDashboard.prototype, { let itemValue = this.getFieldValue(item, field, tabType, jobNameMap); if (field === 'status') { - const statusNames = ['uninitialized', 'blocked', 'ready', 'pending', 'running', 'completed', 'failed', 'canceled', 'terminated', 'disabled']; const filterStatusName = value.toLowerCase(); - let itemStatusName; - if (typeof item.status === 'number') { - itemStatusName = statusNames[item.status] || ''; - } else { - itemStatusName = String(item.status).toLowerCase(); - } + const itemStatusName = typeof item.status === 'number' + ? (this.JOB_STATUS_ORDER[item.status] || '') + : String(item.status).toLowerCase(); switch (operator) { case '=': return itemStatusName === filterStatusName; case '!=': return itemStatusName !== filterStatusName; @@ -631,11 +625,10 @@ Object.assign(TorcDashboard.prototype, { }, getSearchableText(item, tabType, jobNameMap) { - const statusNames = ['Uninitialized', 'Blocked', 'Ready', 'Pending', 'Running', 'Completed', 'Failed', 'Canceled', 'Terminated', 'Disabled']; const parts = []; if (item.id != null) parts.push(String(item.id)); if (item.name) parts.push(item.name); - if (item.status != null) parts.push(statusNames[item.status] || ''); + if (item.status != null) parts.push(this.formatJobStatus(item.status)); switch (tabType) { case 'jobs': diff --git a/torc-dash/static/js/app-job-details.js b/torc-dash/static/js/app-job-details.js index 5192f1675..cae477d96 100644 --- a/torc-dash/static/js/app-job-details.js +++ b/torc-dash/static/js/app-job-details.js @@ -144,7 +144,6 @@ Object.assign(TorcDashboard.prototype, { }, renderJobDetailsSummary(job) { - const statusNames = ['Uninitialized', 'Blocked', 'Ready', 'Pending', 'Running', 'Completed', 'Failed', 'Canceled', 'Terminated', 'Disabled']; const summaryEl = document.getElementById('job-details-summary'); summaryEl.innerHTML = ` @@ -159,7 +158,7 @@ Object.assign(TorcDashboard.prototype, {Status | Exec Time (min) | Peak Mem | -Avg CPU % | +Peak CPU % | ${r.run_id ?? '-'} | ${r.attempt_id ?? 1} | ${r.return_code ?? '-'} | -${statusNames[r.status] || r.status} | +${this.formatJobStatus(r.status)} | ${r.exec_time_minutes != null ? r.exec_time_minutes.toFixed(2) : '-'} | ${this.formatBytes(r.peak_memory_bytes)} | -${r.avg_cpu_percent != null ? r.avg_cpu_percent.toFixed(1) : '-'} | +${r.peak_cpu_percent != null ? r.peak_cpu_percent.toFixed(1) : '-'} | `).join('')} @@ -306,7 +304,7 @@ Object.assign(TorcDashboard.prototype, {
|---|---|---|---|---|---|---|
${j.id ?? '-'} |
${this.escapeHtml(j.name || '-')} | -${statusNames[j.status] || j.status} | +${this.formatJobStatus(j.status)} | |||
${j.id ?? '-'} |
${this.escapeHtml(j.name || '-')} | -${statusNames[j.status] || j.status} | +${this.formatJobStatus(j.status)} | |||
${job.id ?? '-'} |
${this.escapeHtml(job.name || '-')} | -${statusNames[job.status] || job.status} | +${this.formatJobStatus(job.status)} | ${job.compute_node_id ?? '-'} |
${elapsed} |
${this.escapeHtml(this.truncate(job.command || '-', 80))} |
@@ -167,8 +167,6 @@ Object.assign(TorcDashboard.prototype, {
});
}
- const statusNames = ['Uninitialized', 'Blocked', 'Ready', 'Pending', 'Running', 'Completed', 'Failed', 'Canceled', 'Terminated', 'Disabled'];
-
return `
${controls}
${count}
@@ -183,7 +181,7 @@ Object.assign(TorcDashboard.prototype, {
${this.renderSortableHeader('Status', 'status')}
${this.renderSortableHeader('Exec Time (min)', 'exec_time_minutes')}
${this.renderSortableHeader('Peak Mem', 'peak_memory_bytes')}
- ${this.renderSortableHeader('Avg CPU %', 'avg_cpu_percent')}
+ ${this.renderSortableHeader('Peak CPU %', 'peak_cpu_percent')}
${result.run_id ?? '-'} | ${result.attempt_id ?? 1} | ${result.return_code ?? '-'} | -${statusNames[result.status] || result.status} | +${this.formatJobStatus(result.status)} | ${result.exec_time_minutes != null ? result.exec_time_minutes.toFixed(2) : '-'} | ${this.formatBytes(result.peak_memory_bytes)} | -${result.avg_cpu_percent != null ? result.avg_cpu_percent.toFixed(1) : '-'} | +${result.peak_cpu_percent != null ? result.peak_cpu_percent.toFixed(1) : '-'} | `).join('')} diff --git a/torc-dash/static/js/app-utils.js b/torc-dash/static/js/app-utils.js index cb8d15041..746f1c9bd 100644 --- a/torc-dash/static/js/app-utils.js +++ b/torc-dash/static/js/app-utils.js @@ -101,6 +101,52 @@ Object.assign(TorcDashboard.prototype, { return `${secs}s`; }, + // Canonical capitalized display names for JobStatus, keyed by the + // snake_case strings the server sends per the OpenAPI enum. Older code + // indexed `['Uninitialized', ...]` by an integer status, which silently + // returned undefined once the API switched to string status — keep both + // shapes working so the dash doesn't quietly fall back to "unknown". + JOB_STATUS_NAMES: { + uninitialized: 'Uninitialized', + blocked: 'Blocked', + ready: 'Ready', + pending: 'Pending', + running: 'Running', + completed: 'Completed', + failed: 'Failed', + canceled: 'Canceled', + terminated: 'Terminated', + disabled: 'Disabled', + pending_failed: 'Pending Failed', + }, + + JOB_STATUS_ORDER: [ + 'uninitialized', 'blocked', 'ready', 'pending', 'running', + 'completed', 'failed', 'canceled', 'terminated', 'disabled', + 'pending_failed', + ], + + // Return the capitalized display name for a JobStatus, accepting either + // the snake_case string (current API) or the legacy integer index. + // Falls back to stringifying unknown values rather than returning ''. + formatJobStatus(status) { + if (status == null) return ''; + if (typeof status === 'number') { + return this.JOB_STATUS_NAMES[this.JOB_STATUS_ORDER[status]] || String(status); + } + return this.JOB_STATUS_NAMES[status] || String(status); + }, + + // CSS-class slug for the status badge ("running", "completed", ...). + // Returns 'unknown' for nulls/unrecognized values. + jobStatusSlug(status) { + if (status == null) return 'unknown'; + if (typeof status === 'number') { + return this.JOB_STATUS_ORDER[status] || 'unknown'; + } + return this.JOB_STATUS_NAMES[status] ? status : 'unknown'; + }, + formatBytes(bytes) { if (bytes == null) return '-'; if (bytes === 0) return '0 B'; diff --git a/torc-dash/static/js/dag.js b/torc-dash/static/js/dag.js index 8ee62574e..a39ddb214 100644 --- a/torc-dash/static/js/dag.js +++ b/torc-dash/static/js/dag.js @@ -12,30 +12,36 @@ class DAGVisualizer { } // Status to color mapping + // Keyed by the snake_case JobStatus string the server sends, per the + // OpenAPI enum. (Previously keyed by the legacy integer status, which + // made every node fall back to the gray default once the API switched + // to string status.) static statusColors = { - 0: '#6c757d', // uninitialized - 1: '#ffc107', // blocked - 2: '#17a2b8', // ready - 3: '#fd7e14', // pending - 4: '#007bff', // running - 5: '#28a745', // completed - 6: '#dc3545', // failed - 7: '#6f42c1', // canceled - 8: '#adb5bd', // terminated - 9: '#e9ecef', // disabled + uninitialized: '#6c757d', + blocked: '#ffc107', + ready: '#17a2b8', + pending: '#fd7e14', + running: '#007bff', + completed: '#28a745', + failed: '#dc3545', + canceled: '#6f42c1', + terminated: '#adb5bd', + disabled: '#e9ecef', + pending_failed: '#dc3545', }; static statusNames = { - 0: 'Uninitialized', - 1: 'Blocked', - 2: 'Ready', - 3: 'Pending', - 4: 'Running', - 5: 'Completed', - 6: 'Failed', - 7: 'Canceled', - 8: 'Terminated', - 9: 'Disabled', + uninitialized: 'Uninitialized', + blocked: 'Blocked', + ready: 'Ready', + pending: 'Pending', + running: 'Running', + completed: 'Completed', + failed: 'Failed', + canceled: 'Canceled', + terminated: 'Terminated', + disabled: 'Disabled', + pending_failed: 'Pending Failed', }; initialize() {