@saltcorn/server 0.7.4 → 0.8.0-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.
Files changed (50) hide show
  1. package/app.js +18 -11
  2. package/auth/admin.js +370 -120
  3. package/auth/roleadmin.js +5 -23
  4. package/auth/routes.js +40 -15
  5. package/locales/de.json +1049 -273
  6. package/locales/en.json +58 -3
  7. package/locales/es.json +134 -134
  8. package/locales/it.json +6 -1
  9. package/locales/ru.json +44 -7
  10. package/markup/admin.js +46 -42
  11. package/markup/forms.js +4 -3
  12. package/package.json +8 -7
  13. package/public/blockly.js +19 -31
  14. package/public/diagram_utils.js +530 -0
  15. package/public/gridedit.js +4 -1
  16. package/public/jquery-menu-editor.min.js +112 -112
  17. package/public/saltcorn-common.js +31 -8
  18. package/public/saltcorn.css +11 -0
  19. package/public/saltcorn.js +211 -70
  20. package/restart_watcher.js +1 -0
  21. package/routes/actions.js +6 -14
  22. package/routes/admin.js +229 -79
  23. package/routes/api.js +19 -2
  24. package/routes/common_lists.js +137 -134
  25. package/routes/delete.js +6 -5
  26. package/routes/diagram.js +43 -117
  27. package/routes/edit.js +5 -10
  28. package/routes/fields.js +63 -29
  29. package/routes/files.js +137 -101
  30. package/routes/homepage.js +2 -2
  31. package/routes/infoarch.js +2 -2
  32. package/routes/list.js +12 -13
  33. package/routes/page.js +16 -3
  34. package/routes/pageedit.js +13 -8
  35. package/routes/scapi.js +1 -1
  36. package/routes/search.js +1 -1
  37. package/routes/tables.js +9 -14
  38. package/routes/tag_entries.js +31 -10
  39. package/routes/tags.js +10 -10
  40. package/routes/tenant.js +114 -50
  41. package/routes/utils.js +12 -0
  42. package/routes/view.js +3 -4
  43. package/routes/viewedit.js +57 -55
  44. package/serve.js +5 -0
  45. package/tests/admin.test.js +6 -2
  46. package/tests/auth.test.js +20 -0
  47. package/tests/fields.test.js +1 -0
  48. package/tests/files.test.js +11 -20
  49. package/tests/tenant.test.js +12 -2
  50. package/tests/viewedit.test.js +15 -1
package/routes/tenant.js CHANGED
@@ -8,13 +8,14 @@
8
8
  const Router = require("express-promise-router");
9
9
  const Form = require("@saltcorn/data/models/form");
10
10
  const { getState, add_tenant } = require("@saltcorn/data/db/state");
11
- const { create_tenant } = require("@saltcorn/admin-models/models/tenant");
12
11
  const {
12
+ create_tenant,
13
13
  getAllTenants,
14
14
  domain_sanitize,
15
15
  deleteTenant,
16
16
  switchToTenant,
17
17
  insertTenant,
18
+ Tenant,
18
19
  } = require("@saltcorn/admin-models/models/tenant");
19
20
  const {
20
21
  renderForm,
@@ -24,7 +25,6 @@ const {
24
25
  } = require("@saltcorn/markup");
25
26
  const {
26
27
  div,
27
- nbsp,
28
28
  p,
29
29
  a,
30
30
  h4,
@@ -44,7 +44,6 @@ const User = require("@saltcorn/data/models/user");
44
44
  const File = require("@saltcorn/data/models/file");
45
45
  const {
46
46
  send_infoarch_page,
47
- //send_admin_page,
48
47
  config_fields_form,
49
48
  save_config_from_form,
50
49
  } = require("../markup/admin.js");
@@ -148,45 +147,58 @@ router.get(
148
147
  "You are trying to create a tenant while connecting via an IP address rather than a domain. This will probably not work."
149
148
  )
150
149
  );
151
- let create_tenant_warning = "";
152
- // todo add custom create tenant warning message
153
- if (getState().getConfig("create_tenant_warning"))
154
- create_tenant_warning = div(
155
- {
156
- class: "alert alert-warning alert-dismissible fade show mt-5",
157
- role: "alert",
158
- },
159
- h4(req.__("Warning")),
160
- p(
161
- req.__(
162
- "Hosting on this site is provided for free and with no guarantee of availability or security of your application. "
163
- ) +
164
- " " +
165
- req.__(
166
- "This facility is intended solely for you to evaluate the suitability of Saltcorn. "
167
- ) +
168
- " " +
169
- req.__(
170
- "If you would like to store private information that needs to be secure, please use self-hosted Saltcorn. "
171
- ) +
172
- " " +
173
- req.__(
174
- 'See <a href="https://github.com/saltcorn/saltcorn">GitHub repository</a> for instructions<p>'
175
- )
176
- )
150
+ let create_tenant_warning_text = "";
151
+ if (getState().getConfig("create_tenant_warning")) {
152
+ create_tenant_warning_text = getState().getConfig(
153
+ "create_tenant_warning_text"
177
154
  );
155
+ if (create_tenant_warning_text && create_tenant_warning_text.length > 0)
156
+ create_tenant_warning_text = div(
157
+ {
158
+ class: "alert alert-warning alert-dismissible fade show mt-5",
159
+ role: "alert",
160
+ },
161
+ h4(req.__("Warning")),
162
+ p(create_tenant_warning_text)
163
+ );
164
+ else
165
+ create_tenant_warning_text = div(
166
+ {
167
+ class: "alert alert-warning alert-dismissible fade show mt-5",
168
+ role: "alert",
169
+ },
170
+ h4(req.__("Warning")),
171
+ p(
172
+ req.__(
173
+ "Hosting on this site is provided for free and with no guarantee of availability or security of your application. "
174
+ ) +
175
+ " " +
176
+ req.__(
177
+ "This facility is intended solely for you to evaluate the suitability of Saltcorn. "
178
+ ) +
179
+ " " +
180
+ req.__(
181
+ "If you would like to store private information that needs to be secure, please use self-hosted Saltcorn. "
182
+ ) +
183
+ " " +
184
+ req.__(
185
+ 'See <a href="https://github.com/saltcorn/saltcorn">GitHub repository</a> for instructions<p>'
186
+ )
187
+ )
188
+ );
189
+ }
178
190
 
179
191
  res.sendWrap(
180
192
  req.__("Create application"),
181
- create_tenant_warning +
182
- renderForm(tenant_form(req), req.csrfToken()) +
183
- p(
184
- { class: "mt-2" },
185
- req.__("To login to a previously created application, go to: "),
186
- code(`${req.protocol}://`) +
187
- i(req.__("Application name")) +
188
- code("." + req.hostname)
189
- )
193
+ create_tenant_warning_text +
194
+ renderForm(tenant_form(req), req.csrfToken()) +
195
+ p(
196
+ { class: "mt-2" },
197
+ req.__("To login to a previously created application, go to: "),
198
+ code(`${req.protocol}://`) +
199
+ i(req.__("Application name")) +
200
+ code("." + req.hostname)
201
+ )
190
202
  );
191
203
  })
192
204
  );
@@ -246,6 +258,8 @@ router.post(
246
258
  else {
247
259
  // normalize domain name
248
260
  const subdomain = domain_sanitize(valres.success.subdomain);
261
+ // get description
262
+ const description = valres.success.description;
249
263
  // get list of tenants
250
264
  const allTens = await getAllTenants();
251
265
  if (allTens.includes(subdomain) || !subdomain) {
@@ -258,9 +272,23 @@ router.post(
258
272
  renderForm(form, req.csrfToken())
259
273
  );
260
274
  } else {
275
+ // tenant url
261
276
  const newurl = getNewURL(req, subdomain);
277
+ // tenant template
262
278
  const tenant_template = getState().getConfig("tenant_template");
263
- await switchToTenant(await insertTenant(subdomain), newurl);
279
+ // tenant creator
280
+ const user_email = req.user && req.user.email;
281
+ // switch to tenant
282
+ await switchToTenant(
283
+ await insertTenant(
284
+ subdomain,
285
+ user_email,
286
+ description,
287
+ tenant_template
288
+ ),
289
+ newurl
290
+ );
291
+ // add tenant to global state
264
292
  add_tenant(subdomain);
265
293
  await create_tenant({
266
294
  t: subdomain,
@@ -294,13 +322,13 @@ router.post(
294
322
  " " +
295
323
  hasTemplate
296
324
  ? req.__(
297
- 'Use this link: <a href="%s">%s</a> to revisit your application at any time.',
298
- newurl,
299
- newurl
300
- )
325
+ 'Use this link: <a href="%s">%s</a> to revisit your application at any time.',
326
+ newurl,
327
+ newurl
328
+ )
301
329
  : req.__(
302
- "Use this link to revisit your application at any time."
303
- )
330
+ "Use this link to revisit your application at any time."
331
+ )
304
332
  )
305
333
  )
306
334
  );
@@ -348,6 +376,15 @@ router.get(
348
376
  {
349
377
  label: req.__("Description"),
350
378
  key: (r) => text(r.description),
379
+ //blurb: req.__("Specify some description for tenant if need"),
380
+ },
381
+ {
382
+ label: req.__("Creator email"),
383
+ key: (r) => text(r.email),
384
+ },
385
+ {
386
+ label: req.__("Created"),
387
+ key: (r) => text(r.created),
351
388
  },
352
389
  {
353
390
  label: req.__("Information"),
@@ -387,6 +424,7 @@ const tenant_settings_form = (req) =>
387
424
  field_names: [
388
425
  "role_to_create_tenant",
389
426
  "create_tenant_warning",
427
+ "create_tenant_warning_text",
390
428
  "tenant_template",
391
429
  ],
392
430
  action: "/tenant/settings",
@@ -468,8 +506,17 @@ router.post(
468
506
  const get_tenant_info = async (subdomain) => {
469
507
  const saneDomain = domain_sanitize(subdomain);
470
508
 
509
+ let info = {};
510
+
511
+ // get tenant row
512
+ const ten = await Tenant.findOne({ subdomain: saneDomain });
513
+ if (ten) {
514
+ info.description = ten.description;
515
+ info.created = ten.created;
516
+ }
517
+
518
+ // get data from tenant schema
471
519
  return await db.runWithTenant(saneDomain, async () => {
472
- let info = {};
473
520
  // TBD fix the first user issue because not always firt user by id is creator of tenant
474
521
  const firstUser = await User.find({}, { orderBy: "id", limit: 1 });
475
522
  if (firstUser && firstUser.length > 0) {
@@ -497,7 +544,11 @@ const get_tenant_info = async (subdomain) => {
497
544
  info.nconfigs = await db.count("_sc_config");
498
545
  // plugins count
499
546
  info.nplugins = await db.count("_sc_plugins");
500
- // TBD decide Do we need count tenants, table constraints, migrations
547
+ // migration count
548
+ info.nmigrations = await db.count("_sc_migrations");
549
+ // library count
550
+ info.nlibrary = await db.count("_sc_library");
551
+ // TBD decide Do we need count tenants, table constraints
501
552
  // base url
502
553
  info.base_url = await getConfig("base_url");
503
554
  return info;
@@ -525,6 +576,7 @@ router.get(
525
576
  return;
526
577
  }
527
578
  const { subdomain } = req.params;
579
+ // get tenant info
528
580
  const info = await get_tenant_info(subdomain);
529
581
  // get list of files
530
582
  let files;
@@ -545,7 +597,7 @@ router.get(
545
597
  contents: [
546
598
  table(
547
599
  tr(
548
- th(req.__("E-mail")),
600
+ th(req.__("First user E-mail")),
549
601
  td(
550
602
  a(
551
603
  { href: "mailto:" + info.first_user_email },
@@ -616,8 +668,16 @@ router.get(
616
668
  label: req.__("Base URL"),
617
669
  type: "String",
618
670
  },
671
+ {
672
+ name: "description",
673
+ label: req.__("Description"),
674
+ type: "String",
675
+ },
619
676
  ],
620
- values: { base_url: info.base_url },
677
+ values: {
678
+ base_url: info.base_url,
679
+ description: info.description,
680
+ },
621
681
  }),
622
682
  req.csrfToken()
623
683
  ),
@@ -673,6 +733,10 @@ router.post(
673
733
  const { base_url } = req.body;
674
734
  const saneDomain = domain_sanitize(subdomain);
675
735
 
736
+ // save description
737
+ const { description } = req.body;
738
+ await Tenant.update(saneDomain, { description: description });
739
+
676
740
  await db.runWithTenant(saneDomain, async () => {
677
741
  await getState().setConfig("base_url", base_url);
678
742
  });
@@ -701,7 +765,7 @@ router.post(
701
765
  return;
702
766
  }
703
767
  const { sub } = req.params;
704
-
768
+ // todo warning before deletion
705
769
  await deleteTenant(sub);
706
770
  res.redirect(`/tenant/list`);
707
771
  })
package/routes/utils.js CHANGED
@@ -20,6 +20,8 @@ const { validateHeaderName, validateHeaderValue } = require("http");
20
20
  const Crash = require("@saltcorn/data/models/crash");
21
21
 
22
22
  /**
23
+ * Checks that user logged or not.
24
+ * If not shows than shows flash and redirects to login
23
25
  * @param {object} req
24
26
  * @param {object} res
25
27
  * @param {function} next
@@ -35,6 +37,8 @@ function loggedIn(req, res, next) {
35
37
  }
36
38
 
37
39
  /**
40
+ * Checks that user has admin role or not.
41
+ * If user hasn't admin role shows flash and redirects user to login or totp
38
42
  * @param {object} req
39
43
  * @param {object} res
40
44
  * @param {function} next
@@ -60,6 +64,7 @@ function isAdmin(req, res, next) {
60
64
  }
61
65
 
62
66
  /**
67
+ * Sets language for HTTP Request / HTTP Responce
63
68
  * @param {object} req
64
69
  * @param {object} res
65
70
  * @param {string} state
@@ -73,6 +78,7 @@ const setLanguage = (req, res, state) => {
73
78
  };
74
79
 
75
80
  /**
81
+ * Sets Custom HTTP headers using data from "custom_http_headers" config variable
76
82
  * @param {object} res
77
83
  * @param {string} state
78
84
  * @returns {void}
@@ -96,6 +102,7 @@ const set_custom_http_headers = (res, state) => {
96
102
  };
97
103
 
98
104
  /**
105
+ * Tries to recognize tenant from HTTP Request
99
106
  * @param {object} req
100
107
  * @returns {string}
101
108
  */
@@ -179,6 +186,7 @@ const setTenant = (req, res, next) => {
179
186
  };
180
187
 
181
188
  /**
189
+ * Injects hidden input "_csrf" for CSRF token
182
190
  * @param {object} req
183
191
  * @returns {input}
184
192
  */
@@ -190,6 +198,7 @@ const csrfField = (req) =>
190
198
  });
191
199
 
192
200
  /**
201
+ * Errors catcher
193
202
  * @param {function} fn
194
203
  * @returns {function}
195
204
  */
@@ -198,6 +207,7 @@ const error_catcher = (fn) => (request, response, next) => {
198
207
  };
199
208
 
200
209
  /**
210
+ * Scans for page title from contents
201
211
  * @param {string|object} contents
202
212
  * @param {string} viewname
203
213
  * @returns {string}
@@ -218,11 +228,13 @@ const scan_for_page_title = (contents, viewname) => {
218
228
  };
219
229
 
220
230
  /**
231
+ * Gets gir revision
221
232
  * @returns {string}
222
233
  */
223
234
  const getGitRevision = () => db.connectObj.git_commit;
224
235
 
225
236
  /**
237
+ * Gets session store
226
238
  * @returns {session|cookieSession}
227
239
  */
228
240
  const getSessionStore = () => {
package/routes/view.js CHANGED
@@ -8,10 +8,8 @@ const Router = require("express-promise-router");
8
8
 
9
9
  const View = require("@saltcorn/data/models/view");
10
10
  const Table = require("@saltcorn/data/models/table");
11
- const Page = require("@saltcorn/data/models/page");
12
11
 
13
- const { div, text, i, a } = require("@saltcorn/markup/tags");
14
- const { renderForm, link } = require("@saltcorn/markup");
12
+ const { text } = require("@saltcorn/markup/tags");
15
13
  const {
16
14
  isAdmin,
17
15
  error_catcher,
@@ -105,7 +103,8 @@ router.post(
105
103
  if (sf.required && !query[sf.name]) {
106
104
  if (!row) {
107
105
  if (!table)
108
- table = await Table.findOne(view.table_id || view.exttable_name);
106
+ // todo check after where change
107
+ table = await Table.findOne(view.table_id ? { id : view.table_id } : {name: view.exttable_name});
109
108
  row = await table.getRow({});
110
109
  }
111
110
  if (row) query[sf.name] = row[sf.name];
@@ -7,28 +7,8 @@
7
7
 
8
8
  const Router = require("express-promise-router");
9
9
 
10
- const {
11
- renderForm,
12
- mkTable,
13
- link,
14
- //post_btn,
15
- //post_delete_btn,
16
- post_dropdown_item,
17
- renderBuilder,
18
- settingsDropdown,
19
- alert
20
- } = require("@saltcorn/markup");
21
- const {
22
- //span,
23
- //h5,
24
- h4,
25
- //nbsp,
26
- p,
27
- a,
28
- div,
29
- //button,
30
- text,
31
- } = require("@saltcorn/markup/tags");
10
+ const { renderForm, renderBuilder, alert } = require("@saltcorn/markup");
11
+ const { p, a, div, script, text, domReady } = require("@saltcorn/markup/tags");
32
12
 
33
13
  const { getState } = require("@saltcorn/data/db/state");
34
14
  const { isAdmin, error_catcher, addOnDoneRedirect } = require("./utils.js");
@@ -40,7 +20,6 @@ const View = require("@saltcorn/data/models/view");
40
20
  const Workflow = require("@saltcorn/data/models/workflow");
41
21
  const User = require("@saltcorn/data/models/user");
42
22
  const Page = require("@saltcorn/data/models/page");
43
- const Tag = require("@saltcorn/data/models/tag");
44
23
  const db = require("@saltcorn/data/db");
45
24
 
46
25
  const { add_to_menu } = require("@saltcorn/admin-models/models/pack");
@@ -77,20 +56,27 @@ router.get(
77
56
 
78
57
  const viewMarkup = await viewsList(views, req);
79
58
  const tables = await Table.find();
80
- const viewAccessWarning = view => {
81
- const table = tables.find(t => t.name === view.table)
82
- if (!table) return false
83
- if (table.ownership_field_id || table.ownership_formula) return false
59
+ const viewAccessWarning = (view) => {
60
+ const table = tables.find((t) => t.name === view.table);
61
+ if (!table) return false;
62
+ if (table.ownership_field_id || table.ownership_formula) return false;
84
63
 
85
- return table.min_role_read < view.min_role
86
- }
87
- const hasAccessWarning = views.filter(viewAccessWarning)
88
- const accessWarning = hasAccessWarning.length > 0
89
- ? alert("danger", `<p>You have views with a role to access lower than the table role to read,
64
+ return table.min_role_read < view.min_role;
65
+ };
66
+ const hasAccessWarning = views.filter(viewAccessWarning);
67
+ const accessWarning =
68
+ hasAccessWarning.length > 0
69
+ ? alert(
70
+ "danger",
71
+ req.__(
72
+ `<p>You have views with a role to access lower than the table role to read,
90
73
  with no table ownership. In the next version of Saltcorn, this may cause a
91
74
  denial of access. Users will need to have table read access to any data displayed.</p>
92
- Views potentially affected: ${hasAccessWarning.map(v => v.name).join(", ")}`)
93
- : ''
75
+ Views potentially affected: %s`,
76
+ hasAccessWarning.map((v) => v.name).join(", ")
77
+ )
78
+ )
79
+ : "";
94
80
  res.sendWrap(req.__(`Views`), {
95
81
  above: [
96
82
  {
@@ -106,14 +92,14 @@ router.get(
106
92
  viewMarkup,
107
93
  tables.length > 0
108
94
  ? a(
109
- { href: `/viewedit/new`, class: "btn btn-primary" },
110
- req.__("Create view")
111
- )
112
- : p(
113
- req.__(
114
- "You must create at least one table before you can create views."
95
+ { href: `/viewedit/new`, class: "btn btn-primary" },
96
+ req.__("Create view")
115
97
  )
116
- ),
98
+ : p(
99
+ req.__(
100
+ "You must create at least one table before you can create views."
101
+ )
102
+ ),
117
103
  ],
118
104
  },
119
105
  ],
@@ -252,7 +238,7 @@ router.get(
252
238
  error_catcher(async (req, res) => {
253
239
  const { viewname } = req.params;
254
240
 
255
- var viewrow = await View.findOne({ name: viewname });
241
+ const viewrow = await View.findOne({ name: viewname });
256
242
  if (!viewrow) {
257
243
  req.flash("error", `View not found: ${text(viewname)}`);
258
244
  res.redirect("/viewedit");
@@ -380,8 +366,8 @@ router.post(
380
366
  sendForm(form);
381
367
  } else {
382
368
  const existing_view = await View.findOne({ name: result.success.name });
383
- if (typeof existing_view !== "undefined")
384
- if (req.body.id != existing_view.id) {
369
+ if (existing_view)
370
+ if (+req.body.id !== existing_view.id) {
385
371
  // may be need !== but doesnt work
386
372
  form.errors.name = req.__("A view with this name already exists");
387
373
  form.hasErrors = true;
@@ -402,7 +388,7 @@ router.post(
402
388
  const slug = slugOptions.find((so) => so.label === v.slug);
403
389
  v.slug = slug || null;
404
390
  }
405
- const table = await Table.findOne({ name: v.table_name });
391
+ //const table = await Table.findOne({ name: v.table_name });
406
392
  delete v.table_name;
407
393
  if (req.body.id) {
408
394
  await View.update(v, +req.body.id);
@@ -434,7 +420,7 @@ router.post(
434
420
  * @returns {void}
435
421
  */
436
422
  const respondWorkflow = (view, wf, wfres, req, res) => {
437
- const wrap = (contents, noCard) => ({
423
+ const wrap = (contents, noCard, previewURL) => ({
438
424
  above: [
439
425
  {
440
426
  type: "breadcrumbs",
@@ -450,6 +436,18 @@ const respondWorkflow = (view, wf, wfres, req, res) => {
450
436
  title: wfres.title,
451
437
  contents,
452
438
  },
439
+ ...(previewURL
440
+ ? [
441
+ {
442
+ type: "card",
443
+ title: req.__("Preview"),
444
+ contents: div(
445
+ { id: "viewcfg-preview", "data-preview-url": previewURL },
446
+ script(domReady(`updateViewPreview()`))
447
+ ),
448
+ },
449
+ ]
450
+ : []),
453
451
  ],
454
452
  });
455
453
  if (wfres.flash) req.flash(wfres.flash[0], wfres.flash[1]);
@@ -472,7 +470,11 @@ const respondWorkflow = (view, wf, wfres, req, res) => {
472
470
  },
473
471
  ],
474
472
  },
475
- wrap(renderForm(wfres.renderForm, req.csrfToken()))
473
+ wrap(
474
+ renderForm(wfres.renderForm, req.csrfToken()),
475
+ false,
476
+ wfres.previewURL
477
+ )
476
478
  );
477
479
  else if (wfres.renderBuilder) {
478
480
  wfres.renderBuilder.options.view_id = view.id;
@@ -686,14 +688,14 @@ router.post(
686
688
  const view = await View.findOne({ id });
687
689
  const roles = await User.get_roles();
688
690
  const roleRow = roles.find((r) => r.id === +role);
689
- if (roleRow && view)
690
- req.flash(
691
- "success",
692
- req.__(`Minimum role for %s updated to %s`, view.name, roleRow.role)
693
- );
694
- else req.flash("success", req.__(`Minimum role updated`));
695
-
696
- res.redirect("/viewedit");
691
+ const message =
692
+ roleRow && view
693
+ ? req.__(`Minimum role for %s updated to %s`, view.name, roleRow.role)
694
+ : req.__(`Minimum role updated`);
695
+ if (!req.xhr) {
696
+ req.flash("success", message);
697
+ res.redirect("/viewedit");
698
+ } else res.json({ okay: true, responseText: message });
697
699
  })
698
700
  );
699
701
 
package/serve.js CHANGED
@@ -99,11 +99,16 @@ const workerDispatchMsg = ({ tenant, ...msg }) => {
99
99
  db.runWithTenant(tenant, () => workerDispatchMsg(msg));
100
100
  return;
101
101
  }
102
+
102
103
  if (msg.refresh_plugin_cfg) {
103
104
  Plugin.findOne({ name: msg.refresh_plugin_cfg }).then((plugin) => {
104
105
  if (plugin) loadPlugin(plugin);
105
106
  });
106
107
  }
108
+ if (!getState()) {
109
+ console.error("no State for tenant", tenant)
110
+ return
111
+ }
107
112
  if (msg.refresh) getState()[`refresh_${msg.refresh}`](true);
108
113
  if (msg.createTenant) {
109
114
  const tenant_template = getState().getConfig("tenant_template");
@@ -1,13 +1,13 @@
1
1
  const request = require("supertest");
2
2
  const getApp = require("../app");
3
3
  const {
4
- getStaffLoginCookie,
4
+ //getStaffLoginCookie,
5
5
  getAdminLoginCookie,
6
6
  toRedirect,
7
7
  itShouldRedirectUnauthToLogin,
8
8
  toInclude,
9
9
  toSucceed,
10
- toNotInclude,
10
+ //toNotInclude,
11
11
  resetToFixtures,
12
12
  respondJsonWith,
13
13
  } = require("../auth/testhelp");
@@ -68,15 +68,19 @@ describe("admin page", () => {
68
68
  .expect(toInclude("Site identity settings"));
69
69
  });
70
70
  adminPageContains([
71
+ ["/admin", "Site identity"],
71
72
  ["/admin/backup", "Download a backup"],
72
73
  ["/admin/email", "Email settings"],
73
74
  ["/admin/system", "Restart server"],
75
+ ["/admin/dev", "Development"],
74
76
  ]);
75
77
  adminPageContains([
76
78
  ["/useradmin", "Create user"],
77
79
  ["/roleadmin", "Theme"],
78
80
  ["/useradmin/settings", "Authentication settings"],
79
81
  ["/useradmin/ssl", "HTTPS encryption"],
82
+ ["/useradmin/http", "HTTP settings"],
83
+ ["/useradmin/permissions", "Permissions settings"],
80
84
  ]);
81
85
  adminPageContains([
82
86
  ["/menu", "jquery-menu-editor"],
@@ -18,6 +18,7 @@ const { getState } = require("@saltcorn/data/db/state");
18
18
  const { get_reset_link, generate_email } = require("../auth/resetpw");
19
19
  const i18n = require("i18n");
20
20
  const path = require("path");
21
+ const fs = require("fs")
21
22
 
22
23
  afterAll(db.close);
23
24
  beforeAll(async () => {
@@ -602,3 +603,22 @@ describe("signup with custom login form", () => {
602
603
  expect(userrow.height).toBe(15);
603
604
  });
604
605
  });
606
+
607
+ describe("Locale files", () => {
608
+ it("should be valid JSON", async () => {
609
+
610
+ const localeFiles =
611
+ await fs.promises.readdir(path.join(__dirname, "..", "/locales"));
612
+ expect(localeFiles.length).toBeGreaterThan(3)
613
+ expect(localeFiles).toContain("en.json")
614
+ for (const fnm of localeFiles) {
615
+ const conts = await fs.promises.readFile(
616
+ path.join(__dirname, "..", "/locales", fnm)
617
+ )
618
+ expect(conts.length).toBeGreaterThan(1)
619
+
620
+ const j = JSON.parse(conts)
621
+ expect(Object.keys(j).length).toBeGreaterThan(1)
622
+ }
623
+ })
624
+ })
@@ -141,6 +141,7 @@ describe("Field Endpoints", () => {
141
141
  .send("stepName=Summary")
142
142
  .send("contextEnc=" + ctx)
143
143
  .send("summary_field=pages")
144
+ .send("on_delete=Fail")
144
145
  .set("Cookie", loginCookie)
145
146
  .expect(toRedirect("/table/2"));
146
147
  });