@saltcorn/server 0.7.4-beta.3 → 0.7.4

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/diagram.js CHANGED
@@ -2,58 +2,459 @@ const Page = require("@saltcorn/data/models/page");
2
2
  const {
3
3
  buildObjectTrees,
4
4
  } = require("@saltcorn/data/diagram/node_extract_utils");
5
- const { generateCyCode } = require("@saltcorn/data/diagram/cy_generate_utils");
5
+ const {
6
+ generateCyCode,
7
+ genereateCyCfg,
8
+ } = require("@saltcorn/data/diagram/cy_generate_utils");
6
9
  const { getState } = require("@saltcorn/data/db/state");
7
- const { div, script, domReady } = require("@saltcorn/markup/tags");
10
+ const {
11
+ a,
12
+ input,
13
+ label,
14
+ button,
15
+ div,
16
+ script,
17
+ i,
18
+ domReady,
19
+ } = require("@saltcorn/markup/tags");
20
+ const { send_infoarch_page } = require("../markup/admin");
8
21
  const { isAdmin, error_catcher } = require("./utils.js");
22
+ const Tag = require("@saltcorn/data/models/tag");
9
23
  const Router = require("express-promise-router");
10
24
 
11
25
  const router = new Router();
12
26
  module.exports = router;
13
27
 
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()}`;
114
+ };
115
+
116
+ const findEntryPages = async () => {
117
+ const modernCfg = getState().getConfig("home_page_by_role");
118
+ let pages = null;
119
+ if (modernCfg) {
120
+ pages = Object.values(modernCfg)
121
+ .filter((val) => val)
122
+ .map((val) => Page.findOne({ name: val }));
123
+ } else {
124
+ pages = new Array();
125
+ for (const legacyRole of ["public", "user", "staff", "admin"]) {
126
+ const page = await Page.findOne({ name: `${legacyRole}_home` });
127
+ if (page) pages.push(page);
128
+ }
129
+ }
130
+ return pages;
131
+ };
132
+
133
+ const buildFilterIds = async (tags) => {
134
+ if (!tags || tags.length === 0) return null;
135
+ else {
136
+ const viewFilterIds = new Set();
137
+ const pageFilterIds = new Set();
138
+ const tableFilterIds = new Set();
139
+ const triggerFilterIds = new Set();
140
+ for (const tag of tags) {
141
+ for (const id of await tag.getViewIds()) viewFilterIds.add(id);
142
+ for (const id of await tag.getPageIds()) pageFilterIds.add(id);
143
+ for (const id of await tag.getTableIds()) tableFilterIds.add(id);
144
+ for (const id of await tag.getTriggerIds()) triggerFilterIds.add(id);
145
+ }
146
+ return {
147
+ viewFilterIds,
148
+ pageFilterIds,
149
+ tableFilterIds,
150
+ triggerFilterIds,
151
+ };
152
+ }
153
+ };
154
+
155
+ const parseBool = (str) => {
156
+ return str === "true";
157
+ };
158
+
14
159
  router.get(
15
160
  "/",
16
161
  isAdmin,
17
162
  error_catcher(async (req, res) => {
18
- const modernCfg = getState().getConfig("home_page_by_role");
19
- let pages = null;
20
- if (modernCfg) {
21
- pages = Object.values(modernCfg)
22
- .filter((val) => val)
23
- .map((val) => Page.findOne({ name: val }));
24
- } else {
25
- pages = new Array();
26
- for (const legacyRole of ["public", "user", "staff", "admin"]) {
27
- const page = await Page.findOne({ name: `${legacyRole}_home` });
28
- if (page) pages.push(page);
29
- }
30
- }
31
- const cyCode = generateCyCode(await buildObjectTrees(pages));
32
- res.sendWrap(
33
- {
34
- title: req.__(`Application diagram`),
35
- headers: [
36
- {
37
- script:
38
- "https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.22.1/cytoscape.min.js",
39
- style: `
40
- #cy {
41
- width: 100%;
42
- height: 900px;
43
- display: block;
44
- }`,
45
- },
46
- ],
47
- },
48
- {
163
+ const extractOpts = {
164
+ entryPages: await findEntryPages(),
165
+ showViews: true,
166
+ showPages: true,
167
+ showTables: true,
168
+ showTrigger: true,
169
+ };
170
+ const initialCyCode = generateCyCode(await buildObjectTrees(extractOpts));
171
+ const tags = await Tag.find();
172
+ send_infoarch_page({
173
+ res,
174
+ req,
175
+ active_sub: "Diagram",
176
+ contents: {
49
177
  above: [
50
178
  {
51
179
  type: "card",
52
180
  title: req.__(`Application diagram`),
53
- contents: [div({ id: "cy" }), script(domReady(cyCode))],
181
+ contents: [
182
+ div(
183
+ { class: "btn-group" },
184
+ // New dropdown
185
+ button(
186
+ {
187
+ type: "button",
188
+ class: "btn btn-primary m-2 rounded",
189
+ "data-bs-toggle": "dropdown",
190
+ "aria-expanded": false,
191
+ },
192
+ "New",
193
+ i({ class: "fas fa-plus-square ms-2" })
194
+ ),
195
+
196
+ div(
197
+ {
198
+ class: "dropdown-menu",
199
+ },
200
+ input({
201
+ type: "hidden",
202
+ name: "_csrf",
203
+ value: req.csrfToken(),
204
+ }),
205
+ // New View
206
+ div(
207
+ { class: "m-3" },
208
+
209
+ a(
210
+ {
211
+ href: "/viewedit/new?on_done_redirect=diagram",
212
+ },
213
+ req.__("View")
214
+ )
215
+ ),
216
+ // New Page
217
+ div(
218
+ { class: "m-3" },
219
+ a(
220
+ {
221
+ href: "/pageedit/new?on_done_redirect=diagram",
222
+ },
223
+ req.__("Page")
224
+ )
225
+ ),
226
+ // New Table
227
+ div(
228
+ { class: "m-3" },
229
+ a(
230
+ {
231
+ href: "/table/new",
232
+ },
233
+ req.__("Table")
234
+ )
235
+ ),
236
+ // New Trigger
237
+ div(
238
+ { class: "m-3" },
239
+ a(
240
+ {
241
+ href: "/actions/new?on_done_redirect=diagram",
242
+ },
243
+ req.__("Trigger")
244
+ )
245
+ )
246
+ ),
247
+ // Entity type filter dropdown
248
+ button(
249
+ {
250
+ type: "button",
251
+ class: "btn btn-primary m-2 rounded",
252
+ "data-bs-toggle": "dropdown",
253
+ "aria-expanded": false,
254
+ },
255
+ "All entities"
256
+ ),
257
+ div(
258
+ {
259
+ class: "dropdown-menu",
260
+ },
261
+ input({
262
+ type: "hidden",
263
+ name: "_csrf",
264
+ value: req.csrfToken(),
265
+ }),
266
+ // Views checkbox
267
+ div(
268
+ { class: "m-3 form-check" },
269
+ label(
270
+ { class: "form-check-label", for: "showViewsId" },
271
+ "Views"
272
+ ),
273
+ input({
274
+ type: "checkbox",
275
+ class: "form-check-input",
276
+ id: "showViewsId",
277
+ checked: true,
278
+ name: "show_views",
279
+ value: "true",
280
+ onclick: "toggleEntityFilter('views'); reloadCy();",
281
+ autocomplete: "off",
282
+ })
283
+ ),
284
+ // Pages checkbox
285
+ div(
286
+ { class: "m-3 form-check" },
287
+ label(
288
+ { class: "form-check-label", for: "showPagesId" },
289
+ "Pages"
290
+ ),
291
+ input({
292
+ type: "checkbox",
293
+ class: "form-check-input",
294
+ id: "showPagesId",
295
+ name: "show_pages",
296
+ value: "true",
297
+ checked: true,
298
+ onclick: "toggleEntityFilter('pages'); reloadCy();",
299
+ autocomplete: "off",
300
+ })
301
+ ),
302
+ // Tables checkbox
303
+ div(
304
+ { class: "m-3 form-check" },
305
+ label(
306
+ { class: "form-check-label", for: "showTablesId" },
307
+ "Tables"
308
+ ),
309
+ input({
310
+ type: "checkbox",
311
+ class: "form-check-input",
312
+ id: "showTablesId",
313
+ name: "show_tables",
314
+ value: "true",
315
+ checked: true,
316
+ onclick: "toggleEntityFilter('tables'); reloadCy();",
317
+ autocomplete: "off",
318
+ })
319
+ ),
320
+ // Trigger checkbox
321
+ div(
322
+ { class: "m-3 form-check" },
323
+ label(
324
+ { class: "form-check-label", for: "showTriggerId" },
325
+ "Trigger"
326
+ ),
327
+ input({
328
+ type: "checkbox",
329
+ class: "form-check-input",
330
+ id: "showTriggerId",
331
+ name: "show_trigger",
332
+ value: "true",
333
+ checked: true,
334
+ onclick: "toggleEntityFilter('trigger'); reloadCy();",
335
+ autocomplete: "off",
336
+ })
337
+ )
338
+ ),
339
+ // Tags filter dropdown
340
+ button(
341
+ {
342
+ type: "button",
343
+ class: "btn btn-primary m-2 rounded",
344
+ "data-bs-toggle": "dropdown",
345
+ "aria-expanded": false,
346
+ },
347
+ "Tags"
348
+ ),
349
+ div(
350
+ {
351
+ class: "dropdown-menu",
352
+ },
353
+ input({
354
+ type: "hidden",
355
+ name: "_csrf",
356
+ value: req.csrfToken(),
357
+ }),
358
+ // no tags checkbox
359
+ div(
360
+ { class: "m-3 form-check" },
361
+ label(
362
+ { class: "form-check-label", for: "noTagsId" },
363
+ "no tags"
364
+ ),
365
+ input({
366
+ type: "checkbox",
367
+ class: "form-check-input",
368
+ id: "noTagsId",
369
+ name: "no_tags",
370
+ value: "true",
371
+ checked: true,
372
+ onclick: "toggleTagFilterMode(); reloadCy();",
373
+ autocomplete: "off",
374
+ })
375
+ ),
376
+ tags.map((tag) => {
377
+ const inputId = `tagFilter_box_${tag.name}_id`;
378
+ return div(
379
+ { class: "m-3 form-check" },
380
+ label(
381
+ {
382
+ class: "form-check-label",
383
+ id: `tagFilter_label_${tag.name}`,
384
+ style: "opacity: 0.5;",
385
+ for: inputId,
386
+ },
387
+ tag.name
388
+ ),
389
+ input({
390
+ type: "checkbox",
391
+ class: "form-check-input",
392
+ id: inputId,
393
+ name: "choice",
394
+ value: tag.id,
395
+ checked: false,
396
+ onclick: `toggleTagFilter(${tag.id}); reloadCy();`,
397
+ autocomplete: "off",
398
+ })
399
+ );
400
+ }),
401
+ div(
402
+ { class: "m-3" },
403
+ a(
404
+ {
405
+ href: "/tag/new",
406
+ },
407
+ req.__("Add tag"),
408
+ i({ class: "fas fa-plus ms-2" })
409
+ )
410
+ )
411
+ )
412
+ ),
413
+ div({ id: "cy" }),
414
+ script(domReady(initialCyCode)),
415
+ script(buildScript()),
416
+ ],
54
417
  },
55
418
  ],
56
- }
419
+ },
420
+ headers: [
421
+ {
422
+ script:
423
+ "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
+ }`,
430
+ },
431
+ ],
432
+ });
433
+ })
434
+ );
435
+
436
+ router.get(
437
+ "/data",
438
+ isAdmin,
439
+ error_catcher(async (req, res) => {
440
+ const { showViews, showPages, showTables, showTrigger } = req.query;
441
+ const tagFilterIds = req.query.tagFilterIds
442
+ ? req.query.tagFilterIds.map((id) => parseInt(id))
443
+ : [];
444
+ const tags = (await Tag.find()).filter(
445
+ (tag) => tagFilterIds.indexOf(tag.id) > -1
57
446
  );
447
+ let extractOpts = {
448
+ entryPages: await findEntryPages(),
449
+ showViews: parseBool(showViews),
450
+ showPages: parseBool(showPages),
451
+ showTables: parseBool(showTables),
452
+ showTrigger: parseBool(showTrigger),
453
+ };
454
+ const filterIds = await buildFilterIds(tags);
455
+ if (filterIds) {
456
+ extractOpts = { ...extractOpts, ...filterIds };
457
+ }
458
+ res.json(genereateCyCfg(await buildObjectTrees(extractOpts)));
58
459
  })
59
460
  );
@@ -21,7 +21,7 @@ const { add_to_menu } = require("@saltcorn/admin-models/models/pack");
21
21
  const db = require("@saltcorn/data/db");
22
22
  const { getPageList } = require("./common_lists");
23
23
 
24
- const { isAdmin, error_catcher } = require("./utils.js");
24
+ const { isAdmin, error_catcher, addOnDoneRedirect } = require("./utils.js");
25
25
  const {
26
26
  mkTable,
27
27
  renderForm,
@@ -54,7 +54,7 @@ const pagePropertiesForm = async (req) => {
54
54
  const roles = await User.get_roles();
55
55
 
56
56
  const form = new Form({
57
- action: "/pageedit/edit-properties",
57
+ action: addOnDoneRedirect("/pageedit/edit-properties", req),
58
58
  fields: [
59
59
  new Field({
60
60
  label: req.__("Name"),
@@ -357,7 +357,7 @@ router.post(
357
357
  if (!pageRow.fixed_states) pageRow.fixed_states = {};
358
358
  if (!pageRow.layout) pageRow.layout = {};
359
359
  await Page.create(pageRow);
360
- res.redirect(`/pageedit/edit/${pageRow.name}`);
360
+ res.redirect(addOnDoneRedirect(`/pageedit/edit/${pageRow.name}`, req));
361
361
  }
362
362
  }
363
363
  })
@@ -416,20 +416,23 @@ router.post(
416
416
  error_catcher(async (req, res) => {
417
417
  const { pagename } = req.params;
418
418
 
419
+ let redirectTarget = req.query.on_done_redirect
420
+ ? `/${req.query.on_done_redirect}`
421
+ : "/pageedit";
419
422
  const page = await Page.findOne({ name: pagename });
420
423
  if (!page) {
421
424
  req.flash("error", req.__(`Page %s not found`, pagename));
422
- res.redirect(`/pageedit`);
425
+ res.redirect(redirectTarget);
423
426
  } else if (req.body.layout) {
424
427
  await Page.update(page.id, {
425
428
  layout: decodeURIComponent(req.body.layout),
426
429
  });
427
430
 
428
431
  req.flash("success", req.__(`Page %s saved`, pagename));
429
- res.redirect(`/pageedit`);
432
+ res.redirect(redirectTarget);
430
433
  } else {
431
434
  req.flash("error", req.__(`Error processing page`));
432
- res.redirect(`/pageedit`);
435
+ res.redirect(redirectTarget);
433
436
  }
434
437
  })
435
438
  );
package/routes/tags.js CHANGED
@@ -35,30 +35,34 @@ router.get(
35
35
  res,
36
36
  req,
37
37
  active_sub: "Tags",
38
- contents: [
39
- mkTable(
40
- [
41
- {
42
- label: req.__("Tagname"),
43
- key: (r) =>
44
- link(`/tag/${r.id || r.name}?show_list=tables`, text(r.name)),
45
- },
38
+ contents: {
39
+ type: "card",
40
+ title: req.__("Tags"),
41
+ contents: [
42
+ mkTable(
43
+ [
44
+ {
45
+ label: req.__("Tagname"),
46
+ key: (r) =>
47
+ link(`/tag/${r.id || r.name}?show_list=tables`, text(r.name)),
48
+ },
49
+ {
50
+ label: req.__("Delete"),
51
+ key: (r) => post_delete_btn(`/tag/delete/${r.id}`, req, r.name),
52
+ },
53
+ ],
54
+ rows,
55
+ {}
56
+ ),
57
+ a(
46
58
  {
47
- label: req.__("Delete"),
48
- key: (r) => post_delete_btn(`/tag/delete/${r.id}`, req, r.name),
59
+ href: `/tag/new`,
60
+ class: "btn btn-primary",
49
61
  },
50
- ],
51
- rows,
52
- {}
53
- ),
54
- a(
55
- {
56
- href: `/tag/new`,
57
- class: "btn btn-primary",
58
- },
59
- req.__("Create tag")
60
- ),
61
- ],
62
+ req.__("Create tag")
63
+ ),
64
+ ],
65
+ },
62
66
  });
63
67
  })
64
68
  );