@saltcorn/server 0.9.1-beta.0 → 0.9.1-beta.1
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 +71 -0
- package/locales/en.json +5 -1
- package/package.json +8 -8
- package/public/saltcorn-common.js +24 -31
- package/public/saltcorn.js +1 -10
- package/routes/actions.js +1 -0
- package/routes/common_lists.js +5 -1
- package/routes/homepage.js +11 -10
- package/routes/page.js +19 -15
- package/routes/pageedit.js +50 -3
- package/routes/utils.js +36 -0
- package/tests/page.test.js +94 -1
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
The event type for triggers determines when the chosen action should be run.
|
|
2
|
+
The different event types options come from a variety of the types and sources.
|
|
3
|
+
|
|
4
|
+
These events also form the basis of the event log. Use the log settings to enable or disable
|
|
5
|
+
recording of the occurrence of events.
|
|
6
|
+
|
|
7
|
+
## Database events
|
|
8
|
+
|
|
9
|
+
These conditions are triggered by changes to rows in tables. Together with the event type
|
|
10
|
+
a specific table is chosen. The individual conditions are:
|
|
11
|
+
|
|
12
|
+
**Insert**: run the action when a new row is inserted in the table. This is a good choice
|
|
13
|
+
when a table itself represents actions to be carried out; for instance a table of
|
|
14
|
+
outbound emails would have a trigger with When = Insert and Action = send_email
|
|
15
|
+
|
|
16
|
+
**Update**: run this action when changes are made to an existing row. The old row can
|
|
17
|
+
be accessed with the `old_row` variable.
|
|
18
|
+
|
|
19
|
+
**Delete**: run this action when a row is deleted
|
|
20
|
+
|
|
21
|
+
## Periodic events
|
|
22
|
+
|
|
23
|
+
These triggers are run periodically at different times.
|
|
24
|
+
|
|
25
|
+
**Weekly**: run this once a week.
|
|
26
|
+
|
|
27
|
+
**Daily**: run this once a day.
|
|
28
|
+
|
|
29
|
+
**Hourly**: run this once an hour.
|
|
30
|
+
|
|
31
|
+
**Often**: run this every 5 minutes.
|
|
32
|
+
|
|
33
|
+
## User-based events
|
|
34
|
+
|
|
35
|
+
**PageLoad**: run this whenever a page or view is loaded. If you set up the event log to
|
|
36
|
+
record these events you can use this as a basis for an analytics system.
|
|
37
|
+
|
|
38
|
+
**Login**: run this whenever a user log in successfully
|
|
39
|
+
|
|
40
|
+
**LoginFailed**: run this whenever a user login failed
|
|
41
|
+
|
|
42
|
+
**UserVerified**: run this when a user is verified, if an appropriate module for
|
|
43
|
+
user verification is enabled.
|
|
44
|
+
|
|
45
|
+
## System-based events
|
|
46
|
+
|
|
47
|
+
**Error**: run this whenever an error occurs
|
|
48
|
+
|
|
49
|
+
**Startup**: run this whenever this saltcorn process initializes.
|
|
50
|
+
|
|
51
|
+
## Other events
|
|
52
|
+
|
|
53
|
+
**Never**: this trigger is never run on its own. However triggers that are marked as never
|
|
54
|
+
can be chosen as the target action for a button in the UI. Use this if you have a complex
|
|
55
|
+
configuration for an action that needs to be run in response to a button click, or if you
|
|
56
|
+
have a configuration that needs to be reused between two different buttons in two different
|
|
57
|
+
views. You can also use this to switch off a trigger that is running on a different event
|
|
58
|
+
type without deleting it.
|
|
59
|
+
|
|
60
|
+
**API call**: this trigger can be run in response to an inbound API call. To see the URL
|
|
61
|
+
and further help, click the help icon next to the "API call" label in the trigger list.
|
|
62
|
+
|
|
63
|
+
## Custom events
|
|
64
|
+
|
|
65
|
+
You can create your own event type which can then be triggered with an emit_event action
|
|
66
|
+
or the `emitEvent` call in a run js code action
|
|
67
|
+
|
|
68
|
+
## Events supplied by modules
|
|
69
|
+
|
|
70
|
+
Modules can provide new event types. For instance the mqtt module provides an event
|
|
71
|
+
type based on receiving new messages.
|
package/locales/en.json
CHANGED
|
@@ -1273,5 +1273,9 @@
|
|
|
1273
1273
|
"Body size limit (Kb)": "Body size limit (Kb)",
|
|
1274
1274
|
"Maximum request body size in kilobytes": "Maximum request body size in kilobytes",
|
|
1275
1275
|
"URL encoded size limit (Kb)": "URL encoded size limit (Kb)",
|
|
1276
|
-
"Maximum URL encoded request size in kilobytes": "Maximum URL encoded request size in kilobytes"
|
|
1276
|
+
"Maximum URL encoded request size in kilobytes": "Maximum URL encoded request size in kilobytes",
|
|
1277
|
+
"HTML file": "HTML file",
|
|
1278
|
+
"HTML file to use as page content": "HTML file to use as page content",
|
|
1279
|
+
"Offline mode: cannot load file": "Offline mode: cannot load file",
|
|
1280
|
+
"None - use drag and drop builder": "None - use drag and drop builder"
|
|
1277
1281
|
}
|
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/server",
|
|
3
|
-
"version": "0.9.1-beta.
|
|
3
|
+
"version": "0.9.1-beta.1",
|
|
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
|
-
"@saltcorn/base-plugin": "0.9.1-beta.
|
|
10
|
-
"@saltcorn/builder": "0.9.1-beta.
|
|
11
|
-
"@saltcorn/data": "0.9.1-beta.
|
|
12
|
-
"@saltcorn/admin-models": "0.9.1-beta.
|
|
13
|
-
"@saltcorn/filemanager": "0.9.1-beta.
|
|
14
|
-
"@saltcorn/markup": "0.9.1-beta.
|
|
15
|
-
"@saltcorn/sbadmin2": "0.9.1-beta.
|
|
9
|
+
"@saltcorn/base-plugin": "0.9.1-beta.1",
|
|
10
|
+
"@saltcorn/builder": "0.9.1-beta.1",
|
|
11
|
+
"@saltcorn/data": "0.9.1-beta.1",
|
|
12
|
+
"@saltcorn/admin-models": "0.9.1-beta.1",
|
|
13
|
+
"@saltcorn/filemanager": "0.9.1-beta.1",
|
|
14
|
+
"@saltcorn/markup": "0.9.1-beta.1",
|
|
15
|
+
"@saltcorn/sbadmin2": "0.9.1-beta.1",
|
|
16
16
|
"@socket.io/cluster-adapter": "^0.2.1",
|
|
17
17
|
"@socket.io/sticky": "^1.0.1",
|
|
18
18
|
"adm-zip": "0.5.10",
|
|
@@ -585,8 +585,11 @@ function initialize_page() {
|
|
|
585
585
|
$(this).find("span.current time").attr("datetime"); // ||
|
|
586
586
|
//$(this).children("span.current").html();
|
|
587
587
|
}
|
|
588
|
-
|
|
588
|
+
if (type === "Bool") {
|
|
589
|
+
current = current === "true";
|
|
590
|
+
}
|
|
589
591
|
var is_key = type?.startsWith("Key:");
|
|
592
|
+
const resetHtml = this.outerHTML;
|
|
590
593
|
const opts = encodeURIComponent(
|
|
591
594
|
JSON.stringify({
|
|
592
595
|
url,
|
|
@@ -597,6 +600,7 @@ function initialize_page() {
|
|
|
597
600
|
type,
|
|
598
601
|
is_key,
|
|
599
602
|
schema,
|
|
603
|
+
resetHtml,
|
|
600
604
|
...(decimalPlaces ? { decimalPlaces } : {}),
|
|
601
605
|
})
|
|
602
606
|
);
|
|
@@ -649,7 +653,11 @@ function initialize_page() {
|
|
|
649
653
|
: ""
|
|
650
654
|
}
|
|
651
655
|
<input type="${
|
|
652
|
-
type === "Integer" || type === "Float"
|
|
656
|
+
type === "Integer" || type === "Float"
|
|
657
|
+
? "number"
|
|
658
|
+
: type === "Bool"
|
|
659
|
+
? "checkbox"
|
|
660
|
+
: "text"
|
|
653
661
|
}" ${
|
|
654
662
|
type === "Float"
|
|
655
663
|
? `step="${
|
|
@@ -660,7 +668,13 @@ function initialize_page() {
|
|
|
660
668
|
: "any"
|
|
661
669
|
}"`
|
|
662
670
|
: ""
|
|
663
|
-
} name="${key}"
|
|
671
|
+
} name="${key}" ${
|
|
672
|
+
type === "Bool"
|
|
673
|
+
? current
|
|
674
|
+
? "checked"
|
|
675
|
+
: ""
|
|
676
|
+
: `value="${escapeHtml(current)}"`
|
|
677
|
+
}>
|
|
664
678
|
<button type="submit" class="btn btn-sm btn-primary">OK</button>
|
|
665
679
|
<button onclick="cancel_inline_edit(event, '${opts}')" type="button" class="btn btn-sm btn-danger"><i class="fas fa-times"></i></button>
|
|
666
680
|
</form>`
|
|
@@ -777,33 +791,7 @@ function cancel_inline_edit(e, opts1) {
|
|
|
777
791
|
const isNode = typeof parent?.saltcorn?.data?.state === "undefined";
|
|
778
792
|
var opts = JSON.parse(decodeURIComponent(opts1 || "") || "{}");
|
|
779
793
|
var form = $(e.target).closest("form");
|
|
780
|
-
|
|
781
|
-
if (opts.schema) {
|
|
782
|
-
json_fk_opt = form.find(`option[value="${opts.current}"]`).text();
|
|
783
|
-
}
|
|
784
|
-
form.replaceWith(`<div
|
|
785
|
-
data-inline-edit-field="${opts.key}"
|
|
786
|
-
${opts.ajax ? `data-inline-edit-ajax="true"` : ""}
|
|
787
|
-
${opts.type ? `data-inline-edit-type="${opts.type}"` : ""}
|
|
788
|
-
${opts.current ? `data-inline-edit-current="${opts.current}"` : ""}
|
|
789
|
-
${
|
|
790
|
-
opts.current_label
|
|
791
|
-
? `data-inline-edit-current-label="${opts.current_label}"`
|
|
792
|
-
: ""
|
|
793
|
-
}
|
|
794
|
-
${
|
|
795
|
-
opts.schema
|
|
796
|
-
? `data-inline-edit-schema="${encodeURIComponent(
|
|
797
|
-
JSON.stringify(opts.schema)
|
|
798
|
-
)}"`
|
|
799
|
-
: ""
|
|
800
|
-
}
|
|
801
|
-
data-inline-edit-dest-url="${opts.url}">
|
|
802
|
-
<span class="current">${
|
|
803
|
-
json_fk_opt || opts.current_label || opts.current
|
|
804
|
-
}</span>
|
|
805
|
-
<i class="editicon ${!isNode ? "visible" : ""} fas fa-edit ms-1"></i>
|
|
806
|
-
</div>`);
|
|
794
|
+
form.replaceWith(opts.resetHtml);
|
|
807
795
|
initialize_page();
|
|
808
796
|
}
|
|
809
797
|
|
|
@@ -811,7 +799,8 @@ function inline_submit_success(e, form, opts) {
|
|
|
811
799
|
const isNode = typeof parent?.saltcorn?.data?.state === "undefined";
|
|
812
800
|
const formDataArray = form.serializeArray();
|
|
813
801
|
if (opts) {
|
|
814
|
-
let
|
|
802
|
+
let fdEntry = formDataArray.find((f) => f.name == opts.key);
|
|
803
|
+
let rawVal = opts.type === "Bool" ? !!fdEntry : fdEntry.value;
|
|
815
804
|
let val =
|
|
816
805
|
opts.is_key || (opts.schema && opts.schema.type.startsWith("Key to "))
|
|
817
806
|
? form.find("select").find("option:selected").text()
|
|
@@ -846,9 +835,13 @@ function inline_submit_success(e, form, opts) {
|
|
|
846
835
|
function inline_ajax_submit(e, opts1) {
|
|
847
836
|
var opts = JSON.parse(decodeURIComponent(opts1 || "") || "{}");
|
|
848
837
|
e.preventDefault();
|
|
838
|
+
|
|
849
839
|
var form = $(e.target).closest("form");
|
|
850
840
|
var form_data = form.serialize();
|
|
851
841
|
var url = form.attr("action");
|
|
842
|
+
if (opts.type === "Bool" && !form_data.includes(`${opts.key}=on`)) {
|
|
843
|
+
form_data += `&${opts.key}=off`;
|
|
844
|
+
}
|
|
852
845
|
$.ajax(url, {
|
|
853
846
|
type: "POST",
|
|
854
847
|
headers: {
|
package/public/saltcorn.js
CHANGED
|
@@ -397,16 +397,7 @@ function saveAndContinue(e, k) {
|
|
|
397
397
|
error: function (request) {
|
|
398
398
|
var ct = request.getResponseHeader("content-type") || "";
|
|
399
399
|
if (ct.startsWith && ct.startsWith("application/json")) {
|
|
400
|
-
|
|
401
|
-
if (errorArea.length) {
|
|
402
|
-
errorArea.text(request.responseJSON.error);
|
|
403
|
-
} else {
|
|
404
|
-
form
|
|
405
|
-
.parent()
|
|
406
|
-
.append(
|
|
407
|
-
`<p class="text-danger full-form-error">${request.responseJSON.error}</p>`
|
|
408
|
-
);
|
|
409
|
-
}
|
|
400
|
+
notifyAlert({ type: "danger", text: request.responseJSON.error });
|
|
410
401
|
} else {
|
|
411
402
|
$("#page-inner-content").html(request.responseText);
|
|
412
403
|
initialize_page();
|
package/routes/actions.js
CHANGED
|
@@ -171,6 +171,7 @@ const triggerForm = async (req, trigger) => {
|
|
|
171
171
|
required: true,
|
|
172
172
|
options: Trigger.when_options.map((t) => ({ value: t, label: t })),
|
|
173
173
|
sublabel: req.__("Event type which runs the trigger"),
|
|
174
|
+
help: { topic: "Event types" },
|
|
174
175
|
attributes: {
|
|
175
176
|
explainers: {
|
|
176
177
|
Often: req.__("Every 5 minutes"),
|
package/routes/common_lists.js
CHANGED
|
@@ -344,7 +344,11 @@ const getPageList = (rows, roles, req, { tagId, domId, showList } = {}) => {
|
|
|
344
344
|
},
|
|
345
345
|
{
|
|
346
346
|
label: req.__("Edit"),
|
|
347
|
-
key: (r) =>
|
|
347
|
+
key: (r) =>
|
|
348
|
+
link(
|
|
349
|
+
`/pageedit/${!r.html_file ? "edit" : "edit-properties"}/${r.name}`,
|
|
350
|
+
req.__(!r.html_file ? "Edit" : "Edit properties")
|
|
351
|
+
),
|
|
348
352
|
},
|
|
349
353
|
!tagId
|
|
350
354
|
? {
|
package/routes/homepage.js
CHANGED
|
@@ -21,7 +21,7 @@ const { get_latest_npm_version } = require("@saltcorn/data/models/config");
|
|
|
21
21
|
const packagejson = require("../package.json");
|
|
22
22
|
const Trigger = require("@saltcorn/data/models/trigger");
|
|
23
23
|
const { fileUploadForm } = require("../markup/forms");
|
|
24
|
-
const { get_base_url } = require("./utils.js");
|
|
24
|
+
const { get_base_url, sendHtmlFile } = require("./utils.js");
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Tables List
|
|
@@ -480,15 +480,16 @@ const get_config_response = async (role_id, res, req) => {
|
|
|
480
480
|
|
|
481
481
|
if (db_page) {
|
|
482
482
|
const contents = await db_page.run(req.query, { res, req });
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
483
|
+
if (contents.html_file) await sendHtmlFile(req, res, contents.html_file);
|
|
484
|
+
else
|
|
485
|
+
res.sendWrap(
|
|
486
|
+
{
|
|
487
|
+
title: db_page.title,
|
|
488
|
+
description: db_page.description,
|
|
489
|
+
bodyClass: "page_" + db.sqlsanitize(homeCfg),
|
|
490
|
+
},
|
|
491
|
+
contents
|
|
492
|
+
);
|
|
492
493
|
} else res.redirect(homeCfg);
|
|
493
494
|
return true;
|
|
494
495
|
}
|
package/routes/page.js
CHANGED
|
@@ -8,11 +8,13 @@ const Router = require("express-promise-router");
|
|
|
8
8
|
|
|
9
9
|
const Page = require("@saltcorn/data/models/page");
|
|
10
10
|
const Trigger = require("@saltcorn/data/models/trigger");
|
|
11
|
+
const File = require("@saltcorn/data/models/file");
|
|
11
12
|
const { getState } = require("@saltcorn/data/db/state");
|
|
12
13
|
const {
|
|
13
14
|
error_catcher,
|
|
14
15
|
scan_for_page_title,
|
|
15
16
|
isAdmin,
|
|
17
|
+
sendHtmlFile,
|
|
16
18
|
} = require("../routes/utils.js");
|
|
17
19
|
const { add_edit_bar } = require("../markup/admin.js");
|
|
18
20
|
const { traverseSync } = require("@saltcorn/data/models/layout");
|
|
@@ -56,21 +58,23 @@ router.get(
|
|
|
56
58
|
name: pagename,
|
|
57
59
|
render_time: ms,
|
|
58
60
|
});
|
|
59
|
-
res.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
61
|
+
if (contents.html_file) await sendHtmlFile(req, res, contents.html_file);
|
|
62
|
+
else
|
|
63
|
+
res.sendWrap(
|
|
64
|
+
{
|
|
65
|
+
title,
|
|
66
|
+
description: db_page.description,
|
|
67
|
+
bodyClass: "page_" + db.sqlsanitize(pagename),
|
|
68
|
+
no_menu: db_page.attributes?.no_menu,
|
|
69
|
+
} || `${pagename} page`,
|
|
70
|
+
add_edit_bar({
|
|
71
|
+
role,
|
|
72
|
+
title: db_page.name,
|
|
73
|
+
what: req.__("Page"),
|
|
74
|
+
url: `/pageedit/edit/${encodeURIComponent(db_page.name)}`,
|
|
75
|
+
contents,
|
|
76
|
+
})
|
|
77
|
+
);
|
|
74
78
|
} else {
|
|
75
79
|
if (db_page && !req.user) {
|
|
76
80
|
res.redirect(`/auth/login?dest=${encodeURIComponent(req.originalUrl)}`);
|
package/routes/pageedit.js
CHANGED
|
@@ -27,6 +27,7 @@ const {
|
|
|
27
27
|
addOnDoneRedirect,
|
|
28
28
|
is_relative_url,
|
|
29
29
|
} = require("./utils.js");
|
|
30
|
+
const { asyncMap } = require("@saltcorn/data/utils");
|
|
30
31
|
const {
|
|
31
32
|
mkTable,
|
|
32
33
|
renderForm,
|
|
@@ -39,6 +40,7 @@ const {
|
|
|
39
40
|
} = require("@saltcorn/markup");
|
|
40
41
|
const { getActionConfigFields } = require("@saltcorn/data/plugin-helper");
|
|
41
42
|
const Library = require("@saltcorn/data/models/library");
|
|
43
|
+
const path = require("path");
|
|
42
44
|
|
|
43
45
|
/**
|
|
44
46
|
* @type {object}
|
|
@@ -58,6 +60,20 @@ module.exports = router;
|
|
|
58
60
|
const pagePropertiesForm = async (req, isNew) => {
|
|
59
61
|
const roles = await User.get_roles();
|
|
60
62
|
const pages = (await Page.find()).map((p) => p.name);
|
|
63
|
+
const htmlFiles = await File.find(
|
|
64
|
+
{
|
|
65
|
+
mime_super: "text",
|
|
66
|
+
mime_sub: "html",
|
|
67
|
+
},
|
|
68
|
+
{ recursive: true }
|
|
69
|
+
);
|
|
70
|
+
const htmlOptions = await asyncMap(htmlFiles, async (f) => {
|
|
71
|
+
return {
|
|
72
|
+
label: path.join(f.current_folder, f.filename),
|
|
73
|
+
value: File.absPathToServePath(f.location),
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
|
|
61
77
|
const form = new Form({
|
|
62
78
|
action: addOnDoneRedirect("/pageedit/edit-properties", req),
|
|
63
79
|
fields: [
|
|
@@ -92,6 +108,24 @@ const pagePropertiesForm = async (req, isNew) => {
|
|
|
92
108
|
input_type: "select",
|
|
93
109
|
options: roles.map((r) => ({ value: r.id, label: r.role })),
|
|
94
110
|
},
|
|
111
|
+
...(htmlOptions.length > 0
|
|
112
|
+
? [
|
|
113
|
+
{
|
|
114
|
+
name: "html_file",
|
|
115
|
+
label: req.__("HTML file"),
|
|
116
|
+
sublabel: req.__("HTML file to use as page content"),
|
|
117
|
+
input_type: "select",
|
|
118
|
+
|
|
119
|
+
options: [
|
|
120
|
+
{
|
|
121
|
+
label: req.__("None - use drag and drop builder"),
|
|
122
|
+
value: "",
|
|
123
|
+
},
|
|
124
|
+
...htmlOptions,
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
]
|
|
128
|
+
: []),
|
|
95
129
|
{
|
|
96
130
|
name: "no_menu",
|
|
97
131
|
label: req.__("No menu"),
|
|
@@ -367,17 +401,30 @@ router.post(
|
|
|
367
401
|
wrap(renderForm(form, req.csrfToken()), false, req)
|
|
368
402
|
);
|
|
369
403
|
} else {
|
|
370
|
-
const { id, columns, no_menu, ...pageRow } = form.values;
|
|
404
|
+
const { id, columns, no_menu, html_file, ...pageRow } = form.values;
|
|
371
405
|
pageRow.min_role = +pageRow.min_role;
|
|
372
406
|
pageRow.attributes = { no_menu };
|
|
407
|
+
if (html_file) {
|
|
408
|
+
pageRow.layout = {
|
|
409
|
+
html_file: html_file,
|
|
410
|
+
};
|
|
411
|
+
}
|
|
373
412
|
if (+id) {
|
|
413
|
+
const dbPage = Page.findOne({ id: id });
|
|
414
|
+
if (dbPage.layout?.html_file && !html_file) {
|
|
415
|
+
pageRow.layout = {};
|
|
416
|
+
}
|
|
374
417
|
await Page.update(+id, pageRow);
|
|
375
418
|
res.redirect(`/pageedit/`);
|
|
376
419
|
} else {
|
|
377
|
-
if (!pageRow.fixed_states) pageRow.fixed_states = {};
|
|
378
420
|
if (!pageRow.layout) pageRow.layout = {};
|
|
421
|
+
if (!pageRow.fixed_states) pageRow.fixed_states = {};
|
|
379
422
|
await Page.create(pageRow);
|
|
380
|
-
|
|
423
|
+
if (!html_file)
|
|
424
|
+
res.redirect(
|
|
425
|
+
addOnDoneRedirect(`/pageedit/edit/${pageRow.name}`, req)
|
|
426
|
+
);
|
|
427
|
+
else res.redirect(`/pageedit/`);
|
|
381
428
|
}
|
|
382
429
|
}
|
|
383
430
|
})
|
package/routes/utils.js
CHANGED
|
@@ -18,6 +18,7 @@ const cookieSession = require("cookie-session");
|
|
|
18
18
|
const is = require("contractis/is");
|
|
19
19
|
const { validateHeaderName, validateHeaderValue } = require("http");
|
|
20
20
|
const Crash = require("@saltcorn/data/models/crash");
|
|
21
|
+
const File = require("@saltcorn/data/models/file");
|
|
21
22
|
const si = require("systeminformation");
|
|
22
23
|
const {
|
|
23
24
|
config_fields_form,
|
|
@@ -25,6 +26,8 @@ const {
|
|
|
25
26
|
check_if_restart_required,
|
|
26
27
|
flash_restart,
|
|
27
28
|
} = require("../markup/admin.js");
|
|
29
|
+
const path = require("path");
|
|
30
|
+
|
|
28
31
|
const get_sys_info = async () => {
|
|
29
32
|
const disks = await si.fsSize();
|
|
30
33
|
let size = 0;
|
|
@@ -380,6 +383,38 @@ const admin_config_route = ({
|
|
|
380
383
|
);
|
|
381
384
|
};
|
|
382
385
|
|
|
386
|
+
/**
|
|
387
|
+
* Send HTML file to client without any menu
|
|
388
|
+
* @param {any} req
|
|
389
|
+
* @param {any} res
|
|
390
|
+
* @param {string} file
|
|
391
|
+
* @returns
|
|
392
|
+
*/
|
|
393
|
+
const sendHtmlFile = async (req, res, file) => {
|
|
394
|
+
const fullPath = path.join((await File.rootFolder()).location, file);
|
|
395
|
+
const role = req.user && req.user.id ? req.user.role_id : 100;
|
|
396
|
+
try {
|
|
397
|
+
const scFile = await File.from_file_on_disk(
|
|
398
|
+
path.basename(fullPath),
|
|
399
|
+
path.dirname(fullPath)
|
|
400
|
+
);
|
|
401
|
+
if (scFile && role <= scFile.min_role_read) {
|
|
402
|
+
res.sendFile(fullPath);
|
|
403
|
+
} else {
|
|
404
|
+
return res
|
|
405
|
+
.status(404)
|
|
406
|
+
.sendWrap(req.__("An error occurred"), req.__("File not found"));
|
|
407
|
+
}
|
|
408
|
+
} catch (e) {
|
|
409
|
+
return res
|
|
410
|
+
.status(404)
|
|
411
|
+
.sendWrap(
|
|
412
|
+
req.__("An error occurred"),
|
|
413
|
+
e.message || req.__("An error occurred")
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
|
|
383
418
|
module.exports = {
|
|
384
419
|
sqlsanitize,
|
|
385
420
|
csrfField,
|
|
@@ -396,4 +431,5 @@ module.exports = {
|
|
|
396
431
|
is_relative_url,
|
|
397
432
|
get_sys_info,
|
|
398
433
|
admin_config_route,
|
|
434
|
+
sendHtmlFile,
|
|
399
435
|
};
|
package/tests/page.test.js
CHANGED
|
@@ -13,9 +13,42 @@ const {
|
|
|
13
13
|
} = require("../auth/testhelp");
|
|
14
14
|
const db = require("@saltcorn/data/db");
|
|
15
15
|
const Page = require("@saltcorn/data/models/page");
|
|
16
|
+
const File = require("@saltcorn/data/models/file");
|
|
17
|
+
const { existsSync } = require("fs");
|
|
18
|
+
const { join } = require("path");
|
|
19
|
+
|
|
20
|
+
let htmlFile = null;
|
|
21
|
+
|
|
22
|
+
const prepHtmlFiles = async () => {
|
|
23
|
+
const createFile = async (folder, name, content) => {
|
|
24
|
+
const scFolder = join(
|
|
25
|
+
db.connectObj.file_store,
|
|
26
|
+
db.getTenantSchema(),
|
|
27
|
+
folder
|
|
28
|
+
);
|
|
29
|
+
if (!existsSync(scFolder)) await File.new_folder(folder);
|
|
30
|
+
if (!existsSync(join(scFolder, name))) {
|
|
31
|
+
return await File.from_contents(
|
|
32
|
+
name,
|
|
33
|
+
"text/html",
|
|
34
|
+
`<html><head><title>Landing page</title></head><body><h1>${content}</h1></body></html>`,
|
|
35
|
+
1,
|
|
36
|
+
1,
|
|
37
|
+
folder
|
|
38
|
+
);
|
|
39
|
+
} else {
|
|
40
|
+
const file = await File.from_file_on_disk(name, scFolder);
|
|
41
|
+
file.location = File.absPathToServePath(file.location);
|
|
42
|
+
return file;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
htmlFile = await createFile("/", "fixed_page.html", "Land here");
|
|
46
|
+
await createFile("/subfolder", "fixed_page2.html", "Or Land here");
|
|
47
|
+
};
|
|
16
48
|
|
|
17
49
|
beforeAll(async () => {
|
|
18
50
|
await resetToFixtures();
|
|
51
|
+
await prepHtmlFiles();
|
|
19
52
|
});
|
|
20
53
|
afterAll(db.close);
|
|
21
54
|
|
|
@@ -36,9 +69,18 @@ describe("page create", () => {
|
|
|
36
69
|
await request(app)
|
|
37
70
|
.get("/pageedit/new")
|
|
38
71
|
.set("Cookie", loginCookie)
|
|
39
|
-
|
|
40
72
|
.expect(toInclude("A short name that will be in your URL"));
|
|
41
73
|
});
|
|
74
|
+
it("shows new with html file selector", async () => {
|
|
75
|
+
const app = await getApp({ disableCsrf: true });
|
|
76
|
+
const loginCookie = await getAdminLoginCookie();
|
|
77
|
+
await request(app)
|
|
78
|
+
.get("/pageedit/new")
|
|
79
|
+
.set("Cookie", loginCookie)
|
|
80
|
+
.expect(toInclude("HTML file"))
|
|
81
|
+
.expect(toInclude("fixed_page.html"))
|
|
82
|
+
.expect(toInclude(join("subfolder", "fixed_page2.html")));
|
|
83
|
+
});
|
|
42
84
|
it("fills basic details", async () => {
|
|
43
85
|
const app = await getApp({ disableCsrf: true });
|
|
44
86
|
const loginCookie = await getAdminLoginCookie();
|
|
@@ -48,6 +90,19 @@ describe("page create", () => {
|
|
|
48
90
|
.set("Cookie", loginCookie)
|
|
49
91
|
.expect(toRedirect("/pageedit/edit/whales"));
|
|
50
92
|
});
|
|
93
|
+
it("fills details with html-file", async () => {
|
|
94
|
+
const app = await getApp({ disableCsrf: true });
|
|
95
|
+
const loginCookie = await getAdminLoginCookie();
|
|
96
|
+
await request(app)
|
|
97
|
+
.post("/pageedit/edit-properties")
|
|
98
|
+
.send(
|
|
99
|
+
`name=new_page_with_html_file&title=foo&description=bar&min_role=100&html_file=${encodeURIComponent(
|
|
100
|
+
htmlFile.location
|
|
101
|
+
)}`
|
|
102
|
+
)
|
|
103
|
+
.set("Cookie", loginCookie)
|
|
104
|
+
.expect(toRedirect("/pageedit/"));
|
|
105
|
+
});
|
|
51
106
|
it("fills layout", async () => {
|
|
52
107
|
const app = await getApp({ disableCsrf: true });
|
|
53
108
|
const loginCookie = await getAdminLoginCookie();
|
|
@@ -68,6 +123,44 @@ describe("page create", () => {
|
|
|
68
123
|
.set("Cookie", loginCookie)
|
|
69
124
|
.expect(toInclude("Herman"));
|
|
70
125
|
});
|
|
126
|
+
|
|
127
|
+
it("shows page with html file", async () => {
|
|
128
|
+
const app = await getApp({ disableCsrf: true });
|
|
129
|
+
const loginCookie = await getAdminLoginCookie();
|
|
130
|
+
await request(app)
|
|
131
|
+
.get("/page/new_page_with_html_file")
|
|
132
|
+
.set("Cookie", loginCookie)
|
|
133
|
+
.expect(toInclude("Land here"));
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("does not find the html file for staff or public", async () => {
|
|
137
|
+
const app = await getApp({ disableCsrf: true });
|
|
138
|
+
const loginCookie = await getStaffLoginCookie();
|
|
139
|
+
await request(app)
|
|
140
|
+
.get("/page/new_page_with_html_file")
|
|
141
|
+
.set("Cookie", loginCookie)
|
|
142
|
+
.expect(toInclude("not found", 404));
|
|
143
|
+
await request(app)
|
|
144
|
+
.get("/page/new_page_with_html_file")
|
|
145
|
+
.expect(toInclude("not found", 404));
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("finds the html file for staff (after update)", async () => {
|
|
149
|
+
const app = await getApp({ disableCsrf: true });
|
|
150
|
+
await request(app)
|
|
151
|
+
.post("/files/setrole/fixed_page.html")
|
|
152
|
+
.set("Cookie", await getAdminLoginCookie())
|
|
153
|
+
.send("role=40")
|
|
154
|
+
.expect(toRedirect("/files?dir=."));
|
|
155
|
+
const loginCookie = await getStaffLoginCookie();
|
|
156
|
+
await request(app)
|
|
157
|
+
.get("/page/new_page_with_html_file")
|
|
158
|
+
.set("Cookie", loginCookie)
|
|
159
|
+
.expect(toInclude("Land here"));
|
|
160
|
+
await request(app)
|
|
161
|
+
.get("/page/new_page_with_html_file")
|
|
162
|
+
.expect(toInclude("not found", 404));
|
|
163
|
+
});
|
|
71
164
|
});
|
|
72
165
|
|
|
73
166
|
describe("page action", () => {
|