diff --git a/public/index.html b/public/index.html
index e75f9eb..9c34e3b 100644
--- a/public/index.html
+++ b/public/index.html
@@ -969,22 +969,32 @@
}
}
- async function loadExecutions() {
- try {
- const response = await fetch('/api/executions');
- const executions = await response.json();
-
- const dashHtml = executions.length === 0 ?
- '
No executions yet
' :
- executions.slice(0, 5).map(e => `
-
-
${e.status}
-
${e.workflow_name || '[Quick Command]'}
-
by ${e.started_by} at ${new Date(e.started_at).toLocaleString()}
-
- `).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 ?
+ 'No executions yet
' :
+ executions.slice(0, 5).map(e => `
+
+
${e.status}
+
${e.workflow_name || '[Quick Command]'}
+
by ${e.started_by} at ${new Date(e.started_at).toLocaleString()}
+
+ `).join('');
+ document.getElementById('dashExecutions').innerHTML = dashHtml;
+ }
+
+ // Full execution list
const fullHtml = executions.length === 0 ?
'No executions yet
' :
executions.map(e => `
@@ -997,12 +1007,29 @@
`).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 = ``;
+ 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;
diff --git a/server.js b/server.js
index bd0bce4..80032c1 100644
--- a/server.js
+++ b/server.js
@@ -98,6 +98,27 @@ async function initDatabase() {
}
}
+// Auto-cleanup old executions (runs daily)
+async function cleanupOldExecutions() {
+ try {
+ const retentionDays = parseInt(process.env.EXECUTION_RETENTION_DAYS) || 30;
+ const [result] = await pool.query(
+ `DELETE FROM executions
+ WHERE status IN ('completed', 'failed')
+ AND started_at < DATE_SUB(NOW(), INTERVAL ? DAY)`,
+ [retentionDays]
+ );
+ console.log(`[Cleanup] Removed ${result.affectedRows} executions older than ${retentionDays} days`);
+ } catch (error) {
+ console.error('[Cleanup] Error removing old executions:', error);
+ }
+}
+
+// Run cleanup daily at 3 AM
+setInterval(cleanupOldExecutions, 24 * 60 * 60 * 1000);
+// Run cleanup on startup
+cleanupOldExecutions();
+
// WebSocket connections
const clients = new Set();
const workers = new Map(); // Map worker_id -> WebSocket connection
@@ -409,10 +430,25 @@ app.post('/api/executions', authenticateSSO, async (req, res) => {
app.get('/api/executions', authenticateSSO, async (req, res) => {
try {
+ const limit = parseInt(req.query.limit) || 50;
+ const offset = parseInt(req.query.offset) || 0;
+
const [rows] = await pool.query(
- 'SELECT e.*, w.name as workflow_name FROM executions e LEFT JOIN workflows w ON e.workflow_id = w.id ORDER BY e.started_at DESC LIMIT 50'
+ 'SELECT e.*, w.name as workflow_name FROM executions e LEFT JOIN workflows w ON e.workflow_id = w.id ORDER BY e.started_at DESC LIMIT ? OFFSET ?',
+ [limit, offset]
);
- res.json(rows);
+
+ // Get total count
+ const [countRows] = await pool.query('SELECT COUNT(*) as total FROM executions');
+ const total = countRows[0].total;
+
+ res.json({
+ executions: rows,
+ total: total,
+ limit: limit,
+ offset: offset,
+ hasMore: offset + rows.length < total
+ });
} catch (error) {
res.status(500).json({ error: error.message });
}