@saltcorn/server 1.0.0-beta.1 → 1.0.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/auth/admin.js CHANGED
@@ -365,6 +365,7 @@ const auth_settings_form = async (req) =>
365
365
  "signup_role",
366
366
  "elevate_verified",
367
367
  "email_mask",
368
+ "plain_password_triggers",
368
369
  ],
369
370
  action: "/useradmin/settings",
370
371
  submitLabel: req.__("Save"),
@@ -8,7 +8,7 @@ To build an Android app for the Play Store, you need to create an Android Applic
8
8
 
9
9
  *Note: You need a [Play Console developer account](https://support.google.com/googleplay/android-developer/answer/6112435?hl=en&ref_topic=3450769&sjid=11090022771305927482-EU) to publish your app on the Play Store.*
10
10
 
11
- ### Create a Keysore file
11
+ ### Create a Keystore file
12
12
  On any Unix-based system, you can use the `keytool` command to create the keystore file. For example:
13
13
  ```sh
14
14
  keytool -genkey -v -keystore my-app-key.jks
package/load_plugins.js CHANGED
@@ -12,6 +12,37 @@ const { isRoot } = require("@saltcorn/data/utils");
12
12
  const { eachTenant } = require("@saltcorn/admin-models/models/tenant");
13
13
 
14
14
  const PluginInstaller = require("@saltcorn/plugins-loader/plugin_installer");
15
+ const npmFetch = require("npm-registry-fetch");
16
+ const packagejson = require("./package.json");
17
+ const {
18
+ supportedVersion,
19
+ resolveLatest,
20
+ } = require("@saltcorn/plugins-loader/stable_versioning");
21
+
22
+ /**
23
+ * checks the saltcorn engine property and changes the plugin version if necessary
24
+ * @param plugin plugin to load
25
+ */
26
+ const ensurePluginSupport = async (plugin) => {
27
+ const pkgInfo = await npmFetch.json(
28
+ `https://registry.npmjs.org/${plugin.location}`
29
+ );
30
+ const supported = supportedVersion(
31
+ plugin.version || "latest",
32
+ pkgInfo.versions,
33
+ packagejson.version
34
+ );
35
+ if (!supported)
36
+ throw new Error(
37
+ `Unable to find a supported version for '${plugin.location}'`
38
+ );
39
+ else if (
40
+ supported !== plugin.version ||
41
+ (plugin.version === "latest" &&
42
+ supported !== resolveLatest(pkgInfo.versions))
43
+ )
44
+ plugin.version = supported;
45
+ };
15
46
 
16
47
  /**
17
48
  * Load one plugin
@@ -20,6 +51,15 @@ const PluginInstaller = require("@saltcorn/plugins-loader/plugin_installer");
20
51
  * @param force - force flag
21
52
  */
22
53
  const loadPlugin = async (plugin, force) => {
54
+ if (plugin.source === "npm") {
55
+ try {
56
+ await ensurePluginSupport(plugin);
57
+ } catch (e) {
58
+ console.log(
59
+ `Warning: Unable to find a supported version for '${plugin.location}' Continuing with the installed version`
60
+ );
61
+ }
62
+ }
23
63
  // load plugin
24
64
  const loader = new PluginInstaller(plugin);
25
65
  const res = await loader.install(force);
@@ -151,11 +191,14 @@ const loadAndSaveNewPlugin = async (
151
191
  return;
152
192
  }
153
193
  }
154
- const msgs = [];
155
- const loader = new PluginInstaller(plugin);
156
- const { version, plugin_module, location, loadedWithReload } =
194
+ if (plugin.source === "npm") await ensurePluginSupport(plugin);
195
+ const loadMsgs = [];
196
+ const loader = new PluginInstaller(plugin, {
197
+ scVersion: packagejson.version,
198
+ });
199
+ const { version, plugin_module, location, loadedWithReload, msgs } =
157
200
  await loader.install(force);
158
-
201
+ if (msgs) loadMsgs.push(...msgs);
159
202
  // install dependecies
160
203
  for (const loc of plugin_module.dependencies || []) {
161
204
  const existing = await Plugin.findOne({ location: loc });
@@ -201,7 +244,7 @@ const loadAndSaveNewPlugin = async (
201
244
  }
202
245
  }
203
246
  if (loadedWithReload || registeredWithReload) {
204
- msgs.push(
247
+ loadMsgs.push(
205
248
  __(
206
249
  "The plugin was corrupted and had to be repaired. We recommend restarting your server.",
207
250
  plugin.name
@@ -228,7 +271,7 @@ const loadAndSaveNewPlugin = async (
228
271
  force: false, // okay ??
229
272
  });
230
273
  }
231
- return msgs;
274
+ return loadMsgs;
232
275
  };
233
276
 
234
277
  module.exports = {
@@ -236,4 +279,6 @@ module.exports = {
236
279
  loadAllPlugins,
237
280
  loadPlugin,
238
281
  requirePlugin,
282
+ supportedVersion,
283
+ ensurePluginSupport,
239
284
  };
package/locales/en.json CHANGED
@@ -1459,5 +1459,14 @@
1459
1459
  "Hourly": "Hourly",
1460
1460
  "Daily": "Daily",
1461
1461
  "Weekly": "Weekly",
1462
- "Code pages": "Code pages"
1462
+ "Code pages": "Code pages",
1463
+ "Please select a file": "Please select a file",
1464
+ "Zip compression level": "Zip compression level",
1465
+ "1=Fast, larger file, 9=Slow, smaller files": "1=Fast, larger file, 9=Slow, smaller files",
1466
+ "Use system zip": "Use system zip",
1467
+ "Recommended. Executable <code>zip</code> must be installed": "Recommended. Executable <code>zip</code> must be installed",
1468
+ "Time to run": "Time to run",
1469
+ "Mobile": "Mobile",
1470
+ "Plain password trigger row": "Plain password trigger row",
1471
+ "Send plaintext password changes to Users table triggers (Insert, Update and Validate).": "Send plaintext password changes to Users table triggers (Insert, Update and Validate)."
1463
1472
  }
package/markup/blockly.js CHANGED
@@ -22,16 +22,16 @@ const db = require("@saltcorn/data/db");
22
22
  */
23
23
  const blocklyImportScripts = ({ locale }) =>
24
24
  script({
25
- src: "/plugins/pubdeps/base/blockly/6.20210701.0/blockly_compressed.js",
25
+ src: "/plugins/pubdeps/base/blockly/8.0.5/blockly_compressed.js",
26
26
  }) +
27
27
  script({
28
- src: "/plugins/pubdeps/base/blockly/6.20210701.0/blocks_compressed.js",
28
+ src: "/plugins/pubdeps/base/blockly/8.0.5/blocks_compressed.js",
29
29
  }) +
30
30
  script({
31
- src: `/plugins/pubdeps/base/blockly/6.20210701.0/msg/${locale}.js`,
31
+ src: `/plugins/pubdeps/base/blockly/8.0.5/msg/${locale}.js`,
32
32
  }) +
33
33
  script({
34
- src: "/plugins/pubdeps/base/blockly/6.20210701.0/javascript_compressed.js",
34
+ src: "/plugins/pubdeps/base/blockly/8.0.5/javascript_compressed.js",
35
35
  }) +
36
36
  script({
37
37
  src: `/static_assets/${db.connectObj.version_tag}/blockly.js`,
package/package.json CHANGED
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "name": "@saltcorn/server",
3
- "version": "1.0.0-beta.1",
3
+ "version": "1.0.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
9
  "@aws-sdk/client-s3": "^3.451.0",
10
- "@saltcorn/base-plugin": "1.0.0-beta.1",
11
- "@saltcorn/builder": "1.0.0-beta.1",
12
- "@saltcorn/data": "1.0.0-beta.1",
13
- "@saltcorn/admin-models": "1.0.0-beta.1",
14
- "@saltcorn/filemanager": "1.0.0-beta.1",
15
- "@saltcorn/markup": "1.0.0-beta.1",
16
- "@saltcorn/plugins-loader": "1.0.0-beta.1",
17
- "@saltcorn/sbadmin2": "1.0.0-beta.1",
10
+ "@saltcorn/base-plugin": "1.0.0-beta.10",
11
+ "@saltcorn/builder": "1.0.0-beta.10",
12
+ "@saltcorn/data": "1.0.0-beta.10",
13
+ "@saltcorn/admin-models": "1.0.0-beta.10",
14
+ "@saltcorn/filemanager": "1.0.0-beta.10",
15
+ "@saltcorn/markup": "1.0.0-beta.10",
16
+ "@saltcorn/plugins-loader": "1.0.0-beta.10",
17
+ "@saltcorn/sbadmin2": "1.0.0-beta.10",
18
18
  "@socket.io/cluster-adapter": "^0.2.1",
19
19
  "@socket.io/sticky": "^1.0.1",
20
20
  "adm-zip": "0.5.10",
@@ -46,7 +46,7 @@
46
46
  "node-fetch": "2.6.9",
47
47
  "node-watch": "^0.7.2",
48
48
  "notp": "2.0.3",
49
- "npm-registry-fetch": "16.0.0",
49
+ "npm-registry-fetch": "17.1.0",
50
50
  "passport": "^0.6.0",
51
51
  "passport-custom": "^1.1.1",
52
52
  "passport-http-bearer": "^1.0.1",
@@ -63,7 +63,7 @@
63
63
  "tmp-promise": "^3.0.2",
64
64
  "ua-parser-js": "^1.0.37",
65
65
  "underscore": "1.13.6",
66
- "uuid": "^8.2.0"
66
+ "uuid": "^10.0.0"
67
67
  },
68
68
  "optionalDependencies": {
69
69
  "connect-sqlite3": "^0.9.11",
@@ -71,9 +71,9 @@
71
71
  },
72
72
  "repository": "github:saltcorn/saltcorn",
73
73
  "devDependencies": {
74
- "jest": "^28.1.3",
75
- "jest-environment-jsdom": "28.1.3",
76
- "supertest": "^6.3.3"
74
+ "jest": "^29.7.0",
75
+ "jest-environment-jsdom": "29.7.0",
76
+ "supertest": "7.0.0"
77
77
  },
78
78
  "scripts": {
79
79
  "dev": "nodemon index.js",
@@ -303,25 +303,27 @@ function apply_showif() {
303
303
  ...cache,
304
304
  [qs]: "fetching",
305
305
  });
306
- $.ajax(`/api/${dynwhere.table}?${qs}`).then((resp) => {
307
- if (resp.success) {
308
- if (window._sc_loglevel > 4)
309
- console.log("dynwhere fetch", qs, resp.success);
310
-
311
- activate(resp.success, qs);
312
- const cacheNow = e.prop("data-fetch-options-cache") || {};
313
- e.prop("data-fetch-options-cache", {
314
- ...cacheNow,
315
- [qs]: resp.success,
316
- });
317
- } else {
318
- const cacheNow = e.prop("data-fetch-options-cache") || {};
319
- e.prop("data-fetch-options-cache", {
320
- ...cacheNow,
321
- [qs]: undefined,
322
- });
323
- }
324
- });
306
+ $.ajax(`/api/${dynwhere.table}?${qs}`)
307
+ .then((resp) => {
308
+ if (resp.success) {
309
+ if (window._sc_loglevel > 4)
310
+ console.log("dynwhere fetch", qs, resp.success);
311
+
312
+ activate(resp.success, qs);
313
+ const cacheNow = e.prop("data-fetch-options-cache") || {};
314
+ e.prop("data-fetch-options-cache", {
315
+ ...cacheNow,
316
+ [qs]: resp.success,
317
+ });
318
+ } else {
319
+ const cacheNow = e.prop("data-fetch-options-cache") || {};
320
+ e.prop("data-fetch-options-cache", {
321
+ ...cacheNow,
322
+ [qs]: undefined,
323
+ });
324
+ }
325
+ })
326
+ .fail(checkNetworkError);
325
327
  }
326
328
  });
327
329
  $("[data-filter-table]").each(function (ix, element) {
@@ -421,7 +423,14 @@ function apply_showif() {
421
423
  navigator.systemLanguage ||
422
424
  "en";
423
425
  window.detected_locale = locale;
424
- const parse = (s) => JSON.parse(decodeURIComponent(s));
426
+ const parse = (s, def = {}) => {
427
+ try {
428
+ return JSON.parse(decodeURIComponent(s));
429
+ } catch (e) {
430
+ console.error("failed to parse time format", e);
431
+ return def;
432
+ }
433
+ };
425
434
  $("time[locale-time-options]").each(function () {
426
435
  var el = $(this);
427
436
  var date = new Date(el.attr("datetime"));
@@ -443,8 +452,9 @@ function apply_showif() {
443
452
  $("time[locale-date-format]").each(function () {
444
453
  var el = $(this);
445
454
  var date = el.attr("datetime");
446
- const format = parse(el.attr("locale-date-format"));
447
- el.text(dayjs(date).format(format));
455
+ const format = parse(el.attr("locale-date-format"), "");
456
+ if (format) el.text(dayjs(date).format(format));
457
+ else el.text(dayjs(date));
448
458
  });
449
459
 
450
460
  _apply_showif_plugins.forEach((p) => p());
@@ -522,6 +532,7 @@ function get_form_record(e_in, select_labels) {
522
532
  $(e_in).prop("data-join-values", jvs);
523
533
  apply_showif();
524
534
  },
535
+ error: checkNetworkError,
525
536
  });
526
537
  }
527
538
  $(e_in).prop("data-join-key-values", keyVals);
@@ -886,31 +897,33 @@ function initialize_page() {
886
897
  })
887
898
  );
888
899
  const doAjaxOptionsFetch = (tblName, target) => {
889
- $.ajax(`/api/${tblName}`).then((resp) => {
890
- if (resp.success) {
891
- resp.success.sort((a, b) =>
892
- a[target]?.toLowerCase?.() > b[target]?.toLowerCase?.() ? 1 : -1
893
- );
894
-
895
- const selopts = resp.success.map(
896
- (r) =>
897
- `<option ${current == r.id ? `selected ` : ``}value="${
898
- r.id
899
- }">${escapeHtml(r[target])}</option>`
900
- );
901
- $(this).replaceWith(
902
- `<form method="post" action="${url}" ${
903
- ajax ? `onsubmit="inline_ajax_submit(event, '${opts}')"` : ""
904
- }>
900
+ $.ajax(`/api/${tblName}`)
901
+ .then((resp) => {
902
+ if (resp.success) {
903
+ resp.success.sort((a, b) =>
904
+ a[target]?.toLowerCase?.() > b[target]?.toLowerCase?.() ? 1 : -1
905
+ );
906
+
907
+ const selopts = resp.success.map(
908
+ (r) =>
909
+ `<option ${current == r.id ? `selected ` : ``}value="${
910
+ r.id
911
+ }">${escapeHtml(r[target])}</option>`
912
+ );
913
+ $(this).replaceWith(
914
+ `<form method="post" action="${url}" ${
915
+ ajax ? `onsubmit="inline_ajax_submit(event, '${opts}')"` : ""
916
+ }>
905
917
  <input type="hidden" name="_csrf" value="${_sc_globalCsrf}">
906
918
  <select name="${key}" value="${current}">${selopts}
907
919
  </select>
908
920
  <button type="submit" class="btn btn-sm btn-primary">OK</button>
909
921
  <button onclick="cancel_inline_edit(event, '${opts}')" type="button" class="btn btn-sm btn-danger"><i class="fas fa-times"></i></button>
910
922
  </form>`
911
- );
912
- }
913
- });
923
+ );
924
+ }
925
+ })
926
+ .fail(checkNetworkError);
914
927
  };
915
928
  if (type === "JSON" && schema && schema.type.startsWith("Key to ")) {
916
929
  const tblName = schema.type.replace("Key to ", "");
@@ -1089,7 +1102,8 @@ function initialize_page() {
1089
1102
  initialize_page();
1090
1103
  },
1091
1104
  error: function (res) {
1092
- notifyAlert({ type: "danger", text: res.responseText });
1105
+ if (!checkNetworkError(res))
1106
+ notifyAlert({ type: "danger", text: res.responseText });
1093
1107
  if ($e.html() === "Loading...") $e.html("");
1094
1108
  },
1095
1109
  });
@@ -1164,9 +1178,10 @@ function inline_ajax_submit(e, opts1) {
1164
1178
  inline_submit_success(e, form, opts);
1165
1179
  },
1166
1180
  error: function (e) {
1167
- ajax_done(
1168
- e.responseJSON || { error: "Unknown error: " + e.responseText }
1169
- );
1181
+ if (!checkNetworkError(e))
1182
+ ajax_done(
1183
+ e.responseJSON || { error: "Unknown error: " + e.responseText }
1184
+ );
1170
1185
  },
1171
1186
  });
1172
1187
  }
@@ -1209,6 +1224,7 @@ function enable_codemirror(f) {
1209
1224
  dataType: "script",
1210
1225
  cache: true,
1211
1226
  success: f,
1227
+ error: checkNetworkError,
1212
1228
  });
1213
1229
  }
1214
1230
  function tristateClick(e, required) {
@@ -1516,7 +1532,8 @@ function reloadEmbeddedEditOwnViews(form, id) {
1516
1532
  initialize_page();
1517
1533
  },
1518
1534
  error: function (res) {
1519
- notifyAlert({ type: "danger", text: res.responseText });
1535
+ if (!checkNetworkError(res))
1536
+ notifyAlert({ type: "danger", text: res.responseText });
1520
1537
  },
1521
1538
  });
1522
1539
  });
@@ -1749,24 +1766,26 @@ function is_paging_param(key) {
1749
1766
  return key.endsWith("_page") || key.endsWith("_pagesize");
1750
1767
  }
1751
1768
  function check_saltcorn_notifications() {
1752
- $.ajax(`/notifications/count-unread`).then((resp) => {
1753
- if (resp.success) {
1754
- const n = resp.success;
1755
- const menu_item = $(`a.notify-menu-item`);
1756
-
1757
- menu_item.html(
1758
- `<i class="fa-fw mr-05 fas fa-bell"></i>Notifications (${n})`
1759
- );
1760
- $(".user-nav-section").html(
1761
- `<i class="fa-fw mr-05 fas fa-user"></i>User (${n})`
1762
- );
1763
- $(".user-nav-section-with-span").html(
1764
- `<i class="fa-fw mr-05 fas fa-user"></i><span>User (${n})</span>`
1765
- );
1766
- window.update_theme_notification_count &&
1767
- window.update_theme_notification_count(n);
1768
- }
1769
- });
1769
+ $.ajax(`/notifications/count-unread`)
1770
+ .then((resp) => {
1771
+ if (resp.success) {
1772
+ const n = resp.success;
1773
+ const menu_item = $(`a.notify-menu-item`);
1774
+
1775
+ menu_item.html(
1776
+ `<i class="fa-fw mr-05 fas fa-bell"></i>Notifications (${n})`
1777
+ );
1778
+ $(".user-nav-section").html(
1779
+ `<i class="fa-fw mr-05 fas fa-user"></i>User (${n})`
1780
+ );
1781
+ $(".user-nav-section-with-span").html(
1782
+ `<i class="fa-fw mr-05 fas fa-user"></i><span>User (${n})</span>`
1783
+ );
1784
+ window.update_theme_notification_count &&
1785
+ window.update_theme_notification_count(n);
1786
+ }
1787
+ })
1788
+ .fail(checkNetworkError);
1770
1789
  }
1771
1790
 
1772
1791
  function disable_inactive_tab_inputs(id) {
@@ -1862,7 +1881,8 @@ function reload_embedded_view(viewname, new_query_string) {
1862
1881
  updater($e, res);
1863
1882
  },
1864
1883
  error: function (res) {
1865
- notifyAlert({ type: "danger", text: res.responseText });
1884
+ if (!checkNetworkError(res))
1885
+ notifyAlert({ type: "danger", text: res.responseText });
1866
1886
  },
1867
1887
  });
1868
1888
  } else {
@@ -1872,3 +1892,19 @@ function reload_embedded_view(viewname, new_query_string) {
1872
1892
  }
1873
1893
  });
1874
1894
  }
1895
+
1896
+ function update_time_of_week(nm) {
1897
+ return function () {
1898
+ const day = $(`#input${nm}__day`).val();
1899
+ const flat = document.querySelector(`#input${nm}__time`)._flatpickr;
1900
+
1901
+ const time = flat.selectedDates?.[0];
1902
+ let s;
1903
+ if (time) {
1904
+ const m = time.getMinutes();
1905
+
1906
+ s = `${day} ${time.getHours()} ${m < 10 ? `0${m}` : m}`;
1907
+ } else s = day;
1908
+ $(`#inputh${nm}`).val(s).trigger("change");
1909
+ };
1910
+ }
@@ -190,7 +190,8 @@ function pjax_to(href, e) {
190
190
  initialize_page();
191
191
  },
192
192
  error: function (res) {
193
- notifyAlert({ type: "danger", text: res.responseText });
193
+ if (!checkNetworkError(res))
194
+ notifyAlert({ type: "danger", text: res.responseText });
194
195
  },
195
196
  });
196
197
  }
@@ -264,7 +265,8 @@ function view_post(viewnameOrElem, route, data, onDone, sendState) {
264
265
  reset_spinners();
265
266
  })
266
267
  .fail(function (res) {
267
- notifyAlert({ type: "danger", text: res.responseText });
268
+ if (!checkNetworkError(res))
269
+ notifyAlert({ type: "danger", text: res.responseText });
268
270
  reset_spinners();
269
271
  });
270
272
  }
@@ -388,9 +390,12 @@ function ajax_modal(url, opts = {}) {
388
390
  ? {
389
391
  error: opts.onError,
390
392
  }
391
- : {}),
393
+ : { error: checkNetworkError }),
392
394
  });
393
395
  }
396
+ function closeModal() {
397
+ $("#scmodal").modal("toggle");
398
+ }
394
399
 
395
400
  function selectVersionError(res, btnId) {
396
401
  notifyAlert({
@@ -448,7 +453,8 @@ function saveAndContinue(e, k, event) {
448
453
  },
449
454
  error: function (request) {
450
455
  var ct = request.getResponseHeader("content-type") || "";
451
- if (ct.startsWith && ct.startsWith("application/json")) {
456
+ if (checkNetworkError(request)) {
457
+ } else if (ct.startsWith && ct.startsWith("application/json")) {
452
458
  notifyAlert({ type: "danger", text: request.responseJSON.error });
453
459
  } else {
454
460
  $("#page-inner-content").html(request.responseText);
@@ -497,6 +503,7 @@ function applyViewConfig(e, url, k, event) {
497
503
  },
498
504
  data: JSON.stringify(cfg),
499
505
  error: function (request) {
506
+ checkNetworkError(request);
500
507
  window.savingViewConfig = false;
501
508
  ajax_indicate_error(e, request);
502
509
  },
@@ -575,6 +582,7 @@ function ajaxSubmitForm(e, force_no_reload) {
575
582
  else common_done(res, form.attr("data-viewname"));
576
583
  },
577
584
  error: function (request) {
585
+ checkNetworkError(request);
578
586
  var title = request.getResponseHeader("Page-Title");
579
587
  if (title) $("#scmodal .modal-title").html(decodeURIComponent(title));
580
588
  var body = request.responseText;
@@ -591,6 +599,9 @@ function ajax_post_json(url, data, args = {}) {
591
599
  ...args,
592
600
  });
593
601
  }
602
+
603
+ let scNetworkErrorSignaled = false;
604
+
594
605
  function ajax_post(url, args) {
595
606
  $.ajax(url, {
596
607
  type: "POST",
@@ -600,10 +611,30 @@ function ajax_post(url, args) {
600
611
  ...(args || {}),
601
612
  })
602
613
  .done(ajax_done)
603
- .fail((e) =>
604
- ajax_done(e.responseJSON || { error: "Unknown error: " + e.responseText })
605
- );
614
+ .fail((e, ...more) => {
615
+ if (!checkNetworkError(e))
616
+ return ajax_done(
617
+ e.responseJSON || { error: "Unknown error: " + e.responseText }
618
+ );
619
+ });
606
620
  }
621
+
622
+ function checkNetworkError(e) {
623
+ if (e.readyState == 0 && !e.responseText && !e.responseJSON) {
624
+ //network error
625
+ if (scNetworkErrorSignaled) return true;
626
+ scNetworkErrorSignaled = true;
627
+ setTimeout(() => {
628
+ scNetworkErrorSignaled = false;
629
+ }, 1000);
630
+ notifyAlert({
631
+ type: "danger",
632
+ text: "Network connection error",
633
+ });
634
+ return true;
635
+ }
636
+ }
637
+
607
638
  function ajax_post_btn(e, reload_on_done, reload_delay) {
608
639
  var form = $(e).closest("form");
609
640
  var url = form.attr("action");
@@ -617,6 +648,7 @@ function ajax_post_btn(e, reload_on_done, reload_delay) {
617
648
  success: function () {
618
649
  if (reload_on_done) location.reload();
619
650
  },
651
+ error: checkNetworkError,
620
652
  complete: function () {
621
653
  if (reload_delay)
622
654
  setTimeout(function () {
@@ -638,6 +670,7 @@ function api_action_call(name, body) {
638
670
  success: function (res) {
639
671
  common_done(res.data);
640
672
  },
673
+ error: checkNetworkError,
641
674
  });
642
675
  }
643
676
 
package/routes/actions.js CHANGED
@@ -237,6 +237,13 @@ const triggerForm = async (req, trigger) => {
237
237
  showIf: { when_trigger: "Daily" },
238
238
  sublabel: req.__("UTC timezone"),
239
239
  },
240
+ {
241
+ name: "channel",
242
+ label: req.__("Time to run"),
243
+ input_type: "time_of_week",
244
+ showIf: { when_trigger: "Weekly" },
245
+ sublabel: req.__("UTC timezone"),
246
+ },
240
247
  {
241
248
  name: "channel",
242
249
  label: req.__("Channel"),
package/routes/admin.js CHANGED
@@ -309,9 +309,15 @@ router.get(
309
309
  backupForm.values.auto_backup_expire_days = getState().getConfig(
310
310
  "auto_backup_expire_days"
311
311
  );
312
- backupForm.values.backup_with_event_log = getState().getConfig(
312
+ aBackupFilePrefixForm.values.backup_with_event_log = getState().getConfig(
313
313
  "backup_with_event_log"
314
314
  );
315
+ aBackupFilePrefixForm.values.backup_with_system_zip = getState().getConfig(
316
+ "backup_with_system_zip"
317
+ );
318
+ aBackupFilePrefixForm.values.backup_system_zip_level = getState().getConfig(
319
+ "backup_system_zip_level"
320
+ );
315
321
  //
316
322
  const aSnapshotForm = snapshotForm(req);
317
323
  aSnapshotForm.values.snapshots_enabled =
@@ -702,6 +708,33 @@ const backupFilePrefixForm = (req) =>
702
708
  sublabel: req.__("Include table history in backup"),
703
709
  default: true,
704
710
  },
711
+ {
712
+ type: "Bool",
713
+ label: req.__("Include Event Logs"),
714
+ sublabel: req.__("Backup with event logs"),
715
+ name: "backup_with_event_log",
716
+ },
717
+ {
718
+ type: "Bool",
719
+ label: req.__("Use system zip"),
720
+ sublabel: req.__(
721
+ "Recommended. Executable <code>zip</code> must be installed"
722
+ ),
723
+ name: "backup_with_system_zip",
724
+ },
725
+ {
726
+ type: "Integer",
727
+ label: req.__("Zip compression level"),
728
+ sublabel: req.__("1=Fast, larger file, 9=Slow, smaller files"),
729
+ name: "backup_system_zip_level",
730
+ attributes: {
731
+ min: 1,
732
+ max: 9,
733
+ },
734
+ showIf: {
735
+ backup_with_system_zip: true,
736
+ },
737
+ },
705
738
  ],
706
739
  });
707
740
 
@@ -829,15 +862,6 @@ const autoBackupForm = (req) => {
829
862
  },
830
863
  ]
831
864
  : []),
832
- {
833
- type: "Bool",
834
- label: req.__("Include Event Logs"),
835
- sublabel: req.__("Backup with event logs"),
836
- name: "backup_with_event_log",
837
- showIf: {
838
- auto_backup_frequency: ["Daily", "Weekly"],
839
- },
840
- },
841
865
  ],
842
866
  });
843
867
  };
@@ -3514,6 +3538,7 @@ router.get(
3514
3538
  send_admin_page({
3515
3539
  res,
3516
3540
  req,
3541
+ page_title: req.__(`%s code page`, name),
3517
3542
  active_sub: "Development",
3518
3543
  sub2_page: req.__(`%s code page`, name),
3519
3544
  contents: {