diff --git a/public/index.html b/public/index.html
index 6922209..c4e19f7 100644
--- a/public/index.html
+++ b/public/index.html
@@ -1780,6 +1780,11 @@
// Add action buttons
html += '
';
+ // Abort button (only for running executions)
+ if (execution.status === 'running') {
+ html += `
`;
+ }
+
// Re-run button (only for quick commands with command in logs)
const commandLog = execution.logs?.find(l => l.action === 'command_sent');
if (commandLog && commandLog.command) {
@@ -1899,6 +1904,18 @@
`;
}
+ if (log.action === 'execution_aborted') {
+ return `
+
+
[${timestamp}]
+
⛔ Execution Aborted
+
+
Aborted by: ${escapeHtml(log.aborted_by)}
+
+
+ `;
+ }
+
// Fallback for unknown log types
return `
${JSON.stringify(log, null, 2)}`;
}
@@ -1952,6 +1969,28 @@
document.getElementById('quickCommand').scrollIntoView({ behavior: 'smooth' });
}
+ async function abortExecution(executionId) {
+ if (!confirm('Are you sure you want to abort this execution? This will mark it as failed.')) return;
+
+ try {
+ const response = await fetch(`/api/executions/${executionId}/abort`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' }
+ });
+
+ if (response.ok) {
+ showTerminalNotification('Execution aborted', 'success');
+ closeModal('viewExecutionModal');
+ refreshData();
+ } else {
+ alert('Failed to abort execution');
+ }
+ } catch (error) {
+ console.error('Error aborting execution:', error);
+ alert('Error aborting execution');
+ }
+ }
+
async function downloadExecutionLogs(executionId) {
try {
const response = await fetch(`/api/executions/${executionId}`);
diff --git a/server.js b/server.js
index 61e066a..0ac5d29 100644
--- a/server.js
+++ b/server.js
@@ -872,6 +872,40 @@ app.delete('/api/executions/:id', authenticateSSO, async (req, res) => {
}
});
+app.post('/api/executions/:id/abort', authenticateSSO, async (req, res) => {
+ try {
+ const executionId = req.params.id;
+
+ // Check if execution exists and is running
+ const [execution] = await pool.query('SELECT status FROM executions WHERE id = ?', [executionId]);
+
+ if (execution.length === 0) {
+ return res.status(404).json({ error: 'Execution not found' });
+ }
+
+ if (execution[0].status !== 'running') {
+ return res.status(400).json({ error: 'Execution is not running' });
+ }
+
+ // Add abort log entry
+ await addExecutionLog(executionId, {
+ action: 'execution_aborted',
+ aborted_by: req.user.username,
+ timestamp: new Date().toISOString()
+ });
+
+ // Update execution status to failed
+ await updateExecutionStatus(executionId, 'failed');
+
+ console.log(`[Execution] Execution ${executionId} aborted by ${req.user.username}`);
+
+ res.json({ success: true });
+ } catch (error) {
+ console.error('[Execution] Error aborting execution:', error);
+ res.status(500).json({ error: error.message });
+ }
+});
+
// Scheduled Commands API
app.get('/api/scheduled-commands', authenticateSSO, async (req, res) => {
try {