@saltcorn/server 0.9.5-beta.6 → 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.
@@ -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.6",
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.6",
11
- "@saltcorn/builder": "0.9.5-beta.6",
12
- "@saltcorn/data": "0.9.5-beta.6",
13
- "@saltcorn/admin-models": "0.9.5-beta.6",
14
- "@saltcorn/filemanager": "0.9.5-beta.6",
15
- "@saltcorn/markup": "0.9.5-beta.6",
16
- "@saltcorn/plugins-loader": "0.9.5-beta.6",
17
- "@saltcorn/sbadmin2": "0.9.5-beta.6",
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
- const socket = io({ transports: ["websocket"] });
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 = typeof parent?.saltcorn?.data?.state === "undefined";
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 = typeof parent?.saltcorn?.data?.state === "undefined";
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 = typeof parent?.saltcorn?.data?.state === "undefined";
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 = typeof parent?.saltcorn?.data?.state === "undefined";
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 = typeof parent?.saltcorn?.data?.state === "undefined";
1047
+ const isNode = getIsNode();
1039
1048
  const rndid = `tab${Math.floor(Math.random() * 16777215).toString(16)}`;
1040
1049
  return {
1041
1050
  id: rndid,
@@ -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", true);
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"