@saltcorn/server 1.1.0-beta.0 → 1.1.0-beta.1

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/locales/en.json CHANGED
@@ -1490,5 +1490,8 @@
1490
1490
  "Login and signup views should be accessible by public users": "Login and signup views should be accessible by public users",
1491
1491
  "Shared: %s": "Shared: %s",
1492
1492
  "Sharing not enabled": "Sharing not enabled",
1493
- "You must be logged in to share": "You must be logged in to share"
1493
+ "You must be logged in to share": "You must be logged in to share",
1494
+ "Fluid layout": "Fluid layout",
1495
+ "Request fluid layout from theme for a wider display for this page": "Request fluid layout from theme for a wider display for this page",
1496
+ "Location of view to create new row": "Location of view to create new row"
1494
1497
  }
package/package.json CHANGED
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "name": "@saltcorn/server",
3
- "version": "1.1.0-beta.0",
3
+ "version": "1.1.0-beta.1",
4
4
  "description": "Server app for Saltcorn, open-source no-code platform",
5
5
  "homepage": "https://saltcorn.com",
6
6
  "main": "index.js",
7
7
  "license": "MIT",
8
8
  "dependencies": {
9
9
  "@aws-sdk/client-s3": "^3.451.0",
10
- "@saltcorn/base-plugin": "1.1.0-beta.0",
11
- "@saltcorn/builder": "1.1.0-beta.0",
12
- "@saltcorn/data": "1.1.0-beta.0",
13
- "@saltcorn/admin-models": "1.1.0-beta.0",
14
- "@saltcorn/filemanager": "1.1.0-beta.0",
15
- "@saltcorn/markup": "1.1.0-beta.0",
16
- "@saltcorn/plugins-loader": "1.1.0-beta.0",
17
- "@saltcorn/sbadmin2": "1.1.0-beta.0",
10
+ "@saltcorn/base-plugin": "1.1.0-beta.1",
11
+ "@saltcorn/builder": "1.1.0-beta.1",
12
+ "@saltcorn/data": "1.1.0-beta.1",
13
+ "@saltcorn/admin-models": "1.1.0-beta.1",
14
+ "@saltcorn/filemanager": "1.1.0-beta.1",
15
+ "@saltcorn/markup": "1.1.0-beta.1",
16
+ "@saltcorn/plugins-loader": "1.1.0-beta.1",
17
+ "@saltcorn/sbadmin2": "1.1.0-beta.1",
18
18
  "@socket.io/cluster-adapter": "^0.2.1",
19
19
  "@socket.io/sticky": "^1.0.1",
20
20
  "adm-zip": "0.5.10",
package/routes/admin.js CHANGED
@@ -12,6 +12,7 @@ const {
12
12
  setTenant,
13
13
  admin_config_route,
14
14
  get_sys_info,
15
+ tenant_letsencrypt_name,
15
16
  } = require("./utils.js");
16
17
  const Table = require("@saltcorn/data/models/table");
17
18
  const Plugin = require("@saltcorn/data/models/plugin");
@@ -90,7 +91,10 @@ const {
90
91
  } = require("../markup/admin.js");
91
92
  const packagejson = require("../package.json");
92
93
  const Form = require("@saltcorn/data/models/form");
93
- const { get_latest_npm_version } = require("@saltcorn/data/models/config");
94
+ const {
95
+ get_latest_npm_version,
96
+ isFixedConfig,
97
+ } = require("@saltcorn/data/models/config");
94
98
  const { getMailTransport } = require("@saltcorn/data/models/email");
95
99
  const {
96
100
  getBaseDomain,
@@ -976,6 +980,40 @@ router.post(
976
980
  }
977
981
  })
978
982
  );
983
+
984
+ router.post(
985
+ "/save-config",
986
+ isAdmin,
987
+ error_catcher(async (req, res) => {
988
+ const state = getState();
989
+
990
+ //TODO check this is a config key
991
+ const validKeyName = (k) =>
992
+ k !== "_csrf" && k !== "constructor" && k !== "__proto__";
993
+
994
+ for (const [k, v] of Object.entries(req.body)) {
995
+ if (!isFixedConfig(k) && typeof v !== "undefined" && validKeyName(k)) {
996
+ //TODO read value from type
997
+ await state.setConfig(k, v);
998
+ }
999
+ }
1000
+
1001
+ // checkboxes that are false are not sent in post body. Check here
1002
+ const { boolcheck } = req.query;
1003
+ const boolchecks =
1004
+ typeof boolcheck === "undefined"
1005
+ ? []
1006
+ : Array.isArray(boolcheck)
1007
+ ? boolcheck
1008
+ : [boolcheck];
1009
+ for (const k of boolchecks) {
1010
+ if (typeof req.body[k] === "undefined" && validKeyName(k))
1011
+ await state.setConfig(k, false);
1012
+ }
1013
+ res.json({ success: "ok" });
1014
+ })
1015
+ );
1016
+
979
1017
  /**
980
1018
  * Do Auto backup now
981
1019
  */
@@ -1689,6 +1727,64 @@ const clearAllForm = (req) =>
1689
1727
  ],
1690
1728
  });
1691
1729
 
1730
+ router.post(
1731
+ "/acq-ssl-tenant/:subdomain",
1732
+ isAdmin,
1733
+ error_catcher(async (req, res) => {
1734
+ if (
1735
+ db.is_it_multi_tenant() &&
1736
+ db.getTenantSchema() === db.connectObj.default_schema
1737
+ ) {
1738
+ const { subdomain } = req.params;
1739
+
1740
+ const domain = getBaseDomain();
1741
+
1742
+ let altname = await tenant_letsencrypt_name(subdomain);
1743
+
1744
+ if (!altname || domain) {
1745
+ req.json({ error: "Set Base URL for both tenant and root first." });
1746
+ return;
1747
+ }
1748
+
1749
+ try {
1750
+ const file_store = db.connectObj.file_store;
1751
+ const admin_users = await User.find({ role_id: 1 }, { orderBy: "id" });
1752
+ // greenlock logic
1753
+ const Greenlock = require("greenlock");
1754
+ const greenlock = Greenlock.create({
1755
+ packageRoot: path.resolve(__dirname, ".."),
1756
+ configDir: path.join(file_store, "greenlock.d"),
1757
+ maintainerEmail: admin_users[0].email,
1758
+ });
1759
+
1760
+ await greenlock.sites.add({
1761
+ subject: altname,
1762
+ });
1763
+ // letsencrypt
1764
+ const tenant_letsencrypt_sites = getState().getConfig(
1765
+ "tenant_letsencrypt_sites",
1766
+ []
1767
+ );
1768
+ await getState().setConfig(tenant_letsencrypt_sites, [
1769
+ altname,
1770
+ ...tenant_letsencrypt_sites,
1771
+ ]);
1772
+
1773
+ res.json({
1774
+ success: true,
1775
+ notify: "Certificate added, please restart server",
1776
+ });
1777
+ } catch (e) {
1778
+ req.flash("error", e.message);
1779
+ res.redirect("/useradmin/ssl");
1780
+ }
1781
+ } else {
1782
+ req.flash("error", req.__("Not possible for tenant"));
1783
+ res.redirect("/useradmin/ssl");
1784
+ }
1785
+ })
1786
+ );
1787
+
1692
1788
  /**
1693
1789
  * Do Enable letsencrypt
1694
1790
  * @name post/enable-letsencrypt
@@ -1749,6 +1845,15 @@ router.post(
1749
1845
  });
1750
1846
  // letsencrypt
1751
1847
  await getState().setConfig("letsencrypt", true);
1848
+ const tenant_letsencrypt_sites = getState().getConfig(
1849
+ "tenant_letsencrypt_sites",
1850
+ []
1851
+ );
1852
+ await getState().setConfig(tenant_letsencrypt_sites, [
1853
+ ...altnames,
1854
+ ...tenant_letsencrypt_sites,
1855
+ ]);
1856
+
1752
1857
  req.flash(
1753
1858
  "success",
1754
1859
  req.__(
package/routes/page.js CHANGED
@@ -66,6 +66,7 @@ const runPage = async (page, req, res, tic) => {
66
66
  description: page.description,
67
67
  bodyClass: "page_" + db.sqlsanitize(page.name),
68
68
  no_menu: page.attributes?.no_menu,
69
+ requestFluidLayout: page.attributes?.request_fluid_layout,
69
70
  } || `${page.name} page`,
70
71
  add_edit_bar({
71
72
  role,
@@ -143,6 +143,12 @@ const pagePropertiesForm = async (req, isNew) => {
143
143
  sublabel: req.__("Omit the menu from this page"),
144
144
  type: "Bool",
145
145
  },
146
+ {
147
+ name: "request_fluid_layout",
148
+ label: req.__("Fluid layout"),
149
+ sublabel: req.__("Request fluid layout from theme for a wider display for this page"),
150
+ type: "Bool",
151
+ },
146
152
  ],
147
153
  });
148
154
  return form;
@@ -429,6 +435,7 @@ router.get(
429
435
  form.hidden("id");
430
436
  form.values = page;
431
437
  form.values.no_menu = page.attributes?.no_menu;
438
+ form.values.request_fluid_layout = page.attributes?.request_fluid_layout;
432
439
  form.onChange = `saveAndContinue(this)`;
433
440
  res.sendWrap(
434
441
  req.__(`Page attributes`),
@@ -475,9 +482,16 @@ router.post(
475
482
  wrap(renderForm(form, req.csrfToken()), false, req)
476
483
  );
477
484
  } else {
478
- const { id, columns, no_menu, html_file, ...pageRow } = form.values;
485
+ const {
486
+ id,
487
+ columns,
488
+ no_menu,
489
+ request_fluid_layout,
490
+ html_file,
491
+ ...pageRow
492
+ } = form.values;
479
493
  pageRow.min_role = +pageRow.min_role;
480
- pageRow.attributes = { no_menu };
494
+ pageRow.attributes = { no_menu, request_fluid_layout };
481
495
  if (html_file) {
482
496
  pageRow.layout = {
483
497
  html_file: html_file,
package/routes/tenant.js CHANGED
@@ -44,7 +44,12 @@ const {
44
44
  const db = require("@saltcorn/data/db");
45
45
 
46
46
  const { loadAllPlugins, loadAndSaveNewPlugin } = require("../load_plugins");
47
- const { isAdmin, error_catcher, is_ip_address } = require("./utils.js");
47
+ const {
48
+ isAdmin,
49
+ error_catcher,
50
+ is_ip_address,
51
+ tenant_letsencrypt_name,
52
+ } = require("./utils.js");
48
53
  const User = require("@saltcorn/data/models/user");
49
54
  const File = require("@saltcorn/data/models/file");
50
55
  const {
@@ -612,8 +617,18 @@ router.get(
612
617
  return;
613
618
  }
614
619
  const { subdomain } = req.params;
620
+
615
621
  // get tenant info
616
622
  const info = await get_tenant_info(subdomain);
623
+ const letsencrypt = getState().getConfig("letsencrypt", false);
624
+
625
+ let altname = await tenant_letsencrypt_name(subdomain);
626
+ const tenant_letsencrypt_sites = getState().getConfig(
627
+ "tenant_letsencrypt_sites",
628
+ []
629
+ );
630
+ const has_cert = tenant_letsencrypt_sites.includes(altname);
631
+
617
632
  // get list of files
618
633
  let files;
619
634
  await db.runWithTenant(subdomain, async () => {
@@ -632,6 +647,7 @@ router.get(
632
647
  // TBD make more pretty view - in ideal with charts
633
648
  contents: [
634
649
  table(
650
+ { class: "table table-sm" },
635
651
  tr(
636
652
  th(req.__("First user E-mail")),
637
653
  td(
@@ -723,6 +739,20 @@ router.get(
723
739
  submitLabel: req.__("Save"),
724
740
  submitButtonClass: "btn-outline-primary",
725
741
  onChange: "remove_outline(this)",
742
+ additionalButtons: [
743
+ ...(letsencrypt && !has_cert
744
+ ? [
745
+ {
746
+ label: req.__("Acquire LetsEncrypt certificate"),
747
+ id: "btnAcqCert",
748
+ class: "btn btn-secondary",
749
+ onclick: `press_store_button(this);ajax_post('/admin/acq-ssl-tenant/${encodeURIComponent(
750
+ subdomain
751
+ )}')`,
752
+ },
753
+ ]
754
+ : []),
755
+ ],
726
756
  fields: [
727
757
  {
728
758
  name: "base_url",
package/routes/utils.js CHANGED
@@ -33,6 +33,7 @@ const {
33
33
  const path = require("path");
34
34
  const { UAParser } = require("ua-parser-js");
35
35
  const crypto = require("crypto");
36
+ const { domain_sanitize } = require("@saltcorn/admin-models/models/tenant");
36
37
 
37
38
  const get_sys_info = async () => {
38
39
  const disks = await si.fsSize();
@@ -372,6 +373,19 @@ const is_ip_address = (hostname) => {
372
373
  return hostname.split(".").every((s) => +s >= 0 && +s <= 255);
373
374
  };
374
375
 
376
+ const tenant_letsencrypt_name = async (subdomain) => {
377
+ const saneDomain = domain_sanitize(subdomain);
378
+ let altname;
379
+ await db.runWithTenant(saneDomain, async () => {
380
+ altname = getState()
381
+ .getConfig("base_url", "")
382
+ .replace("https://", "")
383
+ .replace("http://", "")
384
+ .replace("/", "");
385
+ });
386
+ return altname;
387
+ };
388
+
375
389
  const admin_config_route = ({
376
390
  router,
377
391
  path,
@@ -587,4 +601,5 @@ module.exports = {
587
601
  setRole,
588
602
  getEligiblePage,
589
603
  getRandomPage,
604
+ tenant_letsencrypt_name,
590
605
  };
@@ -161,7 +161,7 @@ describe("Stable versioning install", () => {
161
161
  name: "@christianhugoch/empty_sc_test_plugin",
162
162
  });
163
163
  expect(dbPlugin).not.toBe(null);
164
- expect(dbPlugin.version).toBe("0.1.0");
164
+ expect(dbPlugin.version).toBe("0.0.1");
165
165
  });
166
166
 
167
167
  it("installs a fixed version", async () => {
@@ -178,7 +178,7 @@ describe("Stable versioning install", () => {
178
178
  name: "@christianhugoch/empty_sc_test_plugin",
179
179
  });
180
180
  expect(dbPlugin).not.toBe(null);
181
- expect(dbPlugin.version).toBe("0.1.0");
181
+ expect(dbPlugin.version).toBe("0.0.1");
182
182
  });
183
183
 
184
184
  it("installs and downgrades a fixed version", async () => {
@@ -196,7 +196,7 @@ describe("Stable versioning install", () => {
196
196
  name: "@christianhugoch/empty_sc_test_plugin",
197
197
  });
198
198
  expect(dbPlugin).not.toBe(null);
199
- expect(dbPlugin.version).toBe("0.0.6");
199
+ expect(dbPlugin.version).toBe("0.0.1");
200
200
  });
201
201
  });
202
202
 
@@ -276,7 +276,7 @@ describe("Stable versioning upgrade", () => {
276
276
  name: "@christianhugoch/empty_sc_test_plugin",
277
277
  });
278
278
  expect(newPlugin).not.toBe(null);
279
- expect(newPlugin.version).toBe("0.1.0");
279
+ expect(newPlugin.version).toBe("0.0.1");
280
280
  });
281
281
 
282
282
  it("upgrades to fixed version", async () => {
@@ -344,6 +344,6 @@ describe("Stable versioning upgrade", () => {
344
344
  name: "@christianhugoch/empty_sc_test_plugin",
345
345
  });
346
346
  expect(newPlugin).not.toBe(null);
347
- expect(newPlugin.version).toBe("0.0.6");
347
+ expect(newPlugin.version).toBe("0.0.1");
348
348
  });
349
349
  });
@@ -404,7 +404,7 @@ describe("Upgrade plugin to supported version", () => {
404
404
  const upgradedPlugin = await Plugin.findOne({
405
405
  name: "@christianhugoch/empty_sc_test_plugin",
406
406
  });
407
- expect(upgradedPlugin.version).toBe("0.1.0");
407
+ expect(upgradedPlugin.version).toBe("0.0.1");
408
408
  });
409
409
 
410
410
  it("upgrades with a downgrade of the most current fixed version", async () => {
@@ -427,7 +427,7 @@ describe("Upgrade plugin to supported version", () => {
427
427
  const upgradedPlugin = await Plugin.findOne({
428
428
  name: "@christianhugoch/empty_sc_test_plugin",
429
429
  });
430
- expect(upgradedPlugin.version).toBe("0.1.0");
430
+ expect(upgradedPlugin.version).toBe("0.0.1");
431
431
  });
432
432
  });
433
433