data-primals-engine 1.3.4 → 1.4.1
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 +72 -2
- package/client/README.md +20 -0
- package/client/package-lock.json +712 -147
- package/client/package.json +38 -37
- package/client/src/App.jsx +9 -10
- package/client/src/App.scss +7 -1
- package/client/src/Dashboard.jsx +349 -208
- package/client/src/DashboardView.jsx +569 -547
- package/client/src/DataEditor.jsx +20 -2
- package/client/src/DataLayout.jsx +24 -12
- package/client/src/DataTable.jsx +807 -760
- package/client/src/DataTable.scss +187 -90
- package/client/src/Field.jsx +1783 -1656
- package/client/src/ModelCreator.jsx +2 -2
- package/client/src/ModelCreatorField.jsx +906 -804
- package/client/src/ModelList.jsx +2 -2
- package/client/src/PackGallery.jsx +391 -290
- package/client/src/PackGallery.scss +231 -210
- package/client/src/constants.js +16 -4
- package/client/src/translations.js +16750 -16392
- package/package.json +14 -11
- package/src/core.js +9 -0
- package/src/defaultModels.js +18 -10
- package/src/email.js +2 -1
- package/src/gameObject.js +1 -1
- package/src/i18n.js +501 -28
- package/src/modules/auth-google/index.js +51 -0
- package/src/modules/auth-microsoft/index.js +82 -0
- package/src/modules/auth-saml/index.js +90 -0
- package/src/modules/data/data.core.js +5 -1
- package/src/modules/data/data.history.js +489 -489
- package/src/modules/data/data.js +86 -12
- package/src/modules/data/data.routes.js +1691 -1663
- package/src/modules/user.js +19 -0
- package/src/modules/workflow.js +2 -12
- package/src/providers.js +110 -1
- package/src/sso.js +194 -0
- package/test/data.integration.test.js +3 -0
- package/test/user.test.js +1 -1
- package/client/public/demo/26899917-d1ba-4df4-bb33-48d09a8778da.jpg +0 -0
- package/client/public/demo/4b9894c7-12cd-466d-8fd3-a2757b09ec96.jpg +0 -0
- package/client/public/demo/7ed369ac-a1a2-4b45-b0cd-4fd5bcf5a75a.jpg +0 -0
package/src/modules/data/data.js
CHANGED
|
@@ -43,7 +43,7 @@ import {
|
|
|
43
43
|
} from "../mongodb.js";
|
|
44
44
|
import {dbUrl, MongoClient, MongoDatabase} from "../../engine.js";
|
|
45
45
|
import path from "node:path";
|
|
46
|
-
import {getObjectHash, getRandom, isGUID, isPlainObject, randomDate} from "../../core.js";
|
|
46
|
+
import {getObjectHash, getRandom, isGUID, isPlainObject, randomDate, sequential} from "../../core.js";
|
|
47
47
|
import {Event} from "../../events.js";
|
|
48
48
|
import fs from "node:fs";
|
|
49
49
|
import schedule from "node-schedule";
|
|
@@ -52,7 +52,7 @@ import i18n from "../../i18n.js";
|
|
|
52
52
|
import {
|
|
53
53
|
executeSafeJavascript,
|
|
54
54
|
runScheduledJobWithDbLock,
|
|
55
|
-
scheduleWorkflowTriggers,
|
|
55
|
+
scheduleWorkflowTriggers, substituteVariables,
|
|
56
56
|
triggerWorkflows
|
|
57
57
|
} from "../workflow.js";
|
|
58
58
|
import NodeCache from "node-cache";
|
|
@@ -60,13 +60,14 @@ import AWS from 'aws-sdk';
|
|
|
60
60
|
import checkDiskSpace from "check-disk-space";
|
|
61
61
|
import {addFile, removeFile} from "../file.js";
|
|
62
62
|
import {downloadFromS3, getUserS3Config, listS3Backups, uploadToS3} from "../bucket.js";
|
|
63
|
-
import {calculateTotalUserStorageUsage, hasPermission, middlewareAuthenticator} from "../user.js";
|
|
63
|
+
import {calculateTotalUserStorageUsage, getSmtpConfig, hasPermission, middlewareAuthenticator} from "../user.js";
|
|
64
64
|
import {getAllPacks} from "../../packs.js";
|
|
65
65
|
import {Config} from "../../config.js";
|
|
66
66
|
import {profiles} from "../../../client/src/constants.js";
|
|
67
67
|
import {registerRoutes, sendSseToUser} from "./data.routes.js";
|
|
68
68
|
import {importJobs, modelsCache, mongoDBWhitelist, runCryptoWorkerTask, runImportExportWorker} from "./data.core.js";
|
|
69
69
|
import readXlsxFile from "read-excel-file/node";
|
|
70
|
+
import {sendEmail} from "../../email.js";
|
|
70
71
|
|
|
71
72
|
let engine;
|
|
72
73
|
let logger;
|
|
@@ -543,7 +544,7 @@ const validateField = (field) => {
|
|
|
543
544
|
break;
|
|
544
545
|
}
|
|
545
546
|
case 'number':
|
|
546
|
-
allowedFieldTest(['min', 'max', 'step', 'unit']);
|
|
547
|
+
allowedFieldTest(['min', 'max', 'step', 'unit', 'delay', 'gauge', 'percent']);
|
|
547
548
|
if (field.min !== undefined && typeof field.min !== 'number') {
|
|
548
549
|
throw new Error(i18n.t('api.validate.fieldNumber', "L'attribut '{{0}}' doit être un nombre.", ["min"]));
|
|
549
550
|
}
|
|
@@ -559,6 +560,15 @@ const validateField = (field) => {
|
|
|
559
560
|
if (field.unit !== undefined && typeof field.unit !== 'string') {
|
|
560
561
|
throw new Error(i18n.t('api.validate.fieldString', "Le champ '{{0}}' doit être une chaîne de caractères.", ["unit"]));
|
|
561
562
|
}
|
|
563
|
+
if (field.delay !== undefined && typeof field.delay !== 'boolean') {
|
|
564
|
+
throw new Error(i18n.t('api.validate.fieldBoolean', "Le champ '{{0}}' doit être un booléen.", ["unit"]));
|
|
565
|
+
}
|
|
566
|
+
if (field.gauge !== undefined && typeof field.gauge !== 'boolean') {
|
|
567
|
+
throw new Error(i18n.t('api.validate.fieldBoolean', "L'attribut '{{0}}' doit être un booléen.", ["gauge"]));
|
|
568
|
+
}
|
|
569
|
+
if (field.percent !== undefined && typeof field.percent !== 'boolean') {
|
|
570
|
+
throw new Error(i18n.t('api.validate.fieldBoolean', "L'attribut '{{0}}' doit être un booléen.", ["percent"]));
|
|
571
|
+
}
|
|
562
572
|
break;
|
|
563
573
|
case 'string':
|
|
564
574
|
case 'string_t':
|
|
@@ -780,7 +790,7 @@ function convertDataTypes(dataArray, modelFields, sourceType = 'csv') {
|
|
|
780
790
|
});
|
|
781
791
|
}
|
|
782
792
|
|
|
783
|
-
export
|
|
793
|
+
export const cancelAlerts = async (user) => {
|
|
784
794
|
|
|
785
795
|
const datasCollection = getCollection('datas'); // Alerts are in the global collection
|
|
786
796
|
|
|
@@ -840,15 +850,56 @@ async function runStatefulAlertJob(alertId) {
|
|
|
840
850
|
if (count > 0) {
|
|
841
851
|
logger.info(`[Scheduled Job] Condition met for alert ${alertDoc.name} (ID: ${alertId}). Sending notification and updating state.`);
|
|
842
852
|
|
|
853
|
+
let emailSent = false;
|
|
854
|
+
try {
|
|
855
|
+
const user = await engine.userProvider.findUserByUsername(alertDoc._user);
|
|
856
|
+
if (user && user.email) {
|
|
857
|
+
const smtpConfig = await getSmtpConfig(user);
|
|
858
|
+
if (alertDoc.sendEmail && smtpConfig) {
|
|
859
|
+
const userLang = user.lang || 'en';
|
|
860
|
+
let emailContent, msg;
|
|
861
|
+
if (alertDoc.message){
|
|
862
|
+
if (alertDoc.message[userLang])
|
|
863
|
+
msg= alertDoc.message[userLang];
|
|
864
|
+
else
|
|
865
|
+
msg = alertDoc.message[Object.keys(alertDoc.message)[0]];
|
|
866
|
+
emailContent = await substituteVariables(msg, { count, alert: alertDoc });
|
|
867
|
+
} else {
|
|
868
|
+
// Sinon, utiliser le message par défaut
|
|
869
|
+
emailContent = i18n.t('alert.email.content', `L'alerte '${alertDoc.name}' s'est déclenchée. ${count} élément(s) correspondent à votre condition.`, { name: alertDoc.name, count: count });
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
await sendEmail(
|
|
873
|
+
user.email,
|
|
874
|
+
{
|
|
875
|
+
title: i18n.t('alert.email.title', `Alerte: ${alertDoc.name}`),
|
|
876
|
+
content: emailContent
|
|
877
|
+
},
|
|
878
|
+
smtpConfig,
|
|
879
|
+
userLang
|
|
880
|
+
);
|
|
881
|
+
emailSent = true;
|
|
882
|
+
logger.info(`[Scheduled Job] Email notification sent for alert ${alertId} to ${user.email}.`);
|
|
883
|
+
} else if (alertDoc.sendEmail) {
|
|
884
|
+
logger.warn(`[Scheduled Job] Could not send email for alert ${alertId}. SMTP config is missing or incomplete for user ${user.username}.`);
|
|
885
|
+
}
|
|
886
|
+
} else {
|
|
887
|
+
logger.warn(`[Scheduled Job] Could not send email for alert ${alertId}. User ${alertDoc._user} not found or has no email address.`);
|
|
888
|
+
}
|
|
889
|
+
} catch (emailError) {
|
|
890
|
+
logger.error(`[Scheduled Job] Failed to send email for alert ${alertId}:`, emailError);
|
|
891
|
+
}
|
|
892
|
+
|
|
843
893
|
// Send notification
|
|
844
894
|
const alertPayload = {
|
|
845
895
|
type: 'cron_alert',
|
|
846
896
|
triggerId: alertDoc._id.toString(),
|
|
847
897
|
triggerName: alertDoc.name,
|
|
848
898
|
timestamp: new Date().toISOString(),
|
|
849
|
-
message: `Alerte '${alertDoc.name}': ${count} élément(s) correspondent à votre condition
|
|
899
|
+
message: `Alerte '${alertDoc.name}': ${count} élément(s) correspondent à votre condition.`,
|
|
900
|
+
emailSent
|
|
850
901
|
};
|
|
851
|
-
|
|
902
|
+
sendSseToUser(alertDoc._user, alertPayload);
|
|
852
903
|
|
|
853
904
|
// Update state in DB to prevent re-notification
|
|
854
905
|
await datasCollection.updateOne(
|
|
@@ -4313,14 +4364,20 @@ async function manageBackupRotation(user, backupFrequency, s3Config = null) { //
|
|
|
4313
4364
|
* @param {string} [lang='en'] - Language code for localized data
|
|
4314
4365
|
* @returns {Promise<{success: boolean, summary: object, errors: Array, modifiedCount: number}>}
|
|
4315
4366
|
*/
|
|
4316
|
-
export async function installPack(packIdentifier, user = null, lang = 'en',
|
|
4367
|
+
export async function installPack(packIdentifier, user = null, lang = 'en', options = {}) {
|
|
4317
4368
|
let pack;
|
|
4318
4369
|
const packsCollection = getCollection('packs');
|
|
4319
4370
|
|
|
4320
4371
|
// Determine if we're working with an ID or direct pack object
|
|
4321
4372
|
if (typeof packIdentifier === 'string') {
|
|
4373
|
+
let p;
|
|
4374
|
+
try {
|
|
4375
|
+
p = new ObjectId(packIdentifier);
|
|
4376
|
+
} catch (e) {
|
|
4377
|
+
p = packIdentifier;
|
|
4378
|
+
}
|
|
4322
4379
|
// Existing behavior - fetch from database
|
|
4323
|
-
pack = await packsCollection.findOne({ _id:
|
|
4380
|
+
pack = await packsCollection.findOne({ $and: [{ _user: {$exists: false} }, {private: false}, {$or:[{ _id: p}, { name: packIdentifier }]}]} );
|
|
4324
4381
|
if (!pack) {
|
|
4325
4382
|
throw new Error(`Pack with ID ${packIdentifier} not found.`);
|
|
4326
4383
|
}
|
|
@@ -4359,6 +4416,8 @@ export async function installPack(packIdentifier, user = null, lang = 'en', isTe
|
|
|
4359
4416
|
? await modelsCollection.find({ _user: username }).toArray()
|
|
4360
4417
|
: await modelsCollection.find({ _user: { $exists: false } }).toArray();
|
|
4361
4418
|
|
|
4419
|
+
console.log("EXISTING", existingModels);
|
|
4420
|
+
|
|
4362
4421
|
const existingModelNames = existingModels.map(m => m.name);
|
|
4363
4422
|
|
|
4364
4423
|
for (const modelOrName of pack.models) {
|
|
@@ -4406,7 +4465,7 @@ export async function installPack(packIdentifier, user = null, lang = 'en', isTe
|
|
|
4406
4465
|
const dataToInstall = { ...pack.data?.all, ...pack.data?.[lang] };
|
|
4407
4466
|
if (!dataToInstall || Object.keys(dataToInstall).length === 0) {
|
|
4408
4467
|
logger.warn(`Pack '${pack.name}' has no data to install.`);
|
|
4409
|
-
return { success:
|
|
4468
|
+
return { success: false, summary, errors, modifiedCount: 0 };
|
|
4410
4469
|
}
|
|
4411
4470
|
|
|
4412
4471
|
// Process link references (same as original)
|
|
@@ -4559,6 +4618,14 @@ export async function installPack(packIdentifier, user = null, lang = 'en', isTe
|
|
|
4559
4618
|
}
|
|
4560
4619
|
}
|
|
4561
4620
|
|
|
4621
|
+
if( options.installForUser && user?.username ){
|
|
4622
|
+
if( pack.name )
|
|
4623
|
+
await packsCollection.deleteOne({ name: pack.name, _user: user.username });
|
|
4624
|
+
logger.info(`--- Creating pack '${pack.name}' for user... ---`);
|
|
4625
|
+
const packToCreate = {...pack, _id: undefined, private: true, _user: user.username };
|
|
4626
|
+
await packsCollection.insertOne(packToCreate);
|
|
4627
|
+
}
|
|
4628
|
+
|
|
4562
4629
|
// Trigger event only if pack came from database (original behavior)
|
|
4563
4630
|
if (typeof packIdentifier === 'string') {
|
|
4564
4631
|
await Event.Trigger("OnPackInstalled", "event", "system", pack);
|
|
@@ -4577,13 +4644,14 @@ export async function installPack(packIdentifier, user = null, lang = 'en', isTe
|
|
|
4577
4644
|
export const installAllPacks = async () => {
|
|
4578
4645
|
const packs = await getAllPacks();
|
|
4579
4646
|
await packsCollection.deleteMany({ _user: { $exists : false }});
|
|
4580
|
-
await packsCollection.insertMany(packs);
|
|
4647
|
+
await packsCollection.insertMany(packs.map(p =>({...p, private: false })));
|
|
4581
4648
|
}
|
|
4582
4649
|
|
|
4583
4650
|
export async function handleDemoInitialization(req, res) {
|
|
4584
4651
|
const user = req.me;
|
|
4585
4652
|
const body = req.fields;
|
|
4586
|
-
const
|
|
4653
|
+
const packs = body.packs;
|
|
4654
|
+
const models = (Object.keys(profiles).includes(body.profile) && profiles[body.profile].models) || '';
|
|
4587
4655
|
if (!isDemoUser(user)) {
|
|
4588
4656
|
return res.status(403).json({ success: false, error: "This action is only for demo users." });
|
|
4589
4657
|
}
|
|
@@ -4617,10 +4685,16 @@ export async function handleDemoInitialization(req, res) {
|
|
|
4617
4685
|
|
|
4618
4686
|
logger.info(`[Demo Init] Installing dynamically generated pack with models: [${models.join(', ')}].`);
|
|
4619
4687
|
|
|
4688
|
+
await sequential(packs.map(p => {
|
|
4689
|
+
return () => installPack(p, user, req.query.lang || 'en');
|
|
4690
|
+
}));
|
|
4691
|
+
|
|
4620
4692
|
// Create and install pack
|
|
4621
4693
|
const result = await installPack(packToInstall, user, req.query.lang || 'en');
|
|
4622
4694
|
|
|
4623
4695
|
if (result.success || result.modifiedCount > 0) {
|
|
4696
|
+
|
|
4697
|
+
await Event.Trigger('OnDemoUserAdded', "event", "system", req.me.username);
|
|
4624
4698
|
logger.info(`[Demo Init] Pack installed successfully for user '${user.username}'.`);
|
|
4625
4699
|
res.status(200).json({ success: true, message: "Demo environment initialized successfully.", summary: result.summary });
|
|
4626
4700
|
} else {
|