@saltcorn/server 1.0.0-beta.13 → 1.0.0-beta.14
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/app.js +3 -0
- package/load_plugins.js +39 -11
- package/locales/en.json +5 -1
- package/package.json +9 -9
- package/public/saltcorn-common.js +14 -1
- package/routes/actions.js +5 -23
- package/routes/admin.js +92 -5
- package/routes/infoarch.js +8 -1
- package/routes/plugins.js +70 -15
- package/serve.js +16 -2
package/app.js
CHANGED
|
@@ -447,6 +447,9 @@ Sitemap: ${base}sitemap.xml
|
|
|
447
447
|
app.get("*", function (req, res) {
|
|
448
448
|
res.status(404).sendWrap(req.__("Not found"), h1(req.__("Page not found")));
|
|
449
449
|
});
|
|
450
|
+
|
|
451
|
+
//prevent prototype pollution
|
|
452
|
+
delete Object.prototype.__proto__;
|
|
450
453
|
return app;
|
|
451
454
|
};
|
|
452
455
|
module.exports = getApp;
|
package/load_plugins.js
CHANGED
|
@@ -19,17 +19,46 @@ const {
|
|
|
19
19
|
resolveLatest,
|
|
20
20
|
} = require("@saltcorn/plugins-loader/stable_versioning");
|
|
21
21
|
|
|
22
|
+
const isFixedPlugin = (plugin) =>
|
|
23
|
+
plugin.location === "@saltcorn/sbadmin2" ||
|
|
24
|
+
plugin.location === "@saltcorn/base-plugin";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* return the cached engine infos or fetch them from npm and update the cache
|
|
28
|
+
* @param plugin plugin to load
|
|
29
|
+
*/
|
|
30
|
+
const getEngineInfos = async (plugin, forceFetch) => {
|
|
31
|
+
const rootState = getRootState();
|
|
32
|
+
const cached = rootState.getConfig("engines_cache", {}) || {};
|
|
33
|
+
if (cached[plugin.location] && !forceFetch) {
|
|
34
|
+
return cached[plugin.location];
|
|
35
|
+
} else {
|
|
36
|
+
getState().log(5, `Fetching versions for '${plugin.location}'`);
|
|
37
|
+
const pkgInfo = await npmFetch.json(
|
|
38
|
+
`https://registry.npmjs.org/${plugin.location}`
|
|
39
|
+
);
|
|
40
|
+
const versions = pkgInfo.versions;
|
|
41
|
+
const newCached = {};
|
|
42
|
+
for (const [k, v] of Object.entries(versions)) {
|
|
43
|
+
newCached[k] = v.engines?.saltcorn
|
|
44
|
+
? { engines: { saltcorn: v.engines.saltcorn } }
|
|
45
|
+
: {};
|
|
46
|
+
}
|
|
47
|
+
cached[plugin.location] = newCached;
|
|
48
|
+
await rootState.setConfig("engines_cache", { ...cached });
|
|
49
|
+
return newCached;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
22
53
|
/**
|
|
23
54
|
* checks the saltcorn engine property and changes the plugin version if necessary
|
|
24
55
|
* @param plugin plugin to load
|
|
25
56
|
*/
|
|
26
|
-
const ensurePluginSupport = async (plugin) => {
|
|
27
|
-
const
|
|
28
|
-
`https://registry.npmjs.org/${plugin.location}`
|
|
29
|
-
);
|
|
57
|
+
const ensurePluginSupport = async (plugin, forceFetch) => {
|
|
58
|
+
const versions = await getEngineInfos(plugin, forceFetch);
|
|
30
59
|
const supported = supportedVersion(
|
|
31
60
|
plugin.version || "latest",
|
|
32
|
-
|
|
61
|
+
versions,
|
|
33
62
|
packagejson.version
|
|
34
63
|
);
|
|
35
64
|
if (!supported)
|
|
@@ -38,8 +67,7 @@ const ensurePluginSupport = async (plugin) => {
|
|
|
38
67
|
);
|
|
39
68
|
else if (
|
|
40
69
|
supported !== plugin.version ||
|
|
41
|
-
(plugin.version === "latest" &&
|
|
42
|
-
supported !== resolveLatest(pkgInfo.versions))
|
|
70
|
+
(plugin.version === "latest" && supported !== resolveLatest(versions))
|
|
43
71
|
)
|
|
44
72
|
plugin.version = supported;
|
|
45
73
|
};
|
|
@@ -50,10 +78,10 @@ const ensurePluginSupport = async (plugin) => {
|
|
|
50
78
|
* @param plugin - plugin to load
|
|
51
79
|
* @param force - force flag
|
|
52
80
|
*/
|
|
53
|
-
const loadPlugin = async (plugin, force) => {
|
|
54
|
-
if (plugin.source === "npm" &&
|
|
81
|
+
const loadPlugin = async (plugin, force, forceFetch) => {
|
|
82
|
+
if (plugin.source === "npm" && !isFixedPlugin(plugin)) {
|
|
55
83
|
try {
|
|
56
|
-
await ensurePluginSupport(plugin);
|
|
84
|
+
await ensurePluginSupport(plugin, forceFetch);
|
|
57
85
|
} catch (e) {
|
|
58
86
|
console.log(
|
|
59
87
|
`Warning: Unable to find a supported version for '${plugin.location}' Continuing with the installed version`
|
|
@@ -279,6 +307,6 @@ module.exports = {
|
|
|
279
307
|
loadAllPlugins,
|
|
280
308
|
loadPlugin,
|
|
281
309
|
requirePlugin,
|
|
282
|
-
|
|
310
|
+
getEngineInfos,
|
|
283
311
|
ensurePluginSupport,
|
|
284
312
|
};
|
package/locales/en.json
CHANGED
|
@@ -1468,5 +1468,9 @@
|
|
|
1468
1468
|
"Time to run": "Time to run",
|
|
1469
1469
|
"Mobile": "Mobile",
|
|
1470
1470
|
"Plain password trigger row": "Plain password trigger row",
|
|
1471
|
-
"Send plaintext password changes to Users table triggers (Insert, Update and Validate).": "Send plaintext password changes to Users table triggers (Insert, Update and Validate)."
|
|
1471
|
+
"Send plaintext password changes to Users table triggers (Insert, Update and Validate).": "Send plaintext password changes to Users table triggers (Insert, Update and Validate).",
|
|
1472
|
+
"Minimum user role required to create a new tenant<div class=\"alert alert-danger fst-normal\" role=\"alert\" data-show-if=\"showIfFormulaInputs($('select[name=role_to_create_tenant]'), '+role_to_create_tenant>1')\">Giving non-trusted users access to create tenants is a security risk and not recommended.</div>": "Minimum user role required to create a new tenant<div class=\"alert alert-danger fst-normal\" role=\"alert\" data-show-if=\"showIfFormulaInputs($('select[name=role_to_create_tenant]'), '+role_to_create_tenant>1')\">Giving non-trusted users access to create tenants is a security risk and not recommended.</div>",
|
|
1473
|
+
"Select tag": "Select tag",
|
|
1474
|
+
"Invalid build directory path": "Invalid build directory path",
|
|
1475
|
+
"Invalid build directory name": "Invalid build directory name"
|
|
1472
1476
|
}
|
package/package.json
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/server",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.14",
|
|
4
4
|
"description": "Server app for Saltcorn, open-source no-code platform",
|
|
5
5
|
"homepage": "https://saltcorn.com",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@aws-sdk/client-s3": "^3.451.0",
|
|
10
|
-
"@saltcorn/base-plugin": "1.0.0-beta.
|
|
11
|
-
"@saltcorn/builder": "1.0.0-beta.
|
|
12
|
-
"@saltcorn/data": "1.0.0-beta.
|
|
13
|
-
"@saltcorn/admin-models": "1.0.0-beta.
|
|
14
|
-
"@saltcorn/filemanager": "1.0.0-beta.
|
|
15
|
-
"@saltcorn/markup": "1.0.0-beta.
|
|
16
|
-
"@saltcorn/plugins-loader": "1.0.0-beta.
|
|
17
|
-
"@saltcorn/sbadmin2": "1.0.0-beta.
|
|
10
|
+
"@saltcorn/base-plugin": "1.0.0-beta.14",
|
|
11
|
+
"@saltcorn/builder": "1.0.0-beta.14",
|
|
12
|
+
"@saltcorn/data": "1.0.0-beta.14",
|
|
13
|
+
"@saltcorn/admin-models": "1.0.0-beta.14",
|
|
14
|
+
"@saltcorn/filemanager": "1.0.0-beta.14",
|
|
15
|
+
"@saltcorn/markup": "1.0.0-beta.14",
|
|
16
|
+
"@saltcorn/plugins-loader": "1.0.0-beta.14",
|
|
17
|
+
"@saltcorn/sbadmin2": "1.0.0-beta.14",
|
|
18
18
|
"@socket.io/cluster-adapter": "^0.2.1",
|
|
19
19
|
"@socket.io/sticky": "^1.0.1",
|
|
20
20
|
"adm-zip": "0.5.10",
|
|
@@ -184,8 +184,21 @@ function apply_showif() {
|
|
|
184
184
|
var current = e.attr("data-selected") || e.val();
|
|
185
185
|
//console.log({ field: e.attr("name"), target: data[0], val, current });
|
|
186
186
|
e.empty();
|
|
187
|
+
//TODO clean repetition in following cose
|
|
187
188
|
(options || []).forEach((o) => {
|
|
188
|
-
if (
|
|
189
|
+
if (o && o.optgroup) {
|
|
190
|
+
const opts = o.options
|
|
191
|
+
.map(
|
|
192
|
+
(innero) =>
|
|
193
|
+
`<option ${
|
|
194
|
+
`${current}` === `${innero.value || innero}` ? "selected " : ""
|
|
195
|
+
}value="${innero.value || innero}">${
|
|
196
|
+
innero.label || innero
|
|
197
|
+
}</option>`
|
|
198
|
+
)
|
|
199
|
+
.join("");
|
|
200
|
+
e.append($(`<optgroup label="${o.label}">` + opts + "</optgroup>"));
|
|
201
|
+
} else if (
|
|
189
202
|
!(o && typeof o.label !== "undefined" && typeof o.value !== "undefined")
|
|
190
203
|
) {
|
|
191
204
|
if (`${current}` === `${o}`)
|
package/routes/actions.js
CHANGED
|
@@ -57,21 +57,6 @@ const {
|
|
|
57
57
|
blocklyToolbox,
|
|
58
58
|
} = require("../markup/blockly.js");
|
|
59
59
|
|
|
60
|
-
/**
|
|
61
|
-
* @returns {Promise<object>}
|
|
62
|
-
*/
|
|
63
|
-
const getActions = async () => {
|
|
64
|
-
return Object.entries(getState().actions).map(([k, v]) => {
|
|
65
|
-
const hasConfig = !!v.configFields;
|
|
66
|
-
const requireRow = !!v.requireRow;
|
|
67
|
-
return {
|
|
68
|
-
name: k,
|
|
69
|
-
hasConfig,
|
|
70
|
-
requireRow,
|
|
71
|
-
};
|
|
72
|
-
});
|
|
73
|
-
};
|
|
74
|
-
|
|
75
60
|
/**
|
|
76
61
|
* Show list of Actions (Triggers) (HTTP GET)
|
|
77
62
|
* @name get
|
|
@@ -96,7 +81,7 @@ router.get(
|
|
|
96
81
|
triggers = triggers.filter((t) => tagged_trigger_ids.has(t.id));
|
|
97
82
|
filterOnTag = await Tag.findOne({ id: +req.query._tag });
|
|
98
83
|
}
|
|
99
|
-
const actions =
|
|
84
|
+
const actions = Trigger.abbreviated_actions;
|
|
100
85
|
send_events_page({
|
|
101
86
|
res,
|
|
102
87
|
req,
|
|
@@ -156,7 +141,7 @@ const triggerForm = async (req, trigger) => {
|
|
|
156
141
|
value: r.id,
|
|
157
142
|
label: r.role,
|
|
158
143
|
}));
|
|
159
|
-
const actions =
|
|
144
|
+
const actions = Trigger.abbreviated_actions;
|
|
160
145
|
const tables = await Table.find({});
|
|
161
146
|
let id;
|
|
162
147
|
let form_action;
|
|
@@ -168,14 +153,11 @@ const triggerForm = async (req, trigger) => {
|
|
|
168
153
|
const hasChannel = Object.entries(getState().eventTypes)
|
|
169
154
|
.filter(([k, v]) => v.hasChannel)
|
|
170
155
|
.map(([k, v]) => k);
|
|
171
|
-
|
|
172
|
-
allActions.
|
|
156
|
+
|
|
157
|
+
const allActions = Trigger.action_options(false);
|
|
173
158
|
const table_triggers = ["Insert", "Update", "Delete", "Validate"];
|
|
174
159
|
const action_options = {};
|
|
175
|
-
const actionsNotRequiringRow =
|
|
176
|
-
.filter((a) => !a.requireRow)
|
|
177
|
-
.map((t) => t.name);
|
|
178
|
-
actionsNotRequiringRow.push("Multi-step action");
|
|
160
|
+
const actionsNotRequiringRow = Trigger.action_options(true);
|
|
179
161
|
|
|
180
162
|
Trigger.when_options.forEach((t) => {
|
|
181
163
|
if (table_triggers.includes(t)) action_options[t] = allActions;
|
package/routes/admin.js
CHANGED
|
@@ -673,7 +673,10 @@ router.get(
|
|
|
673
673
|
const backup_file_prefix = getState().getConfig("backup_file_prefix");
|
|
674
674
|
if (
|
|
675
675
|
!isRoot ||
|
|
676
|
-
!(
|
|
676
|
+
!(
|
|
677
|
+
path.resolve(filename).startsWith(backup_file_prefix) &&
|
|
678
|
+
filename.endsWith(".zip")
|
|
679
|
+
)
|
|
677
680
|
) {
|
|
678
681
|
res.redirect("/admin/backup");
|
|
679
682
|
return;
|
|
@@ -1312,6 +1315,23 @@ router.get(
|
|
|
1312
1315
|
)
|
|
1313
1316
|
)
|
|
1314
1317
|
),
|
|
1318
|
+
div(
|
|
1319
|
+
{ class: "form-group" },
|
|
1320
|
+
input({
|
|
1321
|
+
id: "deep_clean",
|
|
1322
|
+
class: "form-check-input",
|
|
1323
|
+
type: "checkbox",
|
|
1324
|
+
name: "deep_clean",
|
|
1325
|
+
checked: false,
|
|
1326
|
+
}),
|
|
1327
|
+
label(
|
|
1328
|
+
{
|
|
1329
|
+
for: "deep_clean",
|
|
1330
|
+
class: "form-label ms-2",
|
|
1331
|
+
},
|
|
1332
|
+
req.__("clean node_modules")
|
|
1333
|
+
)
|
|
1334
|
+
),
|
|
1315
1335
|
div(
|
|
1316
1336
|
{ class: "d-flex justify-content-end" },
|
|
1317
1337
|
button(
|
|
@@ -1343,7 +1363,17 @@ router.get(
|
|
|
1343
1363
|
})
|
|
1344
1364
|
);
|
|
1345
1365
|
|
|
1346
|
-
const
|
|
1366
|
+
const cleanNodeModules = async () => {
|
|
1367
|
+
const topSaltcornDir = path.join(__dirname, "..", "..", "..", "..", "..");
|
|
1368
|
+
if (path.basename(topSaltcornDir) === "@saltcorn")
|
|
1369
|
+
await fs.promises.rm(topSaltcornDir, { recursive: true, force: true });
|
|
1370
|
+
else
|
|
1371
|
+
throw new Error(
|
|
1372
|
+
`'${topSaltcornDir}' is not a Saltcorn installation directory`
|
|
1373
|
+
);
|
|
1374
|
+
};
|
|
1375
|
+
|
|
1376
|
+
const doInstall = async (req, res, version, deepClean, runPull) => {
|
|
1347
1377
|
if (db.getTenantSchema() !== db.connectObj.default_schema) {
|
|
1348
1378
|
req.flash("error", req.__("Not possible for tenant"));
|
|
1349
1379
|
res.redirect("/admin");
|
|
@@ -1353,6 +1383,14 @@ const doInstall = async (req, res, version, runPull) => {
|
|
|
1353
1383
|
? req.__("Starting upgrade, please wait...\n")
|
|
1354
1384
|
: req.__("Installing %s, please wait...\n", version)
|
|
1355
1385
|
);
|
|
1386
|
+
if (deepClean) {
|
|
1387
|
+
res.write(req.__("Cleaning node_modules...\n"));
|
|
1388
|
+
try {
|
|
1389
|
+
await cleanNodeModules();
|
|
1390
|
+
} catch (e) {
|
|
1391
|
+
res.write(req.__("Error cleaning node_modules: %s\n", e.message));
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1356
1394
|
const child = spawn(
|
|
1357
1395
|
"npm",
|
|
1358
1396
|
["install", "-g", `@saltcorn/cli@${version}`, "--unsafe"],
|
|
@@ -1390,8 +1428,8 @@ const doInstall = async (req, res, version, runPull) => {
|
|
|
1390
1428
|
};
|
|
1391
1429
|
|
|
1392
1430
|
router.post("/install", isAdmin, async (req, res) => {
|
|
1393
|
-
const { version } = req.body;
|
|
1394
|
-
await doInstall(req, res, version, false);
|
|
1431
|
+
const { version, deep_clean } = req.body;
|
|
1432
|
+
await doInstall(req, res, version, deep_clean === "on", false);
|
|
1395
1433
|
});
|
|
1396
1434
|
|
|
1397
1435
|
/**
|
|
@@ -1404,7 +1442,7 @@ router.post(
|
|
|
1404
1442
|
"/upgrade",
|
|
1405
1443
|
isAdmin,
|
|
1406
1444
|
error_catcher(async (req, res) => {
|
|
1407
|
-
await doInstall(req, res, "latest", true);
|
|
1445
|
+
await doInstall(req, res, "latest", false, true);
|
|
1408
1446
|
})
|
|
1409
1447
|
);
|
|
1410
1448
|
/**
|
|
@@ -2877,17 +2915,66 @@ router.get(
|
|
|
2877
2915
|
})
|
|
2878
2916
|
);
|
|
2879
2917
|
|
|
2918
|
+
const validateBuildDirName = (buildDirName) => {
|
|
2919
|
+
// ensure characters
|
|
2920
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(buildDirName)) {
|
|
2921
|
+
getState().log(
|
|
2922
|
+
4,
|
|
2923
|
+
`Invalid characters in build directory name '${buildDirName}'`
|
|
2924
|
+
);
|
|
2925
|
+
return false;
|
|
2926
|
+
}
|
|
2927
|
+
// ensure format is 'build_1234567890'
|
|
2928
|
+
if (!/^build_\d+$/.test(buildDirName)) {
|
|
2929
|
+
getState().log(4, `Invalid build directory name format '${buildDirName}'`);
|
|
2930
|
+
return false;
|
|
2931
|
+
}
|
|
2932
|
+
return true;
|
|
2933
|
+
};
|
|
2934
|
+
|
|
2935
|
+
const validateBuildDir = (buildDir, rootPath) => {
|
|
2936
|
+
const resolvedBuildDir = path.resolve(buildDir);
|
|
2937
|
+
if (!resolvedBuildDir.startsWith(path.join(rootPath, "mobile_app"))) {
|
|
2938
|
+
getState().log(4, `Invalid build directory path '${buildDir}'`);
|
|
2939
|
+
return false;
|
|
2940
|
+
}
|
|
2941
|
+
return true;
|
|
2942
|
+
};
|
|
2943
|
+
|
|
2880
2944
|
router.get(
|
|
2881
2945
|
"/build-mobile-app/result",
|
|
2882
2946
|
isAdmin,
|
|
2883
2947
|
error_catcher(async (req, res) => {
|
|
2884
2948
|
const { build_dir_name } = req.query;
|
|
2949
|
+
if (!validateBuildDirName(build_dir_name)) {
|
|
2950
|
+
return res.sendWrap(req.__(`Admin`), {
|
|
2951
|
+
above: [
|
|
2952
|
+
{
|
|
2953
|
+
type: "card",
|
|
2954
|
+
title: req.__("Build Result"),
|
|
2955
|
+
contents: div(req.__("Invalid build directory name")),
|
|
2956
|
+
},
|
|
2957
|
+
],
|
|
2958
|
+
});
|
|
2959
|
+
}
|
|
2885
2960
|
const rootFolder = await File.rootFolder();
|
|
2886
2961
|
const buildDir = path.join(
|
|
2887
2962
|
rootFolder.location,
|
|
2888
2963
|
"mobile_app",
|
|
2889
2964
|
build_dir_name
|
|
2890
2965
|
);
|
|
2966
|
+
if (!validateBuildDir(buildDir, rootFolder.location)) {
|
|
2967
|
+
return res.sendWrap(req.__(`Admin`), {
|
|
2968
|
+
above: [
|
|
2969
|
+
{
|
|
2970
|
+
type: "card",
|
|
2971
|
+
title: req.__("Build Result"),
|
|
2972
|
+
contents: div(req.__("Invalid build directory path")),
|
|
2973
|
+
},
|
|
2974
|
+
],
|
|
2975
|
+
});
|
|
2976
|
+
}
|
|
2977
|
+
|
|
2891
2978
|
const files = await Promise.all(
|
|
2892
2979
|
fs
|
|
2893
2980
|
.readdirSync(buildDir)
|
package/routes/infoarch.js
CHANGED
|
@@ -234,7 +234,14 @@ router.post(
|
|
|
234
234
|
isAdmin,
|
|
235
235
|
error_catcher(async (req, res) => {
|
|
236
236
|
const { lang, defstring } = req.params;
|
|
237
|
-
|
|
237
|
+
if (
|
|
238
|
+
lang === "__proto__" ||
|
|
239
|
+
defstring === "__proto__" ||
|
|
240
|
+
lang === "constructor"
|
|
241
|
+
) {
|
|
242
|
+
res.redirect(`/`);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
238
245
|
const cfgStrings = getState().getConfigCopy("localizer_strings");
|
|
239
246
|
if (cfgStrings[lang]) cfgStrings[lang][defstring] = text(req.body.value);
|
|
240
247
|
else cfgStrings[lang] = { [defstring]: text(req.body.value) };
|
package/routes/plugins.js
CHANGED
|
@@ -49,6 +49,8 @@ const {
|
|
|
49
49
|
input,
|
|
50
50
|
label,
|
|
51
51
|
text,
|
|
52
|
+
script,
|
|
53
|
+
domReady,
|
|
52
54
|
} = require("@saltcorn/markup/tags");
|
|
53
55
|
const { search_bar } = require("@saltcorn/markup/helpers");
|
|
54
56
|
const fs = require("fs");
|
|
@@ -614,13 +616,14 @@ router.get(
|
|
|
614
616
|
res.set("Page-Title", req.__("%s versions", text(withoutOrg)));
|
|
615
617
|
const versions = Object.keys(pkgInfo.versions);
|
|
616
618
|
if (versions.length === 0) throw new Error(req.__("No versions found"));
|
|
619
|
+
const tags = pkgInfo["dist-tags"] || {};
|
|
617
620
|
let selected = null;
|
|
618
621
|
if (getState().plugins[plugin.name]) {
|
|
619
622
|
const mod = await load_plugins.requirePlugin(plugin);
|
|
620
623
|
if (mod) selected = mod.version;
|
|
621
624
|
}
|
|
622
625
|
if (!selected) selected = versions[versions.length - 1];
|
|
623
|
-
const
|
|
626
|
+
const scVersion = getState().scVersion;
|
|
624
627
|
return res.send(
|
|
625
628
|
form(
|
|
626
629
|
{
|
|
@@ -630,6 +633,7 @@ router.get(
|
|
|
630
633
|
input({ type: "hidden", name: "_csrf", value: req.csrfToken() }),
|
|
631
634
|
div(
|
|
632
635
|
{ class: "form-group" },
|
|
636
|
+
// version
|
|
633
637
|
label(
|
|
634
638
|
{
|
|
635
639
|
for: "version_select",
|
|
@@ -645,7 +649,7 @@ router.get(
|
|
|
645
649
|
},
|
|
646
650
|
versions
|
|
647
651
|
.filter((v) =>
|
|
648
|
-
isVersionSupported(v, pkgInfo.versions,
|
|
652
|
+
isVersionSupported(v, pkgInfo.versions, scVersion)
|
|
649
653
|
)
|
|
650
654
|
.map((version) =>
|
|
651
655
|
option({
|
|
@@ -655,6 +659,37 @@ router.get(
|
|
|
655
659
|
selected: version === selected,
|
|
656
660
|
})
|
|
657
661
|
)
|
|
662
|
+
),
|
|
663
|
+
// tag
|
|
664
|
+
label(
|
|
665
|
+
{
|
|
666
|
+
for: "tag_select",
|
|
667
|
+
class: "form-label fw-bold mt-2",
|
|
668
|
+
},
|
|
669
|
+
req.__("Tags")
|
|
670
|
+
),
|
|
671
|
+
select(
|
|
672
|
+
{
|
|
673
|
+
id: "tag_select",
|
|
674
|
+
class: "form-control form-select",
|
|
675
|
+
},
|
|
676
|
+
option({
|
|
677
|
+
id: "empty_opt",
|
|
678
|
+
value: "",
|
|
679
|
+
label: req.__("Select tag"),
|
|
680
|
+
selected: true,
|
|
681
|
+
}),
|
|
682
|
+
Object.keys(tags)
|
|
683
|
+
.filter((tag) =>
|
|
684
|
+
isVersionSupported(tags[tag], pkgInfo.versions, scVersion)
|
|
685
|
+
)
|
|
686
|
+
.map((tag) =>
|
|
687
|
+
option({
|
|
688
|
+
id: `${tag}_opt`,
|
|
689
|
+
value: tags[tag],
|
|
690
|
+
label: `${tag} (${tags[tag]})`,
|
|
691
|
+
})
|
|
692
|
+
)
|
|
658
693
|
)
|
|
659
694
|
),
|
|
660
695
|
div(
|
|
@@ -676,7 +711,19 @@ router.get(
|
|
|
676
711
|
req.__("Install")
|
|
677
712
|
)
|
|
678
713
|
)
|
|
679
|
-
)
|
|
714
|
+
) +
|
|
715
|
+
script(
|
|
716
|
+
domReady(`
|
|
717
|
+
document.getElementById('tag_select').onchange = () => {
|
|
718
|
+
const version = document.getElementById('tag_select').value;
|
|
719
|
+
if (version) document.getElementById('version_select').value = version;
|
|
720
|
+
};
|
|
721
|
+
document.getElementById('version_select').onchange = () => {
|
|
722
|
+
const tagSelect = document.getElementById('tag_select');
|
|
723
|
+
tagSelect.value = '';
|
|
724
|
+
};
|
|
725
|
+
`)
|
|
726
|
+
)
|
|
680
727
|
);
|
|
681
728
|
} catch (error) {
|
|
682
729
|
getState().log(
|
|
@@ -1184,9 +1231,20 @@ router.get(
|
|
|
1184
1231
|
const update_permitted =
|
|
1185
1232
|
db.getTenantSchema() === db.connectObj.default_schema &&
|
|
1186
1233
|
plugin_db.source === "npm";
|
|
1187
|
-
|
|
1234
|
+
|
|
1235
|
+
let latest =
|
|
1188
1236
|
update_permitted &&
|
|
1189
1237
|
(await get_latest_npm_version(plugin_db.location, 1000));
|
|
1238
|
+
if (
|
|
1239
|
+
latest &&
|
|
1240
|
+
!isVersionSupported(latest, await load_plugins.getEngineInfos(plugin_db)) // with cache
|
|
1241
|
+
) {
|
|
1242
|
+
// with force fetch
|
|
1243
|
+
latest = supportedVersion(
|
|
1244
|
+
latest,
|
|
1245
|
+
await load_plugins.getEngineInfos(plugin_db, true)
|
|
1246
|
+
);
|
|
1247
|
+
}
|
|
1190
1248
|
const can_update = update_permitted && latest && mod.version !== latest;
|
|
1191
1249
|
const can_select_version = update_permitted && plugin_db.source === "npm";
|
|
1192
1250
|
let pkgjson;
|
|
@@ -1332,8 +1390,8 @@ router.get(
|
|
|
1332
1390
|
error_catcher(async (req, res) => {
|
|
1333
1391
|
const schema = db.getTenantSchema();
|
|
1334
1392
|
if (schema === db.connectObj.default_schema) {
|
|
1335
|
-
await upgrade_all_tenants_plugins((p, f) =>
|
|
1336
|
-
load_plugins.loadPlugin(p, f)
|
|
1393
|
+
await upgrade_all_tenants_plugins((p, f, forceFetch) =>
|
|
1394
|
+
load_plugins.loadPlugin(p, f, forceFetch)
|
|
1337
1395
|
);
|
|
1338
1396
|
req.flash(
|
|
1339
1397
|
"success",
|
|
@@ -1344,7 +1402,9 @@ router.get(
|
|
|
1344
1402
|
} else {
|
|
1345
1403
|
const installed_plugins = await Plugin.find({});
|
|
1346
1404
|
for (const plugin of installed_plugins) {
|
|
1347
|
-
await plugin.upgrade_version((p, f) =>
|
|
1405
|
+
await plugin.upgrade_version((p, f, forceFetch) =>
|
|
1406
|
+
load_plugins.loadPlugin(p, f, forceFetch)
|
|
1407
|
+
);
|
|
1348
1408
|
}
|
|
1349
1409
|
req.flash("success", req.__(`Modules up-to-date`));
|
|
1350
1410
|
await restart_tenant(loadAllPlugins);
|
|
@@ -1370,16 +1430,11 @@ router.get(
|
|
|
1370
1430
|
const { name } = req.params;
|
|
1371
1431
|
|
|
1372
1432
|
const plugin = await Plugin.findOne({ name });
|
|
1373
|
-
const
|
|
1374
|
-
|
|
1375
|
-
);
|
|
1433
|
+
const versions = await load_plugins.getEngineInfos(plugin, true);
|
|
1434
|
+
|
|
1376
1435
|
await plugin.upgrade_version(
|
|
1377
1436
|
(p, f) => load_plugins.loadPlugin(p, f),
|
|
1378
|
-
supportedVersion(
|
|
1379
|
-
"latest",
|
|
1380
|
-
pkgInfo.versions,
|
|
1381
|
-
require("../package.json").version
|
|
1382
|
-
)
|
|
1437
|
+
supportedVersion("latest", versions, require("../package.json").version)
|
|
1383
1438
|
);
|
|
1384
1439
|
req.flash("success", req.__(`Module up-to-date`));
|
|
1385
1440
|
|
package/serve.js
CHANGED
|
@@ -27,7 +27,7 @@ const {
|
|
|
27
27
|
loadAndSaveNewPlugin,
|
|
28
28
|
loadPlugin,
|
|
29
29
|
} = require("./load_plugins");
|
|
30
|
-
const { getConfig } = require("@saltcorn/data/models/config");
|
|
30
|
+
const { getConfig, setConfig } = require("@saltcorn/data/models/config");
|
|
31
31
|
const { migrate } = require("@saltcorn/data/migrate");
|
|
32
32
|
const socketio = require("socket.io");
|
|
33
33
|
const { createAdapter, setupPrimary } = require("@socket.io/cluster-adapter");
|
|
@@ -77,6 +77,17 @@ const ensureJwtSecret = () => {
|
|
|
77
77
|
}
|
|
78
78
|
};
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Ensure the engines cache is up to date with the current sc version
|
|
82
|
+
*/
|
|
83
|
+
const ensureEnginesCache = async () => {
|
|
84
|
+
const cacheScVersion = await getConfig("engines_cache_sc_version", "");
|
|
85
|
+
if (!cacheScVersion || cacheScVersion !== getState().scVersion) {
|
|
86
|
+
await setConfig("engines_cache", {});
|
|
87
|
+
await setConfig("engines_cache_sc_version", getState().scVersion);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
80
91
|
// helpful https://gist.github.com/jpoehls/2232358
|
|
81
92
|
/**
|
|
82
93
|
* @param {object} opts
|
|
@@ -222,7 +233,10 @@ module.exports =
|
|
|
222
233
|
dev,
|
|
223
234
|
...appargs
|
|
224
235
|
} = {}) => {
|
|
225
|
-
|
|
236
|
+
if (cluster.isMaster) {
|
|
237
|
+
ensureJwtSecret();
|
|
238
|
+
await ensureEnginesCache();
|
|
239
|
+
}
|
|
226
240
|
process.on("unhandledRejection", (reason, p) => {
|
|
227
241
|
console.error(reason, "Unhandled Rejection at Promise");
|
|
228
242
|
});
|