@saltcorn/server 1.1.0-beta.23 → 1.1.0-beta.25

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/load_plugins.js CHANGED
@@ -8,7 +8,7 @@
8
8
  const db = require("@saltcorn/data/db");
9
9
  const { getState, getRootState } = require("@saltcorn/data/db/state");
10
10
  const Plugin = require("@saltcorn/data/models/plugin");
11
- const { isRoot } = require("@saltcorn/data/utils");
11
+ const { isRoot, getFetchProxyOptions } = 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");
@@ -35,7 +35,8 @@ const getEngineInfos = async (plugin, forceFetch) => {
35
35
  } else {
36
36
  getState().log(5, `Fetching versions for '${plugin.location}'`);
37
37
  const pkgInfo = await npmFetch.json(
38
- `https://registry.npmjs.org/${plugin.location}`
38
+ `https://registry.npmjs.org/${plugin.location}`,
39
+ getFetchProxyOptions()
39
40
  );
40
41
  const versions = pkgInfo.versions;
41
42
  const newCached = {};
package/locales/en.json CHANGED
@@ -1514,5 +1514,9 @@
1514
1514
  "Please enter a version in the format 'x.y.z' (e.g. 0.0.1 with numbers from 0 to 999) or leave it empty.": "Please enter a version in the format 'x.y.z' (e.g. 0.0.1 with numbers from 0 to 999) or leave it empty.",
1515
1515
  "Delete unchanged": "Delete unchanged",
1516
1516
  "Delete allocated row if there are no changes.": "Delete allocated row if there are no changes.",
1517
- "Triggers on table": "Triggers on table"
1517
+ "Triggers on table": "Triggers on table",
1518
+ "Submit": "Submit",
1519
+ "OK": "OK",
1520
+ "Step settings": "Step settings",
1521
+ "Action settings": "Action settings"
1518
1522
  }
package/package.json CHANGED
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "name": "@saltcorn/server",
3
- "version": "1.1.0-beta.23",
3
+ "version": "1.1.0-beta.25",
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.1.0-beta.23",
11
- "@saltcorn/builder": "1.1.0-beta.23",
12
- "@saltcorn/data": "1.1.0-beta.23",
13
- "@saltcorn/admin-models": "1.1.0-beta.23",
14
- "@saltcorn/filemanager": "1.1.0-beta.23",
15
- "@saltcorn/markup": "1.1.0-beta.23",
16
- "@saltcorn/plugins-loader": "1.1.0-beta.23",
17
- "@saltcorn/sbadmin2": "1.1.0-beta.23",
10
+ "@saltcorn/base-plugin": "1.1.0-beta.25",
11
+ "@saltcorn/builder": "1.1.0-beta.25",
12
+ "@saltcorn/data": "1.1.0-beta.25",
13
+ "@saltcorn/admin-models": "1.1.0-beta.25",
14
+ "@saltcorn/filemanager": "1.1.0-beta.25",
15
+ "@saltcorn/markup": "1.1.0-beta.25",
16
+ "@saltcorn/plugins-loader": "1.1.0-beta.25",
17
+ "@saltcorn/sbadmin2": "1.1.0-beta.25",
18
18
  "@socket.io/cluster-adapter": "^0.2.1",
19
19
  "@socket.io/sticky": "^1.0.1",
20
20
  "adm-zip": "0.5.10",
@@ -1213,14 +1213,16 @@ function check_delete_unsaved(tablename, script_tag) {
1213
1213
  if (form.length && !form.attr("data-form-changed")) {
1214
1214
  //delete row
1215
1215
  const rec = get_form_record(form);
1216
-
1217
- $.ajax({
1218
- url: `/api/${tablename}/${rec.id}`,
1219
- type: "DELETE",
1220
- headers: {
1221
- "CSRF-Token": _sc_globalCsrf,
1222
- },
1223
- });
1216
+ if (navigator.sendBeacon) {
1217
+ navigator.sendBeacon(`/api/${tablename}/delete/${rec.id}`);
1218
+ } else
1219
+ $.ajax({
1220
+ url: `/api/${tablename}/${rec.id}`,
1221
+ type: "DELETE",
1222
+ headers: {
1223
+ "CSRF-Token": _sc_globalCsrf,
1224
+ },
1225
+ });
1224
1226
  }
1225
1227
  }
1226
1228
 
package/routes/actions.js CHANGED
@@ -554,18 +554,6 @@ const getWorkflowConfig = async (req, id, table, trigger) => {
554
554
  });
555
555
  trigCfgForm.values = trigger.configuration;
556
556
  return (
557
- /*ul(
558
- steps.map((step) =>
559
- li(
560
- a(
561
- {
562
- href: `/actions/stepedit/${trigger.id}/${step.id}`,
563
- },
564
- step.name
565
- )
566
- )
567
- )
568
- ) +*/
569
557
  pre({ class: "mermaid" }, genWorkflowDiagram(steps)) +
570
558
  script(
571
559
  { defer: "defer" },
@@ -588,8 +576,8 @@ window.addEventListener('DOMContentLoaded',tryAddWFNodes)`
588
576
  ) +
589
577
  a(
590
578
  {
591
- href: `/actions/stepedit/${trigger.id}?name=step${steps.length + 1}${
592
- initial_step ? "" : "&initial_step=true"
579
+ href: `/actions/stepedit/${trigger.id}${
580
+ initial_step ? "" : "?initial_step=true"
593
581
  }`,
594
582
  class: "btn btn-primary",
595
583
  },
@@ -619,6 +607,8 @@ const jsIdentifierValidator = (s) => {
619
607
 
620
608
  const getWorkflowStepForm = async (trigger, req, step_id) => {
621
609
  const table = trigger.table_id ? Table.findOne(trigger.table_id) : null;
610
+ const actionExplainers = {};
611
+
622
612
  let stateActions = getState().actions;
623
613
  const stateActionKeys = Object.entries(stateActions)
624
614
  .filter(([k, v]) => !v.disableInWorkflow)
@@ -627,6 +617,9 @@ const getWorkflowStepForm = async (trigger, req, step_id) => {
627
617
  const actionConfigFields = [];
628
618
  for (const [name, action] of Object.entries(stateActions)) {
629
619
  if (!stateActionKeys.includes(name)) continue;
620
+
621
+ if (action.description) actionExplainers[name] = action.description;
622
+
630
623
  try {
631
624
  const cfgFields = await getActionConfigFields(action, table, {
632
625
  mode: "workflow",
@@ -654,14 +647,30 @@ const getWorkflowStepForm = async (trigger, req, step_id) => {
654
647
  "TableQuery",
655
648
  "Output",
656
649
  "WaitUntil",
657
- "UserForm",
658
650
  "WaitNextTick",
651
+ "UserForm",
659
652
  ],
660
653
  forWorkflow: true,
661
654
  });
655
+ const triggers = Trigger.find({
656
+ when_trigger: { or: ["API call", "Never"] },
657
+ });
658
+ triggers.forEach((tr) => {
659
+ if (tr.description) actionExplainers[tr.name] = tr.description;
660
+ });
661
+ actionExplainers.SetContext = "Set variables in the context";
662
+ actionExplainers.TableQuery = "Query a table into a variable in the context";
663
+ actionExplainers.Output =
664
+ "Display a message to the user. Pause workflow until the message is read.";
665
+ actionExplainers.WaitUntil = "Pause until a time in the future";
666
+ actionExplainers.WaitNextTick =
667
+ "Pause until the next scheduler invocation (at most 5 minutes)";
668
+ actionExplainers.UserForm =
669
+ "Ask a user one or more questions, pause until they are answered";
662
670
 
663
671
  actionConfigFields.push({
664
672
  label: "Form header",
673
+ sublabel: "Text shown to the user at the top of the form",
665
674
  name: "form_header",
666
675
  type: "String",
667
676
  showIf: { wf_action_name: "UserForm" },
@@ -792,6 +801,10 @@ const getWorkflowStepForm = async (trigger, req, step_id) => {
792
801
  ]
793
802
  : undefined,
794
803
  fields: [
804
+ {
805
+ input_type: "section_header",
806
+ label: req.__("Step settings"),
807
+ },
795
808
  {
796
809
  name: "wf_step_name",
797
810
  label: req.__("Step name"),
@@ -821,6 +834,10 @@ const getWorkflowStepForm = async (trigger, req, step_id) => {
821
834
  sublabel:
822
835
  "Name of next step. Can be a JavaScript expression based on the run context. Blank if final step",
823
836
  },
837
+ {
838
+ input_type: "section_header",
839
+ label: req.__("Action"),
840
+ },
824
841
  {
825
842
  name: "wf_action_name",
826
843
  label: req.__("Action"),
@@ -828,8 +845,13 @@ const getWorkflowStepForm = async (trigger, req, step_id) => {
828
845
  required: true,
829
846
  attributes: {
830
847
  options: actionsNotRequiringRow,
848
+ explainers: actionExplainers,
831
849
  },
832
850
  },
851
+ {
852
+ input_type: "section_header",
853
+ label: req.__("Action settings"),
854
+ },
833
855
  ...actionConfigFields,
834
856
  ],
835
857
  });
@@ -1349,7 +1371,13 @@ router.get(
1349
1371
  const form = await getWorkflowStepForm(trigger, req, step_id);
1350
1372
 
1351
1373
  if (initial_step) form.values.wf_initial_step = true;
1352
- if (name) form.values.wf_step_name = name;
1374
+ if (!step_id) {
1375
+ const steps = await WorkflowStep.find({ trigger_id });
1376
+ const stepNames = new Set(steps.map((s) => s.name));
1377
+ let name_ix = steps.length + 1;
1378
+ while (stepNames.has(`step${name_ix}`)) name_ix += 1;
1379
+ form.values.wf_step_name = `step${name_ix}`;
1380
+ }
1353
1381
  send_events_page({
1354
1382
  res,
1355
1383
  req,
@@ -1699,6 +1727,7 @@ const getWorkflowStepUserForm = async (run, trigger, step, req) => {
1699
1727
 
1700
1728
  const form = new Form({
1701
1729
  action: `/actions/fill-workflow-form/${run.id}`,
1730
+ submitLabel: run.wait_info.output ? req.__("OK") : req.__("Submit"),
1702
1731
  blurb: run.wait_info.output || step.configuration?.form_header || "",
1703
1732
  formStyle: run.wait_info.output || req.xhr ? "vert" : undefined,
1704
1733
  fields: (step.configuration.user_form_questions || []).map((q) => ({
@@ -1767,12 +1796,15 @@ router.post(
1767
1796
  res.sendWrap(title, renderForm(form, req.csrfToken()));
1768
1797
  } else {
1769
1798
  await run.provide_form_input(form.values);
1770
- await run.run({
1799
+ const runres = await run.run({
1771
1800
  user: req.user,
1772
1801
  trace: trigger.configuration?.save_traces,
1802
+ interactive: true,
1773
1803
  });
1774
1804
  if (req.xhr) {
1775
1805
  const retDirs = await run.popReturnDirectives();
1806
+
1807
+ if (runres?.popup) retDirs.popup = runres.popup;
1776
1808
  res.json({ success: "ok", ...retDirs });
1777
1809
  } else {
1778
1810
  if (run.context.goto) res.redirect(run.context.goto);
@@ -1825,16 +1857,12 @@ router.post(
1825
1857
 
1826
1858
  WORKFLOWS TODO
1827
1859
 
1828
- delete is not always working?
1829
1860
  help file to explain steps, and context
1830
1861
 
1831
- Output after form not popping up
1832
- action explainer
1833
1862
  workflow actions: ForLoop, EndForLoop, ReadFile, WriteFile, APIResponse
1834
1863
 
1835
- correctly suggest new step name - on step cfg load
1836
-
1837
1864
  Error handlers
1865
+ other triggers can be steps
1838
1866
  interactive workflows for not logged in
1839
1867
  show end node in diagram
1840
1868
  actions can declare which variables they inject into scope
package/routes/admin.js CHANGED
@@ -104,7 +104,10 @@ const PageGroup = require("@saltcorn/data/models/page_group");
104
104
  const { getConfigFile } = require("@saltcorn/data/db/connect");
105
105
  const os = require("os");
106
106
  const Page = require("@saltcorn/data/models/page");
107
- const { getSafeSaltcornCmd } = require("@saltcorn/data/utils");
107
+ const {
108
+ getSafeSaltcornCmd,
109
+ getFetchProxyOptions,
110
+ } = require("@saltcorn/data/utils");
108
111
  const stream = require("stream");
109
112
  const Crash = require("@saltcorn/data/models/crash");
110
113
  const { get_help_markup } = require("../help/index.js");
@@ -1334,7 +1337,8 @@ router.get(
1334
1337
  error_catcher(async (req, res) => {
1335
1338
  try {
1336
1339
  const pkgInfo = await npmFetch.json(
1337
- "https://registry.npmjs.org/@saltcorn/cli"
1340
+ "https://registry.npmjs.org/@saltcorn/cli",
1341
+ getFetchProxyOptions()
1338
1342
  );
1339
1343
  if (!pkgInfo?.versions)
1340
1344
  throw new Error(req.__("Unable to fetch versions"));
package/routes/api.js CHANGED
@@ -484,6 +484,57 @@ router.post(
484
484
  })
485
485
  );
486
486
 
487
+ /**
488
+ * Delete Table row by ID using POST
489
+ * @name delete/:tableName/:id
490
+ * @function
491
+ * @memberof module:routes/api~apiRouter
492
+ */
493
+ router.post(
494
+ "/:tableName/delete/:id",
495
+ // in case of primary key different from id - id will be string "undefined"
496
+ error_catcher(async (req, res, next) => {
497
+ const { tableName, id } = req.params;
498
+ const table = Table.findOne({ name: tableName });
499
+ if (!table) {
500
+ getState().log(3, `API DELETE ${tableName} not found`);
501
+ res.status(404).json({ error: req.__("Not found") });
502
+ return;
503
+ }
504
+ await passport.authenticate(
505
+ "api-bearer",
506
+ { session: false },
507
+ async function (err, user, info) {
508
+ if (accessAllowedWrite(req, user, table)) {
509
+ try {
510
+ if (id === "undefined") {
511
+ const pk_name = table.pk_name;
512
+ //const fields = table.getFields();
513
+ const row = req.body;
514
+ //readState(row, fields);
515
+ await table.deleteRows(
516
+ { [pk_name]: row[pk_name] },
517
+ user || req.user || { role_id: 100 }
518
+ );
519
+ } else
520
+ await table.deleteRows(
521
+ { id },
522
+ user || req.user || { role_id: 100 }
523
+ );
524
+ res.json({ success: true });
525
+ } catch (e) {
526
+ getState().log(2, `API DELETE ${table.name} error: ${e.message}`);
527
+ res.status(400).json({ error: e.message });
528
+ }
529
+ } else {
530
+ getState().log(3, `API DELETE ${table.name} not authorized`);
531
+ res.status(401).json({ error: req.__("Not authorized") });
532
+ }
533
+ }
534
+ )(req, res, next);
535
+ })
536
+ );
537
+
487
538
  /**
488
539
  * Update Table row directed by ID using POST
489
540
  * POST api/<table>/id
package/routes/plugins.js CHANGED
@@ -57,7 +57,11 @@ const fs = require("fs");
57
57
  const path = require("path");
58
58
  const { get_latest_npm_version } = require("@saltcorn/data/models/config");
59
59
  const { flash_restart } = require("../markup/admin.js");
60
- const { sleep, removeNonWordChars } = require("@saltcorn/data/utils");
60
+ const {
61
+ sleep,
62
+ removeNonWordChars,
63
+ getFetchProxyOptions,
64
+ } = require("@saltcorn/data/utils");
61
65
  const { loadAllPlugins } = require("../load_plugins");
62
66
  const npmFetch = require("npm-registry-fetch");
63
67
  const PluginInstaller = require("@saltcorn/plugins-loader/plugin_installer");
@@ -609,7 +613,8 @@ router.get(
609
613
  } else {
610
614
  try {
611
615
  const pkgInfo = await npmFetch.json(
612
- `https://registry.npmjs.org/${plugin.location}`
616
+ `https://registry.npmjs.org/${plugin.location}`,
617
+ getFetchProxyOptions()
613
618
  );
614
619
  if (!pkgInfo?.versions)
615
620
  throw new Error(req.__("Unable to fetch versions"));