@saltcorn/server 0.7.3-beta.6 → 0.7.4-beta.0

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/routes/admin.js CHANGED
@@ -18,7 +18,13 @@ const { spawn } = require("child_process");
18
18
  const User = require("@saltcorn/data/models/user");
19
19
  const path = require("path");
20
20
  const { getAllTenants } = require("@saltcorn/admin-models/models/tenant");
21
- const { post_btn, renderForm, mkTable, link } = require("@saltcorn/markup");
21
+ const {
22
+ post_btn,
23
+ renderForm,
24
+ mkTable,
25
+ link,
26
+ localeDateTime,
27
+ } = require("@saltcorn/markup");
22
28
  const {
23
29
  div,
24
30
  a,
@@ -42,10 +48,11 @@ const {
42
48
  select,
43
49
  option,
44
50
  fieldset,
45
- legend,
46
51
  ul,
47
52
  li,
48
53
  ol,
54
+ script,
55
+ domReady,
49
56
  } = require("@saltcorn/markup/tags");
50
57
  const db = require("@saltcorn/data/db");
51
58
  const {
@@ -61,6 +68,7 @@ const {
61
68
  restore,
62
69
  auto_backup_now,
63
70
  } = require("@saltcorn/admin-models/models/backup");
71
+ const Snapshot = require("@saltcorn/admin-models/models/snapshot");
64
72
  const {
65
73
  runConfigurationCheck,
66
74
  } = require("@saltcorn/admin-models/models/config-check");
@@ -86,6 +94,8 @@ const {
86
94
  const moment = require("moment");
87
95
  const View = require("@saltcorn/data/models/view");
88
96
  const { getConfigFile } = require("@saltcorn/data/db/connect");
97
+ const os = require("os");
98
+ const Page = require("@saltcorn/data/models/page");
89
99
 
90
100
  /**
91
101
  * @type {object}
@@ -115,6 +125,7 @@ const site_id_form = (req) =>
115
125
  "page_custom_html",
116
126
  "development_mode",
117
127
  "log_sql",
128
+ "log_level",
118
129
  "plugins_store_endpoint",
119
130
  "packs_store_endpoint",
120
131
  ...(getConfigFile() ? ["multitenancy_enabled"] : []),
@@ -140,10 +151,6 @@ const email_form = async (req) => {
140
151
  ],
141
152
  action: "/admin/email",
142
153
  });
143
- form.submitButtonClass = "btn-outline-primary";
144
- form.submitLabel = req.__("Save");
145
- form.onChange =
146
- "remove_outline(this);$('#testemail').attr('href','#').removeClass('btn-primary').addClass('btn-outline-primary')";
147
154
  return form;
148
155
  };
149
156
 
@@ -214,8 +221,10 @@ router.post(
214
221
  flash_restart_if_required(form, req);
215
222
  await save_config_from_form(form);
216
223
 
217
- req.flash("success", req.__("Site identity settings updated"));
218
- res.redirect("/admin");
224
+ if (!req.xhr) {
225
+ req.flash("success", req.__("Site identity settings updated"));
226
+ res.redirect("/admin");
227
+ } else res.json({ success: "ok" });
219
228
  }
220
229
  })
221
230
  );
@@ -308,7 +317,8 @@ router.post(
308
317
  } else {
309
318
  await save_config_from_form(form);
310
319
  req.flash("success", req.__("Email settings updated"));
311
- res.redirect("/admin/email");
320
+ if (!req.xhr) res.redirect("/admin/email");
321
+ else res.json({ success: "ok" });
312
322
  }
313
323
  })
314
324
  );
@@ -335,6 +345,9 @@ router.get(
335
345
  backupForm.values.auto_backup_expire_days = getState().getConfig(
336
346
  "auto_backup_expire_days"
337
347
  );
348
+ const aSnapshotForm = snapshotForm(req);
349
+ aSnapshotForm.values.snapshots_enabled =
350
+ getState().getConfig("snapshots_enabled");
338
351
  const isRoot = db.getTenantSchema() === db.connectObj.default_schema;
339
352
 
340
353
  send_admin_page({
@@ -378,10 +391,31 @@ router.get(
378
391
  a(
379
392
  { href: "/admin/auto-backup-list" },
380
393
  "Restore/download automated backups »"
394
+ ),
395
+ script(
396
+ domReady(
397
+ `$('#btnBackupNow').prop('disabled', $('#inputauto_backup_frequency').val()==='Never');`
398
+ )
381
399
  )
382
400
  ),
383
401
  }
384
402
  : { type: "blank", contents: "" },
403
+ {
404
+ type: "card",
405
+ title: req.__("Snapshots"),
406
+ contents: div(
407
+ p(
408
+ i(
409
+ "Snapshots store your application structure and definition, without the table data. Individual views and pages can be restored from snapshots from the <a href='/viewedit'>view</a> or <a href='/pageedit'>pages</a> overviews (\"Restore\" from individual page or view dropdowns)."
410
+ )
411
+ ),
412
+ renderForm(aSnapshotForm, req.csrfToken()),
413
+ a(
414
+ { href: "/admin/snapshot-list" },
415
+ "List/download snapshots &raquo;"
416
+ )
417
+ ),
418
+ },
385
419
  ],
386
420
  },
387
421
  });
@@ -457,6 +491,103 @@ router.get(
457
491
  })
458
492
  );
459
493
 
494
+ router.get(
495
+ "/snapshot-list",
496
+ isAdmin,
497
+ error_catcher(async (req, res) => {
498
+ const snaps = await Snapshot.find();
499
+ send_admin_page({
500
+ res,
501
+ req,
502
+ active_sub: "Backup",
503
+ contents: {
504
+ above: [
505
+ {
506
+ type: "card",
507
+ title: req.__("Download snapshots"),
508
+ contents: div(
509
+ ul(
510
+ snaps.map((snap) =>
511
+ li(
512
+ a(
513
+ {
514
+ href: `/admin/snapshot-download/${encodeURIComponent(
515
+ snap.id
516
+ )}`,
517
+ target: "_blank",
518
+ },
519
+ `${localeDateTime(snap.created)} (${moment(
520
+ snap.created
521
+ ).fromNow()})`
522
+ )
523
+ )
524
+ )
525
+ )
526
+ ),
527
+ },
528
+ ],
529
+ },
530
+ });
531
+ })
532
+ );
533
+
534
+ router.get(
535
+ "/snapshot-download/:id",
536
+ isAdmin,
537
+ error_catcher(async (req, res) => {
538
+ const { id } = req.params;
539
+ const snap = await Snapshot.findOne({ id });
540
+ res.send(snap.pack);
541
+ })
542
+ );
543
+
544
+ router.get(
545
+ "/snapshot-restore/:type/:name",
546
+ isAdmin,
547
+ error_catcher(async (req, res) => {
548
+ const { type, name } = req.params;
549
+ const snaps = await Snapshot.entity_history(type, name);
550
+ res.send(
551
+ mkTable(
552
+ [
553
+ {
554
+ label: "When",
555
+ key: (r) =>
556
+ `${localeDateTime(r.created)} (${moment(r.created).fromNow()})`,
557
+ },
558
+
559
+ {
560
+ label: req.__("Restore"),
561
+ key: (r) =>
562
+ post_btn(
563
+ `/admin/snapshot-restore/${type}/${name}/${r.id}`,
564
+ req.__("Restore"),
565
+ req.csrfToken()
566
+ ),
567
+ },
568
+ ],
569
+ snaps
570
+ )
571
+ );
572
+ })
573
+ );
574
+
575
+ router.post(
576
+ "/snapshot-restore/:type/:name/:id",
577
+ isAdmin,
578
+ error_catcher(async (req, res) => {
579
+ const { type, name, id } = req.params;
580
+ const snap = await Snapshot.findOne({ id });
581
+ await snap.restore_entity(type, name);
582
+ req.flash(
583
+ "success",
584
+ `${type} ${name} restored to snapshot saved ${moment(
585
+ snap.created
586
+ ).fromNow()}`
587
+ );
588
+ res.redirect(`/${type}edit`);
589
+ })
590
+ );
460
591
  router.get(
461
592
  "/auto-backup-download/:filename",
462
593
  isAdmin,
@@ -483,9 +614,8 @@ router.get(
483
614
  const autoBackupForm = (req) =>
484
615
  new Form({
485
616
  action: "/admin/set-auto-backup",
486
- submitButtonClass: "btn-outline-primary",
487
- onChange: "remove_outline(this)",
488
- submitLabel: "Save settings",
617
+ onChange: `saveAndContinue(this);$('#btnBackupNow').prop('disabled', $('#inputauto_backup_frequency').val()==='Never');`,
618
+ noSubmitButton: true,
489
619
  additionalButtons: [
490
620
  {
491
621
  label: "Backup now",
@@ -534,6 +664,43 @@ const autoBackupForm = (req) =>
534
664
  ],
535
665
  });
536
666
 
667
+ const snapshotForm = (req) =>
668
+ new Form({
669
+ action: "/admin/set-snapshot",
670
+ onChange: `saveAndContinue(this);`,
671
+ noSubmitButton: true,
672
+ additionalButtons: [
673
+ {
674
+ label: "Snapshot now",
675
+ id: "btnSnapNow",
676
+ class: "btn btn-outline-secondary",
677
+ onclick: "ajax_post('/admin/snapshot-now')",
678
+ },
679
+ ],
680
+ fields: [
681
+ {
682
+ type: "Bool",
683
+ label: req.__("Periodic snapshots enabled"),
684
+ name: "snapshots_enabled",
685
+ sublabel: req.__(
686
+ "Snapshot will be made every hour if there are changes"
687
+ ),
688
+ },
689
+ ],
690
+ });
691
+ router.post(
692
+ "/set-snapshot",
693
+ isAdmin,
694
+ error_catcher(async (req, res) => {
695
+ const form = await snapshotForm(req);
696
+ form.validate(req.body);
697
+
698
+ await save_config_from_form(form);
699
+ req.flash("success", req.__("Snapshot settings updated"));
700
+ if (!req.xhr) res.redirect("/admin/backup");
701
+ else res.json({ success: "ok" });
702
+ })
703
+ );
537
704
  router.post(
538
705
  "/set-auto-backup",
539
706
  isAdmin,
@@ -554,7 +721,8 @@ router.post(
554
721
  } else {
555
722
  await save_config_from_form(form);
556
723
  req.flash("success", req.__("Backup settings updated"));
557
- res.redirect("/admin/backup");
724
+ if (!req.xhr) res.redirect("/admin/backup");
725
+ else res.json({ success: "ok" });
558
726
  }
559
727
  })
560
728
  );
@@ -572,6 +740,22 @@ router.post(
572
740
  })
573
741
  );
574
742
 
743
+ router.post(
744
+ "/snapshot-now",
745
+ isAdmin,
746
+ error_catcher(async (req, res) => {
747
+ try {
748
+ const taken = await Snapshot.take_if_changed();
749
+ if (taken) req.flash("success", req.__("Snapshot successful"));
750
+ else
751
+ req.flash("success", req.__("No changes detected, snapshot skipped"));
752
+ } catch (e) {
753
+ req.flash("error", e.message);
754
+ }
755
+ res.json({ reload_page: true });
756
+ })
757
+ );
758
+
575
759
  /**
576
760
  * @name get/system
577
761
  * @function
@@ -1057,12 +1241,39 @@ router.get(
1057
1241
  })
1058
1242
  );
1059
1243
 
1244
+ const dialogScript = `<script>
1245
+ function swapEntryInputs(activeTab, activeInput, disabledTab, disabledInput) {
1246
+ activeTab.addClass("active");
1247
+ activeInput.removeClass("d-none");
1248
+ activeInput.addClass("d-block");
1249
+ activeInput.attr("name", "entryPoint");
1250
+ disabledTab.removeClass("active");
1251
+ disabledInput.removeClass("d-block");
1252
+ disabledInput.addClass("d-none");
1253
+ disabledInput.removeAttr("name");
1254
+ }
1255
+
1256
+ function showEntrySelect(type) {
1257
+ const viewNavLin = $("#viewNavLinkID");
1258
+ const pageNavLink = $("#pageNavLinkID");
1259
+ const viewInp = $("#viewInputID");
1260
+ const pageInp = $("#pageInputID");
1261
+ if (type === "page") {
1262
+ swapEntryInputs(pageNavLink, pageInp, viewNavLin, viewInp);
1263
+ }
1264
+ else if (type === "view") {
1265
+ swapEntryInputs(viewNavLin, viewInp, pageNavLink, pageInp);
1266
+ }
1267
+ $("#entryPointTypeID").attr("value", type);
1268
+ }
1269
+ </script>`;
1270
+
1060
1271
  router.get(
1061
1272
  "/build-mobile-app",
1062
1273
  isAdmin,
1063
1274
  error_catcher(async (req, res) => {
1064
- const isRoot = db.getTenantSchema() === db.connectObj.default_schema;
1065
1275
  const views = await View.find();
1276
+ const pages = await Page.find();
1066
1277
  const execBuildMsg =
1067
1278
  "This is still under development and might run longer.";
1068
1279
 
@@ -1070,6 +1281,11 @@ router.get(
1070
1281
  res,
1071
1282
  req,
1072
1283
  active_sub: "Mobile app",
1284
+ headers: [
1285
+ {
1286
+ headerTag: dialogScript,
1287
+ },
1288
+ ],
1073
1289
  contents: {
1074
1290
  above: [
1075
1291
  {
@@ -1087,11 +1303,17 @@ router.get(
1087
1303
  name: "_csrf",
1088
1304
  value: req.csrfToken(),
1089
1305
  }),
1306
+ input({
1307
+ type: "hidden",
1308
+ name: "entryPointType",
1309
+ value: "view",
1310
+ id: "entryPointTypeID",
1311
+ }),
1090
1312
  div(
1091
1313
  { class: "container ps-2" },
1092
1314
  div(
1093
1315
  { class: "row pb-2" },
1094
- div({ class: "col-sm-4 fw-bold" }, "Entry view"),
1316
+ div({ class: "col-sm-4 fw-bold" }, "Entry point"),
1095
1317
  div({ class: "col-sm-4 fw-bold" }, "Platform"),
1096
1318
  div(
1097
1319
  {
@@ -1104,17 +1326,54 @@ router.get(
1104
1326
  { class: "row" },
1105
1327
  div(
1106
1328
  { class: "col-sm-4" },
1329
+ // 'view/page' tabs
1330
+ ul(
1331
+ { class: "nav nav-pills" },
1332
+ li(
1333
+ {
1334
+ class: "nav-item",
1335
+ onClick: "showEntrySelect('view')",
1336
+ },
1337
+ div(
1338
+ { class: "nav-link active", id: "viewNavLinkID" },
1339
+ "View"
1340
+ )
1341
+ ),
1342
+ li(
1343
+ {
1344
+ class: "nav-item",
1345
+ onClick: "showEntrySelect('page')",
1346
+ },
1347
+ div(
1348
+ { class: "nav-link", id: "pageNavLinkID" },
1349
+ "Page"
1350
+ )
1351
+ )
1352
+ ),
1353
+ // select entry-view
1107
1354
  select(
1108
1355
  {
1109
1356
  class: "form-control",
1110
- name: "entryView",
1111
- id: "entryViewInput",
1357
+ name: "entryPoint",
1358
+ id: "viewInputID",
1112
1359
  },
1113
1360
  views
1114
1361
  .map((view) =>
1115
1362
  option({ value: view.name }, view.name)
1116
1363
  )
1117
1364
  .join(",")
1365
+ ),
1366
+ // select entry-page
1367
+ select(
1368
+ {
1369
+ class: "form-control d-none",
1370
+ id: "pageInputID",
1371
+ },
1372
+ pages
1373
+ .map((page) =>
1374
+ option({ value: page.name }, page.name)
1375
+ )
1376
+ .join(",")
1118
1377
  )
1119
1378
  ),
1120
1379
  div(
@@ -1196,7 +1455,7 @@ router.get(
1196
1455
  class: "form-control",
1197
1456
  name: "serverURL",
1198
1457
  id: "serverURLInputId",
1199
- placeholder: "http://10.0.2.2:3000",
1458
+ placeholder: getState().getConfig("base_url") || "",
1200
1459
  })
1201
1460
  )
1202
1461
  )
@@ -1225,7 +1484,8 @@ router.post(
1225
1484
  isAdmin,
1226
1485
  error_catcher(async (req, res) => {
1227
1486
  let {
1228
- entryView,
1487
+ entryPoint,
1488
+ entryPointType,
1229
1489
  androidPlatform,
1230
1490
  iOSPlatform,
1231
1491
  useDocker,
@@ -1244,15 +1504,24 @@ router.post(
1244
1504
  return res.redirect("/admin/build-mobile-app");
1245
1505
  }
1246
1506
  if (appFile && !appFile.endsWith(".apk")) appFile = `${appFile}.apk`;
1507
+ if (!serverURL || serverURL.length == 0) {
1508
+ serverURL = getState().getConfig("base_url") || "";
1509
+ }
1510
+ if (!serverURL.startsWith("http")) {
1511
+ req.flash("error", req.__("Please enter a valid server URL."));
1512
+ return res.redirect("/admin/build-mobile-app");
1513
+ }
1247
1514
  const appOut = path.join(__dirname, "..", "mobile-app-out");
1248
1515
  const spawnParams = [
1249
1516
  "build-app",
1250
- "-v",
1251
- entryView,
1517
+ "-e",
1518
+ entryPoint,
1519
+ "-t",
1520
+ entryPointType,
1252
1521
  "-c",
1253
1522
  appOut,
1254
1523
  "-b",
1255
- "/tmp/mobile_app_build",
1524
+ `${os.userInfo().homedir}/mobile_app_build`,
1256
1525
  ];
1257
1526
  if (useDocker) spawnParams.push("-d");
1258
1527
  if (androidPlatform) spawnParams.push("-p", "android");
@@ -1260,7 +1529,7 @@ router.post(
1260
1529
  if (appFile) spawnParams.push("-a", appFile);
1261
1530
  if (serverURL) spawnParams.push("-s", serverURL);
1262
1531
  const child = spawn("saltcorn", spawnParams, {
1263
- stdio: ["ignore", "pipe", process.stderr],
1532
+ stdio: ["ignore", "pipe", "pipe"],
1264
1533
  cwd: ".",
1265
1534
  });
1266
1535
  const childOutputs = [];
@@ -1268,6 +1537,10 @@ router.post(
1268
1537
  // console.log(data.toString());
1269
1538
  childOutputs.push(data.toString());
1270
1539
  });
1540
+ child.stderr.on("data", (data) => {
1541
+ // console.log(data.toString());
1542
+ childOutputs.push(data.toString());
1543
+ });
1271
1544
  child.on("exit", async function (exitCode, signal) {
1272
1545
  if (exitCode === 0) {
1273
1546
  const file = await File.from_existing_file(
package/routes/api.js CHANGED
@@ -112,6 +112,13 @@ function accessAllowed(req, user, trigger) {
112
112
  return role <= trigger.min_role;
113
113
  }
114
114
 
115
+ const getFlashes = (req) =>
116
+ ["error", "success", "danger", "warning", "information"]
117
+ .map((type) => {
118
+ return { type, msg: req.flash(type) };
119
+ })
120
+ .filter((a) => a.msg && a.msg.length && a.msg.length > 0);
121
+
115
122
  router.post(
116
123
  "/viewQuery/:viewName/:queryName",
117
124
  error_catcher(async (req, res, next) => {
@@ -134,7 +141,7 @@ router.post(
134
141
  if (queries[queryName]) {
135
142
  const { args } = req.body;
136
143
  const resp = await queries[queryName](...args, true);
137
- res.json({ success: resp });
144
+ res.json({ success: resp, alerts: getFlashes(req) });
138
145
  } else {
139
146
  res.status(404).json({ error: req.__("Not found") });
140
147
  }
@@ -235,6 +242,7 @@ router.get(
235
242
  rows = await table.getJoinedRows(joinOpts);
236
243
  } else if (req_query && req_query !== {}) {
237
244
  const tbl_fields = await table.getFields();
245
+ readState(req_query, tbl_fields, req);
238
246
  const qstate = await stateFieldsToWhere({
239
247
  fields: tbl_fields,
240
248
  approximate: !!approximate,
@@ -73,8 +73,9 @@ const logSettingsForm = async (req) => {
73
73
  fields.push({
74
74
  name: w + "_channel",
75
75
  label: w + " channel",
76
- sublabel:
77
- req.__("Channels to create events for. Separate by comma; leave blank for all"),
76
+ sublabel: req.__(
77
+ "Channels to create events for. Separate by comma; leave blank for all"
78
+ ),
78
79
  type: "String",
79
80
  showIf: { [w]: true },
80
81
  });
@@ -82,8 +83,8 @@ const logSettingsForm = async (req) => {
82
83
  return new Form({
83
84
  action: "/eventlog/settings",
84
85
  blurb: req.__("Which events should be logged?"),
85
- submitButtonClass: "btn-outline-primary",
86
- onChange: "remove_outline(this)",
86
+ noSubmitButton: true,
87
+ onChange: "saveAndContinue(this)",
87
88
  fields,
88
89
  });
89
90
  };
@@ -169,23 +170,23 @@ router.get(
169
170
  * @returns {Form}
170
171
  */
171
172
  const customEventForm = async (req) => {
172
- return new Form({
173
- action: "/eventlog/custom/new",
174
- submitButtonClass: "btn-outline-primary",
175
- onChange: "remove_outline(this)",
176
- fields: [
177
- {
178
- name: "name",
179
- label: req.__("Event Name"),
180
- type: "String",
181
- },
182
- {
183
- name: "hasChannel",
184
- label: req.__("Has channels?"),
185
- type: "Bool",
186
- },
187
- ],
188
- });
173
+ return new Form({
174
+ action: "/eventlog/custom/new",
175
+ submitButtonClass: "btn-outline-primary",
176
+ onChange: "remove_outline(this)",
177
+ fields: [
178
+ {
179
+ name: "name",
180
+ label: req.__("Event Name"),
181
+ type: "String",
182
+ },
183
+ {
184
+ name: "hasChannel",
185
+ label: req.__("Has channels?"),
186
+ type: "Bool",
187
+ },
188
+ ],
189
+ });
189
190
  };
190
191
  /**
191
192
  * @name get/custom/new
@@ -297,7 +298,8 @@ router.post(
297
298
  } else {
298
299
  await getState().setConfig("event_log_settings", form.values);
299
300
 
300
- res.redirect(`/eventlog/settings`);
301
+ if (!req.xhr) res.redirect(`/eventlog/settings`);
302
+ else res.json({ success: "ok" });
301
303
  }
302
304
  })
303
305
  );
package/routes/files.js CHANGED
@@ -378,9 +378,6 @@ const storage_form = async (req) => {
378
378
  ],
379
379
  action: "/files/storage",
380
380
  });
381
- form.submitButtonClass = "btn-outline-primary";
382
- form.submitLabel = req.__("Save");
383
- form.onChange = "remove_outline(this)";
384
381
  return form;
385
382
  };
386
383
 
@@ -431,8 +428,11 @@ router.post(
431
428
  });
432
429
  } else {
433
430
  await save_config_from_form(form);
434
- req.flash("success", req.__("Storage settings updated"));
435
- res.redirect("/files/storage");
431
+
432
+ if (!req.xhr) {
433
+ req.flash("success", req.__("Storage settings updated"));
434
+ res.redirect("/files/storage");
435
+ } else res.json({ success: "ok" });
436
436
  }
437
437
  })
438
438
  );
@@ -48,8 +48,8 @@ router.get(
48
48
  const languageForm = (req) =>
49
49
  new Form({
50
50
  action: "/site-structure/localizer/save-lang",
51
- submitButtonClass: "btn-outline-primary",
52
- onChange: "remove_outline(this)",
51
+ onChange: "saveAndContinue(this)",
52
+ noSubmitButton: true,
53
53
  fields: [
54
54
  {
55
55
  name: "name",
@@ -270,7 +270,10 @@ router.post(
270
270
  ...cfgLangs,
271
271
  [lang.locale]: lang,
272
272
  });
273
- res.redirect(`/site-structure/localizer/edit/${lang.locale}`);
273
+
274
+ if (!req.xhr)
275
+ res.redirect(`/site-structure/localizer/edit/${lang.locale}`);
276
+ else res.json({ success: "ok" });
274
277
  }
275
278
  })
276
279
  );
package/routes/page.js CHANGED
@@ -36,6 +36,8 @@ router.get(
36
36
  "/:pagename",
37
37
  error_catcher(async (req, res) => {
38
38
  const { pagename } = req.params;
39
+ const state = getState();
40
+ state.log(3, `Route /page/${pagename} user=${req.user?.id}`);
39
41
 
40
42
  const role = req.user && req.user.id ? req.user.role_id : 10;
41
43
  const db_page = await Page.findOne({ name: pagename });
@@ -56,10 +58,12 @@ router.get(
56
58
  contents,
57
59
  })
58
60
  );
59
- } else
61
+ } else {
62
+ state.log(2, `Page $pagename} not found or not authorized`);
60
63
  res
61
64
  .status(404)
62
65
  .sendWrap(`${pagename} page`, req.__("Page %s not found", pagename));
66
+ }
63
67
  })
64
68
  );
65
69