@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/tags.js ADDED
@@ -0,0 +1,266 @@
1
+ const { a, text } = require("@saltcorn/markup/tags");
2
+
3
+ const Tag = require("@saltcorn/data/models/tag");
4
+ const Router = require("express-promise-router");
5
+ const Form = require("@saltcorn/data/models/form");
6
+ const User = require("@saltcorn/data/models/user");
7
+
8
+ const { isAdmin, error_catcher, csrfField } = require("./utils");
9
+ const { send_admin_page } = require("../markup/admin");
10
+
11
+ const {
12
+ mkTable,
13
+ post_delete_btn,
14
+ link,
15
+ renderForm,
16
+ } = require("@saltcorn/markup");
17
+
18
+ const {
19
+ tablesList,
20
+ setTableRefs,
21
+ viewsList,
22
+ getPageList,
23
+ getTriggerList,
24
+ } = require("./common_lists");
25
+
26
+ const router = new Router();
27
+ module.exports = router;
28
+
29
+ router.get(
30
+ "/",
31
+ isAdmin,
32
+ error_catcher(async (req, res) => {
33
+ const rows = await Tag.find();
34
+ send_admin_page({
35
+ res,
36
+ req,
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
+ },
46
+ {
47
+ label: req.__("Delete"),
48
+ key: (r) => post_delete_btn(`/tag/delete/${r.id}`, req, r.name),
49
+ },
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
+ });
63
+ })
64
+ );
65
+
66
+ router.get(
67
+ "/new",
68
+ isAdmin,
69
+ error_catcher(async (req, res) => {
70
+ res.sendWrap(req.__(`New tag`), {
71
+ above: [
72
+ {
73
+ type: "card",
74
+ title: req.__(`New tag`),
75
+ contents: renderForm(
76
+ new Form({
77
+ action: "/tag",
78
+ submitLabel: req.__("Create"),
79
+ fields: [
80
+ {
81
+ label: req.__("Tag name"),
82
+ name: "name",
83
+ input_type: "text",
84
+ required: true,
85
+ },
86
+ ],
87
+ }),
88
+ req.csrfToken()
89
+ ),
90
+ },
91
+ ],
92
+ });
93
+ })
94
+ );
95
+
96
+ const headerWithCollapser = (title, cardId, showList) =>
97
+ a(
98
+ {
99
+ class: `card-header-left-collapse ${!showList ? "collapsed" : ""} ps-3`,
100
+ "data-bs-toggle": "collapse",
101
+ href: `#${cardId}`,
102
+ "aria-expanded": "false",
103
+ "aria-controls": cardId,
104
+ role: "button",
105
+ },
106
+ title
107
+ );
108
+
109
+ const isShowList = (showList, listType) => showList === listType;
110
+
111
+ router.get(
112
+ "/:idorname",
113
+ isAdmin,
114
+ error_catcher(async (req, res) => {
115
+ const { idorname } = req.params;
116
+ const { show_list } = req.query;
117
+ const id = parseInt(idorname);
118
+ const tag = await Tag.findOne(id ? { id } : { name: idorname });
119
+ if (!tag) {
120
+ req.flash("error", req.__("Tag not found"));
121
+ return res.redirect(`/tag`);
122
+ }
123
+ const tables = await tag.getTables();
124
+ const views = await tag.getViews();
125
+ await setTableRefs(views);
126
+ const pages = await tag.getPages();
127
+ const trigger = await tag.getTrigger();
128
+ const roles = await User.get_roles();
129
+
130
+ const tablesDomId = "tablesListId";
131
+ const viewsDomId = "viewsListId";
132
+ const pagesDomId = "pagesDomId";
133
+ const triggerDomId = "triggerDomId";
134
+ res.sendWrap(req.__("%s Tag", tag.name), {
135
+ above: [
136
+ {
137
+ type: "breadcrumbs",
138
+ crumbs: [{ text: `Tag: ${tag.name}` }],
139
+ },
140
+ {
141
+ type: "card",
142
+ title: headerWithCollapser(
143
+ req.__("Tables"),
144
+ tablesDomId,
145
+ isShowList(show_list, "tables")
146
+ ),
147
+ contents: [
148
+ await tablesList(tables, req, {
149
+ tagId: tag.id,
150
+ domId: tablesDomId,
151
+ showList: isShowList(show_list, "tables"),
152
+ }),
153
+ a(
154
+ {
155
+ href: `/tag-entries/add/tables/${tag.id}`,
156
+ class: "btn btn-primary",
157
+ },
158
+ req.__("Add tables")
159
+ ),
160
+ ],
161
+ },
162
+ {
163
+ type: "card",
164
+ title: headerWithCollapser(
165
+ req.__("Views"),
166
+ viewsDomId,
167
+ isShowList(show_list, "views")
168
+ ),
169
+ contents: [
170
+ await viewsList(views, req, {
171
+ tagId: tag.id,
172
+ domId: viewsDomId,
173
+ showList: isShowList(show_list, "views"),
174
+ }),
175
+ a(
176
+ {
177
+ href: `/tag-entries/add/views/${tag.id}`,
178
+ class: "btn btn-primary",
179
+ },
180
+ req.__("Add views")
181
+ ),
182
+ ],
183
+ },
184
+ {
185
+ type: "card",
186
+ title: headerWithCollapser(
187
+ req.__("Pages"),
188
+ pagesDomId,
189
+ isShowList(show_list, "pages")
190
+ ),
191
+ contents: [
192
+ getPageList(pages, roles, req, {
193
+ tagId: tag.id,
194
+ domId: pagesDomId,
195
+ showList: isShowList(show_list, "pages"),
196
+ }),
197
+ a(
198
+ {
199
+ href: `/tag-entries/add/pages/${tag.id}`,
200
+ class: "btn btn-primary",
201
+ },
202
+ req.__("Add tages")
203
+ ),
204
+ ],
205
+ },
206
+ {
207
+ type: "card",
208
+ bodyId: "collapseTriggerCard",
209
+ title: headerWithCollapser(
210
+ req.__("Trigger"),
211
+ triggerDomId,
212
+ isShowList(show_list, "trigger")
213
+ ),
214
+ contents: [
215
+ getTriggerList(trigger, req, {
216
+ tagId: tag.id,
217
+ domId: triggerDomId,
218
+ showList: isShowList(show_list, "trigger"),
219
+ }),
220
+ a(
221
+ {
222
+ href: `/tag-entries/add/trigger/${tag.id}`,
223
+ class: "btn btn-primary",
224
+ },
225
+ req.__("Add trigger")
226
+ ),
227
+ ],
228
+ },
229
+ ],
230
+ });
231
+ })
232
+ );
233
+
234
+ // create
235
+ router.post(
236
+ "/",
237
+ isAdmin,
238
+ error_catcher(async (req, res) => {
239
+ const { name } = req.body;
240
+ const tag = await Tag.create({ name });
241
+ req.flash("success", req.__(`Tag %s created`, name));
242
+ res.redirect(`/tag/${tag.id}?show_list=tables`);
243
+ })
244
+ );
245
+
246
+ // delete
247
+ router.post(
248
+ "/delete/:id",
249
+ isAdmin,
250
+ error_catcher(async (req, res) => {
251
+ const { id } = req.params;
252
+ const tag = await Tag.findOne({ id });
253
+ if (!tag) {
254
+ req.flash("error", req.__("Tag not found"));
255
+ return res.redirect("/tag");
256
+ }
257
+ try {
258
+ await tag.delete();
259
+ req.flash("success", req.__("Tag %s deleted", tag.name));
260
+ res.redirect(`/tag`);
261
+ } catch (error) {
262
+ req.flash("error", error.message);
263
+ res.redirect(`/tag`);
264
+ }
265
+ })
266
+ );
package/routes/tenant.js CHANGED
@@ -161,32 +161,32 @@ router.get(
161
161
  req.__(
162
162
  "Hosting on this site is provided for free and with no guarantee of availability or security of your application. "
163
163
  ) +
164
- " " +
165
- req.__(
166
- "This facility is intended solely for you to evaluate the suitability of Saltcorn. "
167
- ) +
168
- " " +
169
- req.__(
170
- "If you would like to store private information that needs to be secure, please use self-hosted Saltcorn. "
171
- ) +
172
- " " +
173
- req.__(
174
- 'See <a href="https://github.com/saltcorn/saltcorn">GitHub repository</a> for instructions<p>'
175
- )
164
+ " " +
165
+ req.__(
166
+ "This facility is intended solely for you to evaluate the suitability of Saltcorn. "
167
+ ) +
168
+ " " +
169
+ req.__(
170
+ "If you would like to store private information that needs to be secure, please use self-hosted Saltcorn. "
171
+ ) +
172
+ " " +
173
+ req.__(
174
+ 'See <a href="https://github.com/saltcorn/saltcorn">GitHub repository</a> for instructions<p>'
175
+ )
176
176
  )
177
177
  );
178
178
 
179
179
  res.sendWrap(
180
180
  req.__("Create application"),
181
181
  create_tenant_warning +
182
- renderForm(tenant_form(req), req.csrfToken()) +
183
- p(
184
- { class: "mt-2" },
185
- req.__("To login to a previously created application, go to: "),
186
- code(`${req.protocol}://`) +
187
- i(req.__("Application name")) +
188
- code("." + req.hostname)
189
- )
182
+ renderForm(tenant_form(req), req.csrfToken()) +
183
+ p(
184
+ { class: "mt-2" },
185
+ req.__("To login to a previously created application, go to: "),
186
+ code(`${req.protocol}://`) +
187
+ i(req.__("Application name")) +
188
+ code("." + req.hostname)
189
+ )
190
190
  );
191
191
  })
192
192
  );
@@ -294,13 +294,13 @@ router.post(
294
294
  " " +
295
295
  hasTemplate
296
296
  ? req.__(
297
- 'Use this link: <a href="%s">%s</a> to revisit your application at any time.',
298
- newurl,
299
- newurl
300
- )
297
+ 'Use this link: <a href="%s">%s</a> to revisit your application at any time.',
298
+ newurl,
299
+ newurl
300
+ )
301
301
  : req.__(
302
- "Use this link to revisit your application at any time."
303
- )
302
+ "Use this link to revisit your application at any time."
303
+ )
304
304
  )
305
305
  )
306
306
  );
@@ -586,7 +586,7 @@ router.get(
586
586
  td(a({ href: info.base_url + "actions" }, info.nactions))
587
587
  ),
588
588
  tr(
589
- th(req.__("Plugins")),
589
+ th(req.__("Modules")),
590
590
  td(a({ href: info.base_url + "plugins" }, info.nplugins))
591
591
  ),
592
592
  tr(
package/routes/utils.js CHANGED
@@ -128,6 +128,7 @@ const setTenant = (req, res, next) => {
128
128
  } else {
129
129
  db.runWithTenant(other_domain, () => {
130
130
  setLanguage(req, res, state);
131
+ state.log(5, `${req.method} ${req.originalUrl}`);
131
132
  next();
132
133
  });
133
134
  }
@@ -140,12 +141,14 @@ const setTenant = (req, res, next) => {
140
141
  } else {
141
142
  db.runWithTenant(ten, () => {
142
143
  setLanguage(req, res, state);
144
+ state.log(5, `${req.method} ${req.originalUrl}`);
143
145
  next();
144
146
  });
145
147
  }
146
148
  }
147
149
  } else {
148
150
  setLanguage(req, res);
151
+ getState().log(5, `${req.method} ${req.originalUrl}`);
149
152
  next();
150
153
  }
151
154
  };
@@ -240,4 +243,5 @@ module.exports = {
240
243
  getGitRevision,
241
244
  getSessionStore,
242
245
  setTenant,
246
+ get_tenant_from_req
243
247
  };
package/routes/view.js CHANGED
@@ -20,6 +20,7 @@ const {
20
20
  } = require("../routes/utils.js");
21
21
  const { add_edit_bar } = require("../markup/admin.js");
22
22
  const { InvalidConfiguration } = require("@saltcorn/data/utils");
23
+ const { getState } = require("@saltcorn/data/db/state");
23
24
 
24
25
  /**
25
26
  * @type {object}
@@ -44,8 +45,11 @@ router.get(
44
45
  const query = { ...req.query };
45
46
  const view = await View.findOne({ name: viewname });
46
47
  const role = req.user && req.user.id ? req.user.role_id : 10;
48
+ const state = getState();
49
+ state.log(3, `Route /view/${viewname} user=${req.user?.id}`);
47
50
  if (!view) {
48
51
  req.flash("danger", req.__(`No such view: %s`, text(viewname)));
52
+ state.log(2, `View ${viewname} not found`);
49
53
  res.redirect("/");
50
54
  return;
51
55
  }
@@ -56,6 +60,7 @@ router.get(
56
60
  !(await view.authorise_get({ query, req, ...view }))
57
61
  ) {
58
62
  req.flash("danger", req.__("Not authorized"));
63
+ state.log(2, `View ${viewname} not authorized`);
59
64
  res.redirect("/");
60
65
  return;
61
66
  }
@@ -123,13 +128,21 @@ router.post(
123
128
  error_catcher(async (req, res) => {
124
129
  const { viewname, route } = req.params;
125
130
  const role = req.user && req.user.id ? req.user.role_id : 10;
131
+ const state = getState();
132
+ state.log(
133
+ 3,
134
+ `Route /view/${viewname} viewroute ${route} user=${req.user?.id}`
135
+ );
126
136
 
127
137
  const view = await View.findOne({ name: viewname });
128
138
  if (!view) {
129
139
  req.flash("danger", req.__(`No such view: %s`, text(viewname)));
140
+ state.log(2, `View ${viewname} not found`);
130
141
  res.redirect("/");
131
142
  } else if (role > view.min_role) {
132
143
  req.flash("danger", req.__("Not authorized"));
144
+ state.log(2, `View ${viewname} viewroute ${route} not authorized`);
145
+
133
146
  res.redirect("/");
134
147
  } else {
135
148
  await view.runRoute(route, req.body, res, { res, req });
@@ -150,10 +163,12 @@ router.post(
150
163
  const { viewname } = req.params;
151
164
  const role = req.user && req.user.id ? req.user.role_id : 10;
152
165
  const query = { ...req.query };
153
-
166
+ const state = getState();
167
+ state.log(3, `Route /view/${viewname} POST user=${req.user?.id}`);
154
168
  const view = await View.findOne({ name: viewname });
155
169
  if (!view) {
156
170
  req.flash("danger", req.__(`No such view: %s`, text(viewname)));
171
+ state.log(2, `View ${viewname} not found`);
157
172
  res.redirect("/");
158
173
  return;
159
174
  }
@@ -164,6 +179,8 @@ router.post(
164
179
  !(await view.authorise_post({ body: req.body, req, ...view }))
165
180
  ) {
166
181
  req.flash("danger", req.__("Not authorized"));
182
+ state.log(2, `View ${viewname} POST not authorized`);
183
+
167
184
  res.redirect("/");
168
185
  } else if (!view.runPost) {
169
186
  throw new InvalidConfiguration(
@@ -31,6 +31,7 @@ const {
31
31
 
32
32
  const { getState } = require("@saltcorn/data/db/state");
33
33
  const { isAdmin, error_catcher } = require("./utils.js");
34
+ const { setTableRefs, viewsList } = require("./common_lists");
34
35
  const Form = require("@saltcorn/data/models/form");
35
36
  const Field = require("@saltcorn/data/models/field");
36
37
  const Table = require("@saltcorn/data/models/table");
@@ -38,10 +39,10 @@ const View = require("@saltcorn/data/models/view");
38
39
  const Workflow = require("@saltcorn/data/models/workflow");
39
40
  const User = require("@saltcorn/data/models/user");
40
41
  const Page = require("@saltcorn/data/models/page");
42
+ const Tag = require("@saltcorn/data/models/tag");
41
43
  const db = require("@saltcorn/data/db");
42
44
 
43
45
  const { add_to_menu } = require("@saltcorn/admin-models/models/pack");
44
- const { editRoleForm } = require("../markup/forms.js");
45
46
 
46
47
  /**
47
48
  * @type {object}
@@ -53,61 +54,6 @@ const { editRoleForm } = require("../markup/forms.js");
53
54
  const router = new Router();
54
55
  module.exports = router;
55
56
 
56
- /**
57
- * @param {object} view
58
- * @param {object[]} roles
59
- * @param {object} req
60
- * @returns {Form}
61
- */
62
- const editViewRoleForm = (view, roles, req) =>
63
- editRoleForm({
64
- url: `/viewedit/setrole/${view.id}`,
65
- current_role: view.min_role,
66
- roles,
67
- req,
68
- });
69
-
70
- /**
71
- * @param {object} view
72
- * @param {object} req
73
- * @returns {div}
74
- */
75
- const view_dropdown = (view, req) =>
76
- settingsDropdown(`dropdownMenuButton${view.id}`, [
77
- a(
78
- {
79
- class: "dropdown-item",
80
- href: `/view/${encodeURIComponent(view.name)}`,
81
- },
82
- '<i class="fas fa-running"></i>&nbsp;' + req.__("Run")
83
- ),
84
- a(
85
- {
86
- class: "dropdown-item",
87
- href: `/viewedit/edit/${encodeURIComponent(view.name)}`,
88
- },
89
- '<i class="fas fa-edit"></i>&nbsp;' + req.__("Edit")
90
- ),
91
- post_dropdown_item(
92
- `/viewedit/add-to-menu/${view.id}`,
93
- '<i class="fas fa-bars"></i>&nbsp;' + req.__("Add to menu"),
94
- req
95
- ),
96
- post_dropdown_item(
97
- `/viewedit/clone/${view.id}`,
98
- '<i class="far fa-copy"></i>&nbsp;' + req.__("Duplicate"),
99
- req
100
- ),
101
- div({ class: "dropdown-divider" }),
102
- post_dropdown_item(
103
- `/viewedit/delete/${view.id}`,
104
- '<i class="far fa-trash-alt"></i>&nbsp;' + req.__("Delete"),
105
- req,
106
- true,
107
- view.name
108
- ),
109
- ]);
110
-
111
57
  /**
112
58
  * @name get
113
59
  * @function
@@ -118,77 +64,18 @@ router.get(
118
64
  "/",
119
65
  isAdmin,
120
66
  error_catcher(async (req, res) => {
121
- var orderBy = "name";
67
+ let orderBy = "name";
122
68
  if (req.query._sortby === "viewtemplate") orderBy = "viewtemplate";
69
+ const views = await View.find({}, { orderBy, nocase: true });
70
+ await setTableRefs(views);
123
71
 
124
- var views = await View.find({}, { orderBy, nocase: true });
125
- const tables = await Table.find();
126
- const getTable = (tid) => tables.find((t) => t.id === tid).name;
127
- views.forEach((v) => {
128
- if (v.table_id) v.table = getTable(v.table_id);
129
- else if (v.exttable_name) v.table = v.exttable_name;
130
- else v.table = "";
131
- });
132
72
  if (req.query._sortby === "table")
133
73
  views.sort((a, b) =>
134
74
  a.table.toLowerCase() > b.table.toLowerCase() ? 1 : -1
135
75
  );
136
- const roles = await User.get_roles();
137
76
 
138
- const viewMarkup =
139
- views.length > 0
140
- ? mkTable(
141
- [
142
- {
143
- label: req.__("Name"),
144
- key: (r) => link(`/view/${encodeURIComponent(r.name)}`, r.name),
145
- sortlink: `javascript:set_state_field('_sortby', 'name')`,
146
- },
147
- // description - currently I dont want to show description in view list
148
- // because description can be long
149
- /*
150
- {
151
- label: req.__("Description"),
152
- key: "description",
153
- // this is sorting by column
154
- sortlink: `javascript:set_state_field('_sortby', 'description')`,
155
- },
156
- */
157
- // template
158
- {
159
- label: req.__("Template"),
160
- key: "viewtemplate",
161
- sortlink: `javascript:set_state_field('_sortby', 'viewtemplate')`,
162
- },
163
- {
164
- label: req.__("Table"),
165
- key: (r) => link(`/table/${r.table}`, r.table),
166
- sortlink: `javascript:set_state_field('_sortby', 'table')`,
167
- },
168
- {
169
- label: req.__("Role to access"),
170
- key: (row) => editViewRoleForm(row, roles, req),
171
- },
172
- {
173
- label: "",
174
- key: (r) =>
175
- link(
176
- `/viewedit/config/${encodeURIComponent(r.name)}`,
177
- req.__("Configure")
178
- ),
179
- },
180
- {
181
- label: "",
182
- key: (r) => view_dropdown(r, req),
183
- },
184
- ],
185
- views,
186
- { hover: true }
187
- )
188
- : div(
189
- h4(req.__("No views defined")),
190
- p(req.__("Views define how table rows are displayed to the user"))
191
- );
77
+ const viewMarkup = await viewsList(views, req);
78
+ const tables = await Table.find();
192
79
  res.sendWrap(req.__(`Views`), {
193
80
  above: [
194
81
  {
@@ -263,10 +150,12 @@ const viewForm = async (req, tableOptions, roles, pages, values) => {
263
150
  ),
264
151
  }),
265
152
  new Field({
266
- label: req.__("Template"),
153
+ label: req.__("View pattern"),
267
154
  name: "viewtemplate",
268
155
  input_type: "select",
269
- sublabel: req.__("Views are based on a view template"),
156
+ sublabel: req.__(
157
+ "The view pattern sets the foundation of how the view relates to the table and the behaviour of the view"
158
+ ),
270
159
  options: Object.keys(getState().viewtemplates),
271
160
  attributes: {
272
161
  explainers: mapObjectValues(
@@ -320,15 +209,15 @@ const viewForm = async (req, tableOptions, roles, pages, values) => {
320
209
  }),
321
210
  ...(isEdit
322
211
  ? [
323
- new Field({
324
- name: "viewtemplate",
325
- input_type: "hidden",
326
- }),
327
- new Field({
328
- name: "table_name",
329
- input_type: "hidden",
330
- }),
331
- ]
212
+ new Field({
213
+ name: "viewtemplate",
214
+ input_type: "hidden",
215
+ }),
216
+ new Field({
217
+ name: "table_name",
218
+ input_type: "hidden",
219
+ }),
220
+ ]
332
221
  : []),
333
222
  ],
334
223
  values,
@@ -355,7 +244,8 @@ router.get(
355
244
  }
356
245
  const tables = await Table.find_with_external();
357
246
  const currentTable = tables.find(
358
- (t) => t.id === viewrow.table_id || t.name === viewrow.exttable_name
247
+ (t) =>
248
+ (t.id && t.id === viewrow.table_id) || t.name === viewrow.exttable_name
359
249
  );
360
250
  viewrow.table_name = currentTable && currentTable.name;
361
251
  if (viewrow.slug && currentTable) {