@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
@@ -8,7 +8,9 @@ const {
8
8
  settingsDropdown,
9
9
  post_dropdown_item,
10
10
  } = require("@saltcorn/markup");
11
-
11
+ const {
12
+ get_base_url,
13
+ } = require("./utils.js");
12
14
  const { h4, p, div, a, input, text } = require("@saltcorn/markup/tags");
13
15
 
14
16
  /**
@@ -46,52 +48,52 @@ const tablesList = async (tables, req, { tagId, domId, showList } = {}) => {
46
48
  const getRole = (rid) => roles.find((r) => r.id === rid).role;
47
49
  return tables.length > 0
48
50
  ? mkTable(
49
- [
50
- {
51
- label: req.__("Name"),
52
- key: (r) => link(`/table/${r.id || r.name}`, text(r.name)),
53
- },
54
- {
55
- label: "",
56
- key: (r) => tableBadges(r, req),
57
- },
58
- {
59
- label: req.__("Access Read/Write"),
60
- key: (t) =>
61
- t.external
62
- ? `${getRole(t.min_role_read)} (read only)`
63
- : `${getRole(t.min_role_read)}/${getRole(t.min_role_write)}`,
64
- },
65
- !tagId
66
- ? {
67
- label: req.__("Delete"),
68
- key: (r) =>
69
- r.name === "users" || r.external
70
- ? ""
71
- : post_delete_btn(`/table/delete/${r.id}`, req, r.name),
72
- }
73
- : {
74
- label: req.__("Remove From Tag"),
75
- key: (r) =>
76
- post_delete_btn(
77
- `/tag-entries/remove/tables/${r.id}/${tagId}`,
78
- req,
79
- `${r.name} from this tag`
80
- ),
81
- },
82
- ],
83
- tables,
51
+ [
84
52
  {
85
- hover: true,
86
- tableClass: listClass(tagId, showList),
87
- tableId: domId,
88
- }
89
- )
53
+ label: req.__("Name"),
54
+ key: (r) => link(`/table/${r.id || r.name}`, text(r.name)),
55
+ },
56
+ {
57
+ label: "",
58
+ key: (r) => tableBadges(r, req),
59
+ },
60
+ {
61
+ label: req.__("Access Read/Write"),
62
+ key: (t) =>
63
+ t.external
64
+ ? `${getRole(t.min_role_read)} (read only)`
65
+ : `${getRole(t.min_role_read)}/${getRole(t.min_role_write)}`,
66
+ },
67
+ !tagId
68
+ ? {
69
+ label: req.__("Delete"),
70
+ key: (r) =>
71
+ r.name === "users" || r.external
72
+ ? ""
73
+ : post_delete_btn(`/table/delete/${r.id}`, req, r.name),
74
+ }
75
+ : {
76
+ label: req.__("Remove From Tag"),
77
+ key: (r) =>
78
+ post_delete_btn(
79
+ `/tag-entries/remove/tables/${r.id}/${tagId}`,
80
+ req,
81
+ `${r.name} from this tag`
82
+ ),
83
+ },
84
+ ],
85
+ tables,
86
+ {
87
+ hover: true,
88
+ tableClass: listClass(tagId, showList),
89
+ tableId: domId,
90
+ }
91
+ )
90
92
  : div(
91
- { class: listClass(tagId, showList), id: domId },
92
- h4(req.__("No tables defined")),
93
- p(req.__("Tables hold collections of similar data"))
94
- );
93
+ { class: listClass(tagId, showList), id: domId },
94
+ h4(req.__("No tables defined")),
95
+ p(req.__("Tables hold collections of similar data"))
96
+ );
95
97
  };
96
98
 
97
99
  /**
@@ -173,78 +175,78 @@ const viewsList = async (views, req, { tagId, domId, showList } = {}) => {
173
175
 
174
176
  return views.length > 0
175
177
  ? mkTable(
176
- [
177
- {
178
- label: req.__("Name"),
179
- key: (r) => link(`/view/${encodeURIComponent(r.name)}`, r.name),
180
- sortlink: !tagId
181
- ? `javascript:set_state_field('_sortby', 'name')`
182
- : undefined,
183
- },
184
- // description - currently I dont want to show description in view list
185
- // because description can be long
186
- /*
187
- {
188
- label: req.__("Description"),
189
- key: "description",
190
- // this is sorting by column
191
- sortlink: `javascript:set_state_field('_sortby', 'description')`,
192
- },
193
- */
194
- // template
195
- {
196
- label: req.__("Pattern"),
197
- key: "viewtemplate",
198
- sortlink: !tagId
199
- ? `javascript:set_state_field('_sortby', 'viewtemplate')`
200
- : undefined,
201
- },
202
- {
203
- label: req.__("Table"),
204
- key: (r) => link(`/table/${r.table}`, r.table),
205
- sortlink: !tagId
206
- ? `javascript:set_state_field('_sortby', 'table')`
207
- : undefined,
208
- },
209
- {
210
- label: req.__("Role to access"),
211
- key: (row) => editViewRoleForm(row, roles, req),
212
- },
213
- {
178
+ [
179
+ {
180
+ label: req.__("Name"),
181
+ key: (r) => link(`/view/${encodeURIComponent(r.name)}`, r.name),
182
+ sortlink: !tagId
183
+ ? `javascript:set_state_field('_sortby', 'name')`
184
+ : undefined,
185
+ },
186
+ // description - currently I dont want to show description in view list
187
+ // because description can be long
188
+ /*
189
+ {
190
+ label: req.__("Description"),
191
+ key: "description",
192
+ // this is sorting by column
193
+ sortlink: `javascript:set_state_field('_sortby', 'description')`,
194
+ },
195
+ */
196
+ // template
197
+ {
198
+ label: req.__("Pattern"),
199
+ key: "viewtemplate",
200
+ sortlink: !tagId
201
+ ? `javascript:set_state_field('_sortby', 'viewtemplate')`
202
+ : undefined,
203
+ },
204
+ {
205
+ label: req.__("Table"),
206
+ key: (r) => link(`/table/${r.table}`, r.table),
207
+ sortlink: !tagId
208
+ ? `javascript:set_state_field('_sortby', 'table')`
209
+ : undefined,
210
+ },
211
+ {
212
+ label: req.__("Role to access"),
213
+ key: (row) => editViewRoleForm(row, roles, req),
214
+ },
215
+ {
216
+ label: "",
217
+ key: (r) =>
218
+ link(
219
+ `/viewedit/config/${encodeURIComponent(r.name)}`,
220
+ req.__("Configure")
221
+ ),
222
+ },
223
+ !tagId
224
+ ? {
214
225
  label: "",
226
+ key: (r) => view_dropdown(r, req),
227
+ }
228
+ : {
229
+ label: req.__("Remove From Tag"),
215
230
  key: (r) =>
216
- link(
217
- `/viewedit/config/${encodeURIComponent(r.name)}`,
218
- req.__("Configure")
231
+ post_delete_btn(
232
+ `/tag-entries/remove/views/${r.id}/${tagId}`,
233
+ req,
234
+ `${r.name} from this tag`
219
235
  ),
220
236
  },
221
- !tagId
222
- ? {
223
- label: "",
224
- key: (r) => view_dropdown(r, req),
225
- }
226
- : {
227
- label: req.__("Remove From Tag"),
228
- key: (r) =>
229
- post_delete_btn(
230
- `/tag-entries/remove/views/${r.id}/${tagId}`,
231
- req,
232
- `${r.name} from this tag`
233
- ),
234
- },
235
- ],
236
- views,
237
- {
238
- hover: true,
239
- tableClass: listClass(tagId, showList),
240
- tableId: domId,
241
- }
242
- )
237
+ ],
238
+ views,
239
+ {
240
+ hover: true,
241
+ tableClass: listClass(tagId, showList),
242
+ tableId: domId,
243
+ }
244
+ )
243
245
  : div(
244
- { class: listClass(tagId, showList), id: domId },
245
- h4(req.__("No views defined")),
246
- p(req.__("Views define how table rows are displayed to the user"))
247
- );
246
+ { class: listClass(tagId, showList), id: domId },
247
+ h4(req.__("No views defined")),
248
+ p(req.__("Views define how table rows are displayed to the user"))
249
+ );
248
250
  };
249
251
 
250
252
  /**
@@ -332,18 +334,18 @@ const getPageList = (rows, roles, req, { tagId, domId, showList } = {}) => {
332
334
  },
333
335
  !tagId
334
336
  ? {
335
- label: "",
336
- key: (r) => page_dropdown(r, req),
337
- }
337
+ label: "",
338
+ key: (r) => page_dropdown(r, req),
339
+ }
338
340
  : {
339
- label: req.__("Remove From Tag"),
340
- key: (r) =>
341
- post_delete_btn(
342
- `/tag-entries/remove/pages/${r.id}/${tagId}`,
343
- req,
344
- `${r.name} from this tag`
345
- ),
346
- },
341
+ label: req.__("Remove From Tag"),
342
+ key: (r) =>
343
+ post_delete_btn(
344
+ `/tag-entries/remove/pages/${r.id}/${tagId}`,
345
+ req,
346
+ `${r.name} from this tag`
347
+ ),
348
+ },
347
349
  ,
348
350
  ],
349
351
  rows,
@@ -356,6 +358,7 @@ const getPageList = (rows, roles, req, { tagId, domId, showList } = {}) => {
356
358
  };
357
359
 
358
360
  const getTriggerList = (triggers, req, { tagId, domId, showList } = {}) => {
361
+ const base_url = get_base_url(req);
359
362
  return mkTable(
360
363
  [
361
364
  { label: req.__("Name"), key: "name" },
@@ -388,18 +391,18 @@ const getTriggerList = (triggers, req, { tagId, domId, showList } = {}) => {
388
391
  },
389
392
  !tagId
390
393
  ? {
391
- label: req.__("Delete"),
392
- key: (r) => post_delete_btn(`/actions/delete/${r.id}`, req),
393
- }
394
+ label: req.__("Delete"),
395
+ key: (r) => post_delete_btn(`/actions/delete/${r.id}`, req),
396
+ }
394
397
  : {
395
- label: req.__("Remove From Tag"),
396
- key: (r) =>
397
- post_delete_btn(
398
- `/tag-entries/remove/trigger/${r.id}/${tagId}`,
399
- req,
400
- `${r.name} from this tag`
401
- ),
402
- },
398
+ label: req.__("Remove From Tag"),
399
+ key: (r) =>
400
+ post_delete_btn(
401
+ `/tag-entries/remove/trigger/${r.id}/${tagId}`,
402
+ req,
403
+ `${r.name} from this tag`
404
+ ),
405
+ },
403
406
  ],
404
407
  triggers,
405
408
  {
package/routes/delete.js CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  const Router = require("express-promise-router");
8
8
 
9
- const { loggedIn, error_catcher } = require("./utils.js");
9
+ const { error_catcher } = require("./utils.js");
10
10
  const Table = require("@saltcorn/data/models/table");
11
11
 
12
12
  /**
@@ -28,17 +28,18 @@ module.exports = router;
28
28
  * @function
29
29
  */
30
30
  router.post(
31
- "/:name/:id",
31
+ "/:tableName/:id",
32
32
  error_catcher(async (req, res) => {
33
- const { name, id } = req.params;
33
+ const { tableName, id } = req.params;
34
34
  const { redirect } = req.query;
35
- const table = await Table.findOne({ name });
35
+ // todo check that works after where change
36
+ const table = await Table.findOne({ name : tableName });
36
37
  const role = req.user && req.user.id ? req.user.role_id : 10;
37
38
  try {
38
39
  if (role <= table.min_role_write) await table.deleteRows({ id });
39
40
  else if (table.ownership_field_id && req.user) {
40
41
  const row = await table.getRow({ id });
41
- if (row && (await table.is_owner(req.user, row)))
42
+ if (row && (table.is_owner(req.user, row)))
42
43
  await table.deleteRows({ id });
43
44
  else req.flash("error", req.__("Not authorized"));
44
45
  } else
package/routes/diagram.js CHANGED
@@ -21,107 +21,27 @@ const { send_infoarch_page } = require("../markup/admin");
21
21
  const { isAdmin, error_catcher } = require("./utils.js");
22
22
  const Tag = require("@saltcorn/data/models/tag");
23
23
  const Router = require("express-promise-router");
24
+ const User = require("@saltcorn/data/models/user");
24
25
 
25
26
  const router = new Router();
26
27
  module.exports = router;
27
28
 
28
- function reloadCy() {
29
- $.ajax("/diagram/data", {
30
- dataType: "json",
31
- type: "GET",
32
- headers: { "CSRF-Token": _sc_globalCsrf },
33
- data: !tagFilterEnabled ? entityFilter : { ...entityFilter, tagFilterIds },
34
- }).done((res) => {
35
- const cfg = {
36
- container: document.getElementById("cy"),
37
- ...res,
38
- };
39
- window.cy = cytoscape(cfg);
40
- });
41
- }
42
-
43
- function toggleEntityFilter(type) {
44
- switch (type) {
45
- case "views": {
46
- entityFilter.showViews = !entityFilter.showViews;
47
- break;
48
- }
49
- case "pages": {
50
- entityFilter.showPages = !entityFilter.showPages;
51
- break;
52
- }
53
- case "tables": {
54
- entityFilter.showTables = !entityFilter.showTables;
55
- break;
56
- }
57
- case "trigger": {
58
- entityFilter.showTrigger = !entityFilter.showTrigger;
59
- break;
60
- }
61
- }
62
- }
63
-
64
- function toggleTagFilter(id) {
65
- if (!tagFilterEnabled) enableTagFilter();
66
- const index = tagFilterIds.indexOf(id);
67
- if (index > -1) {
68
- tagFilterIds.splice(index, 1);
69
- } else {
70
- tagFilterIds.push(id);
71
- }
72
- }
73
-
74
- function enableTagFilter() {
75
- tagFilterEnabled = true;
76
- for (const node of document.querySelectorAll('[id^="tagFilter_box_"]')) {
77
- node.style = "";
78
- }
79
- for (const node of document.querySelectorAll('[id^="tagFilter_label_"]')) {
80
- node.style = "";
81
- }
82
- const box = document.getElementById("noTagsId");
83
- box.checked = false;
84
- }
85
-
86
- function toggleTagFilterMode() {
87
- if (tagFilterEnabled) {
88
- tagFilterEnabled = false;
89
- for (const node of document.querySelectorAll('[id^="tagFilter_box_"]')) {
90
- node.style = "opacity: 0.5;";
91
- }
92
- for (const node of document.querySelectorAll('[id^="tagFilter_label_"]')) {
93
- node.style = "opacity: 0.5;";
94
- }
95
- } else {
96
- enableTagFilter();
97
- }
98
- }
99
-
100
- const buildScript = () => {
101
- return `const entityFilter = {
102
- showViews: true,
103
- showPages: true,
104
- showTables: true,
105
- showTrigger: true,
106
- };
107
- const tagFilterIds = [];
108
- let tagFilterEnabled = false;
109
- ${reloadCy.toString()}
110
- ${toggleTagFilterMode.toString()}
111
- ${enableTagFilter.toString()}
112
- ${toggleEntityFilter.toString()}
113
- ${toggleTagFilter.toString()}`;
29
+ const buildGlobalVars = (tags, roles) => {
30
+ return `
31
+ const allTags = ${JSON.stringify(tags)};
32
+ const roles = ${JSON.stringify(roles)};
33
+ `;
114
34
  };
115
35
 
116
36
  const findEntryPages = async () => {
117
37
  const modernCfg = getState().getConfig("home_page_by_role");
118
- let pages = null;
38
+ let pages;
119
39
  if (modernCfg) {
120
40
  pages = Object.values(modernCfg)
121
41
  .filter((val) => val)
122
42
  .map((val) => Page.findOne({ name: val }));
123
43
  } else {
124
- pages = new Array();
44
+ pages = [];
125
45
  for (const legacyRole of ["public", "user", "staff", "admin"]) {
126
46
  const page = await Page.findOne({ name: `${legacyRole}_home` });
127
47
  if (page) pages.push(page);
@@ -169,6 +89,7 @@ router.get(
169
89
  };
170
90
  const initialCyCode = generateCyCode(await buildObjectTrees(extractOpts));
171
91
  const tags = await Tag.find();
92
+ const roles = await User.get_roles();
172
93
  send_infoarch_page({
173
94
  res,
174
95
  req,
@@ -197,11 +118,6 @@ router.get(
197
118
  {
198
119
  class: "dropdown-menu",
199
120
  },
200
- input({
201
- type: "hidden",
202
- name: "_csrf",
203
- value: req.csrfToken(),
204
- }),
205
121
  // New View
206
122
  div(
207
123
  { class: "m-3" },
@@ -252,23 +168,18 @@ router.get(
252
168
  "data-bs-toggle": "dropdown",
253
169
  "aria-expanded": false,
254
170
  },
255
- "All entities"
171
+ req.__("All entities")
256
172
  ),
257
173
  div(
258
174
  {
259
175
  class: "dropdown-menu",
260
176
  },
261
- input({
262
- type: "hidden",
263
- name: "_csrf",
264
- value: req.csrfToken(),
265
- }),
266
177
  // Views checkbox
267
178
  div(
268
179
  { class: "m-3 form-check" },
269
180
  label(
270
181
  { class: "form-check-label", for: "showViewsId" },
271
- "Views"
182
+ req.__("Views")
272
183
  ),
273
184
  input({
274
185
  type: "checkbox",
@@ -286,7 +197,7 @@ router.get(
286
197
  { class: "m-3 form-check" },
287
198
  label(
288
199
  { class: "form-check-label", for: "showPagesId" },
289
- "Pages"
200
+ req.__("Pages")
290
201
  ),
291
202
  input({
292
203
  type: "checkbox",
@@ -304,7 +215,7 @@ router.get(
304
215
  { class: "m-3 form-check" },
305
216
  label(
306
217
  { class: "form-check-label", for: "showTablesId" },
307
- "Tables"
218
+ req.__("Tables")
308
219
  ),
309
220
  input({
310
221
  type: "checkbox",
@@ -322,7 +233,7 @@ router.get(
322
233
  { class: "m-3 form-check" },
323
234
  label(
324
235
  { class: "form-check-label", for: "showTriggerId" },
325
- "Trigger"
236
+ req.__("Triggers")
326
237
  ),
327
238
  input({
328
239
  type: "checkbox",
@@ -344,23 +255,18 @@ router.get(
344
255
  "data-bs-toggle": "dropdown",
345
256
  "aria-expanded": false,
346
257
  },
347
- "Tags"
258
+ req.__("Tags")
348
259
  ),
349
260
  div(
350
261
  {
351
262
  class: "dropdown-menu",
352
263
  },
353
- input({
354
- type: "hidden",
355
- name: "_csrf",
356
- value: req.csrfToken(),
357
- }),
358
264
  // no tags checkbox
359
265
  div(
360
266
  { class: "m-3 form-check" },
361
267
  label(
362
268
  { class: "form-check-label", for: "noTagsId" },
363
- "no tags"
269
+ req.__("no tags")
364
270
  ),
365
271
  input({
366
272
  type: "checkbox",
@@ -408,25 +314,45 @@ router.get(
408
314
  i({ class: "fas fa-plus ms-2" })
409
315
  )
410
316
  )
317
+ ),
318
+ // refresh button
319
+ button(
320
+ {
321
+ type: "button",
322
+ class: "btn btn-primary m-2 rounded",
323
+ onclick: "reloadCy(true);",
324
+ },
325
+ i({ class: "fas fa-sync-alt" })
411
326
  )
412
327
  ),
413
328
  div({ id: "cy" }),
329
+ script(buildGlobalVars(tags, roles)),
330
+ script({ src: "/diagram_utils.js" }),
414
331
  script(domReady(initialCyCode)),
415
- script(buildScript()),
416
332
  ],
417
333
  },
418
334
  ],
419
335
  },
420
336
  headers: [
337
+ {
338
+ style: `
339
+ #cy {
340
+ width: 100%;
341
+ height: 900px;
342
+ display: block;
343
+ }`,
344
+ },
345
+ {
346
+ script:
347
+ "https://cdnjs.cloudflare.com/ajax/libs/popper.js/2.9.2/umd/popper.min.js",
348
+ },
421
349
  {
422
350
  script:
423
351
  "https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.22.1/cytoscape.min.js",
424
- style: `
425
- #cy {
426
- width: 100%;
427
- height: 900px;
428
- display: block;
429
- }`,
352
+ },
353
+ {
354
+ script:
355
+ "https://cdnjs.cloudflare.com/ajax/libs/cytoscape-popper/2.0.0/cytoscape-popper.min.js",
430
356
  },
431
357
  ],
432
358
  });
package/routes/edit.js CHANGED
@@ -6,14 +6,8 @@
6
6
 
7
7
  const Router = require("express-promise-router");
8
8
 
9
- const Field = require("@saltcorn/data/models/field");
10
- const File = require("@saltcorn/data/models/file");
11
- const Form = require("@saltcorn/data/models/form");
12
- const { loggedIn, error_catcher } = require("./utils.js");
9
+ const { error_catcher } = require("./utils.js");
13
10
  const Table = require("@saltcorn/data/models/table");
14
- const pluralize = require("pluralize");
15
-
16
- const { renderForm } = require("@saltcorn/markup");
17
11
 
18
12
  /**
19
13
  * @type {object}
@@ -32,11 +26,12 @@ module.exports = router;
32
26
  * @function
33
27
  */
34
28
  router.post(
35
- "/toggle/:name/:id/:field_name",
29
+ "/toggle/:tableName/:id/:field_name",
36
30
  error_catcher(async (req, res) => {
37
- const { name, id, field_name } = req.params;
31
+ const { tableName, id, field_name } = req.params;
38
32
  const { redirect } = req.query;
39
- const table = await Table.findOne({ name });
33
+ // todo check that works after where change
34
+ const table = await Table.findOne({ name : tableName });
40
35
  const role = req.user && req.user.id ? req.user.role_id : 10;
41
36
  if (role <= table.min_role_write) await table.toggleBool(+id, field_name);
42
37
  else