Phase 5: Auto-cleanup and pagination for executions

Changes:
Server-side:
- Added automatic cleanup of old executions (runs daily)
- Configurable retention period via EXECUTION_RETENTION_DAYS env var (default: 30 days)
- Cleanup runs on server startup and every 24 hours
- Only cleans completed/failed executions, keeps running ones
- Added pagination support to /api/executions endpoint
- Returns total count, limit, offset, and hasMore flag

Client-side:
- Implemented "Load More" button for execution pagination
- Loads 50 executions at a time
- Appends additional executions when "Load More" clicked
- Shows total execution count info
- Backward compatible with old API format

Benefits:
- Automatic database maintenance
- Prevents execution table from growing indefinitely
- Better performance with large execution histories
- User can browse all executions via pagination
- Configurable retention policy per deployment

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-07 22:50:39 -05:00
parent fff50f19da
commit 9f972182b2
2 changed files with 81 additions and 18 deletions

View File

@@ -969,22 +969,32 @@
}
}
async function loadExecutions() {
try {
const response = await fetch('/api/executions');
const executions = await response.json();
const dashHtml = executions.length === 0 ?
'<div class="empty">No executions yet</div>' :
executions.slice(0, 5).map(e => `
<div class="execution-item" onclick="viewExecution('${e.id}')">
<span class="status ${e.status}">${e.status}</span>
<strong>${e.workflow_name || '[Quick Command]'}</strong>
<div class="timestamp">by ${e.started_by} at ${new Date(e.started_at).toLocaleString()}</div>
</div>
`).join('');
document.getElementById('dashExecutions').innerHTML = dashHtml;
let executionOffset = 0;
const executionLimit = 50;
async function loadExecutions(append = false) {
try {
if (!append) executionOffset = 0;
const response = await fetch(`/api/executions?limit=${executionLimit}&offset=${executionOffset}`);
const data = await response.json();
const executions = data.executions || data; // Handle old and new API format
// Dashboard view (always first 5)
if (!append) {
const dashHtml = executions.length === 0 ?
'<div class="empty">No executions yet</div>' :
executions.slice(0, 5).map(e => `
<div class="execution-item" onclick="viewExecution('${e.id}')">
<span class="status ${e.status}">${e.status}</span>
<strong>${e.workflow_name || '[Quick Command]'}</strong>
<div class="timestamp">by ${e.started_by} at ${new Date(e.started_at).toLocaleString()}</div>
</div>
`).join('');
document.getElementById('dashExecutions').innerHTML = dashHtml;
}
// Full execution list
const fullHtml = executions.length === 0 ?
'<div class="empty">No executions yet</div>' :
executions.map(e => `
@@ -997,12 +1007,29 @@
</div>
</div>
`).join('');
document.getElementById('executionList').innerHTML = fullHtml;
if (append) {
document.getElementById('executionList').innerHTML += fullHtml;
} else {
document.getElementById('executionList').innerHTML = fullHtml;
}
// Add "Load More" button if there are more executions
if (data.hasMore) {
const loadMoreBtn = `<button onclick="loadMoreExecutions()" style="width: 100%; margin-top: 15px;">[ Load More Executions ]</button>`;
document.getElementById('executionList').innerHTML += loadMoreBtn;
}
} catch (error) {
console.error('Error loading executions:', error);
}
}
async function loadMoreExecutions() {
executionOffset += executionLimit;
await loadExecutions(true);
}
async function clearCompletedExecutions() {
if (!confirm('Delete all completed and failed executions?')) return;