@saltcorn/server 0.7.0 → 0.7.1-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/locales/en.json CHANGED
@@ -878,5 +878,10 @@
878
878
  "Enter your two-factor code in order to disable it": "Enter your two-factor code in order to disable it",
879
879
  "Allow the user to enter a new key that is not in the schema": "Allow the user to enter a new key that is not in the schema",
880
880
  "Check for updates": "Check for updates",
881
- "Versions refreshed": "Versions refreshed"
881
+ "Versions refreshed": "Versions refreshed",
882
+ "Configuration check": "Configuration check",
883
+ "Configuration errors": "Configuration errors",
884
+ "Configuration checks passed": "Configuration checks passed",
885
+ "On delete cascade": "On delete cascade",
886
+ "If the parent row is deleted, automatically delete the child rows.": "If the parent row is deleted, automatically delete the child rows."
882
887
  }
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@saltcorn/server",
3
- "version": "0.7.0",
3
+ "version": "0.7.1-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.7.0",
10
- "@saltcorn/builder": "0.7.0",
11
- "@saltcorn/data": "0.7.0",
12
- "@saltcorn/admin-models": "0.7.0",
13
- "@saltcorn/markup": "0.7.0",
14
- "@saltcorn/sbadmin2": "0.7.0",
9
+ "@saltcorn/base-plugin": "0.7.1-beta.2",
10
+ "@saltcorn/builder": "0.7.1-beta.2",
11
+ "@saltcorn/data": "0.7.1-beta.2",
12
+ "@saltcorn/admin-models": "0.7.1-beta.2",
13
+ "@saltcorn/markup": "0.7.1-beta.2",
14
+ "@saltcorn/sbadmin2": "0.7.1-beta.2",
15
15
  "@socket.io/cluster-adapter": "^0.1.0",
16
16
  "@socket.io/sticky": "^1.0.1",
17
17
  "aws-sdk": "^2.1037.0",
@@ -1,9 +1,13 @@
1
1
  function MenuEditor(e, t) {
2
+ const radioValue = (e) =>
3
+ $(`input:radio[name ='${$(e).attr("name")}']:checked`).val();
2
4
  var s = $("#" + e).data("level", "0"),
3
5
  l = {
4
6
  labelEdit: '<i class="fas fa-edit clickable"></i>',
5
7
  labelRemove: '<i class="fas fa-trash-alt clickable"></i>',
6
8
  textConfirmDelete: "This item will be deleted. Are you sure?",
9
+ getLabelText: (e) => e.text,
10
+ onUpdate: () => {},
7
11
  iconPicker: { cols: 4, rows: 4, footer: !1, iconset: "fontawesome5" },
8
12
  maxLevel: -1,
9
13
  listOptions: {
@@ -28,6 +32,8 @@ function MenuEditor(e, t) {
28
32
  },
29
33
  };
30
34
  $.extend(!0, l, t);
35
+ var mqeSets = l;
36
+
31
37
  var n = null,
32
38
  o = !0,
33
39
  i = null,
@@ -35,10 +41,11 @@ function MenuEditor(e, t) {
35
41
  a = l.iconPicker,
36
42
  c = ((t = l.listOptions), $("#" + e + "_icon").iconpicker(a));
37
43
  function d() {
38
- i[0].reset(),
39
- (c = c.iconpicker(a)).iconpicker("setIcon", "empty"),
40
- r.attr("disabled", !0),
41
- (n = null);
44
+ if (!i || !i[0])
45
+ i[0].reset(),
46
+ (c = c.iconpicker(a)).iconpicker("setIcon", "empty"),
47
+ r.attr("disabled", !0),
48
+ (n = null);
42
49
  }
43
50
  function p(e) {
44
51
  return $("<a>")
@@ -117,7 +124,14 @@ function MenuEditor(e, t) {
117
124
  (function (e) {
118
125
  var t = e.data();
119
126
  $.each(t, function (e, t) {
120
- i.find("[name=" + e + "]").val(t);
127
+ var el = i.find("[name=" + e + "]");
128
+ if (el.prop("type") == "checkbox") el.prop("checked", !!t);
129
+ else if (el.prop("type") == "radio") {
130
+ $(`input[name=${el.prop("name")}][value="${t}"]`).prop(
131
+ "checked",
132
+ true
133
+ );
134
+ } else el.val(t);
121
135
  }),
122
136
  i.find(".item-menu").first().focus(),
123
137
  t.hasOwnProperty("icon")
@@ -125,6 +139,7 @@ function MenuEditor(e, t) {
125
139
  : c.iconpicker("setIcon", "empty");
126
140
  r.removeAttr("disabled");
127
141
  })((n = $(this).closest("li")));
142
+ l.onUpdate();
128
143
  }),
129
144
  s.on("click", ".btnUp", function (e) {
130
145
  e.preventDefault();
@@ -180,20 +195,34 @@ function MenuEditor(e, t) {
180
195
  if (null !== e) {
181
196
  var t = e.data("icon");
182
197
  i.find(".item-menu").each(function () {
183
- e.data($(this).attr("name"), $(this).val());
198
+ if (!$(this).prop("disabled"))
199
+ e.data(
200
+ $(this).attr("name"),
201
+ $(this).attr("type") === "radio"
202
+ ? radioValue(this)
203
+ : $(this).attr("type") === "checkbox"
204
+ ? $(this).prop("checked")
205
+ : $(this).val()
206
+ );
184
207
  }),
185
208
  e.children().children("i").removeClass(t).addClass(e.data("icon")),
186
- e.find("span.txt").first().text(e.data("text")),
209
+ e.find("span.txt").first().text(mqeSets.getLabelText(e.data())),
187
210
  d();
188
211
  }
189
212
  }),
190
213
  (this.add = function () {
191
214
  var e = {};
192
215
  i.find(".item-menu").each(function () {
193
- e[$(this).attr("name")] = $(this).val();
216
+ if (!$(this).prop("disabled"))
217
+ e[$(this).attr("name")] =
218
+ $(this).attr("type") === "radio"
219
+ ? radioValue(this)
220
+ : $(this).attr("type") === "checkbox"
221
+ ? $(this).prop("checked")
222
+ : $(this).val();
194
223
  });
195
224
  var t = u(),
196
- l = $("<span>").addClass("txt").text(e.text),
225
+ l = $("<span>").addClass("txt").text(mqeSets.getLabelText(e)),
197
226
  n = $("<i>").addClass(e.icon),
198
227
  o = $("<div>")
199
228
  .css({ overflow: "auto" })
@@ -251,7 +280,7 @@ function MenuEditor(e, t) {
251
280
  d = $("<i>").addClass(s.icon),
252
281
  p = $("<span>")
253
282
  .addClass("txt")
254
- .append(s.text)
283
+ .append(mqeSets.getLabelText(s))
255
284
  .css("margin-right", "5px"),
256
285
  f = u();
257
286
  c.append(d).append("&nbsp;").append(p).append(f),
@@ -486,34 +515,38 @@ function MenuEditor(e, t) {
486
515
  : h(d),
487
516
  (d.oElOld = d.oEl),
488
517
  (s.el[0].style.visibility = "hidden"),
489
- (d.oEl = oEl = (function (t, s) {
490
- if (!document.elementFromPoint) return null;
491
- var l = d.isRelEFP;
492
- if (null === l) {
493
- var n, o;
494
- (n = d.doc.scrollTop()) > 0 &&
495
- (l =
496
- null ==
497
- (o = document.elementFromPoint(
498
- 0,
499
- n + e(window).height() - 1
500
- )) || "HTML" == o.tagName.toUpperCase()),
501
- (n = d.doc.scrollLeft()) > 0 &&
518
+ (d.oEl = oEl =
519
+ (function (t, s) {
520
+ if (!document.elementFromPoint) return null;
521
+ var l = d.isRelEFP;
522
+ if (null === l) {
523
+ var n, o;
524
+ (n = d.doc.scrollTop()) > 0 &&
502
525
  (l =
503
526
  null ==
504
527
  (o = document.elementFromPoint(
505
- n + e(window).width() - 1,
506
- 0
507
- )) || "HTML" == o.tagName.toUpperCase());
508
- }
509
- l && ((t -= d.doc.scrollLeft()), (s -= d.doc.scrollTop()));
510
- var i = e(document.elementFromPoint(t, s));
511
- if (!d.rootEl.el.find(i).length) return null;
512
- if (i.is("#sortableListsPlaceholder") || i.is("#sortableListsHint"))
513
- return null;
514
- if (!i.is("li")) return (i = i.closest("li"))[0] ? i : null;
515
- if (i.is("li")) return i;
516
- })(t.pageX, t.pageY)),
528
+ 0,
529
+ n + e(window).height() - 1
530
+ )) || "HTML" == o.tagName.toUpperCase()),
531
+ (n = d.doc.scrollLeft()) > 0 &&
532
+ (l =
533
+ null ==
534
+ (o = document.elementFromPoint(
535
+ n + e(window).width() - 1,
536
+ 0
537
+ )) || "HTML" == o.tagName.toUpperCase());
538
+ }
539
+ l && ((t -= d.doc.scrollLeft()), (s -= d.doc.scrollTop()));
540
+ var i = e(document.elementFromPoint(t, s));
541
+ if (!d.rootEl.el.find(i).length) return null;
542
+ if (
543
+ i.is("#sortableListsPlaceholder") ||
544
+ i.is("#sortableListsHint")
545
+ )
546
+ return null;
547
+ if (!i.is("li")) return (i = i.closest("li"))[0] ? i : null;
548
+ if (i.is("li")) return i;
549
+ })(t.pageX, t.pageY)),
517
550
  (s.el[0].style.visibility = "visible"),
518
551
  (function (e, t) {
519
552
  var s = t.oEl;
@@ -224,7 +224,3 @@ footer.bs-mobile-nav-footer {
224
224
  .form-group {
225
225
  margin-bottom: 1rem;
226
226
  }
227
-
228
- .table-responsive {
229
- overflow: visible;
230
- }
@@ -8,6 +8,20 @@ jQuery.fn.swapWith = function (to) {
8
8
  });
9
9
  };
10
10
 
11
+ //avoids hiding in overflow:hidden
12
+ function init_bs5_dropdowns() {
13
+ $("body").on(
14
+ "show.bs.dropdown",
15
+ "table [data-bs-toggle=dropdown]",
16
+ function () {
17
+ let target;
18
+ if (!$("#page-inner-content").length) target = $("body");
19
+ else target = $("#page-inner-content");
20
+ let dropdown = bootstrap.Dropdown.getInstance(this);
21
+ $(dropdown._menu).insertAfter(target);
22
+ }
23
+ );
24
+ }
11
25
  function sortby(k, desc) {
12
26
  set_state_fields({ _sortby: k, _sortdesc: desc ? "on" : { unset: true } });
13
27
  }
@@ -98,6 +112,8 @@ function get_form_record(e, select_labels) {
98
112
  .each(function () {
99
113
  if (select_labels && $(this).prop("tagName").toLowerCase() === "select")
100
114
  rec[$(this).attr("name")] = $(this).find("option:selected").text();
115
+ else if ($(this).prop("type") === "checkbox")
116
+ rec[$(this).attr("name")] = $(this).prop("checked");
101
117
  else rec[$(this).attr("name")] = $(this).val();
102
118
  });
103
119
  return rec;
@@ -293,6 +309,7 @@ function initialize_page() {
293
309
  el.text(date.toLocaleDateString(locale, options));
294
310
  });
295
311
  $('a[data-bs-toggle="tab"].deeplink').historyTabs();
312
+ init_bs5_dropdowns();
296
313
  }
297
314
 
298
315
  $(initialize_page);
@@ -511,7 +528,8 @@ function globalErrorCatcher(message, source, lineno, colno, error) {
511
528
  }
512
529
 
513
530
  function press_store_button(clicked) {
514
- $(clicked).html('<i class="fas fa-spinner fa-spin"></i>');
531
+ const width = $(clicked).width();
532
+ $(clicked).html('<i class="fas fa-spinner fa-spin"></i>').width(width);
515
533
  }
516
534
 
517
535
  function ajax_modal(url, opts = {}) {
@@ -789,6 +807,26 @@ async function fill_formula_btn_click(btn, k) {
789
807
  if (k) k();
790
808
  }
791
809
 
810
+ const columnSummary = (col) => {
811
+ if (!col) return "Unknown";
812
+ switch (col.type) {
813
+ case "Field":
814
+ return `Field ${col.field_name} ${col.fieldview}`;
815
+ case "Link":
816
+ return `Link ${col.link_text}`;
817
+ case "JoinField":
818
+ return `Join ${col.join_field}`;
819
+ case "ViewLink":
820
+ return `View ${col.view_label || col.view.split(":")[1] || ""}`;
821
+ case "Action":
822
+ return `Action ${col.action_label || col.action_name}`;
823
+ case "Aggregation":
824
+ return `${col.stat} ${col.agg_field} ${col.agg_relation}`;
825
+ default:
826
+ return "Unknown";
827
+ }
828
+ };
829
+
792
830
  /*
793
831
  https://github.com/jeffdavidgreen/bootstrap-html5-history-tabs/blob/master/bootstrap-history-tabs.js
794
832
  Copyright (c) 2015 Jeff Green
package/routes/admin.js CHANGED
@@ -36,6 +36,9 @@ const {
36
36
  button,
37
37
  span,
38
38
  p,
39
+ code,
40
+ h5,
41
+ pre,
39
42
  } = require("@saltcorn/markup/tags");
40
43
  const db = require("@saltcorn/data/db");
41
44
  const {
@@ -50,6 +53,9 @@ const {
50
53
  create_backup,
51
54
  restore,
52
55
  } = require("@saltcorn/admin-models/models/backup");
56
+ const {
57
+ runConfigurationCheck,
58
+ } = require("@saltcorn/admin-models/models/config-check");
53
59
  const fs = require("fs");
54
60
  const load_plugins = require("../load_plugins");
55
61
  const {
@@ -365,6 +371,18 @@ router.get(
365
371
  ),
366
372
  hr(),
367
373
 
374
+ a(
375
+ {
376
+ href: "/admin/configuration-check",
377
+ class: "btn btn-info",
378
+ onClick: "press_store_button(this)",
379
+ },
380
+ i({ class: "fas fa-stethoscope" }),
381
+ " ",
382
+ req.__("Configuration check")
383
+ ),
384
+ hr(),
385
+
368
386
  a(
369
387
  { href: "/admin/clear-all", class: "btn btn-danger" },
370
388
  i({ class: "fas fa-trash-alt" }),
@@ -502,6 +520,7 @@ router.post(
502
520
  `Upgrade done (if it was available) with code ${code}.\n\nPress the BACK button in your browser, then RELOAD the page.`
503
521
  );
504
522
  setTimeout(() => {
523
+ if (process.send) process.send("RestartServer");
505
524
  process.exit(0);
506
525
  }, 100);
507
526
  });
@@ -740,6 +759,49 @@ router.get(
740
759
  })
741
760
  );
742
761
 
762
+ router.get(
763
+ "/configuration-check",
764
+ isAdmin,
765
+ error_catcher(async (req, res) => {
766
+ const { passes, errors, pass } = await runConfigurationCheck(req);
767
+ const mkError = (err) =>
768
+ div(
769
+ { class: "alert alert-danger", role: "alert" },
770
+ pre({ class: "mb-0" }, code(err))
771
+ );
772
+ res.sendWrap(req.__(`Admin`), {
773
+ above: [
774
+ {
775
+ type: "breadcrumbs",
776
+ crumbs: [
777
+ { text: req.__("Settings") },
778
+ { text: req.__("Admin"), href: "/admin" },
779
+ { text: req.__("Configuration check") },
780
+ ],
781
+ },
782
+ {
783
+ type: "card",
784
+ title: req.__("Configuration errors"),
785
+ contents: div(
786
+ pass
787
+ ? div(
788
+ { class: "alert alert-success", role: "alert" },
789
+ i({ class: "fas fa-check-circle fa-lg me-2" }),
790
+ h5({ class: "d-inline" }, "No errors detected")
791
+ )
792
+ : errors.map(mkError)
793
+ ),
794
+ },
795
+ {
796
+ type: "card",
797
+ title: req.__("Configuration checks passed"),
798
+ contents: div(pre(code(passes.join("\n")))),
799
+ },
800
+ ],
801
+ });
802
+ })
803
+ );
804
+
743
805
  /**
744
806
  * @name post/clear-all
745
807
  * @function
package/routes/fields.js CHANGED
@@ -181,6 +181,7 @@ const fieldFlow = (req) =>
181
181
  var attributes = context.attributes || {};
182
182
  attributes.default = context.default;
183
183
  attributes.summary_field = context.summary_field;
184
+ attributes.on_delete_cascade = context.on_delete_cascade;
184
185
  const {
185
186
  table_id,
186
187
  name,
@@ -377,6 +378,14 @@ const fieldFlow = (req) =>
377
378
  input_type: "select",
378
379
  options: keyfields,
379
380
  }),
381
+ new Field({
382
+ name: "on_delete_cascade",
383
+ label: req.__("On delete cascade"),
384
+ type: "Bool",
385
+ sublabel: req.__(
386
+ "If the parent row is deleted, automatically delete the child rows."
387
+ ),
388
+ }),
380
389
  ],
381
390
  });
382
391
  },
package/routes/menu.js CHANGED
@@ -10,7 +10,6 @@ const Field = require("@saltcorn/data/models/field");
10
10
  const Form = require("@saltcorn/data/models/form");
11
11
  const { isAdmin, setTenant, error_catcher } = require("./utils.js");
12
12
  const { getState } = require("@saltcorn/data/db/state");
13
- const FieldRepeat = require("@saltcorn/data/models/fieldrepeat");
14
13
  const File = require("@saltcorn/data/models/file");
15
14
  const User = require("@saltcorn/data/models/user");
16
15
  const View = require("@saltcorn/data/models/view");
package/routes/plugins.js CHANGED
@@ -14,7 +14,7 @@ const {
14
14
  post_btn,
15
15
  post_delete_btn,
16
16
  } = require("@saltcorn/markup");
17
- const { getState } = require("@saltcorn/data/db/state");
17
+ const { getState, restart_tenant } = require("@saltcorn/data/db/state");
18
18
  const Form = require("@saltcorn/data/models/form");
19
19
  const Field = require("@saltcorn/data/models/field");
20
20
  const Plugin = require("@saltcorn/data/models/plugin");
@@ -52,6 +52,7 @@ const path = require("path");
52
52
  const { get_latest_npm_version } = require("@saltcorn/data/models/config");
53
53
  const { flash_restart } = require("../markup/admin.js");
54
54
  const { sleep } = require("@saltcorn/data/utils");
55
+ const { loadAllPlugins } = require("../load_plugins");
55
56
 
56
57
  /**
57
58
  * @type {object}
@@ -647,9 +648,10 @@ router.get(
647
648
  error_catcher(async (req, res) => {
648
649
  const { plugin } = req.params;
649
650
  const filepath = req.params[0];
650
- const location = getState().plugin_locations[
651
- plugin.includes("@") ? plugin.split("@")[0] : plugin
652
- ];
651
+ const location =
652
+ getState().plugin_locations[
653
+ plugin.includes("@") ? plugin.split("@")[0] : plugin
654
+ ];
653
655
  if (location) {
654
656
  const safeFile = path
655
657
  .normalize(filepath)
@@ -841,7 +843,9 @@ router.get(
841
843
  await plugin.upgrade_version((p, f) => load_plugins.loadPlugin(p, f));
842
844
  }
843
845
  req.flash("success", req.__(`Plugins up-to-date`));
844
-
846
+ await restart_tenant(loadAllPlugins);
847
+ process.send &&
848
+ process.send({ restart_tenant: true, tenant: db.getTenantSchema() });
845
849
  res.redirect(`/plugins`);
846
850
  })
847
851
  );
@@ -38,6 +38,7 @@ const View = require("@saltcorn/data/models/view");
38
38
  const Workflow = require("@saltcorn/data/models/workflow");
39
39
  const User = require("@saltcorn/data/models/user");
40
40
  const Page = require("@saltcorn/data/models/page");
41
+ const db = require("@saltcorn/data/db");
41
42
 
42
43
  const { add_to_menu } = require("@saltcorn/admin-models/models/pack");
43
44
  const { editRoleForm } = require("../markup/forms.js");
@@ -534,7 +535,23 @@ const respondWorkflow = (view, wf, wfres, req, res) => {
534
535
  if (wfres.flash) req.flash(wfres.flash[0], wfres.flash[1]);
535
536
  if (wfres.renderForm)
536
537
  res.sendWrap(
537
- req.__(`View configuration`),
538
+ {
539
+ title: req.__(`View configuration`),
540
+ headers: [
541
+ {
542
+ script: `/static_assets/${db.connectObj.version_tag}/jquery-menu-editor.min.js`,
543
+ },
544
+ {
545
+ script: `/static_assets/${db.connectObj.version_tag}/iconset-fontawesome5-3-1.min.js`,
546
+ },
547
+ {
548
+ script: `/static_assets/${db.connectObj.version_tag}/bootstrap-iconpicker.js`,
549
+ },
550
+ {
551
+ css: `/static_assets/${db.connectObj.version_tag}/bootstrap-iconpicker.min.css`,
552
+ },
553
+ ],
554
+ },
538
555
  wrap(renderForm(wfres.renderForm, req.csrfToken()))
539
556
  );
540
557
  else if (wfres.renderBuilder) {