@saltcorn/server 0.8.5-beta.2 → 0.8.5-beta.4
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/auth/testhelp.js +15 -0
- package/locales/en.json +20 -1
- package/locales/pl.json +1110 -0
- package/package.json +9 -8
- package/public/saltcorn-common.js +44 -8
- package/public/saltcorn.css +23 -0
- package/public/vis-network.min.js +49 -0
- package/routes/admin.js +30 -8
- package/routes/api.js +73 -18
- package/routes/fields.js +11 -1
- package/routes/list.js +8 -5
- package/routes/plugins.js +47 -16
- package/routes/tables.js +104 -35
- package/routes/tenant.js +4 -0
- package/routes/utils.js +19 -0
- package/tests/api.test.js +2 -2
- package/tests/table.test.js +19 -2
- package/wrapper.js +14 -2
package/routes/admin.js
CHANGED
|
@@ -10,6 +10,7 @@ const {
|
|
|
10
10
|
error_catcher,
|
|
11
11
|
getGitRevision,
|
|
12
12
|
setTenant,
|
|
13
|
+
get_sys_info,
|
|
13
14
|
} = require("./utils.js");
|
|
14
15
|
const Table = require("@saltcorn/data/models/table");
|
|
15
16
|
const Plugin = require("@saltcorn/data/models/plugin");
|
|
@@ -864,7 +865,7 @@ router.get(
|
|
|
864
865
|
const can_update =
|
|
865
866
|
!is_latest && !process.env.SALTCORN_DISABLE_UPGRADE && !git_commit;
|
|
866
867
|
const dbversion = await db.getVersion(true);
|
|
867
|
-
|
|
868
|
+
const { memUsage, diskUsage, cpuUsage } = await get_sys_info();
|
|
868
869
|
send_admin_page({
|
|
869
870
|
res,
|
|
870
871
|
req,
|
|
@@ -986,7 +987,20 @@ router.get(
|
|
|
986
987
|
tr(
|
|
987
988
|
th(req.__("Process uptime")),
|
|
988
989
|
td(moment(get_process_init_time()).fromNow(true))
|
|
989
|
-
)
|
|
990
|
+
),
|
|
991
|
+
tr(
|
|
992
|
+
th(req.__("Disk usage")),
|
|
993
|
+
diskUsage > 95
|
|
994
|
+
? td(
|
|
995
|
+
{ class: "text-danger fw-bold" },
|
|
996
|
+
diskUsage,
|
|
997
|
+
"%",
|
|
998
|
+
i({ class: "fas fa-exclamation-triangle ms-1" })
|
|
999
|
+
)
|
|
1000
|
+
: td(diskUsage, "%")
|
|
1001
|
+
),
|
|
1002
|
+
tr(th(req.__("CPU usage")), td(cpuUsage, "%")),
|
|
1003
|
+
tr(th(req.__("Mem usage")), td(memUsage, "%"))
|
|
990
1004
|
)
|
|
991
1005
|
),
|
|
992
1006
|
p(
|
|
@@ -1307,12 +1321,16 @@ router.get(
|
|
|
1307
1321
|
"/configuration-check",
|
|
1308
1322
|
isAdmin,
|
|
1309
1323
|
error_catcher(async (req, res) => {
|
|
1310
|
-
const
|
|
1324
|
+
const start = new Date();
|
|
1325
|
+
const filename = `${moment(start).format("YYYYMMDDHHmm")}.html`;
|
|
1311
1326
|
await File.new_folder("configuration_checks");
|
|
1312
1327
|
const go = async () => {
|
|
1313
1328
|
const { passes, errors, pass, warnings } = await runConfigurationCheck(
|
|
1314
1329
|
req
|
|
1315
1330
|
);
|
|
1331
|
+
const end = new Date();
|
|
1332
|
+
const secs = Math.round((end.getTime() - start.getTime()) / 1000);
|
|
1333
|
+
|
|
1316
1334
|
const mkError = (err) =>
|
|
1317
1335
|
div(
|
|
1318
1336
|
{ class: "alert alert-danger", role: "alert" },
|
|
@@ -1341,6 +1359,11 @@ router.get(
|
|
|
1341
1359
|
h3("Passes"),
|
|
1342
1360
|
|
|
1343
1361
|
pre(code(passes.join("\n")))
|
|
1362
|
+
) +
|
|
1363
|
+
p(
|
|
1364
|
+
`Configuration check completed in ${
|
|
1365
|
+
secs > 60 ? `${Math.floor(secs / 60)}m ${secs % 60}s` : secs + "s"
|
|
1366
|
+
}`
|
|
1344
1367
|
);
|
|
1345
1368
|
await File.from_contents(
|
|
1346
1369
|
filename,
|
|
@@ -1950,8 +1973,9 @@ router.post(
|
|
|
1950
1973
|
* @returns {Promise<Form>} form
|
|
1951
1974
|
*/
|
|
1952
1975
|
const dev_form = async (req) => {
|
|
1953
|
-
const
|
|
1954
|
-
"
|
|
1976
|
+
const tenants_set_npm_modules = getRootState().getConfig(
|
|
1977
|
+
"tenants_set_npm_modules",
|
|
1978
|
+
false
|
|
1955
1979
|
);
|
|
1956
1980
|
const isRoot = db.getTenantSchema() === db.connectObj.default_schema;
|
|
1957
1981
|
|
|
@@ -1962,9 +1986,7 @@ const dev_form = async (req) => {
|
|
|
1962
1986
|
"log_sql",
|
|
1963
1987
|
"log_client_errors",
|
|
1964
1988
|
"log_level",
|
|
1965
|
-
...(isRoot ||
|
|
1966
|
-
? ["npm_available_js_code"]
|
|
1967
|
-
: []),
|
|
1989
|
+
...(isRoot || tenants_set_npm_modules ? ["npm_available_js_code"] : []),
|
|
1968
1990
|
],
|
|
1969
1991
|
action: "/admin/dev",
|
|
1970
1992
|
});
|
package/routes/api.js
CHANGED
|
@@ -31,6 +31,7 @@ const {
|
|
|
31
31
|
readState,
|
|
32
32
|
strictParseInt,
|
|
33
33
|
} = require("@saltcorn/data/plugin-helper");
|
|
34
|
+
const Crash = require("@saltcorn/data/models/crash");
|
|
34
35
|
|
|
35
36
|
/**
|
|
36
37
|
* @type {object}
|
|
@@ -66,7 +67,7 @@ const limitFields = (fields) => (r) => {
|
|
|
66
67
|
* @param {Table} table
|
|
67
68
|
* @returns {boolean}
|
|
68
69
|
*/
|
|
69
|
-
function accessAllowedRead(req, user, table) {
|
|
70
|
+
function accessAllowedRead(req, user, table, allow_ownership) {
|
|
70
71
|
const role =
|
|
71
72
|
req.user && req.user.id
|
|
72
73
|
? req.user.role_id
|
|
@@ -74,7 +75,12 @@ function accessAllowedRead(req, user, table) {
|
|
|
74
75
|
? user.role_id
|
|
75
76
|
: 10;
|
|
76
77
|
|
|
77
|
-
return
|
|
78
|
+
return (
|
|
79
|
+
role <= table.min_role_read ||
|
|
80
|
+
((req.user?.id || user?.id) &&
|
|
81
|
+
allow_ownership &&
|
|
82
|
+
(table.ownership_field_id || table.ownership_formula))
|
|
83
|
+
);
|
|
78
84
|
}
|
|
79
85
|
|
|
80
86
|
/**
|
|
@@ -92,7 +98,11 @@ function accessAllowedWrite(req, user, table) {
|
|
|
92
98
|
? user.role_id
|
|
93
99
|
: 10;
|
|
94
100
|
|
|
95
|
-
return
|
|
101
|
+
return (
|
|
102
|
+
role <= table.min_role_write ||
|
|
103
|
+
((req.user?.id || user?.id) &&
|
|
104
|
+
(table.ownership_field_id || table.ownership_formula))
|
|
105
|
+
);
|
|
96
106
|
}
|
|
97
107
|
/**
|
|
98
108
|
* Check that user has right to trigger call
|
|
@@ -126,6 +136,7 @@ router.post(
|
|
|
126
136
|
const view = await View.findOne({ name: viewName });
|
|
127
137
|
const db = require("@saltcorn/data/db");
|
|
128
138
|
if (!view) {
|
|
139
|
+
getState().log(3, `API viewQuery ${view.name} not found`);
|
|
129
140
|
res.status(404).json({
|
|
130
141
|
error: req.__("View %s not found", viewName),
|
|
131
142
|
view: viewName,
|
|
@@ -152,6 +163,10 @@ router.post(
|
|
|
152
163
|
const resp = await queries[queryName](...args, true);
|
|
153
164
|
res.json({ success: resp, alerts: getFlashes(req) });
|
|
154
165
|
} else {
|
|
166
|
+
getState().log(
|
|
167
|
+
3,
|
|
168
|
+
`API viewQuery ${view.name} ${queryName} not found`
|
|
169
|
+
);
|
|
155
170
|
res.status(404).json({
|
|
156
171
|
error: req.__("Query %s not found", queryName),
|
|
157
172
|
view: viewName,
|
|
@@ -163,6 +178,7 @@ router.post(
|
|
|
163
178
|
});
|
|
164
179
|
}
|
|
165
180
|
} else {
|
|
181
|
+
getState().log(3, `API viewQuery ${view.name} not authorized`);
|
|
166
182
|
res.status(401).json({ error: req.__("Not authorized") });
|
|
167
183
|
}
|
|
168
184
|
}
|
|
@@ -208,6 +224,10 @@ router.get(
|
|
|
208
224
|
}
|
|
209
225
|
res.json({ success: dvs });
|
|
210
226
|
} else {
|
|
227
|
+
getState().log(
|
|
228
|
+
3,
|
|
229
|
+
`API distinct ${table.name}.${fieldName} not authorized`
|
|
230
|
+
);
|
|
211
231
|
res.status(401).json({ error: req.__("Not authorized") });
|
|
212
232
|
}
|
|
213
233
|
}
|
|
@@ -234,6 +254,7 @@ router.get(
|
|
|
234
254
|
: { name: tableName }
|
|
235
255
|
);
|
|
236
256
|
if (!table) {
|
|
257
|
+
getState().log(3, `API get ${tableName} table not found`);
|
|
237
258
|
res.status(404).json({ error: req.__("Not found") });
|
|
238
259
|
return;
|
|
239
260
|
}
|
|
@@ -242,11 +263,13 @@ router.get(
|
|
|
242
263
|
["api-bearer", "jwt"],
|
|
243
264
|
{ session: false },
|
|
244
265
|
async function (err, user, info) {
|
|
245
|
-
if (accessAllowedRead(req, user, table)) {
|
|
266
|
+
if (accessAllowedRead(req, user, table, true)) {
|
|
246
267
|
let rows;
|
|
247
268
|
if (versioncount === "on") {
|
|
248
269
|
const joinOpts = {
|
|
249
270
|
orderBy: "id",
|
|
271
|
+
forUser: req.user || user || { role_id: 10 },
|
|
272
|
+
forPublic: !(req.user || user),
|
|
250
273
|
aggregations: {
|
|
251
274
|
_versions: {
|
|
252
275
|
table: table.name + "__history",
|
|
@@ -266,12 +289,22 @@ router.get(
|
|
|
266
289
|
state: req_query,
|
|
267
290
|
table,
|
|
268
291
|
});
|
|
269
|
-
rows = await table.getRows(qstate
|
|
292
|
+
rows = await table.getRows(qstate, {
|
|
293
|
+
forPublic: !(req.user || user),
|
|
294
|
+
forUser: req.user || user,
|
|
295
|
+
});
|
|
270
296
|
} else {
|
|
271
|
-
rows = await table.getRows(
|
|
297
|
+
rows = await table.getRows(
|
|
298
|
+
{},
|
|
299
|
+
{
|
|
300
|
+
forPublic: !(req.user || user),
|
|
301
|
+
forUser: req.user || user,
|
|
302
|
+
}
|
|
303
|
+
);
|
|
272
304
|
}
|
|
273
305
|
res.json({ success: rows.map(limitFields(fields)) });
|
|
274
306
|
} else {
|
|
307
|
+
getState().log(3, `API get ${table.name} not authorized`);
|
|
275
308
|
res.status(401).json({ error: req.__("Not authorized") });
|
|
276
309
|
}
|
|
277
310
|
}
|
|
@@ -301,6 +334,7 @@ router.post(
|
|
|
301
334
|
});
|
|
302
335
|
|
|
303
336
|
if (!trigger) {
|
|
337
|
+
getState().log(3, `API action ${actionname} not found`);
|
|
304
338
|
res.status(400).json({ error: req.__("Not found") });
|
|
305
339
|
return;
|
|
306
340
|
}
|
|
@@ -318,9 +352,11 @@ router.post(
|
|
|
318
352
|
});
|
|
319
353
|
res.json({ success: true, data: resp });
|
|
320
354
|
} catch (e) {
|
|
355
|
+
Crash.create(e, req);
|
|
321
356
|
res.status(400).json({ success: false, error: e.message });
|
|
322
357
|
}
|
|
323
358
|
} else {
|
|
359
|
+
getState().log(3, `API action ${actionname} not authorized`);
|
|
324
360
|
res.status(401).json({ error: req.__("Not authorized") });
|
|
325
361
|
}
|
|
326
362
|
}
|
|
@@ -340,6 +376,7 @@ router.post(
|
|
|
340
376
|
const { tableName } = req.params;
|
|
341
377
|
const table = await Table.findOne({ name: tableName });
|
|
342
378
|
if (!table) {
|
|
379
|
+
getState().log(3, `API POST ${tableName} not found`);
|
|
343
380
|
res.status(404).json({ error: req.__("Not found") });
|
|
344
381
|
return;
|
|
345
382
|
}
|
|
@@ -378,6 +415,10 @@ router.post(
|
|
|
378
415
|
}
|
|
379
416
|
});
|
|
380
417
|
if (hasErrors) {
|
|
418
|
+
getState().log(
|
|
419
|
+
2,
|
|
420
|
+
`API POST ${table.name} error: ${errors.join(", ")}`
|
|
421
|
+
);
|
|
381
422
|
res.status(400).json({ error: errors.join(", ") });
|
|
382
423
|
return;
|
|
383
424
|
}
|
|
@@ -385,9 +426,12 @@ router.post(
|
|
|
385
426
|
row,
|
|
386
427
|
req.user || user || { role_id: 10 }
|
|
387
428
|
);
|
|
388
|
-
if (ins_res.error)
|
|
389
|
-
|
|
429
|
+
if (ins_res.error) {
|
|
430
|
+
getState().log(2, `API POST ${table.name} error: ${ins_res.error}`);
|
|
431
|
+
res.status(400).json(ins_res);
|
|
432
|
+
} else res.json(ins_res);
|
|
390
433
|
} else {
|
|
434
|
+
getState().log(3, `API POST ${table.name} not authorized`);
|
|
391
435
|
res.status(401).json({ error: req.__("Not authorized") });
|
|
392
436
|
}
|
|
393
437
|
}
|
|
@@ -408,6 +452,7 @@ router.post(
|
|
|
408
452
|
const { tableName, id } = req.params;
|
|
409
453
|
const table = await Table.findOne({ name: tableName });
|
|
410
454
|
if (!table) {
|
|
455
|
+
getState().log(3, `API POST ${tableName} not found`);
|
|
411
456
|
res.status(404).json({ error: req.__("Not found") });
|
|
412
457
|
return;
|
|
413
458
|
}
|
|
@@ -421,30 +466,34 @@ router.post(
|
|
|
421
466
|
readState(row, fields);
|
|
422
467
|
let errors = [];
|
|
423
468
|
let hasErrors = false;
|
|
424
|
-
Object.keys(row)
|
|
469
|
+
for (const k of Object.keys(row)) {
|
|
425
470
|
const field = fields.find((f) => f.name === k);
|
|
426
471
|
if (!field && k.includes(".")) {
|
|
427
472
|
const [fnm, jkey] = k.split(".");
|
|
428
473
|
const jfield = fields.find((f) => f.name === fnm);
|
|
429
474
|
if (jfield?.type?.name === "JSON") {
|
|
430
|
-
if (
|
|
431
|
-
|
|
475
|
+
if (typeof row[fnm] === "undefined") {
|
|
476
|
+
const dbrow = await table.getRow({ [table.pk_name]: id });
|
|
477
|
+
row[fnm] = dbrow[fnm] || {};
|
|
478
|
+
}
|
|
479
|
+
row[fnm][jkey] = row[k];
|
|
432
480
|
delete row[k];
|
|
433
481
|
}
|
|
434
482
|
} else if (!field || field.calculated) {
|
|
435
483
|
delete row[k];
|
|
436
|
-
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
if (field?.type && field.type.validate) {
|
|
484
|
+
} else if (field?.type && field.type.validate) {
|
|
440
485
|
const vres = field.type.validate(field.attributes || {})(row[k]);
|
|
441
486
|
if (vres.error) {
|
|
442
487
|
hasErrors = true;
|
|
443
488
|
errors.push(`${k}: ${vres.error}`);
|
|
444
489
|
}
|
|
445
490
|
}
|
|
446
|
-
}
|
|
491
|
+
}
|
|
447
492
|
if (hasErrors) {
|
|
493
|
+
getState().log(
|
|
494
|
+
2,
|
|
495
|
+
`API POST ${table.name} error: ${errors.join(", ")}`
|
|
496
|
+
);
|
|
448
497
|
res.status(400).json({ error: errors.join(", ") });
|
|
449
498
|
return;
|
|
450
499
|
}
|
|
@@ -454,9 +503,12 @@ router.post(
|
|
|
454
503
|
user || req.user || { role_id: 10 }
|
|
455
504
|
);
|
|
456
505
|
|
|
457
|
-
if (ins_res.error)
|
|
458
|
-
|
|
506
|
+
if (ins_res.error) {
|
|
507
|
+
getState().log(2, `API POST ${table.name} error: ${ins_res.error}`);
|
|
508
|
+
res.status(400).json(ins_res);
|
|
509
|
+
} else res.json(ins_res);
|
|
459
510
|
} else {
|
|
511
|
+
getState().log(3, `API POST ${table.name} not authorized`);
|
|
460
512
|
res.status(401).json({ error: req.__("Not authorized") });
|
|
461
513
|
}
|
|
462
514
|
}
|
|
@@ -477,6 +529,7 @@ router.delete(
|
|
|
477
529
|
const { tableName, id } = req.params;
|
|
478
530
|
const table = await Table.findOne({ name: tableName });
|
|
479
531
|
if (!table) {
|
|
532
|
+
getState().log(3, `API DELETE ${tableName} not found`);
|
|
480
533
|
res.status(404).json({ error: req.__("Not found") });
|
|
481
534
|
return;
|
|
482
535
|
}
|
|
@@ -502,9 +555,11 @@ router.delete(
|
|
|
502
555
|
);
|
|
503
556
|
res.json({ success: true });
|
|
504
557
|
} catch (e) {
|
|
558
|
+
getState().log(2, `API DELETE ${table.name} error: ${e.message}`);
|
|
505
559
|
res.status(400).json({ error: e.message });
|
|
506
560
|
}
|
|
507
561
|
} else {
|
|
562
|
+
getState().log(3, `API DELETE ${table.name} not authorized`);
|
|
508
563
|
res.status(401).json({ error: req.__("Not authorized") });
|
|
509
564
|
}
|
|
510
565
|
}
|
package/routes/fields.js
CHANGED
|
@@ -398,9 +398,16 @@ const fieldFlow = (req) =>
|
|
|
398
398
|
];
|
|
399
399
|
const keyfields = orderedFields
|
|
400
400
|
.filter((f) => !f.calculated || f.stored)
|
|
401
|
+
.sort((a, b) =>
|
|
402
|
+
a.type?.name === "String" && b.type?.name !== "String"
|
|
403
|
+
? -1
|
|
404
|
+
: a.type?.name !== "String" && b.type?.name === "String"
|
|
405
|
+
? 1
|
|
406
|
+
: 0
|
|
407
|
+
)
|
|
401
408
|
.map((f) => ({
|
|
402
409
|
value: f.name,
|
|
403
|
-
label: f.label
|
|
410
|
+
label: `${f.label} [${f.type?.name || f.type}]`,
|
|
404
411
|
}));
|
|
405
412
|
const textfields = orderedFields
|
|
406
413
|
.filter(
|
|
@@ -412,6 +419,9 @@ const fieldFlow = (req) =>
|
|
|
412
419
|
new Field({
|
|
413
420
|
name: "summary_field",
|
|
414
421
|
label: req.__("Summary field"),
|
|
422
|
+
sublabel: req.__(
|
|
423
|
+
"The field that will be shown to the user when choosing a value"
|
|
424
|
+
),
|
|
415
425
|
input_type: "select",
|
|
416
426
|
options: keyfields,
|
|
417
427
|
}),
|
package/routes/list.js
CHANGED
|
@@ -241,13 +241,16 @@ router.get(
|
|
|
241
241
|
for (const f of fields) {
|
|
242
242
|
if (f.type === "File") f.attributes = { select_file_where: {} };
|
|
243
243
|
await f.fill_fkey_options();
|
|
244
|
+
|
|
245
|
+
if (f.type === "File") {
|
|
246
|
+
//add existing values in folders
|
|
247
|
+
const dvs = await f.distinct_values();
|
|
248
|
+
dvs.forEach((dv) => {
|
|
249
|
+
if (dv?.value?.includes("/")) f.options.push(dv);
|
|
250
|
+
});
|
|
251
|
+
}
|
|
244
252
|
}
|
|
245
253
|
|
|
246
|
-
//console.log(fields);
|
|
247
|
-
// todo remove keyfields - unused
|
|
248
|
-
const keyfields = fields
|
|
249
|
-
.filter((f) => f.type === "Key" || f.type === "File")
|
|
250
|
-
.map((f) => ({ name: f.name, type: f.reftype }));
|
|
251
254
|
const jsfields = arrangeIdFirst(fields).map((f) =>
|
|
252
255
|
typeToGridType(f.type, f)
|
|
253
256
|
);
|
package/routes/plugins.js
CHANGED
|
@@ -14,11 +14,18 @@ const {
|
|
|
14
14
|
post_btn,
|
|
15
15
|
post_delete_btn,
|
|
16
16
|
} = require("@saltcorn/markup");
|
|
17
|
-
const {
|
|
17
|
+
const {
|
|
18
|
+
getState,
|
|
19
|
+
restart_tenant,
|
|
20
|
+
getRootState,
|
|
21
|
+
} = require("@saltcorn/data/db/state");
|
|
18
22
|
const Form = require("@saltcorn/data/models/form");
|
|
19
23
|
const Field = require("@saltcorn/data/models/field");
|
|
20
24
|
const Plugin = require("@saltcorn/data/models/plugin");
|
|
21
25
|
const { fetch_available_packs } = require("@saltcorn/admin-models/models/pack");
|
|
26
|
+
const {
|
|
27
|
+
upgrade_all_tenants_plugins,
|
|
28
|
+
} = require("@saltcorn/admin-models/models/tenant");
|
|
22
29
|
const { getConfig, setConfig } = require("@saltcorn/data/models/config");
|
|
23
30
|
const db = require("@saltcorn/data/db");
|
|
24
31
|
const {
|
|
@@ -150,7 +157,10 @@ const local_has_theme = (name) => {
|
|
|
150
157
|
const get_store_items = async () => {
|
|
151
158
|
const installed_plugins = await Plugin.find({});
|
|
152
159
|
const isRoot = db.getTenantSchema() === db.connectObj.default_schema;
|
|
153
|
-
|
|
160
|
+
const tenants_unsafe_plugins = getRootState().getConfig(
|
|
161
|
+
"tenants_unsafe_plugins",
|
|
162
|
+
false
|
|
163
|
+
);
|
|
154
164
|
const instore = await Plugin.store_plugins_available();
|
|
155
165
|
const packs_available = await fetch_available_packs();
|
|
156
166
|
const packs_installed = getState().getConfig("installed_packs", []);
|
|
@@ -168,7 +178,7 @@ const get_store_items = async () => {
|
|
|
168
178
|
has_auth: plugin.has_auth,
|
|
169
179
|
unsafe: plugin.unsafe,
|
|
170
180
|
}))
|
|
171
|
-
.filter((p) => !p.unsafe || isRoot);
|
|
181
|
+
.filter((p) => !p.unsafe || isRoot || tenants_unsafe_plugins);
|
|
172
182
|
const local_logins = installed_plugins
|
|
173
183
|
.filter((p) => !store_plugin_names.includes(p.name) && p.name !== "base")
|
|
174
184
|
.map((plugin) => ({
|
|
@@ -424,8 +434,12 @@ const filter_items_set = (items, query) => {
|
|
|
424
434
|
* @param {object} req
|
|
425
435
|
* @returns {div}
|
|
426
436
|
*/
|
|
427
|
-
const store_actions_dropdown = (req) =>
|
|
428
|
-
|
|
437
|
+
const store_actions_dropdown = (req) => {
|
|
438
|
+
const tenants_install_git = getRootState().getConfig(
|
|
439
|
+
"tenants_install_git",
|
|
440
|
+
false
|
|
441
|
+
);
|
|
442
|
+
return div(
|
|
429
443
|
{ class: "dropdown" },
|
|
430
444
|
button(
|
|
431
445
|
{
|
|
@@ -460,7 +474,8 @@ const store_actions_dropdown = (req) =>
|
|
|
460
474
|
'<i class="far fa-arrow-alt-circle-up"></i> ' +
|
|
461
475
|
req.__("Upgrade installed modules")
|
|
462
476
|
),
|
|
463
|
-
db.getTenantSchema() === db.connectObj.default_schema
|
|
477
|
+
(db.getTenantSchema() === db.connectObj.default_schema ||
|
|
478
|
+
tenants_install_git) &&
|
|
464
479
|
a(
|
|
465
480
|
{
|
|
466
481
|
class: "dropdown-item",
|
|
@@ -488,6 +503,7 @@ const store_actions_dropdown = (req) =>
|
|
|
488
503
|
//create pack
|
|
489
504
|
)
|
|
490
505
|
);
|
|
506
|
+
};
|
|
491
507
|
|
|
492
508
|
/**
|
|
493
509
|
* @param {object[]} items
|
|
@@ -926,14 +942,22 @@ router.get(
|
|
|
926
942
|
"/upgrade",
|
|
927
943
|
isAdmin,
|
|
928
944
|
error_catcher(async (req, res) => {
|
|
929
|
-
const
|
|
930
|
-
|
|
931
|
-
await
|
|
945
|
+
const schema = db.getTenantSchema();
|
|
946
|
+
if (schema === db.connectObj.default_schema) {
|
|
947
|
+
await upgrade_all_tenants_plugins((p, f) =>
|
|
948
|
+
load_plugins.loadPlugin(p, f)
|
|
949
|
+
);
|
|
950
|
+
req.flash("success", req.__(`Modules up-to-date. Please restart server`));
|
|
951
|
+
} else {
|
|
952
|
+
const installed_plugins = await Plugin.find({});
|
|
953
|
+
for (const plugin of installed_plugins) {
|
|
954
|
+
await plugin.upgrade_version((p, f) => load_plugins.loadPlugin(p, f));
|
|
955
|
+
}
|
|
956
|
+
req.flash("success", req.__(`Modules up-to-date`));
|
|
957
|
+
await restart_tenant(loadAllPlugins);
|
|
958
|
+
process.send &&
|
|
959
|
+
process.send({ restart_tenant: true, tenant: db.getTenantSchema() });
|
|
932
960
|
}
|
|
933
|
-
req.flash("success", req.__(`Modules up-to-date`));
|
|
934
|
-
await restart_tenant(loadAllPlugins);
|
|
935
|
-
process.send &&
|
|
936
|
-
process.send({ restart_tenant: true, tenant: db.getTenantSchema() });
|
|
937
961
|
res.redirect(`/plugins`);
|
|
938
962
|
})
|
|
939
963
|
);
|
|
@@ -970,7 +994,11 @@ router.post(
|
|
|
970
994
|
error_catcher(async (req, res) => {
|
|
971
995
|
const plugin = new Plugin(req.body);
|
|
972
996
|
const schema = db.getTenantSchema();
|
|
973
|
-
|
|
997
|
+
const tenants_install_git = getRootState().getConfig(
|
|
998
|
+
"tenants_install_git",
|
|
999
|
+
false
|
|
1000
|
+
);
|
|
1001
|
+
if (schema !== db.connectObj.default_schema && !tenants_install_git) {
|
|
974
1002
|
req.flash(
|
|
975
1003
|
"error",
|
|
976
1004
|
req.__(`Only store modules can be installed on tenant instances`)
|
|
@@ -1039,7 +1067,10 @@ router.post(
|
|
|
1039
1067
|
isAdmin,
|
|
1040
1068
|
error_catcher(async (req, res) => {
|
|
1041
1069
|
const { name } = req.params;
|
|
1042
|
-
|
|
1070
|
+
const tenants_unsafe_plugins = getRootState().getConfig(
|
|
1071
|
+
"tenants_unsafe_plugins",
|
|
1072
|
+
false
|
|
1073
|
+
);
|
|
1043
1074
|
const plugin = await Plugin.store_by_name(decodeURIComponent(name));
|
|
1044
1075
|
if (!plugin) {
|
|
1045
1076
|
req.flash(
|
|
@@ -1050,7 +1081,7 @@ router.post(
|
|
|
1050
1081
|
return;
|
|
1051
1082
|
}
|
|
1052
1083
|
const isRoot = db.getTenantSchema() === db.connectObj.default_schema;
|
|
1053
|
-
if (!isRoot && plugin.unsafe) {
|
|
1084
|
+
if (!isRoot && plugin.unsafe && !tenants_unsafe_plugins) {
|
|
1054
1085
|
req.flash(
|
|
1055
1086
|
"error",
|
|
1056
1087
|
req.__("Cannot install unsafe modules on subdomain tenants")
|