@saltcorn/server 0.9.0-beta.1 → 0.9.0-beta.10
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 +58 -6
- package/auth/routes.js +16 -20
- package/errors.js +15 -4
- package/help/Actions.tmd +9 -0
- package/help/Extra state formula.tmd +62 -0
- package/help/Field views.tmd +22 -0
- package/help/JavaScript action code.tmd +161 -0
- package/help/Table formula constraint.tmd +2 -0
- package/help/View patterns.tmd +35 -0
- package/help/Where formula.tmd +30 -0
- package/help/index.js +11 -5
- package/locales/da.json +709 -709
- package/locales/de.json +1049 -1049
- package/locales/en.json +16 -2
- package/locales/pl.json +1155 -1155
- package/locales/ru.json +1101 -1101
- package/locales/si.json +1196 -1196
- package/locales/uk.json +1168 -1168
- package/locales/zh.json +886 -886
- package/package.json +10 -9
- package/public/saltcorn-builder.css +4 -0
- package/public/saltcorn-common.js +59 -5
- package/public/saltcorn.js +29 -3
- package/routes/actions.js +5 -3
- package/routes/admin.js +19 -4
- package/routes/fields.js +15 -3
- package/routes/menu.js +1 -1
- package/routes/packs.js +134 -9
- package/routes/plugins.js +186 -36
- package/routes/sync.js +4 -1
- package/routes/tables.js +4 -3
- package/routes/viewedit.js +21 -1
- package/tests/admin.test.js +2 -2
- package/tests/sync.test.js +140 -6
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/server",
|
|
3
|
-
"version": "0.9.0-beta.
|
|
3
|
+
"version": "0.9.0-beta.10",
|
|
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.9.0-beta.
|
|
10
|
-
"@saltcorn/builder": "0.9.0-beta.
|
|
11
|
-
"@saltcorn/data": "0.9.0-beta.
|
|
12
|
-
"@saltcorn/admin-models": "0.9.0-beta.
|
|
13
|
-
"@saltcorn/filemanager": "0.9.0-beta.
|
|
14
|
-
"@saltcorn/markup": "0.9.0-beta.
|
|
15
|
-
"@saltcorn/sbadmin2": "0.9.0-beta.
|
|
9
|
+
"@saltcorn/base-plugin": "0.9.0-beta.10",
|
|
10
|
+
"@saltcorn/builder": "0.9.0-beta.10",
|
|
11
|
+
"@saltcorn/data": "0.9.0-beta.10",
|
|
12
|
+
"@saltcorn/admin-models": "0.9.0-beta.10",
|
|
13
|
+
"@saltcorn/filemanager": "0.9.0-beta.10",
|
|
14
|
+
"@saltcorn/markup": "0.9.0-beta.10",
|
|
15
|
+
"@saltcorn/sbadmin2": "0.9.0-beta.10",
|
|
16
16
|
"@socket.io/cluster-adapter": "^0.2.1",
|
|
17
17
|
"@socket.io/sticky": "^1.0.1",
|
|
18
18
|
"adm-zip": "0.5.10",
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"node-fetch": "2.6.9",
|
|
46
46
|
"node-watch": "^0.7.2",
|
|
47
47
|
"notp": "2.0.3",
|
|
48
|
+
"npm-registry-fetch": "16.0.0",
|
|
48
49
|
"passport": "^0.6.0",
|
|
49
50
|
"passport-custom": "^1.1.1",
|
|
50
51
|
"passport-http-bearer": "^1.0.1",
|
|
@@ -55,7 +56,7 @@
|
|
|
55
56
|
"qrcode": "1.5.1",
|
|
56
57
|
"resize-with-sharp-or-jimp": "0.1.6",
|
|
57
58
|
"socket.io": "4.6.0",
|
|
58
|
-
"systeminformation": "^5.
|
|
59
|
+
"systeminformation": "^5.21.7",
|
|
59
60
|
"thirty-two": "1.0.2",
|
|
60
61
|
"tmp-promise": "^3.0.2",
|
|
61
62
|
"uuid": "^8.2.0",
|
|
@@ -51,6 +51,7 @@ const nubBy = (prop, xs) => {
|
|
|
51
51
|
});
|
|
52
52
|
};
|
|
53
53
|
function apply_showif() {
|
|
54
|
+
const isNode = typeof parent?.saltcorn?.data?.state === "undefined";
|
|
54
55
|
$("[data-show-if]").each(function (ix, element) {
|
|
55
56
|
var e = $(element);
|
|
56
57
|
try {
|
|
@@ -78,6 +79,15 @@ function apply_showif() {
|
|
|
78
79
|
console.error(e);
|
|
79
80
|
}
|
|
80
81
|
});
|
|
82
|
+
$("[data-dyn-href]").each(function (ix, element) {
|
|
83
|
+
const e = $(element);
|
|
84
|
+
const rec = get_form_record(e);
|
|
85
|
+
const href = new Function(
|
|
86
|
+
`{${Object.keys(rec).join(",")}}`,
|
|
87
|
+
"return " + e.attr("data-dyn-href")
|
|
88
|
+
)(rec);
|
|
89
|
+
e.attr("href", href);
|
|
90
|
+
});
|
|
81
91
|
$("[data-calc-options]").each(function (ix, element) {
|
|
82
92
|
var e = $(element);
|
|
83
93
|
var data = JSON.parse(decodeURIComponent(e.attr("data-calc-options")));
|
|
@@ -263,10 +273,12 @@ function apply_showif() {
|
|
|
263
273
|
|
|
264
274
|
if (typeof cache[recS] !== "undefined") {
|
|
265
275
|
e.html(cache[recS]);
|
|
276
|
+
e.prop("data-source-url-current", recS);
|
|
266
277
|
activate_onchange_coldef();
|
|
267
278
|
return;
|
|
268
279
|
}
|
|
269
|
-
|
|
280
|
+
|
|
281
|
+
const cb = {
|
|
270
282
|
success: (data) => {
|
|
271
283
|
e.html(data);
|
|
272
284
|
const cacheNow = e.prop("data-source-url-cache") || {};
|
|
@@ -286,7 +298,11 @@ function apply_showif() {
|
|
|
286
298
|
});
|
|
287
299
|
e.html("");
|
|
288
300
|
},
|
|
289
|
-
}
|
|
301
|
+
};
|
|
302
|
+
if (isNode) ajax_post_json(e.attr("data-source-url"), rec, cb);
|
|
303
|
+
else {
|
|
304
|
+
local_post_json(e.attr("data-source-url"), rec, cb);
|
|
305
|
+
}
|
|
290
306
|
});
|
|
291
307
|
const locale =
|
|
292
308
|
navigator.userLanguage ||
|
|
@@ -509,6 +525,11 @@ function initialize_page() {
|
|
|
509
525
|
});
|
|
510
526
|
|
|
511
527
|
$("form").change(apply_showif);
|
|
528
|
+
// also change if we select same
|
|
529
|
+
$("form select").on("blur", (e) => {
|
|
530
|
+
if (!e || !e.target) return;
|
|
531
|
+
$(e.target).closest("form").trigger("change");
|
|
532
|
+
});
|
|
512
533
|
apply_showif();
|
|
513
534
|
apply_showif();
|
|
514
535
|
$("[data-inline-edit-dest-url]").each(function () {
|
|
@@ -530,6 +551,7 @@ function initialize_page() {
|
|
|
530
551
|
var ajax = !!$(this).attr("data-inline-edit-ajax");
|
|
531
552
|
var type = $(this).attr("data-inline-edit-type");
|
|
532
553
|
var schema = $(this).attr("data-inline-edit-schema");
|
|
554
|
+
var decimalPlaces = $(this).attr("data-inline-edit-decimal-places");
|
|
533
555
|
if (schema) {
|
|
534
556
|
schema = JSON.parse(decodeURIComponent(schema));
|
|
535
557
|
}
|
|
@@ -552,6 +574,7 @@ function initialize_page() {
|
|
|
552
574
|
type,
|
|
553
575
|
is_key,
|
|
554
576
|
schema,
|
|
577
|
+
...(decimalPlaces ? { decimalPlaces } : {}),
|
|
555
578
|
})
|
|
556
579
|
);
|
|
557
580
|
const doAjaxOptionsFetch = (tblName, target) => {
|
|
@@ -604,7 +627,17 @@ function initialize_page() {
|
|
|
604
627
|
}
|
|
605
628
|
<input type="${
|
|
606
629
|
type === "Integer" || type === "Float" ? "number" : "text"
|
|
607
|
-
}"
|
|
630
|
+
}" ${
|
|
631
|
+
type === "Float"
|
|
632
|
+
? `step="${
|
|
633
|
+
decimalPlaces
|
|
634
|
+
? Math.round(
|
|
635
|
+
Math.pow(10, -decimalPlaces) * Math.pow(10, decimalPlaces)
|
|
636
|
+
) / Math.pow(10, decimalPlaces)
|
|
637
|
+
: "any"
|
|
638
|
+
}"`
|
|
639
|
+
: ""
|
|
640
|
+
} name="${key}" value="${escapeHtml(current)}">
|
|
608
641
|
<button type="submit" class="btn btn-sm btn-primary">OK</button>
|
|
609
642
|
<button onclick="cancel_inline_edit(event, '${opts}')" type="button" class="btn btn-sm btn-danger"><i class="fas fa-times"></i></button>
|
|
610
643
|
</form>`
|
|
@@ -657,10 +690,13 @@ function initialize_page() {
|
|
|
657
690
|
setTimeout(() => {
|
|
658
691
|
codes.forEach((el) => {
|
|
659
692
|
//console.log($(el).attr("mode"), el);
|
|
693
|
+
if ($(el).hasClass("codemirror-enabled")) return;
|
|
694
|
+
|
|
660
695
|
const cm = CodeMirror.fromTextArea(el, {
|
|
661
696
|
lineNumbers: true,
|
|
662
697
|
mode: $(el).attr("mode"),
|
|
663
698
|
});
|
|
699
|
+
$(el).addClass("codemirror-enabled");
|
|
664
700
|
cm.on(
|
|
665
701
|
"change",
|
|
666
702
|
$.debounce(() => {
|
|
@@ -771,6 +807,11 @@ function inline_submit_success(e, form, opts) {
|
|
|
771
807
|
: ""
|
|
772
808
|
}
|
|
773
809
|
${opts.current_label ? `data-inline-edit-current-label="${val}"` : ""}
|
|
810
|
+
${
|
|
811
|
+
opts.decimalPlaces
|
|
812
|
+
? `data-inline-edit-decimal-places="${opts.decimalPlaces}"`
|
|
813
|
+
: ""
|
|
814
|
+
}
|
|
774
815
|
data-inline-edit-dest-url="${opts.url}">
|
|
775
816
|
<span class="current">${val}</span>
|
|
776
817
|
<i class="editicon ${!isNode ? "visible" : ""} fas fa-edit ms-1"></i>
|
|
@@ -948,14 +989,25 @@ function emptyAlerts() {
|
|
|
948
989
|
$("#toasts-area").html("");
|
|
949
990
|
}
|
|
950
991
|
|
|
951
|
-
function press_store_button(clicked) {
|
|
992
|
+
function press_store_button(clicked, keepOld) {
|
|
952
993
|
let btn = clicked;
|
|
953
994
|
if ($(clicked).is("form")) btn = $(clicked).find("button[type=submit]");
|
|
954
|
-
|
|
995
|
+
if (keepOld) {
|
|
996
|
+
const oldText = $(btn).html();
|
|
997
|
+
$(btn).data("old-text", oldText);
|
|
998
|
+
}
|
|
955
999
|
const width = $(btn).width();
|
|
956
1000
|
$(btn).html('<i class="fas fa-spinner fa-spin"></i>').width(width);
|
|
957
1001
|
}
|
|
958
1002
|
|
|
1003
|
+
function restore_old_button(btnId) {
|
|
1004
|
+
const btn = $(`#${btnId}`);
|
|
1005
|
+
const oldText = $(btn).data("old-text");
|
|
1006
|
+
btn.html(oldText);
|
|
1007
|
+
btn.css({ width: "" });
|
|
1008
|
+
btn.removeData("old-text");
|
|
1009
|
+
}
|
|
1010
|
+
|
|
959
1011
|
function common_done(res, viewname, isWeb = true) {
|
|
960
1012
|
const handle = (element, fn) => {
|
|
961
1013
|
if (Array.isArray(element)) for (const current of element) fn(current);
|
|
@@ -994,6 +1046,7 @@ function common_done(res, viewname, isWeb = true) {
|
|
|
994
1046
|
if (input.attr("type") === "checkbox")
|
|
995
1047
|
input.prop("checked", res.set_fields[k]);
|
|
996
1048
|
else input.val(res.set_fields[k]);
|
|
1049
|
+
input.trigger("set_form_field");
|
|
997
1050
|
});
|
|
998
1051
|
}
|
|
999
1052
|
if (res.goto && !isWeb)
|
|
@@ -1011,6 +1064,7 @@ function common_done(res, viewname, isWeb = true) {
|
|
|
1011
1064
|
if (
|
|
1012
1065
|
prev.origin === next.origin &&
|
|
1013
1066
|
prev.pathname === next.pathname &&
|
|
1067
|
+
prev.searchParams.toString() === next.searchParams.toString() &&
|
|
1014
1068
|
next.hash !== prev.hash
|
|
1015
1069
|
)
|
|
1016
1070
|
location.reload();
|
package/public/saltcorn.js
CHANGED
|
@@ -340,6 +340,28 @@ function ajax_modal(url, opts = {}) {
|
|
|
340
340
|
$("body").css("overflow", "");
|
|
341
341
|
});
|
|
342
342
|
},
|
|
343
|
+
...(opts.onError
|
|
344
|
+
? {
|
|
345
|
+
error: opts.onError,
|
|
346
|
+
}
|
|
347
|
+
: {}),
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function selectVersionError(res, btnId) {
|
|
352
|
+
notifyAlert({
|
|
353
|
+
type: "danger",
|
|
354
|
+
text: res.responseJSON?.error || "unknown error",
|
|
355
|
+
});
|
|
356
|
+
restore_old_button(btnId);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function submitWithAjax(e) {
|
|
360
|
+
saveAndContinue(e, (res) => {
|
|
361
|
+
if (res && res.responseJSON && res.responseJSON.url_when_done)
|
|
362
|
+
window.location.href = res.responseJSON.url_when_done;
|
|
363
|
+
if (res && res.responseJSON && res.responseJSON.error)
|
|
364
|
+
notifyAlert({ type: "danger", text: res.responseJSON.error });
|
|
343
365
|
});
|
|
344
366
|
}
|
|
345
367
|
|
|
@@ -368,6 +390,9 @@ function saveAndContinue(e, k) {
|
|
|
368
390
|
if (res.notify) {
|
|
369
391
|
notifyAlert(res.notify);
|
|
370
392
|
}
|
|
393
|
+
if (res.reload_page) {
|
|
394
|
+
location.reload(); //TODO notify to cookie if reload or goto
|
|
395
|
+
}
|
|
371
396
|
},
|
|
372
397
|
error: function (request) {
|
|
373
398
|
var ct = request.getResponseHeader("content-type") || "";
|
|
@@ -388,8 +413,8 @@ function saveAndContinue(e, k) {
|
|
|
388
413
|
}
|
|
389
414
|
ajax_indicate_error(e, request);
|
|
390
415
|
},
|
|
391
|
-
complete: function () {
|
|
392
|
-
if (k) k();
|
|
416
|
+
complete: function (res) {
|
|
417
|
+
if (k) k(res);
|
|
393
418
|
},
|
|
394
419
|
});
|
|
395
420
|
|
|
@@ -410,7 +435,8 @@ function updateMatchingRows(e, viewname) {
|
|
|
410
435
|
}
|
|
411
436
|
}
|
|
412
437
|
|
|
413
|
-
function applyViewConfig(e, url, k) {
|
|
438
|
+
function applyViewConfig(e, url, k, event) {
|
|
439
|
+
if (event && event.target && event.target.id === "myEditor_icon") return;
|
|
414
440
|
var form = $(e).closest("form");
|
|
415
441
|
var form_data = form.serializeArray();
|
|
416
442
|
const cfg = {};
|
package/routes/actions.js
CHANGED
|
@@ -174,8 +174,9 @@ const triggerForm = async (req, trigger) => {
|
|
|
174
174
|
attributes: {
|
|
175
175
|
explainers: {
|
|
176
176
|
Often: req.__("Every 5 minutes"),
|
|
177
|
-
Never:
|
|
178
|
-
|
|
177
|
+
Never: req.__(
|
|
178
|
+
"Not scheduled but can be run as an action from a button click"
|
|
179
|
+
),
|
|
179
180
|
},
|
|
180
181
|
},
|
|
181
182
|
},
|
|
@@ -201,6 +202,7 @@ const triggerForm = async (req, trigger) => {
|
|
|
201
202
|
label: req.__("Action"),
|
|
202
203
|
type: "String",
|
|
203
204
|
required: true,
|
|
205
|
+
help: { topic: "Actions" },
|
|
204
206
|
attributes: {
|
|
205
207
|
calcOptions: ["when_trigger", action_options],
|
|
206
208
|
},
|
|
@@ -402,7 +404,7 @@ router.get(
|
|
|
402
404
|
form.values = trigger.configuration;
|
|
403
405
|
const events = Trigger.when_options;
|
|
404
406
|
const actions = Trigger.find({
|
|
405
|
-
when_trigger: {or: ["API call", "Never"]},
|
|
407
|
+
when_trigger: { or: ["API call", "Never"] },
|
|
406
408
|
});
|
|
407
409
|
const tables = (await Table.find({})).map((t) => ({
|
|
408
410
|
name: t.name,
|
package/routes/admin.js
CHANGED
|
@@ -285,6 +285,9 @@ router.get(
|
|
|
285
285
|
backupForm.values.auto_backup_expire_days = getState().getConfig(
|
|
286
286
|
"auto_backup_expire_days"
|
|
287
287
|
);
|
|
288
|
+
backupForm.values.backup_with_event_log = getState().getConfig(
|
|
289
|
+
"backup_with_event_log"
|
|
290
|
+
);
|
|
288
291
|
//
|
|
289
292
|
const aSnapshotForm = snapshotForm(req);
|
|
290
293
|
aSnapshotForm.values.snapshots_enabled =
|
|
@@ -721,6 +724,15 @@ const autoBackupForm = (req) =>
|
|
|
721
724
|
auto_backup_destination: "Local directory",
|
|
722
725
|
},
|
|
723
726
|
},
|
|
727
|
+
{
|
|
728
|
+
type: "Bool",
|
|
729
|
+
label: req.__("Include Event Logs"),
|
|
730
|
+
sublabel: req.__("Backup with event logs"),
|
|
731
|
+
name: "backup_with_event_log",
|
|
732
|
+
showIf: {
|
|
733
|
+
auto_backup_frequency: ["Daily", "Weekly"],
|
|
734
|
+
},
|
|
735
|
+
},
|
|
724
736
|
],
|
|
725
737
|
});
|
|
726
738
|
|
|
@@ -1545,7 +1557,7 @@ router.get(
|
|
|
1545
1557
|
input({
|
|
1546
1558
|
type: "hidden",
|
|
1547
1559
|
name: "entryPointType",
|
|
1548
|
-
value: "view",
|
|
1560
|
+
value: builderSettings.entryPointType || "view",
|
|
1549
1561
|
id: "entryPointTypeID",
|
|
1550
1562
|
}),
|
|
1551
1563
|
div(
|
|
@@ -1578,7 +1590,8 @@ router.get(
|
|
|
1578
1590
|
div(
|
|
1579
1591
|
{
|
|
1580
1592
|
class: `nav-link ${
|
|
1581
|
-
!builderSettings.entryPointType ||
|
|
1593
|
+
!builderSettings.entryPointType ||
|
|
1594
|
+
builderSettings.entryPointType === "view"
|
|
1582
1595
|
? "active"
|
|
1583
1596
|
: ""
|
|
1584
1597
|
}`,
|
|
@@ -1613,7 +1626,8 @@ router.get(
|
|
|
1613
1626
|
? "d-none"
|
|
1614
1627
|
: ""
|
|
1615
1628
|
}`,
|
|
1616
|
-
...(!builderSettings.entryPointType ||
|
|
1629
|
+
...(!builderSettings.entryPointType ||
|
|
1630
|
+
builderSettings.entryPointType === "view"
|
|
1617
1631
|
? { name: "entryPoint" }
|
|
1618
1632
|
: {}),
|
|
1619
1633
|
id: "viewInputID",
|
|
@@ -1636,7 +1650,8 @@ router.get(
|
|
|
1636
1650
|
select(
|
|
1637
1651
|
{
|
|
1638
1652
|
class: `form-select ${
|
|
1639
|
-
!builderSettings.entryPointType ||
|
|
1653
|
+
!builderSettings.entryPointType ||
|
|
1654
|
+
builderSettings.entryPointType === "view"
|
|
1640
1655
|
? "d-none"
|
|
1641
1656
|
: ""
|
|
1642
1657
|
}`,
|
package/routes/fields.js
CHANGED
|
@@ -409,7 +409,7 @@ const fieldFlow = (req) =>
|
|
|
409
409
|
instance_options[model.name].push(...instances.map((i) => i.name));
|
|
410
410
|
|
|
411
411
|
const outputs = await applyAsync(
|
|
412
|
-
model.templateObj
|
|
412
|
+
model.templateObj?.prediction_outputs || [], // unit tests can have templateObj undefined
|
|
413
413
|
{ table, configuration: model.configuration }
|
|
414
414
|
);
|
|
415
415
|
output_options[model.name] = outputs.map((o) => o.name);
|
|
@@ -840,6 +840,11 @@ router.post(
|
|
|
840
840
|
const table = Table.findOne({ name: tableName });
|
|
841
841
|
const role = req.user && req.user.id ? req.user.role_id : 100;
|
|
842
842
|
|
|
843
|
+
getState().log(
|
|
844
|
+
5,
|
|
845
|
+
`Route /fields/show-calculated/${tableName}/${fieldName}/${fieldview} user=${req.user?.id}`
|
|
846
|
+
);
|
|
847
|
+
|
|
843
848
|
const fields = table.getFields();
|
|
844
849
|
let row = { ...req.body };
|
|
845
850
|
if (row && Object.keys(row).length > 0) readState(row, fields);
|
|
@@ -1018,6 +1023,13 @@ router.post(
|
|
|
1018
1023
|
const { tableName, fieldName, fieldview } = req.params;
|
|
1019
1024
|
const table = Table.findOne({ name: tableName });
|
|
1020
1025
|
const fields = table.getFields();
|
|
1026
|
+
const state = getState();
|
|
1027
|
+
|
|
1028
|
+
state.log(
|
|
1029
|
+
5,
|
|
1030
|
+
`Route /fields/preview/${tableName}/${fieldName}/${fieldview} user=${req.user?.id}`
|
|
1031
|
+
);
|
|
1032
|
+
|
|
1021
1033
|
let field, row, value;
|
|
1022
1034
|
if (fieldName.includes(".")) {
|
|
1023
1035
|
const [refNm, targetNm] = fieldName.split(".");
|
|
@@ -1048,9 +1060,9 @@ router.post(
|
|
|
1048
1060
|
}
|
|
1049
1061
|
const fieldviews =
|
|
1050
1062
|
field.type === "Key"
|
|
1051
|
-
?
|
|
1063
|
+
? state.keyFieldviews
|
|
1052
1064
|
: field.type === "File"
|
|
1053
|
-
?
|
|
1065
|
+
? state.fileviews
|
|
1054
1066
|
: field.type.fieldviews;
|
|
1055
1067
|
if (!field.type || !fieldviews) {
|
|
1056
1068
|
res.send("");
|
package/routes/menu.js
CHANGED
|
@@ -44,7 +44,7 @@ const menuForm = async (req) => {
|
|
|
44
44
|
const views = await View.find({}, { orderBy: "name", nocase: true });
|
|
45
45
|
const pages = await Page.find({}, { orderBy: "name", nocase: true });
|
|
46
46
|
const roles = await User.get_roles();
|
|
47
|
-
const tables = await Table.
|
|
47
|
+
const tables = await Table.find_with_external({});
|
|
48
48
|
const dynTableOptions = tables.map((t) => t.name);
|
|
49
49
|
const dynOrderFieldOptions = {},
|
|
50
50
|
dynSectionFieldOptions = {};
|
package/routes/packs.js
CHANGED
|
@@ -6,18 +6,19 @@
|
|
|
6
6
|
|
|
7
7
|
const Router = require("express-promise-router");
|
|
8
8
|
const { isAdmin, error_catcher } = require("./utils.js");
|
|
9
|
-
const {
|
|
10
|
-
const { getState } = require("@saltcorn/data/db/state");
|
|
9
|
+
const { renderForm } = require("@saltcorn/markup");
|
|
11
10
|
const Table = require("@saltcorn/data/models/table");
|
|
12
11
|
const Form = require("@saltcorn/data/models/form");
|
|
13
12
|
const View = require("@saltcorn/data/models/view");
|
|
14
|
-
const Field = require("@saltcorn/data/models/field");
|
|
15
13
|
const Plugin = require("@saltcorn/data/models/plugin");
|
|
16
14
|
const Page = require("@saltcorn/data/models/page");
|
|
15
|
+
const Tag = require("@saltcorn/data/models/tag");
|
|
16
|
+
const EventLog = require("@saltcorn/data/models/eventlog");
|
|
17
|
+
const Model = require("@saltcorn/data/models/model");
|
|
18
|
+
const ModelInstance = require("@saltcorn/data/models/model_instance");
|
|
17
19
|
const load_plugins = require("../load_plugins");
|
|
18
20
|
|
|
19
21
|
const { is_pack } = require("@saltcorn/data/contracts");
|
|
20
|
-
const { contract, is } = require("contractis");
|
|
21
22
|
const {
|
|
22
23
|
table_pack,
|
|
23
24
|
view_pack,
|
|
@@ -26,15 +27,20 @@ const {
|
|
|
26
27
|
role_pack,
|
|
27
28
|
library_pack,
|
|
28
29
|
trigger_pack,
|
|
30
|
+
tag_pack,
|
|
31
|
+
model_pack,
|
|
32
|
+
model_instance_pack,
|
|
29
33
|
install_pack,
|
|
30
34
|
fetch_pack_by_name,
|
|
31
35
|
can_install_pack,
|
|
32
36
|
uninstall_pack,
|
|
37
|
+
event_log_pack,
|
|
33
38
|
} = require("@saltcorn/admin-models/models/pack");
|
|
34
|
-
const {
|
|
39
|
+
const { pre, code, p, text, text_attr } = require("@saltcorn/markup/tags");
|
|
35
40
|
const Library = require("@saltcorn/data/models/library");
|
|
36
41
|
const Trigger = require("@saltcorn/data/models/trigger");
|
|
37
42
|
const Role = require("@saltcorn/data/models/role");
|
|
43
|
+
const fs = require("fs");
|
|
38
44
|
|
|
39
45
|
/**
|
|
40
46
|
* @type {object}
|
|
@@ -98,6 +104,52 @@ router.get(
|
|
|
98
104
|
name: `role.${l.role}`,
|
|
99
105
|
type: "Bool",
|
|
100
106
|
}));
|
|
107
|
+
const tags = await Tag.find({});
|
|
108
|
+
const tagFields = tags.map((t) => ({
|
|
109
|
+
label: `${t.name} tag`,
|
|
110
|
+
name: `tag.${t.name}`,
|
|
111
|
+
type: "Bool",
|
|
112
|
+
}));
|
|
113
|
+
const models = await Model.find({});
|
|
114
|
+
const modelFields = models.map((m) => {
|
|
115
|
+
const modelTbl = Table.findOne({ id: m.table_id });
|
|
116
|
+
return {
|
|
117
|
+
label: `${m.name} model, table: ${
|
|
118
|
+
modelTbl.name || req.__("Table not found")
|
|
119
|
+
}`,
|
|
120
|
+
name: `model.${m.name}.${modelTbl.name}`,
|
|
121
|
+
type: "Bool",
|
|
122
|
+
};
|
|
123
|
+
});
|
|
124
|
+
const modelInstances = await ModelInstance.find({});
|
|
125
|
+
const modelInstanceFields = (
|
|
126
|
+
await Promise.all(
|
|
127
|
+
modelInstances.map(async (instance) => {
|
|
128
|
+
const model = await Model.findOne({ id: instance.model_id });
|
|
129
|
+
if (!model) {
|
|
130
|
+
req.flash(
|
|
131
|
+
"warning",
|
|
132
|
+
req.__(`Model with '${instance.model_id}' not found`)
|
|
133
|
+
);
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
const mTable = await Table.findOne({ id: model.table_id });
|
|
137
|
+
if (!mTable) {
|
|
138
|
+
req.flash(
|
|
139
|
+
"warning",
|
|
140
|
+
req.__(`Table of model '${model.name}' not found`)
|
|
141
|
+
);
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
label: `${instance.name} model instance, model: ${model.name}, table: ${mTable.name}`,
|
|
146
|
+
name: `model_instance.${instance.name}.${model.name}.${mTable.name}`,
|
|
147
|
+
type: "Bool",
|
|
148
|
+
};
|
|
149
|
+
})
|
|
150
|
+
)
|
|
151
|
+
).filter((f) => f);
|
|
152
|
+
|
|
101
153
|
const form = new Form({
|
|
102
154
|
action: "/packs/create",
|
|
103
155
|
fields: [
|
|
@@ -108,6 +160,14 @@ router.get(
|
|
|
108
160
|
...trigFields,
|
|
109
161
|
...roleFields,
|
|
110
162
|
...libFields,
|
|
163
|
+
...tagFields,
|
|
164
|
+
...modelFields,
|
|
165
|
+
...modelInstanceFields,
|
|
166
|
+
{
|
|
167
|
+
name: "with_event_logs",
|
|
168
|
+
label: req.__("Include Event Logs"),
|
|
169
|
+
type: "Bool",
|
|
170
|
+
},
|
|
111
171
|
],
|
|
112
172
|
});
|
|
113
173
|
res.sendWrap(req.__(`Create Pack`), {
|
|
@@ -140,7 +200,7 @@ router.post(
|
|
|
140
200
|
"/create",
|
|
141
201
|
isAdmin,
|
|
142
202
|
error_catcher(async (req, res) => {
|
|
143
|
-
|
|
203
|
+
const pack = {
|
|
144
204
|
tables: [],
|
|
145
205
|
views: [],
|
|
146
206
|
plugins: [],
|
|
@@ -148,9 +208,13 @@ router.post(
|
|
|
148
208
|
roles: [],
|
|
149
209
|
library: [],
|
|
150
210
|
triggers: [],
|
|
211
|
+
tags: [],
|
|
212
|
+
models: [],
|
|
213
|
+
model_instances: [],
|
|
214
|
+
event_logs: [],
|
|
151
215
|
};
|
|
152
216
|
for (const k of Object.keys(req.body)) {
|
|
153
|
-
const [type, name] = k.split(".");
|
|
217
|
+
const [type, name, ...rest] = k.split(".");
|
|
154
218
|
switch (type) {
|
|
155
219
|
case "table":
|
|
156
220
|
pack.tables.push(await table_pack(name));
|
|
@@ -173,7 +237,32 @@ router.post(
|
|
|
173
237
|
case "trigger":
|
|
174
238
|
pack.triggers.push(await trigger_pack(name));
|
|
175
239
|
break;
|
|
176
|
-
|
|
240
|
+
case "tag":
|
|
241
|
+
pack.tags.push(await tag_pack(name));
|
|
242
|
+
break;
|
|
243
|
+
case "model": {
|
|
244
|
+
const table = rest[0];
|
|
245
|
+
if (!table) throw new Error(`Table for model '${name}' not found`);
|
|
246
|
+
pack.models.push(await model_pack(name, table));
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
case "model_instance": {
|
|
250
|
+
const model = rest[0];
|
|
251
|
+
if (!model)
|
|
252
|
+
throw new Error(`Model of Model Instance '${name}' not found`);
|
|
253
|
+
const table = rest[1];
|
|
254
|
+
if (!table) throw new Error(`Table of Model '${model}' not found`);
|
|
255
|
+
pack.model_instances.push(
|
|
256
|
+
await model_instance_pack(name, model, table)
|
|
257
|
+
);
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
case "with_event_logs":
|
|
261
|
+
const logs = await EventLog.find({});
|
|
262
|
+
pack.event_logs = await Promise.all(
|
|
263
|
+
logs.map(async (l) => await event_log_pack(l))
|
|
264
|
+
);
|
|
265
|
+
break;
|
|
177
266
|
default:
|
|
178
267
|
break;
|
|
179
268
|
}
|
|
@@ -217,11 +306,33 @@ const install_pack_form = (req) =>
|
|
|
217
306
|
action: "/packs/install",
|
|
218
307
|
submitLabel: req.__("Install"),
|
|
219
308
|
fields: [
|
|
309
|
+
{
|
|
310
|
+
name: "source",
|
|
311
|
+
label: req.__("Source"),
|
|
312
|
+
type: "String",
|
|
313
|
+
attributes: {
|
|
314
|
+
options: [
|
|
315
|
+
{ label: "from text", name: "from_text" },
|
|
316
|
+
{ label: "from file", name: "from_file" },
|
|
317
|
+
],
|
|
318
|
+
},
|
|
319
|
+
default: "from_text",
|
|
320
|
+
required: true,
|
|
321
|
+
},
|
|
220
322
|
{
|
|
221
323
|
name: "pack",
|
|
222
324
|
label: req.__("Pack"),
|
|
223
325
|
type: "String",
|
|
224
326
|
fieldview: "textarea",
|
|
327
|
+
showIf: { source: "from_text" },
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
name: "pack_file",
|
|
331
|
+
label: req.__("Pack file"),
|
|
332
|
+
class: "form-control",
|
|
333
|
+
type: "File",
|
|
334
|
+
sublabel: req.__("Upload a pack file"),
|
|
335
|
+
showIf: { source: "from_file" },
|
|
225
336
|
},
|
|
226
337
|
],
|
|
227
338
|
});
|
|
@@ -267,8 +378,22 @@ router.post(
|
|
|
267
378
|
isAdmin,
|
|
268
379
|
error_catcher(async (req, res) => {
|
|
269
380
|
var pack, error;
|
|
381
|
+
const source = req.body.source || "from_text";
|
|
270
382
|
try {
|
|
271
|
-
|
|
383
|
+
switch (source) {
|
|
384
|
+
case "from_text":
|
|
385
|
+
pack = JSON.parse(req.body.pack);
|
|
386
|
+
break;
|
|
387
|
+
case "from_file":
|
|
388
|
+
if (req.files?.pack_file?.tempFilePath)
|
|
389
|
+
pack = JSON.parse(
|
|
390
|
+
fs.readFileSync(req.files?.pack_file?.tempFilePath)
|
|
391
|
+
);
|
|
392
|
+
else throw new Error(req.__("No file uploaded"));
|
|
393
|
+
break;
|
|
394
|
+
default:
|
|
395
|
+
throw new Error(req.__("Invalid source"));
|
|
396
|
+
}
|
|
272
397
|
} catch (e) {
|
|
273
398
|
error = e.message;
|
|
274
399
|
}
|