2025-11-29 19:26:20 -05:00
const express = require ( 'express' ) ;
const http = require ( 'http' ) ;
const WebSocket = require ( 'ws' ) ;
const mysql = require ( 'mysql2/promise' ) ;
const crypto = require ( 'crypto' ) ;
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
const vm = require ( 'vm' ) ;
2025-11-29 19:26:20 -05:00
require ( 'dotenv' ) . config ( ) ;
const app = express ( ) ;
const server = http . createServer ( app ) ;
const wss = new WebSocket . Server ( { server } ) ;
// Middleware
app . use ( express . json ( ) ) ;
app . use ( express . static ( 'public' ) ) ;
// Database pool
const pool = mysql . createPool ( {
host : process . env . DB _HOST ,
port : process . env . DB _PORT || 3306 ,
user : process . env . DB _USER ,
password : process . env . DB _PASSWORD ,
database : process . env . DB _NAME ,
waitForConnections : true ,
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
connectionLimit : 50 ,
2025-11-29 19:26:20 -05:00
queueLimit : 0
} ) ;
// Initialize database tables
async function initDatabase ( ) {
const connection = await pool . getConnection ( ) ;
try {
await connection . query ( `
CREATE TABLE IF NOT EXISTS users (
id VARCHAR ( 36 ) PRIMARY KEY ,
username VARCHAR ( 255 ) UNIQUE NOT NULL ,
display _name VARCHAR ( 255 ) ,
email VARCHAR ( 255 ) ,
groups TEXT ,
last _login TIMESTAMP ,
created _at TIMESTAMP DEFAULT CURRENT _TIMESTAMP
)
` );
2026-01-07 22:11:07 -05:00
// Database schema is managed manually - migrations removed after direct database fixes
2026-01-07 20:28:47 -05:00
2025-11-29 19:26:20 -05:00
await connection . query ( `
CREATE TABLE IF NOT EXISTS workers (
id VARCHAR ( 36 ) PRIMARY KEY ,
name VARCHAR ( 255 ) UNIQUE NOT NULL ,
status VARCHAR ( 50 ) NOT NULL ,
last _heartbeat TIMESTAMP NULL ,
api _key VARCHAR ( 255 ) ,
metadata JSON ,
INDEX idx _status ( status ) ,
INDEX idx _heartbeat ( last _heartbeat )
)
` );
await connection . query ( `
CREATE TABLE IF NOT EXISTS workflows (
id VARCHAR ( 36 ) PRIMARY KEY ,
name VARCHAR ( 255 ) NOT NULL ,
description TEXT ,
definition JSON NOT NULL ,
created _by VARCHAR ( 255 ) ,
created _at TIMESTAMP DEFAULT CURRENT _TIMESTAMP ,
updated _at TIMESTAMP DEFAULT CURRENT _TIMESTAMP ON UPDATE CURRENT _TIMESTAMP ,
INDEX idx _name ( name )
)
` );
await connection . query ( `
CREATE TABLE IF NOT EXISTS executions (
id VARCHAR ( 36 ) PRIMARY KEY ,
2026-01-07 20:27:02 -05:00
workflow _id VARCHAR ( 36 ) NULL ,
2025-11-29 19:26:20 -05:00
status VARCHAR ( 50 ) NOT NULL ,
started _by VARCHAR ( 255 ) ,
started _at TIMESTAMP NULL ,
completed _at TIMESTAMP NULL ,
logs JSON ,
INDEX idx _workflow ( workflow _id ) ,
INDEX idx _status ( status ) ,
INDEX idx _started ( started _at )
)
` );
2026-01-07 23:13:27 -05:00
await connection . query ( `
CREATE TABLE IF NOT EXISTS scheduled _commands (
id VARCHAR ( 36 ) PRIMARY KEY ,
name VARCHAR ( 255 ) NOT NULL ,
command TEXT NOT NULL ,
worker _ids JSON NOT NULL ,
schedule _type VARCHAR ( 50 ) NOT NULL ,
schedule _value VARCHAR ( 255 ) NOT NULL ,
enabled BOOLEAN DEFAULT TRUE ,
created _by VARCHAR ( 255 ) ,
created _at TIMESTAMP DEFAULT CURRENT _TIMESTAMP ,
last _run TIMESTAMP NULL ,
next _run TIMESTAMP NULL ,
INDEX idx _enabled ( enabled ) ,
INDEX idx _next _run ( next _run )
)
` );
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
// Recover stale executions from a previous server crash
const [ staleExecs ] = await connection . query ( "SELECT id FROM executions WHERE status = 'running'" ) ;
if ( staleExecs . length > 0 ) {
for ( const exec of staleExecs ) {
await connection . query (
"UPDATE executions SET status = 'failed', completed_at = NOW() WHERE id = ?" ,
[ exec . id ]
) ;
await connection . query (
"UPDATE executions SET logs = JSON_ARRAY_APPEND(COALESCE(logs, '[]'), '$', CAST(? AS JSON)) WHERE id = ?" ,
[ JSON . stringify ( { action : 'server_restart_recovery' , message : 'Execution marked failed due to server restart' , timestamp : new Date ( ) . toISOString ( ) } ) , exec . id ]
) ;
}
console . log ( ` [Recovery] Marked ${ staleExecs . length } stale execution(s) as failed ` ) ;
}
2025-11-29 19:26:20 -05:00
console . log ( 'Database tables initialized successfully' ) ;
} catch ( error ) {
console . error ( 'Database initialization error:' , error ) ;
throw error ;
} finally {
connection . release ( ) ;
}
}
2026-03-03 16:04:22 -05:00
// Auto-cleanup old executions (runs hourly)
2026-01-07 22:50:39 -05:00
async function cleanupOldExecutions ( ) {
try {
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
const retentionDays = parseInt ( process . env . EXECUTION _RETENTION _DAYS ) || 30 ;
2026-01-07 22:50:39 -05:00
const [ result ] = await pool . query (
` DELETE FROM executions
WHERE status IN ( 'completed' , 'failed' )
AND started _at < DATE _SUB ( NOW ( ) , INTERVAL ? DAY ) ` ,
[ retentionDays ]
) ;
2026-03-03 16:04:22 -05:00
if ( result . affectedRows > 0 ) {
console . log ( ` [Cleanup] Removed ${ result . affectedRows } executions older than ${ retentionDays } day(s) ` ) ;
}
2026-01-07 22:50:39 -05:00
} catch ( error ) {
console . error ( '[Cleanup] Error removing old executions:' , error ) ;
}
}
2026-03-03 16:04:22 -05:00
// Run cleanup hourly
setInterval ( cleanupOldExecutions , 60 * 60 * 1000 ) ;
2026-01-07 22:50:39 -05:00
// Run cleanup on startup
cleanupOldExecutions ( ) ;
2026-01-07 23:13:27 -05:00
// Scheduled Commands Processor
async function processScheduledCommands ( ) {
try {
const [ schedules ] = await pool . query (
` SELECT * FROM scheduled_commands
WHERE enabled = TRUE
AND ( next _run IS NULL OR next _run <= NOW ( ) ) `
) ;
for ( const schedule of schedules ) {
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
// Prevent overlapping execution — skip if a previous run is still active
const [ runningExecs ] = await pool . query (
"SELECT id FROM executions WHERE started_by = ? AND status = 'running'" ,
[ ` scheduler: ${ schedule . name } ` ]
) ;
if ( runningExecs . length > 0 ) {
console . log ( ` [Scheduler] Skipping " ${ schedule . name } " - previous execution still running ` ) ;
continue ;
}
2026-01-07 23:13:27 -05:00
console . log ( ` [Scheduler] Running scheduled command: ${ schedule . name } ` ) ;
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
// Handle both string (raw SQL) and object (auto-parsed by MySQL2 JSON column)
const workerIds = typeof schedule . worker _ids === 'string'
? JSON . parse ( schedule . worker _ids )
: schedule . worker _ids ;
2026-01-07 23:13:27 -05:00
// Execute command on each worker
for ( const workerId of workerIds ) {
const workerWs = workers . get ( workerId ) ;
if ( workerWs && workerWs . readyState === WebSocket . OPEN ) {
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
const executionId = crypto . randomUUID ( ) ;
2026-01-07 23:13:27 -05:00
// Create execution record
await pool . query (
'INSERT INTO executions (id, workflow_id, status, started_by, started_at, logs) VALUES (?, ?, ?, ?, NOW(), ?)' ,
[ executionId , null , 'running' , ` scheduler: ${ schedule . name } ` , JSON . stringify ( [ {
step : 'scheduled_command' ,
action : 'command_sent' ,
worker _id : workerId ,
command : schedule . command ,
timestamp : new Date ( ) . toISOString ( )
} ] ) ]
) ;
// Send command to worker
workerWs . send ( JSON . stringify ( {
type : 'execute_command' ,
execution _id : executionId ,
command : schedule . command ,
worker _id : workerId ,
timeout : 300000 // 5 minute timeout for scheduled commands
} ) ) ;
broadcast ( { type : 'execution_started' , execution _id : executionId , workflow _id : null } ) ;
}
}
// Update last_run and calculate next_run
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
let nextRun ;
try {
nextRun = calculateNextRun ( schedule . schedule _type , schedule . schedule _value ) ;
} catch ( err ) {
console . error ( ` [Scheduler] Invalid schedule config for " ${ schedule . name } ": ${ err . message } ` ) ;
continue ;
}
2026-01-07 23:13:27 -05:00
await pool . query (
'UPDATE scheduled_commands SET last_run = NOW(), next_run = ? WHERE id = ?' ,
[ nextRun , schedule . id ]
) ;
}
} catch ( error ) {
console . error ( '[Scheduler] Error processing scheduled commands:' , error ) ;
}
}
function calculateNextRun ( scheduleType , scheduleValue ) {
const now = new Date ( ) ;
if ( scheduleType === 'interval' ) {
// Interval in minutes
const minutes = parseInt ( scheduleValue ) ;
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
if ( isNaN ( minutes ) || minutes <= 0 ) throw new Error ( ` Invalid interval value: ${ scheduleValue } ` ) ;
2026-01-07 23:13:27 -05:00
return new Date ( now . getTime ( ) + minutes * 60000 ) ;
} else if ( scheduleType === 'daily' ) {
// Daily at HH:MM
const [ hours , minutes ] = scheduleValue . split ( ':' ) . map ( Number ) ;
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
if ( isNaN ( hours ) || isNaN ( minutes ) ) throw new Error ( ` Invalid daily time format: ${ scheduleValue } ` ) ;
2026-01-07 23:13:27 -05:00
const next = new Date ( now ) ;
next . setHours ( hours , minutes , 0 , 0 ) ;
// If time has passed today, schedule for tomorrow
if ( next <= now ) {
next . setDate ( next . getDate ( ) + 1 ) ;
}
return next ;
} else if ( scheduleType === 'hourly' ) {
// Every N hours
const hours = parseInt ( scheduleValue ) ;
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
if ( isNaN ( hours ) || hours <= 0 ) throw new Error ( ` Invalid hourly value: ${ scheduleValue } ` ) ;
2026-01-07 23:13:27 -05:00
return new Date ( now . getTime ( ) + hours * 3600000 ) ;
}
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
throw new Error ( ` Unknown schedule type: ${ scheduleType } ` ) ;
2026-01-07 23:13:27 -05:00
}
// Run scheduler every minute
setInterval ( processScheduledCommands , 60 * 1000 ) ;
// Initial run on startup
setTimeout ( processScheduledCommands , 5000 ) ;
2025-11-29 19:26:20 -05:00
// WebSocket connections
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
const browserClients = new Set ( ) ; // Browser UI connections
const workerClients = new Set ( ) ; // Worker agent connections
2026-01-07 22:23:02 -05:00
const workers = new Map ( ) ; // Map worker_id -> WebSocket connection
2025-11-29 19:26:20 -05:00
wss . on ( 'connection' , ( ws ) => {
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
// Default to browser client until worker_connect identifies it as a worker
browserClients . add ( ws ) ;
2026-01-07 20:20:18 -05:00
// Handle incoming messages from workers
ws . on ( 'message' , async ( data ) => {
try {
const message = JSON . parse ( data . toString ( ) ) ;
console . log ( 'WebSocket message received:' , message . type ) ;
if ( message . type === 'command_result' ) {
// Handle command result from worker
2026-01-07 23:19:12 -05:00
const { execution _id , command _id , worker _id , success , stdout , stderr , duration , timestamp } = message ;
2026-01-07 20:20:18 -05:00
// Add result to execution logs
await addExecutionLog ( execution _id , {
step : 'command_execution' ,
action : 'command_result' ,
2026-01-07 23:19:12 -05:00
command _id : command _id , // Include command_id for workflow tracking
2026-01-07 20:20:18 -05:00
worker _id : worker _id ,
success : success ,
stdout : stdout ,
stderr : stderr ,
duration : duration ,
timestamp : timestamp || new Date ( ) . toISOString ( )
} ) ;
2026-01-07 23:19:12 -05:00
// For non-workflow executions, update status immediately
// For workflow executions, the workflow engine will update status
2026-03-04 16:26:18 -05:00
const [ execution ] = await pool . query ( 'SELECT workflow_id, started_by FROM executions WHERE id = ?' , [ execution _id ] ) ;
const startedBy = execution . length > 0 ? ( execution [ 0 ] . started _by || '' ) : '' ;
const isAutomated = startedBy . startsWith ( 'gandalf:' ) || startedBy . startsWith ( 'scheduler:' ) ;
2026-01-07 23:19:12 -05:00
if ( execution . length > 0 && ! execution [ 0 ] . workflow _id ) {
// Only update status for quick commands (no workflow_id)
const finalStatus = success ? 'completed' : 'failed' ;
await updateExecutionStatus ( execution _id , finalStatus ) ;
}
2026-01-07 22:23:02 -05:00
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
// Resolve any pending event-driven command promise (eliminates DB polling)
if ( command _id ) {
const resolver = _commandResolvers . get ( command _id ) ;
if ( resolver ) {
resolver ( { success , stdout , stderr } ) ;
}
}
// Broadcast result to browser clients only
2026-01-07 20:20:18 -05:00
broadcast ( {
type : 'command_result' ,
execution _id : execution _id ,
worker _id : worker _id ,
success : success ,
stdout : stdout ,
2026-03-04 16:26:18 -05:00
stderr : stderr ,
is _automated : isAutomated ,
2026-01-07 20:20:18 -05:00
} ) ;
2026-01-07 23:19:12 -05:00
console . log ( ` Command result received for execution ${ execution _id } : ${ success ? 'success' : 'failed' } ` ) ;
2026-01-07 20:20:18 -05:00
}
if ( message . type === 'workflow_result' ) {
// Handle workflow result from worker
const { execution _id , worker _id , success , message : resultMessage , timestamp } = message ;
// Add final result to logs
await addExecutionLog ( execution _id , {
step : 'workflow_completion' ,
action : 'workflow_result' ,
worker _id : worker _id ,
success : success ,
message : resultMessage ,
timestamp : timestamp || new Date ( ) . toISOString ( )
} ) ;
// Update execution status
const finalStatus = success ? 'completed' : 'failed' ;
await updateExecutionStatus ( execution _id , finalStatus ) ;
// Broadcast completion to all clients
broadcast ( {
type : 'workflow_result' ,
execution _id : execution _id ,
status : finalStatus ,
success : success ,
message : resultMessage
} ) ;
console . log ( ` Workflow result received for execution ${ execution _id } : ${ finalStatus } ` ) ;
}
if ( message . type === 'worker_connect' ) {
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
// Authenticate worker — reject if api_key is provided but wrong
const { worker _id , worker _name , api _key } = message ;
if ( api _key && api _key !== process . env . WORKER _API _KEY ) {
console . warn ( ` [Security] Worker connection rejected: invalid API key from " ${ worker _name } " ` ) ;
ws . close ( 4001 , 'Unauthorized' ) ;
return ;
}
// Move from browser set to worker set
browserClients . delete ( ws ) ;
workerClients . add ( ws ) ;
2026-01-07 20:20:18 -05:00
console . log ( ` Worker connected: ${ worker _name } ( ${ worker _id } ) ` ) ;
2026-01-07 22:29:23 -05:00
// Find the database worker ID by name
const [ dbWorkers ] = await pool . query (
'SELECT id FROM workers WHERE name = ?' ,
[ worker _name ]
2026-01-07 20:20:18 -05:00
) ;
2026-01-07 22:29:23 -05:00
if ( dbWorkers . length > 0 ) {
const dbWorkerId = dbWorkers [ 0 ] . id ;
// Store worker WebSocket connection using BOTH IDs
workers . set ( worker _id , ws ) ; // Runtime ID
workers . set ( dbWorkerId , ws ) ; // Database ID
// Store mapping for cleanup
ws . workerId = worker _id ;
ws . dbWorkerId = dbWorkerId ;
console . log ( ` Mapped worker: runtime_id= ${ worker _id } , db_id= ${ dbWorkerId } , name= ${ worker _name } ` ) ;
// Update worker status to online
await pool . query (
` UPDATE workers SET status='online', last_heartbeat=NOW() WHERE id=? ` ,
[ dbWorkerId ]
) ;
// Broadcast worker status update with database ID
broadcast ( {
type : 'worker_update' ,
worker _id : dbWorkerId ,
status : 'online'
} ) ;
} else {
console . log ( ` Worker ${ worker _name } not found in database, will be created on heartbeat ` ) ;
}
2026-01-07 20:20:18 -05:00
}
if ( message . type === 'pong' ) {
// Handle worker pong response
const { worker _id } = message ;
await pool . query (
` UPDATE workers SET last_heartbeat=NOW() WHERE id=? ` ,
[ worker _id ]
) ;
}
} catch ( error ) {
console . error ( 'WebSocket message error:' , error ) ;
}
} ) ;
2026-01-07 22:23:02 -05:00
ws . on ( 'close' , ( ) => {
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
browserClients . delete ( ws ) ;
workerClients . delete ( ws ) ;
2026-01-07 22:29:23 -05:00
// Remove worker from workers map when disconnected (both runtime and db IDs)
if ( ws . workerId ) {
workers . delete ( ws . workerId ) ;
console . log ( ` Worker ${ ws . workerId } (runtime ID) disconnected ` ) ;
}
if ( ws . dbWorkerId ) {
workers . delete ( ws . dbWorkerId ) ;
console . log ( ` Worker ${ ws . dbWorkerId } (database ID) disconnected ` ) ;
2026-01-07 22:23:02 -05:00
}
} ) ;
2025-11-29 19:26:20 -05:00
} ) ;
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
// Broadcast to browser clients only (NOT worker agents)
2025-11-29 19:26:20 -05:00
function broadcast ( data ) {
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
browserClients . forEach ( client => {
2025-11-29 19:26:20 -05:00
if ( client . readyState === WebSocket . OPEN ) {
client . send ( JSON . stringify ( data ) ) ;
}
} ) ;
}
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
// Helper function to add log entry to execution (atomic — no read-modify-write race condition)
2026-01-08 22:03:00 -05:00
async function addExecutionLog ( executionId , logEntry ) {
try {
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
await pool . query (
"UPDATE executions SET logs = JSON_ARRAY_APPEND(COALESCE(logs, '[]'), '$', CAST(? AS JSON)) WHERE id = ?" ,
[ JSON . stringify ( logEntry ) , executionId ]
) ;
2026-01-08 22:03:00 -05:00
} catch ( error ) {
console . error ( ` [Workflow] Error adding execution log: ` , error ) ;
}
}
// Helper function to update execution status
async function updateExecutionStatus ( executionId , status ) {
try {
await pool . query (
'UPDATE executions SET status = ?, completed_at = NOW() WHERE id = ?' ,
[ status , executionId ]
) ;
broadcast ( {
type : 'execution_status' ,
execution _id : executionId ,
status : status
} ) ;
console . log ( ` [Workflow] Execution ${ executionId } status updated to: ${ status } ` ) ;
} catch ( error ) {
console . error ( ` [Workflow] Error updating execution status: ` , error ) ;
}
}
2025-11-29 19:26:20 -05:00
// Authelia SSO Middleware
async function authenticateSSO ( req , res , next ) {
// Check for Authelia headers
const remoteUser = req . headers [ 'remote-user' ] ;
const remoteName = req . headers [ 'remote-name' ] ;
const remoteEmail = req . headers [ 'remote-email' ] ;
const remoteGroups = req . headers [ 'remote-groups' ] ;
if ( ! remoteUser ) {
return res . status ( 401 ) . json ( {
error : 'Not authenticated' ,
message : 'Please access this service through auth.lotusguild.org'
} ) ;
}
// Check if user is in allowed groups (admin or employee)
const groups = remoteGroups ? remoteGroups . split ( ',' ) . map ( g => g . trim ( ) ) : [ ] ;
const allowedGroups = [ 'admin' , 'employee' ] ;
const hasAccess = groups . some ( g => allowedGroups . includes ( g ) ) ;
if ( ! hasAccess ) {
return res . status ( 403 ) . json ( {
error : 'Access denied' ,
message : 'You must be in admin or employee group'
} ) ;
}
// Store/update user in database
try {
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
const userId = crypto . randomUUID ( ) ;
2025-11-29 19:26:20 -05:00
await pool . query (
` INSERT INTO users (id, username, display_name, email, groups, last_login)
VALUES ( ? , ? , ? , ? , ? , NOW ( ) )
ON DUPLICATE KEY UPDATE
display _name = VALUES ( display _name ) ,
email = VALUES ( email ) ,
groups = VALUES ( groups ) ,
last _login = NOW ( ) ` ,
[ userId , remoteUser , remoteName , remoteEmail , remoteGroups ]
) ;
} catch ( error ) {
console . error ( 'Error updating user:' , error ) ;
}
// Attach user info to request
req . user = {
username : remoteUser ,
name : remoteName || remoteUser ,
email : remoteEmail || '' ,
groups : groups ,
isAdmin : groups . includes ( 'admin' )
} ;
next ( ) ;
}
2026-03-03 16:04:22 -05:00
// Gandalf machine-to-machine API key auth
function authenticateGandalf ( req , res , next ) {
const apiKey = req . headers [ 'x-gandalf-api-key' ] ;
if ( ! apiKey || apiKey !== process . env . GANDALF _API _KEY ) {
return res . status ( 401 ) . json ( { error : 'Unauthorized' } ) ;
}
req . user = { username : 'gandalf:link_stats' , isAdmin : false } ;
next ( ) ;
}
2026-01-07 23:19:12 -05:00
// Workflow Execution Engine
2026-03-03 16:20:05 -05:00
// Substitute {{param_name}} placeholders in a command string.
// Only alphanumeric + safe punctuation allowed in substituted values.
function applyParams ( command , params ) {
return command . replace ( /\{\{(\w+)\}\}/g , ( match , key ) => {
if ( ! ( key in params ) ) return match ;
const val = String ( params [ key ] ) . trim ( ) ;
if ( ! /^[a-zA-Z0-9._:@\-\/]+$/ . test ( val ) ) {
throw new Error ( ` Unsafe value for workflow parameter " ${ key } " ` ) ;
}
return val ;
} ) ;
}
2026-03-03 16:55:02 -05:00
// Evaluate a condition string against execution state and params.
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
// Uses vm.runInNewContext with a timeout to avoid arbitrary code execution risk.
2026-03-03 16:55:02 -05:00
function evalCondition ( condition , state , params ) {
try {
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
const context = vm . createContext ( { state , params , promptResponse : state . promptResponse } ) ;
return ! ! vm . runInNewContext ( condition , context , { timeout : 100 } ) ;
2026-03-03 16:55:02 -05:00
} catch ( e ) {
return false ;
}
}
// Per-execution mutable state (params + user-keyed state dict).
// Survives across step boundaries; cleaned up when execution ends.
const _executionState = new Map ( ) ; // executionId → { params, state }
// Pending prompt resolvers — set when a prompt step is waiting for user input.
const _executionPrompts = new Map ( ) ; // executionId → resolve fn
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
const _commandResolvers = new Map ( ) ; // commandId → resolve fn (event-driven result delivery)
2026-03-03 16:55:02 -05:00
2026-03-03 16:20:05 -05:00
async function executeWorkflowSteps ( executionId , workflowId , definition , username , params = { } ) {
2026-03-03 16:55:02 -05:00
_executionState . set ( executionId , { params , state : { } } ) ;
2026-01-07 23:19:12 -05:00
try {
console . log ( ` [Workflow] Starting execution ${ executionId } for workflow ${ workflowId } ` ) ;
const steps = definition . steps || [ ] ;
2026-03-03 16:55:02 -05:00
// Build step-id → index map for goto support
const stepIdMap = new Map ( ) ;
steps . forEach ( ( step , i ) => { if ( step . id ) stepIdMap . set ( step . id , i ) ; } ) ;
let currentIndex = 0 ;
while ( currentIndex < steps . length ) {
const step = steps [ currentIndex ] ;
const execState = _executionState . get ( executionId ) ;
const stepLabel = step . name || step . id || ` Step ${ currentIndex + 1 } ` ;
console . log ( ` [Workflow] ${ executionId } — step ${ currentIndex + 1 } : ${ stepLabel } ` ) ;
2026-01-07 23:19:12 -05:00
await addExecutionLog ( executionId , {
2026-03-03 16:55:02 -05:00
step : currentIndex + 1 , step _name : stepLabel , action : 'step_started' ,
2026-01-07 23:19:12 -05:00
timestamp : new Date ( ) . toISOString ( )
} ) ;
2026-03-03 16:55:02 -05:00
broadcast ( { type : 'workflow_step_started' , execution _id : executionId ,
step : currentIndex + 1 , step _name : stepLabel } ) ;
2026-01-07 23:19:12 -05:00
2026-03-03 16:55:02 -05:00
let gotoId = step . goto || null ; // may be overridden by prompt routes
2026-01-07 23:19:12 -05:00
if ( step . type === 'execute' ) {
2026-03-03 16:55:02 -05:00
const condOk = ! step . condition || evalCondition ( step . condition , execState . state , execState . params ) ;
if ( condOk ) {
const success = await executeCommandStep ( executionId , step , currentIndex + 1 , params ) ;
if ( ! success ) {
await updateExecutionStatus ( executionId , 'failed' ) ;
return ;
}
} else {
await addExecutionLog ( executionId , {
step : currentIndex + 1 , action : 'step_skipped' ,
reason : 'condition false' , timestamp : new Date ( ) . toISOString ( )
} ) ;
2026-01-07 23:19:12 -05:00
}
2026-03-03 16:55:02 -05:00
2026-01-07 23:19:12 -05:00
} else if ( step . type === 'prompt' ) {
2026-03-03 16:55:02 -05:00
const response = await executePromptStep ( executionId , step , currentIndex + 1 ) ;
if ( response !== null ) {
execState . state [ step . key || 'lastPrompt' ] = response ;
execState . state . promptResponse = response ; // backwards compat
// Prompt routes override goto
if ( step . routes && step . routes [ response ] ) {
gotoId = step . routes [ response ] ;
}
}
} else if ( step . type === 'wait' ) {
const ms = ( step . duration || 5 ) * 1000 ;
2026-01-07 23:19:12 -05:00
await addExecutionLog ( executionId , {
2026-03-03 16:55:02 -05:00
step : currentIndex + 1 , action : 'waiting' , duration _ms : ms ,
2026-01-07 23:19:12 -05:00
timestamp : new Date ( ) . toISOString ( )
} ) ;
2026-03-03 16:55:02 -05:00
await new Promise ( r => setTimeout ( r , ms ) ) ;
2026-01-07 23:19:12 -05:00
}
await addExecutionLog ( executionId , {
2026-03-03 16:55:02 -05:00
step : currentIndex + 1 , step _name : stepLabel , action : 'step_completed' ,
2026-01-07 23:19:12 -05:00
timestamp : new Date ( ) . toISOString ( )
} ) ;
2026-03-03 16:55:02 -05:00
broadcast ( { type : 'workflow_step_completed' , execution _id : executionId ,
step : currentIndex + 1 , step _name : stepLabel } ) ;
// Determine next step
if ( gotoId === 'end' || gotoId === '__end__' ) {
break ;
} else if ( gotoId ) {
if ( stepIdMap . has ( gotoId ) ) {
currentIndex = stepIdMap . get ( gotoId ) ;
} else {
await addExecutionLog ( executionId , {
action : 'goto_error' , target : gotoId ,
message : ` No step with id " ${ gotoId } " found ` ,
timestamp : new Date ( ) . toISOString ( )
} ) ;
break ;
}
} else {
currentIndex ++ ;
}
2026-01-07 23:19:12 -05:00
}
2026-03-03 16:55:02 -05:00
await updateExecutionStatus ( executionId , 'completed' ) ;
console . log ( ` [Workflow] Execution ${ executionId } completed ` ) ;
2026-01-07 23:19:12 -05:00
} catch ( error ) {
console . error ( ` [Workflow] Execution ${ executionId } error: ` , error ) ;
await addExecutionLog ( executionId , {
2026-03-03 16:55:02 -05:00
action : 'workflow_error' , error : error . message ,
2026-01-07 23:19:12 -05:00
timestamp : new Date ( ) . toISOString ( )
} ) ;
await updateExecutionStatus ( executionId , 'failed' ) ;
2026-03-03 16:55:02 -05:00
} finally {
_executionState . delete ( executionId ) ;
_executionPrompts . delete ( executionId ) ;
2026-01-07 23:19:12 -05:00
}
}
2026-03-03 16:55:02 -05:00
// Pause execution and wait for user to respond via POST /api/executions/:id/respond.
// Resolves with the chosen option string, or null on 60-minute timeout.
async function executePromptStep ( executionId , step , stepNumber ) {
const message = step . message || 'Please choose an option:' ;
const options = step . options || [ 'Continue' ] ;
await addExecutionLog ( executionId , {
step : stepNumber , step _name : step . name , action : 'prompt' ,
message , options , timestamp : new Date ( ) . toISOString ( )
} ) ;
broadcast ( {
type : 'execution_prompt' ,
execution _id : executionId ,
prompt : { message , options , step : stepNumber , step _name : step . name }
} ) ;
return new Promise ( resolve => {
const timer = setTimeout ( ( ) => {
_executionPrompts . delete ( executionId ) ;
console . warn ( ` [Workflow] Prompt timed out for execution ${ executionId } ` ) ;
resolve ( null ) ;
} , 60 * 60 * 1000 ) ; // 60 minute timeout
_executionPrompts . set ( executionId , ( response ) => {
clearTimeout ( timer ) ;
_executionPrompts . delete ( executionId ) ;
resolve ( response ) ;
} ) ;
} ) ;
}
2026-03-03 16:20:05 -05:00
async function executeCommandStep ( executionId , step , stepNumber , params = { } ) {
2026-01-07 23:19:12 -05:00
try {
2026-03-03 16:20:05 -05:00
let command = step . command ;
if ( Object . keys ( params ) . length > 0 ) {
command = applyParams ( command , params ) ;
}
2026-01-07 23:19:12 -05:00
const targets = step . targets || [ 'all' ] ;
// Determine which workers to target
let targetWorkerIds = [ ] ;
if ( targets . includes ( 'all' ) ) {
// Get all online workers
const [ onlineWorkers ] = await pool . query ( 'SELECT id FROM workers WHERE status = ?' , [ 'online' ] ) ;
targetWorkerIds = onlineWorkers . map ( w => w . id ) ;
} else {
// Specific worker IDs or names
for ( const target of targets ) {
// Try to find by ID first, then by name
const [ workerById ] = await pool . query ( 'SELECT id FROM workers WHERE id = ?' , [ target ] ) ;
if ( workerById . length > 0 ) {
targetWorkerIds . push ( workerById [ 0 ] . id ) ;
} else {
const [ workerByName ] = await pool . query ( 'SELECT id FROM workers WHERE name = ?' , [ target ] ) ;
if ( workerByName . length > 0 ) {
targetWorkerIds . push ( workerByName [ 0 ] . id ) ;
}
}
}
}
if ( targetWorkerIds . length === 0 ) {
await addExecutionLog ( executionId , {
step : stepNumber ,
action : 'no_workers' ,
message : 'No workers available for this step' ,
timestamp : new Date ( ) . toISOString ( )
} ) ;
return false ;
}
// Execute command on each target worker and wait for results
const results = [ ] ;
for ( const workerId of targetWorkerIds ) {
const workerWs = workers . get ( workerId ) ;
if ( ! workerWs || workerWs . readyState !== WebSocket . OPEN ) {
await addExecutionLog ( executionId , {
step : stepNumber ,
action : 'worker_offline' ,
worker _id : workerId ,
timestamp : new Date ( ) . toISOString ( )
} ) ;
continue ;
}
// Send command to worker
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
const commandId = crypto . randomUUID ( ) ;
2026-01-07 23:19:12 -05:00
await addExecutionLog ( executionId , {
step : stepNumber ,
action : 'command_sent' ,
worker _id : workerId ,
command : command ,
command _id : commandId ,
timestamp : new Date ( ) . toISOString ( )
} ) ;
workerWs . send ( JSON . stringify ( {
type : 'execute_command' ,
execution _id : executionId ,
command _id : commandId ,
command : command ,
worker _id : workerId ,
timeout : 120000 // 2 minute timeout
} ) ) ;
// Wait for command result (with timeout)
const result = await waitForCommandResult ( executionId , commandId , 120000 ) ;
results . push ( result ) ;
if ( ! result . success ) {
// Command failed, workflow should stop
return false ;
}
}
// All commands succeeded
return results . every ( r => r . success ) ;
} catch ( error ) {
console . error ( ` [Workflow] Error executing command step: ` , error ) ;
await addExecutionLog ( executionId , {
step : stepNumber ,
action : 'step_error' ,
error : error . message ,
timestamp : new Date ( ) . toISOString ( )
} ) ;
return false ;
}
}
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
// Wait for a command result using event-driven promise resolution.
// The resolver is stored in _commandResolvers and called immediately when
// command_result arrives via WebSocket — no DB polling required.
2026-01-07 23:19:12 -05:00
async function waitForCommandResult ( executionId , commandId , timeout ) {
return new Promise ( ( resolve ) => {
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
const timer = setTimeout ( ( ) => {
_commandResolvers . delete ( commandId ) ;
resolve ( { success : false , error : 'Command timeout' } ) ;
} , timeout ) ;
2026-01-07 23:19:12 -05:00
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
_commandResolvers . set ( commandId , ( result ) => {
clearTimeout ( timer ) ;
_commandResolvers . delete ( commandId ) ;
resolve ( result ) ;
} ) ;
2026-01-07 23:19:12 -05:00
} ) ;
}
2025-11-29 19:26:20 -05:00
// Routes - All protected by SSO
app . get ( '/api/user' , authenticateSSO , ( req , res ) => {
res . json ( req . user ) ;
} ) ;
app . get ( '/api/workflows' , authenticateSSO , async ( req , res ) => {
try {
const [ rows ] = await pool . query ( 'SELECT * FROM workflows ORDER BY created_at DESC' ) ;
res . json ( rows ) ;
} catch ( error ) {
res . status ( 500 ) . json ( { error : error . message } ) ;
}
} ) ;
2026-03-03 16:55:02 -05:00
app . get ( '/api/workflows/:id' , authenticateSSO , async ( req , res ) => {
try {
const [ rows ] = await pool . query ( 'SELECT * FROM workflows WHERE id = ?' , [ req . params . id ] ) ;
if ( rows . length === 0 ) return res . status ( 404 ) . json ( { error : 'Not found' } ) ;
const wf = rows [ 0 ] ;
res . json ( { ... wf , definition : JSON . parse ( wf . definition || '{}' ) } ) ;
} catch ( error ) {
res . status ( 500 ) . json ( { error : error . message } ) ;
}
} ) ;
2025-11-29 19:26:20 -05:00
app . post ( '/api/workflows' , authenticateSSO , async ( req , res ) => {
try {
const { name , description , definition } = req . body ;
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
const id = crypto . randomUUID ( ) ;
2026-01-07 23:30:50 -05:00
console . log ( '[Workflow] Creating workflow:' , name ) ;
console . log ( '[Workflow] Definition:' , JSON . stringify ( definition , null , 2 ) ) ;
2025-11-29 19:26:20 -05:00
await pool . query (
'INSERT INTO workflows (id, name, description, definition, created_by) VALUES (?, ?, ?, ?, ?)' ,
[ id , name , description , JSON . stringify ( definition ) , req . user . username ]
) ;
2026-01-07 23:30:50 -05:00
console . log ( '[Workflow] Successfully inserted workflow:' , id ) ;
2025-11-29 19:26:20 -05:00
res . json ( { id , name , description , definition } ) ;
2026-01-07 23:30:50 -05:00
console . log ( '[Workflow] Broadcasting workflow_created' ) ;
2025-11-29 19:26:20 -05:00
broadcast ( { type : 'workflow_created' , workflow _id : id } ) ;
2026-01-07 23:30:50 -05:00
console . log ( '[Workflow] Broadcast complete' ) ;
2025-11-29 19:26:20 -05:00
} catch ( error ) {
2026-01-07 23:30:50 -05:00
console . error ( '[Workflow] Error creating workflow:' , error ) ;
2025-11-29 19:26:20 -05:00
res . status ( 500 ) . json ( { error : error . message } ) ;
}
} ) ;
app . delete ( '/api/workflows/:id' , authenticateSSO , async ( req , res ) => {
try {
// Only admins can delete workflows
if ( ! req . user . isAdmin ) {
return res . status ( 403 ) . json ( { error : 'Admin access required' } ) ;
}
await pool . query ( 'DELETE FROM workflows WHERE id = ?' , [ req . params . id ] ) ;
res . json ( { success : true } ) ;
broadcast ( { type : 'workflow_deleted' , workflow _id : req . params . id } ) ;
} catch ( error ) {
res . status ( 500 ) . json ( { error : error . message } ) ;
}
} ) ;
app . get ( '/api/workers' , authenticateSSO , async ( req , res ) => {
try {
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
const [ rows ] = await pool . query ( 'SELECT id, name, status, last_heartbeat, metadata FROM workers ORDER BY name' ) ;
2025-11-29 19:26:20 -05:00
res . json ( rows ) ;
} catch ( error ) {
res . status ( 500 ) . json ( { error : error . message } ) ;
}
} ) ;
app . post ( '/api/workers/heartbeat' , async ( req , res ) => {
try {
const { worker _id , name , metadata } = req . body ;
const apiKey = req . headers [ 'x-api-key' ] ;
// Verify API key
if ( apiKey !== process . env . WORKER _API _KEY ) {
return res . status ( 401 ) . json ( { error : 'Invalid API key' } ) ;
}
await pool . query (
` INSERT INTO workers (id, name, status, last_heartbeat, api_key, metadata)
VALUES ( ? , ? , 'online' , NOW ( ) , ? , ? )
ON DUPLICATE KEY UPDATE
status = 'online' ,
last _heartbeat = NOW ( ) ,
metadata = VALUES ( metadata ) ` ,
[ worker _id , name , apiKey , JSON . stringify ( metadata ) ]
) ;
broadcast ( { type : 'worker_update' , worker _id , status : 'online' } ) ;
res . json ( { success : true } ) ;
} catch ( error ) {
res . status ( 500 ) . json ( { error : error . message } ) ;
}
} ) ;
app . post ( '/api/executions' , authenticateSSO , async ( req , res ) => {
try {
2026-03-03 16:20:05 -05:00
const { workflow _id , params = { } } = req . body ;
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
const id = crypto . randomUUID ( ) ;
2026-01-07 23:19:12 -05:00
// Get workflow definition
const [ workflows ] = await pool . query ( 'SELECT * FROM workflows WHERE id = ?' , [ workflow _id ] ) ;
if ( workflows . length === 0 ) {
return res . status ( 404 ) . json ( { error : 'Workflow not found' } ) ;
}
const workflow = workflows [ 0 ] ;
const definition = typeof workflow . definition === 'string' ? JSON . parse ( workflow . definition ) : workflow . definition ;
2026-03-03 16:20:05 -05:00
// Validate required params
const paramDefs = definition . params || [ ] ;
for ( const pd of paramDefs ) {
if ( pd . required && ! params [ pd . name ] ) {
return res . status ( 400 ) . json ( { error : ` Missing required parameter: ${ pd . label || pd . name } ` } ) ;
}
}
2026-01-07 23:19:12 -05:00
// Create execution record
2026-03-03 16:20:05 -05:00
const initLogs = Object . keys ( params ) . length > 0
? [ { action : 'params' , params , timestamp : new Date ( ) . toISOString ( ) } ]
: [ ] ;
2025-11-29 19:26:20 -05:00
await pool . query (
'INSERT INTO executions (id, workflow_id, status, started_by, started_at, logs) VALUES (?, ?, ?, ?, NOW(), ?)' ,
2026-03-03 16:20:05 -05:00
[ id , workflow _id , 'running' , req . user . username , JSON . stringify ( initLogs ) ]
2025-11-29 19:26:20 -05:00
) ;
2026-01-07 23:19:12 -05:00
2025-11-29 19:26:20 -05:00
broadcast ( { type : 'execution_started' , execution _id : id , workflow _id } ) ;
2026-01-07 23:19:12 -05:00
// Start workflow execution asynchronously
2026-03-03 16:20:05 -05:00
executeWorkflowSteps ( id , workflow _id , definition , req . user . username , params ) . catch ( err => {
2026-01-07 23:19:12 -05:00
console . error ( ` [Workflow] Execution ${ id } failed: ` , err ) ;
} ) ;
2025-11-29 19:26:20 -05:00
res . json ( { id , workflow _id , status : 'running' } ) ;
} catch ( error ) {
res . status ( 500 ) . json ( { error : error . message } ) ;
}
} ) ;
app . get ( '/api/executions' , authenticateSSO , async ( req , res ) => {
try {
2026-01-07 22:50:39 -05:00
const limit = parseInt ( req . query . limit ) || 50 ;
const offset = parseInt ( req . query . offset ) || 0 ;
2026-03-03 16:04:22 -05:00
const hideInternal = req . query . hide _internal === 'true' ;
const whereClause = hideInternal
? "WHERE started_by NOT LIKE 'gandalf:%' AND started_by NOT LIKE 'scheduler:%'"
: '' ;
2026-01-07 22:50:39 -05:00
2025-11-29 19:26:20 -05:00
const [ rows ] = await pool . query (
2026-03-03 16:04:22 -05:00
` SELECT e.*, w.name as workflow_name FROM executions e LEFT JOIN workflows w ON e.workflow_id = w.id ${ whereClause } ORDER BY e.started_at DESC LIMIT ? OFFSET ? ` ,
2026-01-07 22:50:39 -05:00
[ limit , offset ]
2025-11-29 19:26:20 -05:00
) ;
2026-01-07 22:50:39 -05:00
// Get total count
2026-03-03 16:04:22 -05:00
const [ countRows ] = await pool . query ( ` SELECT COUNT(*) as total FROM executions ${ whereClause } ` ) ;
2026-01-07 22:50:39 -05:00
const total = countRows [ 0 ] . total ;
res . json ( {
executions : rows ,
total : total ,
limit : limit ,
offset : offset ,
hasMore : offset + rows . length < total
} ) ;
2025-11-29 19:26:20 -05:00
} catch ( error ) {
res . status ( 500 ) . json ( { error : error . message } ) ;
}
} ) ;
2026-01-07 22:36:51 -05:00
app . delete ( '/api/executions/:id' , authenticateSSO , async ( req , res ) => {
try {
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
if ( ! req . user . isAdmin ) return res . status ( 403 ) . json ( { error : 'Admin access required' } ) ;
2026-01-07 22:36:51 -05:00
await pool . query ( 'DELETE FROM executions WHERE id = ?' , [ req . params . id ] ) ;
broadcast ( { type : 'execution_deleted' , execution _id : req . params . id } ) ;
res . json ( { success : true } ) ;
} catch ( error ) {
res . status ( 500 ) . json ( { error : error . message } ) ;
}
} ) ;
2026-01-08 22:11:59 -05:00
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' ) ;
2026-03-03 16:55:02 -05:00
// Unblock any pending prompt so the thread can exit
const pending = _executionPrompts . get ( executionId ) ;
if ( pending ) pending ( null ) ;
2026-01-08 22:11:59 -05:00
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 } ) ;
}
} ) ;
2026-03-03 16:55:02 -05:00
// Respond to a pending prompt in a running execution
app . post ( '/api/executions/:id/respond' , authenticateSSO , async ( req , res ) => {
try {
const { id } = req . params ;
const { response } = req . body ;
if ( ! response ) return res . status ( 400 ) . json ( { error : 'response is required' } ) ;
const pending = _executionPrompts . get ( id ) ;
if ( ! pending ) return res . status ( 404 ) . json ( { error : 'No pending prompt for this execution' } ) ;
await addExecutionLog ( id , {
action : 'prompt_response' , response ,
responded _by : req . user . username ,
timestamp : new Date ( ) . toISOString ( )
} ) ;
broadcast ( { type : 'prompt_response' , execution _id : id , response } ) ;
pending ( response ) ;
res . json ( { success : true } ) ;
} catch ( error ) {
res . status ( 500 ) . json ( { error : error . message } ) ;
}
} ) ;
// Edit a workflow definition (admin only)
app . put ( '/api/workflows/:id' , authenticateSSO , async ( req , res ) => {
try {
if ( ! req . user . isAdmin ) return res . status ( 403 ) . json ( { error : 'Admin only' } ) ;
const { id } = req . params ;
const { name , description , definition } = req . body ;
if ( ! name || ! definition ) return res . status ( 400 ) . json ( { error : 'name and definition required' } ) ;
// Validate definition is parseable JSON
let defObj ;
try {
defObj = typeof definition === 'string' ? JSON . parse ( definition ) : definition ;
} catch ( e ) {
return res . status ( 400 ) . json ( { error : 'Invalid JSON in definition' } ) ;
}
const [ result ] = await pool . query (
'UPDATE workflows SET name=?, description=?, definition=?, updated_at=NOW() WHERE id=?' ,
[ name , description || '' , JSON . stringify ( defObj ) , id ]
) ;
if ( result . affectedRows === 0 ) return res . status ( 404 ) . json ( { error : 'Workflow not found' } ) ;
broadcast ( { type : 'workflow_updated' , workflow _id : id } ) ;
res . json ( { success : true } ) ;
} catch ( error ) {
res . status ( 500 ) . json ( { error : error . message } ) ;
}
} ) ;
2026-01-07 23:13:27 -05:00
// Scheduled Commands API
app . get ( '/api/scheduled-commands' , authenticateSSO , async ( req , res ) => {
try {
const [ schedules ] = await pool . query (
'SELECT * FROM scheduled_commands ORDER BY created_at DESC'
) ;
res . json ( schedules ) ;
} catch ( error ) {
res . status ( 500 ) . json ( { error : error . message } ) ;
}
} ) ;
app . post ( '/api/scheduled-commands' , authenticateSSO , async ( req , res ) => {
try {
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
if ( ! req . user . isAdmin ) return res . status ( 403 ) . json ( { error : 'Admin access required' } ) ;
2026-01-07 23:13:27 -05:00
const { name , command , worker _ids , schedule _type , schedule _value } = req . body ;
if ( ! name || ! command || ! worker _ids || ! schedule _type || ! schedule _value ) {
return res . status ( 400 ) . json ( { error : 'Missing required fields' } ) ;
}
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
const id = crypto . randomUUID ( ) ;
2026-01-07 23:13:27 -05:00
const nextRun = calculateNextRun ( schedule _type , schedule _value ) ;
await pool . query (
` INSERT INTO scheduled_commands
( id , name , command , worker _ids , schedule _type , schedule _value , created _by , next _run )
VALUES ( ? , ? , ? , ? , ? , ? , ? , ? ) ` ,
[ id , name , command , JSON . stringify ( worker _ids ) , schedule _type , schedule _value , req . user . username , nextRun ]
) ;
res . json ( { success : true , id } ) ;
} catch ( error ) {
res . status ( 500 ) . json ( { error : error . message } ) ;
}
} ) ;
app . put ( '/api/scheduled-commands/:id/toggle' , authenticateSSO , async ( req , res ) => {
try {
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
if ( ! req . user . isAdmin ) return res . status ( 403 ) . json ( { error : 'Admin access required' } ) ;
2026-01-07 23:13:27 -05:00
const { id } = req . params ;
const [ current ] = await pool . query ( 'SELECT enabled FROM scheduled_commands WHERE id = ?' , [ id ] ) ;
if ( current . length === 0 ) {
return res . status ( 404 ) . json ( { error : 'Schedule not found' } ) ;
}
const newEnabled = ! current [ 0 ] . enabled ;
await pool . query ( 'UPDATE scheduled_commands SET enabled = ? WHERE id = ?' , [ newEnabled , id ] ) ;
res . json ( { success : true , enabled : newEnabled } ) ;
} catch ( error ) {
res . status ( 500 ) . json ( { error : error . message } ) ;
}
} ) ;
app . delete ( '/api/scheduled-commands/:id' , authenticateSSO , async ( req , res ) => {
try {
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
if ( ! req . user . isAdmin ) return res . status ( 403 ) . json ( { error : 'Admin access required' } ) ;
2026-01-07 23:13:27 -05:00
const { id } = req . params ;
await pool . query ( 'DELETE FROM scheduled_commands WHERE id = ?' , [ id ] ) ;
res . json ( { success : true } ) ;
} catch ( error ) {
res . status ( 500 ) . json ( { error : error . message } ) ;
}
} ) ;
2026-03-03 16:04:22 -05:00
// Internal M2M API for Gandalf
app . post ( '/api/internal/command' , authenticateGandalf , async ( req , res ) => {
try {
const { worker _id , command } = req . body ;
if ( ! worker _id || ! command ) {
return res . status ( 400 ) . json ( { error : 'worker_id and command are required' } ) ;
}
const workerWs = workers . get ( worker _id ) ;
if ( ! workerWs || workerWs . readyState !== WebSocket . OPEN ) {
return res . status ( 400 ) . json ( { error : 'Worker not connected' } ) ;
}
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
const executionId = crypto . randomUUID ( ) ;
2026-03-03 16:04:22 -05:00
await pool . query (
'INSERT INTO executions (id, workflow_id, status, started_by, started_at, logs) VALUES (?, ?, ?, ?, NOW(), ?)' ,
[ executionId , null , 'running' , req . user . username , JSON . stringify ( [ {
step : 'internal_command' ,
action : 'command_sent' ,
worker _id : worker _id ,
command : command ,
timestamp : new Date ( ) . toISOString ( )
} ] ) ]
) ;
workerWs . send ( JSON . stringify ( {
type : 'execute_command' ,
execution _id : executionId ,
command : command ,
worker _id : worker _id ,
timeout : 60000
} ) ) ;
res . json ( { execution _id : executionId } ) ;
} catch ( error ) {
res . status ( 500 ) . json ( { error : error . message } ) ;
}
} ) ;
app . get ( '/api/internal/executions/:id' , authenticateGandalf , async ( req , res ) => {
try {
const [ rows ] = await pool . query ( 'SELECT * FROM executions WHERE id = ?' , [ req . params . id ] ) ;
if ( rows . length === 0 ) {
return res . status ( 404 ) . json ( { error : 'Not found' } ) ;
}
const execution = rows [ 0 ] ;
res . json ( {
... execution ,
logs : JSON . parse ( execution . logs || '[]' )
} ) ;
} catch ( error ) {
res . status ( 500 ) . json ( { error : error . message } ) ;
}
} ) ;
2025-11-29 19:26:20 -05:00
// Health check (no auth required)
app . get ( '/health' , async ( req , res ) => {
try {
await pool . query ( 'SELECT 1' ) ;
res . json ( {
status : 'ok' ,
timestamp : new Date ( ) . toISOString ( ) ,
database : 'connected' ,
auth : 'authelia-sso'
} ) ;
} catch ( error ) {
res . status ( 500 ) . json ( {
status : 'error' ,
timestamp : new Date ( ) . toISOString ( ) ,
database : 'disconnected' ,
error : error . message
} ) ;
}
} ) ;
2025-11-30 13:03:18 -05:00
// Get execution details with logs
app . get ( '/api/executions/:id' , authenticateSSO , async ( req , res ) => {
try {
const [ rows ] = await pool . query ( 'SELECT * FROM executions WHERE id = ?' , [ req . params . id ] ) ;
if ( rows . length === 0 ) {
return res . status ( 404 ) . json ( { error : 'Not found' } ) ;
}
2026-01-08 22:06:25 -05:00
2025-11-30 13:03:18 -05:00
const execution = rows [ 0 ] ;
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
const parsedLogs = typeof execution . logs === 'string' ? JSON . parse ( execution . logs || '[]' ) : ( execution . logs || [ ] ) ;
2026-03-03 16:55:02 -05:00
const waitingForInput = _executionPrompts . has ( req . params . id ) ;
let pendingPrompt = null ;
if ( waitingForInput ) {
for ( let i = parsedLogs . length - 1 ; i >= 0 ; i -- ) {
if ( parsedLogs [ i ] . action === 'prompt' ) {
pendingPrompt = { message : parsedLogs [ i ] . message , options : parsedLogs [ i ] . options } ;
break ;
}
}
}
2026-01-08 22:06:25 -05:00
2025-11-30 13:03:18 -05:00
res . json ( {
... execution ,
2026-03-03 16:55:02 -05:00
logs : parsedLogs ,
waiting _for _input : waitingForInput ,
prompt : pendingPrompt ,
2025-11-30 13:03:18 -05:00
} ) ;
} catch ( error ) {
res . status ( 500 ) . json ( { error : error . message } ) ;
}
} ) ;
// Delete worker (admin only)
app . delete ( '/api/workers/:id' , authenticateSSO , async ( req , res ) => {
try {
if ( ! req . user . isAdmin ) {
return res . status ( 403 ) . json ( { error : 'Admin access required' } ) ;
}
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
2025-11-30 13:03:18 -05:00
await pool . query ( 'DELETE FROM workers WHERE id = ?' , [ req . params . id ] ) ;
res . json ( { success : true } ) ;
broadcast ( { type : 'worker_deleted' , worker _id : req . params . id } ) ;
} catch ( error ) {
res . status ( 500 ) . json ( { error : error . message } ) ;
}
} ) ;
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
// Send direct command to specific worker
2025-11-30 13:03:18 -05:00
app . post ( '/api/workers/:id/command' , authenticateSSO , async ( req , res ) => {
try {
const { command } = req . body ;
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
const executionId = crypto . randomUUID ( ) ;
2026-01-07 20:24:11 -05:00
const workerId = req . params . id ;
// Create execution record in database
await pool . query (
'INSERT INTO executions (id, workflow_id, status, started_by, started_at, logs) VALUES (?, ?, ?, ?, NOW(), ?)' ,
[ executionId , null , 'running' , req . user . username , JSON . stringify ( [ {
step : 'quick_command' ,
action : 'command_sent' ,
worker _id : workerId ,
command : command ,
timestamp : new Date ( ) . toISOString ( )
} ] ) ]
) ;
2026-01-07 22:23:02 -05:00
// Send command via WebSocket to specific worker
2025-11-30 13:03:18 -05:00
const commandMessage = {
type : 'execute_command' ,
execution _id : executionId ,
command : command ,
2026-01-07 20:24:11 -05:00
worker _id : workerId ,
2025-11-30 13:03:18 -05:00
timeout : 60000
} ;
2026-01-07 22:23:02 -05:00
const workerWs = workers . get ( workerId ) ;
if ( ! workerWs || workerWs . readyState !== WebSocket . OPEN ) {
return res . status ( 400 ) . json ( { error : 'Worker not connected' } ) ;
}
workerWs . send ( JSON . stringify ( commandMessage ) ) ;
console . log ( ` Command sent to worker ${ workerId } : ${ command } ` ) ;
2025-11-30 13:03:18 -05:00
2026-01-07 20:24:11 -05:00
broadcast ( { type : 'execution_started' , execution _id : executionId , workflow _id : null } ) ;
2025-11-30 13:03:18 -05:00
res . json ( { success : true , execution _id : executionId } ) ;
} catch ( error ) {
res . status ( 500 ) . json ( { error : error . message } ) ;
}
} ) ;
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
// Start server
const PORT = process . env . PORT || 8080 ;
const HOST = process . env . HOST || '0.0.0.0' ;
initDatabase ( ) . then ( ( ) => {
server . listen ( PORT , HOST , ( ) => {
console . log ( ` PULSE Server running on http:// ${ HOST } : ${ PORT } ` ) ;
console . log ( ` Connected to MariaDB at ${ process . env . DB _HOST } ` ) ;
console . log ( ` Authentication: Authelia SSO ` ) ;
console . log ( ` Worker API Key configured: ${ process . env . WORKER _API _KEY ? 'Yes' : 'No' } ` ) ;
} ) ;
} ) . catch ( err => {
console . error ( 'Failed to start server:' , err ) ;
process . exit ( 1 ) ;
} ) ;