@saltcorn/server 0.7.4-beta.1 → 0.7.4-beta.3

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.
@@ -0,0 +1,419 @@
1
+ const User = require("@saltcorn/data/models/user");
2
+ const Table = require("@saltcorn/data/models/table");
3
+ const { editRoleForm } = require("../markup/forms.js");
4
+ const {
5
+ mkTable,
6
+ link,
7
+ post_delete_btn,
8
+ settingsDropdown,
9
+ post_dropdown_item,
10
+ } = require("@saltcorn/markup");
11
+
12
+ const { h4, p, div, a, input, text } = require("@saltcorn/markup/tags");
13
+
14
+ /**
15
+ * @param {string} col
16
+ * @param {string} lbl
17
+ * @returns {string}
18
+ */
19
+ const badge = (col, lbl) => `<span class="badge bg-${col}">${lbl}</span>&nbsp;`;
20
+
21
+ /**
22
+ * Table badges to show in System Table list views
23
+ * Currently supports:
24
+ * - Owned - if ownership_field_id? What is it?
25
+ * - History - if table has versioning
26
+ * - External - if this is external table
27
+ * @param {object} t table object
28
+ * @param {object} req http request
29
+ * @returns {string} html string with list of badges
30
+ */
31
+ const tableBadges = (t, req) => {
32
+ let s = "";
33
+ if (t.ownership_field_id) s += badge("primary", req.__("Owned"));
34
+ if (t.versioned) s += badge("success", req.__("History"));
35
+ if (t.external) s += badge("info", req.__("External"));
36
+ return s;
37
+ };
38
+
39
+ const valIfSet = (check, value) => (check ? value : "");
40
+
41
+ const listClass = (tagId, showList) =>
42
+ valIfSet(tagId, `collapse ${valIfSet(showList, "show")}`);
43
+
44
+ const tablesList = async (tables, req, { tagId, domId, showList } = {}) => {
45
+ const roles = await User.get_roles();
46
+ const getRole = (rid) => roles.find((r) => r.id === rid).role;
47
+ return tables.length > 0
48
+ ? 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,
84
+ {
85
+ hover: true,
86
+ tableClass: listClass(tagId, showList),
87
+ tableId: domId,
88
+ }
89
+ )
90
+ : div(
91
+ { class: listClass(tagId, showList), id: domId },
92
+ h4(req.__("No tables defined")),
93
+ p(req.__("Tables hold collections of similar data"))
94
+ );
95
+ };
96
+
97
+ /**
98
+ * @param {object} view
99
+ * @param {object[]} roles
100
+ * @param {object} req
101
+ * @returns {Form}
102
+ */
103
+ const editViewRoleForm = (view, roles, req) =>
104
+ editRoleForm({
105
+ url: `/viewedit/setrole/${view.id}`,
106
+ current_role: view.min_role,
107
+ roles,
108
+ req,
109
+ });
110
+
111
+ /**
112
+ * @param {object} view
113
+ * @param {object} req
114
+ * @returns {div}
115
+ */
116
+ const view_dropdown = (view, req) =>
117
+ settingsDropdown(`dropdownMenuButton${view.id}`, [
118
+ a(
119
+ {
120
+ class: "dropdown-item",
121
+ href: `/view/${encodeURIComponent(view.name)}`,
122
+ },
123
+ '<i class="fas fa-running"></i>&nbsp;' + req.__("Run")
124
+ ),
125
+ a(
126
+ {
127
+ class: "dropdown-item",
128
+ href: `/viewedit/edit/${encodeURIComponent(view.name)}`,
129
+ },
130
+ '<i class="fas fa-edit"></i>&nbsp;' + req.__("Edit")
131
+ ),
132
+ post_dropdown_item(
133
+ `/viewedit/add-to-menu/${view.id}`,
134
+ '<i class="fas fa-bars"></i>&nbsp;' + req.__("Add to menu"),
135
+ req
136
+ ),
137
+ post_dropdown_item(
138
+ `/viewedit/clone/${view.id}`,
139
+ '<i class="far fa-copy"></i>&nbsp;' + req.__("Duplicate"),
140
+ req
141
+ ),
142
+ a(
143
+ {
144
+ class: "dropdown-item",
145
+ href: `javascript:ajax_modal('/admin/snapshot-restore/view/${view.name}')`,
146
+ },
147
+ '<i class="fas fa-undo-alt"></i>&nbsp;' + req.__("Restore")
148
+ ),
149
+ div({ class: "dropdown-divider" }),
150
+ post_dropdown_item(
151
+ `/viewedit/delete/${view.id}`,
152
+ '<i class="far fa-trash-alt"></i>&nbsp;' + req.__("Delete"),
153
+ req,
154
+ true,
155
+ view.name
156
+ ),
157
+ ]);
158
+
159
+ const setTableRefs = async (views) => {
160
+ const tables = await Table.find();
161
+ const getTable = (tid) => tables.find((t) => t.id === tid).name;
162
+
163
+ views.forEach((v) => {
164
+ if (v.table_id) v.table = getTable(v.table_id);
165
+ else if (v.exttable_name) v.table = v.exttable_name;
166
+ else v.table = "";
167
+ });
168
+ return views;
169
+ };
170
+
171
+ const viewsList = async (views, req, { tagId, domId, showList } = {}) => {
172
+ const roles = await User.get_roles();
173
+
174
+ return views.length > 0
175
+ ? 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
+ {
214
+ label: "",
215
+ key: (r) =>
216
+ link(
217
+ `/viewedit/config/${encodeURIComponent(r.name)}`,
218
+ req.__("Configure")
219
+ ),
220
+ },
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
+ )
243
+ : 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
+ );
248
+ };
249
+
250
+ /**
251
+ * @param {object} page
252
+ * @param {object} req
253
+ * @returns {string}
254
+ */
255
+ const page_dropdown = (page, req) =>
256
+ settingsDropdown(`dropdownMenuButton${page.id}`, [
257
+ a(
258
+ {
259
+ class: "dropdown-item",
260
+ href: `/page/${encodeURIComponent(page.name)}`,
261
+ },
262
+ '<i class="fas fa-running"></i>&nbsp;' + req.__("Run")
263
+ ),
264
+ a(
265
+ {
266
+ class: "dropdown-item",
267
+ href: `/pageedit/edit-properties/${encodeURIComponent(page.name)}`,
268
+ },
269
+ '<i class="fas fa-edit"></i>&nbsp;' + req.__("Edit properties")
270
+ ),
271
+ post_dropdown_item(
272
+ `/pageedit/add-to-menu/${page.id}`,
273
+ '<i class="fas fa-bars"></i>&nbsp;' + req.__("Add to menu"),
274
+ req
275
+ ),
276
+ post_dropdown_item(
277
+ `/pageedit/clone/${page.id}`,
278
+ '<i class="far fa-copy"></i>&nbsp;' + req.__("Duplicate"),
279
+ req
280
+ ),
281
+ a(
282
+ {
283
+ class: "dropdown-item",
284
+ href: `javascript:ajax_modal('/admin/snapshot-restore/page/${page.name}')`,
285
+ },
286
+ '<i class="fas fa-undo-alt"></i>&nbsp;' + req.__("Restore")
287
+ ),
288
+ div({ class: "dropdown-divider" }),
289
+ post_dropdown_item(
290
+ `/pageedit/delete/${page.id}`,
291
+ '<i class="far fa-trash-alt"></i>&nbsp;' + req.__("Delete"),
292
+ req,
293
+ true,
294
+ page.name
295
+ ),
296
+ ]);
297
+
298
+ /**
299
+ * @param {object} page
300
+ * @param {*} roles
301
+ * @param {object} req
302
+ * @returns {Form}
303
+ */
304
+ const editPageRoleForm = (page, roles, req) =>
305
+ editRoleForm({
306
+ url: `/pageedit/setrole/${page.id}`,
307
+ current_role: page.min_role,
308
+ roles,
309
+ req,
310
+ });
311
+
312
+ /**
313
+ * @param {*} rows
314
+ * @param {*} roles
315
+ * @param {object} req
316
+ * @returns {div}
317
+ */
318
+ const getPageList = (rows, roles, req, { tagId, domId, showList } = {}) => {
319
+ return mkTable(
320
+ [
321
+ {
322
+ label: req.__("Name"),
323
+ key: (r) => link(`/page/${r.name}`, r.name),
324
+ },
325
+ {
326
+ label: req.__("Role to access"),
327
+ key: (row) => editPageRoleForm(row, roles, req),
328
+ },
329
+ {
330
+ label: req.__("Edit"),
331
+ key: (r) => link(`/pageedit/edit/${r.name}`, req.__("Edit")),
332
+ },
333
+ !tagId
334
+ ? {
335
+ label: "",
336
+ key: (r) => page_dropdown(r, req),
337
+ }
338
+ : {
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
+ },
347
+ ,
348
+ ],
349
+ rows,
350
+ {
351
+ hover: true,
352
+ tableClass: tagId ? `collapse ${showList ? "show" : ""}` : "",
353
+ tableId: domId,
354
+ }
355
+ );
356
+ };
357
+
358
+ const getTriggerList = (triggers, req, { tagId, domId, showList } = {}) => {
359
+ return mkTable(
360
+ [
361
+ { label: req.__("Name"), key: "name" },
362
+ { label: req.__("Action"), key: "action" },
363
+ {
364
+ label: req.__("Table or Channel"),
365
+ key: (r) => r.table_name || r.channel,
366
+ },
367
+ {
368
+ label: req.__("When"),
369
+ key: (a) =>
370
+ a.when_trigger === "API call"
371
+ ? `API: ${base_url}api/action/${a.name}`
372
+ : a.when_trigger,
373
+ },
374
+ {
375
+ label: req.__("Test run"),
376
+ key: (r) =>
377
+ r.table_id
378
+ ? ""
379
+ : link(`/actions/testrun/${r.id}`, req.__("Test run")),
380
+ },
381
+ {
382
+ label: req.__("Edit"),
383
+ key: (r) => link(`/actions/edit/${r.id}`, req.__("Edit")),
384
+ },
385
+ {
386
+ label: req.__("Configure"),
387
+ key: (r) => link(`/actions/configure/${r.id}`, req.__("Configure")),
388
+ },
389
+ !tagId
390
+ ? {
391
+ label: req.__("Delete"),
392
+ key: (r) => post_delete_btn(`/actions/delete/${r.id}`, req),
393
+ }
394
+ : {
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
+ },
403
+ ],
404
+ triggers,
405
+ {
406
+ hover: true,
407
+ tableClass: tagId ? `collapse ${showList ? "show" : ""}` : "",
408
+ tableId: domId,
409
+ }
410
+ );
411
+ };
412
+
413
+ module.exports = {
414
+ tablesList,
415
+ setTableRefs,
416
+ viewsList,
417
+ getPageList,
418
+ getTriggerList,
419
+ };
@@ -0,0 +1,59 @@
1
+ const Page = require("@saltcorn/data/models/page");
2
+ const {
3
+ buildObjectTrees,
4
+ } = require("@saltcorn/data/diagram/node_extract_utils");
5
+ const { generateCyCode } = require("@saltcorn/data/diagram/cy_generate_utils");
6
+ const { getState } = require("@saltcorn/data/db/state");
7
+ const { div, script, domReady } = require("@saltcorn/markup/tags");
8
+ const { isAdmin, error_catcher } = require("./utils.js");
9
+ const Router = require("express-promise-router");
10
+
11
+ const router = new Router();
12
+ module.exports = router;
13
+
14
+ router.get(
15
+ "/",
16
+ isAdmin,
17
+ 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
+ {
49
+ above: [
50
+ {
51
+ type: "card",
52
+ title: req.__(`Application diagram`),
53
+ contents: [div({ id: "cy" }), script(domReady(cyCode))],
54
+ },
55
+ ],
56
+ }
57
+ );
58
+ })
59
+ );
package/routes/fields.js CHANGED
@@ -19,15 +19,17 @@ const {
19
19
  expressionValidator,
20
20
  get_async_expression_function,
21
21
  get_expression_function,
22
+ freeVariables,
22
23
  } = require("@saltcorn/data/models/expression");
23
24
  const db = require("@saltcorn/data/db");
24
25
 
25
26
  const { isAdmin, error_catcher } = require("./utils.js");
26
27
  const expressionBlurb = require("../markup/expression_blurb");
27
- const { readState } = require("@saltcorn/data/plugin-helper");
28
+ const { readState, add_free_variables_to_joinfields } = require("@saltcorn/data/plugin-helper");
28
29
  const { wizardCardTitle } = require("../markup/forms.js");
29
30
  const FieldRepeat = require("@saltcorn/data/models/fieldrepeat");
30
31
  const { applyAsync } = require("@saltcorn/data/utils");
32
+ const { text } = require("@saltcorn/markup/tags");
31
33
 
32
34
  /**
33
35
  * @type {object}
@@ -620,7 +622,11 @@ router.post(
620
622
  const { formula, tablename, stored } = req.body;
621
623
  const table = await Table.findOne({ name: tablename });
622
624
  const fields = await table.getFields();
623
- const rows = await table.getRows({}, { orderBy: "RANDOM()", limit: 1 });
625
+ const freeVars = freeVariables(formula)
626
+ const joinFields = {}
627
+ if (stored)
628
+ add_free_variables_to_joinfields(freeVars, joinFields, fields)
629
+ const rows = await table.getJoinedRows({ joinFields, orderBy: "RANDOM()", limit: 1 });
624
630
  if (rows.length < 1) return "No rows in table";
625
631
  let result;
626
632
  try {
@@ -660,8 +666,12 @@ router.post(
660
666
  return;
661
667
  }
662
668
  const fields = await table.getFields();
663
- const row = { ...req.body };
664
- readState(row, fields);
669
+ let row = { ...req.body };
670
+ if (!row || Object.keys(row).length === 0) {
671
+ const { id } = req.query
672
+ if (id) row = await table.getRow({ id })
673
+ } else
674
+ readState(row, fields);
665
675
 
666
676
  if (fieldName.includes(".")) {
667
677
  //join field
@@ -677,14 +687,30 @@ router.post(
677
687
  (f) => f.name === kpath[1]
678
688
  );
679
689
  //console.log({ kpath, fieldview, targetField });
680
- let fv = targetField.type.fieldviews[fieldview];
681
- if (!fv) {
682
- fv =
683
- targetField.type.fieldviews.show ||
684
- targetField.type.fieldviews.as_text;
685
- }
686
690
  const q = { [reftable.pk_name]: row[kpath[0]] };
687
691
  const refRow = await reftable.getRow(q);
692
+ let fv;
693
+ if (targetField.type === "Key") {
694
+ fv = getState().keyFieldviews[fieldview]
695
+ if (!fv) {
696
+ const reftable2 = Table.findOne({ name: targetField.reftable_name })
697
+ const refRow2 = await reftable2.getRow({ [reftable2.pk_name]: refRow[kpath[1]] })
698
+ if (refRow2) {
699
+ res.send(text(`${refRow2[targetField.attributes.summary_field]}`));
700
+ } else {
701
+ res.send("");
702
+ }
703
+ return;
704
+ }
705
+ } else {
706
+ fv = targetField.type.fieldviews[fieldview];
707
+ if (!fv)
708
+ fv =
709
+ targetField.type.fieldviews.show ||
710
+ targetField.type.fieldviews.as_text;
711
+
712
+ }
713
+
688
714
  const configuration = req.query;
689
715
  let configFields = [];
690
716
  if (fv.configFields)
@@ -726,7 +752,9 @@ router.post(
726
752
 
727
753
  let result;
728
754
  try {
729
- if (field.stored) {
755
+ if (!field.calculated) {
756
+ result = row[field.name]
757
+ } else if (field.stored) {
730
758
  const f = get_async_expression_function(formula, fields);
731
759
  result = await f(row);
732
760
  } else {
@@ -734,7 +762,9 @@ router.post(
734
762
  result = f(row);
735
763
  }
736
764
  const fv = field.type.fieldviews[fieldview];
737
- res.send(fv.run(result));
765
+ if (!fv)
766
+ res.send(text(result));
767
+ else res.send(fv.run(result));
738
768
  } catch (e) {
739
769
  return res.status(400).send(`Error: ${e.message}`);
740
770
  }
package/routes/index.js CHANGED
@@ -70,6 +70,9 @@ const auth = require("../auth/routes");
70
70
  const useradmin = require("../auth/admin");
71
71
  const roleadmin = require("../auth/roleadmin");
72
72
  const scapi = require("./scapi");
73
+ const tags = require("./tags");
74
+ const tagentries = require("./tag_entries");
75
+ const dataDiagram = require("./diagram");
73
76
 
74
77
  module.exports =
75
78
  /**
@@ -106,4 +109,7 @@ module.exports =
106
109
  app.use("/useradmin", useradmin);
107
110
  app.use("/roleadmin", roleadmin);
108
111
  app.use("/scapi", scapi);
112
+ app.use("/tag", tags);
113
+ app.use("/tag-entries", tagentries);
114
+ app.use("/diagram", dataDiagram);
109
115
  };