@saltcorn/server 0.9.5-beta.19 → 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,20 +1,20 @@
1
1
  {
2
2
  "name": "@saltcorn/server",
3
- "version": "0.9.5-beta.19",
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.19",
11
- "@saltcorn/builder": "0.9.5-beta.19",
12
- "@saltcorn/data": "0.9.5-beta.19",
13
- "@saltcorn/admin-models": "0.9.5-beta.19",
14
- "@saltcorn/filemanager": "0.9.5-beta.19",
15
- "@saltcorn/markup": "0.9.5-beta.19",
16
- "@saltcorn/plugins-loader": "0.9.5-beta.19",
17
- "@saltcorn/sbadmin2": "0.9.5-beta.19",
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",
18
18
  "@socket.io/cluster-adapter": "^0.2.1",
19
19
  "@socket.io/sticky": "^1.0.1",
20
20
  "adm-zip": "0.5.10",
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/`);
@@ -730,7 +743,9 @@ router.post(
730
743
  },
731
744
  });
732
745
  } else {
733
- await Trigger.update(trigger.id, { configuration: form.values });
746
+ await Trigger.update(trigger.id, {
747
+ configuration: { ...trigger.configuration, ...form.values },
748
+ });
734
749
  if (req.xhr) {
735
750
  res.json({ success: "ok" });
736
751
  return;
package/routes/admin.js CHANGED
@@ -284,9 +284,23 @@ router.get(
284
284
  backupForm.values.auto_backup_destination = getState().getConfig(
285
285
  "auto_backup_destination"
286
286
  );
287
+ backupForm.values.auto_backup_tenants = getState().getConfig(
288
+ "auto_backup_tenants"
289
+ );
287
290
  backupForm.values.auto_backup_directory = getState().getConfig(
288
291
  "auto_backup_directory"
289
292
  );
293
+ backupForm.values.auto_backup_username = getState().getConfig(
294
+ "auto_backup_username"
295
+ );
296
+ backupForm.values.auto_backup_server =
297
+ getState().getConfig("auto_backup_server");
298
+ backupForm.values.auto_backup_password = getState().getConfig(
299
+ "auto_backup_password"
300
+ );
301
+ backupForm.values.auto_backup_port =
302
+ getState().getConfig("auto_backup_port");
303
+
290
304
  backupForm.values.auto_backup_expire_days = getState().getConfig(
291
305
  "auto_backup_expire_days"
292
306
  );
@@ -691,8 +705,10 @@ const backupFilePrefixForm = (req) =>
691
705
  * @param {object} req
692
706
  * @returns {Form} form
693
707
  */
694
- const autoBackupForm = (req) =>
695
- new Form({
708
+ const autoBackupForm = (req) => {
709
+ const isRoot = db.getTenantSchema() === db.connectObj.default_schema;
710
+
711
+ return new Form({
696
712
  action: "/admin/set-auto-backup",
697
713
  onChange: `saveAndContinue(this);$('#btnBackupNow').prop('disabled', $('#inputauto_backup_frequency').val()==='Never');`,
698
714
  noSubmitButton: true,
@@ -718,7 +734,47 @@ const autoBackupForm = (req) =>
718
734
  name: "auto_backup_destination",
719
735
  required: true,
720
736
  showIf: { auto_backup_frequency: ["Daily", "Weekly"] },
721
- attributes: { options: ["Saltcorn files", "Local directory"] },
737
+ attributes: {
738
+ auto_backup_frequency: ["Daily", "Weekly"],
739
+ options: ["Saltcorn files", "Local directory", "SFTP server"],
740
+ },
741
+ },
742
+ {
743
+ type: "String",
744
+ label: req.__("Server host"),
745
+ name: "auto_backup_server",
746
+ showIf: {
747
+ auto_backup_frequency: ["Daily", "Weekly"],
748
+ auto_backup_destination: "SFTP server",
749
+ },
750
+ },
751
+ {
752
+ type: "String",
753
+ label: req.__("Username"),
754
+ name: "auto_backup_username",
755
+ showIf: {
756
+ auto_backup_frequency: ["Daily", "Weekly"],
757
+ auto_backup_destination: "SFTP server",
758
+ },
759
+ },
760
+ {
761
+ type: "String",
762
+ label: req.__("Password"),
763
+ fieldview: "password",
764
+ name: "auto_backup_password",
765
+ showIf: {
766
+ auto_backup_frequency: ["Daily", "Weekly"],
767
+ auto_backup_destination: "SFTP server",
768
+ },
769
+ },
770
+ {
771
+ type: "Integer",
772
+ label: req.__("Port"),
773
+ name: "auto_backup_port",
774
+ showIf: {
775
+ auto_backup_frequency: ["Daily", "Weekly"],
776
+ auto_backup_destination: "SFTP server",
777
+ },
722
778
  },
723
779
  {
724
780
  type: "String",
@@ -730,6 +786,19 @@ const autoBackupForm = (req) =>
730
786
  //auto_backup_destination: "Local directory",
731
787
  },
732
788
  },
789
+ {
790
+ type: "String",
791
+ label: req.__("Retain local directory"),
792
+ name: "auto_backup_retain_local_directory",
793
+ sublabel: req.__(
794
+ "Retain a local backup copy in this directory (optional)"
795
+ ),
796
+ showIf: {
797
+ auto_backup_frequency: ["Daily", "Weekly"],
798
+ auto_backup_destination: "SFTP server",
799
+ //auto_backup_destination: "Local directory",
800
+ },
801
+ },
733
802
  {
734
803
  type: "Integer",
735
804
  label: req.__("Expiration in days"),
@@ -742,6 +811,19 @@ const autoBackupForm = (req) =>
742
811
  auto_backup_destination: "Local directory",
743
812
  },
744
813
  },
814
+ ...(isRoot
815
+ ? [
816
+ {
817
+ type: "Bool",
818
+ label: req.__("All tenants"),
819
+ sublabel: req.__("Also backup all tenants"),
820
+ name: "auto_backup_tenants",
821
+ showIf: {
822
+ auto_backup_frequency: ["Daily", "Weekly"],
823
+ },
824
+ },
825
+ ]
826
+ : []),
745
827
  {
746
828
  type: "Bool",
747
829
  label: req.__("Include Event Logs"),
@@ -753,6 +835,7 @@ const autoBackupForm = (req) =>
753
835
  },
754
836
  ],
755
837
  });
838
+ };
756
839
 
757
840
  /**
758
841
  * Snapshot Form
package/routes/api.js CHANGED
@@ -373,7 +373,10 @@ router.all(
373
373
  res.redirect(resp.goto);
374
374
  else if (req.headers?.scgotourl)
375
375
  res.redirect(req.headers?.scgotourl);
376
- else res.json({ success: true, data: resp });
376
+ else {
377
+ if (trigger.configuration?._raw_output) res.json({ resp });
378
+ else res.json({ success: true, data: resp });
379
+ }
377
380
  } catch (e) {
378
381
  Crash.create(e, req);
379
382
  res.status(400).json({ success: false, error: e.message });
package/routes/plugins.js CHANGED
@@ -547,6 +547,16 @@ const plugin_store_html = (items, req) => {
547
547
  };
548
548
  };
549
549
 
550
+ const flash_relogin = (req, exposedConfigs) => {
551
+ req.flash(
552
+ "warning",
553
+ req.__(
554
+ "To see changes for '%s' in show-if-formulas, users need to relogin",
555
+ exposedConfigs.join(", ")
556
+ )
557
+ );
558
+ };
559
+
550
560
  /**
551
561
  * @name get
552
562
  * @function
@@ -789,6 +799,8 @@ router.post(
789
799
  const instore = await Plugin.store_plugins_available();
790
800
  const store_plugin = instore.find((p) => p.name === plugin.name);
791
801
  if (store_plugin && store_plugin.has_auth) flash_restart(req);
802
+ if (module.exposed_configs?.length > 0)
803
+ flash_relogin(req, module.exposed_configs);
792
804
  getState().processSend({
793
805
  refresh_plugin_cfg: plugin.name,
794
806
  tenant: db.getTenantSchema(),
@@ -936,6 +948,14 @@ router.post(
936
948
  ...(plugin.configuration ? plugin.configuration : {}),
937
949
  ...values,
938
950
  });
951
+ const sessionUser = req.session?.passport?.user;
952
+ if (sessionUser) {
953
+ const pluginName = module.plugin_name;
954
+ if (sessionUser.attributes) {
955
+ const oldAttrs = sessionUser.attributes[pluginName] || {};
956
+ sessionUser.attributes[pluginName] = { ...oldAttrs, ...values };
957
+ } else sessionUser.attributes = { [pluginName]: values };
958
+ }
939
959
  getState().processSend({
940
960
  refresh_plugin_cfg: plugin.name,
941
961
  tenant: db.getTenantSchema(),
@@ -976,6 +996,14 @@ router.post(
976
996
  getState().userLayouts[req.user.email] = module.layout(
977
997
  userAttrs.layout.config
978
998
  );
999
+ const sessionUser = req.session?.passport?.user;
1000
+ if (sessionUser) {
1001
+ const pluginName = module.plugin_name;
1002
+ if (sessionUser.attributes) {
1003
+ const oldAttrs = sessionUser.attributes[pluginName] || {};
1004
+ sessionUser.attributes[pluginName] = { ...oldAttrs, ...values };
1005
+ } else sessionUser.attributes = { [pluginName]: values };
1006
+ }
979
1007
  getState().processSend({
980
1008
  refresh_plugin_cfg: plugin.name,
981
1009
  tenant: db.getTenantSchema(),
@@ -997,6 +1025,14 @@ router.post(
997
1025
  delete userAttrs.layout;
998
1026
  await user.update({ _attributes: userAttrs });
999
1027
  getState().userLayouts[req.user.email] = null;
1028
+ let module = getState().plugins[plugin];
1029
+ if (!module) {
1030
+ module = getState().plugins[getState().plugin_module_names[plugin]];
1031
+ }
1032
+ const pluginName = module.plugin_name;
1033
+ const sessionUser = req.session?.passport?.user;
1034
+ if (sessionUser?.attributes[pluginName])
1035
+ sessionUser.attributes[pluginName] = {};
1000
1036
  getState().processSend({
1001
1037
  refresh_plugin_cfg: plugin,
1002
1038
  tenant: db.getTenantSchema(),
package/routes/search.js CHANGED
@@ -52,6 +52,12 @@ const searchConfigForm = (tables, views, req) => {
52
52
  attributes: { options: ok_views.map((v) => v.name).join() },
53
53
  });
54
54
  }
55
+ fields.push({
56
+ name: "search_table_description",
57
+ label: req.__("Description header"),
58
+ sublabel: req.__("Use table description instead of name as header"),
59
+ type: "Bool",
60
+ });
55
61
  const blurb1 = req.__(
56
62
  `Choose views for <a href="/search">search results</a> for each table.<br/>Set to blank to omit table from global search.`
57
63
  );
@@ -84,7 +90,11 @@ router.get(
84
90
  const views = await View.find({}, { orderBy: "name" });
85
91
  const tables = await Table.find();
86
92
  const form = searchConfigForm(tables, views, req);
87
- form.values = getState().getConfig("globalSearch");
93
+ form.values = getState().getConfig("globalSearch", {});
94
+ form.values.search_table_description = getState().getConfig(
95
+ "search_table_description",
96
+ false
97
+ );
88
98
  send_infoarch_page({
89
99
  res,
90
100
  req,
@@ -116,6 +126,13 @@ router.post(
116
126
  const result = form.validate(req.body);
117
127
 
118
128
  if (result.success) {
129
+ const search_table_description =
130
+ !!result.success.search_table_description;
131
+ await getState().setConfig(
132
+ "search_table_description",
133
+ search_table_description
134
+ );
135
+ delete result.success.search_table_description;
119
136
  await getState().setConfig("globalSearch", result.success);
120
137
  if (!req.xhr) res.redirect("/search/config");
121
138
  else res.json({ success: "ok" });
@@ -175,6 +192,10 @@ const runSearch = async ({ q, _page, table }, req, res) => {
175
192
  res.redirect("/");
176
193
  return;
177
194
  }
195
+ const search_table_description = getState().getConfig(
196
+ "search_table_description",
197
+ false
198
+ );
178
199
  const current_page = parseInt(_page) || 1;
179
200
  const offset = (current_page - 1) * page_size;
180
201
  let resp = [];
@@ -184,6 +205,11 @@ const runSearch = async ({ q, _page, table }, req, res) => {
184
205
  if (!viewName || viewName === "") continue;
185
206
  tablesConfigured += 1;
186
207
  if (table && tableName !== table) continue;
208
+ let sectionHeader = tableName;
209
+ if (search_table_description) {
210
+ sectionHeader =
211
+ Table.findOne({ name: tableName })?.description || tableName;
212
+ }
187
213
  const view = await View.findOne({ name: viewName });
188
214
  if (!view)
189
215
  throw new InvalidConfiguration(
@@ -209,7 +235,7 @@ const runSearch = async ({ q, _page, table }, req, res) => {
209
235
  tablesWithResults.push(tableName);
210
236
  resp.push({
211
237
  type: "card",
212
- title: span({ id: tableName }, tableName),
238
+ title: span({ id: tableName }, sectionHeader),
213
239
  contents: vresps.map((vr) => vr.html).join("<hr>") + paginate,
214
240
  });
215
241
  }