Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/tui/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
1 change: 1 addition & 0 deletions torc-dash/static/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
4 changes: 1 addition & 3 deletions torc-dash/static/js/app-debugging.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,6 @@ Object.assign(TorcDashboard.prototype, {
return;
}

const statusNames = ['Uninitialized', 'Blocked', 'Ready', 'Pending', 'Running', 'Completed', 'Failed', 'Canceled', 'Terminated', 'Disabled'];

container.innerHTML = `
<table class="data-table">
<thead>
Expand All @@ -170,7 +168,7 @@ Object.assign(TorcDashboard.prototype, {
return `
<tr class="debug-table-row" onclick="app.selectDebugJob(${idx})">
<td>${this.escapeHtml(job.name || '-')}</td>
<td><span class="status-badge status-${statusNames[job.status]?.toLowerCase() || 'unknown'}">${statusNames[job.status] || '-'}</span></td>
<td><span class="status-badge status-${this.jobStatusSlug(job.status)}">${this.formatJobStatus(job.status) || '-'}</span></td>
<td class="${result?.return_code === 0 ? 'return-code-0' : 'return-code-error'}">${result?.return_code ?? '-'}</td>
<td><code>${result?.stdoutPath ? this.escapeHtml(this.truncate(result.stdoutPath, 40)) : '-'}</code></td>
<td><code>${result?.stderrPath ? this.escapeHtml(this.truncate(result.stderrPath, 40)) : '-'}</code></td>
Expand Down
21 changes: 7 additions & 14 deletions torc-dash/static/js/app-details.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 => `
<tr>
<td><code>${job.id ?? '-'}</code></td>
<td>${this.escapeHtml(job.name || '-')}</td>
<td><span class="status-badge status-${statusNames[job.status]?.toLowerCase() || 'unknown'}">${statusNames[job.status] || job.status}</span></td>
<td><span class="status-badge status-${this.jobStatusSlug(job.status)}">${this.formatJobStatus(job.status)}</span></td>
<td><code>${this.escapeHtml(this.truncate(job.command || '-', 80))}</code></td>
<td><button class="btn-job-details" data-job-id="${job.id}" data-job-name="${this.escapeHtml(job.name || '')}">Details</button></td>
</tr>
Expand All @@ -422,10 +420,10 @@ Object.assign(TorcDashboard.prototype, {
<td>${result.run_id ?? '-'}</td>
<td>${result.attempt_id ?? 1}</td>
<td class="${result.return_code === 0 ? 'return-code-0' : 'return-code-error'}">${result.return_code ?? '-'}</td>
<td><span class="status-badge status-${statusNames[result.status]?.toLowerCase() || 'unknown'}">${statusNames[result.status] || result.status}</span></td>
<td><span class="status-badge status-${this.jobStatusSlug(result.status)}">${this.formatJobStatus(result.status)}</span></td>
<td>${result.exec_time_minutes != null ? result.exec_time_minutes.toFixed(2) : '-'}</td>
<td>${this.formatBytes(result.peak_memory_bytes)}</td>
<td>${result.avg_cpu_percent != null ? result.avg_cpu_percent.toFixed(1) : '-'}</td>
<td>${result.peak_cpu_percent != null ? result.peak_cpu_percent.toFixed(1) : '-'}</td>
</tr>
`).join('');

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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':
Expand Down
14 changes: 6 additions & 8 deletions torc-dash/static/js/app-job-details.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = `
Expand All @@ -159,7 +158,7 @@ Object.assign(TorcDashboard.prototype, {
</div>
<div class="job-details-summary-item">
<span class="label">Status</span>
<span class="value"><span class="status-badge status-${statusNames[job.status]?.toLowerCase() || 'unknown'}">${statusNames[job.status] || job.status}</span></span>
<span class="value"><span class="status-badge status-${this.jobStatusSlug(job.status)}">${this.formatJobStatus(job.status)}</span></span>
</div>
<div class="job-details-summary-item">
<span class="label">Compute Node</span>
Expand Down Expand Up @@ -196,7 +195,6 @@ Object.assign(TorcDashboard.prototype, {
}

const data = this.jobDetailsData;
const statusNames = ['Uninitialized', 'Blocked', 'Ready', 'Pending', 'Running', 'Completed', 'Failed', 'Canceled', 'Terminated', 'Disabled'];

switch (tabName) {
case 'results':
Expand All @@ -213,7 +211,7 @@ Object.assign(TorcDashboard.prototype, {
<th>Status</th>
<th>Exec Time (min)</th>
<th>Peak Mem</th>
<th>Avg CPU %</th>
<th>Peak CPU %</th>
</tr>
</thead>
<tbody>
Expand All @@ -222,10 +220,10 @@ Object.assign(TorcDashboard.prototype, {
<td>${r.run_id ?? '-'}</td>
<td>${r.attempt_id ?? 1}</td>
<td class="${r.return_code === 0 ? 'return-code-0' : 'return-code-error'}">${r.return_code ?? '-'}</td>
<td><span class="status-badge status-${statusNames[r.status]?.toLowerCase() || 'unknown'}">${statusNames[r.status] || r.status}</span></td>
<td><span class="status-badge status-${this.jobStatusSlug(r.status)}">${this.formatJobStatus(r.status)}</span></td>
<td>${r.exec_time_minutes != null ? r.exec_time_minutes.toFixed(2) : '-'}</td>
<td>${this.formatBytes(r.peak_memory_bytes)}</td>
<td>${r.avg_cpu_percent != null ? r.avg_cpu_percent.toFixed(1) : '-'}</td>
<td>${r.peak_cpu_percent != null ? r.peak_cpu_percent.toFixed(1) : '-'}</td>
</tr>
`).join('')}
</tbody>
Expand Down Expand Up @@ -306,7 +304,7 @@ Object.assign(TorcDashboard.prototype, {
<tr>
<td><code>${j.id ?? '-'}</code></td>
<td>${this.escapeHtml(j.name || '-')}</td>
<td><span class="status-badge status-${statusNames[j.status]?.toLowerCase() || 'unknown'}">${statusNames[j.status] || j.status}</span></td>
<td><span class="status-badge status-${this.jobStatusSlug(j.status)}">${this.formatJobStatus(j.status)}</span></td>
</tr>
`).join('')}
</tbody>
Expand All @@ -330,7 +328,7 @@ Object.assign(TorcDashboard.prototype, {
<tr>
<td><code>${j.id ?? '-'}</code></td>
<td>${this.escapeHtml(j.name || '-')}</td>
<td><span class="status-badge status-${statusNames[j.status]?.toLowerCase() || 'unknown'}">${statusNames[j.status] || j.status}</span></td>
<td><span class="status-badge status-${this.jobStatusSlug(j.status)}">${this.formatJobStatus(j.status)}</span></td>
</tr>
`).join('')}
</tbody>
Expand Down
16 changes: 7 additions & 9 deletions torc-dash/static/js/app-tables.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@ Object.assign(TorcDashboard.prototype, {
return `${controls}<div class="placeholder-message">No jobs in this workflow</div>`;
}

const statusNames = ['Uninitialized', 'Blocked', 'Ready', 'Pending', 'Running', 'Completed', 'Failed', 'Canceled', 'Terminated', 'Disabled'];

return `
${controls}
${count}
Expand All @@ -64,13 +62,15 @@ Object.assign(TorcDashboard.prototype, {
</thead>
<tbody>
${jobs.map(job => {
const isRunning = statusNames[job.status] === 'Running';
// Route through jobStatusSlug so the legacy-integer
// fallback in the helper covers both wire formats.
const isRunning = this.jobStatusSlug(job.status) === 'running';
const elapsed = isRunning ? this.formatElapsedSince(job.start_time) : '-';
return `
<tr>
<td><code>${job.id ?? '-'}</code></td>
<td>${this.escapeHtml(job.name || '-')}</td>
<td><span class="status-badge status-${statusNames[job.status]?.toLowerCase() || 'unknown'}">${statusNames[job.status] || job.status}</span></td>
<td><span class="status-badge status-${this.jobStatusSlug(job.status)}">${this.formatJobStatus(job.status)}</span></td>
<td><code>${job.compute_node_id ?? '-'}</code></td>
<td><code>${elapsed}</code></td>
<td><code>${this.escapeHtml(this.truncate(job.command || '-', 80))}</code></td>
Expand Down Expand Up @@ -167,8 +167,6 @@ Object.assign(TorcDashboard.prototype, {
});
}

const statusNames = ['Uninitialized', 'Blocked', 'Ready', 'Pending', 'Running', 'Completed', 'Failed', 'Canceled', 'Terminated', 'Disabled'];

return `
${controls}
${count}
Expand All @@ -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')}
</tr>
</thead>
<tbody>
Expand All @@ -194,10 +192,10 @@ Object.assign(TorcDashboard.prototype, {
<td>${result.run_id ?? '-'}</td>
<td>${result.attempt_id ?? 1}</td>
<td class="${result.return_code === 0 ? 'return-code-0' : 'return-code-error'}">${result.return_code ?? '-'}</td>
<td><span class="status-badge status-${statusNames[result.status]?.toLowerCase() || 'unknown'}">${statusNames[result.status] || result.status}</span></td>
<td><span class="status-badge status-${this.jobStatusSlug(result.status)}">${this.formatJobStatus(result.status)}</span></td>
<td>${result.exec_time_minutes != null ? result.exec_time_minutes.toFixed(2) : '-'}</td>
<td>${this.formatBytes(result.peak_memory_bytes)}</td>
<td>${result.avg_cpu_percent != null ? result.avg_cpu_percent.toFixed(1) : '-'}</td>
<td>${result.peak_cpu_percent != null ? result.peak_cpu_percent.toFixed(1) : '-'}</td>
</tr>
`).join('')}
</tbody>
Expand Down
46 changes: 46 additions & 0 deletions torc-dash/static/js/app-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
},
Comment thread
daniel-thom marked this conversation as resolved.

formatBytes(bytes) {
if (bytes == null) return '-';
if (bytes === 0) return '0 B';
Expand Down
46 changes: 26 additions & 20 deletions torc-dash/static/js/dag.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Loading