Refactor backend and frontend: Added migration scripts, updated dependencies, improved error handling, and enhanced workflow editor functionality. Removed version from docker-compose file.

This commit is contained in:
Mehmet Oezdag 2025-06-08 15:55:14 +02:00
parent 74fb343ead
commit 1c317d1a1d
13 changed files with 21420 additions and 66 deletions

30
backend/knexfile.js Normal file
View File

@ -0,0 +1,30 @@
require('dotenv').config();
module.exports = {
development: {
client: 'pg',
connection: process.env.DATABASE_URL || 'postgres://postgres:postgres@localhost:5432/flowforge',
pool: {
min: 2,
max: 10
},
migrations: {
tableName: 'knex_migrations',
directory: './migrations'
}
},
production: {
client: 'pg',
connection: process.env.DATABASE_URL,
pool: {
min: 2,
max: 10
},
migrations: {
tableName: 'knex_migrations',
directory: './migrations'
},
ssl: process.env.DATABASE_SSL === 'true' ? { rejectUnauthorized: false } : false
}
};

View File

@ -70,3 +70,133 @@
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-07 08:55:59"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-07 08:56:07"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-07 08:56:07"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-08 12:30:14"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-08 12:30:14"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-08 12:31:23"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-08 12:31:23"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-08 12:31:25"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-08 12:31:25"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-08 12:31:35"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-08 12:31:35"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-08 12:32:07"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-08 12:32:07"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-08 12:56:22"}
{"level":"info","message":"Initializing node registry","service":"flowforge-backend","timestamp":"2025-06-08 12:56:22"}
{"level":"debug","message":"Loaded node type: delay","service":"flowforge-backend","timestamp":"2025-06-08 12:56:22"}
{"error":"Cannot find module 'nodemailer'\nRequire stack:\n- /app/src/nodes/email/runner.js\n- /app/src/services/nodeRegistry.js\n- /app/src/index.js","level":"error","message":"Failed to load node type: email","service":"flowforge-backend","timestamp":"2025-06-08 12:56:22"}
{"error":"Cannot find module 'vm2'\nRequire stack:\n- /app/src/nodes/function/runner.js\n- /app/src/services/nodeRegistry.js\n- /app/src/index.js","level":"error","message":"Failed to load node type: function","service":"flowforge-backend","timestamp":"2025-06-08 12:56:22"}
{"error":"Cannot find module 'axios'\nRequire stack:\n- /app/src/nodes/http-request/runner.js\n- /app/src/services/nodeRegistry.js\n- /app/src/index.js","level":"error","message":"Failed to load node type: http-request","service":"flowforge-backend","timestamp":"2025-06-08 12:56:22"}
{"level":"debug","message":"Loaded node type: logger","service":"flowforge-backend","timestamp":"2025-06-08 12:56:22"}
{"level":"debug","message":"Loaded node type: webhook","service":"flowforge-backend","timestamp":"2025-06-08 12:56:22"}
{"level":"info","message":"Node registry initialized with 3 node types","service":"flowforge-backend","timestamp":"2025-06-08 12:56:22"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-08 12:56:22"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-08 12:57:09"}
{"level":"info","message":"Initializing node registry","service":"flowforge-backend","timestamp":"2025-06-08 12:57:09"}
{"level":"debug","message":"Loaded node type: delay","service":"flowforge-backend","timestamp":"2025-06-08 12:57:09"}
{"error":"Cannot find module 'nodemailer'\nRequire stack:\n- /app/src/nodes/email/runner.js\n- /app/src/services/nodeRegistry.js\n- /app/src/index.js","level":"error","message":"Failed to load node type: email","service":"flowforge-backend","timestamp":"2025-06-08 12:57:09"}
{"error":"Cannot find module 'vm2'\nRequire stack:\n- /app/src/nodes/function/runner.js\n- /app/src/services/nodeRegistry.js\n- /app/src/index.js","level":"error","message":"Failed to load node type: function","service":"flowforge-backend","timestamp":"2025-06-08 12:57:09"}
{"error":"Cannot find module 'axios'\nRequire stack:\n- /app/src/nodes/http-request/runner.js\n- /app/src/services/nodeRegistry.js\n- /app/src/index.js","level":"error","message":"Failed to load node type: http-request","service":"flowforge-backend","timestamp":"2025-06-08 12:57:09"}
{"level":"debug","message":"Loaded node type: logger","service":"flowforge-backend","timestamp":"2025-06-08 12:57:09"}
{"level":"debug","message":"Loaded node type: webhook","service":"flowforge-backend","timestamp":"2025-06-08 12:57:09"}
{"level":"info","message":"Node registry initialized with 3 node types","service":"flowforge-backend","timestamp":"2025-06-08 12:57:09"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-08 12:57:09"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-08 12:57:20"}
{"level":"info","message":"Initializing node registry","service":"flowforge-backend","timestamp":"2025-06-08 12:57:20"}
{"level":"debug","message":"Loaded node type: delay","service":"flowforge-backend","timestamp":"2025-06-08 12:57:20"}
{"level":"debug","message":"Loaded node type: email","service":"flowforge-backend","timestamp":"2025-06-08 12:57:20"}
{"level":"debug","message":"Loaded node type: function","service":"flowforge-backend","timestamp":"2025-06-08 12:57:20"}
{"level":"debug","message":"Loaded node type: http-request","service":"flowforge-backend","timestamp":"2025-06-08 12:57:20"}
{"level":"debug","message":"Loaded node type: logger","service":"flowforge-backend","timestamp":"2025-06-08 12:57:20"}
{"level":"debug","message":"Loaded node type: webhook","service":"flowforge-backend","timestamp":"2025-06-08 12:57:20"}
{"level":"info","message":"Node registry initialized with 6 node types","service":"flowforge-backend","timestamp":"2025-06-08 12:57:20"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-08 12:57:20"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-08 12:57:22"}
{"level":"info","message":"Initializing node registry","service":"flowforge-backend","timestamp":"2025-06-08 12:57:22"}
{"level":"debug","message":"Loaded node type: delay","service":"flowforge-backend","timestamp":"2025-06-08 12:57:22"}
{"level":"debug","message":"Loaded node type: email","service":"flowforge-backend","timestamp":"2025-06-08 12:57:22"}
{"level":"debug","message":"Loaded node type: function","service":"flowforge-backend","timestamp":"2025-06-08 12:57:22"}
{"level":"debug","message":"Loaded node type: http-request","service":"flowforge-backend","timestamp":"2025-06-08 12:57:22"}
{"level":"debug","message":"Loaded node type: logger","service":"flowforge-backend","timestamp":"2025-06-08 12:57:22"}
{"level":"debug","message":"Loaded node type: webhook","service":"flowforge-backend","timestamp":"2025-06-08 12:57:22"}
{"level":"info","message":"Node registry initialized with 6 node types","service":"flowforge-backend","timestamp":"2025-06-08 12:57:22"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-08 12:57:22"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-08 12:57:26"}
{"level":"info","message":"Initializing node registry","service":"flowforge-backend","timestamp":"2025-06-08 12:57:26"}
{"level":"debug","message":"Loaded node type: delay","service":"flowforge-backend","timestamp":"2025-06-08 12:57:26"}
{"level":"debug","message":"Loaded node type: email","service":"flowforge-backend","timestamp":"2025-06-08 12:57:26"}
{"level":"debug","message":"Loaded node type: function","service":"flowforge-backend","timestamp":"2025-06-08 12:57:26"}
{"level":"debug","message":"Loaded node type: http-request","service":"flowforge-backend","timestamp":"2025-06-08 12:57:26"}
{"level":"debug","message":"Loaded node type: logger","service":"flowforge-backend","timestamp":"2025-06-08 12:57:26"}
{"level":"debug","message":"Loaded node type: webhook","service":"flowforge-backend","timestamp":"2025-06-08 12:57:26"}
{"level":"info","message":"Node registry initialized with 6 node types","service":"flowforge-backend","timestamp":"2025-06-08 12:57:26"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-08 12:57:26"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-08 12:58:10"}
{"level":"info","message":"Initializing node registry","service":"flowforge-backend","timestamp":"2025-06-08 12:58:10"}
{"level":"debug","message":"Loaded node type: delay","service":"flowforge-backend","timestamp":"2025-06-08 12:58:10"}
{"level":"debug","message":"Loaded node type: email","service":"flowforge-backend","timestamp":"2025-06-08 12:58:10"}
{"level":"debug","message":"Loaded node type: function","service":"flowforge-backend","timestamp":"2025-06-08 12:58:10"}
{"level":"debug","message":"Loaded node type: http-request","service":"flowforge-backend","timestamp":"2025-06-08 12:58:10"}
{"level":"debug","message":"Loaded node type: logger","service":"flowforge-backend","timestamp":"2025-06-08 12:58:10"}
{"level":"debug","message":"Loaded node type: webhook","service":"flowforge-backend","timestamp":"2025-06-08 12:58:10"}
{"level":"info","message":"Node registry initialized with 6 node types","service":"flowforge-backend","timestamp":"2025-06-08 12:58:10"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-08 12:58:10"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-08 12:58:29"}
{"level":"info","message":"Initializing node registry","service":"flowforge-backend","timestamp":"2025-06-08 12:58:29"}
{"level":"debug","message":"Loaded node type: delay","service":"flowforge-backend","timestamp":"2025-06-08 12:58:29"}
{"level":"debug","message":"Loaded node type: email","service":"flowforge-backend","timestamp":"2025-06-08 12:58:29"}
{"error":"Cannot find module 'isolated-vm'\nRequire stack:\n- /app/src/nodes/function/runner.js\n- /app/src/services/nodeRegistry.js\n- /app/src/index.js","level":"error","message":"Failed to load node type: function","service":"flowforge-backend","timestamp":"2025-06-08 12:58:29"}
{"level":"debug","message":"Loaded node type: http-request","service":"flowforge-backend","timestamp":"2025-06-08 12:58:29"}
{"level":"debug","message":"Loaded node type: logger","service":"flowforge-backend","timestamp":"2025-06-08 12:58:29"}
{"level":"debug","message":"Loaded node type: webhook","service":"flowforge-backend","timestamp":"2025-06-08 12:58:29"}
{"level":"info","message":"Node registry initialized with 5 node types","service":"flowforge-backend","timestamp":"2025-06-08 12:58:29"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-08 12:58:29"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-08 12:58:40"}
{"level":"info","message":"Initializing node registry","service":"flowforge-backend","timestamp":"2025-06-08 12:58:40"}
{"level":"debug","message":"Loaded node type: delay","service":"flowforge-backend","timestamp":"2025-06-08 12:58:40"}
{"level":"debug","message":"Loaded node type: email","service":"flowforge-backend","timestamp":"2025-06-08 12:58:40"}
{"level":"debug","message":"Loaded node type: function","service":"flowforge-backend","timestamp":"2025-06-08 12:58:40"}
{"level":"debug","message":"Loaded node type: http-request","service":"flowforge-backend","timestamp":"2025-06-08 12:58:40"}
{"level":"debug","message":"Loaded node type: logger","service":"flowforge-backend","timestamp":"2025-06-08 12:58:40"}
{"level":"debug","message":"Loaded node type: webhook","service":"flowforge-backend","timestamp":"2025-06-08 12:58:40"}
{"level":"info","message":"Node registry initialized with 6 node types","service":"flowforge-backend","timestamp":"2025-06-08 12:58:40"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-08 12:58:40"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-08 12:58:47"}
{"level":"info","message":"Initializing node registry","service":"flowforge-backend","timestamp":"2025-06-08 12:58:47"}
{"level":"debug","message":"Loaded node type: delay","service":"flowforge-backend","timestamp":"2025-06-08 12:58:47"}
{"level":"debug","message":"Loaded node type: email","service":"flowforge-backend","timestamp":"2025-06-08 12:58:47"}
{"level":"debug","message":"Loaded node type: function","service":"flowforge-backend","timestamp":"2025-06-08 12:58:47"}
{"level":"debug","message":"Loaded node type: http-request","service":"flowforge-backend","timestamp":"2025-06-08 12:58:47"}
{"level":"debug","message":"Loaded node type: logger","service":"flowforge-backend","timestamp":"2025-06-08 12:58:47"}
{"level":"debug","message":"Loaded node type: webhook","service":"flowforge-backend","timestamp":"2025-06-08 12:58:47"}
{"level":"info","message":"Node registry initialized with 6 node types","service":"flowforge-backend","timestamp":"2025-06-08 12:58:47"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-08 12:58:47"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-08 13:06:51"}
{"level":"info","message":"Initializing node registry","service":"flowforge-backend","timestamp":"2025-06-08 13:06:51"}
{"level":"debug","message":"Loaded node type: delay","service":"flowforge-backend","timestamp":"2025-06-08 13:06:51"}
{"level":"debug","message":"Loaded node type: email","service":"flowforge-backend","timestamp":"2025-06-08 13:06:51"}
{"level":"debug","message":"Loaded node type: function","service":"flowforge-backend","timestamp":"2025-06-08 13:06:51"}
{"level":"debug","message":"Loaded node type: http-request","service":"flowforge-backend","timestamp":"2025-06-08 13:06:51"}
{"level":"debug","message":"Loaded node type: logger","service":"flowforge-backend","timestamp":"2025-06-08 13:06:51"}
{"level":"debug","message":"Loaded node type: webhook","service":"flowforge-backend","timestamp":"2025-06-08 13:06:51"}
{"level":"info","message":"Node registry initialized with 6 node types","service":"flowforge-backend","timestamp":"2025-06-08 13:06:51"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-08 13:06:51"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-08 13:50:21"}
{"level":"info","message":"Initializing node registry","service":"flowforge-backend","timestamp":"2025-06-08 13:50:21"}
{"level":"debug","message":"Loaded node type: delay","service":"flowforge-backend","timestamp":"2025-06-08 13:50:21"}
{"level":"debug","message":"Loaded node type: email","service":"flowforge-backend","timestamp":"2025-06-08 13:50:21"}
{"level":"debug","message":"Loaded node type: function","service":"flowforge-backend","timestamp":"2025-06-08 13:50:21"}
{"level":"debug","message":"Loaded node type: http-request","service":"flowforge-backend","timestamp":"2025-06-08 13:50:21"}
{"level":"debug","message":"Loaded node type: logger","service":"flowforge-backend","timestamp":"2025-06-08 13:50:21"}
{"level":"debug","message":"Loaded node type: webhook","service":"flowforge-backend","timestamp":"2025-06-08 13:50:21"}
{"level":"info","message":"Node registry initialized with 6 node types","service":"flowforge-backend","timestamp":"2025-06-08 13:50:21"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-08 13:50:21"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-08 13:50:29"}
{"level":"info","message":"Initializing node registry","service":"flowforge-backend","timestamp":"2025-06-08 13:50:29"}
{"level":"debug","message":"Loaded node type: delay","service":"flowforge-backend","timestamp":"2025-06-08 13:50:29"}
{"level":"debug","message":"Loaded node type: email","service":"flowforge-backend","timestamp":"2025-06-08 13:50:29"}
{"level":"debug","message":"Loaded node type: function","service":"flowforge-backend","timestamp":"2025-06-08 13:50:29"}
{"level":"debug","message":"Loaded node type: http-request","service":"flowforge-backend","timestamp":"2025-06-08 13:50:29"}
{"level":"debug","message":"Loaded node type: logger","service":"flowforge-backend","timestamp":"2025-06-08 13:50:29"}
{"level":"debug","message":"Loaded node type: webhook","service":"flowforge-backend","timestamp":"2025-06-08 13:50:29"}
{"level":"info","message":"Node registry initialized with 6 node types","service":"flowforge-backend","timestamp":"2025-06-08 13:50:29"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-08 13:50:29"}

View File

@ -32,3 +32,10 @@
{"level":"error","message":"SyntaxError: Unexpected token o in JSON at position 1","method":"GET","path":"/api/workflows/971f09e0-6490-4be3-a6cb-928e9f2ca5a5","service":"flowforge-backend","stack":"SyntaxError: Unexpected token o in JSON at position 1\n at JSON.parse (<anonymous>)\n at getWorkflowById (/app/src/models/workflow.js:96:17)\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\n at async getById (/app/src/controllers/workflow.js:75:22)","timestamp":"2025-06-07 08:51:08"}
{"level":"error","message":"SyntaxError: Unexpected token o in JSON at position 1","method":"GET","path":"/api/workflows/7140abc0-968a-4728-925b-27f49f139b6d","service":"flowforge-backend","stack":"SyntaxError: Unexpected token o in JSON at position 1\n at JSON.parse (<anonymous>)\n at getWorkflowById (/app/src/models/workflow.js:96:17)\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\n at async getById (/app/src/controllers/workflow.js:75:22)","timestamp":"2025-06-07 08:51:12"}
{"level":"error","message":"SyntaxError: Unexpected token o in JSON at position 1","method":"GET","path":"/api/workflows/7140abc0-968a-4728-925b-27f49f139b6d","service":"flowforge-backend","stack":"SyntaxError: Unexpected token o in JSON at position 1\n at JSON.parse (<anonymous>)\n at getWorkflowById (/app/src/models/workflow.js:96:17)\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\n at async getById (/app/src/controllers/workflow.js:75:22)","timestamp":"2025-06-07 08:51:12"}
{"error":"Cannot find module 'nodemailer'\nRequire stack:\n- /app/src/nodes/email/runner.js\n- /app/src/services/nodeRegistry.js\n- /app/src/index.js","level":"error","message":"Failed to load node type: email","service":"flowforge-backend","timestamp":"2025-06-08 12:56:22"}
{"error":"Cannot find module 'vm2'\nRequire stack:\n- /app/src/nodes/function/runner.js\n- /app/src/services/nodeRegistry.js\n- /app/src/index.js","level":"error","message":"Failed to load node type: function","service":"flowforge-backend","timestamp":"2025-06-08 12:56:22"}
{"error":"Cannot find module 'axios'\nRequire stack:\n- /app/src/nodes/http-request/runner.js\n- /app/src/services/nodeRegistry.js\n- /app/src/index.js","level":"error","message":"Failed to load node type: http-request","service":"flowforge-backend","timestamp":"2025-06-08 12:56:22"}
{"error":"Cannot find module 'nodemailer'\nRequire stack:\n- /app/src/nodes/email/runner.js\n- /app/src/services/nodeRegistry.js\n- /app/src/index.js","level":"error","message":"Failed to load node type: email","service":"flowforge-backend","timestamp":"2025-06-08 12:57:09"}
{"error":"Cannot find module 'vm2'\nRequire stack:\n- /app/src/nodes/function/runner.js\n- /app/src/services/nodeRegistry.js\n- /app/src/index.js","level":"error","message":"Failed to load node type: function","service":"flowforge-backend","timestamp":"2025-06-08 12:57:09"}
{"error":"Cannot find module 'axios'\nRequire stack:\n- /app/src/nodes/http-request/runner.js\n- /app/src/services/nodeRegistry.js\n- /app/src/index.js","level":"error","message":"Failed to load node type: http-request","service":"flowforge-backend","timestamp":"2025-06-08 12:57:09"}
{"error":"Cannot find module 'isolated-vm'\nRequire stack:\n- /app/src/nodes/function/runner.js\n- /app/src/services/nodeRegistry.js\n- /app/src/index.js","level":"error","message":"Failed to load node type: function","service":"flowforge-backend","timestamp":"2025-06-08 12:58:29"}

5751
backend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -6,9 +6,13 @@
"scripts": {
"start": "node src/index.js",
"dev": "nodemon src/index.js",
"test": "jest"
"test": "jest",
"migrate": "knex migrate:latest",
"migrate:rollback": "knex migrate:rollback",
"migrate:status": "knex migrate:status"
},
"dependencies": {
"axios": "^1.4.0",
"bcrypt": "^5.1.0",
"bull": "^4.10.4",
"cors": "^2.8.5",
@ -16,8 +20,10 @@
"express": "^4.18.2",
"express-rate-limit": "^6.7.0",
"helmet": "^6.1.5",
"isolated-vm": "^5.0.1",
"jsonwebtoken": "^9.0.0",
"knex": "^2.4.2",
"nodemailer": "^6.9.3",
"pg": "^8.10.0",
"redis": "^4.6.6",
"uuid": "^9.0.0",

View File

@ -5,6 +5,7 @@ const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const { errorHandler } = require('./middleware/errorHandler');
const logger = require('./utils/logger');
const nodeRegistry = require('./services/nodeRegistry');
// Import routes
const authRoutes = require('./routes/auth');
@ -24,7 +25,7 @@ app.use(express.json());
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
max: 1000, // limit each IP to 1000 requests per windowMs (increased for dev)
standardHeaders: true,
legacyHeaders: false,
});
@ -44,9 +45,18 @@ app.get('/health', (req, res) => {
// Error handling middleware
app.use(errorHandler);
// Initialize services
const initializeServices = async () => {
// Initialize node registry
await nodeRegistry.initialize();
};
// Start server
app.listen(PORT, () => {
app.listen(PORT, async () => {
logger.info(`Server running on port ${PORT}`);
// Initialize services after server starts
await initializeServices();
});
// Handle unhandled promise rejections

View File

@ -1,4 +1,4 @@
const { VM } = require('vm2');
const ivm = require('isolated-vm');
const logger = require('../../utils/logger');
/**
@ -17,53 +17,57 @@ async function run(nodeConfig, inputData) {
// Set timeout (default: 5000ms)
const timeout = nodeConfig.timeout || 5000;
// Create a sandboxed VM
const vm = new VM({
timeout,
sandbox: {
// Provide input data to the sandbox
input: inputData.input || {},
// Provide console methods that log to our logger
console: {
log: (...args) => logger.info('Function node log:', ...args),
info: (...args) => logger.info('Function node info:', ...args),
warn: (...args) => logger.warn('Function node warn:', ...args),
error: (...args) => logger.error('Function node error:', ...args)
}
},
// Prevent access to Node.js internal modules
require: {
external: false,
builtin: ['path', 'util', 'buffer'],
root: "./",
mock: {
fs: {
readFileSync: () => 'Not allowed'
}
}
}
});
// Create a new isolated context
const isolate = new ivm.Isolate({ memoryLimit: 32 });
const context = await isolate.createContext();
// Wrap the code in an async function
// Get a reference to the global object
const jail = context.global;
// Add input data to the context
await jail.set('input', new ivm.ExternalCopy(inputData.input || {}).copyInto());
// Add console methods that log to our logger
const logMethods = {
log: (...args) => logger.info('Function node log:', ...args),
info: (...args) => logger.info('Function node info:', ...args),
warn: (...args) => logger.warn('Function node warn:', ...args),
error: (...args) => logger.error('Function node error:', ...args)
};
await jail.set('console', new ivm.ExternalCopy(logMethods).copyInto());
// Wrap the code in an async function that returns the result
const wrappedCode = `
(async function() {
${nodeConfig.code}
try {
${nodeConfig.code}
} catch (error) {
throw new Error(error.message);
}
})();
`;
// Execute the code
const result = await vm.run(wrappedCode);
// Execute the code with timeout
const script = await isolate.compileScript(wrappedCode);
const result = await script.run(context, { timeout });
// Validate the result
if (result === undefined || result === null) {
return {
output: {}
};
// Handle the result
let output = {};
if (result !== undefined && result !== null) {
// If result is a primitive value, wrap it
if (typeof result === 'object') {
output = result;
} else {
output = { value: result };
}
}
// Return the result
// Clean up
isolate.dispose();
return {
output: result
output: output
};
} catch (error) {
logger.error('Function node error', { error: error.message });

View File

@ -1,5 +1,3 @@
version: '3.8'
services:
frontend:
build:

15260
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@
"jwt-decode": "^3.1.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"reactflow": "^11.7.0",
"reactflow": "^11.10.4",
"react-router-dom": "^6.14.1",
"react-scripts": "5.0.1",
"react-toastify": "^9.1.3",

View File

@ -31,7 +31,7 @@ const getNodeIcon = (type) => {
case 'http-request':
return (
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" />
</svg>
);
case 'function':
@ -67,21 +67,66 @@ const getNodeIcon = (type) => {
}
};
const CustomNode = ({ data, selected }) => {
const CustomNode = ({ id, data, selected, onEdit, onDelete }) => {
const nodeColor = getNodeColor(data.nodeType);
const nodeIcon = getNodeIcon(data.nodeType);
const handleEdit = (e) => {
e.stopPropagation();
if (onEdit) {
onEdit(id);
}
};
const handleDelete = (e) => {
e.stopPropagation();
if (onDelete) {
onDelete(id);
}
};
return (
<div className={`${selected ? 'ring-2 ring-primary-400' : ''} bg-white rounded-md shadow-md border-l-4 ${nodeColor} p-3 min-w-[180px]`}>
<div
className={`group relative ${selected ? 'ring-2 ring-primary-400' : ''} bg-white rounded-md shadow-md border-l-4 ${nodeColor} p-3 min-w-[180px] cursor-grab active:cursor-grabbing hover:shadow-lg transition-shadow`}
style={{ pointerEvents: 'auto' }}
>
{/* Action buttons - show on hover or when selected */}
<div className={`absolute -top-2 -right-2 flex space-x-1 transition-opacity ${selected ? 'opacity-100' : 'opacity-0 group-hover:opacity-100'}`}>
{/* Edit button */}
<button
onClick={handleEdit}
className="w-6 h-6 bg-blue-500 hover:bg-blue-600 text-white rounded-full flex items-center justify-center shadow-md transition-colors"
style={{ pointerEvents: 'auto' }}
title="Edit node"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
</button>
{/* Delete button */}
<button
onClick={handleDelete}
className="w-6 h-6 bg-red-500 hover:bg-red-600 text-white rounded-full flex items-center justify-center shadow-md transition-colors"
style={{ pointerEvents: 'auto' }}
title="Delete node"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
</div>
{/* Input handle */}
<Handle
type="target"
position={Position.Top}
className="w-3 h-3 rounded-full bg-gray-400 border-2 border-white"
style={{ pointerEvents: 'auto' }}
/>
{/* Node content */}
<div className="flex items-center">
<div className="flex items-center" style={{ pointerEvents: 'none' }}>
<div className="flex-shrink-0 text-gray-500">
{nodeIcon}
</div>
@ -100,6 +145,7 @@ const CustomNode = ({ data, selected }) => {
type="source"
position={Position.Bottom}
className="w-3 h-3 rounded-full bg-gray-400 border-2 border-white"
style={{ pointerEvents: 'auto' }}
/>
</div>
);

View File

@ -20,6 +20,13 @@ code {
/* Custom styles for react-flow */
.react-flow__node {
@apply shadow-md rounded-md;
cursor: grab;
user-select: none;
pointer-events: auto;
}
.react-flow__node.dragging {
cursor: grabbing;
}
.react-flow__node-default {
@ -28,6 +35,7 @@ code {
.react-flow__handle {
@apply w-3 h-3;
pointer-events: auto;
}
.react-flow__handle-top {
@ -46,6 +54,11 @@ code {
@apply shadow-md;
}
/* Ensure React Flow pane is properly configured */
.react-flow__pane {
cursor: default;
}
/* Node types */
.node-webhook {
@apply border-l-4 border-l-blue-500;

View File

@ -48,10 +48,18 @@ const WorkflowEditor = () => {
const [latestExecutionId, setLatestExecutionId] = useState(null);
const [currentVersion, setCurrentVersion] = useState(1);
const [showTabs, setShowTabs] = useState(true);
const [viewport, setViewport] = useState({ x: 0, y: 0, zoom: 1 });
const [isFirstLoad, setIsFirstLoad] = useState(true);
// Node type definitions for React Flow
const nodeTypeDefinitions = {
customNode: CustomNode
customNode: (nodeProps) => (
<CustomNode
{...nodeProps}
onEdit={handleEditNode}
onDelete={handleDeleteNode}
/>
)
};
// Load workflow if editing existing one
@ -82,12 +90,17 @@ const WorkflowEditor = () => {
const flowNodes = workflowData.nodes.map(node => ({
id: node.id,
type: 'customNode',
position: { x: node.position_x, y: node.position_y },
position: {
x: Number(node.position_x) || 0,
y: Number(node.position_y) || 0
},
data: {
label: node.name,
nodeType: node.type,
config: node.config || {}
}
},
draggable: true,
selectable: true
}));
const flowEdges = workflowData.connections.map(conn => ({
@ -110,7 +123,7 @@ const WorkflowEditor = () => {
};
fetchData();
}, [id]);
}, [id, setNodes, setEdges]);
// Handle connections between nodes
const onConnect = useCallback((params) => {
@ -151,6 +164,8 @@ const WorkflowEditor = () => {
nodeType: nodeData.type,
config: {}
},
draggable: true,
selectable: true
};
setNodes((nds) => nds.concat(newNode));
@ -283,18 +298,35 @@ const WorkflowEditor = () => {
position: {
x: node.position.x + 50,
y: node.position.y + 50
}
},
draggable: true,
selectable: true
};
setNodes((nds) => nds.concat(newNode));
};
// Delete a node
const handleDeleteNode = (node) => {
setNodes((nds) => nds.filter((n) => n.id !== node.id));
setEdges((eds) => eds.filter((e) => e.source !== node.id && e.target !== node.id));
setSelectedNode(null);
};
// Handle node editing
const handleEditNode = useCallback((nodeId) => {
const node = nodes.find(n => n.id === nodeId);
if (node) {
setSelectedNode(node);
}
}, [nodes]);
// Handle node deletion
const handleDeleteNode = useCallback((nodeId) => {
// Show confirmation dialog
if (window.confirm('Are you sure you want to delete this node?')) {
setNodes((nds) => nds.filter((n) => n.id !== nodeId));
setEdges((eds) => eds.filter((e) => e.source !== nodeId && e.target !== nodeId));
// Clear selection if the deleted node was selected
if (selectedNode && selectedNode.id === nodeId) {
setSelectedNode(null);
}
}
}, [setNodes, setEdges, selectedNode]);
// Handle workflow execution
const handleExecuteWorkflow = (executionId) => {
@ -320,12 +352,17 @@ const WorkflowEditor = () => {
const flowNodes = workflowData.nodes.map(node => ({
id: node.id,
type: 'customNode',
position: { x: node.position_x, y: node.position_y },
position: {
x: Number(node.position_x) || 0,
y: Number(node.position_y) || 0
},
data: {
label: node.name,
nodeType: node.type,
config: node.config || {}
}
},
draggable: true,
selectable: true
}));
const flowEdges = workflowData.connections.map(conn => ({
@ -339,6 +376,8 @@ const WorkflowEditor = () => {
setNodes(flowNodes);
setEdges(flowEdges);
setCurrentVersion(version);
// Don't reset viewport when restoring versions - preserve user's current view
toast.success(`Restored workflow to version ${version}`);
} catch (error) {
console.error('Error restoring version:', error);
@ -348,6 +387,55 @@ const WorkflowEditor = () => {
}
};
// Handle node changes
const handleNodesChange = useCallback((changes) => {
onNodesChange(changes);
}, [onNodesChange]);
// Handle edge changes
const handleEdgesChange = useCallback((changes) => {
onEdgesChange(changes);
}, [onEdgesChange]);
// Handle viewport changes to maintain pan/zoom state
const onViewportChange = useCallback((newViewport) => {
setViewport(newViewport);
// Save viewport to localStorage for this workflow
if (id && id !== 'new') {
localStorage.setItem(`workflow-viewport-${id}`, JSON.stringify(newViewport));
}
}, [id]);
// Load saved viewport for existing workflows
useEffect(() => {
if (id && id !== 'new') {
const savedViewport = localStorage.getItem(`workflow-viewport-${id}`);
if (savedViewport) {
try {
const parsedViewport = JSON.parse(savedViewport);
setViewport(parsedViewport);
} catch (error) {
console.warn('Failed to parse saved viewport:', error);
}
}
}
}, [id]);
// Handle React Flow initialization
const onReactFlowInit = useCallback((reactFlowInstance) => {
setReactFlowInstance(reactFlowInstance);
// Only fit view on first load of new workflows, or if no saved viewport
if (isFirstLoad && (!id || id === 'new' || !localStorage.getItem(`workflow-viewport-${id}`))) {
setTimeout(() => {
reactFlowInstance.fitView({ padding: 0.1, maxZoom: 1.2 });
setIsFirstLoad(false);
}, 100);
} else {
setIsFirstLoad(false);
}
}, [isFirstLoad, id]);
return (
<div className="h-[calc(100vh-6rem)]">
{isLoading ? (
@ -387,7 +475,7 @@ const WorkflowEditor = () => {
workflowId={id}
selectedNode={selectedNode}
onDuplicateNode={handleDuplicateNode}
onDeleteNode={handleDeleteNode}
onDeleteNode={(node) => handleDeleteNode(node.id)}
onExecuteWorkflow={handleExecuteWorkflow}
onTestNode={() => setShowNodeTester(true)}
/>
@ -422,16 +510,27 @@ const WorkflowEditor = () => {
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onNodesChange={handleNodesChange}
onEdgesChange={handleEdgesChange}
onConnect={onConnect}
onInit={setReactFlowInstance}
onInit={onReactFlowInit}
onViewportChange={onViewportChange}
onDrop={onDrop}
onDragOver={onDragOver}
onNodeClick={onNodeClick}
onPaneClick={onPaneClick}
nodeTypes={nodeTypeDefinitions}
fitView
nodesDraggable={true}
nodesConnectable={true}
elementsSelectable={true}
panOnDrag={true}
panOnScroll={true}
zoomOnScroll={true}
zoomOnPinch={true}
zoomOnDoubleClick={false}
selectNodesOnDrag={false}
viewport={viewport}
fitViewOnInit={false}
>
<Controls />
<MiniMap />