diff --git a/public/index.html b/public/index.html index d7fb127..8ae36ba 100644 --- a/public/index.html +++ b/public/index.html @@ -1343,7 +1343,7 @@
-
${s.name}
+
${escapeHtml(s.name || '')}
Command: ${escapeHtml(s.command)}
@@ -1529,7 +1529,7 @@
${e.status} ${e.workflow_name || '[Quick Command]'} -
by ${e.started_by} at ${safeDate(e.started_at)?.toLocaleString() ?? 'N/A'}
+
by ${escapeHtml(e.started_by || '')} at ${safeDate(e.started_at)?.toLocaleString() ?? 'N/A'}
`).join(''); document.getElementById('dashExecutions').innerHTML = dashHtml; @@ -1655,7 +1655,7 @@ ${e.status} ${e.workflow_name || '[Quick Command]'}
- Started by ${e.started_by} at ${safeDate(e.started_at)?.toLocaleString() ?? 'N/A'} + Started by ${escapeHtml(e.started_by || '')} at ${safeDate(e.started_at)?.toLocaleString() ?? 'N/A'} ${e.completed_at ? ` • Completed at ${safeDate(e.completed_at)?.toLocaleString() ?? 'N/A'}` : elapsed}
@@ -1996,7 +1996,7 @@
Status: ${execution.status}
Started: ${safeDate(execution.started_at)?.toLocaleString() ?? 'N/A'}
${execution.completed_at ? `
Completed: ${safeDate(execution.completed_at)?.toLocaleString() ?? 'N/A'}
` : ''} -
Started by: ${execution.started_by}
+
Started by: ${escapeHtml(execution.started_by || '')}
`; if (execution.waiting_for_input && execution.prompt) { diff --git a/server.js b/server.js index 16a26ff..f4870ad 100644 --- a/server.js +++ b/server.js @@ -987,7 +987,7 @@ async function executeCommandStep(executionId, step, stepNumber, params = {}) { } // Send command to worker - const commandId = crypto.randomUUID(); + let commandId = crypto.randomUUID(); await addExecutionLog(executionId, { step: stepNumber, @@ -998,21 +998,56 @@ async function executeCommandStep(executionId, step, stepNumber, params = {}) { timestamp: new Date().toISOString() }); + // Per-step timeout override: step.timeout (seconds) overrides global default + const stepTimeoutMs = step.timeout + ? Math.min(Math.max(parseInt(step.timeout, 10) * 1000, 5000), 600000) + : COMMAND_TIMEOUT_MS; + workerWs.send(JSON.stringify({ type: 'execute_command', execution_id: executionId, command_id: commandId, command: command, worker_id: workerId, - timeout: COMMAND_TIMEOUT_MS + timeout: stepTimeoutMs })); - // Wait for command result (with timeout) - const result = await waitForCommandResult(executionId, commandId, COMMAND_TIMEOUT_MS); + // Per-step retry: step.retries (default 0) with step.retryDelayMs (default 2000) + const maxRetries = Math.min(parseInt(step.retries || 0, 10), 5); + const retryDelayMs = Math.min(parseInt(step.retryDelayMs || 2000, 10), 30000); + + let result; + let attempt = 0; + while (true) { + result = await waitForCommandResult(executionId, commandId, stepTimeoutMs); + if (result.success || attempt >= maxRetries) break; + attempt++; + await addExecutionLog(executionId, { + step: stepNumber, + action: 'command_retry', + worker_id: workerId, + attempt, + max_retries: maxRetries, + message: `Command failed, retrying (attempt ${attempt}/${maxRetries})...`, + timestamp: new Date().toISOString() + }); + await new Promise(r => setTimeout(r, retryDelayMs)); + // Re-send command for retry + const retryCommandId = crypto.randomUUID(); + await addExecutionLog(executionId, { + step: stepNumber, action: 'command_sent', worker_id: workerId, + command: command, command_id: retryCommandId, timestamp: new Date().toISOString() + }); + workerWs.send(JSON.stringify({ + type: 'execute_command', execution_id: executionId, command_id: retryCommandId, + command: command, worker_id: workerId, timeout: stepTimeoutMs + })); + commandId = retryCommandId; // update for next waitForCommandResult call + } results.push(result); if (!result.success) { - // Command failed, workflow should stop + // Command failed after all retries, workflow should stop return false; } }