@saltcorn/server 0.7.2-beta.7 → 0.7.2
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 -1
- package/locales/en.json +916 -906
- package/package.json +7 -7
- package/public/saltcorn-common.js +34 -0
- package/restart_watcher.js +2 -1
- package/routes/actions.js +1 -0
- package/routes/admin.js +153 -27
- package/routes/fields.js +4 -1
- package/routes/tables.js +19 -4
- package/serve.js +15 -1
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/server",
|
|
3
|
-
"version": "0.7.2
|
|
3
|
+
"version": "0.7.2",
|
|
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
|
-
"@saltcorn/base-plugin": "0.7.2
|
|
10
|
-
"@saltcorn/builder": "0.7.2
|
|
11
|
-
"@saltcorn/data": "0.7.2
|
|
12
|
-
"@saltcorn/admin-models": "0.7.2
|
|
13
|
-
"@saltcorn/markup": "0.7.2
|
|
14
|
-
"@saltcorn/sbadmin2": "0.7.2
|
|
9
|
+
"@saltcorn/base-plugin": "0.7.2",
|
|
10
|
+
"@saltcorn/builder": "0.7.2",
|
|
11
|
+
"@saltcorn/data": "0.7.2",
|
|
12
|
+
"@saltcorn/admin-models": "0.7.2",
|
|
13
|
+
"@saltcorn/markup": "0.7.2",
|
|
14
|
+
"@saltcorn/sbadmin2": "0.7.2",
|
|
15
15
|
"@socket.io/cluster-adapter": "^0.1.0",
|
|
16
16
|
"@socket.io/sticky": "^1.0.1",
|
|
17
17
|
"aws-sdk": "^2.1037.0",
|
|
@@ -215,6 +215,40 @@ function initialize_page() {
|
|
|
215
215
|
$(".blur-on-enter-keypress").bind("keyup", function (e) {
|
|
216
216
|
if (e.keyCode === 13) e.target.blur();
|
|
217
217
|
});
|
|
218
|
+
|
|
219
|
+
const validate_expression_elem = (target) => {
|
|
220
|
+
const next = target.next();
|
|
221
|
+
if (next.hasClass("expr-error")) next.remove();
|
|
222
|
+
const val = target.val();
|
|
223
|
+
if (target.hasClass("validate-expression-conditional")) {
|
|
224
|
+
const box = target
|
|
225
|
+
.closest(".form-namespace")
|
|
226
|
+
.find(`[name="${target.attr("name")}_formula"]`);
|
|
227
|
+
if (!box.prop("checked")) return;
|
|
228
|
+
}
|
|
229
|
+
if (!val) return;
|
|
230
|
+
try {
|
|
231
|
+
Function("return " + val);
|
|
232
|
+
} catch (error) {
|
|
233
|
+
target.after(`<small class="text-danger font-monospace d-block expr-error">
|
|
234
|
+
${error.message}
|
|
235
|
+
</small>`);
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
$(".validate-expression").bind("input", function (e) {
|
|
239
|
+
const target = $(e.target);
|
|
240
|
+
validate_expression_elem(target);
|
|
241
|
+
});
|
|
242
|
+
$(".validate-expression-conditional").each(function () {
|
|
243
|
+
const theInput = $(this);
|
|
244
|
+
theInput
|
|
245
|
+
.closest(".form-namespace")
|
|
246
|
+
.find(`[name="${theInput.attr("name")}_formula"]`)
|
|
247
|
+
.bind("change", function (e) {
|
|
248
|
+
validate_expression_elem(theInput);
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
218
252
|
$("form").change(apply_showif);
|
|
219
253
|
apply_showif();
|
|
220
254
|
apply_showif();
|
package/restart_watcher.js
CHANGED
|
@@ -17,6 +17,8 @@ const relevantPackages = [
|
|
|
17
17
|
"db-common",
|
|
18
18
|
"postgres",
|
|
19
19
|
"saltcorn-data",
|
|
20
|
+
"saltcorn-builder",
|
|
21
|
+
"saltcorn-admin-models",
|
|
20
22
|
"saltcorn-markup",
|
|
21
23
|
"saltcorn-sbadmin2",
|
|
22
24
|
"server",
|
|
@@ -28,7 +30,6 @@ const relevantPackages = [
|
|
|
28
30
|
*/
|
|
29
31
|
const excludePatterns = [
|
|
30
32
|
/\/node_modules/,
|
|
31
|
-
/\/public/,
|
|
32
33
|
/\.git/,
|
|
33
34
|
/\.docs/,
|
|
34
35
|
/\.docs/,
|
package/routes/actions.js
CHANGED
package/routes/admin.js
CHANGED
|
@@ -48,6 +48,7 @@ const { loadAllPlugins } = require("../load_plugins");
|
|
|
48
48
|
const {
|
|
49
49
|
create_backup,
|
|
50
50
|
restore,
|
|
51
|
+
auto_backup_now,
|
|
51
52
|
} = require("@saltcorn/admin-models/models/backup");
|
|
52
53
|
const {
|
|
53
54
|
runConfigurationCheck,
|
|
@@ -293,41 +294,163 @@ router.get(
|
|
|
293
294
|
"/backup",
|
|
294
295
|
isAdmin,
|
|
295
296
|
error_catcher(async (req, res) => {
|
|
297
|
+
const backupForm = autoBackupForm(req);
|
|
298
|
+
backupForm.values.auto_backup_frequency = getState().getConfig(
|
|
299
|
+
"auto_backup_frequency"
|
|
300
|
+
);
|
|
301
|
+
backupForm.values.auto_backup_destination = getState().getConfig(
|
|
302
|
+
"auto_backup_destination"
|
|
303
|
+
);
|
|
304
|
+
backupForm.values.auto_backup_directory = getState().getConfig(
|
|
305
|
+
"auto_backup_directory"
|
|
306
|
+
);
|
|
307
|
+
backupForm.values.auto_backup_expire_days = getState().getConfig(
|
|
308
|
+
"auto_backup_expire_days"
|
|
309
|
+
);
|
|
310
|
+
const isRoot = db.getTenantSchema() === db.connectObj.default_schema;
|
|
311
|
+
|
|
296
312
|
send_admin_page({
|
|
297
313
|
res,
|
|
298
314
|
req,
|
|
299
315
|
active_sub: "Backup",
|
|
300
316
|
contents: {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
317
|
+
above: [
|
|
318
|
+
{
|
|
319
|
+
type: "card",
|
|
320
|
+
title: req.__("Manual backup"),
|
|
321
|
+
contents: {
|
|
322
|
+
besides: [
|
|
307
323
|
div(
|
|
308
|
-
post_btn(
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
req.
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
324
|
+
post_btn(
|
|
325
|
+
"/admin/backup",
|
|
326
|
+
i({ class: "fas fa-download me-2" }) +
|
|
327
|
+
req.__("Download a backup"),
|
|
328
|
+
req.csrfToken(),
|
|
329
|
+
{
|
|
330
|
+
btnClass: "btn-outline-primary",
|
|
331
|
+
}
|
|
332
|
+
)
|
|
333
|
+
),
|
|
334
|
+
div(
|
|
335
|
+
restore_backup(req.csrfToken(), [
|
|
336
|
+
i({ class: "fas fa-2x fa-upload me-2" }),
|
|
337
|
+
"",
|
|
338
|
+
req.__("Restore a backup"),
|
|
339
|
+
])
|
|
340
|
+
),
|
|
341
|
+
],
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
isRoot
|
|
345
|
+
? {
|
|
346
|
+
type: "card",
|
|
347
|
+
title: req.__("Automated backup"),
|
|
348
|
+
contents: div(renderForm(backupForm, req.csrfToken())),
|
|
349
|
+
}
|
|
350
|
+
: { type: "blank", contents: "" },
|
|
351
|
+
],
|
|
326
352
|
},
|
|
327
353
|
});
|
|
328
354
|
})
|
|
329
355
|
);
|
|
330
356
|
|
|
357
|
+
/**
|
|
358
|
+
* Auto backup Form
|
|
359
|
+
* @param {object} req
|
|
360
|
+
* @returns {Form} form
|
|
361
|
+
*/
|
|
362
|
+
const autoBackupForm = (req) =>
|
|
363
|
+
new Form({
|
|
364
|
+
action: "/admin/set-auto-backup",
|
|
365
|
+
submitButtonClass: "btn-outline-primary",
|
|
366
|
+
onChange: "remove_outline(this)",
|
|
367
|
+
submitLabel: "Save settings",
|
|
368
|
+
additionalButtons: [
|
|
369
|
+
{
|
|
370
|
+
label: "Backup now",
|
|
371
|
+
id: "btnBackupNow",
|
|
372
|
+
class: "btn btn-outline-secondary",
|
|
373
|
+
onclick: "ajax_post('/admin/auto-backup-now')",
|
|
374
|
+
},
|
|
375
|
+
],
|
|
376
|
+
fields: [
|
|
377
|
+
{
|
|
378
|
+
type: "String",
|
|
379
|
+
label: req.__("Frequency"),
|
|
380
|
+
name: "auto_backup_frequency",
|
|
381
|
+
required: true,
|
|
382
|
+
attributes: { options: ["Never", "Daily", "Weekly"] },
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
type: "String",
|
|
386
|
+
label: req.__("Destination"),
|
|
387
|
+
name: "auto_backup_destination",
|
|
388
|
+
required: true,
|
|
389
|
+
showIf: { auto_backup_frequency: ["Daily", "Weekly"] },
|
|
390
|
+
attributes: { options: ["Saltcorn files", "Local directory"] },
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
type: "String",
|
|
394
|
+
label: req.__("Directory"),
|
|
395
|
+
name: "auto_backup_directory",
|
|
396
|
+
showIf: {
|
|
397
|
+
auto_backup_frequency: ["Daily", "Weekly"],
|
|
398
|
+
auto_backup_destination: "Local directory",
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
type: "Integer",
|
|
403
|
+
label: req.__("Expiration in days"),
|
|
404
|
+
sublabel: req.__(
|
|
405
|
+
"Delete old backup files in this directory after the set number of days"
|
|
406
|
+
),
|
|
407
|
+
name: "auto_backup_expire_days",
|
|
408
|
+
showIf: {
|
|
409
|
+
auto_backup_frequency: ["Daily", "Weekly"],
|
|
410
|
+
auto_backup_destination: "Local directory",
|
|
411
|
+
},
|
|
412
|
+
},
|
|
413
|
+
],
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
router.post(
|
|
417
|
+
"/set-auto-backup",
|
|
418
|
+
isAdmin,
|
|
419
|
+
error_catcher(async (req, res) => {
|
|
420
|
+
const form = await autoBackupForm(req);
|
|
421
|
+
form.validate(req.body);
|
|
422
|
+
if (form.hasErrors) {
|
|
423
|
+
send_admin_page({
|
|
424
|
+
res,
|
|
425
|
+
req,
|
|
426
|
+
active_sub: "Backup",
|
|
427
|
+
contents: {
|
|
428
|
+
type: "card",
|
|
429
|
+
title: req.__("Backup settings"),
|
|
430
|
+
contents: [renderForm(form, req.csrfToken())],
|
|
431
|
+
},
|
|
432
|
+
});
|
|
433
|
+
} else {
|
|
434
|
+
await save_config_from_form(form);
|
|
435
|
+
req.flash("success", req.__("Backup settings updated"));
|
|
436
|
+
res.redirect("/admin/backup");
|
|
437
|
+
}
|
|
438
|
+
})
|
|
439
|
+
);
|
|
440
|
+
router.post(
|
|
441
|
+
"/auto-backup-now",
|
|
442
|
+
isAdmin,
|
|
443
|
+
error_catcher(async (req, res) => {
|
|
444
|
+
try {
|
|
445
|
+
await auto_backup_now();
|
|
446
|
+
req.flash("success", req.__("Backup successful"));
|
|
447
|
+
} catch (e) {
|
|
448
|
+
req.flash("error", e.message);
|
|
449
|
+
}
|
|
450
|
+
res.json({ reload_page: true });
|
|
451
|
+
})
|
|
452
|
+
);
|
|
453
|
+
|
|
331
454
|
/**
|
|
332
455
|
* @name get/system
|
|
333
456
|
* @function
|
|
@@ -550,8 +673,8 @@ router.post(
|
|
|
550
673
|
const fileName = await create_backup();
|
|
551
674
|
res.type("application/zip");
|
|
552
675
|
res.attachment(fileName);
|
|
553
|
-
|
|
554
|
-
|
|
676
|
+
const file = fs.createReadStream(fileName);
|
|
677
|
+
file.on("end", function () {
|
|
555
678
|
fs.unlink(fileName, function () {});
|
|
556
679
|
});
|
|
557
680
|
file.pipe(res);
|
|
@@ -794,7 +917,10 @@ router.get(
|
|
|
794
917
|
? div(
|
|
795
918
|
{ class: "alert alert-success", role: "alert" },
|
|
796
919
|
i({ class: "fas fa-check-circle fa-lg me-2" }),
|
|
797
|
-
h5(
|
|
920
|
+
h5(
|
|
921
|
+
{ class: "d-inline" },
|
|
922
|
+
req.__("No errors detected during configuration check")
|
|
923
|
+
)
|
|
798
924
|
)
|
|
799
925
|
: errors.map(mkError)
|
|
800
926
|
),
|
package/routes/fields.js
CHANGED
|
@@ -300,7 +300,9 @@ const fieldFlow = (req) =>
|
|
|
300
300
|
name: "also_delete_file",
|
|
301
301
|
type: "Bool",
|
|
302
302
|
label: req.__("Cascade delete to file"),
|
|
303
|
-
sublabel: req.__(
|
|
303
|
+
sublabel: req.__(
|
|
304
|
+
"Deleting a row will also delete the file referenced by this field"
|
|
305
|
+
),
|
|
304
306
|
},
|
|
305
307
|
],
|
|
306
308
|
});
|
|
@@ -337,6 +339,7 @@ const fieldFlow = (req) =>
|
|
|
337
339
|
label: req.__("Formula"),
|
|
338
340
|
// todo sublabel
|
|
339
341
|
type: "String",
|
|
342
|
+
class: "validate-expression",
|
|
340
343
|
validator: expressionValidator,
|
|
341
344
|
}),
|
|
342
345
|
new Field({
|
package/routes/tables.js
CHANGED
|
@@ -21,7 +21,10 @@ const {
|
|
|
21
21
|
post_delete_btn,
|
|
22
22
|
post_dropdown_item,
|
|
23
23
|
} = require("@saltcorn/markup");
|
|
24
|
-
const {
|
|
24
|
+
const {
|
|
25
|
+
recalculate_for_stored,
|
|
26
|
+
expressionValidator,
|
|
27
|
+
} = require("@saltcorn/data/models/expression");
|
|
25
28
|
const { isAdmin, error_catcher, setTenant } = require("./utils.js");
|
|
26
29
|
const Form = require("@saltcorn/data/models/form");
|
|
27
30
|
const {
|
|
@@ -101,7 +104,9 @@ const tableForm = async (table, req) => {
|
|
|
101
104
|
{
|
|
102
105
|
name: "ownership_formula",
|
|
103
106
|
label: req.__("Ownership formula"),
|
|
107
|
+
validator: expressionValidator,
|
|
104
108
|
type: "String",
|
|
109
|
+
class: "validate-expression",
|
|
105
110
|
sublabel:
|
|
106
111
|
req.__("User is treated as owner if true. In scope: ") +
|
|
107
112
|
["user", ...fields.map((f) => f.name)]
|
|
@@ -878,10 +883,20 @@ router.post(
|
|
|
878
883
|
const { id, _csrf, ...rest } = v;
|
|
879
884
|
const table = await Table.findOne({ id: parseInt(id) });
|
|
880
885
|
const old_versioned = table.versioned;
|
|
886
|
+
let hasError = false;
|
|
881
887
|
if (!rest.versioned) rest.versioned = false;
|
|
882
|
-
if (rest.ownership_field_id === "_formula")
|
|
888
|
+
if (rest.ownership_field_id === "_formula") {
|
|
883
889
|
rest.ownership_field_id = null;
|
|
884
|
-
|
|
890
|
+
const fmlValidRes = expressionValidator(rest.ownership_formula);
|
|
891
|
+
console.log({ fmlValidRes });
|
|
892
|
+
if (typeof fmlValidRes === "string") {
|
|
893
|
+
req.flash(
|
|
894
|
+
"error",
|
|
895
|
+
req.__(`Invalid ownership formula: %s`, fmlValidRes)
|
|
896
|
+
);
|
|
897
|
+
hasError = true;
|
|
898
|
+
}
|
|
899
|
+
} else rest.ownership_formula = null;
|
|
885
900
|
await table.update(rest);
|
|
886
901
|
if (!old_versioned && rest.versioned)
|
|
887
902
|
req.flash(
|
|
@@ -893,7 +908,7 @@ router.post(
|
|
|
893
908
|
"success",
|
|
894
909
|
req.__("Table saved with version history disabled")
|
|
895
910
|
);
|
|
896
|
-
else req.flash("success", req.__("Table saved"));
|
|
911
|
+
else if (!hasError) req.flash("success", req.__("Table saved"));
|
|
897
912
|
|
|
898
913
|
res.redirect(`/table/${id}`);
|
|
899
914
|
}
|
package/serve.js
CHANGED
|
@@ -43,6 +43,7 @@ const {
|
|
|
43
43
|
eachTenant,
|
|
44
44
|
getAllTenants,
|
|
45
45
|
} = require("@saltcorn/admin-models/models/tenant");
|
|
46
|
+
const { auto_backup_now } = require("@saltcorn/admin-models/models/backup");
|
|
46
47
|
|
|
47
48
|
// helpful https://gist.github.com/jpoehls/2232358
|
|
48
49
|
/**
|
|
@@ -135,7 +136,13 @@ const onMessageFromWorker =
|
|
|
135
136
|
//console.log("worker msg", typeof msg, msg);
|
|
136
137
|
if (msg === "Start" && !masterState.started) {
|
|
137
138
|
masterState.started = true;
|
|
138
|
-
runScheduler({
|
|
139
|
+
runScheduler({
|
|
140
|
+
port,
|
|
141
|
+
watchReaper,
|
|
142
|
+
disableScheduler,
|
|
143
|
+
eachTenant,
|
|
144
|
+
auto_backup_now,
|
|
145
|
+
});
|
|
139
146
|
require("./systemd")({ port });
|
|
140
147
|
return true;
|
|
141
148
|
} else if (msg === "RestartServer") {
|
|
@@ -261,6 +268,13 @@ module.exports =
|
|
|
261
268
|
});
|
|
262
269
|
} else {
|
|
263
270
|
await nonGreenlockWorkerSetup(appargs, port);
|
|
271
|
+
runScheduler({
|
|
272
|
+
port,
|
|
273
|
+
watchReaper,
|
|
274
|
+
disableScheduler,
|
|
275
|
+
eachTenant,
|
|
276
|
+
auto_backup_now,
|
|
277
|
+
});
|
|
264
278
|
}
|
|
265
279
|
Trigger.emitEvent("Startup");
|
|
266
280
|
} else {
|