Enhance jobs page layout with job IDs, worker details, and improved actions

- Add Job ID and Queue ID columns for better job tracking
- Separate worker assignment details from action buttons
- Reorganize action buttons side by side (restart + delete)
- Add confirmation dialog for delete actions
- Update CSS grid layout to accommodate new columns
- Real-time updates for worker details column
parent e04521ef
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
.jobs-table { background: white; border-radius: 12px; box-shadow: 0 2px 10px rgba(0,0,0,0.05); overflow: hidden; } .jobs-table { background: white; border-radius: 12px; box-shadow: 0 2px 10px rgba(0,0,0,0.05); overflow: hidden; }
.table-header { padding: 2rem; border-bottom: 1px solid #e5e7eb; } .table-header { padding: 2rem; border-bottom: 1px solid #e5e7eb; }
.table-header h2 { margin: 0; color: #1e293b; } .table-header h2 { margin: 0; color: #1e293b; }
.job-row { display: grid; grid-template-columns: 1fr 2fr 1fr 100px 100px 120px; gap: 1rem; padding: 1rem 2rem; border-bottom: 1px solid #f1f5f9; align-items: center; } .job-row { display: grid; grid-template-columns: 80px 80px 1fr 2fr 1fr 1fr 100px 100px 120px; gap: 1rem; padding: 1rem 2rem; border-bottom: 1px solid #f1f5f9; align-items: center; }
.job-row:last-child { border-bottom: none; } .job-row:last-child { border-bottom: none; }
.job-type { font-weight: 600; color: #1e293b; } .job-type { font-weight: 600; color: #1e293b; }
.job-data { color: #64748b; font-size: 0.9rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .job-data { color: #64748b; font-size: 0.9rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
...@@ -119,6 +119,12 @@ ...@@ -119,6 +119,12 @@
} }
} }
// Update worker details
const workerDetailsElement = jobRow.querySelector('.worker-details');
if (workerDetailsElement && jobUpdate.result) {
workerDetailsElement.textContent = jobUpdate.result.status || '-';
}
// Add progress info for processing jobs // Add progress info for processing jobs
if (jobUpdate.status === 'processing' && jobUpdate.result) { if (jobUpdate.status === 'processing' && jobUpdate.result) {
let progressElement = jobRow.querySelector('.job-progress'); let progressElement = jobRow.querySelector('.job-progress');
...@@ -167,6 +173,10 @@ ...@@ -167,6 +173,10 @@
actionsElement.appendChild(cancelBtn); actionsElement.appendChild(cancelBtn);
} else if (status === 'cancelled') { } else if (status === 'cancelled') {
const buttonContainer = document.createElement('div');
buttonContainer.style.display = 'flex';
buttonContainer.style.gap = '0.5rem';
const restartBtn = document.createElement('button'); const restartBtn = document.createElement('button');
restartBtn.className = 'restart-btn'; restartBtn.className = 'restart-btn';
restartBtn.style.background = '#10b981'; restartBtn.style.background = '#10b981';
...@@ -176,12 +186,9 @@ ...@@ -176,12 +186,9 @@
restartBtn.style.borderRadius = '4px'; restartBtn.style.borderRadius = '4px';
restartBtn.style.fontSize = '0.8rem'; restartBtn.style.fontSize = '0.8rem';
restartBtn.style.cursor = 'pointer'; restartBtn.style.cursor = 'pointer';
restartBtn.style.marginRight = '0.5rem';
restartBtn.textContent = 'Restart'; restartBtn.textContent = 'Restart';
restartBtn.onclick = () => performJobAction(jobId, 'restart', restartBtn); restartBtn.onclick = () => performJobAction(jobId, 'restart', restartBtn);
actionsElement.appendChild(restartBtn);
const deleteBtn = document.createElement('button'); const deleteBtn = document.createElement('button');
deleteBtn.className = 'delete-btn'; deleteBtn.className = 'delete-btn';
deleteBtn.style.background = '#dc2626'; deleteBtn.style.background = '#dc2626';
...@@ -192,9 +199,15 @@ ...@@ -192,9 +199,15 @@
deleteBtn.style.fontSize = '0.8rem'; deleteBtn.style.fontSize = '0.8rem';
deleteBtn.style.cursor = 'pointer'; deleteBtn.style.cursor = 'pointer';
deleteBtn.textContent = 'Delete'; deleteBtn.textContent = 'Delete';
deleteBtn.onclick = () => performJobAction(jobId, 'delete', deleteBtn); deleteBtn.onclick = () => {
if (confirm('Are you sure you want to delete this job? This action cannot be undone.')) {
performJobAction(jobId, 'delete', deleteBtn);
}
};
actionsElement.appendChild(deleteBtn); buttonContainer.appendChild(restartBtn);
buttonContainer.appendChild(deleteBtn);
actionsElement.appendChild(buttonContainer);
} else if (status === 'queued') { } else if (status === 'queued') {
const deleteBtn = document.createElement('button'); const deleteBtn = document.createElement('button');
deleteBtn.className = 'delete-btn'; deleteBtn.className = 'delete-btn';
...@@ -206,7 +219,11 @@ ...@@ -206,7 +219,11 @@
deleteBtn.style.fontSize = '0.8rem'; deleteBtn.style.fontSize = '0.8rem';
deleteBtn.style.cursor = 'pointer'; deleteBtn.style.cursor = 'pointer';
deleteBtn.textContent = 'Delete'; deleteBtn.textContent = 'Delete';
deleteBtn.onclick = () => performJobAction(jobId, 'delete', deleteBtn); deleteBtn.onclick = () => {
if (confirm('Are you sure you want to delete this job? This action cannot be undone.')) {
performJobAction(jobId, 'delete', deleteBtn);
}
};
actionsElement.appendChild(deleteBtn); actionsElement.appendChild(deleteBtn);
} else if (status === 'failed') { } else if (status === 'failed') {
...@@ -220,7 +237,11 @@ ...@@ -220,7 +237,11 @@
deleteBtn.style.fontSize = '0.8rem'; deleteBtn.style.fontSize = '0.8rem';
deleteBtn.style.cursor = 'pointer'; deleteBtn.style.cursor = 'pointer';
deleteBtn.textContent = 'Delete'; deleteBtn.textContent = 'Delete';
deleteBtn.onclick = () => performJobAction(jobId, 'delete', deleteBtn); deleteBtn.onclick = () => {
if (confirm('Are you sure you want to delete this job? This action cannot be undone.')) {
performJobAction(jobId, 'delete', deleteBtn);
}
};
actionsElement.appendChild(deleteBtn); actionsElement.appendChild(deleteBtn);
} }
...@@ -424,11 +445,20 @@ ...@@ -424,11 +445,20 @@
</div> </div>
{% for job in queue_items %} {% for job in queue_items %}
<div class="job-row" data-job-id="{{ job.id }}"> <div class="job-row" data-job-id="{{ job.id }}">
<div class="job-id" style="font-family: monospace; font-size: 0.8rem; color: #6b7280;">{{ job.job_id or 'N/A' }}</div>
<div class="queue-id" style="font-family: monospace; font-size: 0.8rem; color: #6b7280;">{{ job.id }}</div>
<div class="job-type">{{ job.request_type.title() }}</div> <div class="job-type">{{ job.request_type.title() }}</div>
<div class="job-data" title="{{ job.data.get('prompt', job.data.get('description', 'N/A')) }}"> <div class="job-data" title="{{ job.data.get('prompt', job.data.get('description', 'N/A')) }}">
{{ job.data.get('prompt', job.data.get('description', 'N/A'))[:50] }}{% if job.data.get('prompt', job.data.get('description', 'N/A'))|length > 50 %}...{% endif %} {{ job.data.get('prompt', job.data.get('description', 'N/A'))[:50] }}{% if job.data.get('prompt', job.data.get('description', 'N/A'))|length > 50 %}...{% endif %}
</div> </div>
<div class="job-time">{{ job.created_at[:19] }}</div> <div class="job-time">{{ job.created_at[:19] }}</div>
<div class="worker-details" style="font-size: 0.8rem; color: #6b7280;">
{% if job.result and job.result.get('status') %}
{{ job.result.status }}
{% else %}
-
{% endif %}
</div>
<span class="job-status status-{{ job.status }}"> <span class="job-status status-{{ job.status }}">
{% if job.status == 'processing' %}<div class="spinner"></div>{% endif %} {% if job.status == 'processing' %}<div class="spinner"></div>{% endif %}
{{ job.status.title() }} {{ job.status.title() }}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment