@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.
@@ -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,68 +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
- a(
94
- {
95
- class: "dropdown-item",
96
- href: `javascript:ajax_modal('/admin/snapshot-restore/page/${page.name}')`,
97
- },
98
- '<i class="fas fa-undo-alt"></i>&nbsp;' + req.__("Restore")
99
- ),
100
- div({ class: "dropdown-divider" }),
101
- post_dropdown_item(
102
- `/pageedit/delete/${page.id}`,
103
- '<i class="far fa-trash-alt"></i>&nbsp;' + req.__("Delete"),
104
- req,
105
- true,
106
- page.name
107
- ),
108
- ]);
109
-
110
48
  /**
111
49
  *
112
50
  * @param {object} req
@@ -188,7 +126,7 @@ const pageBuilderData = async (req, context) => {
188
126
  for (const view of views) {
189
127
  fixed_state_fields[view.name] = [];
190
128
  const table = Table.findOne(view.table_id || view.exttable_name);
191
-
129
+
192
130
  const fs = await view.get_state_fields();
193
131
  for (const frec of fs) {
194
132
  const f = new Field(frec);
@@ -236,46 +174,6 @@ const pageBuilderData = async (req, context) => {
236
174
  };
237
175
  };
238
176
 
239
- /**
240
- * @param {*} rows
241
- * @param {*} roles
242
- * @param {object} req
243
- * @returns {div}
244
- */
245
- const getPageList = (rows, roles, req) => {
246
- return div(
247
- mkTable(
248
- [
249
- {
250
- label: req.__("Name"),
251
- key: (r) => link(`/page/${r.name}`, r.name),
252
- },
253
- {
254
- label: req.__("Role to access"),
255
- key: (row) => editPageRoleForm(row, roles, req),
256
- },
257
- {
258
- label: req.__("Edit"),
259
- key: (r) => link(`/pageedit/edit/${r.name}`, req.__("Edit")),
260
- },
261
- {
262
- label: "",
263
- key: (r) => page_dropdown(r, req),
264
- },
265
- ],
266
- rows,
267
- { hover: true }
268
- ),
269
- a(
270
- {
271
- href: `/pageedit/new`,
272
- class: "btn btn-primary",
273
- },
274
- req.__("Create page")
275
- )
276
- );
277
- };
278
-
279
177
  /**
280
178
  * Root pages configuration Form
281
179
  * Allows to configure root page for each role
@@ -287,9 +185,8 @@ const getPageList = (rows, roles, req) => {
287
185
  const getRootPageForm = (pages, roles, req) => {
288
186
  const form = new Form({
289
187
  action: "/pageedit/set_root_page",
290
- submitLabel: req.__("Save"),
291
- submitButtonClass: "btn-outline-primary",
292
- onChange: "remove_outline(this)",
188
+ noSubmitButton: true,
189
+ onChange: "saveAndContinue(this)",
293
190
  blurb: req.__(
294
191
  "The root page is the page that is served when the user visits the home location (/). This can be set for each user role."
295
192
  ),
@@ -335,7 +232,16 @@ router.get(
335
232
  type: "card",
336
233
  title: req.__("Your pages"),
337
234
  class: "mt-0",
338
- contents: getPageList(pages, roles, req),
235
+ contents: div(
236
+ getPageList(pages, roles, req),
237
+ a(
238
+ {
239
+ href: `/pageedit/new`,
240
+ class: "btn btn-primary",
241
+ },
242
+ req.__("Create page")
243
+ )
244
+ ),
339
245
  },
340
246
  {
341
247
  type: "card",
package/routes/plugins.js CHANGED
@@ -455,7 +455,7 @@ const store_actions_dropdown = (req) =>
455
455
  {
456
456
  class: "dropdown-item",
457
457
  href: `/plugins/upgrade`,
458
- onClick: `notifyAlert('Upgrading modules...', true)`,
458
+ onClick: `notifyAlert('${req.__("Upgrading modules...")}', true)`,
459
459
  },
460
460
  '<i class="far fa-arrow-alt-circle-up"></i>&nbsp;' +
461
461
  req.__("Upgrade installed modules")
@@ -551,7 +551,7 @@ router.get(
551
551
  const { name } = req.params;
552
552
  const plugin = await Plugin.findOne({ name: decodeURIComponent(name) });
553
553
  if (!plugin) {
554
- req.flash("warning", "Plugin not found");
554
+ req.flash("warning", req.__("Module not found"));
555
555
  res.redirect("/plugins");
556
556
  return;
557
557
  }
@@ -792,7 +792,7 @@ router.get(
792
792
  pkgjson = require(path.join(mod.location, "package.json"));
793
793
 
794
794
  if (!plugin_db) {
795
- req.flash("warning", "Plugin not found");
795
+ req.flash("warning", "Module not found");
796
796
  res.redirect("/plugins");
797
797
  return;
798
798
  }
@@ -817,7 +817,7 @@ router.get(
817
817
  ),
818
818
  mod.plugin_module.dependencies
819
819
  ? tr(
820
- th(req.__("Plugin dependencies")),
820
+ th(req.__("Module dependencies")),
821
821
  td(
822
822
  mod.plugin_module.dependencies.map((d) =>
823
823
  span({ class: "badge bg-primary me-1" }, d)
@@ -933,7 +933,7 @@ router.get(
933
933
 
934
934
  const plugin = await Plugin.findOne({ name });
935
935
  await plugin.upgrade_version((p, f) => load_plugins.loadPlugin(p, f));
936
- req.flash("success", req.__(`Plugin up-to-date`));
936
+ req.flash("success", req.__(`Module up-to-date`));
937
937
 
938
938
  res.redirect(`/plugins/info/${plugin.name}`);
939
939
  })
@@ -954,7 +954,7 @@ router.post(
954
954
  if (schema !== db.connectObj.default_schema) {
955
955
  req.flash(
956
956
  "error",
957
- req.__(`Only store plugins can be installed on tenant instances`)
957
+ req.__(`Only store modules can be installed on tenant instances`)
958
958
  );
959
959
  res.redirect(`/plugins`);
960
960
  } else {
@@ -963,12 +963,12 @@ router.post(
963
963
  plugin,
964
964
  schema === db.connectObj.default_schema || plugin.source === "github"
965
965
  );
966
- req.flash("success", req.__(`Plugin %s installed`, plugin.name));
966
+ req.flash("success", req.__(`Module %s installed`, plugin.name));
967
967
  res.redirect(`/plugins`);
968
968
  } catch (e) {
969
969
  req.flash("error", `${e.message}`);
970
970
  const form = pluginForm(req, plugin);
971
- res.sendWrap(req.__(`Edit Plugin`), renderForm(form, req.csrfToken()));
971
+ res.sendWrap(req.__(`Edit Module`), renderForm(form, req.csrfToken()));
972
972
  }
973
973
  }
974
974
  })
@@ -988,7 +988,7 @@ router.post(
988
988
 
989
989
  const plugin = await Plugin.findOne({ name: decodeURIComponent(name) });
990
990
  if (!plugin) {
991
- req.flash("warning", "Plugin not found");
991
+ req.flash("warning", "Module not found");
992
992
  res.redirect("/plugins");
993
993
  return;
994
994
  }
@@ -998,11 +998,11 @@ router.post(
998
998
  getState().getConfig("development_mode", false)
999
999
  ) {
1000
1000
  await plugin.delete();
1001
- req.flash("success", req.__(`Plugin %s removed.`, plugin.name));
1001
+ req.flash("success", req.__(`Module %s removed.`, plugin.name));
1002
1002
  } else {
1003
1003
  req.flash(
1004
1004
  "error",
1005
- req.__(`Cannot remove plugin: views %s depend on it`, depviews.join())
1005
+ req.__(`Cannot remove module: views %s depend on it`, depviews.join())
1006
1006
  );
1007
1007
  }
1008
1008
  res.redirect(`/plugins`);
@@ -1025,7 +1025,7 @@ router.post(
1025
1025
  if (!plugin) {
1026
1026
  req.flash(
1027
1027
  "error",
1028
- req.__(`Plugin %s not found`, text(decodeURIComponent(name)))
1028
+ req.__(`Module %s not found`, text(decodeURIComponent(name)))
1029
1029
  );
1030
1030
  res.redirect(`/plugins`);
1031
1031
  return;
@@ -1034,7 +1034,7 @@ router.post(
1034
1034
  if (!isRoot && plugin.unsafe) {
1035
1035
  req.flash(
1036
1036
  "error",
1037
- req.__("Cannot install unsafe plugins on subdomain tenants")
1037
+ req.__("Cannot install unsafe modules on subdomain tenants")
1038
1038
  );
1039
1039
  res.redirect(`/plugins`);
1040
1040
  return;
@@ -1047,14 +1047,14 @@ router.post(
1047
1047
  req.flash(
1048
1048
  "success",
1049
1049
  req.__(
1050
- `Plugin %s installed, please complete configuration.`,
1050
+ `Module %s installed, please complete configuration.`,
1051
1051
  plugin_db.name
1052
1052
  )
1053
1053
  );
1054
1054
  await sleep(1000); // Allow other workers to load this plugin
1055
1055
  res.redirect(`/plugins/configure/${plugin_db.name}`);
1056
1056
  } else {
1057
- req.flash("success", req.__(`Plugin %s installed`, plugin.name));
1057
+ req.flash("success", req.__(`Module %s installed`, plugin.name));
1058
1058
  res.redirect(`/plugins`);
1059
1059
  }
1060
1060
  })
package/routes/tables.js CHANGED
@@ -56,6 +56,7 @@ const {
56
56
  } = require("@saltcorn/data/models/discovery");
57
57
  const { getState } = require("@saltcorn/data/db/state");
58
58
  const { cardHeaderTabs } = require("@saltcorn/markup/layout_utils");
59
+ const { tablesList } = require("./common_lists");
59
60
 
60
61
  /**
61
62
  * @type {object}
@@ -129,7 +130,7 @@ const tableForm = async (table, req) => {
129
130
  {
130
131
  label: req.__("Minimum role to read"),
131
132
  sublabel: req.__(
132
- "User must have this role or higher to read rows from the table"
133
+ "User must have this role or higher to read rows from the table, unless they are the owner"
133
134
  ),
134
135
  name: "min_role_read",
135
136
  input_type: "select",
@@ -143,7 +144,7 @@ const tableForm = async (table, req) => {
143
144
  name: "min_role_write",
144
145
  input_type: "select",
145
146
  sublabel: req.__(
146
- "User must have this role or higher to edit or create new rows in the table"
147
+ "User must have this role or higher to edit or create new rows in the table, unless they are the owner"
147
148
  ),
148
149
  options: roleOptions,
149
150
  },
@@ -888,7 +889,6 @@ router.post(
888
889
  if (rest.ownership_field_id === "_formula") {
889
890
  rest.ownership_field_id = null;
890
891
  const fmlValidRes = expressionValidator(rest.ownership_formula);
891
- console.log({ fmlValidRes });
892
892
  if (typeof fmlValidRes === "string") {
893
893
  req.flash(
894
894
  "error",
@@ -1011,44 +1011,8 @@ router.get(
1011
1011
  const rows = await Table.find_with_external({}, { orderBy: "name" });
1012
1012
  const roles = await User.get_roles();
1013
1013
  const getRole = (rid) => roles.find((r) => r.id === rid).role;
1014
- const mainCard =
1015
- rows.length > 0
1016
- ? mkTable(
1017
- [
1018
- {
1019
- label: req.__("Name"),
1020
- key: (r) => link(`/table/${r.id || r.name}`, text(r.name)),
1021
- },
1022
- {
1023
- label: "",
1024
- key: (r) => tableBadges(r, req),
1025
- },
1026
- {
1027
- label: req.__("Access Read/Write"),
1028
- key: (t) =>
1029
- t.external
1030
- ? `${getRole(t.min_role_read)} (read only)`
1031
- : `${getRole(t.min_role_read)}/${getRole(
1032
- t.min_role_write
1033
- )}`,
1034
- },
1035
- {
1036
- label: req.__("Delete"),
1037
- key: (r) =>
1038
- r.name === "users" || r.external
1039
- ? ""
1040
- : post_delete_btn(`/table/delete/${r.id}`, req, r.name),
1041
- },
1042
- ],
1043
- rows,
1044
- { hover: true }
1045
- )
1046
- : div(
1047
- h4(req.__("No tables defined")),
1048
- p(req.__("Tables hold collections of similar data"))
1049
- );
1014
+ const mainCard = await tablesList(rows, req);
1050
1015
  const createCard = div(
1051
- h5(req.__("Create table")),
1052
1016
  a(
1053
1017
  { href: `/table/new`, class: "btn btn-primary mt-1 me-3" },
1054
1018
  i({ class: "fas fa-plus-square me-1" }),
@@ -1064,8 +1028,13 @@ router.get(
1064
1028
  ),
1065
1029
  !db.isSQLite &&
1066
1030
  a(
1067
- { href: `/table/discover`, class: "btn btn-secondary mt-1" },
1031
+ {
1032
+ href: `/table/discover`,
1033
+ class: "btn btn-secondary mt-1",
1034
+ title: req.__("Discover tables that are already in the Database, but not known to Saltcorn"),
1035
+ },
1068
1036
  i({ class: "fas fa-map-signs me-1" }),
1037
+
1069
1038
  req.__("Discover tables")
1070
1039
  )
1071
1040
  );
@@ -0,0 +1,173 @@
1
+ const {
2
+ a,
3
+ div,
4
+ text,
5
+ button,
6
+ i,
7
+ form,
8
+ select,
9
+ option,
10
+ label,
11
+ } = require("@saltcorn/markup/tags");
12
+
13
+ const Tag = require("@saltcorn/data/models/tag");
14
+ const TagEntry = require("@saltcorn/data/models/tag_entry");
15
+ const Router = require("express-promise-router");
16
+
17
+ const { isAdmin, error_catcher, csrfField } = require("./utils");
18
+
19
+ const Table = require("@saltcorn/data/models/table");
20
+ const View = require("@saltcorn/data/models/view");
21
+ const Page = require("@saltcorn/data/models/page");
22
+ const Trigger = require("@saltcorn/data/models/trigger");
23
+
24
+ const router = new Router();
25
+ module.exports = router;
26
+
27
+ const buildFields = (entryType, formOptions, req) => {
28
+ return Object.entries(formOptions).map(([type, list]) => {
29
+ return div(
30
+ { class: "form-group row" },
31
+ div({ class: "col-sm-2" }, label("type")),
32
+ div(
33
+ { class: "col-sm-10" },
34
+ select(
35
+ { name: "ids", class: "form-control form-select", multiple: true },
36
+ list.map((entry) => {
37
+ return option({ value: entry.id, label: entry.name });
38
+ })
39
+ )
40
+ ),
41
+ div(
42
+ { class: "col-sm-12" },
43
+ button({ type: "submit", class: "btn btn-primary" }, req.__("Save"))
44
+ )
45
+ );
46
+ });
47
+ };
48
+
49
+ const buildForm = (entryType, tag_id, formOptions, req) => {
50
+ return form(
51
+ { action: `/tag-entries/add/${entryType}/${tag_id}`, method: "post" },
52
+ csrfField(req),
53
+ buildFields(entryType, formOptions, req)
54
+ );
55
+ };
56
+
57
+ const formOptions = async (type, tag_id) => {
58
+ const tag = await Tag.findOne({ id: tag_id });
59
+ switch (type) {
60
+ case "tables": {
61
+ const ids = await tag.getTableIds();
62
+ return {
63
+ tables: (await Table.find()).filter(
64
+ (value) => ids.indexOf(value.id) === -1
65
+ ),
66
+ };
67
+ }
68
+ case "views": {
69
+ const ids = await tag.getViewIds();
70
+ return {
71
+ views: (await View.find()).filter(
72
+ (value) => ids.indexOf(value.id) === -1
73
+ ),
74
+ };
75
+ }
76
+ case "pages": {
77
+ const ids = await tag.getPageIds();
78
+ return {
79
+ pages: (await Page.find()).filter(
80
+ (value) => ids.indexOf(value.id) === -1
81
+ ),
82
+ };
83
+ }
84
+ case "trigger": {
85
+ const ids = await tag.getTriggerIds();
86
+ return {
87
+ trigger: (await Trigger.find()).filter(
88
+ (value) => ids.indexOf(value.id) === -1
89
+ ),
90
+ };
91
+ }
92
+ }
93
+ };
94
+
95
+ router.get(
96
+ "/add/:entry_type/:tag_id",
97
+ isAdmin,
98
+ error_catcher(async (req, res) => {
99
+ const { entry_type, tag_id } = req.params;
100
+ res.sendWrap(req.__("Add %s to tag"), {
101
+ above: [
102
+ {
103
+ type: "breadcrumbs",
104
+ crumbs: [{ text: `Tag entry` }],
105
+ },
106
+ {
107
+ type: "card",
108
+ title: req.__(`Add entries to tag`),
109
+ contents: buildForm(
110
+ entry_type,
111
+ tag_id,
112
+ await formOptions(entry_type, tag_id),
113
+ req
114
+ ),
115
+ },
116
+ ],
117
+ });
118
+ })
119
+ );
120
+
121
+ const idField = (entryType) => {
122
+ switch (entryType) {
123
+ case "tables": {
124
+ return "table_id";
125
+ }
126
+ case "views": {
127
+ return "view_id";
128
+ }
129
+ case "pages": {
130
+ return "page_id";
131
+ }
132
+ case "trigger": {
133
+ return "trigger_id";
134
+ }
135
+ }
136
+ return null;
137
+ };
138
+
139
+ router.post(
140
+ "/add/:entry_type/:tag_id",
141
+ isAdmin,
142
+ error_catcher(async (req, res) => {
143
+ const { entry_type, tag_id } = req.params;
144
+ const { ids } = req.body;
145
+ if (!ids) {
146
+ req.flash("error", req.__("Please select at least one item"));
147
+ return res.redirect(`/tag-entries/add/${entry_type}/${tag_id}`);
148
+ }
149
+ const fieldName = idField(entry_type);
150
+ const tag = await Tag.findOne({ id: tag_id });
151
+ for (const id of ids) {
152
+ await tag.addEntry({ [fieldName]: id });
153
+ }
154
+ res.redirect(`/tag/${tag_id}?show_list=${entry_type}`);
155
+ })
156
+ );
157
+
158
+ router.post(
159
+ "/remove/:entry_type/:entry_id/:tag_id",
160
+ isAdmin,
161
+ error_catcher(async (req, res) => {
162
+ const { tag_id, entry_type, entry_id } = req.params;
163
+ const fieldName = idField(entry_type);
164
+ const entry = await TagEntry.findOne({ tag_id, [fieldName]: entry_id });
165
+ entry[fieldName] = undefined;
166
+ if (entry.isEmpty()) {
167
+ await entry.delete();
168
+ } else {
169
+ await TagEntry.update(entry.id, { [fieldName]: null });
170
+ }
171
+ res.redirect(`/tag/${tag_id}?show_list=${entry_type}`);
172
+ })
173
+ );