@saltcorn/server 0.9.5-beta.2 → 0.9.5-beta.20

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,19 +1,20 @@
1
1
  {
2
2
  "name": "@saltcorn/server",
3
- "version": "0.9.5-beta.2",
3
+ "version": "0.9.5-beta.20",
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.2",
11
- "@saltcorn/builder": "0.9.5-beta.2",
12
- "@saltcorn/data": "0.9.5-beta.2",
13
- "@saltcorn/admin-models": "0.9.5-beta.2",
14
- "@saltcorn/filemanager": "0.9.5-beta.2",
15
- "@saltcorn/markup": "0.9.5-beta.2",
16
- "@saltcorn/sbadmin2": "0.9.5-beta.2",
10
+ "@saltcorn/base-plugin": "0.9.5-beta.20",
11
+ "@saltcorn/builder": "0.9.5-beta.20",
12
+ "@saltcorn/data": "0.9.5-beta.20",
13
+ "@saltcorn/admin-models": "0.9.5-beta.20",
14
+ "@saltcorn/filemanager": "0.9.5-beta.20",
15
+ "@saltcorn/markup": "0.9.5-beta.20",
16
+ "@saltcorn/plugins-loader": "0.9.5-beta.20",
17
+ "@saltcorn/sbadmin2": "0.9.5-beta.20",
17
18
  "@socket.io/cluster-adapter": "^0.2.1",
18
19
  "@socket.io/sticky": "^1.0.1",
19
20
  "adm-zip": "0.5.10",
@@ -26,6 +27,7 @@
26
27
  "cors": "2.8.5",
27
28
  "csurf": "^1.11.0",
28
29
  "csv-stringify": "^5.5.0",
30
+ "dockerode": "~4.0.2",
29
31
  "express": "^4.17.1",
30
32
  "express-fileupload": "^1.1.8",
31
33
  "express-promise-router": "^3.0.3",
@@ -37,7 +39,6 @@
37
39
  "i18n": "^0.15.1",
38
40
  "imapflow": "1.0.123",
39
41
  "jsonwebtoken": "^9.0.0",
40
- "live-plugin-manager": "^0.17.1",
41
42
  "markdown-it": "^13.0.2",
42
43
  "moment": "^2.29.4",
43
44
  "multer": "1.4.5-lts.1",
@@ -70,8 +71,8 @@
70
71
  },
71
72
  "repository": "github:saltcorn/saltcorn",
72
73
  "devDependencies": {
73
- "jest": "26.6.3",
74
- "jest-environment-jsdom": "^26.6.2",
74
+ "jest": "^28.1.3",
75
+ "jest-environment-jsdom": "28.1.3",
75
76
  "supertest": "^6.3.3"
76
77
  },
77
78
  "scripts": {
@@ -84,11 +85,13 @@
84
85
  "testEnvironment": "node",
85
86
  "testPathIgnorePatterns": [
86
87
  "/node_modules/",
87
- "/plugin_packages/"
88
+ "/plugin_packages/",
89
+ "/plugins_folder/"
88
90
  ],
89
91
  "coveragePathIgnorePatterns": [
90
92
  "/node_modules/",
91
- "/plugin_packages/"
93
+ "/plugin_packages/",
94
+ "/plugins_folder/"
92
95
  ],
93
96
  "moduleNameMapper": {
94
97
  "@saltcorn/sqlite/(.*)": "<rootDir>/../sqlite/dist/$1",
@@ -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 {
@@ -159,10 +159,18 @@ function apply_showif() {
159
159
  decodeURIComponent(e.attr("data-fetch-options"))
160
160
  );
161
161
  if (window._sc_loglevel > 4) console.log("dynwhere", dynwhere);
162
- const kvToQs = ([k, v]) => {
162
+ const kvToQs = ([k, v], is_or) => {
163
163
  return k === "or" && Array.isArray(v)
164
- ? v.map((v1) => Object.entries(v1).map(kvToQs).join("&")).join("&")
165
- : `${k}=${v[0] === "$" ? rec[v.substring(1)] : v}`;
164
+ ? v
165
+ .map((v1) =>
166
+ Object.entries(v1)
167
+ .map((kv) => kvToQs(kv, true))
168
+ .join("&")
169
+ )
170
+ .join("&")
171
+ : `${k}=${v[0] === "$" ? rec[v.substring(1)] : v}${
172
+ is_or ? "&_or_field=" + k : ""
173
+ }`;
166
174
  };
167
175
  const qss = Object.entries(dynwhere.whereParsed).map(kvToQs);
168
176
  if (dynwhere.dereference) {
@@ -571,7 +579,7 @@ function reload_on_init() {
571
579
  }
572
580
  function initialize_page() {
573
581
  if (window._sc_locale && window.dayjs) dayjs.locale(window._sc_locale);
574
- const isNode = typeof parent?.saltcorn?.data?.state === "undefined";
582
+ const isNode = getIsNode();
575
583
  //console.log("init page");
576
584
  $(".blur-on-enter-keypress").bind("keyup", function (e) {
577
585
  if (e.keyCode === 13) e.target.blur();
@@ -743,23 +751,58 @@ function initialize_page() {
743
751
  </form>`
744
752
  );
745
753
  });
746
- $("[mobile-img-path]").each(async function () {
747
- if (parent.loadEncodedFile) {
748
- const fileId = $(this).attr("mobile-img-path");
749
- const base64Encoded = await parent.loadEncodedFile(fileId);
750
- this.src = base64Encoded;
751
- }
752
- });
753
- $("[mobile-bg-img-path]").each(async function () {
754
- if (parent.loadEncodedFile) {
755
- const fileId = $(this).attr("mobile-bg-img-path");
756
- if (fileId) {
754
+ if (!isNode) {
755
+ $("[mobile-img-path]").each(async function () {
756
+ if (parent.loadEncodedFile) {
757
+ const fileId = $(this).attr("mobile-img-path");
757
758
  const base64Encoded = await parent.loadEncodedFile(fileId);
758
- this.style.backgroundImage = `url("${base64Encoded}")`;
759
+ this.src = base64Encoded;
759
760
  }
760
- }
761
- });
761
+ });
762
762
 
763
+ $("[mobile-bg-img-path]").each(async function () {
764
+ if (parent.loadEncodedFile) {
765
+ const fileId = $(this).attr("mobile-bg-img-path");
766
+ if (fileId) {
767
+ const base64Encoded = await parent.loadEncodedFile(fileId);
768
+ this.style.backgroundImage = `url("${base64Encoded}")`;
769
+ }
770
+ }
771
+ });
772
+
773
+ $("a").each(function () {
774
+ let path = $(this).attr("href") || "";
775
+ if (path.startsWith("http")) {
776
+ const url = new URL(path);
777
+ path = `${url.pathname}${url.search}`;
778
+ }
779
+ if (path.startsWith("/view/")) {
780
+ const jThis = $(this);
781
+ const skip = jThis.attr("skip-mobile-adjust");
782
+ if (!skip) {
783
+ jThis.attr("href", `javascript:execLink('${path}')`);
784
+ if (jThis.find("i,img").length === 0 && !jThis.css("color")) {
785
+ jThis.css(
786
+ "color",
787
+ "rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,1))"
788
+ );
789
+ }
790
+ }
791
+ }
792
+ });
793
+
794
+ $("img").each(async function () {
795
+ if (parent.loadEncodedFile) {
796
+ const jThis = $(this);
797
+ const src = jThis.attr("src");
798
+ if (src?.startsWith("/files/serve/")) {
799
+ const fileId = src.replace("/files/serve/", "");
800
+ const base64Encoded = await parent.loadEncodedFile(fileId);
801
+ this.src = base64Encoded;
802
+ }
803
+ }
804
+ });
805
+ }
763
806
  function setExplainer(that) {
764
807
  var id = $(that).attr("id") + "_explainer";
765
808
 
@@ -877,7 +920,7 @@ function initialize_page() {
877
920
  $(initialize_page);
878
921
 
879
922
  function cancel_inline_edit(e, opts1) {
880
- const isNode = typeof parent?.saltcorn?.data?.state === "undefined";
923
+ const isNode = getIsNode();
881
924
  var opts = JSON.parse(decodeURIComponent(opts1 || "") || "{}");
882
925
  var form = $(e.target).closest("form");
883
926
  form.replaceWith(opts.resetHtml);
@@ -885,7 +928,7 @@ function cancel_inline_edit(e, opts1) {
885
928
  }
886
929
 
887
930
  function inline_submit_success(e, form, opts) {
888
- const isNode = typeof parent?.saltcorn?.data?.state === "undefined";
931
+ const isNode = getIsNode();
889
932
  const formDataArray = form.serializeArray();
890
933
  if (opts) {
891
934
  let fdEntry = formDataArray.find((f) => f.name == opts.key);
@@ -1025,6 +1068,15 @@ function tristateClick(e, required) {
1025
1068
  }
1026
1069
  }
1027
1070
 
1071
+ function getIsNode() {
1072
+ try {
1073
+ return typeof parent?.saltcorn?.data?.state === "undefined";
1074
+ } catch (e) {
1075
+ //probably in an iframe
1076
+ return true;
1077
+ }
1078
+ }
1079
+
1028
1080
  function buildToast(txt, type, spin) {
1029
1081
  const realtype = type === "error" ? "danger" : type;
1030
1082
  const icon =
@@ -1035,7 +1087,7 @@ function buildToast(txt, type, spin) {
1035
1087
  : realtype === "warning"
1036
1088
  ? "fa-exclamation-triangle"
1037
1089
  : "";
1038
- const isNode = typeof parent?.saltcorn?.data?.state === "undefined";
1090
+ const isNode = getIsNode();
1039
1091
  const rndid = `tab${Math.floor(Math.random() * 16777215).toString(16)}`;
1040
1092
  return {
1041
1093
  id: rndid,
@@ -1167,8 +1219,10 @@ async function common_done(res, viewname, isWeb = true) {
1167
1219
  await handle(res.notify_success, (text) =>
1168
1220
  notifyAlert({ type: "success", text: text })
1169
1221
  );
1170
- if (res.set_fields && viewname) {
1171
- const form = $(`form[data-viewname="${viewname}"]`);
1222
+ if (res.set_fields && (viewname || res.set_fields._viewname)) {
1223
+ const form = $(
1224
+ `form[data-viewname="${res.set_fields._viewname || viewname}"]`
1225
+ );
1172
1226
  if (form.length === 0 && set_state_fields) {
1173
1227
  // assume this is a filter
1174
1228
  set_state_fields(
@@ -1178,6 +1232,7 @@ async function common_done(res, viewname, isWeb = true) {
1178
1232
  );
1179
1233
  } else {
1180
1234
  Object.keys(res.set_fields).forEach((k) => {
1235
+ if (k === "_viewname") return;
1181
1236
  const input = form.find(
1182
1237
  `input[name=${k}], textarea[name=${k}], select[name=${k}]`
1183
1238
  );
@@ -1186,6 +1241,7 @@ async function common_done(res, viewname, isWeb = true) {
1186
1241
  form.append(
1187
1242
  `<input type="hidden" name="id" value="${res.set_fields[k]}">`
1188
1243
  );
1244
+ reloadEmbeddedEditOwnViews(form, res.set_fields[k]);
1189
1245
  return;
1190
1246
  }
1191
1247
  if (input.attr("type") === "checkbox")
@@ -1250,6 +1306,28 @@ async function common_done(res, viewname, isWeb = true) {
1250
1306
  }
1251
1307
  }
1252
1308
 
1309
+ function reloadEmbeddedEditOwnViews(form, id) {
1310
+ form.find("div[sc-load-on-assign-id]").each(function () {
1311
+ const $e = $(this);
1312
+ const viewname = $e.attr("sc-load-on-assign-id");
1313
+ const newUrl = `/view/${viewname}?id=${id}`;
1314
+ $.ajax(newUrl, {
1315
+ headers: {
1316
+ pjaxpageload: "true",
1317
+ localizedstate: "true", //no admin bar
1318
+ },
1319
+ success: function (res, textStatus, request) {
1320
+ const newE = `<div class="d-inline" data-sc-embed-viewname="${viewname}" data-sc-view-source="${newUrl}">${res}</div>`;
1321
+ $e.replaceWith(newE);
1322
+ initialize_page();
1323
+ },
1324
+ error: function (res) {
1325
+ notifyAlert({ type: "danger", text: res.responseText });
1326
+ },
1327
+ });
1328
+ });
1329
+ }
1330
+
1253
1331
  const repeaterCopyValuesToForm = (form, editor, noTriggerChange) => {
1254
1332
  const vs = JSON.parse(editor.getString());
1255
1333
 
@@ -1542,3 +1620,55 @@ function set_readonly_select(e) {
1542
1620
  const option = options.find((o) => o.value == e.target.value);
1543
1621
  if (option) $disp.val(option.label);
1544
1622
  }
1623
+
1624
+ function close_saltcorn_modal() {
1625
+ $("#scmodal").off("hidden.bs.modal");
1626
+ var myModalEl = document.getElementById("scmodal");
1627
+ if (!myModalEl) return;
1628
+ var modal = bootstrap.Modal.getInstance(myModalEl);
1629
+ if (modal) {
1630
+ if (modal.hide) modal.hide();
1631
+ if (modal.dispose) modal.dispose();
1632
+ }
1633
+ }
1634
+
1635
+ function reload_embedded_view(viewname, new_query_string) {
1636
+ const isNode = getIsNode();
1637
+ const updater = ($e, res) => {
1638
+ $e.html(res);
1639
+ initialize_page();
1640
+ };
1641
+ if (window._sc_loglevel > 4)
1642
+ console.log(
1643
+ "reload_embedded_view",
1644
+ viewname,
1645
+ "found",
1646
+ $(`[data-sc-embed-viewname="${viewname}"]`).length
1647
+ );
1648
+ $(`[data-sc-embed-viewname="${viewname}"]`).each(function () {
1649
+ const $e = $(this);
1650
+ let url = $e.attr("data-sc-local-state") || $e.attr("data-sc-view-source");
1651
+ if (!url) return;
1652
+ if (new_query_string) {
1653
+ url = url.split("?")[0] + "?" + new_query_string;
1654
+ }
1655
+ if (isNode) {
1656
+ $.ajax(url, {
1657
+ headers: {
1658
+ pjaxpageload: "true",
1659
+ localizedstate: "true", //no admin bar
1660
+ },
1661
+ success: function (res, textStatus, request) {
1662
+ updater($e, res);
1663
+ },
1664
+ error: function (res) {
1665
+ notifyAlert({ type: "danger", text: res.responseText });
1666
+ },
1667
+ });
1668
+ } else {
1669
+ runUrl(url).then((html) => {
1670
+ updater($e, html);
1671
+ });
1672
+ }
1673
+ });
1674
+ }
@@ -502,3 +502,7 @@ tr[onclick] {
502
502
  .editStarRating i.fa-star {
503
503
  color: unset;
504
504
  }
505
+
506
+ .modal-header {
507
+ justify-content: space-between;
508
+ }
@@ -154,37 +154,6 @@ $(function () {
154
154
  });
155
155
  });
156
156
 
157
- function reload_embedded_view(viewname, new_query_string) {
158
- if (window._sc_loglevel > 4)
159
- console.log(
160
- "reload_embedded_view",
161
- viewname,
162
- "found",
163
- $(`[data-sc-embed-viewname="${viewname}"]`).length
164
- );
165
- $(`[data-sc-embed-viewname="${viewname}"]`).each(function () {
166
- const $e = $(this);
167
- let url = $e.attr("data-sc-local-state") || $e.attr("data-sc-view-source");
168
- if (!url) return;
169
- if (new_query_string) {
170
- url = url.split("?")[0] + "?" + new_query_string;
171
- }
172
- $.ajax(url, {
173
- headers: {
174
- pjaxpageload: "true",
175
- localizedstate: "true", //no admin bar
176
- },
177
- success: function (res, textStatus, request) {
178
- $e.html(res);
179
- initialize_page();
180
- },
181
- error: function (res) {
182
- notifyAlert({ type: "danger", text: res.responseText });
183
- },
184
- });
185
- });
186
- }
187
-
188
157
  function pjax_to(href, e) {
189
158
  let $modal = $("#scmodal");
190
159
  const inModal = $modal.length && $modal.hasClass("show");
@@ -323,17 +292,6 @@ function globalErrorCatcher(message, source, lineno, colno, error) {
323
292
  });
324
293
  }
325
294
 
326
- function close_saltcorn_modal() {
327
- $("#scmodal").off("hidden.bs.modal");
328
- var myModalEl = document.getElementById("scmodal");
329
- if (!myModalEl) return;
330
- var modal = bootstrap.Modal.getInstance(myModalEl);
331
- if (modal) {
332
- if (modal.hide) modal.hide();
333
- if (modal.dispose) modal.dispose();
334
- }
335
- }
336
-
337
295
  function ensure_modal_exists_and_closed() {
338
296
  if ($("#scmodal").length === 0) {
339
297
  $("body").append(`<div id="scmodal" class="modal">
@@ -450,7 +408,14 @@ function saveAndContinueAsync(e) {
450
408
  });
451
409
  }
452
410
 
453
- function saveAndContinue(e, k) {
411
+ function saveAndContinue(e, k, event) {
412
+ if (
413
+ event &&
414
+ event.target &&
415
+ event.target.classList &&
416
+ event.target.classList.contains("no-form-change")
417
+ )
418
+ return;
454
419
  var form = $(e).closest("form");
455
420
  const valres = form[0].reportValidity();
456
421
  if (!valres) return;
@@ -471,6 +436,7 @@ function saveAndContinue(e, k) {
471
436
  form.append(
472
437
  `<input type="hidden" class="form-control " name="id" value="${res.id}">`
473
438
  );
439
+ reloadEmbeddedEditOwnViews(form, res.id);
474
440
  }
475
441
  common_done(res, form.attr("data-viewname"));
476
442
  },
@@ -515,6 +481,7 @@ function applyViewConfig(e, url, k, event) {
515
481
  cfg[item.name] = item.value;
516
482
  });
517
483
  ajax_indicator(true, e);
484
+ window.savingViewConfig = true;
518
485
  $.ajax(url, {
519
486
  type: "POST",
520
487
  dataType: "json",
@@ -524,9 +491,11 @@ function applyViewConfig(e, url, k, event) {
524
491
  },
525
492
  data: JSON.stringify(cfg),
526
493
  error: function (request) {
494
+ window.savingViewConfig = false;
527
495
  ajax_indicate_error(e, request);
528
496
  },
529
497
  success: function (res) {
498
+ window.savingViewConfig = false;
530
499
  ajax_indicator(false);
531
500
  k && k(res);
532
501
  !k && updateViewPreview();
@@ -549,10 +518,12 @@ function updateViewPreview() {
549
518
  },
550
519
 
551
520
  error: function (resp) {
552
- $("#viewcfg-preview-error").html(resp.responseText || resp.statusText);
521
+ $("#viewcfg-preview-error")
522
+ .show()
523
+ .html(resp.responseText || resp.statusText);
553
524
  },
554
525
  success: function (res) {
555
- $("#viewcfg-preview-error").html("");
526
+ $("#viewcfg-preview-error").hide().html("");
556
527
  $preview.css({ opacity: 1.0 });
557
528
 
558
529
  //disable functions preview migght try to call
@@ -821,6 +792,17 @@ function build_mobile_app(button) {
821
792
  params.includedPlugins = Array.from(pluginsSelect.options)
822
793
  .filter((option) => !option.hidden)
823
794
  .map((option) => option.value);
795
+
796
+ if (
797
+ params.useDocker &&
798
+ !cordovaBuilderAvailable &&
799
+ !confirm(
800
+ "Docker is selected but the Cordova builder seems not to be installed. " +
801
+ "Do you really want to continue?"
802
+ )
803
+ ) {
804
+ return;
805
+ }
824
806
  ajax_post("/admin/build-mobile-app", {
825
807
  data: params,
826
808
  success: (data) => {
@@ -834,6 +816,41 @@ function build_mobile_app(button) {
834
816
  });
835
817
  }
836
818
 
819
+ function pull_cordova_builder() {
820
+ ajax_post("/admin/mobile-app/pull-cordova-builder", {
821
+ success: () => {
822
+ notifyAlert(
823
+ "Pulling the the cordova-builder. " +
824
+ "To see the progress, open the logs viewer with the System logging verbosity set to 'All'."
825
+ );
826
+ },
827
+ });
828
+ }
829
+
830
+ function check_cordova_builder() {
831
+ $.ajax("/admin/mobile-app/check-cordova-builder", {
832
+ type: "GET",
833
+ success: function (res) {
834
+ cordovaBuilderAvailable = !!res.installed;
835
+ if (cordovaBuilderAvailable) {
836
+ $("#dockerBuilderStatusId").html(
837
+ `<span>
838
+ installed<i class="ps-2 fas fa-check text-success"></i>
839
+ </span>
840
+ `
841
+ );
842
+ } else {
843
+ $("#dockerBuilderStatusId").html(
844
+ `<span>
845
+ not available<i class="ps-2 fas fa-times text-danger"></i>
846
+ </span>
847
+ `
848
+ );
849
+ }
850
+ },
851
+ });
852
+ }
853
+
837
854
  function move_to_synched() {
838
855
  const opts = $("#unsynched-tbls-select-id");
839
856
  $("#synched-tbls-select-id").removeAttr("selected");
@@ -897,7 +914,7 @@ function toggle_tbl_sync() {
897
914
  function toggle_android_platform() {
898
915
  if ($("#androidCheckboxId")[0].checked === true) {
899
916
  $("#dockerCheckboxId").attr("hidden", false);
900
- $("#dockerCheckboxId").attr("checked", true);
917
+ $("#dockerCheckboxId").attr("checked", cordovaBuilderAvailable);
901
918
  $("#dockerLabelId").removeClass("d-none");
902
919
  } else {
903
920
  $("#dockerCheckboxId").attr("hidden", true);
@@ -916,6 +933,10 @@ function join_field_clicked(e, fieldPath) {
916
933
  apply_showif();
917
934
  }
918
935
 
936
+ function execLink(path) {
937
+ window.location.href = `${location.origin}${path}`;
938
+ }
939
+
919
940
  (() => {
920
941
  const e = document.querySelector("[data-sidebar-toggler]");
921
942
  let closed = localStorage.getItem("sidebarClosed") === "true";
@@ -16,6 +16,7 @@ const { eachTenant } = require("@saltcorn/admin-models/models/tenant");
16
16
  const relevantPackages = [
17
17
  "db-common",
18
18
  "common-code",
19
+ "plugins-loader",
19
20
  "postgres",
20
21
  "saltcorn-data",
21
22
  "saltcorn-builder",
@@ -37,6 +38,7 @@ const excludePatterns = [
37
38
  /\.docs/,
38
39
  /migrations/,
39
40
  /.*test.js/,
41
+ /.*test.ts/,
40
42
  ];
41
43
 
42
44
  /**
package/routes/actions.js CHANGED
@@ -307,6 +307,14 @@ const triggerForm = async (req, trigger) => {
307
307
  showIf: { when_trigger: ["API call"] },
308
308
  options: roleOptions,
309
309
  },
310
+ {
311
+ name: "_raw_output",
312
+ label: "Raw Output",
313
+ parent_field: "configuration",
314
+ sublabel: req.__("Do not wrap response in a success object"),
315
+ type: "Bool",
316
+ showIf: { when_trigger: ["API call"] },
317
+ },
310
318
  ],
311
319
  });
312
320
  // if (trigger) {
@@ -450,6 +458,11 @@ router.post(
450
458
  },
451
459
  });
452
460
  } else {
461
+ if (form.values.configuration)
462
+ form.values.configuration = {
463
+ ...trigger.configuration,
464
+ ...form.values.configuration,
465
+ };
453
466
  await Trigger.update(trigger.id, form.values); //{configuration: form.values});
454
467
  req.flash("success", req.__("Action information saved"));
455
468
  res.redirect(`/actions/`);
@@ -662,6 +675,7 @@ router.get(
662
675
  onChange: "saveAndContinue(this)",
663
676
  submitLabel: req.__("Done"),
664
677
  fields: cfgFields,
678
+ ...(action.configFormOptions || {}),
665
679
  });
666
680
  // populate form values
667
681
  form.values = trigger.configuration;
@@ -729,7 +743,9 @@ router.post(
729
743
  },
730
744
  });
731
745
  } else {
732
- await Trigger.update(trigger.id, { configuration: form.values });
746
+ await Trigger.update(trigger.id, {
747
+ configuration: { ...trigger.configuration, ...form.values },
748
+ });
733
749
  if (req.xhr) {
734
750
  res.json({ success: "ok" });
735
751
  return;