@saltcorn/server 0.7.3 → 0.7.4-beta.2

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/api.js CHANGED
@@ -112,6 +112,13 @@ function accessAllowed(req, user, trigger) {
112
112
  return role <= trigger.min_role;
113
113
  }
114
114
 
115
+ const getFlashes = (req) =>
116
+ ["error", "success", "danger", "warning", "information"]
117
+ .map((type) => {
118
+ return { type, msg: req.flash(type) };
119
+ })
120
+ .filter((a) => a.msg && a.msg.length && a.msg.length > 0);
121
+
115
122
  router.post(
116
123
  "/viewQuery/:viewName/:queryName",
117
124
  error_catcher(async (req, res, next) => {
@@ -134,7 +141,7 @@ router.post(
134
141
  if (queries[queryName]) {
135
142
  const { args } = req.body;
136
143
  const resp = await queries[queryName](...args, true);
137
- res.json({ success: resp });
144
+ res.json({ success: resp, alerts: getFlashes(req) });
138
145
  } else {
139
146
  res.status(404).json({ error: req.__("Not found") });
140
147
  }
@@ -235,6 +242,7 @@ router.get(
235
242
  rows = await table.getJoinedRows(joinOpts);
236
243
  } else if (req_query && req_query !== {}) {
237
244
  const tbl_fields = await table.getFields();
245
+ readState(req_query, tbl_fields, req);
238
246
  const qstate = await stateFieldsToWhere({
239
247
  fields: tbl_fields,
240
248
  approximate: !!approximate,
@@ -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
+ };
package/routes/fields.js CHANGED
@@ -28,6 +28,7 @@ const { readState } = require("@saltcorn/data/plugin-helper");
28
28
  const { wizardCardTitle } = require("../markup/forms.js");
29
29
  const FieldRepeat = require("@saltcorn/data/models/fieldrepeat");
30
30
  const { applyAsync } = require("@saltcorn/data/utils");
31
+ const { text } = require("@saltcorn/markup/tags");
31
32
 
32
33
  /**
33
34
  * @type {object}
@@ -348,13 +349,12 @@ const fieldFlow = (req) =>
348
349
  // todo sublabel
349
350
  input_type: "custom_html",
350
351
  attributes: {
351
- html: `<button type="button" id="test_formula_btn" onclick="test_formula('${
352
- table.name
353
- }', ${JSON.stringify(
354
- context.stored
355
- )})" class="btn btn-outline-secondary">${req.__(
356
- "Test"
357
- )}</button>
352
+ html: `<button type="button" id="test_formula_btn" onclick="test_formula('${table.name
353
+ }', ${JSON.stringify(
354
+ context.stored
355
+ )})" class="btn btn-outline-secondary">${req.__(
356
+ "Test"
357
+ )}</button>
358
358
  <div id="test_formula_output"></div>`,
359
359
  },
360
360
  }),
@@ -633,8 +633,7 @@ router.post(
633
633
  result = f(rows[0]);
634
634
  }
635
635
  res.send(
636
- `Result of running on row with id=${
637
- rows[0].id
636
+ `Result of running on row with id=${rows[0].id
638
637
  } is: <pre>${JSON.stringify(result)}</pre>`
639
638
  );
640
639
  } catch (e) {
@@ -679,14 +678,30 @@ router.post(
679
678
  (f) => f.name === kpath[1]
680
679
  );
681
680
  //console.log({ kpath, fieldview, targetField });
682
- let fv = targetField.type.fieldviews[fieldview];
683
- if (!fv) {
684
- fv =
685
- targetField.type.fieldviews.show ||
686
- targetField.type.fieldviews.as_text;
687
- }
688
681
  const q = { [reftable.pk_name]: row[kpath[0]] };
689
682
  const refRow = await reftable.getRow(q);
683
+ let fv;
684
+ if (targetField.type === "Key") {
685
+ fv = getState().keyFieldviews[fieldview]
686
+ if (!fv) {
687
+ const reftable2 = Table.findOne({ name: targetField.reftable_name })
688
+ const refRow2 = await reftable2.getRow({ [reftable2.pk_name]: refRow[kpath[1]] })
689
+ if (refRow2) {
690
+ res.send(text(`${refRow2[targetField.attributes.summary_field]}`));
691
+ } else {
692
+ res.send("");
693
+ }
694
+ return;
695
+ }
696
+ } else {
697
+ targetField.type.fieldviews[fieldview];
698
+ if (!fv)
699
+ fv =
700
+ targetField.type.fieldviews.show ||
701
+ targetField.type.fieldviews.as_text;
702
+
703
+ }
704
+
690
705
  const configuration = req.query;
691
706
  let configFields = [];
692
707
  if (fv.configFields)
@@ -788,22 +803,22 @@ router.post(
788
803
  field.type === "Key"
789
804
  ? getState().keyFieldviews
790
805
  : field.type === "File"
791
- ? getState().fileviews
792
- : field.type.fieldviews;
806
+ ? getState().fileviews
807
+ : field.type.fieldviews;
793
808
  if (!field.type || !fieldviews) {
794
809
  res.send("");
795
810
  return;
796
811
  }
797
812
  const fv = fieldviews[fieldview];
798
813
  if (!fv && field.type === "Key" && fieldview === "select")
799
- res.send("<select disabled></select>");
814
+ res.send(`<input readonly class="form-control form-select"></input>`);
800
815
  else if (!fv) res.send("");
801
816
  else if (fv.isEdit || fv.isFilter)
802
817
  res.send(
803
818
  fv.run(
804
819
  field.name,
805
820
  undefined,
806
- { disabled: true, ...configuration, ...(field.attributes || {}) },
821
+ { readonly: true, ...configuration, ...(field.attributes || {}) },
807
822
  "",
808
823
  false,
809
824
  field