Fix [[ ]] visual bug and add missing log type handlers

Root cause: CSS button::before/after adds [ ] universally, but many
buttons had hardcoded [ text ] content, producing [[ text ]].

- Strip manual [ ] wrappers from all button text in HTML and JS
- Fix JS textContent assignments for compare mode buttons
- Fix dynamic button HTML strings in execution details panel
- Add formatLogEntry handlers for previously unhandled action types:
  dry_run_skipped, execution_timeout, goto_error, step_error,
  workflow_result, params, server_restart_recovery
- Unknown log actions now show action name instead of raw JSON
- Add cron schedule type display in schedules list

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 23:18:18 -04:00
parent 2d6a0f1054
commit d7b26c2b70

View File

@@ -875,15 +875,15 @@
</div> </div>
<div style="display: flex; gap: 10px; align-items: center;"> <div style="display: flex; gap: 10px; align-items: center;">
<button onclick="clearFilters()" class="small">[ Clear Filters ]</button> <button onclick="clearFilters()" class="small">Clear Filters</button>
<span id="filterStats" style="color: var(--terminal-green); font-size: 0.9em; font-family: var(--font-mono);"></span> <span id="filterStats" style="color: var(--terminal-green); font-size: 0.9em; font-family: var(--font-mono);"></span>
</div> </div>
</div> </div>
<button onclick="refreshData()">[ 🔄 Refresh ]</button> <button onclick="refreshData()">🔄 Refresh</button>
<button onclick="clearCompletedExecutions()" style="margin-left: 10px;">[ 🗑️ Clear Completed ]</button> <button onclick="clearCompletedExecutions()" style="margin-left: 10px;">🗑️ Clear Completed</button>
<button onclick="toggleCompareMode()" id="compareModeBtn" style="margin-left: 10px;">[ 📊 Compare Mode ]</button> <button onclick="toggleCompareMode()" id="compareModeBtn" style="margin-left: 10px;">📊 Compare Mode</button>
<button onclick="compareSelectedExecutions()" id="compareBtn" style="margin-left: 10px; display: none;">[ ⚖️ Compare Selected ]</button> <button onclick="compareSelectedExecutions()" id="compareBtn" style="margin-left: 10px; display: none;">⚖️ Compare Selected</button>
<div id="compareInstructions" style="display: none; background: rgba(255, 176, 0, 0.1); border: 2px solid var(--terminal-amber); padding: 12px; margin: 15px 0; color: var(--terminal-amber);"> <div id="compareInstructions" style="display: none; background: rgba(255, 176, 0, 0.1); border: 2px solid var(--terminal-amber); padding: 12px; margin: 15px 0; color: var(--terminal-amber);">
Select 2-5 executions to compare their outputs. Click executions to toggle selection. Select 2-5 executions to compare their outputs. Click executions to toggle selection.
@@ -900,8 +900,8 @@
<p style="color: var(--terminal-green); margin-bottom: 20px;">Execute a command on selected workers instantly</p> <p style="color: var(--terminal-green); margin-bottom: 20px;">Execute a command on selected workers instantly</p>
<div style="display: flex; gap: 10px; margin-bottom: 15px;"> <div style="display: flex; gap: 10px; margin-bottom: 15px;">
<button onclick="showCommandTemplates()" style="flex: 0;">[ 📋 Templates ]</button> <button onclick="showCommandTemplates()" style="flex: 0;">📋 Templates</button>
<button onclick="showCommandHistory()" style="flex: 0;">[ 🕐 History ]</button> <button onclick="showCommandHistory()" style="flex: 0;">🕐 History</button>
</div> </div>
<label style="display: block; margin-bottom: 10px; font-weight: 600;">Execution Mode:</label> <label style="display: block; margin-bottom: 10px; font-weight: 600;">Execution Mode:</label>
@@ -929,16 +929,16 @@
<div class="loading">Loading workers...</div> <div class="loading">Loading workers...</div>
</div> </div>
<div style="margin-bottom: 15px;"> <div style="margin-bottom: 15px;">
<button onclick="selectAllWorkers()" class="small">[ Select All ]</button> <button onclick="selectAllWorkers()" class="small">Select All</button>
<button onclick="selectOnlineWorkers()" class="small">[ Online Only ]</button> <button onclick="selectOnlineWorkers()" class="small">Online Only</button>
<button onclick="deselectAllWorkers()" class="small">[ Clear All ]</button> <button onclick="deselectAllWorkers()" class="small">Clear All</button>
</div> </div>
</div> </div>
<label style="display: block; margin-bottom: 10px; margin-top: 20px; font-weight: 600;">Command:</label> <label style="display: block; margin-bottom: 10px; margin-top: 20px; font-weight: 600;">Command:</label>
<textarea id="quickCommand" placeholder="Enter command to execute (e.g., 'uptime' or 'df -h')"></textarea> <textarea id="quickCommand" placeholder="Enter command to execute (e.g., 'uptime' or 'df -h')"></textarea>
<button onclick="executeQuickCommand()">[ ▶️ Execute Command ]</button> <button onclick="executeQuickCommand()">▶️ Execute Command</button>
<div id="quickCommandResult" style="margin-top: 20px;"></div> <div id="quickCommandResult" style="margin-top: 20px;"></div>
</div> </div>
@@ -950,8 +950,8 @@
<h3>⏰ Scheduled Commands</h3> <h3>⏰ Scheduled Commands</h3>
<p style="color: var(--terminal-green); margin-bottom: 20px;">Automate command execution with flexible scheduling</p> <p style="color: var(--terminal-green); margin-bottom: 20px;">Automate command execution with flexible scheduling</p>
<button onclick="showCreateSchedule()">[ Create Schedule ]</button> <button onclick="showCreateSchedule()"> Create Schedule</button>
<button onclick="refreshData()" style="margin-left: 10px;">[ 🔄 Refresh ]</button> <button onclick="refreshData()" style="margin-left: 10px;">🔄 Refresh</button>
<div id="scheduleList" style="margin-top: 20px;"><div class="loading">Loading...</div></div> <div id="scheduleList" style="margin-top: 20px;"><div class="loading">Loading...</div></div>
</div> </div>
@@ -987,7 +987,7 @@
<div class="modal-content"> <div class="modal-content">
<h2>Execution Details</h2> <h2>Execution Details</h2>
<div id="executionDetails"></div> <div id="executionDetails"></div>
<button onclick="closeModal('viewExecutionModal')">[ Close ]</button> <button onclick="closeModal('viewExecutionModal')">Close</button>
</div> </div>
</div> </div>
@@ -996,7 +996,7 @@
<div class="modal-content"> <div class="modal-content">
<h2>Command Templates</h2> <h2>Command Templates</h2>
<div id="templateList" style="max-height: 400px; overflow-y: auto;"></div> <div id="templateList" style="max-height: 400px; overflow-y: auto;"></div>
<button onclick="closeModal('commandTemplatesModal')">[ Close ]</button> <button onclick="closeModal('commandTemplatesModal')">Close</button>
</div> </div>
</div> </div>
@@ -1005,7 +1005,7 @@
<div class="modal-content"> <div class="modal-content">
<h2>Command History</h2> <h2>Command History</h2>
<div id="historyList" style="max-height: 400px; overflow-y: auto;"></div> <div id="historyList" style="max-height: 400px; overflow-y: auto;"></div>
<button onclick="closeModal('commandHistoryModal')">[ Close ]</button> <button onclick="closeModal('commandHistoryModal')">Close</button>
</div> </div>
</div> </div>
@@ -1014,7 +1014,7 @@
<div class="modal-content" style="max-width: 90%; max-height: 90vh;"> <div class="modal-content" style="max-width: 90%; max-height: 90vh;">
<h2>⚖️ Execution Comparison</h2> <h2>⚖️ Execution Comparison</h2>
<div id="compareContent" style="max-height: 70vh; overflow-y: auto; padding: 20px;"></div> <div id="compareContent" style="max-height: 70vh; overflow-y: auto; padding: 20px;"></div>
<button onclick="closeModal('compareExecutionsModal')">[ Close ]</button> <button onclick="closeModal('compareExecutionsModal')">Close</button>
</div> </div>
</div> </div>
@@ -1031,7 +1031,7 @@
<div style="display:flex;gap:10px;margin-top:20px;"> <div style="display:flex;gap:10px;margin-top:20px;">
<button onclick="submitParamForm()" <button onclick="submitParamForm()"
style="flex:1;padding:8px;background:rgba(0,255,65,.1);border:1px solid var(--terminal-green);color:var(--terminal-green);font-family:var(--font-mono);cursor:pointer;font-size:.9em;"> style="flex:1;padding:8px;background:rgba(0,255,65,.1);border:1px solid var(--terminal-green);color:var(--terminal-green);font-family:var(--font-mono);cursor:pointer;font-size:.9em;">
[ ▶ Run ] ▶ Run
</button> </button>
<button onclick="closeParamModal()" <button onclick="closeParamModal()"
style="padding:8px 16px;background:transparent;border:1px solid #555;color:#888;font-family:var(--font-mono);cursor:pointer;font-size:.9em;"> style="padding:8px 16px;background:transparent;border:1px solid #555;color:#888;font-family:var(--font-mono);cursor:pointer;font-size:.9em;">
@@ -1056,7 +1056,7 @@
<input type="url" id="editWorkflowWebhookUrl" placeholder="https://example.com/webhook"> <input type="url" id="editWorkflowWebhookUrl" placeholder="https://example.com/webhook">
<div id="editWorkflowError" style="color:var(--terminal-red);font-size:0.85em;margin-top:8px;display:none;"></div> <div id="editWorkflowError" style="color:var(--terminal-red);font-size:0.85em;margin-top:8px;display:none;"></div>
<div style="margin-top:16px;display:flex;gap:10px;"> <div style="margin-top:16px;display:flex;gap:10px;">
<button onclick="saveWorkflow()">[ 💾 Save ]</button> <button onclick="saveWorkflow()">💾 Save</button>
<button onclick="closeModal('editWorkflowModal')">Cancel</button> <button onclick="closeModal('editWorkflowModal')">Cancel</button>
</div> </div>
</div> </div>
@@ -1091,8 +1091,8 @@
</div> </div>
<div style="margin-top: 20px;"> <div style="margin-top: 20px;">
<button onclick="createSchedule()">[ Create Schedule ]</button> <button onclick="createSchedule()">Create Schedule</button>
<button onclick="closeModal('createScheduleModal')" style="margin-left: 10px;">[ Cancel ]</button> <button onclick="closeModal('createScheduleModal')" style="margin-left: 10px;">Cancel</button>
</div> </div>
</div> </div>
</div> </div>
@@ -1215,7 +1215,7 @@
` : ''} ` : ''}
</div> </div>
${currentUser && currentUser.isAdmin ? ` ${currentUser && currentUser.isAdmin ? `
<button class="danger small" onclick="deleteWorker('${w.id}', '${w.name}')">[ 🗑️ Delete ]</button> <button class="danger small" onclick="deleteWorker('${w.id}', '${w.name}')">🗑️ Delete</button>
` : ''} ` : ''}
</div> </div>
</div> </div>
@@ -1292,6 +1292,8 @@
scheduleDesc = `Every ${s.schedule_value} hour(s)`; scheduleDesc = `Every ${s.schedule_value} hour(s)`;
} else if (s.schedule_type === 'daily') { } else if (s.schedule_type === 'daily') {
scheduleDesc = `Daily at ${s.schedule_value}`; scheduleDesc = `Daily at ${s.schedule_value}`;
} else if (s.schedule_type === 'cron') {
scheduleDesc = `Cron: ${s.schedule_value}`;
} }
const nextRun = s.next_run ? new Date(s.next_run).toLocaleString() : 'Not scheduled'; const nextRun = s.next_run ? new Date(s.next_run).toLocaleString() : 'Not scheduled';
@@ -1498,7 +1500,7 @@
// Add "Load More" button if there are more executions // Add "Load More" button if there are more executions
if (data.hasMore) { if (data.hasMore) {
const loadMoreBtn = `<button onclick="loadMoreExecutions()" style="width: 100%; margin-top: 15px;">[ Load More Executions ]</button>`; const loadMoreBtn = `<button onclick="loadMoreExecutions()" style="width: 100%; margin-top: 15px;">Load More Executions</button>`;
document.getElementById('executionList').innerHTML += loadMoreBtn; document.getElementById('executionList').innerHTML += loadMoreBtn;
} }
@@ -1640,13 +1642,13 @@
const instructions = document.getElementById('compareInstructions'); const instructions = document.getElementById('compareInstructions');
if (compareMode) { if (compareMode) {
btn.textContent = '[ ✗ Exit Compare Mode ]'; btn.textContent = '✗ Exit Compare Mode';
btn.style.borderColor = 'var(--terminal-amber)'; btn.style.borderColor = 'var(--terminal-amber)';
btn.style.color = 'var(--terminal-amber)'; btn.style.color = 'var(--terminal-amber)';
compareBtn.style.display = 'inline-block'; compareBtn.style.display = 'inline-block';
instructions.style.display = 'block'; instructions.style.display = 'block';
} else { } else {
btn.textContent = '[ 📊 Compare Mode ]'; btn.textContent = '📊 Compare Mode';
btn.style.borderColor = ''; btn.style.borderColor = '';
btn.style.color = ''; btn.style.color = '';
compareBtn.style.display = 'none'; compareBtn.style.display = 'none';
@@ -1672,9 +1674,9 @@
// Update compare button text // Update compare button text
const compareBtn = document.getElementById('compareBtn'); const compareBtn = document.getElementById('compareBtn');
if (selectedExecutions.size >= 2) { if (selectedExecutions.size >= 2) {
compareBtn.textContent = `[ ⚖️ Compare Selected (${selectedExecutions.size}) ]`; compareBtn.textContent = `⚖️ Compare Selected (${selectedExecutions.size})`;
} else { } else {
compareBtn.textContent = '[ ⚖️ Compare Selected ]'; compareBtn.textContent = '⚖️ Compare Selected';
} }
} }
@@ -1999,17 +2001,17 @@
// Abort button (only for running executions) // Abort button (only for running executions)
if (execution.status === 'running') { if (execution.status === 'running') {
html += `<button onclick="abortExecution('${executionId}')" style="background-color: var(--terminal-red); border-color: var(--terminal-red);">[ ⛔ Abort Execution ]</button>`; html += `<button onclick="abortExecution('${executionId}')" style="background-color: var(--terminal-red); border-color: var(--terminal-red);">⛔ Abort Execution</button>`;
} }
// Re-run button (only for quick commands with command in logs) // Re-run button (only for quick commands with command in logs)
const commandLog = execution.logs?.find(l => l.action === 'command_sent'); const commandLog = execution.logs?.find(l => l.action === 'command_sent');
if (commandLog && commandLog.command) { if (commandLog && commandLog.command) {
html += `<button onclick="rerunCommand('${escapeHtml(commandLog.command)}', '${commandLog.worker_id}')">[ 🔄 Re-run Command ]</button>`; html += `<button onclick="rerunCommand('${escapeHtml(commandLog.command)}', '${commandLog.worker_id}')">🔄 Re-run Command</button>`;
} }
// Download logs button // Download logs button
html += `<button onclick="downloadExecutionLogs('${executionId}')">[ 💾 Download Logs ]</button>`; html += `<button onclick="downloadExecutionLogs('${executionId}')">💾 Download Logs</button>`;
html += '</div>'; html += '</div>';
@@ -2170,8 +2172,87 @@
`; `;
} }
if (log.action === 'dry_run_skipped') {
return `
<div class="log-entry" style="border-left-color: var(--terminal-amber);">
<div class="log-timestamp">[${timestamp}]</div>
<div class="log-title" style="color: var(--terminal-amber);">🔍 [DRY RUN] Step ${log.step} Skipped: ${escapeHtml(log.step_name || '')}</div>
</div>
`;
}
if (log.action === 'execution_timeout') {
return `
<div class="log-entry failed">
<div class="log-timestamp">[${timestamp}]</div>
<div class="log-title">⏱️ Execution Timeout</div>
<div class="log-details">
<div class="log-field">${escapeHtml(log.message || 'Execution exceeded maximum allowed time')}</div>
</div>
</div>
`;
}
if (log.action === 'goto_error') {
return `
<div class="log-entry failed">
<div class="log-timestamp">[${timestamp}]</div>
<div class="log-title">✗ Goto Error</div>
<div class="log-details">
<div class="log-field"><span class="log-label">Target:</span> ${escapeHtml(String(log.target || ''))}</div>
</div>
</div>
`;
}
if (log.action === 'step_error') {
return `
<div class="log-entry failed">
<div class="log-timestamp">[${timestamp}]</div>
<div class="log-title">✗ Step ${log.step} Error: ${escapeHtml(log.step_name || '')}</div>
<div class="log-details">
<div class="log-field"><span class="log-label">Error:</span> ${escapeHtml(log.error || '')}</div>
</div>
</div>
`;
}
if (log.action === 'workflow_result') {
const statusIcon = log.success ? '✓' : '✗';
const statusClass = log.success ? 'success' : 'failed';
return `
<div class="log-entry ${statusClass}">
<div class="log-timestamp">[${timestamp}]</div>
<div class="log-title">${statusIcon} Workflow Result: ${log.success ? 'Success' : 'Failed'}</div>
${log.message ? `<div class="log-details"><div class="log-field">${escapeHtml(log.message)}</div></div>` : ''}
</div>
`;
}
if (log.action === 'params') {
const paramStr = Object.entries(log.params || {}).map(([k, v]) => `${escapeHtml(k)}=${escapeHtml(String(v))}`).join(', ');
return `
<div class="log-entry" style="border-left-color: #555;">
<div class="log-timestamp">[${timestamp}]</div>
<div class="log-title" style="color: #888;">⚙ Parameters: ${paramStr || '(none)'}</div>
</div>
`;
}
if (log.action === 'server_restart_recovery') {
return `
<div class="log-entry failed">
<div class="log-timestamp">[${timestamp}]</div>
<div class="log-title" style="color: var(--terminal-red);">⚠️ Server Restart Recovery</div>
<div class="log-details">
<div class="log-field">${escapeHtml(log.message || 'Execution interrupted by server restart')}</div>
</div>
</div>
`;
}
// Fallback for unknown log types // Fallback for unknown log types
return `<div class="log-entry"><pre>${JSON.stringify(log, null, 2)}</pre></div>`; return `<div class="log-entry" style="border-left-color:#555;"><div class="log-timestamp">[${timestamp}]</div><div class="log-title" style="color:#666;">${escapeHtml(log.action || 'unknown')}</div></div>`;
} }
function escapeHtml(text) { function escapeHtml(text) {