@saltcorn/server 0.9.5-beta.2 → 0.9.5-beta.21
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/routes.js +54 -7
- package/errors.js +1 -0
- package/help/Cordova Builder.tmd +13 -0
- package/load_plugins.js +89 -144
- package/locales/en.json +31 -1
- package/locales/ru.json +1134 -1101
- package/package.json +16 -13
- package/public/log_viewer_utils.js +17 -1
- package/public/saltcorn-common.js +173 -24
- package/public/saltcorn.css +4 -0
- package/public/saltcorn.js +67 -46
- package/restart_watcher.js +2 -0
- package/routes/actions.js +17 -1
- package/routes/admin.js +389 -20
- package/routes/api.js +4 -1
- package/routes/common_lists.js +1 -1
- package/routes/fields.js +13 -8
- package/routes/homepage.js +6 -3
- package/routes/menu.js +12 -3
- package/routes/pageedit.js +1 -1
- package/routes/plugins.js +265 -29
- package/routes/search.js +28 -2
- package/routes/tables.js +4 -0
- package/s3storage.js +1 -0
- package/serve.js +1 -1
- package/tests/page.test.js +11 -1
package/routes/admin.js
CHANGED
|
@@ -107,6 +107,7 @@ const { getSafeSaltcornCmd } = require("@saltcorn/data/utils");
|
|
|
107
107
|
const stream = require("stream");
|
|
108
108
|
const Crash = require("@saltcorn/data/models/crash");
|
|
109
109
|
const { get_help_markup } = require("../help/index.js");
|
|
110
|
+
const Docker = require("dockerode");
|
|
110
111
|
|
|
111
112
|
const router = new Router();
|
|
112
113
|
module.exports = router;
|
|
@@ -273,6 +274,8 @@ router.get(
|
|
|
273
274
|
const aBackupFilePrefixForm = backupFilePrefixForm(req);
|
|
274
275
|
aBackupFilePrefixForm.values.backup_file_prefix =
|
|
275
276
|
getState().getConfig("backup_file_prefix");
|
|
277
|
+
aBackupFilePrefixForm.values.backup_history =
|
|
278
|
+
getState().getConfig("backup_history");
|
|
276
279
|
//
|
|
277
280
|
const backupForm = autoBackupForm(req);
|
|
278
281
|
backupForm.values.auto_backup_frequency = getState().getConfig(
|
|
@@ -281,9 +284,23 @@ router.get(
|
|
|
281
284
|
backupForm.values.auto_backup_destination = getState().getConfig(
|
|
282
285
|
"auto_backup_destination"
|
|
283
286
|
);
|
|
287
|
+
backupForm.values.auto_backup_tenants = getState().getConfig(
|
|
288
|
+
"auto_backup_tenants"
|
|
289
|
+
);
|
|
284
290
|
backupForm.values.auto_backup_directory = getState().getConfig(
|
|
285
291
|
"auto_backup_directory"
|
|
286
292
|
);
|
|
293
|
+
backupForm.values.auto_backup_username = getState().getConfig(
|
|
294
|
+
"auto_backup_username"
|
|
295
|
+
);
|
|
296
|
+
backupForm.values.auto_backup_server =
|
|
297
|
+
getState().getConfig("auto_backup_server");
|
|
298
|
+
backupForm.values.auto_backup_password = getState().getConfig(
|
|
299
|
+
"auto_backup_password"
|
|
300
|
+
);
|
|
301
|
+
backupForm.values.auto_backup_port =
|
|
302
|
+
getState().getConfig("auto_backup_port");
|
|
303
|
+
|
|
287
304
|
backupForm.values.auto_backup_expire_days = getState().getConfig(
|
|
288
305
|
"auto_backup_expire_days"
|
|
289
306
|
);
|
|
@@ -673,6 +690,13 @@ const backupFilePrefixForm = (req) =>
|
|
|
673
690
|
sublabel: req.__("Backup file prefix"),
|
|
674
691
|
default: "sc-backup-",
|
|
675
692
|
},
|
|
693
|
+
{
|
|
694
|
+
type: "Bool",
|
|
695
|
+
label: req.__("History"),
|
|
696
|
+
name: "backup_history",
|
|
697
|
+
sublabel: req.__("Include table history in backup"),
|
|
698
|
+
default: true,
|
|
699
|
+
},
|
|
676
700
|
],
|
|
677
701
|
});
|
|
678
702
|
|
|
@@ -681,8 +705,10 @@ const backupFilePrefixForm = (req) =>
|
|
|
681
705
|
* @param {object} req
|
|
682
706
|
* @returns {Form} form
|
|
683
707
|
*/
|
|
684
|
-
const autoBackupForm = (req) =>
|
|
685
|
-
|
|
708
|
+
const autoBackupForm = (req) => {
|
|
709
|
+
const isRoot = db.getTenantSchema() === db.connectObj.default_schema;
|
|
710
|
+
|
|
711
|
+
return new Form({
|
|
686
712
|
action: "/admin/set-auto-backup",
|
|
687
713
|
onChange: `saveAndContinue(this);$('#btnBackupNow').prop('disabled', $('#inputauto_backup_frequency').val()==='Never');`,
|
|
688
714
|
noSubmitButton: true,
|
|
@@ -708,7 +734,47 @@ const autoBackupForm = (req) =>
|
|
|
708
734
|
name: "auto_backup_destination",
|
|
709
735
|
required: true,
|
|
710
736
|
showIf: { auto_backup_frequency: ["Daily", "Weekly"] },
|
|
711
|
-
attributes: {
|
|
737
|
+
attributes: {
|
|
738
|
+
auto_backup_frequency: ["Daily", "Weekly"],
|
|
739
|
+
options: ["Saltcorn files", "Local directory", "SFTP server"],
|
|
740
|
+
},
|
|
741
|
+
},
|
|
742
|
+
{
|
|
743
|
+
type: "String",
|
|
744
|
+
label: req.__("Server host"),
|
|
745
|
+
name: "auto_backup_server",
|
|
746
|
+
showIf: {
|
|
747
|
+
auto_backup_frequency: ["Daily", "Weekly"],
|
|
748
|
+
auto_backup_destination: "SFTP server",
|
|
749
|
+
},
|
|
750
|
+
},
|
|
751
|
+
{
|
|
752
|
+
type: "String",
|
|
753
|
+
label: req.__("Username"),
|
|
754
|
+
name: "auto_backup_username",
|
|
755
|
+
showIf: {
|
|
756
|
+
auto_backup_frequency: ["Daily", "Weekly"],
|
|
757
|
+
auto_backup_destination: "SFTP server",
|
|
758
|
+
},
|
|
759
|
+
},
|
|
760
|
+
{
|
|
761
|
+
type: "String",
|
|
762
|
+
label: req.__("Password"),
|
|
763
|
+
fieldview: "password",
|
|
764
|
+
name: "auto_backup_password",
|
|
765
|
+
showIf: {
|
|
766
|
+
auto_backup_frequency: ["Daily", "Weekly"],
|
|
767
|
+
auto_backup_destination: "SFTP server",
|
|
768
|
+
},
|
|
769
|
+
},
|
|
770
|
+
{
|
|
771
|
+
type: "Integer",
|
|
772
|
+
label: req.__("Port"),
|
|
773
|
+
name: "auto_backup_port",
|
|
774
|
+
showIf: {
|
|
775
|
+
auto_backup_frequency: ["Daily", "Weekly"],
|
|
776
|
+
auto_backup_destination: "SFTP server",
|
|
777
|
+
},
|
|
712
778
|
},
|
|
713
779
|
{
|
|
714
780
|
type: "String",
|
|
@@ -720,6 +786,19 @@ const autoBackupForm = (req) =>
|
|
|
720
786
|
//auto_backup_destination: "Local directory",
|
|
721
787
|
},
|
|
722
788
|
},
|
|
789
|
+
{
|
|
790
|
+
type: "String",
|
|
791
|
+
label: req.__("Retain local directory"),
|
|
792
|
+
name: "auto_backup_retain_local_directory",
|
|
793
|
+
sublabel: req.__(
|
|
794
|
+
"Retain a local backup copy in this directory (optional)"
|
|
795
|
+
),
|
|
796
|
+
showIf: {
|
|
797
|
+
auto_backup_frequency: ["Daily", "Weekly"],
|
|
798
|
+
auto_backup_destination: "SFTP server",
|
|
799
|
+
//auto_backup_destination: "Local directory",
|
|
800
|
+
},
|
|
801
|
+
},
|
|
723
802
|
{
|
|
724
803
|
type: "Integer",
|
|
725
804
|
label: req.__("Expiration in days"),
|
|
@@ -732,6 +811,19 @@ const autoBackupForm = (req) =>
|
|
|
732
811
|
auto_backup_destination: "Local directory",
|
|
733
812
|
},
|
|
734
813
|
},
|
|
814
|
+
...(isRoot
|
|
815
|
+
? [
|
|
816
|
+
{
|
|
817
|
+
type: "Bool",
|
|
818
|
+
label: req.__("All tenants"),
|
|
819
|
+
sublabel: req.__("Also backup all tenants"),
|
|
820
|
+
name: "auto_backup_tenants",
|
|
821
|
+
showIf: {
|
|
822
|
+
auto_backup_frequency: ["Daily", "Weekly"],
|
|
823
|
+
},
|
|
824
|
+
},
|
|
825
|
+
]
|
|
826
|
+
: []),
|
|
735
827
|
{
|
|
736
828
|
type: "Bool",
|
|
737
829
|
label: req.__("Include Event Logs"),
|
|
@@ -743,6 +835,7 @@ const autoBackupForm = (req) =>
|
|
|
743
835
|
},
|
|
744
836
|
],
|
|
745
837
|
});
|
|
838
|
+
};
|
|
746
839
|
|
|
747
840
|
/**
|
|
748
841
|
* Snapshot Form
|
|
@@ -1104,6 +1197,28 @@ router.post(
|
|
|
1104
1197
|
})
|
|
1105
1198
|
);
|
|
1106
1199
|
|
|
1200
|
+
const pullCordovaBuilder = (req, res) => {
|
|
1201
|
+
const child = spawn("docker", ["pull", "saltcorn/cordova-builder"], {
|
|
1202
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1203
|
+
});
|
|
1204
|
+
return new Promise((resolve, reject) => {
|
|
1205
|
+
child.stdout.on("data", (data) => {
|
|
1206
|
+
res.write(data);
|
|
1207
|
+
});
|
|
1208
|
+
child.stderr?.on("data", (data) => {
|
|
1209
|
+
res.write(data);
|
|
1210
|
+
});
|
|
1211
|
+
child.on("exit", function (code, signal) {
|
|
1212
|
+
resolve(code);
|
|
1213
|
+
});
|
|
1214
|
+
child.on("error", (msg) => {
|
|
1215
|
+
const message = msg.message ? msg.message : msg.code;
|
|
1216
|
+
res.write(req.__("Error: ") + message + "\n");
|
|
1217
|
+
resolve(msg.code);
|
|
1218
|
+
});
|
|
1219
|
+
});
|
|
1220
|
+
};
|
|
1221
|
+
|
|
1107
1222
|
/**
|
|
1108
1223
|
* Do Upgrade
|
|
1109
1224
|
* @name post/upgrade
|
|
@@ -1132,7 +1247,14 @@ router.post(
|
|
|
1132
1247
|
child.stderr?.on("data", (data) => {
|
|
1133
1248
|
res.write(data);
|
|
1134
1249
|
});
|
|
1135
|
-
child.on("exit", function (code, signal) {
|
|
1250
|
+
child.on("exit", async function (code, signal) {
|
|
1251
|
+
if (code === 0) {
|
|
1252
|
+
res.write(
|
|
1253
|
+
req.__("Pulling the cordova-builder docker image...") + "\n"
|
|
1254
|
+
);
|
|
1255
|
+
const pullCode = await pullCordovaBuilder(req, res);
|
|
1256
|
+
res.write(req.__("Pull done with code %s", pullCode) + "\n");
|
|
1257
|
+
}
|
|
1136
1258
|
res.end(
|
|
1137
1259
|
req.__(
|
|
1138
1260
|
`Upgrade done (if it was available) with code ${code}.\n\nPress the BACK button in your browser, then RELOAD the page.`
|
|
@@ -1481,8 +1603,9 @@ router.get(
|
|
|
1481
1603
|
});
|
|
1482
1604
|
})
|
|
1483
1605
|
);
|
|
1484
|
-
const buildDialogScript = () => {
|
|
1606
|
+
const buildDialogScript = (cordovaBuilderAvailable) => {
|
|
1485
1607
|
return `<script>
|
|
1608
|
+
var cordovaBuilderAvailable = ${cordovaBuilderAvailable};
|
|
1486
1609
|
function showEntrySelect(type) {
|
|
1487
1610
|
for( const currentType of ["view", "page", "pagegroup"]) {
|
|
1488
1611
|
const tab = $('#' + currentType + 'NavLinkID');
|
|
@@ -1505,20 +1628,20 @@ const buildDialogScript = () => {
|
|
|
1505
1628
|
|
|
1506
1629
|
function handleMessages() {
|
|
1507
1630
|
notifyAlert("Building the app, please wait.", true)
|
|
1508
|
-
${
|
|
1509
|
-
getState().getConfig("apple_team_id") &&
|
|
1510
|
-
getState().getConfig("apple_team_id") !== "null"
|
|
1511
|
-
? ""
|
|
1512
|
-
: `
|
|
1513
|
-
if ($("#iOSCheckboxId")[0].checked) {
|
|
1514
|
-
notifyAlert(
|
|
1515
|
-
"No 'Apple Team ID' is configured, I will try to build a project for the iOS simulator."
|
|
1516
|
-
);
|
|
1517
|
-
}`
|
|
1518
|
-
}
|
|
1519
1631
|
}
|
|
1520
1632
|
</script>`;
|
|
1521
1633
|
};
|
|
1634
|
+
|
|
1635
|
+
const imageAvailable = async () => {
|
|
1636
|
+
try {
|
|
1637
|
+
const image = new Docker().getImage("saltcorn/cordova-builder");
|
|
1638
|
+
await image.inspect();
|
|
1639
|
+
return true;
|
|
1640
|
+
} catch (e) {
|
|
1641
|
+
return false;
|
|
1642
|
+
}
|
|
1643
|
+
};
|
|
1644
|
+
|
|
1522
1645
|
/**
|
|
1523
1646
|
* Build mobile app
|
|
1524
1647
|
*/
|
|
@@ -1538,13 +1661,14 @@ router.get(
|
|
|
1538
1661
|
);
|
|
1539
1662
|
const builderSettings =
|
|
1540
1663
|
getState().getConfig("mobile_builder_settings") || {};
|
|
1664
|
+
const dockerAvailable = await imageAvailable();
|
|
1541
1665
|
send_admin_page({
|
|
1542
1666
|
res,
|
|
1543
1667
|
req,
|
|
1544
1668
|
active_sub: "Mobile app",
|
|
1545
1669
|
headers: [
|
|
1546
1670
|
{
|
|
1547
|
-
headerTag: buildDialogScript(),
|
|
1671
|
+
headerTag: buildDialogScript(dockerAvailable),
|
|
1548
1672
|
},
|
|
1549
1673
|
],
|
|
1550
1674
|
contents: {
|
|
@@ -2165,6 +2289,80 @@ router.get(
|
|
|
2165
2289
|
)
|
|
2166
2290
|
)
|
|
2167
2291
|
)
|
|
2292
|
+
),
|
|
2293
|
+
div(
|
|
2294
|
+
{ class: "row pb-3 pt-3" },
|
|
2295
|
+
div(
|
|
2296
|
+
{ class: "col-sm-8" },
|
|
2297
|
+
label(
|
|
2298
|
+
{
|
|
2299
|
+
for: "splashPageInputId",
|
|
2300
|
+
class: "form-label fw-bold",
|
|
2301
|
+
},
|
|
2302
|
+
req.__("Apple Team ID")
|
|
2303
|
+
),
|
|
2304
|
+
input({
|
|
2305
|
+
type: "text",
|
|
2306
|
+
class: "form-control",
|
|
2307
|
+
name: "appleTeamId",
|
|
2308
|
+
id: "appleTeamIdInputId",
|
|
2309
|
+
value:
|
|
2310
|
+
builderSettings.appleTeamId ||
|
|
2311
|
+
getState().getConfig("apple_team_id") ||
|
|
2312
|
+
"",
|
|
2313
|
+
placeholder: req.__("Please enter your Apple Team ID"),
|
|
2314
|
+
})
|
|
2315
|
+
)
|
|
2316
|
+
),
|
|
2317
|
+
div(
|
|
2318
|
+
{ class: "row pb-3 pt-2" },
|
|
2319
|
+
div(
|
|
2320
|
+
label(
|
|
2321
|
+
{ class: "form-label fw-bold" },
|
|
2322
|
+
req.__("Cordova builder") +
|
|
2323
|
+
a(
|
|
2324
|
+
{
|
|
2325
|
+
href: "javascript:ajax_modal('/admin/help/Cordova Builder?')",
|
|
2326
|
+
},
|
|
2327
|
+
i({ class: "fas fa-question-circle ps-1" })
|
|
2328
|
+
)
|
|
2329
|
+
)
|
|
2330
|
+
),
|
|
2331
|
+
div(
|
|
2332
|
+
{ class: "col-sm-4" },
|
|
2333
|
+
div(
|
|
2334
|
+
{
|
|
2335
|
+
id: "dockerBuilderStatusId",
|
|
2336
|
+
class: "",
|
|
2337
|
+
},
|
|
2338
|
+
dockerAvailable
|
|
2339
|
+
? span(
|
|
2340
|
+
req.__("installed"),
|
|
2341
|
+
i({ class: "ps-2 fas fa-check text-success" })
|
|
2342
|
+
)
|
|
2343
|
+
: span(
|
|
2344
|
+
req.__("not available"),
|
|
2345
|
+
i({ class: "ps-2 fas fa-times text-danger" })
|
|
2346
|
+
)
|
|
2347
|
+
)
|
|
2348
|
+
),
|
|
2349
|
+
div(
|
|
2350
|
+
{ class: "col-sm-4" },
|
|
2351
|
+
button(
|
|
2352
|
+
{
|
|
2353
|
+
id: "pullCordovaBtnId",
|
|
2354
|
+
type: "button",
|
|
2355
|
+
onClick: `pull_cordova_builder(this);`,
|
|
2356
|
+
class: "btn btn-warning",
|
|
2357
|
+
},
|
|
2358
|
+
req.__("pull")
|
|
2359
|
+
),
|
|
2360
|
+
span(
|
|
2361
|
+
{ role: "button", onClick: "check_cordova_builder()" },
|
|
2362
|
+
span({ class: "ps-3" }, req.__("refresh")),
|
|
2363
|
+
i({ class: "ps-2 fas fa-undo" })
|
|
2364
|
+
)
|
|
2365
|
+
)
|
|
2168
2366
|
)
|
|
2169
2367
|
),
|
|
2170
2368
|
button(
|
|
@@ -2275,6 +2473,7 @@ router.post(
|
|
|
2275
2473
|
allowOfflineMode,
|
|
2276
2474
|
synchedTables,
|
|
2277
2475
|
includedPlugins,
|
|
2476
|
+
appleTeamId,
|
|
2278
2477
|
} = req.body;
|
|
2279
2478
|
if (!includedPlugins) includedPlugins = [];
|
|
2280
2479
|
if (!synchedTables) synchedTables = [];
|
|
@@ -2317,10 +2516,9 @@ router.post(
|
|
|
2317
2516
|
if (androidPlatform) spawnParams.push("-p", "android");
|
|
2318
2517
|
if (iOSPlatform) {
|
|
2319
2518
|
spawnParams.push("-p", "ios");
|
|
2320
|
-
|
|
2321
|
-
if (!teamId || teamId === "null") {
|
|
2519
|
+
if (!appleTeamId || appleTeamId === "null")
|
|
2322
2520
|
spawnParams.push("--buildForEmulator");
|
|
2323
|
-
|
|
2521
|
+
else spawnParams.push("--appleTeamId", appleTeamId);
|
|
2324
2522
|
}
|
|
2325
2523
|
if (appName) spawnParams.push("--appName", appName);
|
|
2326
2524
|
if (appVersion) spawnParams.push("--appVersion", appVersion);
|
|
@@ -2365,6 +2563,7 @@ router.post(
|
|
|
2365
2563
|
synchedTables: synchedTables,
|
|
2366
2564
|
includedPlugins: includedPlugins,
|
|
2367
2565
|
excludedPlugins,
|
|
2566
|
+
appleTeamId,
|
|
2368
2567
|
});
|
|
2369
2568
|
// end http call, return the out directory name
|
|
2370
2569
|
// the gui polls for results
|
|
@@ -2419,6 +2618,48 @@ router.post(
|
|
|
2419
2618
|
})
|
|
2420
2619
|
);
|
|
2421
2620
|
|
|
2621
|
+
router.post(
|
|
2622
|
+
"/mobile-app/pull-cordova-builder",
|
|
2623
|
+
isAdmin,
|
|
2624
|
+
error_catcher(async (req, res) => {
|
|
2625
|
+
const state = getState();
|
|
2626
|
+
const child = spawn(
|
|
2627
|
+
"docker",
|
|
2628
|
+
["image", "pull", "saltcorn/cordova-builder:latest"],
|
|
2629
|
+
{
|
|
2630
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2631
|
+
cwd: ".",
|
|
2632
|
+
}
|
|
2633
|
+
);
|
|
2634
|
+
child.stdout.on("data", (data) => {
|
|
2635
|
+
state.log(5, data.toString());
|
|
2636
|
+
});
|
|
2637
|
+
child.stderr.on("data", (data) => {
|
|
2638
|
+
state.log(1, data.toString());
|
|
2639
|
+
});
|
|
2640
|
+
child.on("exit", (exitCode, signal) => {
|
|
2641
|
+
state.log(
|
|
2642
|
+
2,
|
|
2643
|
+
`"pull cordova-builder exit with code: ${exitCode} and signal: ${signal}`
|
|
2644
|
+
);
|
|
2645
|
+
});
|
|
2646
|
+
child.on("error", (msg) => {
|
|
2647
|
+
state.log(1, `pull cordova-builder error: ${msg}`);
|
|
2648
|
+
});
|
|
2649
|
+
|
|
2650
|
+
res.json({});
|
|
2651
|
+
})
|
|
2652
|
+
);
|
|
2653
|
+
|
|
2654
|
+
router.get(
|
|
2655
|
+
"/mobile-app/check-cordova-builder",
|
|
2656
|
+
isAdmin,
|
|
2657
|
+
error_catcher(async (req, res) => {
|
|
2658
|
+
const installed = await imageAvailable();
|
|
2659
|
+
res.json({ installed });
|
|
2660
|
+
})
|
|
2661
|
+
);
|
|
2662
|
+
|
|
2422
2663
|
/**
|
|
2423
2664
|
* Do Clear All
|
|
2424
2665
|
* @function
|
|
@@ -2628,6 +2869,7 @@ admin_config_route({
|
|
|
2628
2869
|
});
|
|
2629
2870
|
},
|
|
2630
2871
|
response(form, req, res) {
|
|
2872
|
+
const code_pages = getState().getConfig("function_code_pages", {});
|
|
2631
2873
|
send_admin_page({
|
|
2632
2874
|
res,
|
|
2633
2875
|
req,
|
|
@@ -2654,11 +2896,138 @@ admin_config_route({
|
|
|
2654
2896
|
),
|
|
2655
2897
|
],
|
|
2656
2898
|
},
|
|
2899
|
+
{
|
|
2900
|
+
type: "card",
|
|
2901
|
+
title: req.__("Constants and function code"),
|
|
2902
|
+
contents: [
|
|
2903
|
+
div(
|
|
2904
|
+
Object.keys(code_pages)
|
|
2905
|
+
.map((k) =>
|
|
2906
|
+
a(
|
|
2907
|
+
{
|
|
2908
|
+
href: `/admin/edit-codepage/${encodeURIComponent(k)}`,
|
|
2909
|
+
class: "",
|
|
2910
|
+
},
|
|
2911
|
+
k
|
|
2912
|
+
)
|
|
2913
|
+
)
|
|
2914
|
+
.join(" | "),
|
|
2915
|
+
button(
|
|
2916
|
+
{
|
|
2917
|
+
class: "btn btn-secondary btn-sm d-block mt-2",
|
|
2918
|
+
onclick: `location.href='/admin/edit-codepage/'+prompt('Name of the new page')`,
|
|
2919
|
+
},
|
|
2920
|
+
i({ class: "fas fa-plus me-1" }),
|
|
2921
|
+
"Add page"
|
|
2922
|
+
)
|
|
2923
|
+
),
|
|
2924
|
+
],
|
|
2925
|
+
},
|
|
2657
2926
|
],
|
|
2658
2927
|
},
|
|
2659
2928
|
});
|
|
2660
2929
|
},
|
|
2661
2930
|
});
|
|
2931
|
+
|
|
2932
|
+
router.get(
|
|
2933
|
+
"/edit-codepage/:name",
|
|
2934
|
+
isAdmin,
|
|
2935
|
+
error_catcher(async (req, res) => {
|
|
2936
|
+
const { name } = req.params;
|
|
2937
|
+
const code_pages = getState().getConfig("function_code_pages", {});
|
|
2938
|
+
const existing = code_pages[name] || "";
|
|
2939
|
+
const form = new Form({
|
|
2940
|
+
action: `/admin/edit-codepage/${encodeURIComponent(name)}`,
|
|
2941
|
+
onChange: "saveAndContinue(this)",
|
|
2942
|
+
values: { code: existing },
|
|
2943
|
+
noSubmitButton: true,
|
|
2944
|
+
labelCols: 0,
|
|
2945
|
+
additionalButtons: [
|
|
2946
|
+
{
|
|
2947
|
+
label: req.__("Delete code page"),
|
|
2948
|
+
class: "btn btn-outline-danger btn-sm",
|
|
2949
|
+
onclick: `if(confirm('Are you sure you would like to delete this code page?'))ajax_post('/admin/delete-codepage/${encodeURIComponent(
|
|
2950
|
+
name
|
|
2951
|
+
)}')`,
|
|
2952
|
+
},
|
|
2953
|
+
],
|
|
2954
|
+
fields: [
|
|
2955
|
+
{
|
|
2956
|
+
name: "code",
|
|
2957
|
+
form_name: "code",
|
|
2958
|
+
label: "Code",
|
|
2959
|
+
sublabel:
|
|
2960
|
+
"Only functions declared as <code>function name(...) {...}</code> or <code>async function name(...) {...}</code> will be available in formulae and code actions. Declare a constant <code>k</code> as <code>globalThis.k = ...</code> In scope: " +
|
|
2961
|
+
a(
|
|
2962
|
+
{
|
|
2963
|
+
href: "https://saltcorn.github.io/saltcorn/classes/_saltcorn_data.models.Table-1.html",
|
|
2964
|
+
target: "_blank",
|
|
2965
|
+
},
|
|
2966
|
+
"Table"
|
|
2967
|
+
),
|
|
2968
|
+
input_type: "code",
|
|
2969
|
+
attributes: { mode: "text/javascript" },
|
|
2970
|
+
class: "validate-statements",
|
|
2971
|
+
validator(s) {
|
|
2972
|
+
try {
|
|
2973
|
+
let AsyncFunction = Object.getPrototypeOf(
|
|
2974
|
+
async function () {}
|
|
2975
|
+
).constructor;
|
|
2976
|
+
AsyncFunction(s);
|
|
2977
|
+
return true;
|
|
2978
|
+
} catch (e) {
|
|
2979
|
+
return e.message;
|
|
2980
|
+
}
|
|
2981
|
+
},
|
|
2982
|
+
},
|
|
2983
|
+
],
|
|
2984
|
+
});
|
|
2985
|
+
|
|
2986
|
+
send_admin_page({
|
|
2987
|
+
res,
|
|
2988
|
+
req,
|
|
2989
|
+
active_sub: "Development",
|
|
2990
|
+
sub2_page: req.__(`%s code page`, name),
|
|
2991
|
+
contents: {
|
|
2992
|
+
type: "card",
|
|
2993
|
+
title: req.__(`%s code page`, name),
|
|
2994
|
+
contents: [renderForm(form, req.csrfToken())],
|
|
2995
|
+
},
|
|
2996
|
+
});
|
|
2997
|
+
})
|
|
2998
|
+
);
|
|
2999
|
+
|
|
3000
|
+
router.post(
|
|
3001
|
+
"/edit-codepage/:name",
|
|
3002
|
+
isAdmin,
|
|
3003
|
+
error_catcher(async (req, res) => {
|
|
3004
|
+
const { name } = req.params;
|
|
3005
|
+
const code_pages = getState().getConfigCopy("function_code_pages", {});
|
|
3006
|
+
|
|
3007
|
+
const code = req.body.code;
|
|
3008
|
+
await getState().setConfig("function_code_pages", {
|
|
3009
|
+
...code_pages,
|
|
3010
|
+
[name]: code,
|
|
3011
|
+
});
|
|
3012
|
+
await getState().refresh_codepages();
|
|
3013
|
+
|
|
3014
|
+
res.json({ success: true });
|
|
3015
|
+
})
|
|
3016
|
+
);
|
|
3017
|
+
router.post(
|
|
3018
|
+
"/delete-codepage/:name",
|
|
3019
|
+
isAdmin,
|
|
3020
|
+
error_catcher(async (req, res) => {
|
|
3021
|
+
const { name } = req.params;
|
|
3022
|
+
const code_pages = getState().getConfigCopy("function_code_pages", {});
|
|
3023
|
+
delete code_pages[name];
|
|
3024
|
+
await getState().setConfig("function_code_pages", code_pages);
|
|
3025
|
+
await getState().refresh_codepages();
|
|
3026
|
+
|
|
3027
|
+
res.json({ goto: `/admin/dev` });
|
|
3028
|
+
})
|
|
3029
|
+
);
|
|
3030
|
+
|
|
2662
3031
|
/**
|
|
2663
3032
|
* Notifications
|
|
2664
3033
|
*/
|
package/routes/api.js
CHANGED
|
@@ -373,7 +373,10 @@ router.all(
|
|
|
373
373
|
res.redirect(resp.goto);
|
|
374
374
|
else if (req.headers?.scgotourl)
|
|
375
375
|
res.redirect(req.headers?.scgotourl);
|
|
376
|
-
else
|
|
376
|
+
else {
|
|
377
|
+
if (trigger.configuration?._raw_output) res.json({ resp });
|
|
378
|
+
else res.json({ success: true, data: resp });
|
|
379
|
+
}
|
|
377
380
|
} catch (e) {
|
|
378
381
|
Crash.create(e, req);
|
|
379
382
|
res.status(400).json({ success: false, error: e.message });
|
package/routes/common_lists.js
CHANGED
package/routes/fields.js
CHANGED
|
@@ -955,8 +955,11 @@ router.post(
|
|
|
955
955
|
}
|
|
956
956
|
return;
|
|
957
957
|
}
|
|
958
|
+
}
|
|
959
|
+
if (targetField.type === "File") {
|
|
960
|
+
fv = getState().fileviews[fieldview];
|
|
958
961
|
} else {
|
|
959
|
-
fv = targetField.type
|
|
962
|
+
fv = targetField.type?.fieldviews?.[fieldview];
|
|
960
963
|
if (!fv)
|
|
961
964
|
fv =
|
|
962
965
|
targetField.type.fieldviews.show ||
|
|
@@ -994,13 +997,15 @@ router.post(
|
|
|
994
997
|
if (oldRow) {
|
|
995
998
|
const value = oldRow[kpath[kpath.length - 1]];
|
|
996
999
|
//TODO run fieldview
|
|
997
|
-
res.send(
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1000
|
+
if (value === null || typeof value === "undefined") res.send("");
|
|
1001
|
+
else
|
|
1002
|
+
res.send(
|
|
1003
|
+
typeof value === "string"
|
|
1004
|
+
? value
|
|
1005
|
+
: value?.toString
|
|
1006
|
+
? value.toString()
|
|
1007
|
+
: `${value}`
|
|
1008
|
+
);
|
|
1004
1009
|
return;
|
|
1005
1010
|
}
|
|
1006
1011
|
}
|
package/routes/homepage.js
CHANGED
|
@@ -547,7 +547,7 @@ const no_views_logged_in = async (req, res) => {
|
|
|
547
547
|
* @returns {Promise<boolean>}
|
|
548
548
|
*/
|
|
549
549
|
const get_config_response = async (role_id, res, req) => {
|
|
550
|
-
const wrap = async (contents, homeCfg, title, description) => {
|
|
550
|
+
const wrap = async (contents, homeCfg, title, description, no_menu) => {
|
|
551
551
|
if (contents.html_file) await sendHtmlFile(req, res, contents.html_file);
|
|
552
552
|
else
|
|
553
553
|
res.sendWrap(
|
|
@@ -555,6 +555,7 @@ const get_config_response = async (role_id, res, req) => {
|
|
|
555
555
|
title: title || "",
|
|
556
556
|
description: description || "",
|
|
557
557
|
bodyClass: "page_" + db.sqlsanitize(homeCfg),
|
|
558
|
+
no_menu,
|
|
558
559
|
},
|
|
559
560
|
contents
|
|
560
561
|
);
|
|
@@ -574,7 +575,8 @@ const get_config_response = async (role_id, res, req) => {
|
|
|
574
575
|
await db_page.run(req.query, { res, req }),
|
|
575
576
|
homeCfg,
|
|
576
577
|
db_page.title,
|
|
577
|
-
db_page.description
|
|
578
|
+
db_page.description,
|
|
579
|
+
db_page.attributes?.no_menu
|
|
578
580
|
);
|
|
579
581
|
else {
|
|
580
582
|
const group = PageGroup.findOne({ name: homeCfg });
|
|
@@ -587,7 +589,8 @@ const get_config_response = async (role_id, res, req) => {
|
|
|
587
589
|
await eligible.run(req.query, { res, req }),
|
|
588
590
|
homeCfg,
|
|
589
591
|
eligible.title,
|
|
590
|
-
eligible.description
|
|
592
|
+
eligible.description,
|
|
593
|
+
eligible.attributes?.no_menu
|
|
591
594
|
);
|
|
592
595
|
} else wrap(req.__("%s has no eligible page", group.name), homeCfg);
|
|
593
596
|
} else res.redirect(homeCfg);
|
package/routes/menu.js
CHANGED
|
@@ -19,7 +19,7 @@ const { save_menu_items } = require("@saltcorn/data/models/config");
|
|
|
19
19
|
const db = require("@saltcorn/data/db");
|
|
20
20
|
|
|
21
21
|
const { renderForm } = require("@saltcorn/markup");
|
|
22
|
-
const { script, domReady, div, ul } = require("@saltcorn/markup/tags");
|
|
22
|
+
const { script, domReady, div, ul, i } = require("@saltcorn/markup/tags");
|
|
23
23
|
const { send_infoarch_page } = require("../markup/admin.js");
|
|
24
24
|
const Table = require("@saltcorn/data/models/table");
|
|
25
25
|
const Trigger = require("@saltcorn/data/models/trigger");
|
|
@@ -368,7 +368,7 @@ const menuEditorScript = (menu_items) => `
|
|
|
368
368
|
iconPicker: iconPickerOptions,
|
|
369
369
|
getLabelText: (item) => item?.text || item?.type,
|
|
370
370
|
labelEdit: 'Edit <i class="fas fa-edit clickable"></i>',
|
|
371
|
-
maxLevel:
|
|
371
|
+
maxLevel: 2 // (Optional) Default is -1 (no level limit)
|
|
372
372
|
// Valid levels are from [0, 1, 2, 3,...N]
|
|
373
373
|
});
|
|
374
374
|
editor.setForm($('#menuForm'));
|
|
@@ -446,7 +446,16 @@ router.get(
|
|
|
446
446
|
above: [
|
|
447
447
|
{
|
|
448
448
|
besides: [
|
|
449
|
-
div(
|
|
449
|
+
div(
|
|
450
|
+
ul({ id: "myEditor", class: "sortableLists list-group" }),
|
|
451
|
+
div(
|
|
452
|
+
i(
|
|
453
|
+
req.__(
|
|
454
|
+
"Some themes support only one level of menu nesting."
|
|
455
|
+
)
|
|
456
|
+
)
|
|
457
|
+
)
|
|
458
|
+
),
|
|
450
459
|
div(
|
|
451
460
|
renderForm(form, req.csrfToken()),
|
|
452
461
|
script(domReady(menuEditorScript(menu_items)))
|