@saltcorn/server 0.9.0-beta.1 → 0.9.0-beta.3
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 +49 -3
- package/auth/routes.js +16 -20
- package/help/Extra state formula.tmd +62 -0
- package/help/Field views.tmd +22 -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 +11 -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 +23 -2
- package/public/saltcorn.js +15 -1
- package/routes/admin.js +7 -4
- package/routes/menu.js +1 -1
- package/routes/plugins.js +186 -36
- package/routes/tables.js +4 -3
- package/routes/viewedit.js +8 -1
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.3",
|
|
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.3",
|
|
10
|
+
"@saltcorn/builder": "0.9.0-beta.3",
|
|
11
|
+
"@saltcorn/data": "0.9.0-beta.3",
|
|
12
|
+
"@saltcorn/admin-models": "0.9.0-beta.3",
|
|
13
|
+
"@saltcorn/filemanager": "0.9.0-beta.3",
|
|
14
|
+
"@saltcorn/markup": "0.9.0-beta.3",
|
|
15
|
+
"@saltcorn/sbadmin2": "0.9.0-beta.3",
|
|
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",
|
|
@@ -78,6 +78,15 @@ function apply_showif() {
|
|
|
78
78
|
console.error(e);
|
|
79
79
|
}
|
|
80
80
|
});
|
|
81
|
+
$("[data-dyn-href]").each(function (ix, element) {
|
|
82
|
+
const e = $(element);
|
|
83
|
+
const rec = get_form_record(e);
|
|
84
|
+
const href = new Function(
|
|
85
|
+
`{${Object.keys(rec).join(",")}}`,
|
|
86
|
+
"return " + e.attr("data-dyn-href")
|
|
87
|
+
)(rec);
|
|
88
|
+
e.attr("href", href);
|
|
89
|
+
});
|
|
81
90
|
$("[data-calc-options]").each(function (ix, element) {
|
|
82
91
|
var e = $(element);
|
|
83
92
|
var data = JSON.parse(decodeURIComponent(e.attr("data-calc-options")));
|
|
@@ -948,14 +957,25 @@ function emptyAlerts() {
|
|
|
948
957
|
$("#toasts-area").html("");
|
|
949
958
|
}
|
|
950
959
|
|
|
951
|
-
function press_store_button(clicked) {
|
|
960
|
+
function press_store_button(clicked, keepOld) {
|
|
952
961
|
let btn = clicked;
|
|
953
962
|
if ($(clicked).is("form")) btn = $(clicked).find("button[type=submit]");
|
|
954
|
-
|
|
963
|
+
if (keepOld) {
|
|
964
|
+
const oldText = $(btn).html();
|
|
965
|
+
$(btn).data("old-text", oldText);
|
|
966
|
+
}
|
|
955
967
|
const width = $(btn).width();
|
|
956
968
|
$(btn).html('<i class="fas fa-spinner fa-spin"></i>').width(width);
|
|
957
969
|
}
|
|
958
970
|
|
|
971
|
+
function restore_old_button(btnId) {
|
|
972
|
+
const btn = $(`#${btnId}`);
|
|
973
|
+
const oldText = $(btn).data("old-text");
|
|
974
|
+
btn.html(oldText);
|
|
975
|
+
btn.css({ width: "" });
|
|
976
|
+
btn.removeData("old-text");
|
|
977
|
+
}
|
|
978
|
+
|
|
959
979
|
function common_done(res, viewname, isWeb = true) {
|
|
960
980
|
const handle = (element, fn) => {
|
|
961
981
|
if (Array.isArray(element)) for (const current of element) fn(current);
|
|
@@ -994,6 +1014,7 @@ function common_done(res, viewname, isWeb = true) {
|
|
|
994
1014
|
if (input.attr("type") === "checkbox")
|
|
995
1015
|
input.prop("checked", res.set_fields[k]);
|
|
996
1016
|
else input.val(res.set_fields[k]);
|
|
1017
|
+
input.trigger("set_form_field");
|
|
997
1018
|
});
|
|
998
1019
|
}
|
|
999
1020
|
if (res.goto && !isWeb)
|
package/public/saltcorn.js
CHANGED
|
@@ -340,7 +340,20 @@ 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",
|
|
343
355
|
});
|
|
356
|
+
restore_old_button(btnId);
|
|
344
357
|
}
|
|
345
358
|
|
|
346
359
|
function saveAndContinue(e, k) {
|
|
@@ -410,7 +423,8 @@ function updateMatchingRows(e, viewname) {
|
|
|
410
423
|
}
|
|
411
424
|
}
|
|
412
425
|
|
|
413
|
-
function applyViewConfig(e, url, k) {
|
|
426
|
+
function applyViewConfig(e, url, k, event) {
|
|
427
|
+
if (event && event.target && event.target.id === "myEditor_icon") return;
|
|
414
428
|
var form = $(e).closest("form");
|
|
415
429
|
var form_data = form.serializeArray();
|
|
416
430
|
const cfg = {};
|
package/routes/admin.js
CHANGED
|
@@ -1545,7 +1545,7 @@ router.get(
|
|
|
1545
1545
|
input({
|
|
1546
1546
|
type: "hidden",
|
|
1547
1547
|
name: "entryPointType",
|
|
1548
|
-
value: "view",
|
|
1548
|
+
value: builderSettings.entryPointType || "view",
|
|
1549
1549
|
id: "entryPointTypeID",
|
|
1550
1550
|
}),
|
|
1551
1551
|
div(
|
|
@@ -1578,7 +1578,8 @@ router.get(
|
|
|
1578
1578
|
div(
|
|
1579
1579
|
{
|
|
1580
1580
|
class: `nav-link ${
|
|
1581
|
-
!builderSettings.entryPointType ||
|
|
1581
|
+
!builderSettings.entryPointType ||
|
|
1582
|
+
builderSettings.entryPointType === "view"
|
|
1582
1583
|
? "active"
|
|
1583
1584
|
: ""
|
|
1584
1585
|
}`,
|
|
@@ -1613,7 +1614,8 @@ router.get(
|
|
|
1613
1614
|
? "d-none"
|
|
1614
1615
|
: ""
|
|
1615
1616
|
}`,
|
|
1616
|
-
...(!builderSettings.entryPointType ||
|
|
1617
|
+
...(!builderSettings.entryPointType ||
|
|
1618
|
+
builderSettings.entryPointType === "view"
|
|
1617
1619
|
? { name: "entryPoint" }
|
|
1618
1620
|
: {}),
|
|
1619
1621
|
id: "viewInputID",
|
|
@@ -1636,7 +1638,8 @@ router.get(
|
|
|
1636
1638
|
select(
|
|
1637
1639
|
{
|
|
1638
1640
|
class: `form-select ${
|
|
1639
|
-
!builderSettings.entryPointType ||
|
|
1641
|
+
!builderSettings.entryPointType ||
|
|
1642
|
+
builderSettings.entryPointType === "view"
|
|
1640
1643
|
? "d-none"
|
|
1641
1644
|
: ""
|
|
1642
1645
|
}`,
|
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/plugins.js
CHANGED
|
@@ -7,13 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
const Router = require("express-promise-router");
|
|
9
9
|
const { isAdmin, error_catcher } = require("./utils.js");
|
|
10
|
-
const {
|
|
11
|
-
mkTable,
|
|
12
|
-
renderForm,
|
|
13
|
-
link,
|
|
14
|
-
post_btn,
|
|
15
|
-
post_delete_btn,
|
|
16
|
-
} = require("@saltcorn/markup");
|
|
10
|
+
const { renderForm, link, post_btn } = require("@saltcorn/markup");
|
|
17
11
|
const {
|
|
18
12
|
getState,
|
|
19
13
|
restart_tenant,
|
|
@@ -26,7 +20,6 @@ const { fetch_available_packs } = require("@saltcorn/admin-models/models/pack");
|
|
|
26
20
|
const {
|
|
27
21
|
upgrade_all_tenants_plugins,
|
|
28
22
|
} = require("@saltcorn/admin-models/models/tenant");
|
|
29
|
-
const { getConfig, setConfig } = require("@saltcorn/data/models/config");
|
|
30
23
|
const db = require("@saltcorn/data/db");
|
|
31
24
|
const {
|
|
32
25
|
plugin_types_info_card,
|
|
@@ -37,7 +30,6 @@ const {
|
|
|
37
30
|
const load_plugins = require("../load_plugins");
|
|
38
31
|
const {
|
|
39
32
|
h5,
|
|
40
|
-
nbsp,
|
|
41
33
|
a,
|
|
42
34
|
div,
|
|
43
35
|
span,
|
|
@@ -50,7 +42,11 @@ const {
|
|
|
50
42
|
th,
|
|
51
43
|
td,
|
|
52
44
|
p,
|
|
53
|
-
|
|
45
|
+
form,
|
|
46
|
+
select,
|
|
47
|
+
option,
|
|
48
|
+
input,
|
|
49
|
+
label,
|
|
54
50
|
text,
|
|
55
51
|
} = require("@saltcorn/markup/tags");
|
|
56
52
|
const { search_bar } = require("@saltcorn/markup/helpers");
|
|
@@ -58,8 +54,9 @@ const fs = require("fs");
|
|
|
58
54
|
const path = require("path");
|
|
59
55
|
const { get_latest_npm_version } = require("@saltcorn/data/models/config");
|
|
60
56
|
const { flash_restart } = require("../markup/admin.js");
|
|
61
|
-
const { sleep } = require("@saltcorn/data/utils");
|
|
57
|
+
const { sleep, removeNonWordChars } = require("@saltcorn/data/utils");
|
|
62
58
|
const { loadAllPlugins } = require("../load_plugins");
|
|
59
|
+
const npmFetch = require("npm-registry-fetch");
|
|
63
60
|
|
|
64
61
|
/**
|
|
65
62
|
* @type {object}
|
|
@@ -177,6 +174,7 @@ const get_store_items = async () => {
|
|
|
177
174
|
has_theme: plugin.has_theme,
|
|
178
175
|
has_auth: plugin.has_auth,
|
|
179
176
|
unsafe: plugin.unsafe,
|
|
177
|
+
source: plugin.source,
|
|
180
178
|
}))
|
|
181
179
|
.filter((p) => !p.unsafe || isRoot || tenants_unsafe_plugins);
|
|
182
180
|
const local_logins = installed_plugins
|
|
@@ -290,15 +288,19 @@ const store_item_html = (req) => (item) => ({
|
|
|
290
288
|
div(
|
|
291
289
|
!item.installed &&
|
|
292
290
|
item.plugin &&
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
291
|
+
div(
|
|
292
|
+
{ class: "me-2 d-inline" },
|
|
293
|
+
post_btn(
|
|
294
|
+
`/plugins/install/${encodeURIComponent(item.name)}`,
|
|
295
|
+
req.__("Install"),
|
|
296
|
+
req.csrfToken(),
|
|
297
|
+
{
|
|
298
|
+
klass: "store-install",
|
|
299
|
+
small: true,
|
|
300
|
+
onClick: "press_store_button(this)",
|
|
301
|
+
formClass: "d-inline",
|
|
302
|
+
}
|
|
303
|
+
)
|
|
302
304
|
),
|
|
303
305
|
!item.installed &&
|
|
304
306
|
item.pack &&
|
|
@@ -526,8 +528,12 @@ const plugin_store_html = (items, req) => {
|
|
|
526
528
|
contents: div(
|
|
527
529
|
{ class: "d-flex justify-content-between" },
|
|
528
530
|
storeNavPills(req),
|
|
529
|
-
div(
|
|
530
|
-
|
|
531
|
+
div(
|
|
532
|
+
search_bar("q", req.query.q || "", {
|
|
533
|
+
placeHolder: req.__("Search for..."),
|
|
534
|
+
stateField: "q",
|
|
535
|
+
})
|
|
536
|
+
),
|
|
531
537
|
div(store_actions_dropdown(req))
|
|
532
538
|
),
|
|
533
539
|
},
|
|
@@ -558,6 +564,105 @@ router.get(
|
|
|
558
564
|
})
|
|
559
565
|
);
|
|
560
566
|
|
|
567
|
+
router.get(
|
|
568
|
+
"/versions_dialog/:name",
|
|
569
|
+
isAdmin,
|
|
570
|
+
error_catcher(async (req, res) => {
|
|
571
|
+
const { name } = req.params;
|
|
572
|
+
const withoutOrg = name.replace(/^@saltcorn\//, "");
|
|
573
|
+
const plugin = await Plugin.store_by_name(decodeURIComponent(withoutOrg));
|
|
574
|
+
if (!plugin) {
|
|
575
|
+
getState().log(
|
|
576
|
+
2,
|
|
577
|
+
`GET /versions_dialog${withoutOrg}: '${withoutOrg}' not found`
|
|
578
|
+
);
|
|
579
|
+
return res
|
|
580
|
+
.status(404)
|
|
581
|
+
.json({ error: req.__("Module '%s' not found", withoutOrg) });
|
|
582
|
+
} else {
|
|
583
|
+
try {
|
|
584
|
+
const pkgInfo = await npmFetch.json(
|
|
585
|
+
`https://registry.npmjs.org/${plugin.location}`
|
|
586
|
+
);
|
|
587
|
+
if (!pkgInfo?.versions)
|
|
588
|
+
throw new Error(req.__("Unable to fetch versions"));
|
|
589
|
+
res.set("Page-Title", req.__("%s versions", text(withoutOrg)));
|
|
590
|
+
const versions = Object.keys(pkgInfo.versions);
|
|
591
|
+
if (versions.length === 0) throw new Error(req.__("No versions found"));
|
|
592
|
+
let selected = null;
|
|
593
|
+
if (getState().plugins[plugin.name]) {
|
|
594
|
+
const mod = await load_plugins.requirePlugin(plugin);
|
|
595
|
+
if (mod) selected = mod.version;
|
|
596
|
+
}
|
|
597
|
+
if (!selected) selected = versions[versions.length - 1];
|
|
598
|
+
return res.send(
|
|
599
|
+
form(
|
|
600
|
+
{
|
|
601
|
+
action: `/plugins/install/${encodeURIComponent(name)}`,
|
|
602
|
+
method: "post",
|
|
603
|
+
},
|
|
604
|
+
input({ type: "hidden", name: "_csrf", value: req.csrfToken() }),
|
|
605
|
+
div(
|
|
606
|
+
{ class: "form-group" },
|
|
607
|
+
label(
|
|
608
|
+
{
|
|
609
|
+
for: "version_select",
|
|
610
|
+
class: "form-label fw-bold",
|
|
611
|
+
},
|
|
612
|
+
req.__("Version")
|
|
613
|
+
),
|
|
614
|
+
select(
|
|
615
|
+
{
|
|
616
|
+
id: "version_select",
|
|
617
|
+
class: "form-control form-select",
|
|
618
|
+
name: "version",
|
|
619
|
+
},
|
|
620
|
+
versions.map((version) =>
|
|
621
|
+
option({
|
|
622
|
+
id: `${version}_opt`,
|
|
623
|
+
value: version,
|
|
624
|
+
label: version,
|
|
625
|
+
selected: version === selected,
|
|
626
|
+
})
|
|
627
|
+
)
|
|
628
|
+
)
|
|
629
|
+
),
|
|
630
|
+
div(
|
|
631
|
+
{ class: "d-flex justify-content-end" },
|
|
632
|
+
button(
|
|
633
|
+
{
|
|
634
|
+
type: "button",
|
|
635
|
+
class: "btn btn-secondary me-2",
|
|
636
|
+
"data-bs-dismiss": "modal",
|
|
637
|
+
},
|
|
638
|
+
req.__("Close")
|
|
639
|
+
),
|
|
640
|
+
button(
|
|
641
|
+
{
|
|
642
|
+
type: "submit",
|
|
643
|
+
class: "btn btn-primary",
|
|
644
|
+
onClick: "press_store_button(this)",
|
|
645
|
+
},
|
|
646
|
+
req.__("Install")
|
|
647
|
+
)
|
|
648
|
+
)
|
|
649
|
+
)
|
|
650
|
+
);
|
|
651
|
+
} catch (error) {
|
|
652
|
+
getState().log(
|
|
653
|
+
2,
|
|
654
|
+
`GET /versions_dialog${withoutOrg}: ${
|
|
655
|
+
error.message || "unknown error"
|
|
656
|
+
}`
|
|
657
|
+
);
|
|
658
|
+
return res
|
|
659
|
+
.status(500)
|
|
660
|
+
.json({ error: error.message || "unknown error" });
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
})
|
|
664
|
+
);
|
|
665
|
+
|
|
561
666
|
/**
|
|
562
667
|
* @name get/configure/:name
|
|
563
668
|
* @function
|
|
@@ -810,7 +915,12 @@ router.get(
|
|
|
810
915
|
isAdmin,
|
|
811
916
|
error_catcher(async (req, res) => {
|
|
812
917
|
const { name } = req.params;
|
|
813
|
-
|
|
918
|
+
let plugin_db = await Plugin.findOne({ name });
|
|
919
|
+
if (!plugin_db) {
|
|
920
|
+
req.flash("warning", req.__("Module not found"));
|
|
921
|
+
res.redirect("/plugins");
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
814
924
|
const mod = await load_plugins.requirePlugin(plugin_db);
|
|
815
925
|
const store_items = await get_store_items();
|
|
816
926
|
const store_item = store_items.find((item) => item.name === name);
|
|
@@ -821,27 +931,49 @@ router.get(
|
|
|
821
931
|
update_permitted &&
|
|
822
932
|
(await get_latest_npm_version(plugin_db.location, 1000));
|
|
823
933
|
const can_update = update_permitted && latest && mod.version !== latest;
|
|
934
|
+
const can_select_version = update_permitted && plugin_db.source === "npm";
|
|
824
935
|
let pkgjson;
|
|
825
936
|
if (mod.location && fs.existsSync(path.join(mod.location, "package.json")))
|
|
826
937
|
pkgjson = require(path.join(mod.location, "package.json"));
|
|
827
|
-
|
|
828
|
-
if (!plugin_db) {
|
|
829
|
-
req.flash("warning", req.__("Module not found"));
|
|
830
|
-
res.redirect("/plugins");
|
|
831
|
-
return;
|
|
832
|
-
}
|
|
938
|
+
const domId = `${removeNonWordChars(mod.name)}_store_version_btn`;
|
|
833
939
|
const infoTable = table(
|
|
834
940
|
tbody(
|
|
835
941
|
tr(th(req.__("Package name")), td(mod.name)),
|
|
836
|
-
tr(
|
|
942
|
+
tr(
|
|
943
|
+
th(req.__("Package version")),
|
|
944
|
+
td(
|
|
945
|
+
span(
|
|
946
|
+
{ style: "display: inline-block; min-width: 2.9rem;" },
|
|
947
|
+
mod.version
|
|
948
|
+
),
|
|
949
|
+
can_select_version
|
|
950
|
+
? a(
|
|
951
|
+
{
|
|
952
|
+
id: domId,
|
|
953
|
+
class: "store-install btn btn-sm btn-primary ms-2",
|
|
954
|
+
onClick: "press_store_button(this, true)",
|
|
955
|
+
href: `javascript:ajax_modal('/plugins/versions_dialog/${encodeURIComponent(
|
|
956
|
+
encodeURIComponent(plugin_db.name)
|
|
957
|
+
)}', { onOpen: () => { restore_old_button('${domId}'); }, onError: (res) => { selectVersionError(res, '${domId}') } });`,
|
|
958
|
+
},
|
|
959
|
+
req.__("install a different version")
|
|
960
|
+
)
|
|
961
|
+
: ""
|
|
962
|
+
)
|
|
963
|
+
),
|
|
837
964
|
tr(
|
|
838
965
|
th(req.__("Latest version")),
|
|
839
966
|
td(
|
|
840
|
-
|
|
967
|
+
span(
|
|
968
|
+
{ style: "display: inline-block; min-width: 2.9rem;" },
|
|
969
|
+
latest || ""
|
|
970
|
+
),
|
|
841
971
|
can_update
|
|
842
972
|
? a(
|
|
843
973
|
{
|
|
844
|
-
href: `/plugins/upgrade-plugin/${
|
|
974
|
+
href: `/plugins/upgrade-plugin/${encodeURIComponent(
|
|
975
|
+
plugin_db.name
|
|
976
|
+
)}`,
|
|
845
977
|
class: "btn btn-primary btn-sm ms-2",
|
|
846
978
|
},
|
|
847
979
|
req.__("Upgrade")
|
|
@@ -979,7 +1111,7 @@ router.get(
|
|
|
979
1111
|
await plugin.upgrade_version((p, f) => load_plugins.loadPlugin(p, f));
|
|
980
1112
|
req.flash("success", req.__(`Module up-to-date`));
|
|
981
1113
|
|
|
982
|
-
res.redirect(`/plugins/info/${plugin.name}`);
|
|
1114
|
+
res.redirect(`/plugins/info/${encodeURIComponent(plugin.name)}`);
|
|
983
1115
|
})
|
|
984
1116
|
);
|
|
985
1117
|
|
|
@@ -1068,11 +1200,25 @@ router.post(
|
|
|
1068
1200
|
isAdmin,
|
|
1069
1201
|
error_catcher(async (req, res) => {
|
|
1070
1202
|
const { name } = req.params;
|
|
1203
|
+
const { version } = req.body;
|
|
1071
1204
|
const tenants_unsafe_plugins = getRootState().getConfig(
|
|
1072
1205
|
"tenants_unsafe_plugins",
|
|
1073
1206
|
false
|
|
1074
1207
|
);
|
|
1075
|
-
|
|
1208
|
+
// when a version is specified, either update the db row or use the plugin from the store
|
|
1209
|
+
// when no version is specified, allways use the plugin from the store
|
|
1210
|
+
let plugin = null;
|
|
1211
|
+
if (version) {
|
|
1212
|
+
plugin = await Plugin.findOne({ name: name });
|
|
1213
|
+
if (plugin) plugin.version = version;
|
|
1214
|
+
}
|
|
1215
|
+
if (!plugin) {
|
|
1216
|
+
plugin = await Plugin.store_by_name(decodeURIComponent(name));
|
|
1217
|
+
if (plugin) {
|
|
1218
|
+
delete plugin.id;
|
|
1219
|
+
if (version) plugin.version = version;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1076
1222
|
if (!plugin) {
|
|
1077
1223
|
req.flash(
|
|
1078
1224
|
"error",
|
|
@@ -1081,6 +1227,11 @@ router.post(
|
|
|
1081
1227
|
res.redirect(`/plugins`);
|
|
1082
1228
|
return;
|
|
1083
1229
|
}
|
|
1230
|
+
let forceReInstall =
|
|
1231
|
+
version !== undefined ||
|
|
1232
|
+
(plugin.source === "npm" && plugin.version === "latest");
|
|
1233
|
+
if (version) plugin.version = version;
|
|
1234
|
+
|
|
1084
1235
|
const isRoot = db.getTenantSchema() === db.connectObj.default_schema;
|
|
1085
1236
|
if (!isRoot && plugin.unsafe && !tenants_unsafe_plugins) {
|
|
1086
1237
|
req.flash(
|
|
@@ -1090,8 +1241,7 @@ router.post(
|
|
|
1090
1241
|
res.redirect(`/plugins`);
|
|
1091
1242
|
return;
|
|
1092
1243
|
}
|
|
1093
|
-
|
|
1094
|
-
await load_plugins.loadAndSaveNewPlugin(plugin);
|
|
1244
|
+
await load_plugins.loadAndSaveNewPlugin(plugin, forceReInstall);
|
|
1095
1245
|
const plugin_module = getState().plugins[name];
|
|
1096
1246
|
if (plugin_module && plugin_module.configuration_workflow) {
|
|
1097
1247
|
const plugin_db = await Plugin.findOne({ name });
|
package/routes/tables.js
CHANGED
|
@@ -163,7 +163,7 @@ const tableForm = async (table, req) => {
|
|
|
163
163
|
{
|
|
164
164
|
label: req.__("Version history"),
|
|
165
165
|
sublabel: req.__(
|
|
166
|
-
"
|
|
166
|
+
"Track table data changes over time"
|
|
167
167
|
),
|
|
168
168
|
name: "versioned",
|
|
169
169
|
type: "Bool",
|
|
@@ -235,7 +235,8 @@ router.get(
|
|
|
235
235
|
name: "provider_name",
|
|
236
236
|
input_type: "select",
|
|
237
237
|
options: [
|
|
238
|
-
|
|
238
|
+
// Due to packages/saltcorn-markup/helpers.ts#L45 (select_options replaces label if o.value === "")
|
|
239
|
+
{label:req.__("Database table"), value:'-'},
|
|
239
240
|
...table_provider_names,
|
|
240
241
|
],
|
|
241
242
|
required: true,
|
|
@@ -1066,7 +1067,7 @@ router.post(
|
|
|
1066
1067
|
res.redirect(`/table/new`);
|
|
1067
1068
|
} else if (
|
|
1068
1069
|
rest.provider_name &&
|
|
1069
|
-
rest.provider_name !== "
|
|
1070
|
+
rest.provider_name !== "-"
|
|
1070
1071
|
) {
|
|
1071
1072
|
const table = await Table.create(name, rest);
|
|
1072
1073
|
res.redirect(`/table/provider-cfg/${table.id}`);
|
package/routes/viewedit.js
CHANGED
|
@@ -147,6 +147,10 @@ const viewForm = async (req, tableOptions, roles, pages, values) => {
|
|
|
147
147
|
sublabel: req.__(
|
|
148
148
|
"The view pattern sets the foundation of how the view relates to the table and the behaviour of the view"
|
|
149
149
|
),
|
|
150
|
+
help: {
|
|
151
|
+
topic: "View patterns",
|
|
152
|
+
context: {},
|
|
153
|
+
},
|
|
150
154
|
options: Object.keys(getState().viewtemplates),
|
|
151
155
|
attributes: {
|
|
152
156
|
explainers: mapObjectValues(
|
|
@@ -209,7 +213,7 @@ const viewForm = async (req, tableOptions, roles, pages, values) => {
|
|
|
209
213
|
}),
|
|
210
214
|
{
|
|
211
215
|
name: "popup_width",
|
|
212
|
-
label: req.__("
|
|
216
|
+
label: req.__("Popup width"),
|
|
213
217
|
type: "Integer",
|
|
214
218
|
tab: "Popup settings",
|
|
215
219
|
parent_field: "attributes",
|
|
@@ -232,6 +236,9 @@ const viewForm = async (req, tableOptions, roles, pages, values) => {
|
|
|
232
236
|
label: req.__("Save indicator"),
|
|
233
237
|
type: "Bool",
|
|
234
238
|
parent_field: "attributes",
|
|
239
|
+
sublabel: req.__(
|
|
240
|
+
"Show an icon in the title bar to indicate when form data is being saved"
|
|
241
|
+
),
|
|
235
242
|
tab: "Popup settings",
|
|
236
243
|
},
|
|
237
244
|
{
|