@saltcorn/server 1.1.0 → 1.1.1-beta.0
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 +2 -1
- package/package.json +9 -9
- package/public/saltcorn.css +18 -4
- package/routes/actions.js +122 -17
package/locales/en.json
CHANGED
|
@@ -1521,5 +1521,6 @@
|
|
|
1521
1521
|
"Step settings": "Step settings",
|
|
1522
1522
|
"Action settings": "Action settings",
|
|
1523
1523
|
"Workflow": "Workflow",
|
|
1524
|
-
"Previous runs": "Previous runs"
|
|
1524
|
+
"Previous runs": "Previous runs",
|
|
1525
|
+
"The workflow the user will be interacting with.": "The workflow the user will be interacting with."
|
|
1525
1526
|
}
|
package/package.json
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/server",
|
|
3
|
-
"version": "1.1.0",
|
|
3
|
+
"version": "1.1.1-beta.0",
|
|
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",
|
|
11
|
-
"@saltcorn/builder": "1.1.0",
|
|
12
|
-
"@saltcorn/data": "1.1.0",
|
|
13
|
-
"@saltcorn/admin-models": "1.1.0",
|
|
14
|
-
"@saltcorn/filemanager": "1.1.0",
|
|
15
|
-
"@saltcorn/markup": "1.1.0",
|
|
16
|
-
"@saltcorn/plugins-loader": "1.1.0",
|
|
17
|
-
"@saltcorn/sbadmin2": "1.1.0",
|
|
10
|
+
"@saltcorn/base-plugin": "1.1.1-beta.0",
|
|
11
|
+
"@saltcorn/builder": "1.1.1-beta.0",
|
|
12
|
+
"@saltcorn/data": "1.1.1-beta.0",
|
|
13
|
+
"@saltcorn/admin-models": "1.1.1-beta.0",
|
|
14
|
+
"@saltcorn/filemanager": "1.1.1-beta.0",
|
|
15
|
+
"@saltcorn/markup": "1.1.1-beta.0",
|
|
16
|
+
"@saltcorn/plugins-loader": "1.1.1-beta.0",
|
|
17
|
+
"@saltcorn/sbadmin2": "1.1.1-beta.0",
|
|
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.css
CHANGED
|
@@ -469,6 +469,18 @@ div.unread-notify {
|
|
|
469
469
|
color: inherit;
|
|
470
470
|
}
|
|
471
471
|
|
|
472
|
+
i.add-btw-nodes {
|
|
473
|
+
cursor: pointer;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
pre.mermaid g.node[class*=" wfstep"] {
|
|
477
|
+
cursor: pointer;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
pre.mermaid g.node[class*=" wfadd"] {
|
|
481
|
+
cursor: pointer;
|
|
482
|
+
}
|
|
483
|
+
|
|
472
484
|
.link-style {
|
|
473
485
|
cursor: pointer;
|
|
474
486
|
text-decoration: underline;
|
|
@@ -605,16 +617,18 @@ button.monospace-copy-btn {
|
|
|
605
617
|
display: block;
|
|
606
618
|
}
|
|
607
619
|
|
|
608
|
-
i[class^="unicode-"],
|
|
620
|
+
i[class^="unicode-"],
|
|
621
|
+
i[class*=" unicode-"] {
|
|
609
622
|
font-style: normal;
|
|
610
623
|
}
|
|
611
624
|
|
|
612
|
-
.tabulator.table-dark:not(.thead-light) .tabulator-footer,
|
|
625
|
+
.tabulator.table-dark:not(.thead-light) .tabulator-footer,
|
|
626
|
+
.tabulator.table-dark:not(.thead-light) .tabulator-footer .tabulator-col {
|
|
613
627
|
background-color: #212529;
|
|
614
628
|
border-color: #32383e;
|
|
615
629
|
color: #fff;
|
|
616
630
|
}
|
|
617
631
|
|
|
618
632
|
.mobile-toast-margin {
|
|
619
|
-
margin-bottom:
|
|
620
|
-
}
|
|
633
|
+
margin-bottom: 1rem;
|
|
634
|
+
}
|
package/routes/actions.js
CHANGED
|
@@ -22,6 +22,8 @@ const WorkflowRun = require("@saltcorn/data/models/workflow_run");
|
|
|
22
22
|
const WorkflowTrace = require("@saltcorn/data/models/workflow_trace");
|
|
23
23
|
const Tag = require("@saltcorn/data/models/tag");
|
|
24
24
|
const db = require("@saltcorn/data/db");
|
|
25
|
+
const MarkdownIt = require("markdown-it"),
|
|
26
|
+
md = new MarkdownIt();
|
|
25
27
|
|
|
26
28
|
/**
|
|
27
29
|
* @type {object}
|
|
@@ -507,11 +509,29 @@ function genWorkflowDiagram(steps) {
|
|
|
507
509
|
` ${step.name} --> ${step.configuration.for_loop_step_name}`
|
|
508
510
|
);
|
|
509
511
|
} else if (stepNames.includes(step.next_step)) {
|
|
510
|
-
linkLines.push(
|
|
512
|
+
linkLines.push(
|
|
513
|
+
` ${step.name}-- <i class="fas fa-plus add-btw-nodes btw-nodes-${step.id}-${step.next_step}"></i> ---${step.next_step}`
|
|
514
|
+
);
|
|
511
515
|
} else if (step.next_step) {
|
|
516
|
+
let found = false;
|
|
512
517
|
for (const otherStep of stepNames)
|
|
513
|
-
if (step.next_step.includes(otherStep))
|
|
518
|
+
if (step.next_step.includes(otherStep)) {
|
|
514
519
|
linkLines.push(` ${step.name} --> ${otherStep}`);
|
|
520
|
+
found = true;
|
|
521
|
+
}
|
|
522
|
+
if (!found) {
|
|
523
|
+
linkLines.push(
|
|
524
|
+
` ${step.name}-- <a href="/actions/stepedit/${step.trigger_id}/${step.id}">Error: missing next step in ${step.name}</a> ---_End_${step.name}`
|
|
525
|
+
);
|
|
526
|
+
nodeLines.push(
|
|
527
|
+
` _End_${step.name}:::wfadd${step.id}@{ shape: circle, label: "<i class='fas fa-plus with-link'></i>" }`
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
} else if (!step.next_step) {
|
|
531
|
+
linkLines.push(` ${step.name} --> _End_${step.name}`);
|
|
532
|
+
nodeLines.push(
|
|
533
|
+
` _End_${step.name}:::wfadd${step.id}@{ shape: circle, label: "<i class='fas fa-plus with-link'></i>" }`
|
|
534
|
+
);
|
|
515
535
|
}
|
|
516
536
|
if (step.action_name === "EndForLoop") {
|
|
517
537
|
// TODO this is not correct. improve.
|
|
@@ -526,8 +546,16 @@ function genWorkflowDiagram(steps) {
|
|
|
526
546
|
}
|
|
527
547
|
step_ix += 1;
|
|
528
548
|
}
|
|
549
|
+
if (!steps.length || !steps.find((s) => s.initial_step)) {
|
|
550
|
+
linkLines.push(` _Start --> _End`);
|
|
551
|
+
nodeLines.push(
|
|
552
|
+
` _End:::wfaddstart@{ shape: circle, label: "<i class='fas fa-plus with-link'></i>" }`
|
|
553
|
+
);
|
|
554
|
+
}
|
|
529
555
|
const fc =
|
|
530
556
|
"flowchart TD\n" + nodeLines.join("\n") + "\n" + linkLines.join("\n");
|
|
557
|
+
//console.log(fc);
|
|
558
|
+
|
|
531
559
|
return fc;
|
|
532
560
|
}
|
|
533
561
|
|
|
@@ -561,13 +589,31 @@ const getWorkflowConfig = async (req, id, table, trigger) => {
|
|
|
561
589
|
const ns = $("g.node");
|
|
562
590
|
if(!ns.length) setTimeout(tryAddWFNodes, 200)
|
|
563
591
|
else {
|
|
592
|
+
$("i.add-btw-nodes").on("click", (e)=>{
|
|
593
|
+
const $e = $(e.target || e);
|
|
594
|
+
const cls = $e.attr('class');
|
|
595
|
+
const idnext = cls.split(" ").find(c=>c.startsWith("btw-nodes-")).
|
|
596
|
+
substr(10);
|
|
597
|
+
const [idprev, nmnext] = idnext.split("-");
|
|
598
|
+
location.href = '/actions/stepedit/${trigger.id}?after_step='+idprev+'&before_step='+nmnext;
|
|
599
|
+
})
|
|
564
600
|
$("g.node").on("click", (e)=>{
|
|
565
601
|
const $e = $(e.target || e).closest("g.node")
|
|
566
602
|
const cls = $e.attr('class')
|
|
567
|
-
if(!cls
|
|
603
|
+
if(!cls) return;
|
|
604
|
+
//console.log(cls)
|
|
605
|
+
if(cls.includes("wfstep")) {
|
|
568
606
|
const id = cls.split(" ").find(c=>c.startsWith("wfstep")).
|
|
569
607
|
substr(6);
|
|
570
608
|
location.href = '/actions/stepedit/${trigger.id}/'+id;
|
|
609
|
+
}
|
|
610
|
+
if(cls.includes("wfaddstart")) {
|
|
611
|
+
location.href = '/actions/stepedit/${trigger.id}?initial_step=true';
|
|
612
|
+
} else if(cls.includes("wfadd")) {
|
|
613
|
+
const id = cls.split(" ").find(c=>c.startsWith("wfadd")).
|
|
614
|
+
substr(5);
|
|
615
|
+
location.href = '/actions/stepedit/${trigger.id}?after_step='+id;
|
|
616
|
+
}
|
|
571
617
|
//console.log($e.attr('class'), id)
|
|
572
618
|
})
|
|
573
619
|
}
|
|
@@ -579,7 +625,7 @@ window.addEventListener('DOMContentLoaded',tryAddWFNodes)`
|
|
|
579
625
|
href: `/actions/stepedit/${trigger.id}${
|
|
580
626
|
initial_step ? "" : "?initial_step=true"
|
|
581
627
|
}`,
|
|
582
|
-
class: "btn btn-
|
|
628
|
+
class: "btn btn-secondary",
|
|
583
629
|
},
|
|
584
630
|
i({ class: "fas fa-plus me-2" }),
|
|
585
631
|
"Add step"
|
|
@@ -605,7 +651,13 @@ const jsIdentifierValidator = (s) => {
|
|
|
605
651
|
if (badc) return `Character ${badc} not allowed`;
|
|
606
652
|
};
|
|
607
653
|
|
|
608
|
-
const getWorkflowStepForm = async (
|
|
654
|
+
const getWorkflowStepForm = async (
|
|
655
|
+
trigger,
|
|
656
|
+
req,
|
|
657
|
+
step_id,
|
|
658
|
+
after_step,
|
|
659
|
+
before_step
|
|
660
|
+
) => {
|
|
609
661
|
const table = trigger.table_id ? Table.findOne(trigger.table_id) : null;
|
|
610
662
|
const actionExplainers = {};
|
|
611
663
|
|
|
@@ -626,13 +678,23 @@ const getWorkflowStepForm = async (trigger, req, step_id) => {
|
|
|
626
678
|
});
|
|
627
679
|
|
|
628
680
|
for (const field of cfgFields) {
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
681
|
+
let cfgFld;
|
|
682
|
+
if (field.isRepeat)
|
|
683
|
+
cfgFld = new FieldRepeat({
|
|
684
|
+
...field,
|
|
685
|
+
showIf: {
|
|
686
|
+
wf_action_name: name,
|
|
687
|
+
...(field.showIf || {}),
|
|
688
|
+
},
|
|
689
|
+
});
|
|
690
|
+
else
|
|
691
|
+
cfgFld = {
|
|
692
|
+
...field,
|
|
693
|
+
showIf: {
|
|
694
|
+
wf_action_name: name,
|
|
695
|
+
...(field.showIf || {}),
|
|
696
|
+
},
|
|
697
|
+
};
|
|
636
698
|
if (cfgFld.input_type === "code") cfgFld.input_type = "textarea";
|
|
637
699
|
actionConfigFields.push(cfgFld);
|
|
638
700
|
}
|
|
@@ -646,6 +708,7 @@ const getWorkflowStepForm = async (trigger, req, step_id) => {
|
|
|
646
708
|
"SetContext",
|
|
647
709
|
"TableQuery",
|
|
648
710
|
"Output",
|
|
711
|
+
"DataOutput",
|
|
649
712
|
"WaitUntil",
|
|
650
713
|
"WaitNextTick",
|
|
651
714
|
"UserForm",
|
|
@@ -662,6 +725,8 @@ const getWorkflowStepForm = async (trigger, req, step_id) => {
|
|
|
662
725
|
actionExplainers.TableQuery = "Query a table into a variable in the context";
|
|
663
726
|
actionExplainers.Output =
|
|
664
727
|
"Display a message to the user. Pause workflow until the message is read.";
|
|
728
|
+
actionExplainers.DataOutput =
|
|
729
|
+
"Display a value to the user. Arrays of objects will be displayed as tables. Pause workflow until the message is read.";
|
|
665
730
|
actionExplainers.WaitUntil = "Pause until a time in the future";
|
|
666
731
|
actionExplainers.WaitNextTick =
|
|
667
732
|
"Pause until the next scheduler invocation (at most 5 minutes)";
|
|
@@ -710,6 +775,23 @@ const getWorkflowStepForm = async (trigger, req, step_id) => {
|
|
|
710
775
|
fieldview: "textarea",
|
|
711
776
|
showIf: { wf_action_name: "Output" },
|
|
712
777
|
});
|
|
778
|
+
actionConfigFields.push({
|
|
779
|
+
label: "Output expression",
|
|
780
|
+
name: "output_expr",
|
|
781
|
+
sublabel:
|
|
782
|
+
"JavaScript expression for the value to output. Typically the name of a variable",
|
|
783
|
+
type: "String",
|
|
784
|
+
class: "validate-expression",
|
|
785
|
+
showIf: { wf_action_name: "DataOutput" },
|
|
786
|
+
});
|
|
787
|
+
actionConfigFields.push({
|
|
788
|
+
label: "Markdown",
|
|
789
|
+
name: "markdown",
|
|
790
|
+
sublabel:
|
|
791
|
+
"The centents are markdown formatted and should be rendered to HTML",
|
|
792
|
+
type: "Bool",
|
|
793
|
+
showIf: { wf_action_name: "Output" },
|
|
794
|
+
});
|
|
713
795
|
actionConfigFields.push({
|
|
714
796
|
label: "Table",
|
|
715
797
|
name: "query_table",
|
|
@@ -788,8 +870,8 @@ const getWorkflowStepForm = async (trigger, req, step_id) => {
|
|
|
788
870
|
|
|
789
871
|
const form = new Form({
|
|
790
872
|
action: addOnDoneRedirect(`/actions/stepedit/${trigger.id}`, req),
|
|
791
|
-
onChange: "saveAndContinueIfValid(this)",
|
|
792
|
-
submitLabel: req.__("Done"),
|
|
873
|
+
onChange: step_id ? "saveAndContinueIfValid(this)" : undefined,
|
|
874
|
+
submitLabel: step_id ? req.__("Done") : undefined,
|
|
793
875
|
additionalButtons: step_id
|
|
794
876
|
? [
|
|
795
877
|
{
|
|
@@ -856,6 +938,9 @@ const getWorkflowStepForm = async (trigger, req, step_id) => {
|
|
|
856
938
|
],
|
|
857
939
|
});
|
|
858
940
|
form.hidden("wf_step_id");
|
|
941
|
+
form.hidden("_after_step");
|
|
942
|
+
if (before_step) form.values.wf_next_step = before_step;
|
|
943
|
+
if (after_step) form.values._after_step = after_step;
|
|
859
944
|
if (step_id) {
|
|
860
945
|
const step = await WorkflowStep.findOne({ id: step_id });
|
|
861
946
|
if (!step) throw new Error("Step not found");
|
|
@@ -1366,9 +1451,15 @@ router.get(
|
|
|
1366
1451
|
isAdmin,
|
|
1367
1452
|
error_catcher(async (req, res) => {
|
|
1368
1453
|
const { trigger_id, step_id } = req.params;
|
|
1369
|
-
const { initial_step,
|
|
1454
|
+
const { initial_step, after_step, before_step } = req.query;
|
|
1370
1455
|
const trigger = await Trigger.findOne({ id: trigger_id });
|
|
1371
|
-
const form = await getWorkflowStepForm(
|
|
1456
|
+
const form = await getWorkflowStepForm(
|
|
1457
|
+
trigger,
|
|
1458
|
+
req,
|
|
1459
|
+
step_id,
|
|
1460
|
+
after_step,
|
|
1461
|
+
before_step
|
|
1462
|
+
);
|
|
1372
1463
|
|
|
1373
1464
|
if (initial_step) form.values.wf_initial_step = true;
|
|
1374
1465
|
if (!step_id) {
|
|
@@ -1434,6 +1525,7 @@ router.post(
|
|
|
1434
1525
|
wf_initial_step,
|
|
1435
1526
|
wf_only_if,
|
|
1436
1527
|
wf_step_id,
|
|
1528
|
+
_after_step,
|
|
1437
1529
|
...configuration
|
|
1438
1530
|
} = form.values;
|
|
1439
1531
|
Object.entries(configuration).forEach(([k, v]) => {
|
|
@@ -1469,6 +1561,13 @@ router.post(
|
|
|
1469
1561
|
res.redirect(`/actions/configure/${step.trigger_id}`);
|
|
1470
1562
|
}
|
|
1471
1563
|
}
|
|
1564
|
+
if (_after_step) {
|
|
1565
|
+
const astep = await WorkflowStep.findOne({
|
|
1566
|
+
id: _after_step,
|
|
1567
|
+
trigger_id,
|
|
1568
|
+
});
|
|
1569
|
+
if (astep) await astep.update({ next_step: step.name });
|
|
1570
|
+
}
|
|
1472
1571
|
} catch (e) {
|
|
1473
1572
|
const emsg =
|
|
1474
1573
|
e.message ===
|
|
@@ -1514,6 +1613,10 @@ router.get(
|
|
|
1514
1613
|
[
|
|
1515
1614
|
{ label: "Trigger", key: (run) => trNames[run.trigger_id] },
|
|
1516
1615
|
{ label: "Started", key: (run) => localeDateTime(run.started_at) },
|
|
1616
|
+
{
|
|
1617
|
+
label: "Updated",
|
|
1618
|
+
key: (run) => localeDateTime(run.status_updated_at),
|
|
1619
|
+
},
|
|
1517
1620
|
{ label: "Status", key: "status" },
|
|
1518
1621
|
{
|
|
1519
1622
|
label: "",
|
|
@@ -1698,10 +1801,12 @@ router.post(
|
|
|
1698
1801
|
);
|
|
1699
1802
|
|
|
1700
1803
|
const getWorkflowStepUserForm = async (run, trigger, step, req) => {
|
|
1804
|
+
let blurb = run.wait_info.output || step.configuration?.form_header || "";
|
|
1805
|
+
if (run.wait_info.markdown && run.wait_info.output) blurb = md.render(blurb);
|
|
1701
1806
|
const form = new Form({
|
|
1702
1807
|
action: `/actions/fill-workflow-form/${run.id}`,
|
|
1703
1808
|
submitLabel: run.wait_info.output ? req.__("OK") : req.__("Submit"),
|
|
1704
|
-
blurb
|
|
1809
|
+
blurb,
|
|
1705
1810
|
formStyle: run.wait_info.output || req.xhr ? "vert" : undefined,
|
|
1706
1811
|
fields: await run.userFormFields(step),
|
|
1707
1812
|
});
|