@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 +3 -2
- package/locales/en.json +5 -1
- package/package.json +9 -9
- package/public/saltcorn.js +10 -8
- package/routes/actions.js +50 -22
- package/routes/admin.js +6 -2
- package/routes/api.js +51 -0
- package/routes/plugins.js +7 -2
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.
|
|
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.
|
|
11
|
-
"@saltcorn/builder": "1.1.0-beta.
|
|
12
|
-
"@saltcorn/data": "1.1.0-beta.
|
|
13
|
-
"@saltcorn/admin-models": "1.1.0-beta.
|
|
14
|
-
"@saltcorn/filemanager": "1.1.0-beta.
|
|
15
|
-
"@saltcorn/markup": "1.1.0-beta.
|
|
16
|
-
"@saltcorn/plugins-loader": "1.1.0-beta.
|
|
17
|
-
"@saltcorn/sbadmin2": "1.1.0-beta.
|
|
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",
|
package/public/saltcorn.js
CHANGED
|
@@ -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
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
"
|
|
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}
|
|
592
|
-
initial_step ? "" : "
|
|
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 (
|
|
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 {
|
|
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 {
|
|
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"));
|