@saltcorn/server 0.7.1-beta.3 → 0.7.2-beta.10

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.
@@ -1,4 +1,6 @@
1
1
  /**
2
+ * Default Home Page (Wellcome page)
3
+ * Opens for new site without any data
2
4
  * @category server
3
5
  * @module routes/homepage
4
6
  * @subcategory routes
@@ -10,11 +12,11 @@ const View = require("@saltcorn/data/models/view");
10
12
  const User = require("@saltcorn/data/models/user");
11
13
  const File = require("@saltcorn/data/models/file");
12
14
  const Page = require("@saltcorn/data/models/page");
13
- const { link, renderForm, mkTable, post_btn } = require("@saltcorn/markup");
14
- const { ul, li, div, small, a, h5, p, i } = require("@saltcorn/markup/tags");
15
+ const { link, mkTable } = require("@saltcorn/markup");
16
+ const { div, a, p, i } = require("@saltcorn/markup/tags");
15
17
  const Table = require("@saltcorn/data/models/table");
16
18
  const { fetch_available_packs } = require("@saltcorn/admin-models/models/pack");
17
- const { restore_backup } = require("../markup/admin");
19
+ // const { restore_backup } = require("../markup/admin");
18
20
  const { get_latest_npm_version } = require("@saltcorn/data/models/config");
19
21
  const packagejson = require("../package.json");
20
22
  const Trigger = require("@saltcorn/data/models/trigger");
@@ -22,6 +24,7 @@ const { fileUploadForm } = require("../markup/forms");
22
24
  const { get_base_url } = require("./utils.js");
23
25
 
24
26
  /**
27
+ * Tables List
25
28
  * @param {*} tables
26
29
  * @param {object} req
27
30
  * @returns {Table}
@@ -39,6 +42,7 @@ const tableTable = (tables, req) =>
39
42
  );
40
43
 
41
44
  /**
45
+ * Tables Card
42
46
  * @param {*} tables
43
47
  * @param {object} req
44
48
  * @returns {object}
@@ -68,6 +72,7 @@ const tableCard = (tables, req) => ({
68
72
  });
69
73
 
70
74
  /**
75
+ * Views List
71
76
  * @param {*} views
72
77
  * @param {object} req
73
78
  * @returns {Table}
@@ -89,6 +94,7 @@ const viewTable = (views, req) =>
89
94
  );
90
95
 
91
96
  /**
97
+ * Views Card
92
98
  * @param {*} views
93
99
  * @param {object} req
94
100
  * @returns {object}
@@ -120,6 +126,7 @@ const viewCard = (views, req) => ({
120
126
  });
121
127
 
122
128
  /**
129
+ * Pages List
123
130
  * @param {*} pages
124
131
  * @param {object} req
125
132
  * @returns {Table}
@@ -141,6 +148,7 @@ const pageTable = (pages, req) =>
141
148
  );
142
149
 
143
150
  /**
151
+ * Page Card
144
152
  * @param {*} pages
145
153
  * @param {object} req
146
154
  * @returns {object}
@@ -173,13 +181,14 @@ const pageCard = (pages, req) => ({
173
181
  });
174
182
 
175
183
  /**
184
+ * Files Tab
176
185
  * @param {object} req
177
186
  * @returns {Promise<div>}
178
187
  */
179
188
  const filesTab = async (req) => {
180
189
  const files = await File.find({}, { orderBy: "filename", cached: true });
181
190
  return div(
182
- files.length == 0
191
+ files.length === 0
183
192
  ? p(req.__("No files"))
184
193
  : mkTable(
185
194
  [
@@ -197,7 +206,10 @@ const filesTab = async (req) => {
197
206
  };
198
207
 
199
208
  /**
209
+ * Users Tab
200
210
  * @param {object} req
211
+ * @param users
212
+ * @param roleMap
201
213
  * @returns {Promise<div>}
202
214
  */
203
215
  const usersTab = async (req, users, roleMap) => {
@@ -221,7 +233,9 @@ const usersTab = async (req, users, roleMap) => {
221
233
  };
222
234
 
223
235
  /**
236
+ * Actions (Triggers) Tab
224
237
  * @param {object} req
238
+ * @param triggers
225
239
  * @returns {Promise<div>}
226
240
  */
227
241
  const actionsTab = async (req, triggers) => {
@@ -234,7 +248,7 @@ const actionsTab = async (req, triggers) => {
234
248
  { class: "mt-2 pe-2" },
235
249
  i(req.__("Triggers run actions in response to events."))
236
250
  ),
237
- triggers.length == 0
251
+ triggers.length === 0
238
252
  ? p(req.__("No triggers"))
239
253
  : mkTable(
240
254
  [
@@ -260,6 +274,12 @@ const actionsTab = async (req, triggers) => {
260
274
  )
261
275
  );
262
276
  };
277
+ /**
278
+ * Plugins and Packs Tab
279
+ * @param req
280
+ * @param packlist
281
+ * @returns {*}
282
+ */
263
283
  const packTab = (req, packlist) =>
264
284
  div(
265
285
  { class: "pb-3 pt-2 pe-4" },
@@ -286,7 +306,11 @@ const packTab = (req, packlist) =>
286
306
  req.__("Go to pack store »")
287
307
  )
288
308
  );
289
-
309
+ /**
310
+ * Help Card
311
+ * @param req
312
+ * @returns {*}
313
+ */
290
314
  const helpCard = (req) =>
291
315
  div(
292
316
  { class: "pb-3 pt-2 pe-4" },
@@ -321,6 +345,7 @@ const helpCard = (req) =>
321
345
  );
322
346
 
323
347
  /**
348
+ * Wellcome page
324
349
  * @param {object} req
325
350
  * @returns {Promise<object>}
326
351
  */
@@ -393,6 +418,7 @@ const welcome_page = async (req) => {
393
418
  };
394
419
 
395
420
  /**
421
+ * No Views logged in
396
422
  * @param {object} req
397
423
  * @param {object} res
398
424
  * @returns {Promise<void>}
@@ -423,6 +449,7 @@ const no_views_logged_in = async (req, res) => {
423
449
  };
424
450
 
425
451
  /**
452
+ * Get Config respounce
426
453
  * @param {number} role_id
427
454
  * @param {object} res
428
455
  * @param {object} req
@@ -430,6 +457,7 @@ const no_views_logged_in = async (req, res) => {
430
457
  */
431
458
  const get_config_response = async (role_id, res, req) => {
432
459
  const modernCfg = getState().getConfig("home_page_by_role", false);
460
+ // predefined roles
433
461
  const legacy_role = { 10: "public", 8: "user", 4: "staff", 1: "admin" }[
434
462
  role_id
435
463
  ];
@@ -472,7 +500,7 @@ module.exports =
472
500
  const hasUsers = await User.nonEmpty();
473
501
  if (!hasUsers) {
474
502
  res.redirect("/auth/create_first_user");
475
- return;
503
+ // return;
476
504
  } else res.redirect("/auth/login");
477
505
  } else {
478
506
  await no_views_logged_in(req, res);
package/routes/menu.js CHANGED
@@ -1,4 +1,5 @@
1
1
  /**
2
+ * Menu Editor
2
3
  * @category server
3
4
  * @module routes/menu
4
5
  * @subcategory routes
@@ -6,18 +7,18 @@
6
7
 
7
8
  const Router = require("express-promise-router");
8
9
 
9
- const Field = require("@saltcorn/data/models/field");
10
+ //const Field = require("@saltcorn/data/models/field");
10
11
  const Form = require("@saltcorn/data/models/form");
11
- const { isAdmin, setTenant, error_catcher } = require("./utils.js");
12
+ const { isAdmin, error_catcher } = require("./utils.js");
12
13
  const { getState } = require("@saltcorn/data/db/state");
13
- const File = require("@saltcorn/data/models/file");
14
+ //const File = require("@saltcorn/data/models/file");
14
15
  const User = require("@saltcorn/data/models/user");
15
16
  const View = require("@saltcorn/data/models/view");
16
17
  const Page = require("@saltcorn/data/models/page");
17
18
  const { save_menu_items } = require("@saltcorn/data/models/config");
18
19
  const db = require("@saltcorn/data/db");
19
20
 
20
- const { mkTable, renderForm, link, post_btn } = require("@saltcorn/markup");
21
+ const { renderForm } = require("@saltcorn/markup");
21
22
  const { script, domReady, div, ul } = require("@saltcorn/markup/tags");
22
23
  const { send_infoarch_page } = require("../markup/admin.js");
23
24
  const Table = require("@saltcorn/data/models/table");
@@ -33,7 +34,7 @@ const router = new Router();
33
34
  module.exports = router;
34
35
 
35
36
  /**
36
- *
37
+ * Menu Form
37
38
  * @param {object} req
38
39
  * @returns {Promise<Form>}
39
40
  */
@@ -246,9 +247,11 @@ const menuForm = async (req) => {
246
247
  //create -- new
247
248
 
248
249
  /**
250
+ * Menu Editor Script
249
251
  * @param {object[]} menu_items
250
252
  * @returns {string}
251
253
  */
254
+ // todo move to file the content of menuEditorScript
252
255
  const menuEditorScript = (menu_items) => `
253
256
  var iconPickerOptions = {searchText: "Search icon...", labelHeader: "{0}/{1}"};
254
257
  let lastState;
@@ -262,7 +265,7 @@ const menuEditorScript = (menu_items) => `
262
265
  }
263
266
  var sortableListOptions = {
264
267
  placeholderCss: {'background-color': "#cccccc"},
265
- onChange: ajax_save_menu,
268
+ onChange: ajax_save_menu
266
269
  };
267
270
  editor = new MenuEditor('myEditor',
268
271
  {
@@ -285,7 +288,7 @@ const menuEditorScript = (menu_items) => `
285
288
  editor.update();
286
289
  ajax_save_menu(true);
287
290
  location.reload();
288
- });
291
+ });
289
292
  // Calling the add method
290
293
  $('#btnAdd').click(function(){
291
294
  editor.add();
@@ -360,6 +363,7 @@ router.get(
360
363
  })
361
364
  );
362
365
  /**
366
+ * jQME to Menu
363
367
  * @param {object[]} menu_items
364
368
  * @returns {object[]}
365
369
  */
package/routes/plugins.js CHANGED
@@ -652,16 +652,16 @@ router.get(
652
652
  error_catcher(async (req, res) => {
653
653
  const { plugin } = req.params;
654
654
  const filepath = req.params[0];
655
+ const hasVersion = plugin.includes("@");
655
656
  const location =
656
- getState().plugin_locations[
657
- plugin.includes("@") ? plugin.split("@")[0] : plugin
658
- ];
657
+ getState().plugin_locations[hasVersion ? plugin.split("@")[0] : plugin];
659
658
  if (location) {
660
659
  const safeFile = path
661
660
  .normalize(filepath)
662
661
  .replace(/^(\.\.(\/|\\|$))+/, "");
663
662
  const fullpath = path.join(location, "public", safeFile);
664
- if (fs.existsSync(fullpath)) res.sendFile(fullpath, { maxAge: "1d" });
663
+ if (fs.existsSync(fullpath))
664
+ res.sendFile(fullpath, { maxAge: hasVersion ? "100d" : "1d" });
665
665
  else res.status(404).send(req.__("Not found"));
666
666
  } else {
667
667
  res.status(404).send(req.__("Not found"));
package/routes/tables.js CHANGED
@@ -1437,7 +1437,7 @@ router.post(
1437
1437
  res.redirect(`/table`);
1438
1438
  return;
1439
1439
  }
1440
-
1440
+ //intentionally omit await
1441
1441
  recalculate_for_stored(table);
1442
1442
 
1443
1443
  req.flash("success", req.__("Started recalculating stored fields"));
package/routes/tenant.js CHANGED
@@ -1,4 +1,5 @@
1
1
  /**
2
+ * Tenant(s) Route
2
3
  * @category server
3
4
  * @module routes/tenant
4
5
  * @subcategory routes
@@ -36,22 +37,23 @@ const {
36
37
  code,
37
38
  } = require("@saltcorn/markup/tags");
38
39
  const db = require("@saltcorn/data/db");
39
- const url = require("url");
40
+ //const url = require("url");
40
41
  const { loadAllPlugins, loadAndSaveNewPlugin } = require("../load_plugins");
41
42
  const { isAdmin, error_catcher } = require("./utils.js");
42
43
  const User = require("@saltcorn/data/models/user");
43
44
  const File = require("@saltcorn/data/models/file");
44
45
  const {
45
46
  send_infoarch_page,
46
- send_admin_page,
47
+ //send_admin_page,
47
48
  config_fields_form,
48
49
  save_config_from_form,
49
50
  } = require("../markup/admin.js");
50
51
  const { getConfig } = require("@saltcorn/data/models/config");
51
- const {
52
- create_backup,
53
- restore,
54
- } = require("@saltcorn/admin-models/models/backup");
52
+ // todo add button backup / restore for particular tenant (available in admin tenants screens)
53
+ //const {
54
+ // create_backup,
55
+ // restore,
56
+ //} = require("@saltcorn/admin-models/models/backup");
55
57
 
56
58
  /**
57
59
  * @type {object}
@@ -116,6 +118,7 @@ const is_ip_address = (hostname) => {
116
118
  };
117
119
 
118
120
  /**
121
+ * Create tenant screen runnning
119
122
  * @name get/create
120
123
  * @function
121
124
  * @memberof module:routes/tenant~tenantRouter
@@ -146,6 +149,7 @@ router.get(
146
149
  )
147
150
  );
148
151
  let create_tenant_warning = "";
152
+ // todo add custom create tenant warning message
149
153
  if (getState().getConfig("create_tenant_warning"))
150
154
  create_tenant_warning = div(
151
155
  {
@@ -200,9 +204,8 @@ const getNewURL = (req, subdomain) => {
200
204
  if (hosts.length > 1) ports = `:${hosts[1]}`;
201
205
  }
202
206
  const hostname = req.hostname;
203
- const newurl = `${req.protocol}://${subdomain}.${hostname}${ports}/`;
204
-
205
- return newurl;
207
+ // return newurl
208
+ return `${req.protocol}://${subdomain}.${hostname}${ports}/`;
206
209
  };
207
210
 
208
211
  /**
@@ -307,7 +310,7 @@ router.post(
307
310
  );
308
311
 
309
312
  /**
310
- * List tenants HTTP GET Web UI
313
+ * List tenants ( on /tenant/list)
311
314
  * @name get/list
312
315
  * @function
313
316
  * @memberof module:routes/tenant~tenantRouter
@@ -582,12 +582,15 @@ router.get(
582
582
  return;
583
583
  }
584
584
  const configFlow = await view.get_config_flow(req);
585
+ const hasConfig =
586
+ view.configuration && Object.keys(view.configuration).length > 0;
585
587
  const wfres = await configFlow.run(
586
588
  {
589
+ ...view.configuration,
590
+ id: hasConfig ? view.id : undefined,
587
591
  table_id: view.table_id,
588
592
  exttable_name: view.exttable_name,
589
593
  viewname: name,
590
- ...view.configuration,
591
594
  },
592
595
  req
593
596
  );
@@ -704,6 +707,48 @@ router.post(
704
707
  })
705
708
  );
706
709
 
710
+ /**
711
+ * @name post/saveconfig/:id
712
+ * @function
713
+ * @memberof module:routes/viewedit~vieweditRouter
714
+ * @function
715
+ */
716
+ router.post(
717
+ "/saveconfig/:viewname",
718
+ isAdmin,
719
+ error_catcher(async (req, res) => {
720
+ const { viewname } = req.params;
721
+
722
+ if (viewname && req.body) {
723
+ const view = await View.findOne({ name: viewname });
724
+ const configFlow = await view.get_config_flow(req);
725
+ const step = await configFlow.singleStepForm(req.body, req);
726
+ if (step?.renderForm) {
727
+ if (!step.renderForm.hasErrors) {
728
+ let newcfg;
729
+ if (step.contextField)
730
+ newcfg = {
731
+ ...view.configuration,
732
+ [step.contextField]: {
733
+ ...view.configuration?.[step.contextField],
734
+ ...step.renderForm.values,
735
+ },
736
+ };
737
+ else newcfg = { ...view.configuration, ...step.renderForm.values };
738
+ await View.update({ configuration: newcfg }, view.id);
739
+ res.json({ success: "ok" });
740
+ } else {
741
+ res.json({ error: step.renderForm.errorSummary });
742
+ }
743
+ } else {
744
+ res.json({ error: "no form" });
745
+ }
746
+ } else {
747
+ res.json({ error: "no view" });
748
+ }
749
+ })
750
+ );
751
+
707
752
  /**
708
753
  * @name post/setrole/:id
709
754
  * @function
@@ -730,3 +775,12 @@ router.post(
730
775
  res.redirect("/viewedit");
731
776
  })
732
777
  );
778
+
779
+ router.post(
780
+ "/test/inserter",
781
+ isAdmin,
782
+ error_catcher(async (req, res) => {
783
+ const view = await View.create(req.body);
784
+ res.json({ view });
785
+ })
786
+ );
package/serve.js CHANGED
@@ -43,6 +43,7 @@ const {
43
43
  eachTenant,
44
44
  getAllTenants,
45
45
  } = require("@saltcorn/admin-models/models/tenant");
46
+ const { auto_backup_now } = require("@saltcorn/admin-models/models/backup");
46
47
 
47
48
  // helpful https://gist.github.com/jpoehls/2232358
48
49
  /**
@@ -135,7 +136,13 @@ const onMessageFromWorker =
135
136
  //console.log("worker msg", typeof msg, msg);
136
137
  if (msg === "Start" && !masterState.started) {
137
138
  masterState.started = true;
138
- runScheduler({ port, watchReaper, disableScheduler, eachTenant });
139
+ runScheduler({
140
+ port,
141
+ watchReaper,
142
+ disableScheduler,
143
+ eachTenant,
144
+ auto_backup_now,
145
+ });
139
146
  require("./systemd")({ port });
140
147
  return true;
141
148
  } else if (msg === "RestartServer") {
@@ -261,6 +268,13 @@ module.exports =
261
268
  });
262
269
  } else {
263
270
  await nonGreenlockWorkerSetup(appargs, port);
271
+ runScheduler({
272
+ port,
273
+ watchReaper,
274
+ disableScheduler,
275
+ eachTenant,
276
+ auto_backup_now,
277
+ });
264
278
  }
265
279
  Trigger.emitEvent("Startup");
266
280
  } else {
@@ -12,6 +12,7 @@ const {
12
12
  respondJsonWith,
13
13
  } = require("../auth/testhelp");
14
14
  const db = require("@saltcorn/data/db");
15
+ const { sleep } = require("@saltcorn/data/tests/mocks");
15
16
  const fs = require("fs").promises;
16
17
  const File = require("@saltcorn/data/models/file");
17
18
  const User = require("@saltcorn/data/models/user");
@@ -30,7 +31,12 @@ beforeAll(async () => {
30
31
  4
31
32
  );
32
33
  });
33
- afterAll(db.close);
34
+
35
+ afterAll(async () => {
36
+ await sleep(200);
37
+ db.close();
38
+ });
39
+
34
40
  const adminPageContains = (specs) =>
35
41
  it("adminPageContains " + specs.map((s) => s[1]).join(","), async () => {
36
42
  const app = await getApp({ disableCsrf: true });
@@ -456,6 +462,71 @@ describe("actions", () => {
456
462
  .expect(toRedirect("/actions/"));
457
463
  });
458
464
  });
465
+ describe("localizer", () => {
466
+ itShouldRedirectUnauthToLogin("/site-structure/localizer");
467
+ itShouldRedirectUnauthToLogin("/site-structure/localizer/add-lang");
468
+ it("redirects site struct to menu", async () => {
469
+ const app = await getApp({ disableCsrf: true });
470
+ const loginCookie = await getAdminLoginCookie();
471
+ await request(app)
472
+ .get("/site-structure")
473
+ .set("Cookie", loginCookie)
474
+ .expect(toRedirect("/menu"));
475
+ });
476
+ it("shows languages", async () => {
477
+ const app = await getApp({ disableCsrf: true });
478
+ const loginCookie = await getAdminLoginCookie();
479
+ await request(app)
480
+ .get("/site-structure/localizer")
481
+ .set("Cookie", loginCookie)
482
+ .expect(toInclude("Languages"));
483
+ });
484
+ it("shows add language form", async () => {
485
+ const app = await getApp({ disableCsrf: true });
486
+ const loginCookie = await getAdminLoginCookie();
487
+ await request(app)
488
+ .get("/site-structure/localizer/add-lang")
489
+ .set("Cookie", loginCookie)
490
+ .expect(toInclude("Locale identifier short code"));
491
+ });
492
+ it("add language", async () => {
493
+ const app = await getApp({ disableCsrf: true });
494
+ const loginCookie = await getAdminLoginCookie();
495
+ await request(app)
496
+ .post("/site-structure/localizer/save-lang")
497
+ .set("Cookie", loginCookie)
498
+ .send("name=dansk")
499
+ .send("locale=da")
500
+ .expect(toRedirect("/site-structure/localizer/edit/da"));
501
+ });
502
+ it("shows new in languages", async () => {
503
+ const app = await getApp({ disableCsrf: true });
504
+ const loginCookie = await getAdminLoginCookie();
505
+ await request(app)
506
+ .get("/site-structure/localizer")
507
+ .set("Cookie", loginCookie)
508
+ .expect(toInclude("dansk"));
509
+ });
510
+
511
+ it("shows edit language form", async () => {
512
+ const app = await getApp({ disableCsrf: true });
513
+ const loginCookie = await getAdminLoginCookie();
514
+ await request(app)
515
+ .get("/site-structure/localizer/edit/da")
516
+ .set("Cookie", loginCookie)
517
+ .expect(toInclude("Hello world"));
518
+ });
519
+ it("set string language", async () => {
520
+ const app = await getApp({ disableCsrf: true });
521
+ const loginCookie = await getAdminLoginCookie();
522
+ await request(app)
523
+ .post("/site-structure/localizer/save-string/da/Hello%20world")
524
+ .set("Cookie", loginCookie)
525
+ .send("value=Hej+verden")
526
+ .expect(toRedirect("/site-structure/localizer/edit/da"));
527
+ });
528
+ });
529
+
459
530
  /**
460
531
  * Pages tests
461
532
  */
@@ -14,6 +14,7 @@ const load_script = (fnm) => {
14
14
  };
15
15
 
16
16
  load_script("jquery-3.6.0.min.js");
17
+ load_script("saltcorn-common.js");
17
18
  load_script("saltcorn.js");
18
19
 
19
20
  test("updateQueryStringParameter", () => {
@@ -90,7 +90,7 @@ describe("Plugin Endpoints", () => {
90
90
  .expect(toInclude("testfilecontents"));
91
91
  await request(app)
92
92
  .get(
93
- "/plugins/pubdeps/sbadmin2/startbootstrap-sb-admin-2-bs5/4.1.5-beta.0/css/sb-admin-2.min.css"
93
+ "/plugins/pubdeps/sbadmin2/startbootstrap-sb-admin-2-bs5/4.1.5-beta.4/css/sb-admin-2.min.css"
94
94
  )
95
95
  .expect(toInclude("Start Bootstrap"));
96
96
 
@@ -100,7 +100,7 @@ describe("Plugin Endpoints", () => {
100
100
  .expect(toRedirect("/plugins"));
101
101
  await request(app)
102
102
  .get(
103
- "/plugins/pubdeps/sbadmin2/startbootstrap-sb-admin-2-bs5/4.1.5-beta.0/css/sb-admin-2.min.css"
103
+ "/plugins/pubdeps/sbadmin2/startbootstrap-sb-admin-2-bs5/4.1.5-beta.4/css/sb-admin-2.min.css"
104
104
  )
105
105
  .expect(toInclude("Start Bootstrap"));
106
106
  });
@@ -82,7 +82,7 @@ describe("edit view", () => {
82
82
  .post("/view/authoredit")
83
83
  .send("author=Chekov")
84
84
 
85
- .expect(toRedirect("/"));
85
+ .expect(toRedirect("/view/authorlist"));
86
86
  });
87
87
  });
88
88