@saltcorn/server 0.9.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/auth/admin.js +2 -0
- package/auth/testhelp.js +13 -0
- package/help/API actions.tmd +12 -0
- package/help/Event types.tmd +71 -0
- package/locales/en.json +10 -1
- package/locales/fr.json +7 -1
- package/package.json +8 -8
- package/public/saltcorn-common.js +55 -38
- package/public/saltcorn.js +15 -10
- package/routes/actions.js +1 -0
- package/routes/api.js +2 -2
- package/routes/common_lists.js +16 -6
- package/routes/homepage.js +21 -14
- package/routes/page.js +19 -15
- package/routes/pageedit.js +51 -4
- package/routes/utils.js +36 -0
- package/tests/api.test.js +67 -0
- package/tests/page.test.js +94 -1
package/auth/admin.js
CHANGED
package/auth/testhelp.js
CHANGED
|
@@ -215,6 +215,18 @@ const succeedJsonWith = (pred) => (res) => {
|
|
|
215
215
|
}
|
|
216
216
|
};
|
|
217
217
|
|
|
218
|
+
const succeedJsonWithWholeBody = (pred) => (res) => {
|
|
219
|
+
if (res.statusCode !== 200) {
|
|
220
|
+
console.log(res.text);
|
|
221
|
+
throw new Error(`Expected status 200, received ${res.statusCode}`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (!pred(res.body)) {
|
|
225
|
+
console.log(res.body);
|
|
226
|
+
throw new Error(`Not satisfied`);
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
|
|
218
230
|
/**
|
|
219
231
|
*
|
|
220
232
|
* @param {number} code
|
|
@@ -260,6 +272,7 @@ module.exports = {
|
|
|
260
272
|
notAuthorized,
|
|
261
273
|
respondJsonWith,
|
|
262
274
|
toSucceedWithImage,
|
|
275
|
+
succeedJsonWithWholeBody,
|
|
263
276
|
resToLoginCookie,
|
|
264
277
|
itShouldIncludeTextForAdmin,
|
|
265
278
|
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
This trigger can be run with by making an HTTP request to
|
|
2
|
+
|
|
3
|
+
{{scState.getConfig("base_url","").replace(/\/$/, "")}}/api/action/{{ query.name }}
|
|
4
|
+
|
|
5
|
+
If you make a POST request, the POST body is expected to be JSON and its value
|
|
6
|
+
is accessible using the `row` variable (e.g. in a `run_js_code`)
|
|
7
|
+
|
|
8
|
+
If you make a GET request, the query string values
|
|
9
|
+
are accessible as a JSON object using the `row` variable (e.g. in a `run_js_code`).
|
|
10
|
+
|
|
11
|
+
If you return a value from the action, it will be available in the `data`
|
|
12
|
+
subfield of the JSON body in the HTTP response
|
|
@@ -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
|
@@ -1268,5 +1268,14 @@
|
|
|
1268
1268
|
"Pack file": "Pack file",
|
|
1269
1269
|
"Upload a pack file": "Upload a pack file",
|
|
1270
1270
|
"No menu": "No menu",
|
|
1271
|
-
"Omit the menu from this page": "Omit the menu from this page"
|
|
1271
|
+
"Omit the menu from this page": "Omit the menu from this page",
|
|
1272
|
+
"%s finished without a result": "%s finished without a result",
|
|
1273
|
+
"Body size limit (Kb)": "Body size limit (Kb)",
|
|
1274
|
+
"Maximum request body size in kilobytes": "Maximum request body size in kilobytes",
|
|
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",
|
|
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"
|
|
1272
1281
|
}
|
package/locales/fr.json
CHANGED
|
@@ -286,5 +286,11 @@
|
|
|
286
286
|
"Users and security": "Users and security",
|
|
287
287
|
"Site structure": "Site structure",
|
|
288
288
|
"Events": "Events",
|
|
289
|
-
"Are you sure?": "Are you sure?"
|
|
289
|
+
"Are you sure?": "Are you sure?",
|
|
290
|
+
"API token": "API token",
|
|
291
|
+
"No API token issued": "No API token issued",
|
|
292
|
+
"Generate": "Generate",
|
|
293
|
+
"Two-factor authentication": "Two-factor authentication",
|
|
294
|
+
"Two-factor authentication is disabled": "Two-factor authentication is disabled",
|
|
295
|
+
"Enable 2FA": "Enable 2FA"
|
|
290
296
|
}
|
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/server",
|
|
3
|
-
"version": "0.9.
|
|
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.
|
|
10
|
-
"@saltcorn/builder": "0.9.
|
|
11
|
-
"@saltcorn/data": "0.9.
|
|
12
|
-
"@saltcorn/admin-models": "0.9.
|
|
13
|
-
"@saltcorn/filemanager": "0.9.
|
|
14
|
-
"@saltcorn/markup": "0.9.
|
|
15
|
-
"@saltcorn/sbadmin2": "0.9.
|
|
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",
|
|
@@ -154,7 +154,8 @@ function apply_showif() {
|
|
|
154
154
|
e.empty();
|
|
155
155
|
e.prop("data-fetch-options-current-set", qs);
|
|
156
156
|
const toAppend = [];
|
|
157
|
-
if (!dynwhere.required)
|
|
157
|
+
if (!dynwhere.required)
|
|
158
|
+
toAppend.push({ label: dynwhere.neutral_label || "" });
|
|
158
159
|
let currentDataOption = undefined;
|
|
159
160
|
const dataOptions = [];
|
|
160
161
|
//console.log(success);
|
|
@@ -173,12 +174,28 @@ function apply_showif() {
|
|
|
173
174
|
const selected = `${current}` === `${r[dynwhere.refname]}`;
|
|
174
175
|
dataOptions.push({ text: label, value });
|
|
175
176
|
if (selected) currentDataOption = value;
|
|
176
|
-
|
|
177
|
-
selected ? "selected" : ""
|
|
178
|
-
} value="${value}">${label}</option>`;
|
|
179
|
-
toAppend.push(html);
|
|
177
|
+
toAppend.push({ selected, value, label });
|
|
180
178
|
});
|
|
181
|
-
|
|
179
|
+
toAppend.sort((a, b) =>
|
|
180
|
+
a.label === dynwhere.neutral_label
|
|
181
|
+
? -1
|
|
182
|
+
: b.label === dynwhere.neutral_label
|
|
183
|
+
? 1
|
|
184
|
+
: (a.label?.toLowerCase?.() || a.label) >
|
|
185
|
+
(b.label?.toLowerCase?.() || b.label)
|
|
186
|
+
? 1
|
|
187
|
+
: -1
|
|
188
|
+
);
|
|
189
|
+
e.html(
|
|
190
|
+
toAppend
|
|
191
|
+
.map(
|
|
192
|
+
({ label, value, selected }) =>
|
|
193
|
+
`<option${selected ? ` selected` : ""}${
|
|
194
|
+
value ? ` value="${value}"` : ""
|
|
195
|
+
}>${label || ""}</option>`
|
|
196
|
+
)
|
|
197
|
+
.join("")
|
|
198
|
+
);
|
|
182
199
|
|
|
183
200
|
//TODO: also sort inserted HTML options
|
|
184
201
|
dataOptions.sort((a, b) =>
|
|
@@ -568,8 +585,11 @@ function initialize_page() {
|
|
|
568
585
|
$(this).find("span.current time").attr("datetime"); // ||
|
|
569
586
|
//$(this).children("span.current").html();
|
|
570
587
|
}
|
|
571
|
-
|
|
588
|
+
if (type === "Bool") {
|
|
589
|
+
current = current === "true";
|
|
590
|
+
}
|
|
572
591
|
var is_key = type?.startsWith("Key:");
|
|
592
|
+
const resetHtml = this.outerHTML;
|
|
573
593
|
const opts = encodeURIComponent(
|
|
574
594
|
JSON.stringify({
|
|
575
595
|
url,
|
|
@@ -580,6 +600,7 @@ function initialize_page() {
|
|
|
580
600
|
type,
|
|
581
601
|
is_key,
|
|
582
602
|
schema,
|
|
603
|
+
resetHtml,
|
|
583
604
|
...(decimalPlaces ? { decimalPlaces } : {}),
|
|
584
605
|
})
|
|
585
606
|
);
|
|
@@ -632,7 +653,11 @@ function initialize_page() {
|
|
|
632
653
|
: ""
|
|
633
654
|
}
|
|
634
655
|
<input type="${
|
|
635
|
-
type === "Integer" || type === "Float"
|
|
656
|
+
type === "Integer" || type === "Float"
|
|
657
|
+
? "number"
|
|
658
|
+
: type === "Bool"
|
|
659
|
+
? "checkbox"
|
|
660
|
+
: "text"
|
|
636
661
|
}" ${
|
|
637
662
|
type === "Float"
|
|
638
663
|
? `step="${
|
|
@@ -643,7 +668,13 @@ function initialize_page() {
|
|
|
643
668
|
: "any"
|
|
644
669
|
}"`
|
|
645
670
|
: ""
|
|
646
|
-
} name="${key}"
|
|
671
|
+
} name="${key}" ${
|
|
672
|
+
type === "Bool"
|
|
673
|
+
? current
|
|
674
|
+
? "checked"
|
|
675
|
+
: ""
|
|
676
|
+
: `value="${escapeHtml(current)}"`
|
|
677
|
+
}>
|
|
647
678
|
<button type="submit" class="btn btn-sm btn-primary">OK</button>
|
|
648
679
|
<button onclick="cancel_inline_edit(event, '${opts}')" type="button" class="btn btn-sm btn-danger"><i class="fas fa-times"></i></button>
|
|
649
680
|
</form>`
|
|
@@ -760,33 +791,7 @@ function cancel_inline_edit(e, opts1) {
|
|
|
760
791
|
const isNode = typeof parent?.saltcorn?.data?.state === "undefined";
|
|
761
792
|
var opts = JSON.parse(decodeURIComponent(opts1 || "") || "{}");
|
|
762
793
|
var form = $(e.target).closest("form");
|
|
763
|
-
|
|
764
|
-
if (opts.schema) {
|
|
765
|
-
json_fk_opt = form.find(`option[value="${opts.current}"]`).text();
|
|
766
|
-
}
|
|
767
|
-
form.replaceWith(`<div
|
|
768
|
-
data-inline-edit-field="${opts.key}"
|
|
769
|
-
${opts.ajax ? `data-inline-edit-ajax="true"` : ""}
|
|
770
|
-
${opts.type ? `data-inline-edit-type="${opts.type}"` : ""}
|
|
771
|
-
${opts.current ? `data-inline-edit-current="${opts.current}"` : ""}
|
|
772
|
-
${
|
|
773
|
-
opts.current_label
|
|
774
|
-
? `data-inline-edit-current-label="${opts.current_label}"`
|
|
775
|
-
: ""
|
|
776
|
-
}
|
|
777
|
-
${
|
|
778
|
-
opts.schema
|
|
779
|
-
? `data-inline-edit-schema="${encodeURIComponent(
|
|
780
|
-
JSON.stringify(opts.schema)
|
|
781
|
-
)}"`
|
|
782
|
-
: ""
|
|
783
|
-
}
|
|
784
|
-
data-inline-edit-dest-url="${opts.url}">
|
|
785
|
-
<span class="current">${
|
|
786
|
-
json_fk_opt || opts.current_label || opts.current
|
|
787
|
-
}</span>
|
|
788
|
-
<i class="editicon ${!isNode ? "visible" : ""} fas fa-edit ms-1"></i>
|
|
789
|
-
</div>`);
|
|
794
|
+
form.replaceWith(opts.resetHtml);
|
|
790
795
|
initialize_page();
|
|
791
796
|
}
|
|
792
797
|
|
|
@@ -794,7 +799,8 @@ function inline_submit_success(e, form, opts) {
|
|
|
794
799
|
const isNode = typeof parent?.saltcorn?.data?.state === "undefined";
|
|
795
800
|
const formDataArray = form.serializeArray();
|
|
796
801
|
if (opts) {
|
|
797
|
-
let
|
|
802
|
+
let fdEntry = formDataArray.find((f) => f.name == opts.key);
|
|
803
|
+
let rawVal = opts.type === "Bool" ? !!fdEntry : fdEntry.value;
|
|
798
804
|
let val =
|
|
799
805
|
opts.is_key || (opts.schema && opts.schema.type.startsWith("Key to "))
|
|
800
806
|
? form.find("select").find("option:selected").text()
|
|
@@ -829,9 +835,13 @@ function inline_submit_success(e, form, opts) {
|
|
|
829
835
|
function inline_ajax_submit(e, opts1) {
|
|
830
836
|
var opts = JSON.parse(decodeURIComponent(opts1 || "") || "{}");
|
|
831
837
|
e.preventDefault();
|
|
838
|
+
|
|
832
839
|
var form = $(e.target).closest("form");
|
|
833
840
|
var form_data = form.serialize();
|
|
834
841
|
var url = form.attr("action");
|
|
842
|
+
if (opts.type === "Bool" && !form_data.includes(`${opts.key}=on`)) {
|
|
843
|
+
form_data += `&${opts.key}=off`;
|
|
844
|
+
}
|
|
835
845
|
$.ajax(url, {
|
|
836
846
|
type: "POST",
|
|
837
847
|
headers: {
|
|
@@ -1022,7 +1032,14 @@ function common_done(res, viewname, isWeb = true) {
|
|
|
1022
1032
|
if (res.notify) handle(res.notify, notifyAlert);
|
|
1023
1033
|
if (res.error)
|
|
1024
1034
|
handle(res.error, (text) => notifyAlert({ type: "danger", text: text }));
|
|
1025
|
-
|
|
1035
|
+
|
|
1036
|
+
if (res.eval_js && res.row && res.field_names) {
|
|
1037
|
+
const f = new Function(`row, {${res.field_names}}`, res.eval_js);
|
|
1038
|
+
const evalres = f(res.row, res.row);
|
|
1039
|
+
if (evalres) common_done(evalres, viewname, isWeb);
|
|
1040
|
+
} else if (res.eval_js) {
|
|
1041
|
+
handle(res.eval_js, eval);
|
|
1042
|
+
}
|
|
1026
1043
|
|
|
1027
1044
|
if (res.reload_page) {
|
|
1028
1045
|
(isWeb ? location : parent).reload(); //TODO notify to cookie if reload or goto
|
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();
|
|
@@ -572,6 +563,20 @@ function ajax_post_btn(e, reload_on_done, reload_delay) {
|
|
|
572
563
|
|
|
573
564
|
return false;
|
|
574
565
|
}
|
|
566
|
+
|
|
567
|
+
function api_action_call(name, body) {
|
|
568
|
+
$.ajax(`/api/action/${name}`, {
|
|
569
|
+
type: "POST",
|
|
570
|
+
headers: {
|
|
571
|
+
"CSRF-Token": _sc_globalCsrf,
|
|
572
|
+
},
|
|
573
|
+
data: body,
|
|
574
|
+
success: function (res) {
|
|
575
|
+
common_done(res.data);
|
|
576
|
+
},
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
|
|
575
580
|
function make_unique_field(
|
|
576
581
|
id,
|
|
577
582
|
table_id,
|
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/api.js
CHANGED
|
@@ -332,7 +332,7 @@ router.get(
|
|
|
332
332
|
* @function
|
|
333
333
|
* @memberof module:routes/api~apiRouter
|
|
334
334
|
*/
|
|
335
|
-
router.
|
|
335
|
+
router.all(
|
|
336
336
|
"/action/:actionname/",
|
|
337
337
|
error_catcher(async (req, res, next) => {
|
|
338
338
|
const { actionname } = req.params;
|
|
@@ -361,7 +361,7 @@ router.post(
|
|
|
361
361
|
const resp = await action.run({
|
|
362
362
|
configuration: trigger.configuration,
|
|
363
363
|
body: req.body,
|
|
364
|
-
row: req.body,
|
|
364
|
+
row: req.method === "GET" ? req.query : req.body,
|
|
365
365
|
req,
|
|
366
366
|
user: user || req.user,
|
|
367
367
|
});
|
package/routes/common_lists.js
CHANGED
|
@@ -9,7 +9,7 @@ const {
|
|
|
9
9
|
post_dropdown_item,
|
|
10
10
|
} = require("@saltcorn/markup");
|
|
11
11
|
const { get_base_url } = require("./utils.js");
|
|
12
|
-
const { h4, p, div, a, input, text } = require("@saltcorn/markup/tags");
|
|
12
|
+
const { h4, p, div, a, i, input, text } = require("@saltcorn/markup/tags");
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* @param {string} col
|
|
@@ -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
|
? {
|
|
@@ -385,10 +389,16 @@ const getTriggerList = (triggers, req, { tagId, domId, showList } = {}) => {
|
|
|
385
389
|
},
|
|
386
390
|
{
|
|
387
391
|
label: req.__("When"),
|
|
388
|
-
key: (
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
+
key: (act) =>
|
|
393
|
+
act.when_trigger +
|
|
394
|
+
(act.when_trigger === "API call"
|
|
395
|
+
? a(
|
|
396
|
+
{
|
|
397
|
+
href: `javascript:ajax_modal('/admin/help/API%20actions?name=${act.name}')`,
|
|
398
|
+
},
|
|
399
|
+
i({ class: "fas fa-question-circle ms-1" })
|
|
400
|
+
)
|
|
401
|
+
: ""),
|
|
392
402
|
},
|
|
393
403
|
{
|
|
394
404
|
label: req.__("Test run"),
|
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
|
|
@@ -262,10 +262,16 @@ const actionsTab = async (req, triggers) => {
|
|
|
262
262
|
},
|
|
263
263
|
{
|
|
264
264
|
label: req.__("When"),
|
|
265
|
-
key: (
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
265
|
+
key: (act) =>
|
|
266
|
+
act.when_trigger +
|
|
267
|
+
(act.when_trigger === "API call"
|
|
268
|
+
? a(
|
|
269
|
+
{
|
|
270
|
+
href: `javascript:ajax_modal('/admin/help/API%20actions?name=${act.name}')`,
|
|
271
|
+
},
|
|
272
|
+
i({ class: "fas fa-question-circle ms-1" })
|
|
273
|
+
)
|
|
274
|
+
: ""),
|
|
269
275
|
},
|
|
270
276
|
],
|
|
271
277
|
triggers
|
|
@@ -474,15 +480,16 @@ const get_config_response = async (role_id, res, req) => {
|
|
|
474
480
|
|
|
475
481
|
if (db_page) {
|
|
476
482
|
const contents = await db_page.run(req.query, { res, req });
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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
|
+
);
|
|
486
493
|
} else res.redirect(homeCfg);
|
|
487
494
|
return true;
|
|
488
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"),
|
|
@@ -289,7 +323,7 @@ const wrap = (contents, noCard, req, page) => ({
|
|
|
289
323
|
crumbs: [
|
|
290
324
|
{ text: req.__("Pages"), href: "/pageedit" },
|
|
291
325
|
page
|
|
292
|
-
? { href: `/
|
|
326
|
+
? { href: `/page/${page.name}`, text: page.name }
|
|
293
327
|
: { text: req.__("New") },
|
|
294
328
|
],
|
|
295
329
|
},
|
|
@@ -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/api.test.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
const request = require("supertest");
|
|
2
2
|
const getApp = require("../app");
|
|
3
3
|
const Table = require("@saltcorn/data/models/table");
|
|
4
|
+
const Trigger = require("@saltcorn/data/models/trigger");
|
|
5
|
+
|
|
6
|
+
const Field = require("@saltcorn/data/models/field");
|
|
4
7
|
const {
|
|
5
8
|
getStaffLoginCookie,
|
|
6
9
|
getAdminLoginCookie,
|
|
@@ -9,6 +12,7 @@ const {
|
|
|
9
12
|
succeedJsonWith,
|
|
10
13
|
notAuthorized,
|
|
11
14
|
toRedirect,
|
|
15
|
+
succeedJsonWithWholeBody,
|
|
12
16
|
} = require("../auth/testhelp");
|
|
13
17
|
const db = require("@saltcorn/data/db");
|
|
14
18
|
const User = require("@saltcorn/data/models/user");
|
|
@@ -322,3 +326,66 @@ describe("API authentication", () => {
|
|
|
322
326
|
.expect(succeedJsonWith((rows) => rows.length == 2));
|
|
323
327
|
});
|
|
324
328
|
});
|
|
329
|
+
|
|
330
|
+
describe("API action", () => {
|
|
331
|
+
it("should set up trigger", async () => {
|
|
332
|
+
const table = await Table.create("triggercounter");
|
|
333
|
+
await Field.create({
|
|
334
|
+
table,
|
|
335
|
+
name: "thing",
|
|
336
|
+
label: "TheThing",
|
|
337
|
+
type: "String",
|
|
338
|
+
});
|
|
339
|
+
await Trigger.create({
|
|
340
|
+
action: "run_js_code",
|
|
341
|
+
when_trigger: "API call",
|
|
342
|
+
name: "mywebhook",
|
|
343
|
+
min_role: 100,
|
|
344
|
+
configuration: {
|
|
345
|
+
code: `
|
|
346
|
+
const table = Table.findOne({ name: "triggercounter" });
|
|
347
|
+
await table.insertRow({ thing: row?.thing || "no body" });
|
|
348
|
+
return {studio: 54}
|
|
349
|
+
`,
|
|
350
|
+
},
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
it("should POST to trigger", async () => {
|
|
354
|
+
const app = await getApp({ disableCsrf: true });
|
|
355
|
+
await request(app)
|
|
356
|
+
.post("/api/action/mywebhook")
|
|
357
|
+
.send({
|
|
358
|
+
thing: "inthebody",
|
|
359
|
+
})
|
|
360
|
+
.set("Content-Type", "application/json")
|
|
361
|
+
.set("Accept", "application/json")
|
|
362
|
+
.expect(succeedJsonWithWholeBody((resp) => resp?.data?.studio === 54));
|
|
363
|
+
const table = Table.findOne({ name: "triggercounter" });
|
|
364
|
+
const counts = await table.getRows({});
|
|
365
|
+
expect(counts.map((c) => c.thing)).toContain("inthebody");
|
|
366
|
+
expect(counts.map((c) => c.thing)).not.toContain("no body");
|
|
367
|
+
});
|
|
368
|
+
it("should GET with query to trigger", async () => {
|
|
369
|
+
const app = await getApp({ disableCsrf: true });
|
|
370
|
+
await request(app)
|
|
371
|
+
.get("/api/action/mywebhook?thing=inthequery")
|
|
372
|
+
.set("Content-Type", "application/json")
|
|
373
|
+
.set("Accept", "application/json")
|
|
374
|
+
.expect(succeedJsonWithWholeBody((resp) => resp?.data?.studio === 54));
|
|
375
|
+
const table = Table.findOne({ name: "triggercounter" });
|
|
376
|
+
const counts = await table.getRows({});
|
|
377
|
+
expect(counts.map((c) => c.thing)).toContain("inthequery");
|
|
378
|
+
expect(counts.map((c) => c.thing)).not.toContain("no body");
|
|
379
|
+
});
|
|
380
|
+
it("should GET to trigger", async () => {
|
|
381
|
+
const app = await getApp({ disableCsrf: true });
|
|
382
|
+
await request(app)
|
|
383
|
+
.get("/api/action/mywebhook")
|
|
384
|
+
.set("Content-Type", "application/json")
|
|
385
|
+
.set("Accept", "application/json")
|
|
386
|
+
.expect(succeedJsonWithWholeBody((resp) => resp?.data?.studio === 54));
|
|
387
|
+
const table = Table.findOne({ name: "triggercounter" });
|
|
388
|
+
const counts = await table.getRows({});
|
|
389
|
+
expect(counts.map((c) => c.thing)).toContain("no body");
|
|
390
|
+
});
|
|
391
|
+
});
|
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", () => {
|