@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.
@@ -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.0",
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.0",
10
- "@saltcorn/builder": "0.9.1-beta.0",
11
- "@saltcorn/data": "0.9.1-beta.0",
12
- "@saltcorn/admin-models": "0.9.1-beta.0",
13
- "@saltcorn/filemanager": "0.9.1-beta.0",
14
- "@saltcorn/markup": "0.9.1-beta.0",
15
- "@saltcorn/sbadmin2": "0.9.1-beta.0",
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
- console.log({ type, current });
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" ? "number" : "text"
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}" value="${escapeHtml(current)}">
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
- var json_fk_opt;
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 rawVal = formDataArray.find((f) => f.name == opts.key).value;
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: {
@@ -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
- var errorArea = form.parent().find(".full-form-error");
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"),
@@ -344,7 +344,11 @@ const getPageList = (rows, roles, req, { tagId, domId, showList } = {}) => {
344
344
  },
345
345
  {
346
346
  label: req.__("Edit"),
347
- key: (r) => link(`/pageedit/edit/${r.name}`, req.__("Edit")),
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
  ? {
@@ -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
- res.sendWrap(
485
- {
486
- title: db_page.title,
487
- description: db_page.description,
488
- bodyClass: "page_" + db.sqlsanitize(homeCfg),
489
- },
490
- contents
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.sendWrap(
60
- {
61
- title,
62
- description: db_page.description,
63
- bodyClass: "page_" + db.sqlsanitize(pagename),
64
- no_menu: db_page.attributes?.no_menu,
65
- } || `${pagename} page`,
66
- add_edit_bar({
67
- role,
68
- title: db_page.name,
69
- what: req.__("Page"),
70
- url: `/pageedit/edit/${encodeURIComponent(db_page.name)}`,
71
- contents,
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)}`);
@@ -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
- res.redirect(addOnDoneRedirect(`/pageedit/edit/${pageRow.name}`, req));
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
  };
@@ -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", () => {