data-primals-engine 1.7.1 → 1.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -9
- package/client/package-lock.json +8430 -8121
- package/client/package.json +7 -4
- package/client/src/APIInfo.jsx +1 -1
- package/client/src/App.jsx +2 -1
- package/client/src/App.scss +1635 -1626
- package/client/src/AssistantChat.jsx +2 -3
- package/client/src/CalendarView.jsx +1 -0
- package/client/src/ContentView.jsx +3 -3
- package/client/src/Dashboard.jsx +5 -2
- package/client/src/DashboardChart.jsx +1 -0
- package/client/src/DashboardFlexViewItem.jsx +1 -0
- package/client/src/DashboardHtmlViewItem.jsx +1 -0
- package/client/src/DashboardView.jsx +2 -0
- package/client/src/DataEditor.jsx +0 -1
- package/client/src/DataImporter.jsx +489 -468
- package/client/src/DataLayout.jsx +25 -23
- package/client/src/DataTable.jsx +6 -5
- package/client/src/Dialog.jsx +92 -90
- package/client/src/Dialog.scss +122 -116
- package/client/src/DisplayFlexNodeRenderer.jsx +1 -0
- package/client/src/DocumentationPageLayout.scss +1 -1
- package/client/src/FlexBuilderControls.jsx +1 -0
- package/client/src/FlexBuilderPreview.jsx +1 -1
- package/client/src/FlexNode.jsx +1 -1
- package/client/src/HistoryDialog.jsx +3 -2
- package/client/src/KPIWidget.jsx +1 -1
- package/client/src/KanbanView.jsx +1 -0
- package/client/src/ModelCreator.jsx +4 -0
- package/client/src/ModelList.jsx +1 -0
- package/client/src/PackGallery.jsx +5 -4
- package/client/src/RTETrans.jsx +1 -1
- package/client/src/RelationField.jsx +2 -0
- package/client/src/RelationSelectorWidget.jsx +2 -0
- package/client/src/RelationValue.jsx +1 -0
- package/client/src/RestoreDialog.jsx +1 -0
- package/client/src/ViewSwitcher.jsx +1 -1
- package/client/src/WorkflowEditor.jsx +3 -0
- package/client/src/contexts/CommandContext.jsx +2 -1
- package/client/src/contexts/ModelContext.jsx +3 -3
- package/client/src/hooks/data.js +1 -0
- package/client/src/hooks/useValidation.js +1 -0
- package/client/src/translations.js +24 -24
- package/client/vite.config.js +31 -30
- package/doc/AI-assistance.md +87 -63
- package/doc/Concepts.md +122 -0
- package/doc/Custom-Endpoints.md +31 -0
- package/doc/Event-system.md +13 -14
- package/doc/Home.md +33 -0
- package/doc/Modules.md +83 -0
- package/doc/Packs-gallery.md +8 -23
- package/doc/Workflows.md +32 -0
- package/doc/automation-workflows.md +141 -102
- package/doc/dashboards-kpis-charts.md +47 -49
- package/doc/data-management.md +126 -120
- package/doc/data-models.md +68 -75
- package/doc/roles-permissions.md +144 -43
- package/doc/sharding-replication.md +158 -0
- package/doc/users.md +54 -30
- package/package.json +27 -17
- package/server.js +37 -37
- package/src/ai.jobs.js +135 -0
- package/src/constants.js +560 -545
- package/src/data.js +521 -518
- package/src/email.js +157 -154
- package/src/engine.js +167 -49
- package/src/gameObject.js +6 -0
- package/src/i18n.js +0 -1
- package/src/modules/assistant/assistant.js +782 -763
- package/src/modules/assistant/constants.js +23 -16
- package/src/modules/assistant/providers.js +77 -37
- package/src/modules/auth-google/index.js +53 -50
- package/src/modules/bucket.js +346 -335
- package/src/modules/data/data.backup.js +400 -376
- package/src/modules/data/data.cluster.js +559 -0
- package/src/modules/data/data.core.js +11 -8
- package/src/modules/data/data.js +311 -311
- package/src/modules/data/data.operations.js +3666 -3555
- package/src/modules/data/data.relations.js +1 -0
- package/src/modules/data/data.replication.js +125 -0
- package/src/modules/data/data.routes.js +2206 -1879
- package/src/modules/data/data.scheduling.js +2 -1
- package/src/modules/file.js +248 -247
- package/src/modules/mongodb.js +76 -73
- package/src/modules/user.js +18 -2
- package/src/modules/worker-script-runner.js +97 -0
- package/src/modules/workflow.js +177 -52
- package/src/packs.js +5701 -5701
- package/src/providers.js +298 -297
- package/src/sso.js +91 -25
- package/test/assistant.test.js +207 -206
- package/test/cluster.test.js +221 -0
- package/test/core.test.js +0 -2
- package/test/data.integration.test.js +1425 -1416
- package/test/import_export.integration.test.js +210 -210
- package/test/replication.test.js +163 -0
- package/test/workflow.actions.integration.test.js +487 -475
- package/test/workflow.integration.test.js +332 -329
- package/doc/core-concepts.md +0 -33
package/src/modules/mongodb.js
CHANGED
|
@@ -1,73 +1,76 @@
|
|
|
1
|
-
|
|
2
|
-
import {Logger} from "../gameObject.js";
|
|
3
|
-
import {MongoDatabase} from "../engine.js";
|
|
4
|
-
import {ObjectId} from "mongodb";
|
|
5
|
-
import {isLocalUser} from "../data.js";
|
|
6
|
-
import {Event} from "../events.js";
|
|
7
|
-
import {Config} from "../config.js";
|
|
8
|
-
|
|
9
|
-
export let modelsCollection, datasCollection, filesCollection, packsCollection;
|
|
10
|
-
|
|
11
|
-
export {ObjectId};
|
|
12
|
-
|
|
13
|
-
let engine, logger, currentDb
|
|
14
|
-
|
|
15
|
-
let colls= [];
|
|
16
|
-
export async function onInit(defaultEngine) {
|
|
17
|
-
engine = defaultEngine;
|
|
18
|
-
logger = engine.getComponent(Logger);
|
|
19
|
-
|
|
20
|
-
currentDb = MongoDatabase();
|
|
21
|
-
modelsCollection = getCollection("models");
|
|
22
|
-
datasCollection = getCollection(Config.Get('dataCollection', 'datas'));
|
|
23
|
-
filesCollection = getCollection("files");
|
|
24
|
-
packsCollection = getCollection("packs");
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
export const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
1
|
+
|
|
2
|
+
import {Logger} from "../gameObject.js";
|
|
3
|
+
import {MongoDatabase} from "../engine.js";
|
|
4
|
+
import {ObjectId} from "mongodb";
|
|
5
|
+
import {isLocalUser} from "../data.js";
|
|
6
|
+
import {Event} from "../events.js";
|
|
7
|
+
import {Config} from "../config.js";
|
|
8
|
+
|
|
9
|
+
export let modelsCollection, datasCollection, filesCollection, packsCollection;
|
|
10
|
+
|
|
11
|
+
export {ObjectId};
|
|
12
|
+
|
|
13
|
+
let engine, logger, currentDb
|
|
14
|
+
|
|
15
|
+
let colls= [];
|
|
16
|
+
export async function onInit(defaultEngine) {
|
|
17
|
+
engine = defaultEngine;
|
|
18
|
+
logger = engine.getComponent(Logger);
|
|
19
|
+
|
|
20
|
+
currentDb = MongoDatabase();
|
|
21
|
+
modelsCollection = getCollection("models");
|
|
22
|
+
datasCollection = getCollection(Config.Get('dataCollection', 'datas'));
|
|
23
|
+
filesCollection = getCollection("files");
|
|
24
|
+
packsCollection = getCollection("packs");
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
colls = await (currentDb.listCollections()).toArray();
|
|
28
|
+
|
|
29
|
+
await Event.Trigger("OnDatabaseLoaded", "system", "calls", engine)
|
|
30
|
+
logger.info("MongoDB collections loaded.");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const getCollections= async (forceRefresh)=>{
|
|
34
|
+
if( !forceRefresh )
|
|
35
|
+
return colls;
|
|
36
|
+
colls = await currentDb.listCollections().toArray();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const createCollection = async (coll)=>{
|
|
40
|
+
const found =colls.find(f => f.name === coll);
|
|
41
|
+
if( found){
|
|
42
|
+
return getCollection(coll);
|
|
43
|
+
}
|
|
44
|
+
return await currentDb.createCollection(coll);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const isObjectId = (id) => {
|
|
48
|
+
return (typeof(id) === 'string' && id.match(/^[0-9a-fA-F]{24}$/));
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
export const getCollection = (str) => {
|
|
53
|
+
if (typeof str !== 'string') {
|
|
54
|
+
logger?.error(`[MongoDB] Tentative d'accès à une collection avec un type invalide: ${typeof str}`);
|
|
55
|
+
throw new Error("Le nom de la collection doit être une chaîne de caractères.");
|
|
56
|
+
}
|
|
57
|
+
return currentDb.collection(str);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const getDatabase = () => {
|
|
61
|
+
return currentDb;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// New function to determine the collection name for a user
|
|
65
|
+
export const getUserCollectionName = async (user) => {
|
|
66
|
+
const feat = await engine.userProvider.hasFeature(user, 'indexes');
|
|
67
|
+
const dataCollectionName = Config.Get('dataCollection', 'datas');
|
|
68
|
+
return feat ? (isLocalUser(user) ? `${dataCollectionName}_${user._user}` :`${dataCollectionName}_${user.username}` ) : dataCollectionName;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
// Modify existing functions to use the correct collection
|
|
73
|
+
export const getCollectionForUser = async (user) => {
|
|
74
|
+
const collectionName = await getUserCollectionName(user);
|
|
75
|
+
return getCollection(collectionName);
|
|
76
|
+
};
|
package/src/modules/user.js
CHANGED
|
@@ -68,7 +68,8 @@ export const middlewareAuthenticator = async (req, res, next) => {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
try {
|
|
71
|
-
|
|
71
|
+
const logger = engine.getComponent(Logger);
|
|
72
|
+
// 1. On demande au provider d'identifier l'utilisateur
|
|
72
73
|
await engine.userProvider.initiateUser(req);
|
|
73
74
|
|
|
74
75
|
// 2. On vérifie simplement si le provider a attaché un utilisateur
|
|
@@ -344,6 +345,15 @@ export async function getEnv(user){
|
|
|
344
345
|
return envObject;
|
|
345
346
|
}
|
|
346
347
|
|
|
348
|
+
export function getInternalSmtpConfig(){
|
|
349
|
+
const cfg = Config.Get('emailDefaultConfig', emailDefaultConfig);
|
|
350
|
+
cfg.host = process.env.SMTP_HOST || cfg.host;
|
|
351
|
+
cfg.port = process.env.SMTP_PORT || cfg.port;
|
|
352
|
+
cfg.user = process.env.SMTP_USER || cfg.user;
|
|
353
|
+
cfg.pass = process.env.SMTP_PASS || cfg.pass;
|
|
354
|
+
return cfg;
|
|
355
|
+
}
|
|
356
|
+
|
|
347
357
|
export async function getSmtpConfig(user) {
|
|
348
358
|
|
|
349
359
|
const cfg = Config.Get('emailDefaultConfig', emailDefaultConfig);
|
|
@@ -358,8 +368,14 @@ export async function getSmtpConfig(user) {
|
|
|
358
368
|
acc[variable.name.replace('SMTP_', '').toLowerCase()] = variable.value;
|
|
359
369
|
return acc;
|
|
360
370
|
}, {});
|
|
371
|
+
|
|
361
372
|
if( !smtpConfig.port )
|
|
362
373
|
smtpConfig.port = cfg.port;
|
|
363
|
-
|
|
374
|
+
if( !smtpConfig.host )
|
|
375
|
+
smtpConfig.host = cfg.host;
|
|
376
|
+
if( !smtpConfig.user )
|
|
377
|
+
smtpConfig.user = cfg.user;
|
|
378
|
+
if( !smtpConfig.pass )
|
|
379
|
+
smtpConfig.pass = cfg.pass;
|
|
364
380
|
return smtpConfig;
|
|
365
381
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { parentPort } from 'worker_threads';
|
|
2
|
+
|
|
3
|
+
let callIdCounter = 0;
|
|
4
|
+
const pendingCalls = new Map();
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Appelle une fonction sur le thread principal et attend la réponse.
|
|
8
|
+
* @param {string} service - Le nom du service (ex: 'db').
|
|
9
|
+
* @param {string} method - Le nom de la méthode (ex: 'find').
|
|
10
|
+
* @param {...any} args - Les arguments de la méthode.
|
|
11
|
+
* @returns {Promise<any>}
|
|
12
|
+
*/
|
|
13
|
+
function callHost(service, method, ...args) {
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
const callId = callIdCounter++;
|
|
16
|
+
pendingCalls.set(callId, { resolve, reject });
|
|
17
|
+
parentPort.postMessage({ type: 'call', callId, service, method, args });
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// API exposée au script de l'utilisateur
|
|
22
|
+
const db = {
|
|
23
|
+
create: (model, data) => callHost('db', 'create', model, data),
|
|
24
|
+
find: (model, filter, options) => callHost('db', 'find', model, filter, options),
|
|
25
|
+
findOne: (model, filter) => callHost('db', 'findOne', model, filter),
|
|
26
|
+
update: (model, filter, update) => callHost('db', 'update', model, filter, update),
|
|
27
|
+
delete: (model, filter) => callHost('db', 'delete', model, filter),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const workflow = {
|
|
31
|
+
run: (name, context) => callHost('workflow', 'run', name, context),
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const logger = {
|
|
35
|
+
info: (...args) => callHost('log', 'info', ...args),
|
|
36
|
+
warn: (...args) => callHost('log', 'warn', ...args),
|
|
37
|
+
error: (...args) => callHost('log', 'error', ...args),
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const env = {
|
|
41
|
+
get: (name) => callHost('env', 'get', name),
|
|
42
|
+
getAll: () => callHost('env', 'getAll'),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const http = {
|
|
46
|
+
request: (method, url, options) => callHost('http', 'request', method, url, options),
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Exécute le code utilisateur dans un scope sécurisé.
|
|
51
|
+
* @param {string} code - Le code à exécuter.
|
|
52
|
+
* @param {object} context - Le contexte du workflow.
|
|
53
|
+
*/
|
|
54
|
+
async function runUserCode(code, context) {
|
|
55
|
+
// Crée une fonction asynchrone avec le code utilisateur.
|
|
56
|
+
// Les variables `db`, `logger`, `context`, etc., sont disponibles dans son scope.
|
|
57
|
+
const userFunction = new Function('db', 'workflow', 'logger', 'env', 'http', 'context', `return (async () => { ${code} })();`);
|
|
58
|
+
|
|
59
|
+
// Appelle la fonction avec l'API sandboxed.
|
|
60
|
+
return userFunction(db, workflow, logger, env, http, context);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Gestionnaire de messages unique pour le worker
|
|
64
|
+
parentPort.on('message', (msg) => {
|
|
65
|
+
// Message initial pour démarrer l'exécution
|
|
66
|
+
if (msg.code) {
|
|
67
|
+
// Le worker ne doit pas se terminer tant que le code n'est pas fini.
|
|
68
|
+
parentPort.ref();
|
|
69
|
+
runUserCode(msg.code, msg.context)
|
|
70
|
+
.then(result => {
|
|
71
|
+
parentPort.postMessage({ type: 'done', result });
|
|
72
|
+
})
|
|
73
|
+
.catch(e => {
|
|
74
|
+
parentPort.postMessage({ type: 'error', error: e.message });
|
|
75
|
+
})
|
|
76
|
+
.finally(() => {
|
|
77
|
+
// Une fois terminé, le worker peut se fermer.
|
|
78
|
+
parentPort.unref();
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
// Message de réponse à un appel `callHost`
|
|
82
|
+
else if (msg.type === 'response') {
|
|
83
|
+
const { callId, result, error } = msg;
|
|
84
|
+
if (pendingCalls.has(callId)) {
|
|
85
|
+
const { resolve, reject } = pendingCalls.get(callId);
|
|
86
|
+
if (error) {
|
|
87
|
+
reject(new Error(error));
|
|
88
|
+
} else {
|
|
89
|
+
resolve(result);
|
|
90
|
+
}
|
|
91
|
+
pendingCalls.delete(callId);
|
|
92
|
+
}
|
|
93
|
+
} else if (msg.type === 'call') {
|
|
94
|
+
// Ce cas ne devrait pas arriver (le worker envoie 'call', il ne le reçoit pas),
|
|
95
|
+
// mais on le laisse pour la robustesse.
|
|
96
|
+
}
|
|
97
|
+
});
|
package/src/modules/workflow.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import {getCollection, getCollectionForUser, isObjectId} from "./mongodb.js";
|
|
2
2
|
import schedule from "node-schedule";
|
|
3
3
|
import {ObjectId} from "mongodb";
|
|
4
|
-
|
|
5
|
-
import ivm from 'isolated-vm';
|
|
4
|
+
import { Worker } from 'worker_threads';
|
|
6
5
|
|
|
7
6
|
import {Logger} from "../gameObject.js";
|
|
8
7
|
import {deleteData, getModel, insertData, patchData, searchData} from "./data/index.js";
|
|
@@ -19,7 +18,22 @@ import { providers } from "./assistant/constants.js";
|
|
|
19
18
|
import { getAIProvider } from "./assistant/providers.js";
|
|
20
19
|
import {Config} from "../config.js";
|
|
21
20
|
import {safeAssignObject} from "../core.js";
|
|
22
|
-
|
|
21
|
+
|
|
22
|
+
// Add Node.js path utilities for worker_threads
|
|
23
|
+
import { fileURLToPath, URL } from 'node:url';
|
|
24
|
+
import path from "node:path";
|
|
25
|
+
let executionEngine = { type: null, module: null };
|
|
26
|
+
try {
|
|
27
|
+
// Tentative de chargement de 'isolated-vm'.
|
|
28
|
+
// Si cela échoue (ex: incompatibilité d'architecture, build manquant),
|
|
29
|
+
// le serveur pourra quand même démarrer.
|
|
30
|
+
|
|
31
|
+
const ivm = (await import(/* @vite-ignore */ 'isolated-vm')).default;
|
|
32
|
+
executionEngine = { type: 'isolated-vm', module: ivm };
|
|
33
|
+
} catch (e) {
|
|
34
|
+
executionEngine = { type: 'worker_threads', module: null };
|
|
35
|
+
console.warn(`[Module] Le module 'isolated-vm' n'a pas pu être chargé. L'action de workflow 'ExecuteScript' sera désactivée. Erreur : ${e.message}`);
|
|
36
|
+
}
|
|
23
37
|
|
|
24
38
|
/**
|
|
25
39
|
* Récupère une valeur imbriquée dans un objet (ex: 'user.address.city').
|
|
@@ -383,7 +397,103 @@ async function handleWaitAction(actionDef, contextData, user) {
|
|
|
383
397
|
}
|
|
384
398
|
|
|
385
399
|
|
|
386
|
-
|
|
400
|
+
async function executeWithWorkerThreads(actionDef, context, user) {
|
|
401
|
+
const TIMEOUT = 5000;
|
|
402
|
+
const collectedLogs = [];
|
|
403
|
+
const code = actionDef.script;
|
|
404
|
+
// Contexte sécurisé
|
|
405
|
+
const safeContext = JSON.parse(JSON.stringify(context));
|
|
406
|
+
|
|
407
|
+
return new Promise((resolve, reject) => {
|
|
408
|
+
// Construct an absolute file path for the worker. This is the most reliable method,
|
|
409
|
+
// ensuring Node.js loads the script directly without ambiguity.
|
|
410
|
+
const workerPath = path.dirname('src/workers/worker-script-runner.js');
|
|
411
|
+
|
|
412
|
+
const worker = new Worker(workerPath);
|
|
413
|
+
|
|
414
|
+
const timeoutId = setTimeout(() => {
|
|
415
|
+
worker.terminate();
|
|
416
|
+
const errorMessage = `Script execution timed out after ${TIMEOUT}ms.`;
|
|
417
|
+
collectedLogs.push({level: 'critical', message: errorMessage, timestamp: new Date().toISOString()});
|
|
418
|
+
resolve({success: false, message: errorMessage, logs: collectedLogs});
|
|
419
|
+
}, TIMEOUT);
|
|
420
|
+
|
|
421
|
+
worker.on('message', async (msg) => {
|
|
422
|
+
if (msg.type === 'call') {
|
|
423
|
+
const {callId, service, method, args} = msg;
|
|
424
|
+
try {
|
|
425
|
+
let result;
|
|
426
|
+
// Recréez ici la logique d'appel des fonctions natives
|
|
427
|
+
// C'est un pont entre le worker et votre application principale.
|
|
428
|
+
if (service === 'db') {
|
|
429
|
+
if (method === 'find') result = await searchData({
|
|
430
|
+
model: args[0],
|
|
431
|
+
filter: args[1], ...args[2]
|
|
432
|
+
}, user);
|
|
433
|
+
else if (method === 'findOne') result = (await searchData({
|
|
434
|
+
model: args[0],
|
|
435
|
+
filter: args[1],
|
|
436
|
+
limit: 1
|
|
437
|
+
}, user))?.data?.[0] || null;
|
|
438
|
+
else if (method === 'create') result = await insertData(args[0], args[1], {}, user, false);
|
|
439
|
+
else if (method === 'update') result = await patchData(args[0], args[1], args[2], {}, user, false);
|
|
440
|
+
else if (method === 'delete') result = await deleteData(args[0], args[1], user, false);
|
|
441
|
+
} else if (service === 'workflow') {
|
|
442
|
+
if (method === 'run') result = await runWorkflowByName(args[0], args[1], user);
|
|
443
|
+
} else if (service === 'log') {
|
|
444
|
+
const message = args.join(' ');
|
|
445
|
+
collectedLogs.push({level: method, message, timestamp: new Date().toISOString()});
|
|
446
|
+
logger.trace(`[Worker Script Log - ${method}]`, message);
|
|
447
|
+
result = {success: true};
|
|
448
|
+
} else if (service === 'env') {
|
|
449
|
+
if (method === 'get') result = (await searchData({
|
|
450
|
+
model: 'env',
|
|
451
|
+
filter: {name: args[0]},
|
|
452
|
+
limit: 1
|
|
453
|
+
}, user))?.data?.[0]?.value || null;
|
|
454
|
+
else if (method === 'getAll') result = await getEnv(user);
|
|
455
|
+
} else if (service === 'http') {
|
|
456
|
+
const [methodHttp, url, options] = args;
|
|
457
|
+
const fetchOptions = {
|
|
458
|
+
method: methodHttp.toUpperCase(),
|
|
459
|
+
headers: options.headers || {},
|
|
460
|
+
body: options.body ? (typeof options.body === 'object' ? JSON.stringify(options.body) : options.body) : undefined
|
|
461
|
+
};
|
|
462
|
+
const response = await fetch(url, fetchOptions);
|
|
463
|
+
const responseBody = await response.json().catch(() => response.text());
|
|
464
|
+
result = {success: response.ok, status: response.status, body: responseBody};
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
worker.postMessage({type: 'response', callId, result});
|
|
468
|
+
} catch (error) {
|
|
469
|
+
worker.postMessage({type: 'response', callId, error: error.message});
|
|
470
|
+
}
|
|
471
|
+
} else if (msg.type === 'done') {
|
|
472
|
+
clearTimeout(timeoutId);
|
|
473
|
+
worker.terminate();
|
|
474
|
+
resolve({success: true, data: msg.result, logs: collectedLogs, updatedContext: {result: msg.result}});
|
|
475
|
+
} else if (msg.type === 'error') {
|
|
476
|
+
clearTimeout(timeoutId);
|
|
477
|
+
worker.terminate();
|
|
478
|
+
const errorMessage = `Script execution failed: ${msg.error}`;
|
|
479
|
+
collectedLogs.push({level: 'critical', message: errorMessage, timestamp: new Date().toISOString()});
|
|
480
|
+
resolve({success: false, message: errorMessage, logs: collectedLogs});
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
worker.on('error', (err) => {
|
|
485
|
+
clearTimeout(timeoutId);
|
|
486
|
+
const errorMessage = `Worker error: ${err.message}`;
|
|
487
|
+
collectedLogs.push({level: 'critical', message: errorMessage, timestamp: new Date().toISOString()});
|
|
488
|
+
resolve({success: false, message: errorMessage, logs: collectedLogs});
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
worker.postMessage({code, context: safeContext});
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
async function executeWithIsolatedVm(actionDef, context, user) {
|
|
496
|
+
const ivm = executionEngine.module;
|
|
387
497
|
const code = actionDef.script;
|
|
388
498
|
const collectedLogs = [];
|
|
389
499
|
const isolate = new ivm.Isolate({ memoryLimit: 128 }); // 128MB memory limit
|
|
@@ -477,43 +587,43 @@ export async function executeSafeJavascript(actionDef, context, user) {
|
|
|
477
587
|
|
|
478
588
|
// Exécution
|
|
479
589
|
const fullScript = `
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
590
|
+
const normalizeArgs = args => args.map(arg => {
|
|
591
|
+
if (typeof arg === 'object' && arg !== null) {
|
|
592
|
+
return JSON.stringify(arg); // Convert objects to strings
|
|
593
|
+
}
|
|
594
|
+
return arg;
|
|
595
|
+
});
|
|
596
|
+
const db = {
|
|
597
|
+
create: (...args) => _db_create.applySyncPromise(null, normalizeArgs(args)),
|
|
598
|
+
find: (...args) => _db_find.applySyncPromise(null, normalizeArgs(args)),
|
|
599
|
+
findOne: (...args) => _db_findOne.applySyncPromise(null, normalizeArgs(args)),
|
|
600
|
+
update: (...args) => _db_update.applySyncPromise(null, normalizeArgs(args)),
|
|
601
|
+
delete: (...args) => _db_delete.applySyncPromise(null, normalizeArgs(args))
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
const workflow = {
|
|
605
|
+
run: (...args) => _workflow_run.applySyncPromise(null, normalizeArgs(args))
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
const logger = {
|
|
609
|
+
info: _log_info,
|
|
610
|
+
warn: _log_warn,
|
|
611
|
+
error: _log_error
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
const env = {
|
|
615
|
+
get: _env_get,
|
|
616
|
+
getAll: _env_get_all
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
const http = {
|
|
620
|
+
request: (...args) => _http_request.applySyncPromise(null, normalizeArgs(args))
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
(async function() {
|
|
624
|
+
${code}
|
|
625
|
+
})();
|
|
626
|
+
`;
|
|
517
627
|
|
|
518
628
|
const TIMEOUT = 5000;
|
|
519
629
|
const script = await isolate.compileScript(fullScript, { timeout: TIMEOUT });
|
|
@@ -556,6 +666,22 @@ export async function executeSafeJavascript(actionDef, context, user) {
|
|
|
556
666
|
}
|
|
557
667
|
}
|
|
558
668
|
|
|
669
|
+
export async function executeSafeJavascript(actionDef, context, user) {
|
|
670
|
+
// Vérifie si un moteur d'exécution est disponible.
|
|
671
|
+
if (!executionEngine.type) {
|
|
672
|
+
const errorMessage = "L'action 'ExecuteScript' est désactivée car aucun moteur d'exécution (isolated-vm ou fallback) n'a pu être initialisé.";
|
|
673
|
+
logger.error(`[Script Engine] ${errorMessage}`);
|
|
674
|
+
return { success: false, message: errorMessage, logs: [{ level: 'critical', message: errorMessage, timestamp: new Date().toISOString() }] };
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
logger.info(`[Script Engine] Using '${executionEngine.type}' for script execution.`);
|
|
678
|
+
|
|
679
|
+
if (executionEngine.type === 'isolated-vm') {
|
|
680
|
+
return executeWithIsolatedVm(actionDef, context, user);
|
|
681
|
+
} else { // 'worker_threads'
|
|
682
|
+
return executeWithWorkerThreads(actionDef, context, user);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
559
685
|
/**
|
|
560
686
|
* Handles the 'HttpRequest' workflow action.
|
|
561
687
|
* Sends an HTTP request to a specified URL with substituted data using native fetch.
|
|
@@ -567,18 +693,17 @@ export async function executeSafeJavascript(actionDef, context, user) {
|
|
|
567
693
|
* @returns {Promise<{success: boolean, message?: string, responseStatus?: number, responseBody?: any}>} - Result of the action.
|
|
568
694
|
*/
|
|
569
695
|
async function handleHttpRequestAction(actionDef, contextData, user, dbCollection) {
|
|
570
|
-
const { name: actionName, _id: actionId, url, method = 'POST', headers: headersTemplate, body: bodyTemplate } = actionDef;
|
|
571
|
-
|
|
572
|
-
// 1. Basic Validation
|
|
573
|
-
if (!url) {
|
|
574
|
-
const msg = `[handleHttpRequestAction] Action ${actionName} (${actionId}): Missing 'url'.`;
|
|
575
|
-
logger.error(msg);
|
|
576
|
-
return { success: false, message: msg };
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
logger.info(`[handleHttpRequestAction] Action ${actionName} (${actionId}): Executing webhook. Method: ${method}`);
|
|
580
696
|
|
|
581
697
|
try {
|
|
698
|
+
const {
|
|
699
|
+
method = 'GET',
|
|
700
|
+
url,
|
|
701
|
+
headers: headersTemplate,
|
|
702
|
+
body: bodyTemplate,
|
|
703
|
+
name: actionName = 'Unnamed Action',
|
|
704
|
+
_id: actionId = 'unknown'
|
|
705
|
+
} = actionDef;
|
|
706
|
+
|
|
582
707
|
// 2. Substitute Variables
|
|
583
708
|
const substitutedUrl = await substituteVariables(url, contextData, user);
|
|
584
709
|
let substitutedHeadersString;
|
|
@@ -1423,7 +1548,7 @@ export async function processWorkflowRun(workflowRunId, user) {
|
|
|
1423
1548
|
const mw = Config.Get('maxWorkflowSteps', maxWorkflowSteps);
|
|
1424
1549
|
while (currentStepId) {
|
|
1425
1550
|
if (stepCount++ >= mw) {
|
|
1426
|
-
return await logError(`Maximum workflow step executions exceeded (${
|
|
1551
|
+
return await logError(`Maximum workflow step executions exceeded (${mw} max).`);
|
|
1427
1552
|
}
|
|
1428
1553
|
|
|
1429
1554
|
const execCount = (stepExecutionsCount[currentStepId] || 0) + 1;
|