@saltcorn/server 0.7.4-beta.2 → 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/admin.js CHANGED
@@ -390,7 +390,7 @@ router.get(
390
390
  renderForm(backupForm, req.csrfToken()),
391
391
  a(
392
392
  { href: "/admin/auto-backup-list" },
393
- "Restore/download automated backups »"
393
+ req.__("Restore/download automated backups »")
394
394
  ),
395
395
  script(
396
396
  domReady(
@@ -406,13 +406,13 @@ router.get(
406
406
  contents: div(
407
407
  p(
408
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)."
409
+ req.__("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
410
  )
411
411
  ),
412
412
  renderForm(aSnapshotForm, req.csrfToken()),
413
413
  a(
414
414
  { href: "/admin/snapshot-list" },
415
- "List/download snapshots &raquo;"
415
+ req.__("List/download snapshots &raquo;")
416
416
  )
417
417
  ),
418
418
  },
@@ -472,16 +472,16 @@ router.get(
472
472
  title: req.__("Restoring automated backup"),
473
473
  contents: div(
474
474
  ol(
475
- li("Download one of the backups above"),
475
+ li(req.__("Download one of the backups above")),
476
476
  li(
477
- a({ href: "/admin/clear-all" }, "Clear this application"),
477
+ a({ href: "/admin/clear-all" }, req.__("Clear this application")),
478
478
  " ",
479
- "(tick all boxes)"
479
+ req.__("(tick all boxes)")
480
480
  ),
481
481
  li(
482
- "When prompted to create the first user, click the link to restore a backup"
482
+ req.__("When prompted to create the first user, click the link to restore a backup")
483
483
  ),
484
- li("Select the downloaded backup file")
484
+ li(req.__("Select the downloaded backup file"))
485
485
  )
486
486
  ),
487
487
  },
@@ -551,7 +551,7 @@ router.get(
551
551
  mkTable(
552
552
  [
553
553
  {
554
- label: "When",
554
+ label: req.__("When"),
555
555
  key: (r) =>
556
556
  `${localeDateTime(r.created)} (${moment(r.created).fromNow()})`,
557
557
  },
@@ -618,7 +618,7 @@ const autoBackupForm = (req) =>
618
618
  noSubmitButton: true,
619
619
  additionalButtons: [
620
620
  {
621
- label: "Backup now",
621
+ label: req.__("Backup now"),
622
622
  id: "btnBackupNow",
623
623
  class: "btn btn-outline-secondary",
624
624
  onclick: "ajax_post('/admin/auto-backup-now')",
@@ -671,7 +671,7 @@ const snapshotForm = (req) =>
671
671
  noSubmitButton: true,
672
672
  additionalButtons: [
673
673
  {
674
- label: "Snapshot now",
674
+ label: req.__("Snapshot now"),
675
675
  id: "btnSnapNow",
676
676
  class: "btn btn-outline-secondary",
677
677
  onclick: "ajax_post('/admin/snapshot-now')",
@@ -808,7 +808,6 @@ router.get(
808
808
  " ",
809
809
  req.__("Configuration check")
810
810
  ),
811
-
812
811
  hr(),
813
812
 
814
813
  a(
@@ -945,7 +944,7 @@ router.post(
945
944
  });
946
945
  child.on("exit", function (code, signal) {
947
946
  res.end(
948
- `Upgrade done (if it was available) with code ${code}.\n\nPress the BACK button in your browser, then RELOAD the page.`
947
+ req.__(`Upgrade done (if it was available) with code ${code}.\n\nPress the BACK button in your browser, then RELOAD the page.`)
949
948
  );
950
949
  setTimeout(() => {
951
950
  if (process.send) process.send("RestartServer");
@@ -981,7 +980,7 @@ router.post(
981
980
  res.attachment(fileName);
982
981
  const file = fs.createReadStream(fileName);
983
982
  file.on("end", function () {
984
- fs.unlink(fileName, function () { });
983
+ fs.unlink(fileName, function () {});
985
984
  });
986
985
  file.pipe(res);
987
986
  })
@@ -1004,7 +1003,7 @@ router.post(
1004
1003
  );
1005
1004
  if (err) req.flash("error", err);
1006
1005
  else req.flash("success", req.__("Successfully restored backup"));
1007
- fs.unlink(newPath, function () { });
1006
+ fs.unlink(newPath, function () {});
1008
1007
  res.redirect(`/admin`);
1009
1008
  })
1010
1009
  );
@@ -1193,18 +1192,23 @@ router.get(
1193
1192
  })
1194
1193
  );
1195
1194
  /**
1196
- * /confiuration-check
1195
+ * /configuration-check
1197
1196
  */
1198
1197
  router.get(
1199
1198
  "/configuration-check",
1200
1199
  isAdmin,
1201
1200
  error_catcher(async (req, res) => {
1202
- const { passes, errors, pass } = await runConfigurationCheck(req);
1201
+ const { passes, errors, pass, warnings } = await runConfigurationCheck(req);
1203
1202
  const mkError = (err) =>
1204
1203
  div(
1205
1204
  { class: "alert alert-danger", role: "alert" },
1206
1205
  pre({ class: "mb-0" }, code(err))
1207
1206
  );
1207
+ const mkWarning = (err) =>
1208
+ div(
1209
+ { class: "alert alert-warning", role: "alert" },
1210
+ pre({ class: "mb-0" }, code(err))
1211
+ );
1208
1212
  res.sendWrap(req.__(`Admin`), {
1209
1213
  above: [
1210
1214
  {
@@ -1228,7 +1232,8 @@ router.get(
1228
1232
  req.__("No errors detected during configuration check")
1229
1233
  )
1230
1234
  )
1231
- : errors.map(mkError)
1235
+ : errors.map(mkError),
1236
+ (warnings || []).map(mkWarning)
1232
1237
  ),
1233
1238
  },
1234
1239
  {
@@ -1270,11 +1275,10 @@ const buildDialogScript = () => {
1270
1275
 
1271
1276
  function handleMessages() {
1272
1277
  notifyAlert("This is still under development and might run longer.")
1273
- ${
1274
- getState().getConfig("apple_team_id") &&
1278
+ ${getState().getConfig("apple_team_id") &&
1275
1279
  getState().getConfig("apple_team_id") !== "null"
1276
- ? ""
1277
- : `
1280
+ ? ""
1281
+ : `
1278
1282
  if ($("#iOSCheckboxId")[0].checked) {
1279
1283
  notifyAlert(
1280
1284
  "No 'Apple Team ID' is configured, I will try to build a project for the iOS simulator."
@@ -1548,6 +1552,12 @@ router.post(
1548
1552
  }
1549
1553
  if (appFile) spawnParams.push("-a", appFile);
1550
1554
  if (serverURL) spawnParams.push("-s", serverURL);
1555
+ if (
1556
+ db.is_it_multi_tenant() &&
1557
+ db.getTenantSchema() !== db.connectObj.default_schema
1558
+ ) {
1559
+ spawnParams.push("--tenantAppName", db.getTenantSchema());
1560
+ }
1551
1561
  const child = spawn("saltcorn", spawnParams, {
1552
1562
  stdio: ["ignore", "pipe", "pipe"],
1553
1563
  cwd: ".",
@@ -1576,7 +1586,7 @@ router.post(
1576
1586
  {
1577
1587
  type: "card",
1578
1588
  title: req.__("Build Result"),
1579
- contents: div("The build was successfully"),
1589
+ contents: div(req.__("The build was successfully")),
1580
1590
  },
1581
1591
  files.length > 0 ? app_files_table(files, req) : "",
1582
1592
  ],
@@ -1588,7 +1598,7 @@ router.post(
1588
1598
  type: "card",
1589
1599
  title: req.__("Build Result"),
1590
1600
  contents: div(
1591
- "Unable to build the app:",
1601
+ req.__("Unable to build the app:"),
1592
1602
  pre(code(childOutputs.join("<br/>")))
1593
1603
  ),
1594
1604
  },
@@ -1604,7 +1614,7 @@ router.post(
1604
1614
  type: "card",
1605
1615
  title: req.__("Build Result"),
1606
1616
  contents: div(
1607
- p("Unable to build the app:"),
1617
+ p(req.__("Unable to build the app:")),
1608
1618
  pre(code(message)),
1609
1619
  pre(code(stack))
1610
1620
  ),
package/routes/api.js CHANGED
@@ -71,8 +71,8 @@ function accessAllowedRead(req, user, table) {
71
71
  req.user && req.user.id
72
72
  ? req.user.role_id
73
73
  : user && user.role_id
74
- ? user.role_id
75
- : 10;
74
+ ? user.role_id
75
+ : 10;
76
76
 
77
77
  return role <= table.min_role_read;
78
78
  }
@@ -89,8 +89,8 @@ function accessAllowedWrite(req, user, table) {
89
89
  req.user && req.user.id
90
90
  ? req.user.role_id
91
91
  : user && user.role_id
92
- ? user.role_id
93
- : 10;
92
+ ? user.role_id
93
+ : 10;
94
94
 
95
95
  return role <= table.min_role_write;
96
96
  }
@@ -106,8 +106,8 @@ function accessAllowed(req, user, trigger) {
106
106
  req.user && req.user.id
107
107
  ? req.user.role_id
108
108
  : user && user.role_id
109
- ? user.role_id
110
- : 10;
109
+ ? user.role_id
110
+ : 10;
111
111
 
112
112
  return role <= trigger.min_role;
113
113
  }
@@ -247,6 +247,7 @@ router.get(
247
247
  fields: tbl_fields,
248
248
  approximate: !!approximate,
249
249
  state: req_query,
250
+ table
250
251
  });
251
252
  rows = await table.getRows(qstate);
252
253
  } else {
@@ -0,0 +1,460 @@
1
+ const Page = require("@saltcorn/data/models/page");
2
+ const {
3
+ buildObjectTrees,
4
+ } = require("@saltcorn/data/diagram/node_extract_utils");
5
+ const {
6
+ generateCyCode,
7
+ genereateCyCfg,
8
+ } = require("@saltcorn/data/diagram/cy_generate_utils");
9
+ const { getState } = require("@saltcorn/data/db/state");
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");
21
+ const { isAdmin, error_catcher } = require("./utils.js");
22
+ const Tag = require("@saltcorn/data/models/tag");
23
+ const Router = require("express-promise-router");
24
+
25
+ const router = new Router();
26
+ module.exports = router;
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
+
159
+ router.get(
160
+ "/",
161
+ isAdmin,
162
+ error_catcher(async (req, res) => {
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: {
177
+ above: [
178
+ {
179
+ type: "card",
180
+ title: req.__(`Application diagram`),
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
+ ],
417
+ },
418
+ ],
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
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)));
459
+ })
460
+ );