Implement AJAX job actions for Jobs page

- Replace form submissions with AJAX calls for cancel/restart/delete
- Remove confirm alerts and page redirects
- Add success/error notifications for actions
- Initialize job actions on page load
- Update backend routes to handle AJAX requests with JSON responses
- Maintain real-time status updates via polling
parent b05ab8f9
...@@ -139,15 +139,7 @@ ...@@ -139,15 +139,7 @@
viewLink.textContent = 'View Result'; viewLink.textContent = 'View Result';
actionsElement.appendChild(viewLink); actionsElement.appendChild(viewLink);
} else if (status === 'processing') { } else if (status === 'processing') {
const cancelForm = document.createElement('form');
cancelForm.method = 'post';
cancelForm.action = `/job/${jobId}/cancel`;
cancelForm.style.display = 'inline';
cancelForm.style.marginRight = '0.5rem';
cancelForm.onsubmit = () => confirm('Are you sure you want to cancel this running job?');
const cancelBtn = document.createElement('button'); const cancelBtn = document.createElement('button');
cancelBtn.type = 'submit';
cancelBtn.className = 'cancel-btn'; cancelBtn.className = 'cancel-btn';
cancelBtn.style.background = '#f59e0b'; cancelBtn.style.background = '#f59e0b';
cancelBtn.style.color = 'white'; cancelBtn.style.color = 'white';
...@@ -156,20 +148,13 @@ ...@@ -156,20 +148,13 @@
cancelBtn.style.borderRadius = '4px'; cancelBtn.style.borderRadius = '4px';
cancelBtn.style.fontSize = '0.8rem'; cancelBtn.style.fontSize = '0.8rem';
cancelBtn.style.cursor = 'pointer'; cancelBtn.style.cursor = 'pointer';
cancelBtn.style.marginRight = '0.5rem';
cancelBtn.textContent = 'Cancel'; cancelBtn.textContent = 'Cancel';
cancelBtn.onclick = () => performJobAction(jobId, 'cancel');
cancelForm.appendChild(cancelBtn); actionsElement.appendChild(cancelBtn);
actionsElement.appendChild(cancelForm);
} else if (status === 'cancelled') { } else if (status === 'cancelled') {
const restartForm = document.createElement('form');
restartForm.method = 'post';
restartForm.action = `/job/${jobId}/restart`;
restartForm.style.display = 'inline';
restartForm.style.marginRight = '0.5rem';
restartForm.onsubmit = () => confirm('Are you sure you want to restart this cancelled job?');
const restartBtn = document.createElement('button'); const restartBtn = document.createElement('button');
restartBtn.type = 'submit';
restartBtn.className = 'restart-btn'; restartBtn.className = 'restart-btn';
restartBtn.style.background = '#10b981'; restartBtn.style.background = '#10b981';
restartBtn.style.color = 'white'; restartBtn.style.color = 'white';
...@@ -178,19 +163,13 @@ ...@@ -178,19 +163,13 @@
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');
restartForm.appendChild(restartBtn); actionsElement.appendChild(restartBtn);
actionsElement.appendChild(restartForm);
const deleteForm = document.createElement('form');
deleteForm.method = 'post';
deleteForm.action = `/job/${jobId}/delete`;
deleteForm.style.display = 'inline';
deleteForm.onsubmit = () => confirm('Are you sure you want to delete this cancelled job?');
const deleteBtn = document.createElement('button'); const deleteBtn = document.createElement('button');
deleteBtn.type = 'submit';
deleteBtn.className = 'delete-btn'; deleteBtn.className = 'delete-btn';
deleteBtn.style.background = '#dc2626'; deleteBtn.style.background = '#dc2626';
deleteBtn.style.color = 'white'; deleteBtn.style.color = 'white';
...@@ -200,18 +179,11 @@ ...@@ -200,18 +179,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');
deleteForm.appendChild(deleteBtn); actionsElement.appendChild(deleteBtn);
actionsElement.appendChild(deleteForm);
} else if (status === 'queued') { } else if (status === 'queued') {
const deleteForm = document.createElement('form');
deleteForm.method = 'post';
deleteForm.action = `/job/${jobId}/delete`;
deleteForm.style.display = 'inline';
deleteForm.onsubmit = () => confirm('Are you sure you want to delete this queued job?');
const deleteBtn = document.createElement('button'); const deleteBtn = document.createElement('button');
deleteBtn.type = 'submit';
deleteBtn.className = 'delete-btn'; deleteBtn.className = 'delete-btn';
deleteBtn.style.background = '#dc2626'; deleteBtn.style.background = '#dc2626';
deleteBtn.style.color = 'white'; deleteBtn.style.color = 'white';
...@@ -221,18 +193,11 @@ ...@@ -221,18 +193,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');
deleteForm.appendChild(deleteBtn); actionsElement.appendChild(deleteBtn);
actionsElement.appendChild(deleteForm);
} else if (status === 'failed') { } else if (status === 'failed') {
const deleteForm = document.createElement('form');
deleteForm.method = 'post';
deleteForm.action = `/job/${jobId}/delete`;
deleteForm.style.display = 'inline';
deleteForm.onsubmit = () => confirm('Are you sure you want to delete this failed job?');
const deleteBtn = document.createElement('button'); const deleteBtn = document.createElement('button');
deleteBtn.type = 'submit';
deleteBtn.className = 'delete-btn'; deleteBtn.className = 'delete-btn';
deleteBtn.style.background = '#dc2626'; deleteBtn.style.background = '#dc2626';
deleteBtn.style.color = 'white'; deleteBtn.style.color = 'white';
...@@ -242,12 +207,65 @@ ...@@ -242,12 +207,65 @@
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');
deleteForm.appendChild(deleteBtn); actionsElement.appendChild(deleteBtn);
actionsElement.appendChild(deleteForm);
} }
} }
function performJobAction(jobId, action) {
const formData = new FormData();
formData.append('_method', 'POST');
fetch(`/job/${jobId}/${action}`, {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest'
},
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Show success notification
showActionNotification(jobId, action, 'success');
// Status will be updated by the polling mechanism
} else {
showActionNotification(jobId, action, 'error');
}
})
.catch(error => {
console.log(`Error performing ${action} on job ${jobId}:`, error);
showActionNotification(jobId, action, 'error');
});
}
function showActionNotification(jobId, action, status) {
const notificationContainer = document.getElementById('notificationContainer');
if (!notificationContainer) return;
const actionText = action.charAt(0).toUpperCase() + action.slice(1);
const statusText = status === 'success' ? 'successful' : 'failed';
const notification = document.createElement('div');
notification.className = `notification ${status}`;
notification.innerHTML = `
<span class="notification-close" onclick="closeNotification(this)">&times;</span>
<strong>Job ${actionText} ${statusText}!</strong><br>
Job ${jobId} has been ${actionText.toLowerCase()}ed ${statusText}ly.
`;
notificationContainer.appendChild(notification);
// Auto-hide after 5 seconds
setTimeout(() => {
notification.classList.add('fade-out');
setTimeout(() => {
notification.remove();
}, 300);
}, 5000);
}
// Initialize completed jobs tracking // Initialize completed jobs tracking
function initializeCompletedJobs() { function initializeCompletedJobs() {
const jobRows = document.querySelectorAll('[data-job-id]'); const jobRows = document.querySelectorAll('[data-job-id]');
...@@ -266,8 +284,24 @@ ...@@ -266,8 +284,24 @@
// Initial update // Initial update
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
initializeCompletedJobs(); initializeCompletedJobs();
initializeJobActions();
updateJobStatuses(); updateJobStatuses();
}); });
function initializeJobActions() {
const jobRows = document.querySelectorAll('[data-job-id]');
jobRows.forEach(row => {
const jobId = parseInt(row.getAttribute('data-job-id'));
const statusElement = row.querySelector('.job-status');
if (statusElement) {
const statusClass = Array.from(statusElement.classList).find(cls => cls.startsWith('status-'));
if (statusClass) {
const status = statusClass.replace('status-', '');
updateJobActions(jobId, status);
}
}
});
}
</script> </script>
{% endblock %} {% endblock %}
...@@ -291,34 +325,7 @@ ...@@ -291,34 +325,7 @@
</span> </span>
<div class="job-tokens">{{ job.used_tokens or 0 }}</div> <div class="job-tokens">{{ job.used_tokens or 0 }}</div>
<div class="job-actions"> <div class="job-actions">
{% if job.status == 'completed' %} <!-- Actions will be populated by JavaScript -->
<a href="/job_result/{{ job.id }}" class="view-result-link" style="margin-right: 0.5rem;">View Result</a>
<form method="post" action="/job/{{ job.id }}/delete" style="display: inline;" onsubmit="return confirm('Are you sure you want to delete this completed job?')">
<button type="submit" class="delete-btn" style="background: #dc2626; color: white; border: none; padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.8rem; cursor: pointer;">Delete</button>
</form>
{% elif job.status == 'processing' %}
<form method="post" action="/job/{{ job.id }}/cancel" style="display: inline; margin-right: 0.5rem;" onsubmit="return confirm('Are you sure you want to cancel this running job?')">
<button type="submit" class="cancel-btn" style="background: #f59e0b; color: white; border: none; padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.8rem; cursor: pointer;">Cancel</button>
</form>
{% if job.result %}
<div class="job-progress">{{ job.result.get('status', 'Processing...') }}</div>
{% endif %}
{% elif job.status == 'cancelled' %}
<form method="post" action="/job/{{ job.id }}/restart" style="display: inline; margin-right: 0.5rem;" onsubmit="return confirm('Are you sure you want to restart this cancelled job?')">
<button type="submit" class="restart-btn" style="background: #10b981; color: white; border: none; padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.8rem; cursor: pointer;">Restart</button>
</form>
<form method="post" action="/job/{{ job.id }}/delete" style="display: inline;" onsubmit="return confirm('Are you sure you want to delete this cancelled job?')">
<button type="submit" class="delete-btn" style="background: #dc2626; color: white; border: none; padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.8rem; cursor: pointer;">Delete</button>
</form>
{% elif job.status == 'queued' %}
<form method="post" action="/job/{{ job.id }}/delete" style="display: inline;" onsubmit="return confirm('Are you sure you want to delete this queued job?')">
<button type="submit" class="delete-btn" style="background: #dc2626; color: white; border: none; padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.8rem; cursor: pointer;">Delete</button>
</form>
{% elif job.status == 'failed' %}
<form method="post" action="/job/{{ job.id }}/delete" style="display: inline;" onsubmit="return confirm('Are you sure you want to delete this failed job?')">
<button type="submit" class="delete-btn" style="background: #dc2626; color: white; border: none; padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.8rem; cursor: pointer;">Delete</button>
</form>
{% endif %}
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
......
...@@ -402,8 +402,14 @@ def delete_job(job_id): ...@@ -402,8 +402,14 @@ def delete_job(job_id):
from .queue import queue_manager from .queue import queue_manager
if queue_manager.delete_job(job_id, user['id']): if queue_manager.delete_job(job_id, user['id']):
# Check if this is an AJAX request
if request.headers.get('Content-Type') == 'application/x-www-form-urlencoded' and request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return {'success': True, 'message': 'Job deleted successfully!'}
flash('Job deleted successfully!', 'success') flash('Job deleted successfully!', 'success')
else: else:
# Check if this is an AJAX request
if request.headers.get('Content-Type') == 'application/x-www-form-urlencoded' and request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return {'success': False, 'message': 'Failed to delete job or access denied.'}, 400
flash('Failed to delete job or access denied.', 'error') flash('Failed to delete job or access denied.', 'error')
return redirect(url_for('history')) return redirect(url_for('history'))
...@@ -417,8 +423,14 @@ def cancel_job(job_id): ...@@ -417,8 +423,14 @@ def cancel_job(job_id):
from .queue import queue_manager from .queue import queue_manager
if queue_manager.cancel_job(job_id, user['id']): if queue_manager.cancel_job(job_id, user['id']):
# Check if this is an AJAX request
if request.headers.get('Content-Type') == 'application/x-www-form-urlencoded' and request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return {'success': True, 'message': 'Job cancelled successfully!'}
flash('Job cancelled successfully!', 'success') flash('Job cancelled successfully!', 'success')
else: else:
# Check if this is an AJAX request
if request.headers.get('Content-Type') == 'application/x-www-form-urlencoded' and request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return {'success': False, 'message': 'Failed to cancel job or access denied.'}, 400
flash('Failed to cancel job or access denied.', 'error') flash('Failed to cancel job or access denied.', 'error')
return redirect(url_for('history')) return redirect(url_for('history'))
...@@ -432,8 +444,14 @@ def restart_job(job_id): ...@@ -432,8 +444,14 @@ def restart_job(job_id):
from .queue import queue_manager from .queue import queue_manager
if queue_manager.restart_job(job_id, user['id']): if queue_manager.restart_job(job_id, user['id']):
# Check if this is an AJAX request
if request.headers.get('Content-Type') == 'application/x-www-form-urlencoded' and request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return {'success': True, 'message': 'Job restarted successfully!'}
flash('Job restarted successfully!', 'success') flash('Job restarted successfully!', 'success')
else: else:
# Check if this is an AJAX request
if request.headers.get('Content-Type') == 'application/x-www-form-urlencoded' and request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return {'success': False, 'message': 'Failed to restart job or access denied.'}, 400
flash('Failed to restart job or access denied.', 'error') flash('Failed to restart job or access denied.', 'error')
return redirect(url_for('history')) return redirect(url_for('history'))
......
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