@saltcorn/server 1.1.0-beta.9 → 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 +91 -81
- 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 +10 -2
- package/routes/viewedit.js +8 -8
- 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");
|
|
@@ -1990,9 +1969,9 @@ router.get(
|
|
|
1990
1969
|
});
|
|
1991
1970
|
})
|
|
1992
1971
|
);
|
|
1993
|
-
const buildDialogScript = (
|
|
1972
|
+
const buildDialogScript = (capacitorBuilderAvailable, isSbadmin2) =>
|
|
1994
1973
|
`<script>
|
|
1995
|
-
var
|
|
1974
|
+
var capacitorBuilderAvailable = ${capacitorBuilderAvailable};
|
|
1996
1975
|
var isSbadmin2 = ${isSbadmin2};
|
|
1997
1976
|
function showEntrySelect(type) {
|
|
1998
1977
|
for( const currentType of ["view", "page", "pagegroup"]) {
|
|
@@ -2017,11 +1996,26 @@ const buildDialogScript = (cordovaBuilderAvailable, isSbadmin2) =>
|
|
|
2017
1996
|
function handleMessages() {
|
|
2018
1997
|
notifyAlert("Building the app, please wait.", true)
|
|
2019
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
|
+
`)}
|
|
2020
2014
|
</script>`;
|
|
2021
2015
|
|
|
2022
2016
|
const imageAvailable = async () => {
|
|
2023
2017
|
try {
|
|
2024
|
-
const image = new Docker().getImage("saltcorn/
|
|
2018
|
+
const image = new Docker().getImage("saltcorn/capacitor-builder");
|
|
2025
2019
|
await image.inspect();
|
|
2026
2020
|
return true;
|
|
2027
2021
|
} catch (e) {
|
|
@@ -2404,9 +2398,15 @@ router.get(
|
|
|
2404
2398
|
class: "form-control",
|
|
2405
2399
|
name: "appVersion",
|
|
2406
2400
|
id: "appVersionInputId",
|
|
2407
|
-
placeholder: "
|
|
2401
|
+
placeholder: "0.0.1",
|
|
2408
2402
|
value: builderSettings.appVersion || "",
|
|
2409
|
-
})
|
|
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
|
+
)
|
|
2410
2410
|
)
|
|
2411
2411
|
),
|
|
2412
2412
|
// server url
|
|
@@ -2817,10 +2817,10 @@ router.get(
|
|
|
2817
2817
|
div(
|
|
2818
2818
|
label(
|
|
2819
2819
|
{ class: "form-label fw-bold" },
|
|
2820
|
-
req.__("
|
|
2820
|
+
req.__("Capacitor builder") +
|
|
2821
2821
|
a(
|
|
2822
2822
|
{
|
|
2823
|
-
href: "javascript:ajax_modal('/admin/help/
|
|
2823
|
+
href: "javascript:ajax_modal('/admin/help/Capacitor Builder?')",
|
|
2824
2824
|
},
|
|
2825
2825
|
i({ class: "fas fa-question-circle ps-1" })
|
|
2826
2826
|
)
|
|
@@ -2848,9 +2848,8 @@ router.get(
|
|
|
2848
2848
|
{ class: "col-sm-4" },
|
|
2849
2849
|
button(
|
|
2850
2850
|
{
|
|
2851
|
-
id: "pullCordovaBtnId",
|
|
2852
2851
|
type: "button",
|
|
2853
|
-
onClick: `
|
|
2852
|
+
onClick: `pull_capacitor_builder(this);`,
|
|
2854
2853
|
class: "btn btn-warning",
|
|
2855
2854
|
},
|
|
2856
2855
|
req.__("pull")
|
|
@@ -2858,7 +2857,7 @@ router.get(
|
|
|
2858
2857
|
span(
|
|
2859
2858
|
{
|
|
2860
2859
|
role: "button",
|
|
2861
|
-
onClick: "
|
|
2860
|
+
onClick: "check_capacitor_builder()",
|
|
2862
2861
|
},
|
|
2863
2862
|
span({ class: "ps-3" }, req.__("refresh")),
|
|
2864
2863
|
i({ class: "ps-2 fas fa-undo" })
|
|
@@ -3270,6 +3269,16 @@ router.post(
|
|
|
3270
3269
|
error: req.__("Please enter a valid server URL."),
|
|
3271
3270
|
});
|
|
3272
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
|
+
}
|
|
3273
3282
|
if (iOSPlatform && !provisioningProfile) {
|
|
3274
3283
|
return res.json({
|
|
3275
3284
|
error: req.__(
|
|
@@ -3348,36 +3357,37 @@ router.post(
|
|
|
3348
3357
|
});
|
|
3349
3358
|
const childOutputs = [];
|
|
3350
3359
|
child.stdout.on("data", (data) => {
|
|
3351
|
-
|
|
3352
|
-
|
|
3360
|
+
const outMsg = data.toString();
|
|
3361
|
+
getState().log(5, outMsg);
|
|
3362
|
+
if (data) childOutputs.push(outMsg);
|
|
3353
3363
|
});
|
|
3354
3364
|
child.stderr.on("data", (data) => {
|
|
3355
|
-
|
|
3356
|
-
|
|
3365
|
+
const errMsg = data ? data.toString() : req.__("An error occurred");
|
|
3366
|
+
getState().log(5, errMsg);
|
|
3367
|
+
childOutputs.push(errMsg);
|
|
3357
3368
|
});
|
|
3358
3369
|
child.on("exit", (exitCode, signal) => {
|
|
3359
3370
|
const logFile = exitCode === 0 ? "logs.txt" : "error_logs.txt";
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
// no transaction, '/build-mobile-app/finished' filters for valid attributes
|
|
3369
|
-
await File.set_xattr_of_existing_file(logFile, buildDir, req.user);
|
|
3370
|
-
}
|
|
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);
|
|
3371
3379
|
}
|
|
3372
|
-
);
|
|
3380
|
+
});
|
|
3373
3381
|
});
|
|
3374
3382
|
child.on("error", (msg) => {
|
|
3375
3383
|
const message = msg.message ? msg.message : msg.code;
|
|
3376
3384
|
const stack = msg.stack ? msg.stack : "";
|
|
3377
3385
|
const logFile = "error_logs.txt";
|
|
3386
|
+
const errMsg = [message, stack].join("\n");
|
|
3387
|
+
getState().log(5, msg);
|
|
3378
3388
|
fs.writeFile(
|
|
3379
3389
|
path.join(buildDir, "error_logs.txt"),
|
|
3380
|
-
|
|
3390
|
+
errMsg,
|
|
3381
3391
|
async (error) => {
|
|
3382
3392
|
if (error) {
|
|
3383
3393
|
console.log(`unable to write logFile to '${buildDir}'`);
|
|
@@ -3393,13 +3403,13 @@ router.post(
|
|
|
3393
3403
|
);
|
|
3394
3404
|
|
|
3395
3405
|
router.post(
|
|
3396
|
-
"/mobile-app/pull-
|
|
3406
|
+
"/mobile-app/pull-capacitor-builder",
|
|
3397
3407
|
isAdmin,
|
|
3398
3408
|
error_catcher(async (req, res) => {
|
|
3399
3409
|
const state = getState();
|
|
3400
3410
|
const child = spawn(
|
|
3401
3411
|
`${process.env.DOCKER_BIN ? `${process.env.DOCKER_BIN}/` : ""}docker`,
|
|
3402
|
-
["pull",
|
|
3412
|
+
["pull", `saltcorn/capacitor-builder:${state.scVersion}`],
|
|
3403
3413
|
{
|
|
3404
3414
|
stdio: ["ignore", "pipe", "pipe"],
|
|
3405
3415
|
cwd: ".",
|
|
@@ -3414,11 +3424,11 @@ router.post(
|
|
|
3414
3424
|
child.on("exit", (exitCode, signal) => {
|
|
3415
3425
|
state.log(
|
|
3416
3426
|
2,
|
|
3417
|
-
`"pull
|
|
3427
|
+
`"pull capacitor-builder exit with code: ${exitCode} and signal: ${signal}`
|
|
3418
3428
|
);
|
|
3419
3429
|
});
|
|
3420
3430
|
child.on("error", (msg) => {
|
|
3421
|
-
state.log(1, `pull
|
|
3431
|
+
state.log(1, `pull capacitor-builder error: ${msg}`);
|
|
3422
3432
|
});
|
|
3423
3433
|
|
|
3424
3434
|
res.json({});
|
|
@@ -3426,7 +3436,7 @@ router.post(
|
|
|
3426
3436
|
);
|
|
3427
3437
|
|
|
3428
3438
|
router.get(
|
|
3429
|
-
"/mobile-app/check-
|
|
3439
|
+
"/mobile-app/check-capacitor-builder",
|
|
3430
3440
|
isAdmin,
|
|
3431
3441
|
error_catcher(async (req, res) => {
|
|
3432
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);
|
package/routes/list.js
CHANGED
|
@@ -26,6 +26,7 @@ const {
|
|
|
26
26
|
const Table = require("@saltcorn/data/models/table");
|
|
27
27
|
const { isAdmin, error_catcher } = require("./utils");
|
|
28
28
|
const moment = require("moment");
|
|
29
|
+
const { getState } = require("@saltcorn/data/db/state");
|
|
29
30
|
|
|
30
31
|
/**
|
|
31
32
|
* @type {object}
|
|
@@ -267,9 +268,11 @@ router.get(
|
|
|
267
268
|
clipboard: false,
|
|
268
269
|
cellClick: "__delete_tabulator_row",
|
|
269
270
|
});
|
|
271
|
+
const isDark = getState().getLightDarkMode(req.user) === "dark";
|
|
270
272
|
res.sendWrap(
|
|
271
273
|
{
|
|
272
274
|
title: req.__(`%s data table`, table.name),
|
|
275
|
+
requestFluidLayout: true,
|
|
273
276
|
headers: [
|
|
274
277
|
//jsgrid - grid editor external component
|
|
275
278
|
{
|
|
@@ -295,6 +298,13 @@ router.get(
|
|
|
295
298
|
{
|
|
296
299
|
css: `/static_assets/${db.connectObj.version_tag}/flatpickr.min.css`,
|
|
297
300
|
},
|
|
301
|
+
...(isDark
|
|
302
|
+
? [
|
|
303
|
+
{
|
|
304
|
+
css: `/static_assets/${db.connectObj.version_tag}/flatpickr-dark.css`,
|
|
305
|
+
},
|
|
306
|
+
]
|
|
307
|
+
: []),
|
|
298
308
|
],
|
|
299
309
|
},
|
|
300
310
|
{
|
|
@@ -426,7 +436,13 @@ router.get(
|
|
|
426
436
|
),
|
|
427
437
|
div({ id: "jsGridNotify" }),
|
|
428
438
|
|
|
429
|
-
div({
|
|
439
|
+
div({
|
|
440
|
+
id: "jsGrid",
|
|
441
|
+
class:
|
|
442
|
+
getState().getLightDarkMode(req.user) === "dark"
|
|
443
|
+
? "table-dark"
|
|
444
|
+
: undefined,
|
|
445
|
+
})
|
|
430
446
|
),
|
|
431
447
|
},
|
|
432
448
|
],
|