@saltcorn/server 0.9.6-beta.9 → 0.9.7-rc.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/app.js +9 -2
- package/auth/admin.js +51 -52
- package/auth/roleadmin.js +6 -2
- package/auth/routes.js +28 -10
- package/auth/testhelp.js +86 -0
- package/help/Field label.tmd +11 -0
- package/help/Field types.tmd +39 -0
- package/help/Inclusion Formula.tmd +38 -0
- package/help/Ownership field.tmd +76 -0
- package/help/Ownership formula.tmd +75 -0
- package/help/Table roles.tmd +20 -0
- package/help/User groups.tmd +35 -0
- package/help/User roles.tmd +30 -0
- package/load_plugins.js +28 -4
- package/locales/en.json +28 -1
- package/locales/it.json +3 -2
- package/markup/forms.js +5 -1
- package/package.json +9 -9
- package/public/log_viewer_utils.js +32 -0
- package/public/mermaid.min.js +705 -306
- package/public/saltcorn-builder.css +23 -0
- package/public/saltcorn-common.js +195 -71
- package/public/saltcorn.css +72 -0
- package/public/saltcorn.js +78 -0
- package/restart_watcher.js +1 -0
- package/routes/actions.js +27 -0
- package/routes/admin.js +180 -66
- package/routes/api.js +6 -0
- package/routes/common_lists.js +42 -32
- package/routes/fields.js +9 -1
- package/routes/homepage.js +2 -0
- package/routes/menu.js +69 -4
- package/routes/notifications.js +90 -10
- package/routes/pageedit.js +18 -13
- package/routes/plugins.js +5 -1
- package/routes/search.js +10 -4
- package/routes/tables.js +47 -27
- package/routes/tenant.js +4 -15
- package/routes/utils.js +20 -6
- package/routes/viewedit.js +11 -7
- package/serve.js +27 -5
- package/tests/edit.test.js +426 -0
- package/tests/fields.test.js +21 -0
- package/tests/filter.test.js +68 -0
- package/tests/page.test.js +2 -2
- package/tests/sync.test.js +59 -0
- package/wrapper.js +4 -1
package/routes/admin.js
CHANGED
|
@@ -108,6 +108,7 @@ const stream = require("stream");
|
|
|
108
108
|
const Crash = require("@saltcorn/data/models/crash");
|
|
109
109
|
const { get_help_markup } = require("../help/index.js");
|
|
110
110
|
const Docker = require("dockerode");
|
|
111
|
+
const npmFetch = require("npm-registry-fetch");
|
|
111
112
|
|
|
112
113
|
const router = new Router();
|
|
113
114
|
module.exports = router;
|
|
@@ -1004,6 +1005,7 @@ router.get(
|
|
|
1004
1005
|
"custom_ssl_certificate",
|
|
1005
1006
|
false
|
|
1006
1007
|
);
|
|
1008
|
+
const rndid = `bs${Math.round(Math.random() * 100000)}`;
|
|
1007
1009
|
let expiry = "";
|
|
1008
1010
|
if (custom_ssl_certificate && X509Certificate) {
|
|
1009
1011
|
const { validTo } = new X509Certificate(custom_ssl_certificate);
|
|
@@ -1062,7 +1064,8 @@ router.get(
|
|
|
1062
1064
|
" ",
|
|
1063
1065
|
req.__("Clear all"),
|
|
1064
1066
|
" »"
|
|
1065
|
-
)
|
|
1067
|
+
),
|
|
1068
|
+
hr()
|
|
1066
1069
|
),
|
|
1067
1070
|
},
|
|
1068
1071
|
{
|
|
@@ -1075,32 +1078,45 @@ router.get(
|
|
|
1075
1078
|
tr(
|
|
1076
1079
|
th(req.__("Saltcorn version")),
|
|
1077
1080
|
td(
|
|
1078
|
-
packagejson.version
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1081
|
+
packagejson.version,
|
|
1082
|
+
isRoot && can_update
|
|
1083
|
+
? post_btn(
|
|
1084
|
+
"/admin/upgrade",
|
|
1085
|
+
req.__("Upgrade"),
|
|
1086
|
+
req.csrfToken(),
|
|
1087
|
+
{
|
|
1088
|
+
btnClass: "btn-primary btn-sm",
|
|
1089
|
+
formClass: "d-inline",
|
|
1090
|
+
}
|
|
1091
|
+
)
|
|
1092
|
+
: isRoot && is_latest
|
|
1093
|
+
? span(
|
|
1094
|
+
{ class: "badge bg-primary ms-2" },
|
|
1095
|
+
req.__("Latest")
|
|
1096
|
+
) +
|
|
1097
|
+
post_btn(
|
|
1098
|
+
"/admin/check-for-upgrade",
|
|
1099
|
+
req.__("Check updates"),
|
|
1100
|
+
req.csrfToken(),
|
|
1101
|
+
{
|
|
1102
|
+
btnClass: "btn-primary btn-sm px-1 py-0",
|
|
1103
|
+
formClass: "d-inline",
|
|
1104
|
+
}
|
|
1105
|
+
)
|
|
1106
|
+
: "",
|
|
1107
|
+
!git_commit &&
|
|
1108
|
+
a(
|
|
1109
|
+
{
|
|
1110
|
+
id: rndid,
|
|
1111
|
+
class: "btn btn-sm btn-secondary ms-1 px-1 py-0",
|
|
1112
|
+
onClick: "press_store_button(this, true)",
|
|
1113
|
+
href:
|
|
1114
|
+
`javascript:ajax_modal('/admin/install_dialog', ` +
|
|
1115
|
+
`{ onOpen: () => { restore_old_button('${rndid}'); }, ` +
|
|
1116
|
+
` onError: (res) => { selectVersionError(res, '${rndid}') } });`,
|
|
1117
|
+
},
|
|
1118
|
+
req.__("Choose version")
|
|
1119
|
+
)
|
|
1104
1120
|
)
|
|
1105
1121
|
),
|
|
1106
1122
|
git_commit &&
|
|
@@ -1222,6 +1238,137 @@ const pullCordovaBuilder = (req, res) => {
|
|
|
1222
1238
|
});
|
|
1223
1239
|
};
|
|
1224
1240
|
|
|
1241
|
+
/*
|
|
1242
|
+
* fetch available saltcorn versions and show a dialog to select one
|
|
1243
|
+
*/
|
|
1244
|
+
router.get(
|
|
1245
|
+
"/install_dialog",
|
|
1246
|
+
isAdmin,
|
|
1247
|
+
error_catcher(async (req, res) => {
|
|
1248
|
+
try {
|
|
1249
|
+
const pkgInfo = await npmFetch.json(
|
|
1250
|
+
"https://registry.npmjs.org/@saltcorn/cli"
|
|
1251
|
+
);
|
|
1252
|
+
if (!pkgInfo?.versions)
|
|
1253
|
+
throw new Error(req.__("Unable to fetch versions"));
|
|
1254
|
+
const versions = Object.keys(pkgInfo.versions);
|
|
1255
|
+
if (versions.length === 0) throw new Error(req.__("No versions found"));
|
|
1256
|
+
res.set("Page-Title", req.__("%s versions", "Saltcorn"));
|
|
1257
|
+
let selected = packagejson.version;
|
|
1258
|
+
res.send(
|
|
1259
|
+
form(
|
|
1260
|
+
{
|
|
1261
|
+
action: `/admin/install`,
|
|
1262
|
+
method: "post",
|
|
1263
|
+
},
|
|
1264
|
+
input({ type: "hidden", name: "_csrf", value: req.csrfToken() }),
|
|
1265
|
+
div(
|
|
1266
|
+
{ class: "form-group" },
|
|
1267
|
+
label(
|
|
1268
|
+
{
|
|
1269
|
+
for: "version_select",
|
|
1270
|
+
class: "form-label fw-bold",
|
|
1271
|
+
},
|
|
1272
|
+
req.__("Version")
|
|
1273
|
+
),
|
|
1274
|
+
select(
|
|
1275
|
+
{
|
|
1276
|
+
id: "version_select",
|
|
1277
|
+
class: "form-control form-select",
|
|
1278
|
+
name: "version",
|
|
1279
|
+
},
|
|
1280
|
+
versions.map((version) =>
|
|
1281
|
+
option({
|
|
1282
|
+
id: `${version}_opt`,
|
|
1283
|
+
value: version,
|
|
1284
|
+
label: version,
|
|
1285
|
+
selected: version === selected,
|
|
1286
|
+
})
|
|
1287
|
+
)
|
|
1288
|
+
)
|
|
1289
|
+
),
|
|
1290
|
+
div(
|
|
1291
|
+
{ class: "d-flex justify-content-end" },
|
|
1292
|
+
button(
|
|
1293
|
+
{
|
|
1294
|
+
type: "button",
|
|
1295
|
+
class: "btn btn-secondary me-2",
|
|
1296
|
+
"data-bs-dismiss": "modal",
|
|
1297
|
+
},
|
|
1298
|
+
req.__("Close")
|
|
1299
|
+
),
|
|
1300
|
+
button(
|
|
1301
|
+
{
|
|
1302
|
+
type: "submit",
|
|
1303
|
+
class: "btn btn-primary",
|
|
1304
|
+
onClick: "press_store_button(this)",
|
|
1305
|
+
},
|
|
1306
|
+
req.__("Install")
|
|
1307
|
+
)
|
|
1308
|
+
)
|
|
1309
|
+
)
|
|
1310
|
+
);
|
|
1311
|
+
} catch (error) {
|
|
1312
|
+
getState().log(
|
|
1313
|
+
2,
|
|
1314
|
+
`GET /install_dialog: ${error.message || "unknown error"}`
|
|
1315
|
+
);
|
|
1316
|
+
return res.status(500).json({ error: error.message || "unknown error" });
|
|
1317
|
+
}
|
|
1318
|
+
})
|
|
1319
|
+
);
|
|
1320
|
+
|
|
1321
|
+
const doInstall = async (req, res, version, runPull) => {
|
|
1322
|
+
if (db.getTenantSchema() !== db.connectObj.default_schema) {
|
|
1323
|
+
req.flash("error", req.__("Not possible for tenant"));
|
|
1324
|
+
res.redirect("/admin");
|
|
1325
|
+
} else {
|
|
1326
|
+
res.write(
|
|
1327
|
+
version === "latest"
|
|
1328
|
+
? req.__("Starting upgrade, please wait...\n")
|
|
1329
|
+
: req.__("Installing %s, please wait...\n", version)
|
|
1330
|
+
);
|
|
1331
|
+
const child = spawn(
|
|
1332
|
+
"npm",
|
|
1333
|
+
["install", "-g", `@saltcorn/cli@${version}`, "--unsafe"],
|
|
1334
|
+
{
|
|
1335
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1336
|
+
}
|
|
1337
|
+
);
|
|
1338
|
+
child.stdout.on("data", (data) => {
|
|
1339
|
+
res.write(data);
|
|
1340
|
+
});
|
|
1341
|
+
child.stderr?.on("data", (data) => {
|
|
1342
|
+
res.write(data);
|
|
1343
|
+
});
|
|
1344
|
+
child.on("exit", async function (code, signal) {
|
|
1345
|
+
if (code === 0 && runPull) {
|
|
1346
|
+
res.write(req.__("Pulling the cordova-builder docker image...") + "\n");
|
|
1347
|
+
const pullCode = await pullCordovaBuilder(req, res);
|
|
1348
|
+
res.write(req.__("Pull done with code %s", pullCode) + "\n");
|
|
1349
|
+
}
|
|
1350
|
+
res.end(
|
|
1351
|
+
version === "latest"
|
|
1352
|
+
? req.__(
|
|
1353
|
+
`Upgrade done (if it was available) with code ${code}.\n\nPress the BACK button in your browser, then RELOAD the page.`
|
|
1354
|
+
)
|
|
1355
|
+
: req.__(
|
|
1356
|
+
`Install done with code ${code}.\n\nPress the BACK button in your browser, then RELOAD the page.`
|
|
1357
|
+
)
|
|
1358
|
+
);
|
|
1359
|
+
setTimeout(() => {
|
|
1360
|
+
getState().processSend("RestartServer");
|
|
1361
|
+
process.exit(0);
|
|
1362
|
+
}, 100);
|
|
1363
|
+
});
|
|
1364
|
+
}
|
|
1365
|
+
};
|
|
1366
|
+
|
|
1367
|
+
router.post("/install", isAdmin, async (req, res) => {
|
|
1368
|
+
const { version } = req.body;
|
|
1369
|
+
await doInstall(req, res, version, false);
|
|
1370
|
+
});
|
|
1371
|
+
|
|
1225
1372
|
/**
|
|
1226
1373
|
* Do Upgrade
|
|
1227
1374
|
* @name post/upgrade
|
|
@@ -1232,43 +1379,7 @@ router.post(
|
|
|
1232
1379
|
"/upgrade",
|
|
1233
1380
|
isAdmin,
|
|
1234
1381
|
error_catcher(async (req, res) => {
|
|
1235
|
-
|
|
1236
|
-
req.flash("error", req.__("Not possible for tenant"));
|
|
1237
|
-
res.redirect("/admin");
|
|
1238
|
-
} else {
|
|
1239
|
-
res.write(req.__("Starting upgrade, please wait...\n"));
|
|
1240
|
-
const child = spawn(
|
|
1241
|
-
"npm",
|
|
1242
|
-
["install", "-g", "@saltcorn/cli@latest", "--unsafe"],
|
|
1243
|
-
{
|
|
1244
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
1245
|
-
}
|
|
1246
|
-
);
|
|
1247
|
-
child.stdout.on("data", (data) => {
|
|
1248
|
-
res.write(data);
|
|
1249
|
-
});
|
|
1250
|
-
child.stderr?.on("data", (data) => {
|
|
1251
|
-
res.write(data);
|
|
1252
|
-
});
|
|
1253
|
-
child.on("exit", async function (code, signal) {
|
|
1254
|
-
if (code === 0) {
|
|
1255
|
-
res.write(
|
|
1256
|
-
req.__("Pulling the cordova-builder docker image...") + "\n"
|
|
1257
|
-
);
|
|
1258
|
-
const pullCode = await pullCordovaBuilder(req, res);
|
|
1259
|
-
res.write(req.__("Pull done with code %s", pullCode) + "\n");
|
|
1260
|
-
}
|
|
1261
|
-
res.end(
|
|
1262
|
-
req.__(
|
|
1263
|
-
`Upgrade done (if it was available) with code ${code}.\n\nPress the BACK button in your browser, then RELOAD the page.`
|
|
1264
|
-
)
|
|
1265
|
-
);
|
|
1266
|
-
setTimeout(() => {
|
|
1267
|
-
getState().processSend("RestartServer");
|
|
1268
|
-
process.exit(0);
|
|
1269
|
-
}, 100);
|
|
1270
|
-
});
|
|
1271
|
-
}
|
|
1382
|
+
await doInstall(req, res, "latest", true);
|
|
1272
1383
|
})
|
|
1273
1384
|
);
|
|
1274
1385
|
/**
|
|
@@ -1740,8 +1851,11 @@ router.get(
|
|
|
1740
1851
|
div({ class: "col-sm-4 fw-bold" }, req.__("Platform")),
|
|
1741
1852
|
div(
|
|
1742
1853
|
{
|
|
1743
|
-
class:
|
|
1744
|
-
|
|
1854
|
+
class: `col-sm-1 fw-bold d-flex justify-content-center ${
|
|
1855
|
+
builderSettings.androidPlatform !== "on"
|
|
1856
|
+
? "d-none"
|
|
1857
|
+
: ""
|
|
1858
|
+
}`,
|
|
1745
1859
|
id: "dockerLabelId",
|
|
1746
1860
|
},
|
|
1747
1861
|
req.__("docker")
|
package/routes/api.js
CHANGED
|
@@ -260,6 +260,12 @@ router.get(
|
|
|
260
260
|
);
|
|
261
261
|
if (!table) {
|
|
262
262
|
getState().log(3, `API get ${tableName} table not found`);
|
|
263
|
+
getState().log(
|
|
264
|
+
6,
|
|
265
|
+
`API get failure additonal info: URL=${req.originalUrl}${
|
|
266
|
+
getState().getConfig("log_ip_address", false) ? ` IP=${req.ip}` : ""
|
|
267
|
+
}`
|
|
268
|
+
);
|
|
263
269
|
res.status(404).json({ error: req.__("Not found") });
|
|
264
270
|
return;
|
|
265
271
|
}
|
package/routes/common_lists.js
CHANGED
|
@@ -304,6 +304,18 @@ const viewsList = async (
|
|
|
304
304
|
? `set_state_field('_sortby', 'name', this)`
|
|
305
305
|
: undefined,
|
|
306
306
|
},
|
|
307
|
+
{
|
|
308
|
+
label: "",
|
|
309
|
+
key: (r) =>
|
|
310
|
+
r.id && r.viewtemplateObj?.configuration_workflow
|
|
311
|
+
? link(
|
|
312
|
+
`/viewedit/config/${encodeURIComponent(
|
|
313
|
+
r.name
|
|
314
|
+
)}${on_done_redirect_str}`,
|
|
315
|
+
req.__("Configure")
|
|
316
|
+
)
|
|
317
|
+
: "",
|
|
318
|
+
},
|
|
307
319
|
...(tagId
|
|
308
320
|
? []
|
|
309
321
|
: [
|
|
@@ -340,18 +352,6 @@ const viewsList = async (
|
|
|
340
352
|
? editViewRoleForm(row, roles, req, on_done_redirect_str)
|
|
341
353
|
: "admin",
|
|
342
354
|
},
|
|
343
|
-
{
|
|
344
|
-
label: "",
|
|
345
|
-
key: (r) =>
|
|
346
|
-
r.id && r.viewtemplateObj?.configuration_workflow
|
|
347
|
-
? link(
|
|
348
|
-
`/viewedit/config/${encodeURIComponent(
|
|
349
|
-
r.name
|
|
350
|
-
)}${on_done_redirect_str}`,
|
|
351
|
-
req.__("Configure")
|
|
352
|
-
)
|
|
353
|
-
: "",
|
|
354
|
-
},
|
|
355
355
|
!tagId
|
|
356
356
|
? {
|
|
357
357
|
label: "",
|
|
@@ -428,13 +428,6 @@ const page_dropdown = (page, req) =>
|
|
|
428
428
|
},
|
|
429
429
|
'<i class="fas fa-running"></i> ' + req.__("Run")
|
|
430
430
|
),
|
|
431
|
-
a(
|
|
432
|
-
{
|
|
433
|
-
class: "dropdown-item",
|
|
434
|
-
href: `/pageedit/edit-properties/${encodeURIComponent(page.name)}`,
|
|
435
|
-
},
|
|
436
|
-
'<i class="fas fa-edit"></i> ' + req.__("Edit properties")
|
|
437
|
-
),
|
|
438
431
|
post_dropdown_item(
|
|
439
432
|
`/pageedit/add-to-menu/${page.id}`,
|
|
440
433
|
'<i class="fas fa-bars"></i> ' + req.__("Add to menu"),
|
|
@@ -507,6 +500,22 @@ const getPageList = async (
|
|
|
507
500
|
label: req.__("Name"),
|
|
508
501
|
key: (r) => link(`/page/${encodeURIComponent(r.name)}`, r.name),
|
|
509
502
|
},
|
|
503
|
+
{
|
|
504
|
+
label: "",
|
|
505
|
+
key: (r) =>
|
|
506
|
+
link(
|
|
507
|
+
`/pageedit/edit/${encodeURIComponent(r.name)}`,
|
|
508
|
+
req.__("Configure")
|
|
509
|
+
),
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
label: "",
|
|
513
|
+
key: (r) =>
|
|
514
|
+
link(
|
|
515
|
+
`/pageedit/edit-properties/${encodeURIComponent(r.name)}`,
|
|
516
|
+
req.__("Edit")
|
|
517
|
+
),
|
|
518
|
+
},
|
|
510
519
|
...(tagId
|
|
511
520
|
? []
|
|
512
521
|
: [
|
|
@@ -522,11 +531,7 @@ const getPageList = async (
|
|
|
522
531
|
label: req.__("Role to access"),
|
|
523
532
|
key: (row) => editPageRoleForm(row, roles, req),
|
|
524
533
|
},
|
|
525
|
-
|
|
526
|
-
label: req.__("Edit"),
|
|
527
|
-
key: (r) =>
|
|
528
|
-
link(`/pageedit/edit/${encodeURIComponent(r.name)}`, req.__("Edit")),
|
|
529
|
-
},
|
|
534
|
+
|
|
530
535
|
!tagId
|
|
531
536
|
? {
|
|
532
537
|
label: "",
|
|
@@ -600,6 +605,11 @@ const trigger_dropdown = (trigger, req, on_done_redirect_str = "") =>
|
|
|
600
605
|
},
|
|
601
606
|
'<i class="fas fa-undo-alt"></i> ' + req.__("Restore")
|
|
602
607
|
),
|
|
608
|
+
post_dropdown_item(
|
|
609
|
+
`/actions/clone/${trigger.id}`,
|
|
610
|
+
'<i class="far fa-copy"></i> ' + req.__("Duplicate"),
|
|
611
|
+
req
|
|
612
|
+
),
|
|
603
613
|
div({ class: "dropdown-divider" }),
|
|
604
614
|
|
|
605
615
|
post_dropdown_item(
|
|
@@ -634,6 +644,14 @@ const getTriggerList = async (
|
|
|
634
644
|
return mkTable(
|
|
635
645
|
[
|
|
636
646
|
{ label: req.__("Name"), key: "name" },
|
|
647
|
+
{
|
|
648
|
+
label: req.__("Test run"),
|
|
649
|
+
key: (r) => link(`/actions/testrun/${r.id}`, req.__("Test run")),
|
|
650
|
+
},
|
|
651
|
+
{
|
|
652
|
+
label: req.__("Configure"),
|
|
653
|
+
key: (r) => link(`/actions/configure/${r.id}`, req.__("Configure")),
|
|
654
|
+
},
|
|
637
655
|
...(tagId
|
|
638
656
|
? []
|
|
639
657
|
: [
|
|
@@ -667,14 +685,6 @@ const getTriggerList = async (
|
|
|
667
685
|
? a({ href: `/table/${r.table_name}` }, r.table_name)
|
|
668
686
|
: r.channel,
|
|
669
687
|
},
|
|
670
|
-
{
|
|
671
|
-
label: req.__("Test run"),
|
|
672
|
-
key: (r) => link(`/actions/testrun/${r.id}`, req.__("Test run")),
|
|
673
|
-
},
|
|
674
|
-
{
|
|
675
|
-
label: req.__("Configure"),
|
|
676
|
-
key: (r) => link(`/actions/configure/${r.id}`, req.__("Configure")),
|
|
677
|
-
},
|
|
678
688
|
!tagId
|
|
679
689
|
? {
|
|
680
690
|
label: "",
|
package/routes/fields.js
CHANGED
|
@@ -84,6 +84,10 @@ const fieldForm = async (req, fkey_opts, existing_names, id, hasData) => {
|
|
|
84
84
|
sublabel: req.__("Name of the field"),
|
|
85
85
|
type: "String",
|
|
86
86
|
attributes: { autofocus: true },
|
|
87
|
+
help: {
|
|
88
|
+
topic: "Field label",
|
|
89
|
+
context: {},
|
|
90
|
+
},
|
|
87
91
|
validator(s) {
|
|
88
92
|
if (!s || s === "") return req.__("Missing label");
|
|
89
93
|
if (!id && existing_names.includes(Field.labelToName(s)))
|
|
@@ -104,6 +108,10 @@ const fieldForm = async (req, fkey_opts, existing_names, id, hasData) => {
|
|
|
104
108
|
"The type determines the kind of data that can be stored in the field"
|
|
105
109
|
),
|
|
106
110
|
input_type: "select",
|
|
111
|
+
help: {
|
|
112
|
+
topic: "Field types",
|
|
113
|
+
context: {},
|
|
114
|
+
},
|
|
107
115
|
options: isPrimary
|
|
108
116
|
? primaryTypes
|
|
109
117
|
: getState().type_names.concat(fkey_opts || []),
|
|
@@ -1149,7 +1157,7 @@ router.post(
|
|
|
1149
1157
|
const jf = table.getField(ref);
|
|
1150
1158
|
const jtable = Table.findOne(jf.reftable_name);
|
|
1151
1159
|
const jrow = await jtable.getRow(
|
|
1152
|
-
{ [jtable.pk_name]: row[ref] },
|
|
1160
|
+
{ [jtable.pk_name]: row[ref]?.[jtable.pk_name] || row[ref] },
|
|
1153
1161
|
{ forUser: req.user, forPublic: !req.user }
|
|
1154
1162
|
);
|
|
1155
1163
|
row[ref] = jrow;
|
package/routes/homepage.js
CHANGED
package/routes/menu.js
CHANGED
|
@@ -15,6 +15,7 @@ const { getState } = require("@saltcorn/data/db/state");
|
|
|
15
15
|
const User = require("@saltcorn/data/models/user");
|
|
16
16
|
const View = require("@saltcorn/data/models/view");
|
|
17
17
|
const Page = require("@saltcorn/data/models/page");
|
|
18
|
+
const PageGroup = require("@saltcorn/data/models/page_group");
|
|
18
19
|
const { save_menu_items } = require("@saltcorn/data/models/config");
|
|
19
20
|
const db = require("@saltcorn/data/db");
|
|
20
21
|
|
|
@@ -43,6 +44,10 @@ module.exports = router;
|
|
|
43
44
|
const menuForm = async (req) => {
|
|
44
45
|
const views = await View.find({}, { orderBy: "name", nocase: true });
|
|
45
46
|
const pages = await Page.find({}, { orderBy: "name", nocase: true });
|
|
47
|
+
const pageGroups = await PageGroup.find(
|
|
48
|
+
{},
|
|
49
|
+
{ orderBy: "name", nocase: true }
|
|
50
|
+
);
|
|
46
51
|
const roles = await User.get_roles();
|
|
47
52
|
const tables = await Table.find_with_external({});
|
|
48
53
|
const dynTableOptions = tables.map((t) => t.name);
|
|
@@ -101,6 +106,7 @@ const menuForm = async (req) => {
|
|
|
101
106
|
options: [
|
|
102
107
|
"View",
|
|
103
108
|
"Page",
|
|
109
|
+
"Page Group",
|
|
104
110
|
"Link",
|
|
105
111
|
"Header",
|
|
106
112
|
"Dynamic",
|
|
@@ -141,6 +147,14 @@ const menuForm = async (req) => {
|
|
|
141
147
|
attributes: { options: views.map((r) => r.select_option) },
|
|
142
148
|
showIf: { type: "View" },
|
|
143
149
|
},
|
|
150
|
+
{
|
|
151
|
+
name: "page_group",
|
|
152
|
+
label: req.__("Page group"),
|
|
153
|
+
input_type: "select",
|
|
154
|
+
class: "item-menu",
|
|
155
|
+
options: pageGroups.map((r) => r.name),
|
|
156
|
+
showIf: { type: "Page Group" },
|
|
157
|
+
},
|
|
144
158
|
{
|
|
145
159
|
name: "action_name",
|
|
146
160
|
label: req.__("Action"),
|
|
@@ -194,6 +208,14 @@ const menuForm = async (req) => {
|
|
|
194
208
|
required: true,
|
|
195
209
|
showIf: { type: "Dynamic" },
|
|
196
210
|
},
|
|
211
|
+
{
|
|
212
|
+
name: "dyn_tooltip_fml",
|
|
213
|
+
label: req.__("Tooltip formula"),
|
|
214
|
+
class: "item-menu",
|
|
215
|
+
type: "String",
|
|
216
|
+
required: false,
|
|
217
|
+
showIf: { type: "Dynamic" },
|
|
218
|
+
},
|
|
197
219
|
{
|
|
198
220
|
name: "dyn_url_fml",
|
|
199
221
|
label: req.__("URL formula"),
|
|
@@ -223,6 +245,7 @@ const menuForm = async (req) => {
|
|
|
223
245
|
type: [
|
|
224
246
|
"View",
|
|
225
247
|
"Page",
|
|
248
|
+
"Page Group",
|
|
226
249
|
"Link",
|
|
227
250
|
"Header",
|
|
228
251
|
"Dynamic",
|
|
@@ -238,13 +261,34 @@ const menuForm = async (req) => {
|
|
|
238
261
|
attributes: {
|
|
239
262
|
html: `<button type="button" id="myEditor_icon" class="btn btn-outline-secondary"></button>`,
|
|
240
263
|
},
|
|
241
|
-
showIf: {
|
|
264
|
+
showIf: {
|
|
265
|
+
type: ["View", "Page", "Page Group", "Link", "Header", "Action"],
|
|
266
|
+
},
|
|
242
267
|
},
|
|
243
268
|
{
|
|
244
269
|
name: "icon",
|
|
245
270
|
class: "item-menu",
|
|
246
271
|
input_type: "hidden",
|
|
247
272
|
},
|
|
273
|
+
{
|
|
274
|
+
name: "tooltip",
|
|
275
|
+
label: req.__("Tooltip"),
|
|
276
|
+
class: "item-menu",
|
|
277
|
+
input_type: "text",
|
|
278
|
+
required: false,
|
|
279
|
+
showIf: {
|
|
280
|
+
type: [
|
|
281
|
+
"View",
|
|
282
|
+
"Page",
|
|
283
|
+
"Page Group",
|
|
284
|
+
"Link",
|
|
285
|
+
"Header",
|
|
286
|
+
"Dynamic",
|
|
287
|
+
"Search",
|
|
288
|
+
"Action",
|
|
289
|
+
],
|
|
290
|
+
},
|
|
291
|
+
},
|
|
248
292
|
{
|
|
249
293
|
name: "min_role",
|
|
250
294
|
label: req.__("Minimum role"),
|
|
@@ -258,6 +302,18 @@ const menuForm = async (req) => {
|
|
|
258
302
|
type: "Bool",
|
|
259
303
|
class: "item-menu",
|
|
260
304
|
required: false,
|
|
305
|
+
default: false,
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
name: "mobile_item_html",
|
|
309
|
+
label: req.__("Mobile HTML"),
|
|
310
|
+
sublabel: req.__(
|
|
311
|
+
"HTML for the item in the bottom navigation bar. Currently, only supported by the metronic theme."
|
|
312
|
+
),
|
|
313
|
+
type: "String",
|
|
314
|
+
class: "item-menu",
|
|
315
|
+
input_type: "textarea",
|
|
316
|
+
showIf: { disable_on_mobile: false, location: "Mobile Bottom" },
|
|
261
317
|
},
|
|
262
318
|
{
|
|
263
319
|
name: "target_blank",
|
|
@@ -265,7 +321,7 @@ const menuForm = async (req) => {
|
|
|
265
321
|
type: "Bool",
|
|
266
322
|
required: false,
|
|
267
323
|
class: "item-menu",
|
|
268
|
-
showIf: { type: ["View", "Page", "Link"] },
|
|
324
|
+
showIf: { type: ["View", "Page", "Page Group", "Link"] },
|
|
269
325
|
},
|
|
270
326
|
{
|
|
271
327
|
name: "in_modal",
|
|
@@ -273,7 +329,7 @@ const menuForm = async (req) => {
|
|
|
273
329
|
type: "Bool",
|
|
274
330
|
required: false,
|
|
275
331
|
class: "item-menu",
|
|
276
|
-
showIf: { type: ["View", "Page", "Link"] },
|
|
332
|
+
showIf: { type: ["View", "Page", "Page Group", "Link"] },
|
|
277
333
|
},
|
|
278
334
|
{
|
|
279
335
|
name: "style",
|
|
@@ -283,7 +339,15 @@ const menuForm = async (req) => {
|
|
|
283
339
|
type: "String",
|
|
284
340
|
required: true,
|
|
285
341
|
showIf: {
|
|
286
|
-
type: [
|
|
342
|
+
type: [
|
|
343
|
+
"View",
|
|
344
|
+
"Page",
|
|
345
|
+
"Page Group",
|
|
346
|
+
"Link",
|
|
347
|
+
"Header",
|
|
348
|
+
"Dynamic",
|
|
349
|
+
"Action",
|
|
350
|
+
],
|
|
287
351
|
},
|
|
288
352
|
attributes: {
|
|
289
353
|
options: [
|
|
@@ -310,6 +374,7 @@ const menuForm = async (req) => {
|
|
|
310
374
|
type: [
|
|
311
375
|
"View",
|
|
312
376
|
"Page",
|
|
377
|
+
"Page Group",
|
|
313
378
|
"Link",
|
|
314
379
|
"Header",
|
|
315
380
|
"Dynamic",
|