@saltcorn/server 1.1.0-beta.9 → 1.1.1-beta.0

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/plugins.js CHANGED
@@ -57,7 +57,11 @@ const fs = require("fs");
57
57
  const path = require("path");
58
58
  const { get_latest_npm_version } = require("@saltcorn/data/models/config");
59
59
  const { flash_restart } = require("../markup/admin.js");
60
- const { sleep, removeNonWordChars } = require("@saltcorn/data/utils");
60
+ const {
61
+ sleep,
62
+ removeNonWordChars,
63
+ getFetchProxyOptions,
64
+ } = require("@saltcorn/data/utils");
61
65
  const { loadAllPlugins } = require("../load_plugins");
62
66
  const npmFetch = require("npm-registry-fetch");
63
67
  const PluginInstaller = require("@saltcorn/plugins-loader/plugin_installer");
@@ -609,7 +613,8 @@ router.get(
609
613
  } else {
610
614
  try {
611
615
  const pkgInfo = await npmFetch.json(
612
- `https://registry.npmjs.org/${plugin.location}`
616
+ `https://registry.npmjs.org/${plugin.location}`,
617
+ getFetchProxyOptions()
613
618
  );
614
619
  if (!pkgInfo?.versions)
615
620
  throw new Error(req.__("Unable to fetch versions"));
@@ -7,6 +7,7 @@ const {
7
7
  domReady,
8
8
  a,
9
9
  div,
10
+ h4,
10
11
  i,
11
12
  text,
12
13
  button,
@@ -40,6 +41,7 @@ const {
40
41
  install_pack,
41
42
  } = require("@saltcorn/admin-models/models/pack");
42
43
  const Trigger = require("@saltcorn/data/models/trigger");
44
+ const { getState } = require("@saltcorn/data/db/state");
43
45
  /**
44
46
  * @type {object}
45
47
  * @const
@@ -79,7 +81,17 @@ router.get(
79
81
  {},
80
82
  { orderBy: "name", nocase: true }
81
83
  );
82
- let tables, views, pages, triggers;
84
+ const isRoot = db.getTenantSchema() === db.connectObj.default_schema;
85
+
86
+ const all_configs_obj = await getState().getAllConfigOrDefaults();
87
+ const all_configs = Object.entries(all_configs_obj)
88
+ .map(([name, v]) => ({
89
+ ...v,
90
+ name,
91
+ }))
92
+ .filter((c) => isRoot || !c.root_only);
93
+
94
+ let tables, views, pages, triggers, configs;
83
95
  if (q) {
84
96
  const qlower = q.toLowerCase();
85
97
  const includesQ = (s) => s.toLowerCase().includes(qlower);
@@ -100,11 +112,13 @@ router.get(
100
112
  const pack = await trigger_pack(t);
101
113
  return includesQ(JSON.stringify(pack));
102
114
  });
115
+ configs = all_configs.filter((c) => includesQ(JSON.stringify(c)));
103
116
  } else {
104
117
  tables = all_tables;
105
118
  views = all_views;
106
119
  pages = all_pages;
107
120
  triggers = all_triggers;
121
+ configs = all_configs;
108
122
  }
109
123
  const li_link = (etype1, ename1) =>
110
124
  li(
@@ -124,7 +138,7 @@ router.get(
124
138
  action: `/registry-editor?etype=${etype}&ename=${encodeURIComponent(
125
139
  ename
126
140
  )}${qlink}`,
127
-
141
+ formStyle: "vert",
128
142
  values: { regval: JSON.stringify(jsonVal, null, 2) },
129
143
  fields: [
130
144
  {
@@ -177,6 +191,14 @@ router.get(
177
191
  const ppack = await page_pack(all_pages.find((v) => v.name === ename));
178
192
  edContents = renderForm(mkForm(ppack), req.csrfToken());
179
193
  break;
194
+ case "config":
195
+ const config = all_configs.find((t) => t.name === ename);
196
+ edContents =
197
+ h4(config.label) +
198
+ (config.blurb || "") +
199
+ (config.sublabel || "") +
200
+ renderForm(mkForm(config.value), req.csrfToken());
201
+ break;
180
202
  case "trigger":
181
203
  const trigger = all_triggers.find((t) => t.name === ename);
182
204
  const trpack = await trigger_pack(trigger);
@@ -282,6 +304,16 @@ router.get(
282
304
  triggers.map((t) => li_link("trigger", t.name))
283
305
  )
284
306
  )
307
+ ),
308
+ li(
309
+ details(
310
+ { open: q || etype === "CONFIG" }, //
311
+ summary("Configuration"),
312
+ ul(
313
+ { class: "ps-3" },
314
+ configs.map((t) => li_link("config", t.name))
315
+ )
316
+ )
285
317
  )
286
318
  )
287
319
  ),
@@ -309,7 +341,14 @@ router.post(
309
341
  const qlink = q ? `&q=${encodeURIComponent(q)}` : "";
310
342
 
311
343
  const entVal = JSON.parse(req.body.regval);
312
- let pack = { plugins: [], tables: [], views: [], pages: [], triggers: [] };
344
+ let pack = {
345
+ plugins: [],
346
+ tables: [],
347
+ views: [],
348
+ pages: [],
349
+ triggers: [],
350
+ config: {},
351
+ };
313
352
 
314
353
  switch (etype) {
315
354
  case "table":
@@ -324,6 +363,9 @@ router.post(
324
363
  case "trigger":
325
364
  pack.triggers = [entVal];
326
365
  break;
366
+ case "config":
367
+ pack.config[ename] = entVal;
368
+ break;
327
369
  }
328
370
  await install_pack(pack);
329
371
  res.redirect(
package/routes/tables.js CHANGED
@@ -56,7 +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, viewsList } = require("./common_lists");
59
+ const { tablesList, viewsList, getTriggerList } = require("./common_lists");
60
60
  const {
61
61
  InvalidConfiguration,
62
62
  removeAllWhiteSpace,
@@ -757,6 +757,8 @@ router.get(
757
757
  const triggers = table.id ? Trigger.find({ table_id: table.id }) : [];
758
758
  triggers.sort(comparingCaseInsensitive("name"));
759
759
  let fieldCard;
760
+ const nPrimaryKeys = fields.filter((f) => f.primary_key).length;
761
+
760
762
  if (fields.length === 0) {
761
763
  fieldCard = [
762
764
  h4(req.__(`No fields defined in %s table`, table.name)),
@@ -818,28 +820,25 @@ router.get(
818
820
  { hover: true }
819
821
  );
820
822
  fieldCard = [
823
+ nPrimaryKeys > 1 &&
824
+ div(
825
+ { class: "alert alert-danger", role: "alert" },
826
+ i({ class: "fas fa-exclamation-triangle" }),
827
+ "This table has composite primary keys which is not supported in Saltcorn. A procedure to introduce a single autoincrementing primary key is available.",
828
+ post_btn(
829
+ `/table/repair-composite-primary/${table.id}`,
830
+ "Add autoincrementing primary key",
831
+ req.csrfToken(),
832
+ { btnClass: "btn-danger" }
833
+ )
834
+ ),
835
+
821
836
  tableHtml,
822
837
  inbound_refs.length > 0
823
838
  ? req.__("Inbound keys: ") +
824
839
  inbound_refs.map((tnm) => link(`/table/${tnm}`, tnm)).join(", ") +
825
840
  "<br>"
826
841
  : "",
827
- triggers.length
828
- ? req.__("Table triggers: ") +
829
- triggers
830
- .map((t) =>
831
- link(
832
- `/actions/configure/${
833
- t.id
834
- }?on_done_redirect=${encodeURIComponent(
835
- `table/${table.name}`
836
- )}`,
837
- t.name
838
- )
839
- )
840
- .join(", ") +
841
- "<br>"
842
- : "",
843
842
  !table.external &&
844
843
  !table.provider_name &&
845
844
  a(
@@ -851,7 +850,8 @@ router.get(
851
850
  ),
852
851
  ];
853
852
  }
854
- var viewCard;
853
+ let viewCard;
854
+ let triggerCard = "";
855
855
  if (fields.length > 0) {
856
856
  const views = await View.find(
857
857
  table.id ? { table_id: table.id } : { exttable_name: table.name }
@@ -884,6 +884,25 @@ router.get(
884
884
  req.__("Create view")
885
885
  ),
886
886
  };
887
+
888
+ triggerCard = {
889
+ type: "card",
890
+ id: "table-triggers",
891
+ title: req.__("Triggers on table"),
892
+ contents:
893
+ (triggers.length
894
+ ? await getTriggerList(triggers, req)
895
+ : p("Triggers run actions in response to events on this table")) +
896
+ a(
897
+ {
898
+ href: `/actions/new?table=${encodeURIComponent(
899
+ table.name
900
+ )}&on_done_redirect=${encodeURIComponent(`table/${table.name}`)}`,
901
+ class: "btn btn-primary",
902
+ },
903
+ req.__("Create trigger")
904
+ ),
905
+ };
887
906
  }
888
907
  const models = await Model.find({ table_id: table.id });
889
908
  const modelCard = div(
@@ -1078,6 +1097,7 @@ router.get(
1078
1097
  ]
1079
1098
  : []),
1080
1099
  ...(viewCard ? [viewCard] : []),
1100
+ ...(triggerCard ? [triggerCard] : []),
1081
1101
  {
1082
1102
  type: "card",
1083
1103
  title: req.__("Edit table properties"),
@@ -1111,7 +1131,7 @@ router.post(
1111
1131
  const v = req.body;
1112
1132
  if (typeof v.id === "undefined" && typeof v.external === "undefined") {
1113
1133
  // insert
1114
- v.name = v.name.trim()
1134
+ v.name = v.name.trim();
1115
1135
  const { name, ...rest } = v;
1116
1136
  const alltables = await Table.find({});
1117
1137
  const existing_tables = [
@@ -1162,6 +1182,7 @@ router.post(
1162
1182
  let notify = "";
1163
1183
  if (!rest.versioned) rest.versioned = false;
1164
1184
  if (!rest.has_sync_info) rest.has_sync_info = false;
1185
+ rest.is_user_group = !!rest.is_user_group;
1165
1186
  if (rest.ownership_field_id === "_formula") {
1166
1187
  rest.ownership_field_id = null;
1167
1188
  const fmlValidRes = expressionValidator(rest.ownership_formula);
@@ -1922,7 +1943,7 @@ router.post(
1922
1943
  const table = Table.findOne({ name });
1923
1944
 
1924
1945
  try {
1925
- await table.deleteRows({}, req.user);
1946
+ await table.deleteRows({}, req.user, true);
1926
1947
  req.flash("success", req.__("Deleted all rows"));
1927
1948
  } catch (e) {
1928
1949
  req.flash("error", e.message);
@@ -2074,3 +2095,20 @@ router.post(
2074
2095
  respondWorkflow(table, workflow, wfres, req, res);
2075
2096
  })
2076
2097
  );
2098
+
2099
+ router.post(
2100
+ "/repair-composite-primary/:id",
2101
+ isAdmin,
2102
+ error_catcher(async (req, res) => {
2103
+ const { id } = req.params;
2104
+
2105
+ const table = Table.findOne({ id });
2106
+ if (!table) {
2107
+ req.flash("error", `Table not found`);
2108
+ res.redirect(`/table`);
2109
+ return;
2110
+ }
2111
+ await table.repairCompositePrimary();
2112
+ res.redirect(`/table/${table.id}`);
2113
+ })
2114
+ );
package/routes/tenant.js CHANGED
@@ -355,7 +355,12 @@ router.post(
355
355
  ...tenant_letsencrypt_sites,
356
356
  ]);
357
357
  if (req.user?.role_id === 1) {
358
- req.flash("success", req.__("Tenant created. Certificate will be acquired on first visit."));
358
+ req.flash(
359
+ "success",
360
+ req.__(
361
+ "Tenant created. Certificate will be acquired on first visit."
362
+ )
363
+ );
359
364
  res.redirect("/tenant/list");
360
365
  return;
361
366
  }
@@ -416,6 +421,7 @@ router.get(
416
421
  return;
417
422
  }
418
423
  const tens = await db.select("_sc_tenants");
424
+ const locale = getState().getConfig("default_locale", "en");
419
425
  send_infoarch_page({
420
426
  res,
421
427
  req,
@@ -442,7 +448,8 @@ router.get(
442
448
  },
443
449
  {
444
450
  label: req.__("Created"),
445
- key: (r) => (r.created ? localeDateTime(r.created) : ""),
451
+ key: (r) =>
452
+ r.created ? localeDateTime(r.created, {}, locale) : "",
446
453
  },
447
454
  {
448
455
  label: req.__("Information"),
@@ -486,6 +493,7 @@ const tenant_settings_form = (req) =>
486
493
  "tenant_template",
487
494
  "tenant_baseurl",
488
495
  "tenant_create_unauth_redirect",
496
+ "tenant_inherit_cfgs",
489
497
  { section_header: req.__("Tenant application capabilities") },
490
498
  "tenants_install_git",
491
499
  "tenants_set_npm_modules",
@@ -151,14 +151,6 @@ const viewForm = async (req, tableOptions, roles, pages, values) => {
151
151
  "The view name is part of the URL when it is shown alone."
152
152
  ),
153
153
  }),
154
- new Field({
155
- label: req.__("Description"),
156
- name: "description",
157
- type: "String",
158
- sublabel: req.__(
159
- "Description allows you to give more information about the view."
160
- ),
161
- }),
162
154
  new Field({
163
155
  label: req.__("View pattern"),
164
156
  name: "viewtemplate",
@@ -200,6 +192,14 @@ const viewForm = async (req, tableOptions, roles, pages, values) => {
200
192
  required: true,
201
193
  options: roles.map((r) => ({ value: r.id, label: r.role })),
202
194
  }),
195
+ new Field({
196
+ label: req.__("Description"),
197
+ name: "description",
198
+ type: "String",
199
+ sublabel: req.__(
200
+ "Description allows you to give more information about the view."
201
+ ),
202
+ }),
203
203
  new Field({
204
204
  name: "page_title",
205
205
  label: req.__("Page title"),
package/wrapper.js CHANGED
@@ -193,7 +193,9 @@ const get_headers = (req, version_tag, description, extras = []) => {
193
193
  state.logLevel
194
194
  }, _sc_globalCsrf = "${req.csrfToken()}", _sc_version_tag = "${version_tag}"${
195
195
  locale ? `, _sc_locale = "${locale}"` : ""
196
- };</script>`,
196
+ }, _sc_lightmode = ${JSON.stringify(
197
+ state.getLightDarkMode?.(req.user) || "light"
198
+ )};</script>`,
197
199
  },
198
200
  { css: `/static_assets/${version_tag}/saltcorn.css` },
199
201
  { script: `/static_assets/${version_tag}/saltcorn-common.js` },