@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.
@@ -54,9 +54,9 @@ const tableCard = (tables, req) => ({
54
54
  contents:
55
55
  (tables.length <= 1
56
56
  ? p(
57
- { class: "mt-2 pe-2" },
58
- i(req.__("Tables organise data by fields and rows."))
59
- )
57
+ { class: "mt-2 pe-2" },
58
+ i(req.__("Tables organise data by fields and rows."))
59
+ )
60
60
  : "") + tableTable(tables, req),
61
61
  bodyClass: "py-0 pe-0",
62
62
  footer: div(
@@ -107,13 +107,13 @@ const viewCard = (views, req) => ({
107
107
  contents:
108
108
  (views.length <= 1
109
109
  ? p(
110
- { class: "mt-2 pe-2" },
111
- i(
112
- req.__(
113
- "Views display data from tables. A view is a view template applied to a table, with configuration."
114
- )
110
+ { class: "mt-2 pe-2" },
111
+ i(
112
+ req.__(
113
+ "Views display data from tables. A view is a view pattern applied to a table, with configuration."
115
114
  )
116
115
  )
116
+ )
117
117
  : "") +
118
118
  (views.length > 0 ? viewTable(views, req) : p(req.__("No views"))),
119
119
 
@@ -160,13 +160,13 @@ const pageCard = (pages, req) => ({
160
160
  contents:
161
161
  (pages.length <= 1
162
162
  ? p(
163
- { class: "mt-2 pe-2" },
164
- i(
165
- req.__(
166
- "Pages are the web pages of your application built with a drag-and-drop builder. They have static content, and by embedding views, dynamic content."
167
- )
163
+ { class: "mt-2 pe-2" },
164
+ i(
165
+ req.__(
166
+ "Pages are the web pages of your application built with a drag-and-drop builder. They have static content, and by embedding views, dynamic content."
168
167
  )
169
168
  )
169
+ )
170
170
  : "") +
171
171
  (pages.length > 0
172
172
  ? pageTable(pages, req)
@@ -191,16 +191,16 @@ const filesTab = async (req) => {
191
191
  files.length === 0
192
192
  ? p(req.__("No files"))
193
193
  : mkTable(
194
- [
195
- {
196
- label: req.__("Filename"),
197
- key: (r) => link(`/files/serve/${r.id}`, r.filename),
198
- },
199
- { label: req.__("Size (KiB)"), key: "size_kb", align: "right" },
200
- { label: req.__("Media type"), key: (r) => r.mimetype },
201
- ],
202
- files
203
- ),
194
+ [
195
+ {
196
+ label: req.__("Filename"),
197
+ key: (r) => link(`/files/serve/${r.id}`, r.filename),
198
+ },
199
+ { label: req.__("Size (KiB)"), key: "size_kb", align: "right" },
200
+ { label: req.__("Media type"), key: (r) => r.mimetype },
201
+ ],
202
+ files
203
+ ),
204
204
  fileUploadForm(req)
205
205
  );
206
206
  };
@@ -244,30 +244,30 @@ const actionsTab = async (req, triggers) => {
244
244
  return div(
245
245
  { class: "pb-3" },
246
246
  triggers.length <= 1 &&
247
- p(
248
- { class: "mt-2 pe-2" },
249
- i(req.__("Triggers run actions in response to events."))
250
- ),
247
+ p(
248
+ { class: "mt-2 pe-2" },
249
+ i(req.__("Triggers run actions in response to events."))
250
+ ),
251
251
  triggers.length === 0
252
252
  ? p(req.__("No triggers"))
253
253
  : mkTable(
254
- [
255
- { label: req.__("Name"), key: "name" },
256
- { label: req.__("Action"), key: "action" },
257
- {
258
- label: req.__("Table or Channel"),
259
- key: (r) => r.table_name || r.channel,
260
- },
261
- {
262
- label: req.__("When"),
263
- key: (a) =>
264
- a.when_trigger === "API call"
265
- ? `API: ${base_url}api/action/${a.name}`
266
- : a.when_trigger,
267
- },
268
- ],
269
- triggers
270
- ),
254
+ [
255
+ { label: req.__("Name"), key: "name" },
256
+ { label: req.__("Action"), key: "action" },
257
+ {
258
+ label: req.__("Table or Channel"),
259
+ key: (r) => r.table_name || r.channel,
260
+ },
261
+ {
262
+ label: req.__("When"),
263
+ key: (a) =>
264
+ a.when_trigger === "API call"
265
+ ? `API: ${base_url}api/action/${a.name}`
266
+ : a.when_trigger,
267
+ },
268
+ ],
269
+ triggers
270
+ ),
271
271
  a(
272
272
  { href: "/actions/new", class: "btn btn-secondary my-3" },
273
273
  req.__("Add trigger")
@@ -385,15 +385,15 @@ const welcome_page = async (req) => {
385
385
  tabContents:
386
386
  triggers.length > 0
387
387
  ? {
388
- Triggers: await actionsTab(req, triggers),
389
- Files: await filesTab(req),
390
- Packs: packTab(req, packlist),
391
- }
388
+ Triggers: await actionsTab(req, triggers),
389
+ Files: await filesTab(req),
390
+ Packs: packTab(req, packlist),
391
+ }
392
392
  : {
393
- Packs: packTab(req, packlist),
394
- Triggers: await actionsTab(req, triggers),
395
- Files: await filesTab(req),
396
- },
393
+ Packs: packTab(req, packlist),
394
+ Triggers: await actionsTab(req, triggers),
395
+ Files: await filesTab(req),
396
+ },
397
397
  },
398
398
  {
399
399
  type: "card",
@@ -403,13 +403,13 @@ const welcome_page = async (req) => {
403
403
  tabContents:
404
404
  users.length > 4
405
405
  ? {
406
- Users: await usersTab(req, users, roleMap),
407
- Help: helpCard(req),
408
- }
406
+ Users: await usersTab(req, users, roleMap),
407
+ Help: helpCard(req),
408
+ }
409
409
  : {
410
- Help: helpCard(req),
411
- Users: await usersTab(req, users, roleMap),
412
- },
410
+ Help: helpCard(req),
411
+ Users: await usersTab(req, users, roleMap),
412
+ },
413
413
  },
414
414
  ],
415
415
  },
@@ -440,8 +440,8 @@ const no_views_logged_in = async (req, res) => {
440
440
  packagejson.version,
441
441
  latest
442
442
  ) +
443
- " " +
444
- a({ href: "/admin/system" }, req.__("Upgrade here"))
443
+ " " +
444
+ a({ href: "/admin/system" }, req.__("Upgrade here"))
445
445
  );
446
446
 
447
447
  res.sendWrap(req.__("Hello"), await welcome_page(req));
package/routes/index.js CHANGED
@@ -70,6 +70,8 @@ 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");
73
75
 
74
76
  module.exports =
75
77
  /**
@@ -106,4 +108,6 @@ module.exports =
106
108
  app.use("/useradmin", useradmin);
107
109
  app.use("/roleadmin", roleadmin);
108
110
  app.use("/scapi", scapi);
111
+ app.use("/tag", tags);
112
+ app.use("/tag-entries", tagentries);
109
113
  };
package/routes/menu.js CHANGED
@@ -22,6 +22,9 @@ const { renderForm } = require("@saltcorn/markup");
22
22
  const { script, domReady, div, ul } = require("@saltcorn/markup/tags");
23
23
  const { send_infoarch_page } = require("../markup/admin.js");
24
24
  const Table = require("@saltcorn/data/models/table");
25
+ const Trigger = require("@saltcorn/data/models/trigger");
26
+ const { run_action_column } = require("@saltcorn/data/plugin-helper");
27
+
25
28
 
26
29
  /**
27
30
  * @type {object}
@@ -61,6 +64,18 @@ const menuForm = async (req) => {
61
64
  dynSectionFieldOptions[table.name].push(field.name);
62
65
  }
63
66
  }
67
+ const stateActions = getState().actions;
68
+ const actions = [
69
+ ...Object.entries(stateActions)
70
+ .filter(([k, v]) => !v.requireRow && !v.disableInBuilder)
71
+ .map(([k, v]) => k),
72
+ ];
73
+ const triggers = await Trigger.find({
74
+ when_trigger: { or: ["API call", "Never"] },
75
+ });
76
+ triggers.forEach((tr) => {
77
+ actions.push(tr.name);
78
+ });
64
79
 
65
80
  return new Form({
66
81
  action: "/menu/",
@@ -92,6 +107,7 @@ const menuForm = async (req) => {
92
107
  "Dynamic",
93
108
  "Search",
94
109
  "Separator",
110
+ "Action"
95
111
  ],
96
112
  },
97
113
  {
@@ -101,7 +117,7 @@ const menuForm = async (req) => {
101
117
  input_type: "text",
102
118
  required: true,
103
119
  showIf: {
104
- type: ["View", "Page", "Link", "Header", "Dynamic", "Search"],
120
+ type: ["View", "Page", "Link", "Header", "Dynamic", "Search", "Action"],
105
121
  },
106
122
  },
107
123
  {
@@ -111,7 +127,7 @@ const menuForm = async (req) => {
111
127
  attributes: {
112
128
  html: `<button type="button" id="myEditor_icon" class="btn btn-outline-secondary"></button>`,
113
129
  },
114
- showIf: { type: ["View", "Page", "Link", "Header"] },
130
+ showIf: { type: ["View", "Page", "Link", "Header", "Action"] },
115
131
  },
116
132
  {
117
133
  name: "icon",
@@ -149,6 +165,17 @@ const menuForm = async (req) => {
149
165
  attributes: { options: views.map((r) => r.select_option) },
150
166
  showIf: { type: "View" },
151
167
  },
168
+ {
169
+ name: "action_name",
170
+ label: req.__("Action"),
171
+ type: "String",
172
+ class: "item-menu",
173
+ required: true,
174
+ attributes: {
175
+ options: actions,
176
+ },
177
+ showIf: { type: "Action" },
178
+ },
152
179
  {
153
180
  name: "dyn_table",
154
181
  label: req.__("Table"),
@@ -217,7 +244,7 @@ const menuForm = async (req) => {
217
244
  class: "item-menu",
218
245
  type: "String",
219
246
  required: true,
220
- showIf: { type: ["View", "Page", "Link", "Header", "Dynamic"] },
247
+ showIf: { type: ["View", "Page", "Link", "Header", "Dynamic", "Action"] },
221
248
  attributes: {
222
249
  options: [
223
250
  { name: "", label: "Link" },
@@ -239,7 +266,7 @@ const menuForm = async (req) => {
239
266
  {
240
267
  name: "location",
241
268
  label: req.__("Location"),
242
- showIf: { type: ["View", "Page", "Link", "Header", "Dynamic"] },
269
+ showIf: { type: ["View", "Page", "Link", "Header", "Dynamic", "Action"] },
243
270
  sublabel: req.__("Not all themes support all locations"),
244
271
  class: "item-menu",
245
272
  type: "String",
@@ -404,3 +431,37 @@ router.post(
404
431
  res.json({ success: true });
405
432
  })
406
433
  );
434
+
435
+ router.post(
436
+ "/runaction/:name",
437
+ error_catcher(async (req, res) => {
438
+ const { name } = req.params;
439
+ const role = (req.user || {}).role_id || 10;
440
+ const state = getState();
441
+ const menu_items = state.getConfig("menu_items");
442
+ let menu_item;
443
+ const search = items =>
444
+ items
445
+ .filter((item) => role <= +item.min_role)
446
+ .forEach(item => {
447
+ if (item.type === "Action" && item.action_name === name)
448
+ menu_item = item;
449
+ else if (item.subitems)
450
+ search(item.subitems);
451
+ })
452
+ search(menu_items);
453
+ if (menu_item)
454
+ try {
455
+ const result = await run_action_column({
456
+ col: menu_item,
457
+ referrer: req.get("Referrer"),
458
+ req,
459
+ });
460
+ res.json({ success: "ok", ...(result || {}) });
461
+ } catch (e) {
462
+ res.status(400).json({ error: e.message || e });
463
+ }
464
+
465
+ else res.status(404).json({ error: "Action not found" });
466
+ })
467
+ );
package/routes/packs.js CHANGED
@@ -116,7 +116,7 @@ router.get(
116
116
  type: "breadcrumbs",
117
117
  crumbs: [
118
118
  { text: req.__("Settings") },
119
- { text: req.__("Plugins"), href: "/plugins" },
119
+ { text: req.__("Modules"), href: "/plugins" },
120
120
  { text: req.__("Create pack") },
121
121
  ],
122
122
  },
@@ -184,7 +184,7 @@ router.post(
184
184
  type: "breadcrumbs",
185
185
  crumbs: [
186
186
  { text: req.__("Settings") },
187
- { text: req.__("Plugins"), href: "/plugins" },
187
+ { text: req.__("Modules"), href: "/plugins" },
188
188
  { text: req.__("Create pack") },
189
189
  ],
190
190
  },
@@ -242,7 +242,7 @@ router.get(
242
242
  type: "breadcrumbs",
243
243
  crumbs: [
244
244
  { text: req.__("Settings") },
245
- { text: req.__("Plugins"), href: "/plugins" },
245
+ { text: req.__("Modules"), href: "/plugins" },
246
246
  { text: req.__("Install pack") },
247
247
  ],
248
248
  },
@@ -293,7 +293,7 @@ router.post(
293
293
  type: "breadcrumbs",
294
294
  crumbs: [
295
295
  { text: req.__("Settings") },
296
- { text: req.__("Plugins"), href: "/plugins" },
296
+ { text: req.__("Modules"), href: "/plugins" },
297
297
  { text: req.__("Install pack") },
298
298
  ],
299
299
  },
package/routes/page.js CHANGED
@@ -36,6 +36,8 @@ router.get(
36
36
  "/:pagename",
37
37
  error_catcher(async (req, res) => {
38
38
  const { pagename } = req.params;
39
+ const state = getState();
40
+ state.log(3, `Route /page/${pagename} user=${req.user?.id}`);
39
41
 
40
42
  const role = req.user && req.user.id ? req.user.role_id : 10;
41
43
  const db_page = await Page.findOne({ name: pagename });
@@ -56,10 +58,12 @@ router.get(
56
58
  contents,
57
59
  })
58
60
  );
59
- } else
61
+ } else {
62
+ state.log(2, `Page $pagename} not found or not authorized`);
60
63
  res
61
64
  .status(404)
62
65
  .sendWrap(`${pagename} page`, req.__("Page %s not found", pagename));
66
+ }
63
67
  })
64
68
  );
65
69
 
@@ -19,6 +19,7 @@ const Trigger = require("@saltcorn/data/models/trigger");
19
19
  const { getViews, traverseSync } = require("@saltcorn/data/models/layout");
20
20
  const { add_to_menu } = require("@saltcorn/admin-models/models/pack");
21
21
  const db = require("@saltcorn/data/db");
22
+ const { getPageList } = require("./common_lists");
22
23
 
23
24
  const { isAdmin, error_catcher } = require("./utils.js");
24
25
  const {
@@ -32,7 +33,6 @@ const {
32
33
  settingsDropdown,
33
34
  } = require("@saltcorn/markup");
34
35
  const { getActionConfigFields } = require("@saltcorn/data/plugin-helper");
35
- const { editRoleForm, wizardCardTitle } = require("../markup/forms.js");
36
36
  const Library = require("@saltcorn/data/models/library");
37
37
 
38
38
  /**
@@ -45,61 +45,6 @@ const Library = require("@saltcorn/data/models/library");
45
45
  const router = new Router();
46
46
  module.exports = router;
47
47
 
48
- /**
49
- * @param {object} page
50
- * @param {*} roles
51
- * @param {object} req
52
- * @returns {Form}
53
- */
54
- const editPageRoleForm = (page, roles, req) =>
55
- editRoleForm({
56
- url: `/pageedit/setrole/${page.id}`,
57
- current_role: page.min_role,
58
- roles,
59
- req,
60
- });
61
-
62
- /**
63
- * @param {object} page
64
- * @param {object} req
65
- * @returns {string}
66
- */
67
- const page_dropdown = (page, req) =>
68
- settingsDropdown(`dropdownMenuButton${page.id}`, [
69
- a(
70
- {
71
- class: "dropdown-item",
72
- href: `/page/${encodeURIComponent(page.name)}`,
73
- },
74
- '<i class="fas fa-running"></i>&nbsp;' + req.__("Run")
75
- ),
76
- a(
77
- {
78
- class: "dropdown-item",
79
- href: `/pageedit/edit-properties/${encodeURIComponent(page.name)}`,
80
- },
81
- '<i class="fas fa-edit"></i>&nbsp;' + req.__("Edit properties")
82
- ),
83
- post_dropdown_item(
84
- `/pageedit/add-to-menu/${page.id}`,
85
- '<i class="fas fa-bars"></i>&nbsp;' + req.__("Add to menu"),
86
- req
87
- ),
88
- post_dropdown_item(
89
- `/pageedit/clone/${page.id}`,
90
- '<i class="far fa-copy"></i>&nbsp;' + req.__("Duplicate"),
91
- req
92
- ),
93
- div({ class: "dropdown-divider" }),
94
- post_dropdown_item(
95
- `/pageedit/delete/${page.id}`,
96
- '<i class="far fa-trash-alt"></i>&nbsp;' + req.__("Delete"),
97
- req,
98
- true,
99
- page.name
100
- ),
101
- ]);
102
-
103
48
  /**
104
49
  *
105
50
  * @param {object} req
@@ -180,7 +125,8 @@ const pageBuilderData = async (req, context) => {
180
125
  const fixed_state_fields = {};
181
126
  for (const view of views) {
182
127
  fixed_state_fields[view.name] = [];
183
- const table = Table.findOne({ id: view.table_id });
128
+ const table = Table.findOne(view.table_id || view.exttable_name);
129
+
184
130
  const fs = await view.get_state_fields();
185
131
  for (const frec of fs) {
186
132
  const f = new Field(frec);
@@ -228,46 +174,6 @@ const pageBuilderData = async (req, context) => {
228
174
  };
229
175
  };
230
176
 
231
- /**
232
- * @param {*} rows
233
- * @param {*} roles
234
- * @param {object} req
235
- * @returns {div}
236
- */
237
- const getPageList = (rows, roles, req) => {
238
- return div(
239
- mkTable(
240
- [
241
- {
242
- label: req.__("Name"),
243
- key: (r) => link(`/page/${r.name}`, r.name),
244
- },
245
- {
246
- label: req.__("Role to access"),
247
- key: (row) => editPageRoleForm(row, roles, req),
248
- },
249
- {
250
- label: req.__("Edit"),
251
- key: (r) => link(`/pageedit/edit/${r.name}`, req.__("Edit")),
252
- },
253
- {
254
- label: "",
255
- key: (r) => page_dropdown(r, req),
256
- },
257
- ],
258
- rows,
259
- { hover: true }
260
- ),
261
- a(
262
- {
263
- href: `/pageedit/new`,
264
- class: "btn btn-primary",
265
- },
266
- req.__("Create page")
267
- )
268
- );
269
- };
270
-
271
177
  /**
272
178
  * Root pages configuration Form
273
179
  * Allows to configure root page for each role
@@ -327,7 +233,16 @@ router.get(
327
233
  type: "card",
328
234
  title: req.__("Your pages"),
329
235
  class: "mt-0",
330
- contents: getPageList(pages, roles, req),
236
+ contents: div(
237
+ getPageList(pages, roles, req),
238
+ a(
239
+ {
240
+ href: `/pageedit/new`,
241
+ class: "btn btn-primary",
242
+ },
243
+ req.__("Create page")
244
+ )
245
+ ),
331
246
  },
332
247
  {
333
248
  type: "card",