@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 +4 -1
- package/package.json +9 -9
- package/routes/admin.js +106 -1
- package/routes/page.js +1 -0
- package/routes/pageedit.js +16 -2
- package/routes/tenant.js +31 -1
- package/routes/utils.js +15 -0
- package/tests/plugin_install.test.js +5 -5
- package/tests/plugins.test.js +2 -2
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.
|
|
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.
|
|
11
|
-
"@saltcorn/builder": "1.1.0-beta.
|
|
12
|
-
"@saltcorn/data": "1.1.0-beta.
|
|
13
|
-
"@saltcorn/admin-models": "1.1.0-beta.
|
|
14
|
-
"@saltcorn/filemanager": "1.1.0-beta.
|
|
15
|
-
"@saltcorn/markup": "1.1.0-beta.
|
|
16
|
-
"@saltcorn/plugins-loader": "1.1.0-beta.
|
|
17
|
-
"@saltcorn/sbadmin2": "1.1.0-beta.
|
|
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 {
|
|
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,
|
package/routes/pageedit.js
CHANGED
|
@@ -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 {
|
|
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 {
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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.
|
|
347
|
+
expect(newPlugin.version).toBe("0.0.1");
|
|
348
348
|
});
|
|
349
349
|
});
|
package/tests/plugins.test.js
CHANGED
|
@@ -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
|
|
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
|
|
430
|
+
expect(upgradedPlugin.version).toBe("0.0.1");
|
|
431
431
|
});
|
|
432
432
|
});
|
|
433
433
|
|