@saltcorn/server 1.1.0-beta.8 → 1.1.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/auth/admin.js +1 -1
- package/auth/roleadmin.js +10 -2
- package/help/{Cordova Builder.tmd → Capacitor Builder.tmd } +1 -1
- package/help/Configuration keys.tmd +5 -0
- package/help/index.js +2 -0
- package/load_plugins.js +12 -5
- package/locales/en.json +29 -1
- package/locales/pl.json +19 -2
- package/markup/admin.js +7 -3
- package/package.json +9 -9
- package/public/codemirror.css +33 -0
- package/public/flatpickr-dark.css +795 -0
- package/public/gridedit.js +1 -1
- package/public/mermaid.min.js +1077 -792
- package/public/saltcorn-common.js +93 -44
- package/public/saltcorn.css +11 -4
- package/public/saltcorn.js +60 -22
- package/routes/actions.js +936 -4
- package/routes/admin.js +98 -89
- package/routes/api.js +51 -0
- package/routes/config.js +39 -25
- package/routes/eventlog.js +41 -1
- package/routes/fields.js +8 -2
- package/routes/homepage.js +13 -3
- package/routes/list.js +17 -1
- package/routes/plugins.js +7 -2
- package/routes/registry.js +45 -3
- package/routes/tables.js +58 -20
- package/routes/tenant.js +53 -3
- package/routes/viewedit.js +8 -8
- package/tests/plugin_install.test.js +10 -10
- package/tests/plugins.test.js +6 -6
- package/wrapper.js +3 -1
package/routes/admin.js
CHANGED
|
@@ -91,10 +91,7 @@ const {
|
|
|
91
91
|
} = require("../markup/admin.js");
|
|
92
92
|
const packagejson = require("../package.json");
|
|
93
93
|
const Form = require("@saltcorn/data/models/form");
|
|
94
|
-
const {
|
|
95
|
-
get_latest_npm_version,
|
|
96
|
-
isFixedConfig,
|
|
97
|
-
} = require("@saltcorn/data/models/config");
|
|
94
|
+
const { get_latest_npm_version } = require("@saltcorn/data/models/config");
|
|
98
95
|
const { getMailTransport } = require("@saltcorn/data/models/email");
|
|
99
96
|
const {
|
|
100
97
|
getBaseDomain,
|
|
@@ -107,7 +104,10 @@ const PageGroup = require("@saltcorn/data/models/page_group");
|
|
|
107
104
|
const { getConfigFile } = require("@saltcorn/data/db/connect");
|
|
108
105
|
const os = require("os");
|
|
109
106
|
const Page = require("@saltcorn/data/models/page");
|
|
110
|
-
const {
|
|
107
|
+
const {
|
|
108
|
+
getSafeSaltcornCmd,
|
|
109
|
+
getFetchProxyOptions,
|
|
110
|
+
} = require("@saltcorn/data/utils");
|
|
111
111
|
const stream = require("stream");
|
|
112
112
|
const Crash = require("@saltcorn/data/models/crash");
|
|
113
113
|
const { get_help_markup } = require("../help/index.js");
|
|
@@ -155,6 +155,7 @@ admin_config_route({
|
|
|
155
155
|
field_names: [
|
|
156
156
|
"site_name",
|
|
157
157
|
"timezone",
|
|
158
|
+
"default_locale",
|
|
158
159
|
"base_url",
|
|
159
160
|
...(getConfigFile() ? ["multitenancy_enabled"] : []),
|
|
160
161
|
{ section_header: "Logo image" },
|
|
@@ -534,6 +535,7 @@ router.get(
|
|
|
534
535
|
{},
|
|
535
536
|
{ orderBy: "created", orderDesc: true, fields: ["id", "created", "hash"] }
|
|
536
537
|
);
|
|
538
|
+
const locale = getState().getConfig("default_locale", "en");
|
|
537
539
|
send_admin_page({
|
|
538
540
|
res,
|
|
539
541
|
req,
|
|
@@ -555,9 +557,11 @@ router.get(
|
|
|
555
557
|
)}`,
|
|
556
558
|
target: "_blank",
|
|
557
559
|
},
|
|
558
|
-
`${localeDateTime(
|
|
559
|
-
snap.created
|
|
560
|
-
|
|
560
|
+
`${localeDateTime(
|
|
561
|
+
snap.created,
|
|
562
|
+
{},
|
|
563
|
+
locale
|
|
564
|
+
)} (${moment(snap.created).fromNow()})`
|
|
561
565
|
)
|
|
562
566
|
)
|
|
563
567
|
)
|
|
@@ -595,6 +599,7 @@ router.get(
|
|
|
595
599
|
error_catcher(async (req, res) => {
|
|
596
600
|
const { type, name } = req.params;
|
|
597
601
|
const snaps = await Snapshot.entity_history(type, name);
|
|
602
|
+
const locale = getState().getConfig("default_locale", "en");
|
|
598
603
|
res.set("Page-Title", `Restore ${text(name)}`);
|
|
599
604
|
res.send(
|
|
600
605
|
mkTable(
|
|
@@ -602,7 +607,9 @@ router.get(
|
|
|
602
607
|
{
|
|
603
608
|
label: req.__("When"),
|
|
604
609
|
key: (r) =>
|
|
605
|
-
`${localeDateTime(r.created)} (${moment(
|
|
610
|
+
`${localeDateTime(r.created, {}, locale)} (${moment(
|
|
611
|
+
r.created
|
|
612
|
+
).fromNow()})`,
|
|
606
613
|
},
|
|
607
614
|
|
|
608
615
|
{
|
|
@@ -981,39 +988,6 @@ router.post(
|
|
|
981
988
|
})
|
|
982
989
|
);
|
|
983
990
|
|
|
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
|
-
|
|
1017
991
|
/**
|
|
1018
992
|
* Do Auto backup now
|
|
1019
993
|
*/
|
|
@@ -1284,10 +1258,14 @@ router.post(
|
|
|
1284
1258
|
})
|
|
1285
1259
|
);
|
|
1286
1260
|
|
|
1287
|
-
const
|
|
1288
|
-
const child = spawn(
|
|
1289
|
-
|
|
1290
|
-
|
|
1261
|
+
const pullCapacitorBuilder = (req, res, version) => {
|
|
1262
|
+
const child = spawn(
|
|
1263
|
+
"docker",
|
|
1264
|
+
["pull", `saltcorn/capacitor-builder:${version}`],
|
|
1265
|
+
{
|
|
1266
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1267
|
+
}
|
|
1268
|
+
);
|
|
1291
1269
|
return new Promise((resolve, reject) => {
|
|
1292
1270
|
child.stdout.on("data", (data) => {
|
|
1293
1271
|
res.write(data);
|
|
@@ -1359,7 +1337,8 @@ router.get(
|
|
|
1359
1337
|
error_catcher(async (req, res) => {
|
|
1360
1338
|
try {
|
|
1361
1339
|
const pkgInfo = await npmFetch.json(
|
|
1362
|
-
"https://registry.npmjs.org/@saltcorn/cli"
|
|
1340
|
+
"https://registry.npmjs.org/@saltcorn/cli",
|
|
1341
|
+
getFetchProxyOptions()
|
|
1363
1342
|
);
|
|
1364
1343
|
if (!pkgInfo?.versions)
|
|
1365
1344
|
throw new Error(req.__("Unable to fetch versions"));
|
|
@@ -1543,9 +1522,9 @@ const doInstall = async (req, res, version, deepClean, runPull) => {
|
|
|
1543
1522
|
}
|
|
1544
1523
|
if (runPull) {
|
|
1545
1524
|
res.write(
|
|
1546
|
-
req.__("Pulling the
|
|
1525
|
+
req.__("Pulling the capacitor-builder docker image...") + "\n"
|
|
1547
1526
|
);
|
|
1548
|
-
const pullCode = await
|
|
1527
|
+
const pullCode = await pullCapacitorBuilder(req, res, version);
|
|
1549
1528
|
res.write(req.__("Pull done with code %s", pullCode) + "\n");
|
|
1550
1529
|
if (pullCode === 0) {
|
|
1551
1530
|
res.write(req.__("Pruning docker...") + "\n");
|
|
@@ -1741,8 +1720,8 @@ router.post(
|
|
|
1741
1720
|
|
|
1742
1721
|
let altname = await tenant_letsencrypt_name(subdomain);
|
|
1743
1722
|
|
|
1744
|
-
if (!altname || domain) {
|
|
1745
|
-
|
|
1723
|
+
if (!altname || !domain) {
|
|
1724
|
+
res.json({ error: "Set Base URL for both tenant and root first." });
|
|
1746
1725
|
return;
|
|
1747
1726
|
}
|
|
1748
1727
|
|
|
@@ -1759,13 +1738,14 @@ router.post(
|
|
|
1759
1738
|
|
|
1760
1739
|
await greenlock.sites.add({
|
|
1761
1740
|
subject: altname,
|
|
1741
|
+
altnames: [altname],
|
|
1762
1742
|
});
|
|
1763
1743
|
// letsencrypt
|
|
1764
1744
|
const tenant_letsencrypt_sites = getState().getConfig(
|
|
1765
1745
|
"tenant_letsencrypt_sites",
|
|
1766
1746
|
[]
|
|
1767
1747
|
);
|
|
1768
|
-
await getState().setConfig(tenant_letsencrypt_sites, [
|
|
1748
|
+
await getState().setConfig("tenant_letsencrypt_sites", [
|
|
1769
1749
|
altname,
|
|
1770
1750
|
...tenant_letsencrypt_sites,
|
|
1771
1751
|
]);
|
|
@@ -1775,12 +1755,10 @@ router.post(
|
|
|
1775
1755
|
notify: "Certificate added, please restart server",
|
|
1776
1756
|
});
|
|
1777
1757
|
} catch (e) {
|
|
1778
|
-
|
|
1779
|
-
res.redirect("/useradmin/ssl");
|
|
1758
|
+
res.json({ error: e.message });
|
|
1780
1759
|
}
|
|
1781
1760
|
} else {
|
|
1782
|
-
|
|
1783
|
-
res.redirect("/useradmin/ssl");
|
|
1761
|
+
res.json({ error: req.__("Not possible for tenant") });
|
|
1784
1762
|
}
|
|
1785
1763
|
})
|
|
1786
1764
|
);
|
|
@@ -1849,7 +1827,7 @@ router.post(
|
|
|
1849
1827
|
"tenant_letsencrypt_sites",
|
|
1850
1828
|
[]
|
|
1851
1829
|
);
|
|
1852
|
-
await getState().setConfig(tenant_letsencrypt_sites, [
|
|
1830
|
+
await getState().setConfig("tenant_letsencrypt_sites", [
|
|
1853
1831
|
...altnames,
|
|
1854
1832
|
...tenant_letsencrypt_sites,
|
|
1855
1833
|
]);
|
|
@@ -1991,9 +1969,9 @@ router.get(
|
|
|
1991
1969
|
});
|
|
1992
1970
|
})
|
|
1993
1971
|
);
|
|
1994
|
-
const buildDialogScript = (
|
|
1972
|
+
const buildDialogScript = (capacitorBuilderAvailable, isSbadmin2) =>
|
|
1995
1973
|
`<script>
|
|
1996
|
-
var
|
|
1974
|
+
var capacitorBuilderAvailable = ${capacitorBuilderAvailable};
|
|
1997
1975
|
var isSbadmin2 = ${isSbadmin2};
|
|
1998
1976
|
function showEntrySelect(type) {
|
|
1999
1977
|
for( const currentType of ["view", "page", "pagegroup"]) {
|
|
@@ -2018,11 +1996,26 @@ const buildDialogScript = (cordovaBuilderAvailable, isSbadmin2) =>
|
|
|
2018
1996
|
function handleMessages() {
|
|
2019
1997
|
notifyAlert("Building the app, please wait.", true)
|
|
2020
1998
|
}
|
|
1999
|
+
const versionPattern = /^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/;
|
|
2000
|
+
${domReady(`
|
|
2001
|
+
const versionInput = document.getElementById('appVersionInputId');
|
|
2002
|
+
if (versionInput) {
|
|
2003
|
+
versionInput.addEventListener('change', () => {
|
|
2004
|
+
const version = versionInput.value;
|
|
2005
|
+
if ((version !== '0.0.0' && versionPattern.test(version)) || version === "")
|
|
2006
|
+
versionInput.classList.remove('is-invalid');
|
|
2007
|
+
else
|
|
2008
|
+
versionInput.classList.add('is-invalid');
|
|
2009
|
+
});
|
|
2010
|
+
}
|
|
2011
|
+
else
|
|
2012
|
+
console.error('versionInput not found');
|
|
2013
|
+
`)}
|
|
2021
2014
|
</script>`;
|
|
2022
2015
|
|
|
2023
2016
|
const imageAvailable = async () => {
|
|
2024
2017
|
try {
|
|
2025
|
-
const image = new Docker().getImage("saltcorn/
|
|
2018
|
+
const image = new Docker().getImage("saltcorn/capacitor-builder");
|
|
2026
2019
|
await image.inspect();
|
|
2027
2020
|
return true;
|
|
2028
2021
|
} catch (e) {
|
|
@@ -2405,9 +2398,15 @@ router.get(
|
|
|
2405
2398
|
class: "form-control",
|
|
2406
2399
|
name: "appVersion",
|
|
2407
2400
|
id: "appVersionInputId",
|
|
2408
|
-
placeholder: "
|
|
2401
|
+
placeholder: "0.0.1",
|
|
2409
2402
|
value: builderSettings.appVersion || "",
|
|
2410
|
-
})
|
|
2403
|
+
}),
|
|
2404
|
+
div(
|
|
2405
|
+
{ class: "invalid-feedback" },
|
|
2406
|
+
req.__(
|
|
2407
|
+
"Please enter a version in the format 'x.y.z' (e.g. 0.0.1 with numbers from 0 to 999) or leave it empty."
|
|
2408
|
+
)
|
|
2409
|
+
)
|
|
2411
2410
|
)
|
|
2412
2411
|
),
|
|
2413
2412
|
// server url
|
|
@@ -2818,10 +2817,10 @@ router.get(
|
|
|
2818
2817
|
div(
|
|
2819
2818
|
label(
|
|
2820
2819
|
{ class: "form-label fw-bold" },
|
|
2821
|
-
req.__("
|
|
2820
|
+
req.__("Capacitor builder") +
|
|
2822
2821
|
a(
|
|
2823
2822
|
{
|
|
2824
|
-
href: "javascript:ajax_modal('/admin/help/
|
|
2823
|
+
href: "javascript:ajax_modal('/admin/help/Capacitor Builder?')",
|
|
2825
2824
|
},
|
|
2826
2825
|
i({ class: "fas fa-question-circle ps-1" })
|
|
2827
2826
|
)
|
|
@@ -2849,9 +2848,8 @@ router.get(
|
|
|
2849
2848
|
{ class: "col-sm-4" },
|
|
2850
2849
|
button(
|
|
2851
2850
|
{
|
|
2852
|
-
id: "pullCordovaBtnId",
|
|
2853
2851
|
type: "button",
|
|
2854
|
-
onClick: `
|
|
2852
|
+
onClick: `pull_capacitor_builder(this);`,
|
|
2855
2853
|
class: "btn btn-warning",
|
|
2856
2854
|
},
|
|
2857
2855
|
req.__("pull")
|
|
@@ -2859,7 +2857,7 @@ router.get(
|
|
|
2859
2857
|
span(
|
|
2860
2858
|
{
|
|
2861
2859
|
role: "button",
|
|
2862
|
-
onClick: "
|
|
2860
|
+
onClick: "check_capacitor_builder()",
|
|
2863
2861
|
},
|
|
2864
2862
|
span({ class: "ps-3" }, req.__("refresh")),
|
|
2865
2863
|
i({ class: "ps-2 fas fa-undo" })
|
|
@@ -3271,6 +3269,16 @@ router.post(
|
|
|
3271
3269
|
error: req.__("Please enter a valid server URL."),
|
|
3272
3270
|
});
|
|
3273
3271
|
}
|
|
3272
|
+
if (
|
|
3273
|
+
(appVersion && !/^\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(appVersion)) ||
|
|
3274
|
+
appVersion === "0.0.0"
|
|
3275
|
+
) {
|
|
3276
|
+
return res.json({
|
|
3277
|
+
error: req.__(
|
|
3278
|
+
"Please enter a version in the format 'x.y.z' (e.g. 0.0.1 with numbers from 0 to 999) or leave it empty."
|
|
3279
|
+
),
|
|
3280
|
+
});
|
|
3281
|
+
}
|
|
3274
3282
|
if (iOSPlatform && !provisioningProfile) {
|
|
3275
3283
|
return res.json({
|
|
3276
3284
|
error: req.__(
|
|
@@ -3349,36 +3357,37 @@ router.post(
|
|
|
3349
3357
|
});
|
|
3350
3358
|
const childOutputs = [];
|
|
3351
3359
|
child.stdout.on("data", (data) => {
|
|
3352
|
-
|
|
3353
|
-
|
|
3360
|
+
const outMsg = data.toString();
|
|
3361
|
+
getState().log(5, outMsg);
|
|
3362
|
+
if (data) childOutputs.push(outMsg);
|
|
3354
3363
|
});
|
|
3355
3364
|
child.stderr.on("data", (data) => {
|
|
3356
|
-
|
|
3357
|
-
|
|
3365
|
+
const errMsg = data ? data.toString() : req.__("An error occurred");
|
|
3366
|
+
getState().log(5, errMsg);
|
|
3367
|
+
childOutputs.push(errMsg);
|
|
3358
3368
|
});
|
|
3359
3369
|
child.on("exit", (exitCode, signal) => {
|
|
3360
3370
|
const logFile = exitCode === 0 ? "logs.txt" : "error_logs.txt";
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
// no transaction, '/build-mobile-app/finished' filters for valid attributes
|
|
3370
|
-
await File.set_xattr_of_existing_file(logFile, buildDir, req.user);
|
|
3371
|
-
}
|
|
3371
|
+
const exitMsg = childOutputs.join("\n");
|
|
3372
|
+
fs.writeFile(path.join(buildDir, logFile), exitMsg, async (error) => {
|
|
3373
|
+
if (error) {
|
|
3374
|
+
console.log(`unable to write '${logFile}' to '${buildDir}'`);
|
|
3375
|
+
console.log(error);
|
|
3376
|
+
} else {
|
|
3377
|
+
// no transaction, '/build-mobile-app/finished' filters for valid attributes
|
|
3378
|
+
await File.set_xattr_of_existing_file(logFile, buildDir, req.user);
|
|
3372
3379
|
}
|
|
3373
|
-
);
|
|
3380
|
+
});
|
|
3374
3381
|
});
|
|
3375
3382
|
child.on("error", (msg) => {
|
|
3376
3383
|
const message = msg.message ? msg.message : msg.code;
|
|
3377
3384
|
const stack = msg.stack ? msg.stack : "";
|
|
3378
3385
|
const logFile = "error_logs.txt";
|
|
3386
|
+
const errMsg = [message, stack].join("\n");
|
|
3387
|
+
getState().log(5, msg);
|
|
3379
3388
|
fs.writeFile(
|
|
3380
3389
|
path.join(buildDir, "error_logs.txt"),
|
|
3381
|
-
|
|
3390
|
+
errMsg,
|
|
3382
3391
|
async (error) => {
|
|
3383
3392
|
if (error) {
|
|
3384
3393
|
console.log(`unable to write logFile to '${buildDir}'`);
|
|
@@ -3394,13 +3403,13 @@ router.post(
|
|
|
3394
3403
|
);
|
|
3395
3404
|
|
|
3396
3405
|
router.post(
|
|
3397
|
-
"/mobile-app/pull-
|
|
3406
|
+
"/mobile-app/pull-capacitor-builder",
|
|
3398
3407
|
isAdmin,
|
|
3399
3408
|
error_catcher(async (req, res) => {
|
|
3400
3409
|
const state = getState();
|
|
3401
3410
|
const child = spawn(
|
|
3402
3411
|
`${process.env.DOCKER_BIN ? `${process.env.DOCKER_BIN}/` : ""}docker`,
|
|
3403
|
-
["pull",
|
|
3412
|
+
["pull", `saltcorn/capacitor-builder:${state.scVersion}`],
|
|
3404
3413
|
{
|
|
3405
3414
|
stdio: ["ignore", "pipe", "pipe"],
|
|
3406
3415
|
cwd: ".",
|
|
@@ -3415,11 +3424,11 @@ router.post(
|
|
|
3415
3424
|
child.on("exit", (exitCode, signal) => {
|
|
3416
3425
|
state.log(
|
|
3417
3426
|
2,
|
|
3418
|
-
`"pull
|
|
3427
|
+
`"pull capacitor-builder exit with code: ${exitCode} and signal: ${signal}`
|
|
3419
3428
|
);
|
|
3420
3429
|
});
|
|
3421
3430
|
child.on("error", (msg) => {
|
|
3422
|
-
state.log(1, `pull
|
|
3431
|
+
state.log(1, `pull capacitor-builder error: ${msg}`);
|
|
3423
3432
|
});
|
|
3424
3433
|
|
|
3425
3434
|
res.json({});
|
|
@@ -3427,7 +3436,7 @@ router.post(
|
|
|
3427
3436
|
);
|
|
3428
3437
|
|
|
3429
3438
|
router.get(
|
|
3430
|
-
"/mobile-app/check-
|
|
3439
|
+
"/mobile-app/check-capacitor-builder",
|
|
3431
3440
|
isAdmin,
|
|
3432
3441
|
error_catcher(async (req, res) => {
|
|
3433
3442
|
const installed = await imageAvailable();
|
package/routes/api.js
CHANGED
|
@@ -484,6 +484,57 @@ router.post(
|
|
|
484
484
|
})
|
|
485
485
|
);
|
|
486
486
|
|
|
487
|
+
/**
|
|
488
|
+
* Delete Table row by ID using POST
|
|
489
|
+
* @name delete/:tableName/:id
|
|
490
|
+
* @function
|
|
491
|
+
* @memberof module:routes/api~apiRouter
|
|
492
|
+
*/
|
|
493
|
+
router.post(
|
|
494
|
+
"/:tableName/delete/:id",
|
|
495
|
+
// in case of primary key different from id - id will be string "undefined"
|
|
496
|
+
error_catcher(async (req, res, next) => {
|
|
497
|
+
const { tableName, id } = req.params;
|
|
498
|
+
const table = Table.findOne({ name: tableName });
|
|
499
|
+
if (!table) {
|
|
500
|
+
getState().log(3, `API DELETE ${tableName} not found`);
|
|
501
|
+
res.status(404).json({ error: req.__("Not found") });
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
await passport.authenticate(
|
|
505
|
+
"api-bearer",
|
|
506
|
+
{ session: false },
|
|
507
|
+
async function (err, user, info) {
|
|
508
|
+
if (accessAllowedWrite(req, user, table)) {
|
|
509
|
+
try {
|
|
510
|
+
if (id === "undefined") {
|
|
511
|
+
const pk_name = table.pk_name;
|
|
512
|
+
//const fields = table.getFields();
|
|
513
|
+
const row = req.body;
|
|
514
|
+
//readState(row, fields);
|
|
515
|
+
await table.deleteRows(
|
|
516
|
+
{ [pk_name]: row[pk_name] },
|
|
517
|
+
user || req.user || { role_id: 100 }
|
|
518
|
+
);
|
|
519
|
+
} else
|
|
520
|
+
await table.deleteRows(
|
|
521
|
+
{ id },
|
|
522
|
+
user || req.user || { role_id: 100 }
|
|
523
|
+
);
|
|
524
|
+
res.json({ success: true });
|
|
525
|
+
} catch (e) {
|
|
526
|
+
getState().log(2, `API DELETE ${table.name} error: ${e.message}`);
|
|
527
|
+
res.status(400).json({ error: e.message });
|
|
528
|
+
}
|
|
529
|
+
} else {
|
|
530
|
+
getState().log(3, `API DELETE ${table.name} not authorized`);
|
|
531
|
+
res.status(401).json({ error: req.__("Not authorized") });
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
)(req, res, next);
|
|
535
|
+
})
|
|
536
|
+
);
|
|
537
|
+
|
|
487
538
|
/**
|
|
488
539
|
* Update Table row directed by ID using POST
|
|
489
540
|
* POST api/<table>/id
|
package/routes/config.js
CHANGED
|
@@ -5,31 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
const Router = require("express-promise-router");
|
|
7
7
|
|
|
8
|
-
const
|
|
9
|
-
const File = require("@saltcorn/data/models/file");
|
|
10
|
-
const Table = require("@saltcorn/data/models/table");
|
|
11
|
-
const View = require("@saltcorn/data/models/view");
|
|
12
|
-
const Form = require("@saltcorn/data/models/form");
|
|
13
|
-
const { isAdmin, setTenant, error_catcher } = require("./utils.js");
|
|
8
|
+
const { isAdmin, error_catcher } = require("./utils.js");
|
|
14
9
|
const { getState } = require("@saltcorn/data/db/state");
|
|
15
10
|
|
|
16
|
-
const {
|
|
17
|
-
mkTable,
|
|
18
|
-
renderForm,
|
|
19
|
-
link,
|
|
20
|
-
post_btn,
|
|
21
|
-
post_delete_btn,
|
|
22
|
-
} = require("@saltcorn/markup");
|
|
23
|
-
const {
|
|
24
|
-
getConfig,
|
|
25
|
-
setConfig,
|
|
26
|
-
getAllConfigOrDefaults,
|
|
27
|
-
deleteConfig,
|
|
28
|
-
configTypes,
|
|
29
|
-
isFixedConfig,
|
|
30
|
-
} = require("@saltcorn/data/models/config");
|
|
31
|
-
const { table, tbody, tr, th, td, div } = require("@saltcorn/markup/tags");
|
|
32
|
-
|
|
33
11
|
/**
|
|
34
12
|
* @type {object}
|
|
35
13
|
* @const
|
|
@@ -52,7 +30,43 @@ router.post(
|
|
|
52
30
|
error_catcher(async (req, res) => {
|
|
53
31
|
const { key } = req.params;
|
|
54
32
|
await getState().deleteConfig(key);
|
|
55
|
-
req.
|
|
56
|
-
|
|
33
|
+
if (req.xhr) res.json({ success: "ok" });
|
|
34
|
+
else {
|
|
35
|
+
req.flash("success", req.__(`Configuration key %s deleted`, key));
|
|
36
|
+
res.redirect(`/admin`);
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
router.post(
|
|
42
|
+
"/save",
|
|
43
|
+
isAdmin,
|
|
44
|
+
error_catcher(async (req, res) => {
|
|
45
|
+
const state = getState();
|
|
46
|
+
|
|
47
|
+
//TODO check this is a config key
|
|
48
|
+
const validKeyName = (k) =>
|
|
49
|
+
k !== "_csrf" && k !== "constructor" && k !== "__proto__";
|
|
50
|
+
|
|
51
|
+
for (const [k, v] of Object.entries(req.body)) {
|
|
52
|
+
if (!state.isFixedConfig(k) && typeof v !== "undefined" && validKeyName(k)) {
|
|
53
|
+
//TODO read value from type
|
|
54
|
+
await state.setConfig(k, v);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// checkboxes that are false are not sent in post body. Check here
|
|
59
|
+
const { boolcheck } = req.query;
|
|
60
|
+
const boolchecks =
|
|
61
|
+
typeof boolcheck === "undefined"
|
|
62
|
+
? []
|
|
63
|
+
: Array.isArray(boolcheck)
|
|
64
|
+
? boolcheck
|
|
65
|
+
: [boolcheck];
|
|
66
|
+
for (const k of boolchecks) {
|
|
67
|
+
if (typeof req.body[k] === "undefined" && validKeyName(k))
|
|
68
|
+
await state.setConfig(k, false);
|
|
69
|
+
}
|
|
70
|
+
res.json({ success: "ok" });
|
|
57
71
|
})
|
|
58
72
|
);
|
package/routes/eventlog.js
CHANGED
|
@@ -81,6 +81,31 @@ const logSettingsForm = async (req) => {
|
|
|
81
81
|
input_type: "date",
|
|
82
82
|
attributes: { minDate: new Date(), maxDate: hoursFuture(24 * 7 * 2) },
|
|
83
83
|
},
|
|
84
|
+
{
|
|
85
|
+
input_type: "section_header",
|
|
86
|
+
label: req.__("Delete old workflow runs with status after days"),
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: "delete_finished_workflows_days",
|
|
90
|
+
label: req.__("Finished"),
|
|
91
|
+
type: "Integer",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: "delete_error_workflows_days",
|
|
95
|
+
label: req.__("Error"),
|
|
96
|
+
type: "Integer",
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: "delete_waiting_workflows_days",
|
|
100
|
+
label: req.__("Waiting"),
|
|
101
|
+
type: "Integer",
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
{
|
|
105
|
+
name: "delete_running_workflows_days",
|
|
106
|
+
label: req.__("Running"),
|
|
107
|
+
type: "Integer",
|
|
108
|
+
},
|
|
84
109
|
{
|
|
85
110
|
input_type: "section_header",
|
|
86
111
|
label: req.__("Which events should be logged?"),
|
|
@@ -143,6 +168,10 @@ router.get(
|
|
|
143
168
|
"next_weekly_event",
|
|
144
169
|
{}
|
|
145
170
|
);
|
|
171
|
+
["error", "finished", "running", "waiting"].forEach((k) => {
|
|
172
|
+
let cfgk = `delete_${k}_workflows_days`;
|
|
173
|
+
form.values[cfgk] = getState().getConfig(cfgk);
|
|
174
|
+
});
|
|
146
175
|
|
|
147
176
|
send_events_page({
|
|
148
177
|
res,
|
|
@@ -348,6 +377,13 @@ router.post(
|
|
|
348
377
|
delete form.values[k];
|
|
349
378
|
}
|
|
350
379
|
}
|
|
380
|
+
for (const status of ["error", "finished", "running", "waiting"]) {
|
|
381
|
+
let k = `delete_${status}_workflows_days`;
|
|
382
|
+
if (form.values[k]) {
|
|
383
|
+
await getState().setConfig(k, form.values[k]);
|
|
384
|
+
delete form.values[k];
|
|
385
|
+
}
|
|
386
|
+
}
|
|
351
387
|
|
|
352
388
|
await getState().setConfig("event_log_settings", form.values);
|
|
353
389
|
|
|
@@ -424,6 +460,7 @@ router.get(
|
|
|
424
460
|
error_catcher(async (req, res) => {
|
|
425
461
|
const { id } = req.params;
|
|
426
462
|
const ev = await EventLog.findOneWithUser(id);
|
|
463
|
+
const locale = getState().getConfig("default_locale", "en");
|
|
427
464
|
send_events_page({
|
|
428
465
|
res,
|
|
429
466
|
req,
|
|
@@ -435,7 +472,10 @@ router.get(
|
|
|
435
472
|
table(
|
|
436
473
|
{ class: "table eventlog" },
|
|
437
474
|
tbody(
|
|
438
|
-
tr(
|
|
475
|
+
tr(
|
|
476
|
+
th(req.__("When")),
|
|
477
|
+
td(localeDateTime(ev.occur_at, {}, locale))
|
|
478
|
+
),
|
|
439
479
|
tr(th(req.__("Type")), td(ev.event_type)),
|
|
440
480
|
tr(th(req.__("Channel")), td(ev.channel)),
|
|
441
481
|
tr(th(req.__("User")), td(ev.email))
|
package/routes/fields.js
CHANGED
|
@@ -301,6 +301,10 @@ const fieldFlow = (req) =>
|
|
|
301
301
|
if (context.id) {
|
|
302
302
|
const field = await Field.findOne({ id: context.id });
|
|
303
303
|
try {
|
|
304
|
+
if (fldRow.label && field.label != fldRow.label) {
|
|
305
|
+
fldRow.name = Field.labelToName(fldRow.label);
|
|
306
|
+
}
|
|
307
|
+
|
|
304
308
|
await field.update(fldRow);
|
|
305
309
|
} catch (e) {
|
|
306
310
|
return {
|
|
@@ -362,10 +366,12 @@ const fieldFlow = (req) =>
|
|
|
362
366
|
name: req.__("Attributes"),
|
|
363
367
|
contextField: "attributes",
|
|
364
368
|
onlyWhen: (context) => {
|
|
365
|
-
|
|
369
|
+
const type = getState().types[context.type];
|
|
370
|
+
if (context.calculated && !type?.setTypeAttributesForCalculatedFields)
|
|
371
|
+
return false;
|
|
372
|
+
|
|
366
373
|
if (context.type === "File") return true;
|
|
367
374
|
if (new Field(context).is_fkey) return false;
|
|
368
|
-
const type = getState().types[context.type];
|
|
369
375
|
if (!type) return false;
|
|
370
376
|
const attrs = Field.getTypeAttributes(
|
|
371
377
|
type.attributes,
|
package/routes/homepage.js
CHANGED
|
@@ -549,7 +549,14 @@ const no_views_logged_in = async (req, res) => {
|
|
|
549
549
|
* @returns {Promise<boolean>}
|
|
550
550
|
*/
|
|
551
551
|
const get_config_response = async (role_id, res, req) => {
|
|
552
|
-
const wrap = async (
|
|
552
|
+
const wrap = async (
|
|
553
|
+
contents,
|
|
554
|
+
homeCfg,
|
|
555
|
+
title,
|
|
556
|
+
description,
|
|
557
|
+
no_menu,
|
|
558
|
+
requestFluidLayout
|
|
559
|
+
) => {
|
|
553
560
|
if (contents.html_file) await sendHtmlFile(req, res, contents.html_file);
|
|
554
561
|
else
|
|
555
562
|
res.sendWrap(
|
|
@@ -558,6 +565,7 @@ const get_config_response = async (role_id, res, req) => {
|
|
|
558
565
|
description: description || "",
|
|
559
566
|
bodyClass: "page_" + db.sqlsanitize(homeCfg),
|
|
560
567
|
no_menu,
|
|
568
|
+
requestFluidLayout,
|
|
561
569
|
},
|
|
562
570
|
contents
|
|
563
571
|
);
|
|
@@ -578,7 +586,8 @@ const get_config_response = async (role_id, res, req) => {
|
|
|
578
586
|
homeCfg,
|
|
579
587
|
db_page.title,
|
|
580
588
|
db_page.description,
|
|
581
|
-
db_page.attributes?.no_menu
|
|
589
|
+
db_page.attributes?.no_menu,
|
|
590
|
+
db_page.attributes?.request_fluid_layout
|
|
582
591
|
);
|
|
583
592
|
else {
|
|
584
593
|
const group = PageGroup.findOne({ name: homeCfg });
|
|
@@ -592,7 +601,8 @@ const get_config_response = async (role_id, res, req) => {
|
|
|
592
601
|
homeCfg,
|
|
593
602
|
eligible.title,
|
|
594
603
|
eligible.description,
|
|
595
|
-
eligible.attributes?.no_menu
|
|
604
|
+
eligible.attributes?.no_menu,
|
|
605
|
+
eligible.attributes?.request_fluid_layout
|
|
596
606
|
);
|
|
597
607
|
} else wrap(req.__("%s has no eligible page", group.name), homeCfg);
|
|
598
608
|
} else res.redirect(homeCfg);
|