@saltcorn/server 1.1.1-beta.8 → 1.1.1-rc.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/CHANGELOG.md +37 -1
- package/auth/admin.js +1 -0
- package/locales/en.json +9 -1
- package/markup/admin.js +15 -10
- package/package.json +9 -9
- package/public/saltcorn-common.js +85 -1
- package/public/saltcorn.css +39 -0
- package/public/saltcorn.js +17 -4
- package/routes/actions.js +17 -1
- package/routes/admin.js +29 -25
- package/routes/api.js +68 -5
- package/routes/common_lists.js +67 -16
- package/routes/fields.js +113 -2
- package/routes/list.js +9 -8
- package/routes/menu.js +3 -3
- package/routes/search.js +91 -11
- package/routes/tables.js +18 -4
- package/routes/tag_entries.js +43 -1
- package/routes/tags.js +1 -0
- package/routes/utils.js +15 -0
- package/serve.js +17 -13
- package/tests/api.test.js +13 -0
- package/tests/clientjs.test.js +20 -0
- package/tests/files.test.js +1 -1
- package/wrapper.js +31 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
## 1.1.1 - In beta
|
|
4
4
|
|
|
5
|
+
* Full-text search improvements:
|
|
6
|
+
- An index for full-text search can now be created. When creating an index in
|
|
7
|
+
the constraints setting for a table, you can select "Full-text search" in
|
|
8
|
+
the field selector. This will dramatically speed up search on large tables.
|
|
9
|
+
- Use websearch_to_tsquery if available. This is a more natural and modern syntax.
|
|
10
|
+
- Link to syntax examples in /search
|
|
11
|
+
- Use default locale's language for search localisation.
|
|
12
|
+
- Option to show results in tabs in search configuration.
|
|
13
|
+
|
|
14
|
+
* select_by_view fieldview for Key fields: the user selects the value of a
|
|
15
|
+
Key field based on an clicking in a row of rendered views (typically a Show view) of the joined table. Works for both Edit and Filter views.
|
|
16
|
+
|
|
17
|
+
* Click to edit (Show and List view patterns) is now implemented by rendering
|
|
18
|
+
the first available edit fieldview. This should be more robust and work with
|
|
19
|
+
more data types.
|
|
20
|
+
|
|
21
|
+
* Data in the admin's data edit grid is now loaded by page. This makes it
|
|
22
|
+
possible to work with much larger datasets.
|
|
23
|
+
|
|
5
24
|
* You can now permit to non-admin (role ID > 1) users to edit or inspect tables, or
|
|
6
25
|
edit views, pages or triggers. In the permissions tab of the Users and security
|
|
7
26
|
settings, minimum roles can be set for these capabilities. The appropriate
|
|
@@ -28,6 +47,7 @@
|
|
|
28
47
|
- ForLoop step type for loops over arrays.
|
|
29
48
|
- Varius UX improvements for editing workflows
|
|
30
49
|
- Integrate copilot, if installed, in workflow editing
|
|
50
|
+
- Call non-workflow trigger actions.
|
|
31
51
|
|
|
32
52
|
* sbadmin2 theme - Color update: dark side bar, darker primary blue
|
|
33
53
|
|
|
@@ -35,12 +55,23 @@
|
|
|
35
55
|
is changed.
|
|
36
56
|
|
|
37
57
|
* Mobile builder:
|
|
38
|
-
- PJAX view loading.
|
|
58
|
+
- PJAX view loading: Use pjax for all functions like on the web version.
|
|
59
|
+
- Share content to your app on mobile and PWA.
|
|
60
|
+
- Ensure at least one ReceiveMobileShareData trigger exists when the app is built or the PWA is installed.
|
|
61
|
+
- Shared content is accessible via the row variable.
|
|
62
|
+
- Android: No additional configuration is needed.
|
|
63
|
+
- PWA: Ensure a trusted HTTPS connection is used.
|
|
64
|
+
- iOS:
|
|
65
|
+
- A second provisioning profile is required, with the bundle ID of the main app followed by share-ext (e.g., com.saltcorn.share-ext).
|
|
66
|
+
- The iOS project needs a Share Extension target. To set this up, open Xcode and add a Share Extension target from a template (more documentation is is about to come).
|
|
67
|
+
- The build will stop when the Xcode integration is required, and a "Finish the Build" shows up.
|
|
39
68
|
|
|
40
69
|
### Fixes
|
|
41
70
|
|
|
71
|
+
* Increase plugin install reliability
|
|
42
72
|
* fix workflows on SQLite
|
|
43
73
|
* fix query string build on check_state_field (#2948). Author: St0rml
|
|
74
|
+
* multiple fixes for the Capacitor port
|
|
44
75
|
|
|
45
76
|
### Translations
|
|
46
77
|
|
|
@@ -71,6 +102,11 @@
|
|
|
71
102
|
|
|
72
103
|
* Webhook action has more options: method, set reponse value, headers.
|
|
73
104
|
|
|
105
|
+
* Mobile builder:
|
|
106
|
+
- Ported from Cordova to Capacitor: Cordova's core functionalities and plugins are well-maintained, but for some time now, the trend for mobile application development goes new directions. Capacitor aims to be a drop-in replacement with a more modern approach and an active Community. Existing Cordova plugins do still work, and plugins from the Capacitor ecosystem are available as well. This should make the mobile app development more future-proof.
|
|
107
|
+
- Screen orientation change handling: A Saltcorn plugin can register a listener for screen orientation changes (Landscape / Portrait modes). For an example, take a look at the [metronic-theme](https://github.com/saltcorn/metronic-theme/blob/35b69ba7b4e94e2bcfe2f1c61508bc579c1d914f/index.js#L844). It registers a listener to adjust the mobile bottom navigation bar when the phone rotates.
|
|
108
|
+
- PJAX view loading: Changed the full reload to pjax for sortby and gopage (paging). The remainig set_state calls are still full reloads.
|
|
109
|
+
|
|
74
110
|
### Security
|
|
75
111
|
|
|
76
112
|
- SameSite cookie settings
|
package/auth/admin.js
CHANGED
package/locales/en.json
CHANGED
|
@@ -1543,5 +1543,13 @@
|
|
|
1543
1543
|
"Minimum role to inspect (see, without editing) tables": "Minimum role to inspect (see, without editing) tables",
|
|
1544
1544
|
"Home pages": "Home pages",
|
|
1545
1545
|
"The home page is the page that is served when the user visits the home location (/). This can be set for each user role.": "The home page is the page that is served when the user visits the home location (/). This can be set for each user role.",
|
|
1546
|
-
"Trigger %s deleted": "Trigger %s deleted"
|
|
1546
|
+
"Trigger %s deleted": "Trigger %s deleted",
|
|
1547
|
+
"Edit menu": "Edit menu",
|
|
1548
|
+
"Minimum role to edit menu": "Minimum role to edit menu",
|
|
1549
|
+
"Full-text search index is not available as the table contains Key fields (%s) with the \"Include in full-text search\" option enabled. Disable this before creating a Full-text search index": "Full-text search index is not available as the table contains Key fields (%s) with the \"Include in full-text search\" option enabled. Disable this before creating a Full-text search index",
|
|
1550
|
+
"Share Extension Provisioning Profile": "Share Extension Provisioning Profile",
|
|
1551
|
+
"Show results in": "Show results in",
|
|
1552
|
+
"Show results from each table in this type of element": "Show results from each table in this type of element",
|
|
1553
|
+
"Search syntax help": "Search syntax help",
|
|
1554
|
+
"Search syntax": "Search syntax"
|
|
1547
1555
|
}
|
package/markup/admin.js
CHANGED
|
@@ -224,24 +224,29 @@ const send_infoarch_page = (args) => {
|
|
|
224
224
|
const tenant_list =
|
|
225
225
|
db.is_it_multi_tenant() &&
|
|
226
226
|
db.getTenantSchema() === db.connectObj.default_schema;
|
|
227
|
+
const isUserAdmin = args.req?.user.role_id === 1;
|
|
227
228
|
return send_settings_page({
|
|
228
229
|
main_section: "Site structure",
|
|
229
230
|
main_section_href: "/site-structure",
|
|
230
231
|
sub_sections: [
|
|
231
232
|
{ text: "Menu", href: "/menu" },
|
|
232
|
-
|
|
233
|
-
{ text: "Library", href: "/library/list" },
|
|
234
|
-
{ text: "Languages", href: "/site-structure/localizer" },
|
|
235
|
-
...(tenant_list
|
|
233
|
+
...(isUserAdmin
|
|
236
234
|
? [
|
|
237
|
-
{ text: "
|
|
238
|
-
{ text: "
|
|
235
|
+
{ text: "Search", href: "/search/config" },
|
|
236
|
+
{ text: "Library", href: "/library/list" },
|
|
237
|
+
{ text: "Languages", href: "/site-structure/localizer" },
|
|
238
|
+
...(tenant_list
|
|
239
|
+
? [
|
|
240
|
+
{ text: "Tenants", href: "/tenant/list" },
|
|
241
|
+
{ text: "Multitenancy", href: "/tenant/settings" },
|
|
242
|
+
]
|
|
243
|
+
: []),
|
|
244
|
+
{ text: "Pagegroups", href: "/page_group/settings" },
|
|
245
|
+
{ text: "Tags", href: "/tag" },
|
|
246
|
+
{ text: "Diagram", href: "/diagram" },
|
|
247
|
+
{ text: "Registry editor", href: "/registry-editor" },
|
|
239
248
|
]
|
|
240
249
|
: []),
|
|
241
|
-
{ text: "Pagegroups", href: "/page_group/settings" },
|
|
242
|
-
{ text: "Tags", href: "/tag" },
|
|
243
|
-
{ text: "Diagram", href: "/diagram" },
|
|
244
|
-
{ text: "Registry editor", href: "/registry-editor" },
|
|
245
250
|
],
|
|
246
251
|
...args,
|
|
247
252
|
});
|
package/package.json
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/server",
|
|
3
|
-
"version": "1.1.1-
|
|
3
|
+
"version": "1.1.1-rc.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-
|
|
11
|
-
"@saltcorn/builder": "1.1.1-
|
|
12
|
-
"@saltcorn/data": "1.1.1-
|
|
13
|
-
"@saltcorn/admin-models": "1.1.1-
|
|
14
|
-
"@saltcorn/filemanager": "1.1.1-
|
|
15
|
-
"@saltcorn/markup": "1.1.1-
|
|
16
|
-
"@saltcorn/plugins-loader": "1.1.1-
|
|
17
|
-
"@saltcorn/sbadmin2": "1.1.1-
|
|
10
|
+
"@saltcorn/base-plugin": "1.1.1-rc.2",
|
|
11
|
+
"@saltcorn/builder": "1.1.1-rc.2",
|
|
12
|
+
"@saltcorn/data": "1.1.1-rc.2",
|
|
13
|
+
"@saltcorn/admin-models": "1.1.1-rc.2",
|
|
14
|
+
"@saltcorn/filemanager": "1.1.1-rc.2",
|
|
15
|
+
"@saltcorn/markup": "1.1.1-rc.2",
|
|
16
|
+
"@saltcorn/plugins-loader": "1.1.1-rc.2",
|
|
17
|
+
"@saltcorn/sbadmin2": "1.1.1-rc.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",
|
|
@@ -965,6 +965,38 @@ function initialize_page() {
|
|
|
965
965
|
var current =
|
|
966
966
|
$(this).attr("data-inline-edit-current") ||
|
|
967
967
|
$(this).children("span.current").html();
|
|
968
|
+
const resetHtml = this.outerHTML;
|
|
969
|
+
|
|
970
|
+
let fielddata = $(this).attr("data-inline-edit-fielddata");
|
|
971
|
+
if (fielddata) {
|
|
972
|
+
//fetch edit
|
|
973
|
+
$.ajax(`/field/edit-get-fieldview`, {
|
|
974
|
+
type: "POST",
|
|
975
|
+
headers: {
|
|
976
|
+
"CSRF-Token": _sc_globalCsrf,
|
|
977
|
+
},
|
|
978
|
+
contentType: "application/json",
|
|
979
|
+
data: decodeURIComponent(fielddata),
|
|
980
|
+
}).then((resp) => {
|
|
981
|
+
const opts = encodeURIComponent(
|
|
982
|
+
JSON.stringify({
|
|
983
|
+
resetHtml,
|
|
984
|
+
})
|
|
985
|
+
);
|
|
986
|
+
$(this).replaceWith(
|
|
987
|
+
`<form method="post" action="/field/save-click-edit" onsubmit="inline_ajax_submit_with_fielddata(event, '${opts}')"
|
|
988
|
+
<input type="hidden" name="_csrf" value="${_sc_globalCsrf}">
|
|
989
|
+
<input type="hidden" name="_fielddata" value="${fielddata}">
|
|
990
|
+
<div class="input-group">
|
|
991
|
+
${resp}
|
|
992
|
+
<button type="submit" class="btn btn-sm btn-primary">OK</button>
|
|
993
|
+
<button onclick="cancel_inline_edit(event, '${opts}')" type="button" class="btn btn-sm btn-danger"><i class="fas fa-times"></i></button>
|
|
994
|
+
</div>
|
|
995
|
+
</form>`
|
|
996
|
+
);
|
|
997
|
+
});
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
968
1000
|
var key = $(this).attr("data-inline-edit-field") || "value";
|
|
969
1001
|
var ajax = !!$(this).attr("data-inline-edit-ajax");
|
|
970
1002
|
var type = $(this).attr("data-inline-edit-type");
|
|
@@ -984,7 +1016,6 @@ function initialize_page() {
|
|
|
984
1016
|
current = current === "true";
|
|
985
1017
|
}
|
|
986
1018
|
var is_key = type?.startsWith("Key:");
|
|
987
|
-
const resetHtml = this.outerHTML;
|
|
988
1019
|
const opts = encodeURIComponent(
|
|
989
1020
|
JSON.stringify({
|
|
990
1021
|
url,
|
|
@@ -1267,6 +1298,37 @@ function inline_submit_success(e, form, opts) {
|
|
|
1267
1298
|
} else location.reload();
|
|
1268
1299
|
}
|
|
1269
1300
|
|
|
1301
|
+
function inline_ajax_submit_with_fielddata(e, opts1) {
|
|
1302
|
+
var opts = JSON.parse(decodeURIComponent(opts1 || "") || "{}");
|
|
1303
|
+
e.preventDefault();
|
|
1304
|
+
|
|
1305
|
+
var form = $(e.target).closest("form");
|
|
1306
|
+
var form_data = form.serialize();
|
|
1307
|
+
var url = form.attr("action");
|
|
1308
|
+
if (opts.type === "Bool" && !form_data.includes(`${opts.key}=on`)) {
|
|
1309
|
+
form_data += `&${opts.key}=off`;
|
|
1310
|
+
}
|
|
1311
|
+
$.ajax(url, {
|
|
1312
|
+
type: "POST",
|
|
1313
|
+
headers: {
|
|
1314
|
+
"CSRF-Token": _sc_globalCsrf,
|
|
1315
|
+
},
|
|
1316
|
+
data: form_data,
|
|
1317
|
+
success: function (res) {
|
|
1318
|
+
var opts = JSON.parse(decodeURIComponent(opts1 || "") || "{}");
|
|
1319
|
+
var form = $(e.target).closest("form");
|
|
1320
|
+
form.replaceWith(res);
|
|
1321
|
+
initialize_page();
|
|
1322
|
+
},
|
|
1323
|
+
error: function (e) {
|
|
1324
|
+
if (!checkNetworkError(e))
|
|
1325
|
+
ajax_done(
|
|
1326
|
+
e.responseJSON || { error: "Unknown error: " + e.responseText }
|
|
1327
|
+
);
|
|
1328
|
+
},
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1270
1332
|
function inline_ajax_submit(e, opts1) {
|
|
1271
1333
|
var opts = JSON.parse(decodeURIComponent(opts1 || "") || "{}");
|
|
1272
1334
|
e.preventDefault();
|
|
@@ -2032,6 +2094,28 @@ function update_time_of_week(nm) {
|
|
|
2032
2094
|
};
|
|
2033
2095
|
}
|
|
2034
2096
|
|
|
2097
|
+
function select_by_view_click(element, event, required) {
|
|
2098
|
+
const isAlreadySelected = $(element).hasClass("selected");
|
|
2099
|
+
$(element)
|
|
2100
|
+
.closest(".select-by-view-container")
|
|
2101
|
+
.find(".select-by-view-option")
|
|
2102
|
+
.removeClass("selected");
|
|
2103
|
+
if (!required && isAlreadySelected) {
|
|
2104
|
+
$(element)
|
|
2105
|
+
.closest(".select-by-view-container")
|
|
2106
|
+
.find("input[type=hidden]")
|
|
2107
|
+
.val("")
|
|
2108
|
+
.trigger("change");
|
|
2109
|
+
} else {
|
|
2110
|
+
$(element).addClass("selected");
|
|
2111
|
+
$(element)
|
|
2112
|
+
.closest(".select-by-view-container")
|
|
2113
|
+
.find("input[type=hidden]")
|
|
2114
|
+
.val($(element).attr("data-id"))
|
|
2115
|
+
.trigger("change");
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
|
|
2035
2119
|
const observer = new IntersectionObserver(
|
|
2036
2120
|
(entries, observer) => {
|
|
2037
2121
|
entries.forEach((entry) => {
|
package/public/saltcorn.css
CHANGED
|
@@ -773,3 +773,42 @@ i[class*=" unicode-"] {
|
|
|
773
773
|
[data-animate-initial-hide] {
|
|
774
774
|
opacity: 0;
|
|
775
775
|
}
|
|
776
|
+
|
|
777
|
+
div.select-by-view-container {
|
|
778
|
+
display: flex;
|
|
779
|
+
}
|
|
780
|
+
div.select-by-view-container.justify-start {
|
|
781
|
+
justify-content: flex-start;
|
|
782
|
+
}
|
|
783
|
+
div.select-by-view-container.justify-end {
|
|
784
|
+
justify-content: flex-end;
|
|
785
|
+
}
|
|
786
|
+
div.select-by-view-container.justify-center {
|
|
787
|
+
justify-content: center;
|
|
788
|
+
}
|
|
789
|
+
div.select-by-view-container.justify-between {
|
|
790
|
+
justify-content: space-between;
|
|
791
|
+
}
|
|
792
|
+
div.select-by-view-container.justify-around {
|
|
793
|
+
justify-content: space-around;
|
|
794
|
+
}
|
|
795
|
+
div.select-by-view-container.justify-evenly {
|
|
796
|
+
justify-content: space-evenly;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
div.select-by-view-option.no-card {
|
|
800
|
+
border: 1px solid var(--bs-secondary, var(--tblr-secondary, blue));
|
|
801
|
+
}
|
|
802
|
+
div.select-by-view-option:hover {
|
|
803
|
+
border: 1px solid var(--bs-primary, var(--tblr-primary, blue));
|
|
804
|
+
}
|
|
805
|
+
div.select-by-view-option.selected {
|
|
806
|
+
border: 2px solid var(--bs-primary, var(--tblr-primary, blue));
|
|
807
|
+
}
|
|
808
|
+
tr span.add-tag {
|
|
809
|
+
opacity: 0;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
tr:hover span.add-tag {
|
|
813
|
+
opacity: 1;
|
|
814
|
+
}
|
package/public/saltcorn.js
CHANGED
|
@@ -34,7 +34,10 @@ function updateQueryStringParameter(uri1, key, value) {
|
|
|
34
34
|
uri = uris[0];
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
var re = new RegExp(
|
|
37
|
+
var re = new RegExp(
|
|
38
|
+
"([?&])" + escapeRegExp(key) + "=.*?(&|$)",
|
|
39
|
+
"i"
|
|
40
|
+
);
|
|
38
41
|
var separator = uri.indexOf("?") !== -1 ? "&" : "?";
|
|
39
42
|
if (uri.match(re)) {
|
|
40
43
|
if (Array.isArray(value)) {
|
|
@@ -75,9 +78,12 @@ function removeQueryStringParameter(uri1, key, value) {
|
|
|
75
78
|
}
|
|
76
79
|
let re;
|
|
77
80
|
if (value) {
|
|
78
|
-
re = new RegExp(
|
|
81
|
+
re = new RegExp(
|
|
82
|
+
"([?&])" + escapeRegExp(key) + "=" + value + "?(&|$)",
|
|
83
|
+
"gi"
|
|
84
|
+
);
|
|
79
85
|
} else {
|
|
80
|
-
re = new RegExp("([?&])" + key + "=.*?(&|$)", "gi");
|
|
86
|
+
re = new RegExp("([?&])" + escapeRegExp(key) + "=.*?(&|$)", "gi");
|
|
81
87
|
}
|
|
82
88
|
if (uri.match(re)) {
|
|
83
89
|
uri = uri.replace(re, "$1" + "$2");
|
|
@@ -88,6 +94,10 @@ function removeQueryStringParameter(uri1, key, value) {
|
|
|
88
94
|
return uri + hash;
|
|
89
95
|
}
|
|
90
96
|
|
|
97
|
+
function escapeRegExp(string) {
|
|
98
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
99
|
+
}
|
|
100
|
+
|
|
91
101
|
function addQueryStringParameter(uri1, key, value) {
|
|
92
102
|
let hash = "";
|
|
93
103
|
let uri = uri1;
|
|
@@ -96,7 +106,10 @@ function addQueryStringParameter(uri1, key, value) {
|
|
|
96
106
|
hash = "#" + uris[1];
|
|
97
107
|
uri = uris[0];
|
|
98
108
|
}
|
|
99
|
-
var re = new RegExp(
|
|
109
|
+
var re = new RegExp(
|
|
110
|
+
"([?&])" + escapeRegExp(key) + "=" + value + "?(&|$)",
|
|
111
|
+
"gi"
|
|
112
|
+
);
|
|
100
113
|
if (uri.match(re)) return uri1;
|
|
101
114
|
|
|
102
115
|
var separator = uri.indexOf("?") !== -1 ? "&" : "?";
|
package/routes/actions.js
CHANGED
|
@@ -731,7 +731,7 @@ const getWorkflowStepForm = async (
|
|
|
731
731
|
},
|
|
732
732
|
};
|
|
733
733
|
if (cfgFld.input_type === "code") cfgFld.input_type = "textarea";
|
|
734
|
-
actionConfigFields.push(cfgFld)
|
|
734
|
+
actionConfigFields.push(cfgFld);
|
|
735
735
|
}
|
|
736
736
|
} catch {}
|
|
737
737
|
}
|
|
@@ -745,6 +745,21 @@ const getWorkflowStepForm = async (
|
|
|
745
745
|
wf_action_name: Trigger.find({ action: "Workflow" }).map((wf) => wf.name),
|
|
746
746
|
},
|
|
747
747
|
});
|
|
748
|
+
const nonWfTriggerNames = Trigger.find({})
|
|
749
|
+
.filter((tr) => tr.action !== "Workflow")
|
|
750
|
+
.map((wf) => wf.name);
|
|
751
|
+
|
|
752
|
+
actionConfigFields.push({
|
|
753
|
+
label: "Row expression",
|
|
754
|
+
name: "row_expr",
|
|
755
|
+
type: "String",
|
|
756
|
+
class: "validate-expression",
|
|
757
|
+
sublabel:
|
|
758
|
+
"Expression for the object to set the <code>row</code> value to inside the action. If blank, set to whole context",
|
|
759
|
+
showIf: {
|
|
760
|
+
wf_action_name: nonWfTriggerNames,
|
|
761
|
+
},
|
|
762
|
+
});
|
|
748
763
|
|
|
749
764
|
const builtInActionExplainers = WorkflowStep.builtInActionExplainers({
|
|
750
765
|
api_call: trigger.when_trigger == "API call",
|
|
@@ -752,6 +767,7 @@ const getWorkflowStepForm = async (
|
|
|
752
767
|
const actionsNotRequiringRow = Trigger.action_options({
|
|
753
768
|
notRequireRow: true,
|
|
754
769
|
noMultiStep: true,
|
|
770
|
+
apiNeverTriggers: true,
|
|
755
771
|
builtInLabel: "Workflow Actions",
|
|
756
772
|
builtIns: Object.keys(builtInActionExplainers),
|
|
757
773
|
forWorkflow: true,
|
package/routes/admin.js
CHANGED
|
@@ -14,6 +14,7 @@ const {
|
|
|
14
14
|
get_sys_info,
|
|
15
15
|
tenant_letsencrypt_name,
|
|
16
16
|
isAdminOrHasConfigMinRole,
|
|
17
|
+
checkEditPermission,
|
|
17
18
|
} = require("./utils.js");
|
|
18
19
|
const Table = require("@saltcorn/data/models/table");
|
|
19
20
|
const Plugin = require("@saltcorn/data/models/plugin");
|
|
@@ -567,7 +568,11 @@ router.get(
|
|
|
567
568
|
error_catcher(async (req, res) => {
|
|
568
569
|
const snaps = await Snapshot.find(
|
|
569
570
|
{},
|
|
570
|
-
{
|
|
571
|
+
{
|
|
572
|
+
orderBy: "created",
|
|
573
|
+
orderDesc: true,
|
|
574
|
+
fields: ["id", "created", "hash", "name"],
|
|
575
|
+
}
|
|
571
576
|
);
|
|
572
577
|
const locale = getState().getConfig("default_locale", "en");
|
|
573
578
|
send_admin_page({
|
|
@@ -595,7 +600,9 @@ router.get(
|
|
|
595
600
|
snap.created,
|
|
596
601
|
{},
|
|
597
602
|
locale
|
|
598
|
-
)} (${moment(snap.created).fromNow()})
|
|
603
|
+
)} (${moment(snap.created).fromNow()})${
|
|
604
|
+
snap.name ? ` [${snap.name}]` : ""
|
|
605
|
+
}`
|
|
599
606
|
)
|
|
600
607
|
)
|
|
601
608
|
)
|
|
@@ -627,20 +634,6 @@ router.get(
|
|
|
627
634
|
})
|
|
628
635
|
);
|
|
629
636
|
|
|
630
|
-
const checkEditPermission = (type, user) => {
|
|
631
|
-
if (user.role_id === 1) return true;
|
|
632
|
-
switch (type) {
|
|
633
|
-
case "view":
|
|
634
|
-
return getState().getConfig("min_role_edit_views", 1) >= user.role_id;
|
|
635
|
-
case "page":
|
|
636
|
-
return getState().getConfig("min_role_edit_pages", 1) >= user.role_id;
|
|
637
|
-
case "trigger":
|
|
638
|
-
return getState().getConfig("min_role_edit_triggers", 1) >= user.role_id;
|
|
639
|
-
default:
|
|
640
|
-
return false;
|
|
641
|
-
}
|
|
642
|
-
};
|
|
643
|
-
|
|
644
637
|
router.get(
|
|
645
638
|
"/snapshot-restore/:type/:name",
|
|
646
639
|
isAdminOrHasConfigMinRole([
|
|
@@ -652,7 +645,7 @@ router.get(
|
|
|
652
645
|
const { type, name } = req.params;
|
|
653
646
|
const snaps = await Snapshot.entity_history(type, name);
|
|
654
647
|
const locale = getState().getConfig("default_locale", "en");
|
|
655
|
-
const auth = checkEditPermission(type, req.user);
|
|
648
|
+
const auth = checkEditPermission(type + "s", req.user);
|
|
656
649
|
if (!auth) {
|
|
657
650
|
res.send("Not authorized");
|
|
658
651
|
return;
|
|
@@ -664,11 +657,14 @@ router.get(
|
|
|
664
657
|
{
|
|
665
658
|
label: req.__("When"),
|
|
666
659
|
key: (r) =>
|
|
667
|
-
`${
|
|
660
|
+
`${moment(
|
|
668
661
|
r.created
|
|
669
|
-
).fromNow()})
|
|
662
|
+
).fromNow()}<br><small>${localeDateTime(r.created, {}, locale)}</small>`,
|
|
663
|
+
},
|
|
664
|
+
{
|
|
665
|
+
label: req.__("Name"),
|
|
666
|
+
key: (r) => r.name || "",
|
|
670
667
|
},
|
|
671
|
-
|
|
672
668
|
{
|
|
673
669
|
label: req.__("Restore"),
|
|
674
670
|
key: (r) =>
|
|
@@ -694,7 +690,7 @@ router.post(
|
|
|
694
690
|
]),
|
|
695
691
|
error_catcher(async (req, res) => {
|
|
696
692
|
const { type, name, id } = req.params;
|
|
697
|
-
const auth = checkEditPermission(type, req.user);
|
|
693
|
+
const auth = checkEditPermission(type + "s", req.user);
|
|
698
694
|
if (!auth) {
|
|
699
695
|
req.flash("error", "Not authorized");
|
|
700
696
|
} else {
|
|
@@ -963,7 +959,8 @@ const snapshotForm = (req) =>
|
|
|
963
959
|
label: req.__("Snapshot now"),
|
|
964
960
|
id: "btnSnapNow",
|
|
965
961
|
class: "btn btn-outline-secondary",
|
|
966
|
-
onclick:
|
|
962
|
+
onclick:
|
|
963
|
+
"ajax_post('/admin/snapshot-now/'+prompt('Name of snapshot (optional)'))",
|
|
967
964
|
},
|
|
968
965
|
],
|
|
969
966
|
fields: [
|
|
@@ -1075,11 +1072,18 @@ router.post(
|
|
|
1075
1072
|
* Do Snapshot now
|
|
1076
1073
|
*/
|
|
1077
1074
|
router.post(
|
|
1078
|
-
"/snapshot-now",
|
|
1075
|
+
"/snapshot-now/:snapshotname?",
|
|
1079
1076
|
isAdmin,
|
|
1080
1077
|
error_catcher(async (req, res) => {
|
|
1078
|
+
const { snapshotname } = req.params;
|
|
1079
|
+
if (snapshotname == "null") {
|
|
1080
|
+
//user clicked cancel on prompt
|
|
1081
|
+
res.json({ success: true });
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1081
1085
|
try {
|
|
1082
|
-
const taken = await Snapshot.take_if_changed();
|
|
1086
|
+
const taken = await Snapshot.take_if_changed(snapshotname);
|
|
1083
1087
|
if (taken) req.flash("success", req.__("Snapshot successful"));
|
|
1084
1088
|
else
|
|
1085
1089
|
req.flash("success", req.__("No changes detected, snapshot skipped"));
|
|
@@ -1574,7 +1578,7 @@ const doInstall = async (req, res, version, deepClean, runPull) => {
|
|
|
1574
1578
|
}
|
|
1575
1579
|
const child = spawn(
|
|
1576
1580
|
"npm",
|
|
1577
|
-
["install", "-g", `@saltcorn/cli@${version}`, "--
|
|
1581
|
+
["install", "-g", `@saltcorn/cli@${version}`, "--omit=dev"],
|
|
1578
1582
|
{
|
|
1579
1583
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1580
1584
|
}
|
package/routes/api.js
CHANGED
|
@@ -279,14 +279,58 @@ router.get(
|
|
|
279
279
|
* @function
|
|
280
280
|
* @memberof module:routes/api~apiRouter
|
|
281
281
|
*/
|
|
282
|
-
|
|
282
|
+
|
|
283
|
+
function validateNumberMin(value, min) {
|
|
284
|
+
if (typeof value !== "number") {
|
|
285
|
+
// return false; //throw new TypeError('Value is not a number');
|
|
286
|
+
value = strictParseInt(value);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (!Number.isSafeInteger(value)) {
|
|
290
|
+
return false; //throw new RangeError('Value is outside the valid range for an integer');
|
|
291
|
+
}
|
|
292
|
+
if (value < min) return false;
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
|
|
283
296
|
router.get(
|
|
284
297
|
"/:tableName/",
|
|
285
298
|
//passport.authenticate("api-bearer", { session: false }),
|
|
286
299
|
error_catcher(async (req, res, next) => {
|
|
287
300
|
let { tableName } = req.params;
|
|
288
|
-
const {
|
|
289
|
-
|
|
301
|
+
const {
|
|
302
|
+
fields,
|
|
303
|
+
versioncount,
|
|
304
|
+
limit,
|
|
305
|
+
offset,
|
|
306
|
+
sortBy,
|
|
307
|
+
sortDesc,
|
|
308
|
+
approximate,
|
|
309
|
+
dereference,
|
|
310
|
+
tabulator_pagination_format,
|
|
311
|
+
...req_query0
|
|
312
|
+
} = req.query;
|
|
313
|
+
|
|
314
|
+
let req_query = req_query0;
|
|
315
|
+
let tabulator_size, tabulator_page, tabulator_sort, tabulator_dir;
|
|
316
|
+
if (tabulator_pagination_format) {
|
|
317
|
+
const { page, size, sort, ...rq } = req_query0;
|
|
318
|
+
req_query = rq;
|
|
319
|
+
tabulator_page = page;
|
|
320
|
+
tabulator_size = size;
|
|
321
|
+
tabulator_sort = sort?.[0]?.field;
|
|
322
|
+
tabulator_dir = sort?.[0]?.dir;
|
|
323
|
+
}
|
|
324
|
+
if (typeof limit !== "undefined")
|
|
325
|
+
if (isNaN(limit) || !validateNumberMin(limit, 1)) {
|
|
326
|
+
getState().log(3, `API get ${tableName} Invalid limit parameter`);
|
|
327
|
+
return res.status(400).send({ error: "Invalid limit parameter" });
|
|
328
|
+
}
|
|
329
|
+
if (typeof offset !== "undefined")
|
|
330
|
+
if (isNaN(offset) || !validateNumberMin(offset, 1)) {
|
|
331
|
+
getState().log(3, `API get ${tableName} Invalid offset parameter`);
|
|
332
|
+
return res.status(400).send({ error: "Invalid offset parameter" });
|
|
333
|
+
}
|
|
290
334
|
const table = Table.findOne(
|
|
291
335
|
strictParseInt(tableName)
|
|
292
336
|
? { id: strictParseInt(tableName) }
|
|
@@ -303,6 +347,8 @@ router.get(
|
|
|
303
347
|
res.status(404).json({ error: req.__("Not found") });
|
|
304
348
|
return;
|
|
305
349
|
}
|
|
350
|
+
const orderByField =
|
|
351
|
+
(sortBy || tabulator_sort) && table.getField(sortBy || tabulator_sort);
|
|
306
352
|
|
|
307
353
|
await passport.authenticate(
|
|
308
354
|
["api-bearer", "jwt"],
|
|
@@ -312,9 +358,17 @@ router.get(
|
|
|
312
358
|
let rows;
|
|
313
359
|
if (versioncount === "on") {
|
|
314
360
|
const joinOpts = {
|
|
315
|
-
orderBy: "id",
|
|
316
361
|
forUser: req.user || user || { role_id: 100 },
|
|
317
362
|
forPublic: !(req.user || user),
|
|
363
|
+
limit: tabulator_pagination_format
|
|
364
|
+
? +tabulator_size
|
|
365
|
+
: limit && +limit,
|
|
366
|
+
offset: tabulator_pagination_format
|
|
367
|
+
? +tabulator_size * (+tabulator_page - 1)
|
|
368
|
+
: offset && +offset,
|
|
369
|
+
orderDesc:
|
|
370
|
+
(sortDesc && sortDesc !== "false") || tabulator_dir == "desc",
|
|
371
|
+
orderBy: orderByField?.name || "id",
|
|
318
372
|
aggregations: {
|
|
319
373
|
_versions: {
|
|
320
374
|
table: table.name + "__history",
|
|
@@ -352,11 +406,20 @@ router.get(
|
|
|
352
406
|
rows = await table.getJoinedRows({
|
|
353
407
|
where: qstate,
|
|
354
408
|
joinFields,
|
|
409
|
+
limit: limit && +limit,
|
|
410
|
+
offset: offset && +offset,
|
|
411
|
+
orderDesc: sortDesc && sortDesc !== "false",
|
|
412
|
+
orderBy: orderByField?.name || undefined,
|
|
355
413
|
forPublic: !(req.user || user),
|
|
356
414
|
forUser: req.user || user,
|
|
357
415
|
});
|
|
358
416
|
}
|
|
359
|
-
|
|
417
|
+
if (tabulator_pagination_format) {
|
|
418
|
+
res.json({
|
|
419
|
+
last_page: Math.ceil((await table.countRows()) / +tabulator_size),
|
|
420
|
+
data: rows.map(limitFields(fields)),
|
|
421
|
+
});
|
|
422
|
+
} else res.json({ success: rows.map(limitFields(fields)) });
|
|
360
423
|
} else {
|
|
361
424
|
getState().log(3, `API get ${table.name} not authorized`);
|
|
362
425
|
res.status(401).json({ error: req.__("Not authorized") });
|