@saltcorn/server 0.9.5-beta.5 → 0.9.5-beta.7
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/help/Cordova Builder.tmd +13 -0
- package/locales/en.json +6 -1
- package/package.json +10 -9
- package/public/log_viewer_utils.js +17 -1
- package/public/saltcorn-common.js +14 -5
- package/public/saltcorn.js +47 -1
- package/routes/admin.js +138 -3
- package/routes/tables.js +4 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
The Cordova builder is a docker image with all dependencies to build Android apps.
|
|
2
|
+
It can be pulled from dockerhub while installing Saltcorn, or you can use the pull button.
|
|
3
|
+
|
|
4
|
+
Please make sure your server has a valid and accessible docker daemon running.
|
|
5
|
+
|
|
6
|
+
For this, either set up a standard docker installation
|
|
7
|
+
or use the [docker rootless mode](https://docs.docker.com/engine/security/rootless/) (recommended).
|
|
8
|
+
|
|
9
|
+
In a standard docker environment, you need root user privileges.
|
|
10
|
+
For this, you can add the user running Saltcorn to the docker group,
|
|
11
|
+
or if you already have the privileges, you are ready to go.
|
|
12
|
+
|
|
13
|
+
A docker daemon in rootless mode doesn't need any further configuration.
|
package/locales/en.json
CHANGED
|
@@ -1384,5 +1384,10 @@
|
|
|
1384
1384
|
"Optionally associate a table with this trigger": "Optionally associate a table with this trigger",
|
|
1385
1385
|
"Delete table": "Delete table",
|
|
1386
1386
|
"Signup role": "Signup role",
|
|
1387
|
-
"The initial role of signed up users": "The initial role of signed up users"
|
|
1387
|
+
"The initial role of signed up users": "The initial role of signed up users",
|
|
1388
|
+
"Cordova builder": "Cordova builder",
|
|
1389
|
+
"not available": "not available",
|
|
1390
|
+
"pull": "pull",
|
|
1391
|
+
"refresh": "refresh",
|
|
1392
|
+
"installed": "installed"
|
|
1388
1393
|
}
|
package/package.json
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/server",
|
|
3
|
-
"version": "0.9.5-beta.
|
|
3
|
+
"version": "0.9.5-beta.7",
|
|
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
9
|
"@aws-sdk/client-s3": "^3.451.0",
|
|
10
|
-
"@saltcorn/base-plugin": "0.9.5-beta.
|
|
11
|
-
"@saltcorn/builder": "0.9.5-beta.
|
|
12
|
-
"@saltcorn/data": "0.9.5-beta.
|
|
13
|
-
"@saltcorn/admin-models": "0.9.5-beta.
|
|
14
|
-
"@saltcorn/filemanager": "0.9.5-beta.
|
|
15
|
-
"@saltcorn/markup": "0.9.5-beta.
|
|
16
|
-
"@saltcorn/plugins-loader": "0.9.5-beta.
|
|
17
|
-
"@saltcorn/sbadmin2": "0.9.5-beta.
|
|
10
|
+
"@saltcorn/base-plugin": "0.9.5-beta.7",
|
|
11
|
+
"@saltcorn/builder": "0.9.5-beta.7",
|
|
12
|
+
"@saltcorn/data": "0.9.5-beta.7",
|
|
13
|
+
"@saltcorn/admin-models": "0.9.5-beta.7",
|
|
14
|
+
"@saltcorn/filemanager": "0.9.5-beta.7",
|
|
15
|
+
"@saltcorn/markup": "0.9.5-beta.7",
|
|
16
|
+
"@saltcorn/plugins-loader": "0.9.5-beta.7",
|
|
17
|
+
"@saltcorn/sbadmin2": "0.9.5-beta.7",
|
|
18
18
|
"@socket.io/cluster-adapter": "^0.2.1",
|
|
19
19
|
"@socket.io/sticky": "^1.0.1",
|
|
20
20
|
"adm-zip": "0.5.10",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"cors": "2.8.5",
|
|
28
28
|
"csurf": "^1.11.0",
|
|
29
29
|
"csv-stringify": "^5.5.0",
|
|
30
|
+
"dockerode": "~4.0.2",
|
|
30
31
|
"express": "^4.17.1",
|
|
31
32
|
"express-fileupload": "^1.1.8",
|
|
32
33
|
"express-promise-router": "^3.0.3",
|
|
@@ -131,7 +131,23 @@ var logViewerHelpers = (() => {
|
|
|
131
131
|
|
|
132
132
|
return {
|
|
133
133
|
init_log_socket: () => {
|
|
134
|
-
|
|
134
|
+
let socket = null;
|
|
135
|
+
setTimeout(() => {
|
|
136
|
+
if (socket === null || socket.disconnected) {
|
|
137
|
+
notifyAlert({
|
|
138
|
+
type: "danger",
|
|
139
|
+
text: "Unable to connect to the server",
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}, 5000);
|
|
143
|
+
try {
|
|
144
|
+
socket = io({ transports: ["websocket"] });
|
|
145
|
+
} catch (e) {
|
|
146
|
+
notifyAlert({
|
|
147
|
+
type: "danger",
|
|
148
|
+
text: "Unable to connect to the server " + e.message,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
135
151
|
startTrackingMsg();
|
|
136
152
|
socket.on("connect", () => handleConnect(socket));
|
|
137
153
|
socket.on("disconnect", handleDisconnect);
|
|
@@ -76,7 +76,7 @@ function valid_js_var_name(s) {
|
|
|
76
76
|
return !!s.match(/^[a-zA-Z_$][a-zA-Z_$0-9]*$/);
|
|
77
77
|
}
|
|
78
78
|
function apply_showif() {
|
|
79
|
-
const isNode =
|
|
79
|
+
const isNode = getIsNode();
|
|
80
80
|
$("[data-show-if]").each(function (ix, element) {
|
|
81
81
|
var e = $(element);
|
|
82
82
|
try {
|
|
@@ -571,7 +571,7 @@ function reload_on_init() {
|
|
|
571
571
|
}
|
|
572
572
|
function initialize_page() {
|
|
573
573
|
if (window._sc_locale && window.dayjs) dayjs.locale(window._sc_locale);
|
|
574
|
-
const isNode =
|
|
574
|
+
const isNode = getIsNode();
|
|
575
575
|
//console.log("init page");
|
|
576
576
|
$(".blur-on-enter-keypress").bind("keyup", function (e) {
|
|
577
577
|
if (e.keyCode === 13) e.target.blur();
|
|
@@ -877,7 +877,7 @@ function initialize_page() {
|
|
|
877
877
|
$(initialize_page);
|
|
878
878
|
|
|
879
879
|
function cancel_inline_edit(e, opts1) {
|
|
880
|
-
const isNode =
|
|
880
|
+
const isNode = getIsNode();
|
|
881
881
|
var opts = JSON.parse(decodeURIComponent(opts1 || "") || "{}");
|
|
882
882
|
var form = $(e.target).closest("form");
|
|
883
883
|
form.replaceWith(opts.resetHtml);
|
|
@@ -885,7 +885,7 @@ function cancel_inline_edit(e, opts1) {
|
|
|
885
885
|
}
|
|
886
886
|
|
|
887
887
|
function inline_submit_success(e, form, opts) {
|
|
888
|
-
const isNode =
|
|
888
|
+
const isNode = getIsNode();
|
|
889
889
|
const formDataArray = form.serializeArray();
|
|
890
890
|
if (opts) {
|
|
891
891
|
let fdEntry = formDataArray.find((f) => f.name == opts.key);
|
|
@@ -1025,6 +1025,15 @@ function tristateClick(e, required) {
|
|
|
1025
1025
|
}
|
|
1026
1026
|
}
|
|
1027
1027
|
|
|
1028
|
+
function getIsNode() {
|
|
1029
|
+
try {
|
|
1030
|
+
return typeof parent?.saltcorn?.data?.state === "undefined";
|
|
1031
|
+
} catch (e) {
|
|
1032
|
+
//probably in an iframe
|
|
1033
|
+
return true;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1028
1037
|
function buildToast(txt, type, spin) {
|
|
1029
1038
|
const realtype = type === "error" ? "danger" : type;
|
|
1030
1039
|
const icon =
|
|
@@ -1035,7 +1044,7 @@ function buildToast(txt, type, spin) {
|
|
|
1035
1044
|
: realtype === "warning"
|
|
1036
1045
|
? "fa-exclamation-triangle"
|
|
1037
1046
|
: "";
|
|
1038
|
-
const isNode =
|
|
1047
|
+
const isNode = getIsNode();
|
|
1039
1048
|
const rndid = `tab${Math.floor(Math.random() * 16777215).toString(16)}`;
|
|
1040
1049
|
return {
|
|
1041
1050
|
id: rndid,
|
package/public/saltcorn.js
CHANGED
|
@@ -821,6 +821,17 @@ function build_mobile_app(button) {
|
|
|
821
821
|
params.includedPlugins = Array.from(pluginsSelect.options)
|
|
822
822
|
.filter((option) => !option.hidden)
|
|
823
823
|
.map((option) => option.value);
|
|
824
|
+
|
|
825
|
+
if (
|
|
826
|
+
params.useDocker &&
|
|
827
|
+
!cordovaBuilderAvailable &&
|
|
828
|
+
!confirm(
|
|
829
|
+
"Docker is selected but the Cordova builder seems not to be installed. " +
|
|
830
|
+
"Do you really want to continue?"
|
|
831
|
+
)
|
|
832
|
+
) {
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
824
835
|
ajax_post("/admin/build-mobile-app", {
|
|
825
836
|
data: params,
|
|
826
837
|
success: (data) => {
|
|
@@ -834,6 +845,41 @@ function build_mobile_app(button) {
|
|
|
834
845
|
});
|
|
835
846
|
}
|
|
836
847
|
|
|
848
|
+
function pull_cordova_builder() {
|
|
849
|
+
ajax_post("/admin/mobile-app/pull-cordova-builder", {
|
|
850
|
+
success: () => {
|
|
851
|
+
notifyAlert(
|
|
852
|
+
"Pulling the the cordova-builder. " +
|
|
853
|
+
"To see the progress, open the logs viewer with the System logging verbosity set to 'All'."
|
|
854
|
+
);
|
|
855
|
+
},
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
function check_cordova_builder() {
|
|
860
|
+
$.ajax("/admin/mobile-app/check-cordova-builder", {
|
|
861
|
+
type: "GET",
|
|
862
|
+
success: function (res) {
|
|
863
|
+
cordovaBuilderAvailable = !!res.installed;
|
|
864
|
+
if (cordovaBuilderAvailable) {
|
|
865
|
+
$("#dockerBuilderStatusId").html(
|
|
866
|
+
`<span>
|
|
867
|
+
installed<i class="ps-2 fas fa-check text-success"></i>
|
|
868
|
+
</span>
|
|
869
|
+
`
|
|
870
|
+
);
|
|
871
|
+
} else {
|
|
872
|
+
$("#dockerBuilderStatusId").html(
|
|
873
|
+
`<span>
|
|
874
|
+
not available<i class="ps-2 fas fa-times text-danger"></i>
|
|
875
|
+
</span>
|
|
876
|
+
`
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
},
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
|
|
837
883
|
function move_to_synched() {
|
|
838
884
|
const opts = $("#unsynched-tbls-select-id");
|
|
839
885
|
$("#synched-tbls-select-id").removeAttr("selected");
|
|
@@ -897,7 +943,7 @@ function toggle_tbl_sync() {
|
|
|
897
943
|
function toggle_android_platform() {
|
|
898
944
|
if ($("#androidCheckboxId")[0].checked === true) {
|
|
899
945
|
$("#dockerCheckboxId").attr("hidden", false);
|
|
900
|
-
$("#dockerCheckboxId").attr("checked",
|
|
946
|
+
$("#dockerCheckboxId").attr("checked", cordovaBuilderAvailable);
|
|
901
947
|
$("#dockerLabelId").removeClass("d-none");
|
|
902
948
|
} else {
|
|
903
949
|
$("#dockerCheckboxId").attr("hidden", true);
|
package/routes/admin.js
CHANGED
|
@@ -107,6 +107,7 @@ const { getSafeSaltcornCmd } = require("@saltcorn/data/utils");
|
|
|
107
107
|
const stream = require("stream");
|
|
108
108
|
const Crash = require("@saltcorn/data/models/crash");
|
|
109
109
|
const { get_help_markup } = require("../help/index.js");
|
|
110
|
+
const Docker = require("dockerode");
|
|
110
111
|
|
|
111
112
|
const router = new Router();
|
|
112
113
|
module.exports = router;
|
|
@@ -1104,6 +1105,28 @@ router.post(
|
|
|
1104
1105
|
})
|
|
1105
1106
|
);
|
|
1106
1107
|
|
|
1108
|
+
const pullCordovaBuilder = (req, res) => {
|
|
1109
|
+
const child = spawn("docker", ["pull", "saltcorn/cordova-builder"], {
|
|
1110
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1111
|
+
});
|
|
1112
|
+
return new Promise((resolve, reject) => {
|
|
1113
|
+
child.stdout.on("data", (data) => {
|
|
1114
|
+
res.write(data);
|
|
1115
|
+
});
|
|
1116
|
+
child.stderr?.on("data", (data) => {
|
|
1117
|
+
res.write(data);
|
|
1118
|
+
});
|
|
1119
|
+
child.on("exit", function (code, signal) {
|
|
1120
|
+
resolve(code);
|
|
1121
|
+
});
|
|
1122
|
+
child.on("error", (msg) => {
|
|
1123
|
+
const message = msg.message ? msg.message : msg.code;
|
|
1124
|
+
res.write(req.__("Error: ") + message + "\n");
|
|
1125
|
+
resolve(msg.code);
|
|
1126
|
+
});
|
|
1127
|
+
});
|
|
1128
|
+
};
|
|
1129
|
+
|
|
1107
1130
|
/**
|
|
1108
1131
|
* Do Upgrade
|
|
1109
1132
|
* @name post/upgrade
|
|
@@ -1132,7 +1155,14 @@ router.post(
|
|
|
1132
1155
|
child.stderr?.on("data", (data) => {
|
|
1133
1156
|
res.write(data);
|
|
1134
1157
|
});
|
|
1135
|
-
child.on("exit", function (code, signal) {
|
|
1158
|
+
child.on("exit", async function (code, signal) {
|
|
1159
|
+
if (code === 0) {
|
|
1160
|
+
res.write(
|
|
1161
|
+
req.__("Pulling the cordova-builder docker image...") + "\n"
|
|
1162
|
+
);
|
|
1163
|
+
const pullCode = await pullCordovaBuilder(req, res);
|
|
1164
|
+
res.write(req.__("Pull done with code %s", pullCode) + "\n");
|
|
1165
|
+
}
|
|
1136
1166
|
res.end(
|
|
1137
1167
|
req.__(
|
|
1138
1168
|
`Upgrade done (if it was available) with code ${code}.\n\nPress the BACK button in your browser, then RELOAD the page.`
|
|
@@ -1481,8 +1511,9 @@ router.get(
|
|
|
1481
1511
|
});
|
|
1482
1512
|
})
|
|
1483
1513
|
);
|
|
1484
|
-
const buildDialogScript = () => {
|
|
1514
|
+
const buildDialogScript = (cordovaBuilderAvailable) => {
|
|
1485
1515
|
return `<script>
|
|
1516
|
+
var cordovaBuilderAvailable = ${cordovaBuilderAvailable};
|
|
1486
1517
|
function showEntrySelect(type) {
|
|
1487
1518
|
for( const currentType of ["view", "page", "pagegroup"]) {
|
|
1488
1519
|
const tab = $('#' + currentType + 'NavLinkID');
|
|
@@ -1519,6 +1550,17 @@ const buildDialogScript = () => {
|
|
|
1519
1550
|
}
|
|
1520
1551
|
</script>`;
|
|
1521
1552
|
};
|
|
1553
|
+
|
|
1554
|
+
const imageAvailable = async () => {
|
|
1555
|
+
try {
|
|
1556
|
+
const image = new Docker().getImage("saltcorn/cordova-builder");
|
|
1557
|
+
await image.inspect();
|
|
1558
|
+
return true;
|
|
1559
|
+
} catch (e) {
|
|
1560
|
+
return false;
|
|
1561
|
+
}
|
|
1562
|
+
};
|
|
1563
|
+
|
|
1522
1564
|
/**
|
|
1523
1565
|
* Build mobile app
|
|
1524
1566
|
*/
|
|
@@ -1538,13 +1580,14 @@ router.get(
|
|
|
1538
1580
|
);
|
|
1539
1581
|
const builderSettings =
|
|
1540
1582
|
getState().getConfig("mobile_builder_settings") || {};
|
|
1583
|
+
const dockerAvailable = await imageAvailable();
|
|
1541
1584
|
send_admin_page({
|
|
1542
1585
|
res,
|
|
1543
1586
|
req,
|
|
1544
1587
|
active_sub: "Mobile app",
|
|
1545
1588
|
headers: [
|
|
1546
1589
|
{
|
|
1547
|
-
headerTag: buildDialogScript(),
|
|
1590
|
+
headerTag: buildDialogScript(dockerAvailable),
|
|
1548
1591
|
},
|
|
1549
1592
|
],
|
|
1550
1593
|
contents: {
|
|
@@ -2165,6 +2208,56 @@ router.get(
|
|
|
2165
2208
|
)
|
|
2166
2209
|
)
|
|
2167
2210
|
)
|
|
2211
|
+
),
|
|
2212
|
+
div(
|
|
2213
|
+
{ class: "row pb-3 pt-3" },
|
|
2214
|
+
div(
|
|
2215
|
+
label(
|
|
2216
|
+
{ class: "form-label fw-bold" },
|
|
2217
|
+
req.__("Cordova builder") +
|
|
2218
|
+
a(
|
|
2219
|
+
{
|
|
2220
|
+
href: "javascript:ajax_modal('/admin/help/Cordova Builder?')",
|
|
2221
|
+
},
|
|
2222
|
+
i({ class: "fas fa-question-circle ps-1" })
|
|
2223
|
+
)
|
|
2224
|
+
)
|
|
2225
|
+
),
|
|
2226
|
+
div(
|
|
2227
|
+
{ class: "col-sm-4" },
|
|
2228
|
+
div(
|
|
2229
|
+
{
|
|
2230
|
+
id: "dockerBuilderStatusId",
|
|
2231
|
+
class: "",
|
|
2232
|
+
},
|
|
2233
|
+
dockerAvailable
|
|
2234
|
+
? span(
|
|
2235
|
+
req.__("installed"),
|
|
2236
|
+
i({ class: "ps-2 fas fa-check text-success" })
|
|
2237
|
+
)
|
|
2238
|
+
: span(
|
|
2239
|
+
req.__("not available"),
|
|
2240
|
+
i({ class: "ps-2 fas fa-times text-danger" })
|
|
2241
|
+
)
|
|
2242
|
+
)
|
|
2243
|
+
),
|
|
2244
|
+
div(
|
|
2245
|
+
{ class: "col-sm-4" },
|
|
2246
|
+
button(
|
|
2247
|
+
{
|
|
2248
|
+
id: "pullCordovaBtnId",
|
|
2249
|
+
type: "button",
|
|
2250
|
+
onClick: `pull_cordova_builder(this);`,
|
|
2251
|
+
class: "btn btn-warning",
|
|
2252
|
+
},
|
|
2253
|
+
req.__("pull")
|
|
2254
|
+
),
|
|
2255
|
+
span(
|
|
2256
|
+
{ role: "button", onClick: "check_cordova_builder()" },
|
|
2257
|
+
span({ class: "ps-3" }, req.__("refresh")),
|
|
2258
|
+
i({ class: "ps-2 fas fa-undo" })
|
|
2259
|
+
)
|
|
2260
|
+
)
|
|
2168
2261
|
)
|
|
2169
2262
|
),
|
|
2170
2263
|
button(
|
|
@@ -2419,6 +2512,48 @@ router.post(
|
|
|
2419
2512
|
})
|
|
2420
2513
|
);
|
|
2421
2514
|
|
|
2515
|
+
router.post(
|
|
2516
|
+
"/mobile-app/pull-cordova-builder",
|
|
2517
|
+
isAdmin,
|
|
2518
|
+
error_catcher(async (req, res) => {
|
|
2519
|
+
const state = getState();
|
|
2520
|
+
const child = spawn(
|
|
2521
|
+
"docker",
|
|
2522
|
+
["image", "pull", "saltcorn/cordova-builder:latest"],
|
|
2523
|
+
{
|
|
2524
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2525
|
+
cwd: ".",
|
|
2526
|
+
}
|
|
2527
|
+
);
|
|
2528
|
+
child.stdout.on("data", (data) => {
|
|
2529
|
+
state.log(5, data.toString());
|
|
2530
|
+
});
|
|
2531
|
+
child.stderr.on("data", (data) => {
|
|
2532
|
+
state.log(1, data.toString());
|
|
2533
|
+
});
|
|
2534
|
+
child.on("exit", (exitCode, signal) => {
|
|
2535
|
+
state.log(
|
|
2536
|
+
2,
|
|
2537
|
+
`"pull cordova-builder exit with code: ${exitCode} and signal: ${signal}`
|
|
2538
|
+
);
|
|
2539
|
+
});
|
|
2540
|
+
child.on("error", (msg) => {
|
|
2541
|
+
state.log(1, `pull cordova-builder error: ${msg}`);
|
|
2542
|
+
});
|
|
2543
|
+
|
|
2544
|
+
res.json({});
|
|
2545
|
+
})
|
|
2546
|
+
);
|
|
2547
|
+
|
|
2548
|
+
router.get(
|
|
2549
|
+
"/mobile-app/check-cordova-builder",
|
|
2550
|
+
isAdmin,
|
|
2551
|
+
error_catcher(async (req, res) => {
|
|
2552
|
+
const installed = await imageAvailable();
|
|
2553
|
+
res.json({ installed });
|
|
2554
|
+
})
|
|
2555
|
+
);
|
|
2556
|
+
|
|
2422
2557
|
/**
|
|
2423
2558
|
* Do Clear All
|
|
2424
2559
|
* @function
|
package/routes/tables.js
CHANGED
|
@@ -167,6 +167,10 @@ const tableForm = async (table, req) => {
|
|
|
167
167
|
label: req.__("Version history"),
|
|
168
168
|
sublabel: req.__("Track table data changes over time"),
|
|
169
169
|
name: "versioned",
|
|
170
|
+
attributes: {
|
|
171
|
+
onChange:
|
|
172
|
+
"if(!this.checked && !confirm('Are you sure? This will delete all history')) {this.checked = true; return false}",
|
|
173
|
+
},
|
|
170
174
|
type: "Bool",
|
|
171
175
|
},
|
|
172
176
|
...(table.name === "users"
|