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:
parent
74fb343ead
commit
1c317d1a1d
30
backend/knexfile.js
Normal file
30
backend/knexfile.js
Normal 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
|
||||
}
|
||||
};
|
@ -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"}
|
||||
|
@ -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
5751
backend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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 });
|
||||
|
@ -1,5 +1,3 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
frontend:
|
||||
build:
|
||||
|
15260
frontend/package-lock.json
generated
Normal file
15260
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -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",
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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;
|
||||
|
@ -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 />
|
||||
|
Loading…
x
Reference in New Issue
Block a user