@saltcorn/server 1.1.1-beta.1 → 1.1.1-beta.2
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/help/Event types.tmd +2 -0
- package/locales/en.json +7 -1
- package/package.json +9 -9
- package/public/saltcorn.js +87 -17
- package/routes/actions.js +66 -7
- package/routes/admin.js +257 -46
- package/routes/fields.js +9 -0
- package/routes/menu.js +1 -0
- package/routes/notifications.js +20 -12
- package/routes/page.js +2 -2
- package/routes/pageedit.js +34 -1
- package/routes/utils.js +4 -0
- package/routes/view.js +14 -12
- package/routes/viewedit.js +26 -0
- package/tests/clientjs.test.js +19 -0
package/help/Event types.tmd
CHANGED
|
@@ -55,6 +55,8 @@ user verification is enabled.
|
|
|
55
55
|
|
|
56
56
|
**Startup**: run this whenever this saltcorn process initializes.
|
|
57
57
|
|
|
58
|
+
**AppChange**: the application build (views, pages, triggers etc.) changed
|
|
59
|
+
|
|
58
60
|
## Other events
|
|
59
61
|
|
|
60
62
|
**Never**: this trigger is never run on its own. However triggers that are marked as never
|
package/locales/en.json
CHANGED
|
@@ -1520,7 +1520,13 @@
|
|
|
1520
1520
|
"OK": "OK",
|
|
1521
1521
|
"Step settings": "Step settings",
|
|
1522
1522
|
"Action settings": "Action settings",
|
|
1523
|
+
"Keystore file is not applied for debug builds.": "Keystore file is not applied for debug builds.",
|
|
1523
1524
|
"Workflow": "Workflow",
|
|
1524
1525
|
"Previous runs": "Previous runs",
|
|
1525
|
-
"The workflow the user will be interacting with.": "The workflow the user will be interacting with."
|
|
1526
|
+
"The workflow the user will be interacting with.": "The workflow the user will be interacting with.",
|
|
1527
|
+
"Delete old workflow runs with status after days": "Delete old workflow runs with status after days",
|
|
1528
|
+
"Finished": "Finished",
|
|
1529
|
+
"Error": "Error",
|
|
1530
|
+
"Waiting": "Waiting",
|
|
1531
|
+
"Running": "Running"
|
|
1526
1532
|
}
|
package/package.json
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/server",
|
|
3
|
-
"version": "1.1.1-beta.
|
|
3
|
+
"version": "1.1.1-beta.2",
|
|
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.1-beta.
|
|
11
|
-
"@saltcorn/builder": "1.1.1-beta.
|
|
12
|
-
"@saltcorn/data": "1.1.1-beta.
|
|
13
|
-
"@saltcorn/admin-models": "1.1.1-beta.
|
|
14
|
-
"@saltcorn/filemanager": "1.1.1-beta.
|
|
15
|
-
"@saltcorn/markup": "1.1.1-beta.
|
|
16
|
-
"@saltcorn/plugins-loader": "1.1.1-beta.
|
|
17
|
-
"@saltcorn/sbadmin2": "1.1.1-beta.
|
|
10
|
+
"@saltcorn/base-plugin": "1.1.1-beta.2",
|
|
11
|
+
"@saltcorn/builder": "1.1.1-beta.2",
|
|
12
|
+
"@saltcorn/data": "1.1.1-beta.2",
|
|
13
|
+
"@saltcorn/admin-models": "1.1.1-beta.2",
|
|
14
|
+
"@saltcorn/filemanager": "1.1.1-beta.2",
|
|
15
|
+
"@saltcorn/markup": "1.1.1-beta.2",
|
|
16
|
+
"@saltcorn/plugins-loader": "1.1.1-beta.2",
|
|
17
|
+
"@saltcorn/sbadmin2": "1.1.1-beta.2",
|
|
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
|
@@ -65,7 +65,7 @@ function updateQueryStringParameters(uri1, kvs) {
|
|
|
65
65
|
return uri;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
function removeQueryStringParameter(uri1, key) {
|
|
68
|
+
function removeQueryStringParameter(uri1, key, value) {
|
|
69
69
|
let hash = "";
|
|
70
70
|
let uri = uri1;
|
|
71
71
|
if (uri && uri.includes("#")) {
|
|
@@ -73,8 +73,12 @@ function removeQueryStringParameter(uri1, key) {
|
|
|
73
73
|
hash = "#" + uris[1];
|
|
74
74
|
uri = uris[0];
|
|
75
75
|
}
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
let re;
|
|
77
|
+
if (value) {
|
|
78
|
+
re = new RegExp("([?&])" + key + "=" + value + "?(&|$)", "gi");
|
|
79
|
+
} else {
|
|
80
|
+
re = new RegExp("([?&])" + key + "=.*?(&|$)", "gi");
|
|
81
|
+
}
|
|
78
82
|
if (uri.match(re)) {
|
|
79
83
|
uri = uri.replace(re, "$1" + "$2");
|
|
80
84
|
}
|
|
@@ -84,6 +88,28 @@ function removeQueryStringParameter(uri1, key) {
|
|
|
84
88
|
return uri + hash;
|
|
85
89
|
}
|
|
86
90
|
|
|
91
|
+
function addQueryStringParameter(uri1, key, value) {
|
|
92
|
+
let hash = "";
|
|
93
|
+
let uri = uri1;
|
|
94
|
+
if (uri && uri.includes("#")) {
|
|
95
|
+
let uris = uri1.split("#");
|
|
96
|
+
hash = "#" + uris[1];
|
|
97
|
+
uri = uris[0];
|
|
98
|
+
}
|
|
99
|
+
var re = new RegExp("([?&])" + key + "=" + value + "?(&|$)", "gi");
|
|
100
|
+
if (uri.match(re)) return uri1;
|
|
101
|
+
|
|
102
|
+
var separator = uri.indexOf("?") !== -1 ? "&" : "?";
|
|
103
|
+
if (Array.isArray(value))
|
|
104
|
+
return (
|
|
105
|
+
uri +
|
|
106
|
+
separator +
|
|
107
|
+
value.map((val) => key + "=" + encodeURIComponent(val)).join("&") +
|
|
108
|
+
hash
|
|
109
|
+
);
|
|
110
|
+
else return uri + separator + key + "=" + encodeURIComponent(value) + hash;
|
|
111
|
+
}
|
|
112
|
+
|
|
87
113
|
function select_id(id, e) {
|
|
88
114
|
pjax_to(updateQueryStringParameter(get_current_state_url(e), "id", id), e);
|
|
89
115
|
}
|
|
@@ -96,10 +122,10 @@ function check_state_field(that, e) {
|
|
|
96
122
|
const checked = that.checked;
|
|
97
123
|
const name = that.name;
|
|
98
124
|
const value = encodeURIComponent(that.value);
|
|
99
|
-
var separator = get_current_state_url(e).indexOf("?") !== -1 ? "&" : "?";
|
|
100
125
|
let dest;
|
|
101
|
-
if (checked)
|
|
102
|
-
|
|
126
|
+
if (checked)
|
|
127
|
+
dest = addQueryStringParameter(get_current_state_url(e), name, value);
|
|
128
|
+
else dest = removeQueryStringParameter(get_current_state_url(e), name, value);
|
|
103
129
|
pjax_to(dest.replace("&&", "&").replace("?&", "?"), e);
|
|
104
130
|
}
|
|
105
131
|
|
|
@@ -591,7 +617,7 @@ function updateViewPreview() {
|
|
|
591
617
|
function ajaxSubmitForm(e, force_no_reload, event) {
|
|
592
618
|
var form = $(e).closest("form");
|
|
593
619
|
var url = form.attr("action");
|
|
594
|
-
if(event) event.preventDefault();
|
|
620
|
+
if (event) event.preventDefault();
|
|
595
621
|
$.ajax(url, {
|
|
596
622
|
type: "POST",
|
|
597
623
|
headers: {
|
|
@@ -606,10 +632,10 @@ function ajaxSubmitForm(e, force_no_reload, event) {
|
|
|
606
632
|
"data-on-close-reload-view"
|
|
607
633
|
);
|
|
608
634
|
$("#scmodal").modal("hide");
|
|
609
|
-
if (
|
|
635
|
+
if (on_close_reload_view) {
|
|
610
636
|
const viewE = $(`[data-sc-embed-viewname="${on_close_reload_view}"]`);
|
|
611
637
|
if (viewE.length) reload_embedded_view(on_close_reload_view);
|
|
612
|
-
else location.reload();
|
|
638
|
+
else if (!force_no_reload) location.reload();
|
|
613
639
|
} else if (!force_no_reload && !no_reload) location.reload();
|
|
614
640
|
else common_done(res, form.attr("data-viewname"));
|
|
615
641
|
},
|
|
@@ -859,10 +885,16 @@ function builderMenuChanged(e) {
|
|
|
859
885
|
});
|
|
860
886
|
}
|
|
861
887
|
|
|
862
|
-
function poll_mobile_build_finished(
|
|
888
|
+
function poll_mobile_build_finished(
|
|
889
|
+
outDirName,
|
|
890
|
+
buildDir,
|
|
891
|
+
mode,
|
|
892
|
+
pollCount,
|
|
893
|
+
orginalBtnHtml
|
|
894
|
+
) {
|
|
863
895
|
$.ajax("/admin/build-mobile-app/finished", {
|
|
864
896
|
type: "GET",
|
|
865
|
-
data: {
|
|
897
|
+
data: { out_dir_name: outDirName, mode: mode },
|
|
866
898
|
success: function (res) {
|
|
867
899
|
if (!res.finished) {
|
|
868
900
|
if (pollCount >= 100) {
|
|
@@ -873,14 +905,45 @@ function poll_mobile_build_finished(outDirName, pollCount, orginalBtnHtml) {
|
|
|
873
905
|
});
|
|
874
906
|
} else {
|
|
875
907
|
setTimeout(() => {
|
|
876
|
-
poll_mobile_build_finished(
|
|
908
|
+
poll_mobile_build_finished(
|
|
909
|
+
outDirName,
|
|
910
|
+
buildDir,
|
|
911
|
+
mode,
|
|
912
|
+
++pollCount,
|
|
913
|
+
orginalBtnHtml
|
|
914
|
+
);
|
|
877
915
|
}, 5000);
|
|
878
916
|
}
|
|
879
917
|
} else {
|
|
880
918
|
href_to(
|
|
881
|
-
|
|
919
|
+
`/admin/build-mobile-app/result?out_dir_name=${encodeURIComponent(
|
|
882
920
|
outDirName
|
|
883
|
-
)}`
|
|
921
|
+
)}&build_dir=${encodeURIComponent(buildDir)}&mode=${mode}`
|
|
922
|
+
);
|
|
923
|
+
}
|
|
924
|
+
},
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
function finish_mobile_app(button, outDirName, buildDir) {
|
|
929
|
+
$.ajax("/admin/build-mobile-app/finish", {
|
|
930
|
+
type: "POST",
|
|
931
|
+
headers: {
|
|
932
|
+
"CSRF-Token": _sc_globalCsrf,
|
|
933
|
+
},
|
|
934
|
+
data: { out_dir_name: outDirName, build_dir: buildDir },
|
|
935
|
+
success: function (data) {
|
|
936
|
+
if (data.success) {
|
|
937
|
+
notifyAlert("Finishing the app, please wait.", true);
|
|
938
|
+
for (const msg of data.msgs || []) notifyAlert(msg);
|
|
939
|
+
const orginalBtnHtml = $("#finishMobileAppBtnId").html();
|
|
940
|
+
press_store_button(button);
|
|
941
|
+
poll_mobile_build_finished(
|
|
942
|
+
outDirName,
|
|
943
|
+
buildDir,
|
|
944
|
+
"finish",
|
|
945
|
+
0,
|
|
946
|
+
orginalBtnHtml
|
|
884
947
|
);
|
|
885
948
|
}
|
|
886
949
|
},
|
|
@@ -938,11 +1001,18 @@ function build_mobile_app(button) {
|
|
|
938
1001
|
ajax_post("/admin/build-mobile-app", {
|
|
939
1002
|
data: params,
|
|
940
1003
|
success: (data) => {
|
|
941
|
-
if (data.
|
|
942
|
-
|
|
1004
|
+
if (data.out_dir_name && data.build_dir) {
|
|
1005
|
+
notifyAlert("Building the app, please wait.", true);
|
|
1006
|
+
for (const msg of data.msgs || []) notifyAlert(msg);
|
|
943
1007
|
const orginalBtnHtml = $("#buildMobileAppBtnId").html();
|
|
944
1008
|
press_store_button(button);
|
|
945
|
-
poll_mobile_build_finished(
|
|
1009
|
+
poll_mobile_build_finished(
|
|
1010
|
+
data.out_dir_name,
|
|
1011
|
+
data.build_dir,
|
|
1012
|
+
data.mode,
|
|
1013
|
+
0,
|
|
1014
|
+
orginalBtnHtml
|
|
1015
|
+
);
|
|
946
1016
|
}
|
|
947
1017
|
},
|
|
948
1018
|
});
|
package/routes/actions.js
CHANGED
|
@@ -14,6 +14,10 @@ const {
|
|
|
14
14
|
const { ppVal, jsIdentifierValidator } = require("@saltcorn/data/utils");
|
|
15
15
|
const { getState } = require("@saltcorn/data/db/state");
|
|
16
16
|
const Trigger = require("@saltcorn/data/models/trigger");
|
|
17
|
+
const View = require("@saltcorn/data/models/view");
|
|
18
|
+
const {
|
|
19
|
+
getForm,
|
|
20
|
+
} = require("@saltcorn/data/base-plugin/viewtemplates/viewable_fields");
|
|
17
21
|
const FieldRepeat = require("@saltcorn/data/models/fieldrepeat");
|
|
18
22
|
const { getTriggerList } = require("./common_lists");
|
|
19
23
|
const TagEntry = require("@saltcorn/data/models/tag_entry");
|
|
@@ -441,6 +445,10 @@ router.post(
|
|
|
441
445
|
const tr = await Trigger.create(form.values);
|
|
442
446
|
id = tr.id;
|
|
443
447
|
}
|
|
448
|
+
Trigger.emitEvent("AppChange", `Trigger ${form.values.name}`, req.user, {
|
|
449
|
+
entity_type: "Trigger",
|
|
450
|
+
entity_name: form.values.name,
|
|
451
|
+
});
|
|
444
452
|
res.redirect(addOnDoneRedirect(`/actions/configure/${id}`, req));
|
|
445
453
|
}
|
|
446
454
|
})
|
|
@@ -482,6 +490,10 @@ router.post(
|
|
|
482
490
|
...form.values.configuration,
|
|
483
491
|
};
|
|
484
492
|
await Trigger.update(trigger.id, form.values); //{configuration: form.values});
|
|
493
|
+
Trigger.emitEvent("AppChange", `Trigger ${trigger.name}`, req.user, {
|
|
494
|
+
entity_type: "Trigger",
|
|
495
|
+
entity_name: trigger.name,
|
|
496
|
+
});
|
|
485
497
|
if (req.xhr) {
|
|
486
498
|
res.json({ success: "ok" });
|
|
487
499
|
return;
|
|
@@ -533,9 +545,11 @@ function genWorkflowDiagram(steps) {
|
|
|
533
545
|
}
|
|
534
546
|
if (step.action_name === "ForLoop") {
|
|
535
547
|
linkLines.push(
|
|
536
|
-
` ${step.mmname}-.->${WorkflowStep.mmescape(
|
|
548
|
+
` ${step.mmname}-.->${WorkflowStep.mmescape(
|
|
549
|
+
step.configuration.loop_body_initial_step
|
|
550
|
+
)}`
|
|
537
551
|
);
|
|
538
|
-
}
|
|
552
|
+
}
|
|
539
553
|
if (step.action_name === "EndForLoop") {
|
|
540
554
|
// TODO this is not correct. improve.
|
|
541
555
|
let forStep;
|
|
@@ -711,10 +725,20 @@ const getWorkflowStepForm = async (
|
|
|
711
725
|
},
|
|
712
726
|
};
|
|
713
727
|
if (cfgFld.input_type === "code") cfgFld.input_type = "textarea";
|
|
714
|
-
actionConfigFields.push(cfgFld);
|
|
715
728
|
}
|
|
716
729
|
} catch {}
|
|
717
730
|
}
|
|
731
|
+
actionConfigFields.push({
|
|
732
|
+
label: "Subcontext",
|
|
733
|
+
name: "subcontext",
|
|
734
|
+
type: "String",
|
|
735
|
+
sublabel:
|
|
736
|
+
"Optional. A key on the current workflow's context, the values of which will be the called workflow's context.",
|
|
737
|
+
showIf: {
|
|
738
|
+
wf_action_name: Trigger.find({ action: "Workflow" }).map((wf) => wf.name),
|
|
739
|
+
},
|
|
740
|
+
});
|
|
741
|
+
|
|
718
742
|
const builtInActionExplainers = WorkflowStep.builtInActionExplainers();
|
|
719
743
|
const actionsNotRequiringRow = Trigger.action_options({
|
|
720
744
|
notRequireRow: true,
|
|
@@ -730,7 +754,9 @@ const getWorkflowStepForm = async (
|
|
|
730
754
|
if (tr.description) actionExplainers[tr.name] = tr.description;
|
|
731
755
|
});
|
|
732
756
|
Object.assign(actionExplainers, builtInActionExplainers);
|
|
733
|
-
actionConfigFields.push(
|
|
757
|
+
actionConfigFields.push(
|
|
758
|
+
...(await WorkflowStep.builtInActionConfigFields({ trigger }))
|
|
759
|
+
);
|
|
734
760
|
|
|
735
761
|
const form = new Form({
|
|
736
762
|
action: addOnDoneRedirect(`/actions/stepedit/${trigger.id}`, req),
|
|
@@ -1142,6 +1168,10 @@ router.post(
|
|
|
1142
1168
|
await Trigger.update(trigger.id, {
|
|
1143
1169
|
configuration: { ...trigger.configuration, ...form.values },
|
|
1144
1170
|
});
|
|
1171
|
+
Trigger.emitEvent("AppChange", `Trigger ${trigger.name}`, req.user, {
|
|
1172
|
+
entity_type: "Trigger",
|
|
1173
|
+
entity_name: trigger.name,
|
|
1174
|
+
});
|
|
1145
1175
|
if (req.xhr) {
|
|
1146
1176
|
res.json({ success: "ok" });
|
|
1147
1177
|
return;
|
|
@@ -1168,6 +1198,10 @@ router.post(
|
|
|
1168
1198
|
error_catcher(async (req, res) => {
|
|
1169
1199
|
const { id } = req.params;
|
|
1170
1200
|
const trigger = await Trigger.findOne({ id });
|
|
1201
|
+
Trigger.emitEvent("AppChange", `Trigger ${trigger.name}`, req.user, {
|
|
1202
|
+
entity_type: "Trigger",
|
|
1203
|
+
entity_name: trigger.name,
|
|
1204
|
+
});
|
|
1171
1205
|
await trigger.delete();
|
|
1172
1206
|
res.redirect(`/actions/`);
|
|
1173
1207
|
})
|
|
@@ -1296,6 +1330,10 @@ router.post(
|
|
|
1296
1330
|
const { id } = req.params;
|
|
1297
1331
|
const trig = await Trigger.findOne({ id });
|
|
1298
1332
|
const newtrig = await trig.clone();
|
|
1333
|
+
Trigger.emitEvent("AppChange", `Trigger ${newtrig.name}`, req.user, {
|
|
1334
|
+
entity_type: "Trigger",
|
|
1335
|
+
entity_name: newtrig.name,
|
|
1336
|
+
});
|
|
1299
1337
|
req.flash(
|
|
1300
1338
|
"success",
|
|
1301
1339
|
req.__("Trigger %s duplicated as %s", trig.name, newtrig.name)
|
|
@@ -1425,6 +1463,10 @@ router.post(
|
|
|
1425
1463
|
res.redirect(`/actions/configure/${step.trigger_id}`);
|
|
1426
1464
|
}
|
|
1427
1465
|
}
|
|
1466
|
+
Trigger.emitEvent("AppChange", `Trigger ${trigger.name}`, req.user, {
|
|
1467
|
+
entity_type: "Trigger",
|
|
1468
|
+
entity_name: trigger.name,
|
|
1469
|
+
});
|
|
1428
1470
|
if (_after_step && _after_step !== "undefined") {
|
|
1429
1471
|
const astep = await WorkflowStep.findOne({
|
|
1430
1472
|
id: _after_step,
|
|
@@ -1465,6 +1507,10 @@ router.post(
|
|
|
1465
1507
|
step.trigger_id = trigger.id;
|
|
1466
1508
|
await WorkflowStep.create(step);
|
|
1467
1509
|
}
|
|
1510
|
+
Trigger.emitEvent("AppChange", `Trigger ${trigger.name}`, req.user, {
|
|
1511
|
+
entity_type: "Trigger",
|
|
1512
|
+
entity_name: trigger.name,
|
|
1513
|
+
});
|
|
1468
1514
|
res.redirect(`/actions/configure/${trigger.id}`);
|
|
1469
1515
|
})
|
|
1470
1516
|
);
|
|
@@ -1687,6 +1733,21 @@ router.post(
|
|
|
1687
1733
|
);
|
|
1688
1734
|
|
|
1689
1735
|
const getWorkflowStepUserForm = async (run, trigger, step, req) => {
|
|
1736
|
+
if (step.action_name === "EditViewForm") {
|
|
1737
|
+
const view = View.findOne({ name: step.configuration.edit_view });
|
|
1738
|
+
const table = Table.findOne({ id: view.table_id });
|
|
1739
|
+
const form = await getForm(
|
|
1740
|
+
table,
|
|
1741
|
+
view.name,
|
|
1742
|
+
view.configuration.columns,
|
|
1743
|
+
view.configuration.layout,
|
|
1744
|
+
null,
|
|
1745
|
+
req
|
|
1746
|
+
);
|
|
1747
|
+
form.action = `/actions/fill-workflow-form/${run.id}`;
|
|
1748
|
+
return form;
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1690
1751
|
let blurb = run.wait_info.output || step.configuration?.form_header || "";
|
|
1691
1752
|
if (run.wait_info.markdown && run.wait_info.output) blurb = md.render(blurb);
|
|
1692
1753
|
const form = new Form({
|
|
@@ -1819,12 +1880,10 @@ WORKFLOWS TODO
|
|
|
1819
1880
|
|
|
1820
1881
|
help file to explain steps, and context
|
|
1821
1882
|
|
|
1822
|
-
workflow actions:
|
|
1883
|
+
workflow actions: Stop, RunEditView, ReadFile, WriteFile, APIResponse
|
|
1823
1884
|
|
|
1824
|
-
Error handlers
|
|
1825
1885
|
other triggers can be steps
|
|
1826
1886
|
interactive workflows for not logged in
|
|
1827
|
-
show end node in diagram
|
|
1828
1887
|
actions can declare which variables they inject into scope
|
|
1829
1888
|
|
|
1830
1889
|
show unconnected steps
|
package/routes/admin.js
CHANGED
|
@@ -19,6 +19,7 @@ const Plugin = require("@saltcorn/data/models/plugin");
|
|
|
19
19
|
const File = require("@saltcorn/data/models/file");
|
|
20
20
|
const { spawn, exec } = require("child_process");
|
|
21
21
|
const User = require("@saltcorn/data/models/user");
|
|
22
|
+
const Trigger = require("@saltcorn/data/models/trigger");
|
|
22
23
|
const path = require("path");
|
|
23
24
|
const { X509Certificate } = require("crypto");
|
|
24
25
|
const { getAllTenants } = require("@saltcorn/admin-models/models/tenant");
|
|
@@ -146,6 +147,24 @@ const app_files_table = (files, buildDirName, req) =>
|
|
|
146
147
|
],
|
|
147
148
|
files
|
|
148
149
|
);
|
|
150
|
+
const intermediate_build_result = (outDirName, buildDir, req) => {
|
|
151
|
+
return div(
|
|
152
|
+
h3("Intermediate build result"),
|
|
153
|
+
div(
|
|
154
|
+
button(
|
|
155
|
+
{
|
|
156
|
+
id: "finishMobileAppBtnId",
|
|
157
|
+
type: "button",
|
|
158
|
+
onClick: `finish_mobile_app(this, '${outDirName}', '${buildDir}');`,
|
|
159
|
+
class: "btn btn-warning",
|
|
160
|
+
},
|
|
161
|
+
i({ class: "fas fa-hammer pe-2" }),
|
|
162
|
+
|
|
163
|
+
req.__("Finish the build")
|
|
164
|
+
)
|
|
165
|
+
)
|
|
166
|
+
);
|
|
167
|
+
};
|
|
149
168
|
|
|
150
169
|
admin_config_route({
|
|
151
170
|
router,
|
|
@@ -1993,9 +2012,6 @@ const buildDialogScript = (capacitorBuilderAvailable, isSbadmin2) =>
|
|
|
1993
2012
|
$("#entryPointTypeID").attr("value", type);
|
|
1994
2013
|
}
|
|
1995
2014
|
|
|
1996
|
-
function handleMessages() {
|
|
1997
|
-
notifyAlert("Building the app, please wait.", true)
|
|
1998
|
-
}
|
|
1999
2015
|
const versionPattern = /^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/;
|
|
2000
2016
|
${domReady(`
|
|
2001
2017
|
const versionInput = document.getElementById('appVersionInputId');
|
|
@@ -3077,6 +3093,48 @@ router.get(
|
|
|
3077
3093
|
)
|
|
3078
3094
|
)
|
|
3079
3095
|
)
|
|
3096
|
+
// Share Extension provisioning profile
|
|
3097
|
+
// disabled for now
|
|
3098
|
+
// div(
|
|
3099
|
+
// { class: "row pb-3" },
|
|
3100
|
+
// div(
|
|
3101
|
+
// { class: "col-sm-8" },
|
|
3102
|
+
// label(
|
|
3103
|
+
// {
|
|
3104
|
+
// for: "shareProvisioningProfileInputId",
|
|
3105
|
+
// class: "form-label fw-bold",
|
|
3106
|
+
// },
|
|
3107
|
+
// req.__("Share Extension Provisioning Profile"),
|
|
3108
|
+
// a(
|
|
3109
|
+
// {
|
|
3110
|
+
// href: "javascript:ajax_modal('/admin/help/Provisioning Profile?')",
|
|
3111
|
+
// },
|
|
3112
|
+
// i({ class: "fas fa-question-circle ps-1" })
|
|
3113
|
+
// )
|
|
3114
|
+
// ),
|
|
3115
|
+
// select(
|
|
3116
|
+
// {
|
|
3117
|
+
// class: "form-select",
|
|
3118
|
+
// name: "shareProvisioningProfile",
|
|
3119
|
+
// id: "shareProvisioningProfileInputId",
|
|
3120
|
+
// },
|
|
3121
|
+
// [
|
|
3122
|
+
// option({ value: "" }, ""),
|
|
3123
|
+
// ...provisioningFiles.map((file) =>
|
|
3124
|
+
// option(
|
|
3125
|
+
// {
|
|
3126
|
+
// value: file.location,
|
|
3127
|
+
// selected:
|
|
3128
|
+
// builderSettings.shareProvisioningProfile ===
|
|
3129
|
+
// file.location,
|
|
3130
|
+
// },
|
|
3131
|
+
// file.filename
|
|
3132
|
+
// )
|
|
3133
|
+
// ),
|
|
3134
|
+
// ].join("")
|
|
3135
|
+
// )
|
|
3136
|
+
// )
|
|
3137
|
+
// )
|
|
3080
3138
|
)
|
|
3081
3139
|
)
|
|
3082
3140
|
),
|
|
@@ -3100,15 +3158,13 @@ router.get(
|
|
|
3100
3158
|
})
|
|
3101
3159
|
);
|
|
3102
3160
|
|
|
3103
|
-
const checkFiles = async (
|
|
3161
|
+
const checkFiles = async (outDirName, fileNames) => {
|
|
3104
3162
|
const rootFolder = await File.rootFolder();
|
|
3105
|
-
const
|
|
3163
|
+
const outDir = path.join(rootFolder.location, "mobile_app", outDirName);
|
|
3106
3164
|
const unsafeFiles = await Promise.all(
|
|
3107
3165
|
fs
|
|
3108
|
-
.readdirSync(
|
|
3109
|
-
.map(
|
|
3110
|
-
async (outFile) => await File.from_file_on_disk(outFile, mobile_app_dir)
|
|
3111
|
-
)
|
|
3166
|
+
.readdirSync(outDir)
|
|
3167
|
+
.map(async (outFile) => await File.from_file_on_disk(outFile, outDir))
|
|
3112
3168
|
);
|
|
3113
3169
|
const entries = unsafeFiles
|
|
3114
3170
|
.filter(
|
|
@@ -3127,9 +3183,18 @@ router.get(
|
|
|
3127
3183
|
"/build-mobile-app/finished",
|
|
3128
3184
|
isAdmin,
|
|
3129
3185
|
error_catcher(async (req, res) => {
|
|
3130
|
-
const {
|
|
3186
|
+
const { out_dir_name, mode } = req.query;
|
|
3187
|
+
const stepDesc =
|
|
3188
|
+
mode === "prepare"
|
|
3189
|
+
? "_prepare_step"
|
|
3190
|
+
: mode === "finish"
|
|
3191
|
+
? "_finish_step"
|
|
3192
|
+
: "";
|
|
3131
3193
|
res.json({
|
|
3132
|
-
finished: await checkFiles(
|
|
3194
|
+
finished: await checkFiles(out_dir_name, [
|
|
3195
|
+
`logs${stepDesc}.txt`,
|
|
3196
|
+
`error_logs${stepDesc}.txt`,
|
|
3197
|
+
]),
|
|
3133
3198
|
});
|
|
3134
3199
|
})
|
|
3135
3200
|
);
|
|
@@ -3164,8 +3229,8 @@ router.get(
|
|
|
3164
3229
|
"/build-mobile-app/result",
|
|
3165
3230
|
isAdmin,
|
|
3166
3231
|
error_catcher(async (req, res) => {
|
|
3167
|
-
const {
|
|
3168
|
-
if (!validateBuildDirName(
|
|
3232
|
+
const { out_dir_name, build_dir, mode } = req.query;
|
|
3233
|
+
if (!validateBuildDirName(out_dir_name)) {
|
|
3169
3234
|
return res.sendWrap(req.__(`Admin`), {
|
|
3170
3235
|
above: [
|
|
3171
3236
|
{
|
|
@@ -3177,11 +3242,7 @@ router.get(
|
|
|
3177
3242
|
});
|
|
3178
3243
|
}
|
|
3179
3244
|
const rootFolder = await File.rootFolder();
|
|
3180
|
-
const buildDir = path.join(
|
|
3181
|
-
rootFolder.location,
|
|
3182
|
-
"mobile_app",
|
|
3183
|
-
build_dir_name
|
|
3184
|
-
);
|
|
3245
|
+
const buildDir = path.join(rootFolder.location, "mobile_app", out_dir_name);
|
|
3185
3246
|
if (!validateBuildDir(buildDir, rootFolder.location)) {
|
|
3186
3247
|
return res.sendWrap(req.__(`Admin`), {
|
|
3187
3248
|
above: [
|
|
@@ -3199,7 +3260,15 @@ router.get(
|
|
|
3199
3260
|
.readdirSync(buildDir)
|
|
3200
3261
|
.map(async (outFile) => await File.from_file_on_disk(outFile, buildDir))
|
|
3201
3262
|
);
|
|
3202
|
-
const
|
|
3263
|
+
const stepDesc =
|
|
3264
|
+
mode === "prepare"
|
|
3265
|
+
? "_prepare_step"
|
|
3266
|
+
: mode === "finish"
|
|
3267
|
+
? "_finish_step"
|
|
3268
|
+
: "";
|
|
3269
|
+
const resultMsg = files.find(
|
|
3270
|
+
(file) => file.filename === `logs${stepDesc}.txt`
|
|
3271
|
+
)
|
|
3203
3272
|
? req.__("The build was successfully")
|
|
3204
3273
|
: req.__("Unable to build the app");
|
|
3205
3274
|
res.sendWrap(req.__(`Admin`), {
|
|
@@ -3209,11 +3278,98 @@ router.get(
|
|
|
3209
3278
|
title: req.__("Build Result"),
|
|
3210
3279
|
contents: div(resultMsg),
|
|
3211
3280
|
},
|
|
3212
|
-
files.length > 0 ? app_files_table(files,
|
|
3281
|
+
files.length > 0 ? app_files_table(files, out_dir_name, req) : "",
|
|
3282
|
+
mode === "prepare"
|
|
3283
|
+
? intermediate_build_result(out_dir_name, build_dir, req)
|
|
3284
|
+
: "",
|
|
3213
3285
|
],
|
|
3214
3286
|
});
|
|
3215
3287
|
})
|
|
3216
3288
|
);
|
|
3289
|
+
|
|
3290
|
+
router.post(
|
|
3291
|
+
"/build-mobile-app/finish",
|
|
3292
|
+
isAdmin,
|
|
3293
|
+
error_catcher(async (req, res) => {
|
|
3294
|
+
const { out_dir_name, build_dir } = req.body;
|
|
3295
|
+
const content = await fs.promises.readFile(
|
|
3296
|
+
path.join(build_dir, "spawnParams.json")
|
|
3297
|
+
);
|
|
3298
|
+
const spawnParams = JSON.parse(content);
|
|
3299
|
+
const rootFolder = await File.rootFolder();
|
|
3300
|
+
const outDirFullPath = path.join(
|
|
3301
|
+
rootFolder.location,
|
|
3302
|
+
"mobile_app",
|
|
3303
|
+
out_dir_name
|
|
3304
|
+
);
|
|
3305
|
+
res.json({
|
|
3306
|
+
success: true,
|
|
3307
|
+
});
|
|
3308
|
+
const child = spawn(
|
|
3309
|
+
getSafeSaltcornCmd(),
|
|
3310
|
+
[...spawnParams, "-m", "finish"],
|
|
3311
|
+
{
|
|
3312
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
3313
|
+
cwd: ".",
|
|
3314
|
+
}
|
|
3315
|
+
);
|
|
3316
|
+
const childOutputs = [];
|
|
3317
|
+
child.stdout.on("data", (data) => {
|
|
3318
|
+
const outMsg = data.toString();
|
|
3319
|
+
getState().log(5, outMsg);
|
|
3320
|
+
if (data) childOutputs.push(outMsg);
|
|
3321
|
+
});
|
|
3322
|
+
child.stderr.on("data", (data) => {
|
|
3323
|
+
const errMsg = data ? data.toString() : req.__("An error occurred");
|
|
3324
|
+
getState().log(5, errMsg);
|
|
3325
|
+
childOutputs.push(errMsg);
|
|
3326
|
+
});
|
|
3327
|
+
child.on("exit", async (exitCode, signal) => {
|
|
3328
|
+
const logFile =
|
|
3329
|
+
exitCode === 0 ? "logs_finish_step.txt" : "error_logs_finish_step.txt";
|
|
3330
|
+
try {
|
|
3331
|
+
const exitMsg = childOutputs.join("\n");
|
|
3332
|
+
await fs.promises.writeFile(
|
|
3333
|
+
path.join(outDirFullPath, logFile),
|
|
3334
|
+
exitMsg
|
|
3335
|
+
);
|
|
3336
|
+
await File.set_xattr_of_existing_file(
|
|
3337
|
+
logFile,
|
|
3338
|
+
outDirFullPath,
|
|
3339
|
+
req.user
|
|
3340
|
+
);
|
|
3341
|
+
} catch (error) {
|
|
3342
|
+
console.log(`unable to write '${logFile}' to '${outDirFullPath}'`);
|
|
3343
|
+
console.log(error);
|
|
3344
|
+
}
|
|
3345
|
+
});
|
|
3346
|
+
child.on("error", (msg) => {
|
|
3347
|
+
const message = msg.message ? msg.message : msg.code;
|
|
3348
|
+
const stack = msg.stack ? msg.stack : "";
|
|
3349
|
+
const logFile = "error_logs.txt";
|
|
3350
|
+
const errMsg = [message, stack].join("\n");
|
|
3351
|
+
getState().log(5, msg);
|
|
3352
|
+
fs.writeFile(
|
|
3353
|
+
path.join(outDirFullPath, "error_logs.txt"),
|
|
3354
|
+
errMsg,
|
|
3355
|
+
async (error) => {
|
|
3356
|
+
if (error) {
|
|
3357
|
+
console.log(`unable to write logFile to '${outDirFullPath}'`);
|
|
3358
|
+
console.log(error);
|
|
3359
|
+
} else {
|
|
3360
|
+
// no transaction, '/build-mobile-app/finished' filters for valid attributes
|
|
3361
|
+
await File.set_xattr_of_existing_file(
|
|
3362
|
+
logFile,
|
|
3363
|
+
outDirFullPath,
|
|
3364
|
+
req.user
|
|
3365
|
+
);
|
|
3366
|
+
}
|
|
3367
|
+
}
|
|
3368
|
+
);
|
|
3369
|
+
});
|
|
3370
|
+
})
|
|
3371
|
+
);
|
|
3372
|
+
|
|
3217
3373
|
/**
|
|
3218
3374
|
* Do Build Mobile App
|
|
3219
3375
|
*/
|
|
@@ -3222,6 +3378,8 @@ router.post(
|
|
|
3222
3378
|
isAdmin,
|
|
3223
3379
|
error_catcher(async (req, res) => {
|
|
3224
3380
|
getState().log(2, `starting mobile build: ${JSON.stringify(req.body)}`);
|
|
3381
|
+
const msgs = [];
|
|
3382
|
+
let mode = "full";
|
|
3225
3383
|
let {
|
|
3226
3384
|
entryPoint,
|
|
3227
3385
|
entryPointType,
|
|
@@ -3239,11 +3397,27 @@ router.post(
|
|
|
3239
3397
|
synchedTables,
|
|
3240
3398
|
includedPlugins,
|
|
3241
3399
|
provisioningProfile,
|
|
3400
|
+
shareProvisioningProfile,
|
|
3242
3401
|
buildType,
|
|
3243
3402
|
keystoreFile,
|
|
3244
3403
|
keystoreAlias,
|
|
3245
3404
|
keystorePassword,
|
|
3246
3405
|
} = req.body;
|
|
3406
|
+
// const receiveShareTriggers = Trigger.find({
|
|
3407
|
+
// when_trigger: "ReceiveMobileShareData",
|
|
3408
|
+
// });
|
|
3409
|
+
// disabeling share to support for now
|
|
3410
|
+
let allowShareTo = false; // receiveShareTriggers.length > 0;
|
|
3411
|
+
if (allowShareTo && iOSPlatform && !shareProvisioningProfile) {
|
|
3412
|
+
allowShareTo = false;
|
|
3413
|
+
msgs.push({
|
|
3414
|
+
type: "warning",
|
|
3415
|
+
text: req.__(
|
|
3416
|
+
"A ReceiveMobileShareData trigger exists, but no Share Extension Provisioning Profile is provided. " +
|
|
3417
|
+
"Building without share to support."
|
|
3418
|
+
),
|
|
3419
|
+
});
|
|
3420
|
+
}
|
|
3247
3421
|
if (!includedPlugins) includedPlugins = [];
|
|
3248
3422
|
if (!synchedTables) synchedTables = [];
|
|
3249
3423
|
if (!entryPoint) {
|
|
@@ -3279,14 +3453,26 @@ router.post(
|
|
|
3279
3453
|
),
|
|
3280
3454
|
});
|
|
3281
3455
|
}
|
|
3282
|
-
if (iOSPlatform
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3456
|
+
if (iOSPlatform) {
|
|
3457
|
+
if (!provisioningProfile)
|
|
3458
|
+
return res.json({
|
|
3459
|
+
error: req.__(
|
|
3460
|
+
"Please provide a Provisioning Profile for the iOS build."
|
|
3461
|
+
),
|
|
3462
|
+
});
|
|
3463
|
+
}
|
|
3464
|
+
if (buildType === "debug" && keystoreFile) {
|
|
3465
|
+
msgs.push({
|
|
3466
|
+
type: "warning",
|
|
3467
|
+
text: req.__("Keystore file is not applied for debug builds."),
|
|
3287
3468
|
});
|
|
3288
3469
|
}
|
|
3289
|
-
|
|
3470
|
+
|
|
3471
|
+
if (
|
|
3472
|
+
buildType === "release" &&
|
|
3473
|
+
keystoreFile &&
|
|
3474
|
+
(!keystoreAlias || !keystorePassword)
|
|
3475
|
+
) {
|
|
3290
3476
|
return res.json({
|
|
3291
3477
|
error: req.__(
|
|
3292
3478
|
"Please provide the keystore alias and password for the android build."
|
|
@@ -3294,8 +3480,9 @@ router.post(
|
|
|
3294
3480
|
});
|
|
3295
3481
|
}
|
|
3296
3482
|
const outDirName = `build_${new Date().valueOf()}`;
|
|
3483
|
+
const buildDir = `${os.userInfo().homedir}/mobile_app_build`;
|
|
3297
3484
|
const rootFolder = await File.rootFolder();
|
|
3298
|
-
const
|
|
3485
|
+
const outDir = path.join(rootFolder.location, "mobile_app", outDirName);
|
|
3299
3486
|
await File.new_folder(outDirName, "/mobile_app");
|
|
3300
3487
|
const spawnParams = [
|
|
3301
3488
|
"build-app",
|
|
@@ -3304,9 +3491,9 @@ router.post(
|
|
|
3304
3491
|
"-t",
|
|
3305
3492
|
entryPointType === "pagegroup" ? "page" : entryPointType,
|
|
3306
3493
|
"-c",
|
|
3307
|
-
|
|
3494
|
+
outDir,
|
|
3308
3495
|
"-b",
|
|
3309
|
-
|
|
3496
|
+
buildDir,
|
|
3310
3497
|
"-u",
|
|
3311
3498
|
req.user.email, // ensured by isAdmin
|
|
3312
3499
|
];
|
|
@@ -3319,6 +3506,13 @@ router.post(
|
|
|
3319
3506
|
"--provisioningProfile",
|
|
3320
3507
|
provisioningProfile
|
|
3321
3508
|
);
|
|
3509
|
+
if (allowShareTo) {
|
|
3510
|
+
mode = "prepare";
|
|
3511
|
+
spawnParams.push(
|
|
3512
|
+
"--shareExtensionProvisioningProfile",
|
|
3513
|
+
shareProvisioningProfile
|
|
3514
|
+
);
|
|
3515
|
+
}
|
|
3322
3516
|
}
|
|
3323
3517
|
if (appName) spawnParams.push("--appName", appName);
|
|
3324
3518
|
if (appId) spawnParams.push("--appId", appId);
|
|
@@ -3327,6 +3521,7 @@ router.post(
|
|
|
3327
3521
|
if (serverURL) spawnParams.push("-s", serverURL);
|
|
3328
3522
|
if (splashPage) spawnParams.push("--splashPage", splashPage);
|
|
3329
3523
|
if (allowOfflineMode) spawnParams.push("--allowOfflineMode");
|
|
3524
|
+
if (allowShareTo) spawnParams.push("--allowShareTo");
|
|
3330
3525
|
if (autoPublicLogin) spawnParams.push("--autoPublicLogin");
|
|
3331
3526
|
if (synchedTables.length > 0)
|
|
3332
3527
|
spawnParams.push("--synchedTables", ...synchedTables.map((tbl) => tbl));
|
|
@@ -3348,10 +3543,15 @@ router.post(
|
|
|
3348
3543
|
spawnParams.push("--androidKeyStoreAlias", keystoreAlias);
|
|
3349
3544
|
if (keystorePassword)
|
|
3350
3545
|
spawnParams.push("--androidKeystorePassword", keystorePassword);
|
|
3351
|
-
// end http call, return the out directory name
|
|
3546
|
+
// end http call, return the out directory name, the build directory path and the mode
|
|
3352
3547
|
// the gui polls for results
|
|
3353
|
-
res.json({
|
|
3354
|
-
|
|
3548
|
+
res.json({
|
|
3549
|
+
out_dir_name: outDirName,
|
|
3550
|
+
build_dir: buildDir,
|
|
3551
|
+
mode: mode,
|
|
3552
|
+
msgs,
|
|
3553
|
+
});
|
|
3554
|
+
const child = spawn(getSafeSaltcornCmd(), [...spawnParams, "-m", mode], {
|
|
3355
3555
|
stdio: ["ignore", "pipe", "pipe"],
|
|
3356
3556
|
cwd: ".",
|
|
3357
3557
|
});
|
|
@@ -3366,18 +3566,29 @@ router.post(
|
|
|
3366
3566
|
getState().log(5, errMsg);
|
|
3367
3567
|
childOutputs.push(errMsg);
|
|
3368
3568
|
});
|
|
3369
|
-
child.on("exit", (exitCode, signal) => {
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3569
|
+
child.on("exit", async (exitCode, signal) => {
|
|
3570
|
+
if (mode === "prepare" && exitCode === 0) {
|
|
3571
|
+
try {
|
|
3572
|
+
fs.promises.writeFile(
|
|
3573
|
+
path.join(buildDir, "spawnParams.json"),
|
|
3574
|
+
JSON.stringify(spawnParams)
|
|
3575
|
+
);
|
|
3576
|
+
} catch (error) {
|
|
3577
|
+
console.log(`unable to write spawnParams to '${buildDir}'`);
|
|
3375
3578
|
console.log(error);
|
|
3376
|
-
} else {
|
|
3377
|
-
// no transaction, '/build-mobile-app/finished' filters for valid attributes
|
|
3378
|
-
await File.set_xattr_of_existing_file(logFile, buildDir, req.user);
|
|
3379
3579
|
}
|
|
3380
|
-
}
|
|
3580
|
+
}
|
|
3581
|
+
const stepDesc = mode === "prepare" ? "_prepare_step" : "";
|
|
3582
|
+
const logFile =
|
|
3583
|
+
exitCode === 0 ? `logs${stepDesc}.txt` : `error_logs${stepDesc}.txt`;
|
|
3584
|
+
try {
|
|
3585
|
+
const exitMsg = childOutputs.join("\n");
|
|
3586
|
+
await fs.promises.writeFile(path.join(outDir, logFile), exitMsg);
|
|
3587
|
+
await File.set_xattr_of_existing_file(logFile, outDir, req.user);
|
|
3588
|
+
} catch (error) {
|
|
3589
|
+
console.log(`unable to write '${logFile}' to '${outDir}'`);
|
|
3590
|
+
console.log(error);
|
|
3591
|
+
}
|
|
3381
3592
|
});
|
|
3382
3593
|
child.on("error", (msg) => {
|
|
3383
3594
|
const message = msg.message ? msg.message : msg.code;
|
|
@@ -3386,15 +3597,15 @@ router.post(
|
|
|
3386
3597
|
const errMsg = [message, stack].join("\n");
|
|
3387
3598
|
getState().log(5, msg);
|
|
3388
3599
|
fs.writeFile(
|
|
3389
|
-
path.join(
|
|
3600
|
+
path.join(outDir, "error_logs.txt"),
|
|
3390
3601
|
errMsg,
|
|
3391
3602
|
async (error) => {
|
|
3392
3603
|
if (error) {
|
|
3393
|
-
console.log(`unable to write logFile to '${
|
|
3604
|
+
console.log(`unable to write logFile to '${outDir}'`);
|
|
3394
3605
|
console.log(error);
|
|
3395
3606
|
} else {
|
|
3396
3607
|
// no transaction, '/build-mobile-app/finished' filters for valid attributes
|
|
3397
|
-
await File.set_xattr_of_existing_file(logFile,
|
|
3608
|
+
await File.set_xattr_of_existing_file(logFile, outDir, req.user);
|
|
3398
3609
|
}
|
|
3399
3610
|
}
|
|
3400
3611
|
);
|
|
@@ -3462,7 +3673,7 @@ router.post(
|
|
|
3462
3673
|
.filter(
|
|
3463
3674
|
(plugin) =>
|
|
3464
3675
|
["base", "sbadmin2"].indexOf(plugin.name) < 0 &&
|
|
3465
|
-
newCfg.includedPlugins.indexOf(plugin.name) < 0
|
|
3676
|
+
(newCfg.includedPlugins || []).indexOf(plugin.name) < 0
|
|
3466
3677
|
)
|
|
3467
3678
|
.map((plugin) => plugin.name);
|
|
3468
3679
|
newCfg.excludedPlugins = excludedPlugins;
|
package/routes/fields.js
CHANGED
|
@@ -12,6 +12,7 @@ const { getState } = require("@saltcorn/data/db/state");
|
|
|
12
12
|
const { renderForm } = require("@saltcorn/markup");
|
|
13
13
|
const Field = require("@saltcorn/data/models/field");
|
|
14
14
|
const Table = require("@saltcorn/data/models/table");
|
|
15
|
+
const Trigger = require("@saltcorn/data/models/trigger");
|
|
15
16
|
const Form = require("@saltcorn/data/models/form");
|
|
16
17
|
const Workflow = require("@saltcorn/data/models/workflow");
|
|
17
18
|
const User = require("@saltcorn/data/models/user");
|
|
@@ -306,6 +307,10 @@ const fieldFlow = (req) =>
|
|
|
306
307
|
}
|
|
307
308
|
|
|
308
309
|
await field.update(fldRow);
|
|
310
|
+
Trigger.emitEvent("AppChange", `Field ${fldRow.name}`, req.user, {
|
|
311
|
+
entity_type: "Field",
|
|
312
|
+
entity_name: fldRow.name || fldRow.label,
|
|
313
|
+
});
|
|
309
314
|
} catch (e) {
|
|
310
315
|
return {
|
|
311
316
|
redirect: `/table/${context.table_id}`,
|
|
@@ -315,6 +320,10 @@ const fieldFlow = (req) =>
|
|
|
315
320
|
} else {
|
|
316
321
|
try {
|
|
317
322
|
await Field.create(fldRow);
|
|
323
|
+
Trigger.emitEvent("AppChange", `Field ${fldRow.name}`, req.user, {
|
|
324
|
+
entity_type: "Field",
|
|
325
|
+
entity_name: fldRow.name || fldRow.label,
|
|
326
|
+
});
|
|
318
327
|
} catch (e) {
|
|
319
328
|
return {
|
|
320
329
|
redirect: `/table/${context.table_id}`,
|
package/routes/menu.js
CHANGED
package/routes/notifications.js
CHANGED
|
@@ -199,23 +199,31 @@ router.post(
|
|
|
199
199
|
error_catcher(async (req, res) => {
|
|
200
200
|
const role = req.user?.role_id || 100;
|
|
201
201
|
if (role === 100) {
|
|
202
|
-
|
|
203
|
-
|
|
202
|
+
const msg = req.__("You must be logged in to share");
|
|
203
|
+
if (!req.smr) {
|
|
204
|
+
req.flash("error", msg);
|
|
205
|
+
res.redirect("/auth/login");
|
|
206
|
+
} else res.json({ error: msg });
|
|
204
207
|
} else if (!getState().getConfig("pwa_share_to_enabled", false)) {
|
|
205
|
-
|
|
206
|
-
|
|
208
|
+
const msg = req.__("Sharing not enabled");
|
|
209
|
+
if (!req.smr) {
|
|
210
|
+
req.flash("error", msg);
|
|
211
|
+
res.redirect("/");
|
|
212
|
+
} else res.json({ error: msg });
|
|
207
213
|
} else {
|
|
208
214
|
Trigger.emitEvent("ReceiveMobileShareData", null, req.user, {
|
|
209
215
|
row: req.body,
|
|
210
216
|
});
|
|
211
|
-
req.
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
217
|
+
if (!req.smr) {
|
|
218
|
+
req.flash(
|
|
219
|
+
"success",
|
|
220
|
+
req.__(
|
|
221
|
+
"Shared: %s",
|
|
222
|
+
req.body.title || req.body.text || req.body.url || ""
|
|
223
|
+
)
|
|
224
|
+
);
|
|
225
|
+
res.status(303).redirect("/");
|
|
226
|
+
} else res.json({ success: "ok" });
|
|
219
227
|
}
|
|
220
228
|
})
|
|
221
229
|
);
|
package/routes/page.js
CHANGED
|
@@ -68,13 +68,13 @@ const runPage = async (page, req, res, tic) => {
|
|
|
68
68
|
no_menu: page.attributes?.no_menu,
|
|
69
69
|
requestFluidLayout: page.attributes?.request_fluid_layout,
|
|
70
70
|
} || `${page.name} page`,
|
|
71
|
-
add_edit_bar({
|
|
71
|
+
req.smr ? contents : add_edit_bar({
|
|
72
72
|
role,
|
|
73
73
|
title: page.name,
|
|
74
74
|
what: req.__("Page"),
|
|
75
75
|
url: `/pageedit/edit/${encodeURIComponent(page.name)}`,
|
|
76
76
|
contents,
|
|
77
|
-
})
|
|
77
|
+
}),
|
|
78
78
|
);
|
|
79
79
|
} else {
|
|
80
80
|
getState().log(2, `Page ${page.name} not authorized`);
|
package/routes/pageedit.js
CHANGED
|
@@ -146,7 +146,9 @@ const pagePropertiesForm = async (req, isNew) => {
|
|
|
146
146
|
{
|
|
147
147
|
name: "request_fluid_layout",
|
|
148
148
|
label: req.__("Fluid layout"),
|
|
149
|
-
sublabel: req.__(
|
|
149
|
+
sublabel: req.__(
|
|
150
|
+
"Request fluid layout from theme for a wider display for this page"
|
|
151
|
+
),
|
|
150
152
|
type: "Bool",
|
|
151
153
|
},
|
|
152
154
|
],
|
|
@@ -503,12 +505,21 @@ router.post(
|
|
|
503
505
|
pageRow.layout = {};
|
|
504
506
|
}
|
|
505
507
|
await Page.update(+id, pageRow);
|
|
508
|
+
Trigger.emitEvent("AppChange", `Page ${dbPage.name}`, req.user, {
|
|
509
|
+
entity_type: "Page",
|
|
510
|
+
entity_name: dbPage.name,
|
|
511
|
+
});
|
|
506
512
|
if (req.xhr) res.json({ success: "ok" });
|
|
507
513
|
else res.redirect(`/pageedit/`);
|
|
508
514
|
} else {
|
|
509
515
|
if (!pageRow.layout) pageRow.layout = {};
|
|
510
516
|
if (!pageRow.fixed_states) pageRow.fixed_states = {};
|
|
511
517
|
await Page.create(pageRow);
|
|
518
|
+
Trigger.emitEvent("AppChange", `Page ${pageRow.name}`, req.user, {
|
|
519
|
+
entity_type: "Page",
|
|
520
|
+
entity_name: pageRow.name,
|
|
521
|
+
});
|
|
522
|
+
|
|
512
523
|
if (!html_file)
|
|
513
524
|
res.redirect(
|
|
514
525
|
addOnDoneRedirect(`/pageedit/edit/${pageRow.name}`, req)
|
|
@@ -679,6 +690,10 @@ router.post(
|
|
|
679
690
|
await Page.update(page.id, {
|
|
680
691
|
layout: decodeURIComponent(req.body.layout),
|
|
681
692
|
});
|
|
693
|
+
Trigger.emitEvent("AppChange", `Page ${page.name}`, req.user, {
|
|
694
|
+
entity_type: "Page",
|
|
695
|
+
entity_name: page.name,
|
|
696
|
+
});
|
|
682
697
|
req.flash("success", req.__(`Page %s saved`, pagename));
|
|
683
698
|
res.redirect(redirectTarget);
|
|
684
699
|
} else if (req.body.code) {
|
|
@@ -687,6 +702,10 @@ router.post(
|
|
|
687
702
|
const file = await File.findOne(page.html_file);
|
|
688
703
|
if (!file) throw new Error(req.__("File not found"));
|
|
689
704
|
await fsp.writeFile(file.location, req.body.code);
|
|
705
|
+
Trigger.emitEvent("AppChange", `Page ${page.name}`, req.user, {
|
|
706
|
+
entity_type: "Page",
|
|
707
|
+
entity_name: page.name,
|
|
708
|
+
});
|
|
690
709
|
if (!req.xhr) {
|
|
691
710
|
req.flash("success", req.__(`Page %s saved`, pagename));
|
|
692
711
|
res.redirect(redirectTarget);
|
|
@@ -723,6 +742,11 @@ router.post(
|
|
|
723
742
|
|
|
724
743
|
if (id && req.body.layout) {
|
|
725
744
|
await Page.update(+id, { layout: req.body.layout });
|
|
745
|
+
const page = await Page.findOne({ id });
|
|
746
|
+
Trigger.emitEvent("AppChange", `Page ${page.name}`, req.user, {
|
|
747
|
+
entity_type: "Page",
|
|
748
|
+
entity_name: page.name,
|
|
749
|
+
});
|
|
726
750
|
res.json({
|
|
727
751
|
success: "ok",
|
|
728
752
|
});
|
|
@@ -744,6 +768,10 @@ router.post(
|
|
|
744
768
|
error_catcher(async (req, res) => {
|
|
745
769
|
const { id } = req.params;
|
|
746
770
|
const page = await Page.findOne({ id });
|
|
771
|
+
Trigger.emitEvent("AppChange", `Page ${page.name}`, req.user, {
|
|
772
|
+
entity_type: "Page",
|
|
773
|
+
entity_name: page.name,
|
|
774
|
+
});
|
|
747
775
|
await page.delete();
|
|
748
776
|
req.flash("success", req.__(`Page deleted`));
|
|
749
777
|
res.redirect(`/pageedit`);
|
|
@@ -796,6 +824,7 @@ router.post(
|
|
|
796
824
|
min_role: page.min_role,
|
|
797
825
|
pagename: page.name,
|
|
798
826
|
});
|
|
827
|
+
Trigger.emitEvent("AppChange", `Menu`, req.user, {});
|
|
799
828
|
req.flash(
|
|
800
829
|
"success",
|
|
801
830
|
req.__(
|
|
@@ -820,6 +849,10 @@ router.post(
|
|
|
820
849
|
const { id } = req.params;
|
|
821
850
|
const page = await Page.findOne({ id });
|
|
822
851
|
const newpage = await page.clone();
|
|
852
|
+
Trigger.emitEvent("AppChange", `Page ${newpage.name}`, req.user, {
|
|
853
|
+
entity_type: "Page",
|
|
854
|
+
entity_name: newpage.name,
|
|
855
|
+
});
|
|
823
856
|
req.flash(
|
|
824
857
|
"success",
|
|
825
858
|
req.__("Page %s duplicated as %s", page.name, newpage.name)
|
package/routes/utils.js
CHANGED
|
@@ -23,6 +23,7 @@ const Crash = require("@saltcorn/data/models/crash");
|
|
|
23
23
|
const File = require("@saltcorn/data/models/file");
|
|
24
24
|
const User = require("@saltcorn/data/models/user");
|
|
25
25
|
const Page = require("@saltcorn/data/models/page");
|
|
26
|
+
const Trigger = require("@saltcorn/data/models/trigger");
|
|
26
27
|
const si = require("systeminformation");
|
|
27
28
|
const {
|
|
28
29
|
config_fields_form,
|
|
@@ -426,6 +427,9 @@ const admin_config_route = ({
|
|
|
426
427
|
const restart_required = check_if_restart_required(form, req);
|
|
427
428
|
|
|
428
429
|
await save_config_from_form(form);
|
|
430
|
+
Trigger.emitEvent("AppChange", `Config`, req.user, {
|
|
431
|
+
config_keys: Object.keys(form.values),
|
|
432
|
+
});
|
|
429
433
|
if (!req.xhr) {
|
|
430
434
|
if (restart_required) {
|
|
431
435
|
flash_restart(req);
|
package/routes/view.js
CHANGED
|
@@ -139,18 +139,20 @@ router.get(
|
|
|
139
139
|
: contents0;
|
|
140
140
|
res.sendWrap(
|
|
141
141
|
title,
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
142
|
+
!req.smr
|
|
143
|
+
? add_edit_bar({
|
|
144
|
+
role,
|
|
145
|
+
title: view.name,
|
|
146
|
+
what: req.__("View"),
|
|
147
|
+
url: `/viewedit/edit/${encodeURIComponent(view.name)}`,
|
|
148
|
+
cfgUrl: `/viewedit/config/${encodeURIComponent(view.name)}`,
|
|
149
|
+
contents,
|
|
150
|
+
req,
|
|
151
|
+
view,
|
|
152
|
+
viewtemplate: view.viewtemplate,
|
|
153
|
+
table: view.table_id || view.exttable_name,
|
|
154
|
+
})
|
|
155
|
+
: contents
|
|
154
156
|
);
|
|
155
157
|
}
|
|
156
158
|
})
|
package/routes/viewedit.js
CHANGED
|
@@ -27,6 +27,7 @@ const Table = require("@saltcorn/data/models/table");
|
|
|
27
27
|
const View = require("@saltcorn/data/models/view");
|
|
28
28
|
const Workflow = require("@saltcorn/data/models/workflow");
|
|
29
29
|
const User = require("@saltcorn/data/models/user");
|
|
30
|
+
const Trigger = require("@saltcorn/data/models/trigger");
|
|
30
31
|
const Page = require("@saltcorn/data/models/page");
|
|
31
32
|
const File = require("@saltcorn/data/models/file");
|
|
32
33
|
const Tag = require("@saltcorn/data/models/tag");
|
|
@@ -551,6 +552,10 @@ router.post(
|
|
|
551
552
|
//console.log(v);
|
|
552
553
|
await View.create(v);
|
|
553
554
|
}
|
|
555
|
+
Trigger.emitEvent("AppChange", `View ${v.name}`, req.user, {
|
|
556
|
+
entity_type: "View",
|
|
557
|
+
entity_name: v.name,
|
|
558
|
+
});
|
|
554
559
|
if (req.xhr) res.json({ success: "ok" });
|
|
555
560
|
else
|
|
556
561
|
res.redirect(
|
|
@@ -733,6 +738,10 @@ router.post(
|
|
|
733
738
|
};
|
|
734
739
|
else newcfg = { ...view.configuration, ...context };
|
|
735
740
|
await View.update({ configuration: newcfg }, view.id);
|
|
741
|
+
Trigger.emitEvent("AppChange", `View ${view.name}`, req.user, {
|
|
742
|
+
entity_type: "View",
|
|
743
|
+
entity_name: view.name,
|
|
744
|
+
});
|
|
736
745
|
};
|
|
737
746
|
const wfres = await configFlow.run(req.body, req);
|
|
738
747
|
|
|
@@ -761,6 +770,7 @@ router.post(
|
|
|
761
770
|
min_role: view.min_role,
|
|
762
771
|
viewname: view.name,
|
|
763
772
|
});
|
|
773
|
+
Trigger.emitEvent("AppChange", `Menu`, req.user, {});
|
|
764
774
|
req.flash(
|
|
765
775
|
"success",
|
|
766
776
|
req.__(
|
|
@@ -790,6 +800,10 @@ router.post(
|
|
|
790
800
|
const { id } = req.params;
|
|
791
801
|
const view = await View.findOne({ id });
|
|
792
802
|
const newview = await view.clone();
|
|
803
|
+
Trigger.emitEvent("AppChange", `View ${newview.name}`, req.user, {
|
|
804
|
+
entity_type: "View",
|
|
805
|
+
entity_name: newview.name,
|
|
806
|
+
});
|
|
793
807
|
req.flash(
|
|
794
808
|
"success",
|
|
795
809
|
req.__("View %s duplicated as %s", view.name, newview.name)
|
|
@@ -841,6 +855,10 @@ router.post(
|
|
|
841
855
|
const exview = await View.findOne({ id });
|
|
842
856
|
let newcfg = { ...exview.configuration, ...req.body };
|
|
843
857
|
await View.update({ configuration: newcfg }, +id);
|
|
858
|
+
Trigger.emitEvent("AppChange", `View ${exview.name}`, req.user, {
|
|
859
|
+
entity_type: "View",
|
|
860
|
+
entity_name: exview.name,
|
|
861
|
+
});
|
|
844
862
|
res.json({ success: "ok" });
|
|
845
863
|
} else {
|
|
846
864
|
res.json({ error: req.__("Unable to save: No view") });
|
|
@@ -879,6 +897,10 @@ router.post(
|
|
|
879
897
|
};
|
|
880
898
|
else newcfg = { ...view.configuration, ...step.renderForm.values };
|
|
881
899
|
await View.update({ configuration: newcfg }, view.id);
|
|
900
|
+
Trigger.emitEvent("AppChange", `View ${view.name}`, req.user, {
|
|
901
|
+
entity_type: "View",
|
|
902
|
+
entity_name: view.name,
|
|
903
|
+
});
|
|
882
904
|
res.json({ success: "ok" });
|
|
883
905
|
} else {
|
|
884
906
|
res.json({ error: step.renderForm.errorSummary });
|
|
@@ -906,6 +928,10 @@ router.post(
|
|
|
906
928
|
const role = req.body.role;
|
|
907
929
|
await View.update({ min_role: role }, +id);
|
|
908
930
|
const view = await View.findOne({ id });
|
|
931
|
+
Trigger.emitEvent("AppChange", `View ${view.name}`, req.user, {
|
|
932
|
+
entity_type: "View",
|
|
933
|
+
entity_name: view.name,
|
|
934
|
+
});
|
|
909
935
|
const roles = await User.get_roles();
|
|
910
936
|
const roleRow = roles.find((r) => r.id === +role);
|
|
911
937
|
const message =
|
package/tests/clientjs.test.js
CHANGED
|
@@ -90,6 +90,25 @@ test("updateQueryStringParameter hash", () => {
|
|
|
90
90
|
"/foo?name=Bar#Baz"
|
|
91
91
|
);
|
|
92
92
|
});
|
|
93
|
+
test("addQueryStringParameter", () => {
|
|
94
|
+
expect(addQueryStringParameter("/foo", "age", 43)).toBe(
|
|
95
|
+
"/foo?age=43"
|
|
96
|
+
);
|
|
97
|
+
expect(addQueryStringParameter("/foo?age=43", "age", 44)).toBe(
|
|
98
|
+
"/foo?age=43&age=44"
|
|
99
|
+
);
|
|
100
|
+
expect(addQueryStringParameter("/foo?age=43", "age", 43)).toBe(
|
|
101
|
+
"/foo?age=43"
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
test("addQueryStringParameter hash", () => {
|
|
105
|
+
expect(addQueryStringParameter("/foo#baz", "age", 43)).toBe(
|
|
106
|
+
"/foo?age=43#baz"
|
|
107
|
+
);
|
|
108
|
+
expect(addQueryStringParameter("/foo?age=43#baz", "age", 44)).toBe(
|
|
109
|
+
"/foo?age=43&age=44#baz"
|
|
110
|
+
);
|
|
111
|
+
});
|
|
93
112
|
test("unique_field_from_rows test", () => {
|
|
94
113
|
$("body").append(`<input id="mkuniq6" value="bar"></div>`);
|
|
95
114
|
unique_field_from_rows(
|