@saltcorn/server 0.9.0-beta.1 → 0.9.0-beta.11

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/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@saltcorn/server",
3
- "version": "0.9.0-beta.1",
3
+ "version": "0.9.0-beta.11",
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.1",
10
- "@saltcorn/builder": "0.9.0-beta.1",
11
- "@saltcorn/data": "0.9.0-beta.1",
12
- "@saltcorn/admin-models": "0.9.0-beta.1",
13
- "@saltcorn/filemanager": "0.9.0-beta.1",
14
- "@saltcorn/markup": "0.9.0-beta.1",
15
- "@saltcorn/sbadmin2": "0.9.0-beta.1",
9
+ "@saltcorn/base-plugin": "0.9.0-beta.11",
10
+ "@saltcorn/builder": "0.9.0-beta.11",
11
+ "@saltcorn/data": "0.9.0-beta.11",
12
+ "@saltcorn/admin-models": "0.9.0-beta.11",
13
+ "@saltcorn/filemanager": "0.9.0-beta.11",
14
+ "@saltcorn/markup": "0.9.0-beta.11",
15
+ "@saltcorn/sbadmin2": "0.9.0-beta.11",
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.11.12",
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",
@@ -446,3 +446,7 @@ Copyright (c) 2017 Taha Paksu
446
446
  padding: 5px 9px;
447
447
  z-index: 1;
448
448
  }
449
+
450
+ div.builder-config-field {
451
+ margin-top: 0.5rem;
452
+ }
@@ -51,6 +51,7 @@ const nubBy = (prop, xs) => {
51
51
  });
52
52
  };
53
53
  function apply_showif() {
54
+ const isNode = typeof parent?.saltcorn?.data?.state === "undefined";
54
55
  $("[data-show-if]").each(function (ix, element) {
55
56
  var e = $(element);
56
57
  try {
@@ -78,6 +79,15 @@ function apply_showif() {
78
79
  console.error(e);
79
80
  }
80
81
  });
82
+ $("[data-dyn-href]").each(function (ix, element) {
83
+ const e = $(element);
84
+ const rec = get_form_record(e);
85
+ const href = new Function(
86
+ `{${Object.keys(rec).join(",")}}`,
87
+ "return " + e.attr("data-dyn-href")
88
+ )(rec);
89
+ e.attr("href", href);
90
+ });
81
91
  $("[data-calc-options]").each(function (ix, element) {
82
92
  var e = $(element);
83
93
  var data = JSON.parse(decodeURIComponent(e.attr("data-calc-options")));
@@ -118,10 +128,13 @@ function apply_showif() {
118
128
  const dynwhere = JSON.parse(
119
129
  decodeURIComponent(e.attr("data-fetch-options"))
120
130
  );
121
- //console.log("dynwhere", dynwhere);
122
- const qss = Object.entries(dynwhere.whereParsed).map(
123
- ([k, v]) => `${k}=${v[0] === "$" ? rec[v.substring(1)] : v}`
124
- );
131
+ if (window._sc_loglevel > 4) console.log("dynwhere", dynwhere);
132
+ const kvToQs = ([k, v]) => {
133
+ return k === "or" && Array.isArray(v)
134
+ ? v.map((v1) => Object.entries(v1).map(kvToQs).join("&")).join("&")
135
+ : `${k}=${v[0] === "$" ? rec[v.substring(1)] : v}`;
136
+ };
137
+ const qss = Object.entries(dynwhere.whereParsed).map(kvToQs);
125
138
  if (dynwhere.dereference) {
126
139
  if (Array.isArray(dynwhere.dereference))
127
140
  qss.push(...dynwhere.dereference.map((d) => `dereference=${d}`));
@@ -195,6 +208,9 @@ function apply_showif() {
195
208
  });
196
209
  $.ajax(`/api/${dynwhere.table}?${qs}`).then((resp) => {
197
210
  if (resp.success) {
211
+ if (window._sc_loglevel > 4)
212
+ console.log("dynwhere fetch", qs, resp.success);
213
+
198
214
  activate(resp.success, qs);
199
215
  const cacheNow = e.prop("data-fetch-options-cache") || {};
200
216
  e.prop("data-fetch-options-cache", {
@@ -263,10 +279,12 @@ function apply_showif() {
263
279
 
264
280
  if (typeof cache[recS] !== "undefined") {
265
281
  e.html(cache[recS]);
282
+ e.prop("data-source-url-current", recS);
266
283
  activate_onchange_coldef();
267
284
  return;
268
285
  }
269
- ajax_post_json(e.attr("data-source-url"), rec, {
286
+
287
+ const cb = {
270
288
  success: (data) => {
271
289
  e.html(data);
272
290
  const cacheNow = e.prop("data-source-url-cache") || {};
@@ -286,7 +304,11 @@ function apply_showif() {
286
304
  });
287
305
  e.html("");
288
306
  },
289
- });
307
+ };
308
+ if (isNode) ajax_post_json(e.attr("data-source-url"), rec, cb);
309
+ else {
310
+ local_post_json(e.attr("data-source-url"), rec, cb);
311
+ }
290
312
  });
291
313
  const locale =
292
314
  navigator.userLanguage ||
@@ -509,6 +531,11 @@ function initialize_page() {
509
531
  });
510
532
 
511
533
  $("form").change(apply_showif);
534
+ // also change if we select same
535
+ $("form select").on("blur", (e) => {
536
+ if (!e || !e.target) return;
537
+ $(e.target).closest("form").trigger("change");
538
+ });
512
539
  apply_showif();
513
540
  apply_showif();
514
541
  $("[data-inline-edit-dest-url]").each(function () {
@@ -530,6 +557,7 @@ function initialize_page() {
530
557
  var ajax = !!$(this).attr("data-inline-edit-ajax");
531
558
  var type = $(this).attr("data-inline-edit-type");
532
559
  var schema = $(this).attr("data-inline-edit-schema");
560
+ var decimalPlaces = $(this).attr("data-inline-edit-decimal-places");
533
561
  if (schema) {
534
562
  schema = JSON.parse(decodeURIComponent(schema));
535
563
  }
@@ -552,6 +580,7 @@ function initialize_page() {
552
580
  type,
553
581
  is_key,
554
582
  schema,
583
+ ...(decimalPlaces ? { decimalPlaces } : {}),
555
584
  })
556
585
  );
557
586
  const doAjaxOptionsFetch = (tblName, target) => {
@@ -604,7 +633,17 @@ function initialize_page() {
604
633
  }
605
634
  <input type="${
606
635
  type === "Integer" || type === "Float" ? "number" : "text"
607
- }" name="${key}" value="${escapeHtml(current)}">
636
+ }" ${
637
+ type === "Float"
638
+ ? `step="${
639
+ decimalPlaces
640
+ ? Math.round(
641
+ Math.pow(10, -decimalPlaces) * Math.pow(10, decimalPlaces)
642
+ ) / Math.pow(10, decimalPlaces)
643
+ : "any"
644
+ }"`
645
+ : ""
646
+ } name="${key}" value="${escapeHtml(current)}">
608
647
  <button type="submit" class="btn btn-sm btn-primary">OK</button>
609
648
  <button onclick="cancel_inline_edit(event, '${opts}')" type="button" class="btn btn-sm btn-danger"><i class="fas fa-times"></i></button>
610
649
  </form>`
@@ -657,10 +696,13 @@ function initialize_page() {
657
696
  setTimeout(() => {
658
697
  codes.forEach((el) => {
659
698
  //console.log($(el).attr("mode"), el);
699
+ if ($(el).hasClass("codemirror-enabled")) return;
700
+
660
701
  const cm = CodeMirror.fromTextArea(el, {
661
702
  lineNumbers: true,
662
703
  mode: $(el).attr("mode"),
663
704
  });
705
+ $(el).addClass("codemirror-enabled");
664
706
  cm.on(
665
707
  "change",
666
708
  $.debounce(() => {
@@ -771,6 +813,11 @@ function inline_submit_success(e, form, opts) {
771
813
  : ""
772
814
  }
773
815
  ${opts.current_label ? `data-inline-edit-current-label="${val}"` : ""}
816
+ ${
817
+ opts.decimalPlaces
818
+ ? `data-inline-edit-decimal-places="${opts.decimalPlaces}"`
819
+ : ""
820
+ }
774
821
  data-inline-edit-dest-url="${opts.url}">
775
822
  <span class="current">${val}</span>
776
823
  <i class="editicon ${!isNode ? "visible" : ""} fas fa-edit ms-1"></i>
@@ -948,14 +995,25 @@ function emptyAlerts() {
948
995
  $("#toasts-area").html("");
949
996
  }
950
997
 
951
- function press_store_button(clicked) {
998
+ function press_store_button(clicked, keepOld) {
952
999
  let btn = clicked;
953
1000
  if ($(clicked).is("form")) btn = $(clicked).find("button[type=submit]");
954
-
1001
+ if (keepOld) {
1002
+ const oldText = $(btn).html();
1003
+ $(btn).data("old-text", oldText);
1004
+ }
955
1005
  const width = $(btn).width();
956
1006
  $(btn).html('<i class="fas fa-spinner fa-spin"></i>').width(width);
957
1007
  }
958
1008
 
1009
+ function restore_old_button(btnId) {
1010
+ const btn = $(`#${btnId}`);
1011
+ const oldText = $(btn).data("old-text");
1012
+ btn.html(oldText);
1013
+ btn.css({ width: "" });
1014
+ btn.removeData("old-text");
1015
+ }
1016
+
959
1017
  function common_done(res, viewname, isWeb = true) {
960
1018
  const handle = (element, fn) => {
961
1019
  if (Array.isArray(element)) for (const current of element) fn(current);
@@ -994,6 +1052,7 @@ function common_done(res, viewname, isWeb = true) {
994
1052
  if (input.attr("type") === "checkbox")
995
1053
  input.prop("checked", res.set_fields[k]);
996
1054
  else input.val(res.set_fields[k]);
1055
+ input.trigger("set_form_field");
997
1056
  });
998
1057
  }
999
1058
  if (res.goto && !isWeb)
@@ -1011,6 +1070,7 @@ function common_done(res, viewname, isWeb = true) {
1011
1070
  if (
1012
1071
  prev.origin === next.origin &&
1013
1072
  prev.pathname === next.pathname &&
1073
+ prev.searchParams.toString() === next.searchParams.toString() &&
1014
1074
  next.hash !== prev.hash
1015
1075
  )
1016
1076
  location.reload();
@@ -340,6 +340,28 @@ 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",
355
+ });
356
+ restore_old_button(btnId);
357
+ }
358
+
359
+ function submitWithAjax(e) {
360
+ saveAndContinue(e, (res) => {
361
+ if (res && res.responseJSON && res.responseJSON.url_when_done)
362
+ window.location.href = res.responseJSON.url_when_done;
363
+ if (res && res.responseJSON && res.responseJSON.error)
364
+ notifyAlert({ type: "danger", text: res.responseJSON.error });
343
365
  });
344
366
  }
345
367
 
@@ -368,6 +390,9 @@ function saveAndContinue(e, k) {
368
390
  if (res.notify) {
369
391
  notifyAlert(res.notify);
370
392
  }
393
+ if (res.reload_page) {
394
+ location.reload(); //TODO notify to cookie if reload or goto
395
+ }
371
396
  },
372
397
  error: function (request) {
373
398
  var ct = request.getResponseHeader("content-type") || "";
@@ -388,8 +413,8 @@ function saveAndContinue(e, k) {
388
413
  }
389
414
  ajax_indicate_error(e, request);
390
415
  },
391
- complete: function () {
392
- if (k) k();
416
+ complete: function (res) {
417
+ if (k) k(res);
393
418
  },
394
419
  });
395
420
 
@@ -410,7 +435,8 @@ function updateMatchingRows(e, viewname) {
410
435
  }
411
436
  }
412
437
 
413
- function applyViewConfig(e, url, k) {
438
+ function applyViewConfig(e, url, k, event) {
439
+ if (event && event.target && event.target.id === "myEditor_icon") return;
414
440
  var form = $(e).closest("form");
415
441
  var form_data = form.serializeArray();
416
442
  const cfg = {};
package/routes/actions.js CHANGED
@@ -174,8 +174,9 @@ const triggerForm = async (req, trigger) => {
174
174
  attributes: {
175
175
  explainers: {
176
176
  Often: req.__("Every 5 minutes"),
177
- Never:
178
- req.__("Not scheduled but can be run as an action from a button click"),
177
+ Never: req.__(
178
+ "Not scheduled but can be run as an action from a button click"
179
+ ),
179
180
  },
180
181
  },
181
182
  },
@@ -201,6 +202,7 @@ const triggerForm = async (req, trigger) => {
201
202
  label: req.__("Action"),
202
203
  type: "String",
203
204
  required: true,
205
+ help: { topic: "Actions" },
204
206
  attributes: {
205
207
  calcOptions: ["when_trigger", action_options],
206
208
  },
@@ -388,6 +390,16 @@ router.get(
388
390
  return;
389
391
  }
390
392
  const action = getState().actions[trigger.action];
393
+ // get table related to trigger
394
+ const table = trigger.table_id
395
+ ? Table.findOne({ id: trigger.table_id })
396
+ : null;
397
+
398
+ const subtitle = span(
399
+ { class: "ms-3" },
400
+ trigger.action,
401
+ table ? ` on ` + a({ href: `/table/${table.name}` }, table.name) : ""
402
+ );
391
403
  if (!action) {
392
404
  req.flash("warning", req.__("Action not found"));
393
405
  res.redirect(`/actions/`);
@@ -402,7 +414,7 @@ router.get(
402
414
  form.values = trigger.configuration;
403
415
  const events = Trigger.when_options;
404
416
  const actions = Trigger.find({
405
- when_trigger: {or: ["API call", "Never"]},
417
+ when_trigger: { or: ["API call", "Never"] },
406
418
  });
407
419
  const tables = (await Table.find({})).map((t) => ({
408
420
  name: t.name,
@@ -418,6 +430,7 @@ router.get(
418
430
  type: "card",
419
431
  titleAjaxIndicator: true,
420
432
  title: req.__("Configure trigger %s", trigger.name),
433
+ subtitle,
421
434
  contents: {
422
435
  widths: [8, 4],
423
436
  besides: [
@@ -463,10 +476,6 @@ router.get(
463
476
  req.flash("warning", req.__("Action not configurable"));
464
477
  res.redirect(`/actions/`);
465
478
  } else {
466
- // get table related to trigger
467
- const table = trigger.table_id
468
- ? Table.findOne({ id: trigger.table_id })
469
- : null;
470
479
  // get configuration fields
471
480
  const cfgFields = await getActionConfigFields(action, table);
472
481
  // create form
@@ -489,6 +498,7 @@ router.get(
489
498
  type: "card",
490
499
  titleAjaxIndicator: true,
491
500
  title: req.__("Configure trigger %s", trigger.name),
501
+ subtitle,
492
502
  contents: renderForm(form, req.csrfToken()),
493
503
  },
494
504
  });
package/routes/admin.js CHANGED
@@ -285,6 +285,9 @@ router.get(
285
285
  backupForm.values.auto_backup_expire_days = getState().getConfig(
286
286
  "auto_backup_expire_days"
287
287
  );
288
+ backupForm.values.backup_with_event_log = getState().getConfig(
289
+ "backup_with_event_log"
290
+ );
288
291
  //
289
292
  const aSnapshotForm = snapshotForm(req);
290
293
  aSnapshotForm.values.snapshots_enabled =
@@ -721,6 +724,15 @@ const autoBackupForm = (req) =>
721
724
  auto_backup_destination: "Local directory",
722
725
  },
723
726
  },
727
+ {
728
+ type: "Bool",
729
+ label: req.__("Include Event Logs"),
730
+ sublabel: req.__("Backup with event logs"),
731
+ name: "backup_with_event_log",
732
+ showIf: {
733
+ auto_backup_frequency: ["Daily", "Weekly"],
734
+ },
735
+ },
724
736
  ],
725
737
  });
726
738
 
@@ -1545,7 +1557,7 @@ router.get(
1545
1557
  input({
1546
1558
  type: "hidden",
1547
1559
  name: "entryPointType",
1548
- value: "view",
1560
+ value: builderSettings.entryPointType || "view",
1549
1561
  id: "entryPointTypeID",
1550
1562
  }),
1551
1563
  div(
@@ -1578,7 +1590,8 @@ router.get(
1578
1590
  div(
1579
1591
  {
1580
1592
  class: `nav-link ${
1581
- !builderSettings.entryPointType || builderSettings.entryPointType === "view"
1593
+ !builderSettings.entryPointType ||
1594
+ builderSettings.entryPointType === "view"
1582
1595
  ? "active"
1583
1596
  : ""
1584
1597
  }`,
@@ -1613,7 +1626,8 @@ router.get(
1613
1626
  ? "d-none"
1614
1627
  : ""
1615
1628
  }`,
1616
- ...(!builderSettings.entryPointType || builderSettings.entryPointType === "view"
1629
+ ...(!builderSettings.entryPointType ||
1630
+ builderSettings.entryPointType === "view"
1617
1631
  ? { name: "entryPoint" }
1618
1632
  : {}),
1619
1633
  id: "viewInputID",
@@ -1636,7 +1650,8 @@ router.get(
1636
1650
  select(
1637
1651
  {
1638
1652
  class: `form-select ${
1639
- !builderSettings.entryPointType || builderSettings.entryPointType === "view"
1653
+ !builderSettings.entryPointType ||
1654
+ builderSettings.entryPointType === "view"
1640
1655
  ? "d-none"
1641
1656
  : ""
1642
1657
  }`,
package/routes/fields.js CHANGED
@@ -409,7 +409,7 @@ const fieldFlow = (req) =>
409
409
  instance_options[model.name].push(...instances.map((i) => i.name));
410
410
 
411
411
  const outputs = await applyAsync(
412
- model.templateObj.prediction_outputs || [],
412
+ model.templateObj?.prediction_outputs || [], // unit tests can have templateObj undefined
413
413
  { table, configuration: model.configuration }
414
414
  );
415
415
  output_options[model.name] = outputs.map((o) => o.name);
@@ -840,6 +840,11 @@ router.post(
840
840
  const table = Table.findOne({ name: tableName });
841
841
  const role = req.user && req.user.id ? req.user.role_id : 100;
842
842
 
843
+ getState().log(
844
+ 5,
845
+ `Route /fields/show-calculated/${tableName}/${fieldName}/${fieldview} user=${req.user?.id}`
846
+ );
847
+
843
848
  const fields = table.getFields();
844
849
  let row = { ...req.body };
845
850
  if (row && Object.keys(row).length > 0) readState(row, fields);
@@ -1018,6 +1023,13 @@ router.post(
1018
1023
  const { tableName, fieldName, fieldview } = req.params;
1019
1024
  const table = Table.findOne({ name: tableName });
1020
1025
  const fields = table.getFields();
1026
+ const state = getState();
1027
+
1028
+ state.log(
1029
+ 5,
1030
+ `Route /fields/preview/${tableName}/${fieldName}/${fieldview} user=${req.user?.id}`
1031
+ );
1032
+
1021
1033
  let field, row, value;
1022
1034
  if (fieldName.includes(".")) {
1023
1035
  const [refNm, targetNm] = fieldName.split(".");
@@ -1048,9 +1060,9 @@ router.post(
1048
1060
  }
1049
1061
  const fieldviews =
1050
1062
  field.type === "Key"
1051
- ? getState().keyFieldviews
1063
+ ? state.keyFieldviews
1052
1064
  : field.type === "File"
1053
- ? getState().fileviews
1065
+ ? state.fileviews
1054
1066
  : field.type.fieldviews;
1055
1067
  if (!field.type || !fieldviews) {
1056
1068
  res.send("");
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.find({});
47
+ const tables = await Table.find_with_external({});
48
48
  const dynTableOptions = tables.map((t) => t.name);
49
49
  const dynOrderFieldOptions = {},
50
50
  dynSectionFieldOptions = {};