@saltcorn/server 0.8.8-beta.0 → 0.8.8-beta.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/auth/routes.js +6 -5
- package/load_plugins.js +8 -1
- package/locales/en.json +22 -1
- package/locales/ru.json +148 -100
- package/package.json +8 -8
- package/public/saltcorn.js +44 -1
- package/routes/actions.js +4 -3
- package/routes/admin.js +98 -2
- package/routes/fields.js +5 -9
- package/routes/menu.js +16 -9
- package/routes/scapi.js +1 -1
- package/routes/sync.js +293 -57
- package/routes/tables.js +19 -1
- package/tests/plugin_install.test.js +114 -0
- package/tests/plugins.test.js +2 -102
- package/tests/sync.test.js +451 -75
- package/tests/view.test.js +2 -0
- package/tests/viewedit.test.js +2 -0
- package/wrapper.js +6 -4
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/server",
|
|
3
|
-
"version": "0.8.8-beta.
|
|
3
|
+
"version": "0.8.8-beta.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.8.8-beta.
|
|
10
|
-
"@saltcorn/builder": "0.8.8-beta.
|
|
11
|
-
"@saltcorn/data": "0.8.8-beta.
|
|
12
|
-
"@saltcorn/admin-models": "0.8.8-beta.
|
|
13
|
-
"@saltcorn/filemanager": "0.8.8-beta.
|
|
14
|
-
"@saltcorn/markup": "0.8.8-beta.
|
|
15
|
-
"@saltcorn/sbadmin2": "0.8.8-beta.
|
|
9
|
+
"@saltcorn/base-plugin": "0.8.8-beta.2",
|
|
10
|
+
"@saltcorn/builder": "0.8.8-beta.2",
|
|
11
|
+
"@saltcorn/data": "0.8.8-beta.2",
|
|
12
|
+
"@saltcorn/admin-models": "0.8.8-beta.2",
|
|
13
|
+
"@saltcorn/filemanager": "0.8.8-beta.2",
|
|
14
|
+
"@saltcorn/markup": "0.8.8-beta.2",
|
|
15
|
+
"@saltcorn/sbadmin2": "0.8.8-beta.2",
|
|
16
16
|
"@socket.io/cluster-adapter": "^0.2.1",
|
|
17
17
|
"@socket.io/sticky": "^1.0.1",
|
|
18
18
|
"adm-zip": "0.5.10",
|
package/public/saltcorn.js
CHANGED
|
@@ -282,6 +282,10 @@ function ensure_modal_exists_and_closed() {
|
|
|
282
282
|
</div>
|
|
283
283
|
</div>`);
|
|
284
284
|
} else if ($("#scmodal").hasClass("show")) {
|
|
285
|
+
// remove reload handler added by edit, for when we have popup link
|
|
286
|
+
// in autosave edit in popup
|
|
287
|
+
$("#scmodal").off("hidden.bs.modal");
|
|
288
|
+
|
|
285
289
|
close_saltcorn_modal();
|
|
286
290
|
}
|
|
287
291
|
}
|
|
@@ -319,7 +323,9 @@ function ajax_modal(url, opts = {}) {
|
|
|
319
323
|
if (title) $("#scmodal .modal-title").html(decodeURIComponent(title));
|
|
320
324
|
$("#scmodal .modal-body").html(res);
|
|
321
325
|
$("#scmodal").prop("data-modal-state", url);
|
|
322
|
-
new bootstrap.Modal($("#scmodal")
|
|
326
|
+
new bootstrap.Modal($("#scmodal"), {
|
|
327
|
+
focus: false,
|
|
328
|
+
}).show();
|
|
323
329
|
initialize_page();
|
|
324
330
|
(opts.onOpen || function () {})(res);
|
|
325
331
|
$("#scmodal").on("hidden.bs.modal", function (e) {
|
|
@@ -669,6 +675,9 @@ function build_mobile_app(button) {
|
|
|
669
675
|
form.serializeArray().forEach((item) => {
|
|
670
676
|
params[item.name] = item.value;
|
|
671
677
|
});
|
|
678
|
+
params.synchedTables = Array.from($("#synched-tbls-select-id")[0].options)
|
|
679
|
+
.filter((option) => !option.hidden)
|
|
680
|
+
.map((option) => option.value);
|
|
672
681
|
ajax_post("/admin/build-mobile-app", {
|
|
673
682
|
data: params,
|
|
674
683
|
success: (data) => {
|
|
@@ -682,6 +691,40 @@ function build_mobile_app(button) {
|
|
|
682
691
|
});
|
|
683
692
|
}
|
|
684
693
|
|
|
694
|
+
function move_to_synched() {
|
|
695
|
+
const opts = $("#unsynched-tbls-select-id");
|
|
696
|
+
$("#synched-tbls-select-id").removeAttr("selected");
|
|
697
|
+
for (const selected of opts.val()) {
|
|
698
|
+
const jUnsOpt = $(`[id='${selected}_unsynched_opt']`);
|
|
699
|
+
jUnsOpt.attr("hidden", "true");
|
|
700
|
+
jUnsOpt.removeAttr("selected");
|
|
701
|
+
const jSynOpt = $(`[id='${selected}_synched_opt']`);
|
|
702
|
+
jSynOpt.removeAttr("hidden");
|
|
703
|
+
jSynOpt.removeAttr("selected");
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function move_to_unsynched() {
|
|
708
|
+
const opts = $("#synched-tbls-select-id");
|
|
709
|
+
$("#unsynched-tbls-select-id").removeAttr("selected");
|
|
710
|
+
for (const selected of opts.val()) {
|
|
711
|
+
const jSynOpt = $(`[id='${selected}_synched_opt']`);
|
|
712
|
+
jSynOpt.attr("hidden", "true");
|
|
713
|
+
jSynOpt.removeAttr("selected");
|
|
714
|
+
const jUnsOpt = $(`[id='${selected}_unsynched_opt']`);
|
|
715
|
+
jUnsOpt.removeAttr("hidden");
|
|
716
|
+
jUnsOpt.removeAttr("selected");
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
function toggle_tbl_sync() {
|
|
721
|
+
if ($("#offlineModeBoxId")[0].checked === true) {
|
|
722
|
+
$("#tblSyncSelectorId").attr("hidden", false);
|
|
723
|
+
} else {
|
|
724
|
+
$("#tblSyncSelectorId").attr("hidden", true);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
685
728
|
function join_field_clicked(e, fieldPath) {
|
|
686
729
|
$("#inputjoin_field").val(fieldPath);
|
|
687
730
|
apply_showif();
|
package/routes/actions.js
CHANGED
|
@@ -211,6 +211,7 @@ const triggerForm = async (req, trigger) => {
|
|
|
211
211
|
name: "description",
|
|
212
212
|
label: req.__("Description"),
|
|
213
213
|
type: "String",
|
|
214
|
+
fieldview: "textarea",
|
|
214
215
|
sublabel: req.__(
|
|
215
216
|
"Description allows you to give more information about the action"
|
|
216
217
|
),
|
|
@@ -400,8 +401,8 @@ router.get(
|
|
|
400
401
|
});
|
|
401
402
|
form.values = trigger.configuration;
|
|
402
403
|
const events = Trigger.when_options;
|
|
403
|
-
const actions =
|
|
404
|
-
when_trigger: {
|
|
404
|
+
const actions = Trigger.find({
|
|
405
|
+
when_trigger: {or: ["API call", "Never"]},
|
|
405
406
|
});
|
|
406
407
|
const tables = (await Table.find({})).map((t) => ({
|
|
407
408
|
name: t.name,
|
|
@@ -430,7 +431,7 @@ router.get(
|
|
|
430
431
|
div(
|
|
431
432
|
button(
|
|
432
433
|
{ class: "btn btn-primary mt-2", id: "blocklySave" },
|
|
433
|
-
"Save"
|
|
434
|
+
req.__("Save")
|
|
434
435
|
),
|
|
435
436
|
renderForm(form, req.csrfToken()),
|
|
436
437
|
script(
|
package/routes/admin.js
CHANGED
|
@@ -1491,7 +1491,7 @@ router.get(
|
|
|
1491
1491
|
const images = (await File.find({ mime_super: "image" })).filter((image) =>
|
|
1492
1492
|
image.filename?.endsWith(".png")
|
|
1493
1493
|
);
|
|
1494
|
-
|
|
1494
|
+
const withSyncInfo = await Table.find({ has_sync_info: true });
|
|
1495
1495
|
send_admin_page({
|
|
1496
1496
|
res,
|
|
1497
1497
|
req,
|
|
@@ -1752,7 +1752,6 @@ router.get(
|
|
|
1752
1752
|
),
|
|
1753
1753
|
|
|
1754
1754
|
div(
|
|
1755
|
-
// TODO only for some tables?
|
|
1756
1755
|
{ class: "row pb-2" },
|
|
1757
1756
|
div(
|
|
1758
1757
|
{ class: "col-sm-4" },
|
|
@@ -1761,6 +1760,8 @@ router.get(
|
|
|
1761
1760
|
id: "offlineModeBoxId",
|
|
1762
1761
|
class: "form-check-input me-2",
|
|
1763
1762
|
name: "allowOfflineMode",
|
|
1763
|
+
value: "allowOfflineMode",
|
|
1764
|
+
onClick: "toggle_tbl_sync()",
|
|
1764
1765
|
checked: true,
|
|
1765
1766
|
}),
|
|
1766
1767
|
label(
|
|
@@ -1771,6 +1772,98 @@ router.get(
|
|
|
1771
1772
|
req.__("Allow offline mode")
|
|
1772
1773
|
)
|
|
1773
1774
|
)
|
|
1775
|
+
),
|
|
1776
|
+
div(
|
|
1777
|
+
{
|
|
1778
|
+
id: "tblSyncSelectorId",
|
|
1779
|
+
class: "row pb-2",
|
|
1780
|
+
},
|
|
1781
|
+
div(
|
|
1782
|
+
label(
|
|
1783
|
+
{ class: "form-label fw-bold" },
|
|
1784
|
+
req.__("Table Synchronization")
|
|
1785
|
+
)
|
|
1786
|
+
),
|
|
1787
|
+
div(
|
|
1788
|
+
{ class: "container" },
|
|
1789
|
+
div(
|
|
1790
|
+
{ class: "row" },
|
|
1791
|
+
div(
|
|
1792
|
+
{ class: "col-sm-4 text-center" },
|
|
1793
|
+
req.__("unsynched")
|
|
1794
|
+
),
|
|
1795
|
+
div({ class: "col-sm-1" }),
|
|
1796
|
+
div(
|
|
1797
|
+
{ class: "col-sm-4 text-center" },
|
|
1798
|
+
req.__("synched")
|
|
1799
|
+
)
|
|
1800
|
+
),
|
|
1801
|
+
div(
|
|
1802
|
+
{ class: "row" },
|
|
1803
|
+
div(
|
|
1804
|
+
{ class: "col-sm-4" },
|
|
1805
|
+
select(
|
|
1806
|
+
{
|
|
1807
|
+
id: "unsynched-tbls-select-id",
|
|
1808
|
+
class: "form-control form-select",
|
|
1809
|
+
multiple: true,
|
|
1810
|
+
},
|
|
1811
|
+
withSyncInfo.map((table) =>
|
|
1812
|
+
option({
|
|
1813
|
+
id: `${table.name}_unsynched_opt`,
|
|
1814
|
+
value: table.name,
|
|
1815
|
+
label: table.name,
|
|
1816
|
+
})
|
|
1817
|
+
)
|
|
1818
|
+
)
|
|
1819
|
+
),
|
|
1820
|
+
div(
|
|
1821
|
+
{ class: "col-sm-1 d-flex justify-content-center" },
|
|
1822
|
+
div(
|
|
1823
|
+
div(
|
|
1824
|
+
button(
|
|
1825
|
+
{
|
|
1826
|
+
id: "move-right-btn-id",
|
|
1827
|
+
type: "button",
|
|
1828
|
+
onClick: `move_to_synched()`,
|
|
1829
|
+
class: "btn btn-light pt-1 mb-1",
|
|
1830
|
+
},
|
|
1831
|
+
i({ class: "fas fa-arrow-right" })
|
|
1832
|
+
)
|
|
1833
|
+
),
|
|
1834
|
+
div(
|
|
1835
|
+
button(
|
|
1836
|
+
{
|
|
1837
|
+
id: "move-left-btn-id",
|
|
1838
|
+
type: "button",
|
|
1839
|
+
onClick: `move_to_unsynched()`,
|
|
1840
|
+
class: "btn btn-light pt-1",
|
|
1841
|
+
},
|
|
1842
|
+
i({ class: "fas fa-arrow-left" })
|
|
1843
|
+
)
|
|
1844
|
+
)
|
|
1845
|
+
)
|
|
1846
|
+
),
|
|
1847
|
+
div(
|
|
1848
|
+
{ class: "col-sm-4" },
|
|
1849
|
+
select(
|
|
1850
|
+
{
|
|
1851
|
+
id: "synched-tbls-select-id",
|
|
1852
|
+
class: "form-control form-select",
|
|
1853
|
+
multiple: true,
|
|
1854
|
+
},
|
|
1855
|
+
withSyncInfo.map((table) =>
|
|
1856
|
+
option({
|
|
1857
|
+
id: `${table.name}_synched_opt`,
|
|
1858
|
+
value: table.name,
|
|
1859
|
+
label: table.name,
|
|
1860
|
+
hidden: "true",
|
|
1861
|
+
})
|
|
1862
|
+
)
|
|
1863
|
+
)
|
|
1864
|
+
)
|
|
1865
|
+
)
|
|
1866
|
+
)
|
|
1774
1867
|
)
|
|
1775
1868
|
),
|
|
1776
1869
|
button(
|
|
@@ -1877,6 +1970,7 @@ router.post(
|
|
|
1877
1970
|
serverURL,
|
|
1878
1971
|
splashPage,
|
|
1879
1972
|
allowOfflineMode,
|
|
1973
|
+
synchedTables,
|
|
1880
1974
|
} = req.body;
|
|
1881
1975
|
if (!androidPlatform && !iOSPlatform) {
|
|
1882
1976
|
return res.json({
|
|
@@ -1928,6 +2022,8 @@ router.post(
|
|
|
1928
2022
|
if (serverURL) spawnParams.push("-s", serverURL);
|
|
1929
2023
|
if (splashPage) spawnParams.push("--splashPage", splashPage);
|
|
1930
2024
|
if (allowOfflineMode) spawnParams.push("--allowOfflineMode");
|
|
2025
|
+
if (synchedTables?.length > 0)
|
|
2026
|
+
spawnParams.push("--synchedTables", ...synchedTables.map((tbl) => tbl));
|
|
1931
2027
|
if (
|
|
1932
2028
|
db.is_it_multi_tenant() &&
|
|
1933
2029
|
db.getTenantSchema() !== db.connectObj.default_schema
|
package/routes/fields.js
CHANGED
|
@@ -542,14 +542,6 @@ const fieldFlow = (req) =>
|
|
|
542
542
|
type: "Bool",
|
|
543
543
|
showIf: { summary_field: textfields },
|
|
544
544
|
}),
|
|
545
|
-
/*new Field({
|
|
546
|
-
name: "on_delete_cascade",
|
|
547
|
-
label: req.__("On delete cascade"),
|
|
548
|
-
type: "Bool",
|
|
549
|
-
sublabel: req.__(
|
|
550
|
-
"If the parent row is deleted, automatically delete the child rows."
|
|
551
|
-
),
|
|
552
|
-
}),*/
|
|
553
545
|
new Field({
|
|
554
546
|
name: "on_delete",
|
|
555
547
|
label: req.__("On delete"),
|
|
@@ -1063,7 +1055,11 @@ router.post(
|
|
|
1063
1055
|
res.send("");
|
|
1064
1056
|
return;
|
|
1065
1057
|
}
|
|
1066
|
-
const firefox = /firefox/i.test(req.headers["user-agent"]);
|
|
1058
|
+
//const firefox = /firefox/i.test(req.headers["user-agent"]);
|
|
1059
|
+
|
|
1060
|
+
//Chrome 116 changes its behaviour to align with firefox
|
|
1061
|
+
// - disabled inputs do not dispactch click events
|
|
1062
|
+
const firefox = true;
|
|
1067
1063
|
const fv = fieldviews[fieldview];
|
|
1068
1064
|
if (!fv && field.type === "Key" && fieldview === "select")
|
|
1069
1065
|
res.send(
|
package/routes/menu.js
CHANGED
|
@@ -69,8 +69,8 @@ const menuForm = async (req) => {
|
|
|
69
69
|
.filter(([k, v]) => !v.requireRow && !v.disableInBuilder)
|
|
70
70
|
.map(([k, v]) => k),
|
|
71
71
|
];
|
|
72
|
-
const triggers =
|
|
73
|
-
when_trigger: {
|
|
72
|
+
const triggers = Trigger.find({
|
|
73
|
+
when_trigger: {or: ["API call", "Never"]},
|
|
74
74
|
});
|
|
75
75
|
triggers.forEach((tr) => {
|
|
76
76
|
actions.push(tr.name);
|
|
@@ -148,6 +148,13 @@ const menuForm = async (req) => {
|
|
|
148
148
|
input_type: "select",
|
|
149
149
|
options: roles.map((r) => ({ label: r.role, value: r.id })),
|
|
150
150
|
},
|
|
151
|
+
{
|
|
152
|
+
name: "disable_on_mobile",
|
|
153
|
+
label: req.__("Disable on mobile"),
|
|
154
|
+
type: "Bool",
|
|
155
|
+
class: "item-menu",
|
|
156
|
+
required: false,
|
|
157
|
+
},
|
|
151
158
|
{
|
|
152
159
|
name: "url",
|
|
153
160
|
label: req.__("URL"),
|
|
@@ -264,18 +271,18 @@ const menuForm = async (req) => {
|
|
|
264
271
|
},
|
|
265
272
|
attributes: {
|
|
266
273
|
options: [
|
|
267
|
-
{ name: "", label: "Link" },
|
|
268
|
-
{ name: "btn btn-primary", label: "Primary button" },
|
|
269
|
-
{ name: "btn btn-secondary", label: "Secondary button" },
|
|
270
|
-
{ name: "btn btn-success", label: "Success button" },
|
|
271
|
-
{ name: "btn btn-danger", label: "Danger button" },
|
|
274
|
+
{ name: "", label: req.__("Link") },
|
|
275
|
+
{ name: "btn btn-primary", label: req.__("Primary button") },
|
|
276
|
+
{ name: "btn btn-secondary", label: req.__("Secondary button") },
|
|
277
|
+
{ name: "btn btn-success", label: req.__("Success button") },
|
|
278
|
+
{ name: "btn btn-danger", label: req.__("Danger button") },
|
|
272
279
|
{
|
|
273
280
|
name: "btn btn-outline-primary",
|
|
274
|
-
label: "Primary outline button",
|
|
281
|
+
label: req.__("Primary outline button"),
|
|
275
282
|
},
|
|
276
283
|
{
|
|
277
284
|
name: "btn btn-outline-secondary",
|
|
278
|
-
label: "Secondary outline button",
|
|
285
|
+
label: req.__("Secondary outline button"),
|
|
279
286
|
},
|
|
280
287
|
],
|
|
281
288
|
},
|
package/routes/scapi.js
CHANGED
|
@@ -101,7 +101,7 @@ router.get(
|
|
|
101
101
|
{ session: false },
|
|
102
102
|
async function (err, user, info) {
|
|
103
103
|
if (accessAllowedRead(req, user)) {
|
|
104
|
-
const views = await View.find({});
|
|
104
|
+
const views = await View.find({}, { cached: true });
|
|
105
105
|
|
|
106
106
|
res.json({ success: views });
|
|
107
107
|
} else {
|