@saltcorn/server 0.9.6-beta.2 → 0.9.6-beta.20
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 +6 -1
- package/auth/admin.js +55 -53
- 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/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/load_plugins.js +33 -5
- package/locales/en.json +29 -1
- package/locales/it.json +3 -2
- package/markup/admin.js +1 -0
- 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 +248 -80
- package/public/saltcorn.css +80 -0
- package/public/saltcorn.js +86 -2
- package/restart_watcher.js +1 -0
- package/routes/actions.js +27 -0
- package/routes/admin.js +175 -64
- package/routes/api.js +6 -0
- package/routes/common_lists.js +42 -32
- package/routes/fields.js +70 -42
- package/routes/homepage.js +2 -0
- package/routes/index.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 +11 -2
- package/routes/registry.js +289 -0
- package/routes/search.js +10 -4
- package/routes/tables.js +51 -27
- package/routes/tenant.js +4 -15
- package/routes/utils.js +25 -8
- package/routes/view.js +1 -1
- 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/plugins.test.js +2 -0
- package/tests/sync.test.js +59 -0
- package/wrapper.js +4 -1
package/routes/actions.js
CHANGED
|
@@ -375,6 +375,7 @@ router.get(
|
|
|
375
375
|
|
|
376
376
|
const form = await triggerForm(req, trigger);
|
|
377
377
|
form.values = trigger;
|
|
378
|
+
form.onChange = `saveAndContinue(this)`;
|
|
378
379
|
send_events_page({
|
|
379
380
|
res,
|
|
380
381
|
req,
|
|
@@ -383,6 +384,7 @@ router.get(
|
|
|
383
384
|
contents: {
|
|
384
385
|
type: "card",
|
|
385
386
|
title: req.__("Edit trigger %s", id),
|
|
387
|
+
titleAjaxIndicator: true,
|
|
386
388
|
contents: renderForm(form, req.csrfToken()),
|
|
387
389
|
},
|
|
388
390
|
});
|
|
@@ -464,6 +466,10 @@ router.post(
|
|
|
464
466
|
...form.values.configuration,
|
|
465
467
|
};
|
|
466
468
|
await Trigger.update(trigger.id, form.values); //{configuration: form.values});
|
|
469
|
+
if (req.xhr) {
|
|
470
|
+
res.json({ success: "ok" });
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
467
473
|
req.flash("success", req.__("Action information saved"));
|
|
468
474
|
res.redirect(`/actions/`);
|
|
469
475
|
}
|
|
@@ -881,3 +887,24 @@ router.get(
|
|
|
881
887
|
}
|
|
882
888
|
})
|
|
883
889
|
);
|
|
890
|
+
|
|
891
|
+
/**
|
|
892
|
+
* @name post/clone/:id
|
|
893
|
+
* @function
|
|
894
|
+
* @memberof module:routes/actions~actionsRouter
|
|
895
|
+
* @function
|
|
896
|
+
*/
|
|
897
|
+
router.post(
|
|
898
|
+
"/clone/:id",
|
|
899
|
+
isAdmin,
|
|
900
|
+
error_catcher(async (req, res) => {
|
|
901
|
+
const { id } = req.params;
|
|
902
|
+
const trig = await Trigger.findOne({ id });
|
|
903
|
+
const newtrig = await trig.clone();
|
|
904
|
+
req.flash(
|
|
905
|
+
"success",
|
|
906
|
+
req.__("Trigger %s duplicated as %s", trig.name, newtrig.name)
|
|
907
|
+
);
|
|
908
|
+
res.redirect(`/actions`);
|
|
909
|
+
})
|
|
910
|
+
);
|
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
|
/**
|
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 || []),
|
|
@@ -255,6 +263,7 @@ const fieldFlow = (req) =>
|
|
|
255
263
|
expression = "__aggregation";
|
|
256
264
|
attributes.agg_relation = context.agg_relation;
|
|
257
265
|
attributes.agg_field = context.agg_field;
|
|
266
|
+
attributes.agg_order_by = context.agg_order_by;
|
|
258
267
|
attributes.aggwhere = context.aggwhere;
|
|
259
268
|
attributes.aggregate = context.aggregate;
|
|
260
269
|
const [table, ref] = context.agg_relation.split(".");
|
|
@@ -435,46 +444,64 @@ const fieldFlow = (req) =>
|
|
|
435
444
|
|
|
436
445
|
const { child_field_list, child_relations } =
|
|
437
446
|
await table.get_child_relations(true);
|
|
438
|
-
const agg_field_opts =
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
447
|
+
const agg_field_opts = [];
|
|
448
|
+
const agg_order_opts = [];
|
|
449
|
+
child_relations.forEach(({ table, key_field, through }) => {
|
|
450
|
+
const aggKey =
|
|
451
|
+
(through ? `${through.name}->` : "") +
|
|
452
|
+
`${table.name}.${key_field.name}`;
|
|
453
|
+
aggStatOptions[aggKey] = [
|
|
454
|
+
"Count",
|
|
455
|
+
"CountUnique",
|
|
456
|
+
"Avg",
|
|
457
|
+
"Sum",
|
|
458
|
+
"Max",
|
|
459
|
+
"Min",
|
|
460
|
+
"Array_Agg",
|
|
461
|
+
];
|
|
462
|
+
table.fields.forEach((f) => {
|
|
463
|
+
if (f.type && f.type.name === "Date") {
|
|
464
|
+
aggStatOptions[aggKey].push(`Latest ${f.name}`);
|
|
465
|
+
aggStatOptions[aggKey].push(`Earliest ${f.name}`);
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
agg_field_opts.push({
|
|
469
|
+
name: `agg_field`,
|
|
470
|
+
label: req.__("On Field"),
|
|
471
|
+
type: "String",
|
|
472
|
+
required: true,
|
|
473
|
+
attributes: {
|
|
474
|
+
options: table.fields
|
|
475
|
+
.filter((f) => !f.calculated || f.stored)
|
|
476
|
+
.map((f) => ({
|
|
477
|
+
label: f.name,
|
|
478
|
+
name: `${f.name}@${f.type_name}`,
|
|
479
|
+
})),
|
|
480
|
+
},
|
|
481
|
+
showIf: {
|
|
482
|
+
agg_relation: aggKey,
|
|
483
|
+
expression_type: "Aggregation",
|
|
484
|
+
},
|
|
485
|
+
});
|
|
486
|
+
agg_order_opts.push({
|
|
487
|
+
name: `agg_order_by`,
|
|
488
|
+
label: req.__("Order by"),
|
|
489
|
+
type: "String",
|
|
490
|
+
attributes: {
|
|
491
|
+
options: table.fields
|
|
492
|
+
.filter((f) => !f.calculated || f.stored)
|
|
493
|
+
.map((f) => ({
|
|
494
|
+
label: f.name,
|
|
495
|
+
name: f.name,
|
|
496
|
+
})),
|
|
497
|
+
},
|
|
498
|
+
showIf: {
|
|
499
|
+
agg_relation: aggKey,
|
|
500
|
+
expression_type: "Aggregation",
|
|
501
|
+
aggregate: "Array_Agg",
|
|
502
|
+
},
|
|
503
|
+
});
|
|
504
|
+
});
|
|
478
505
|
return new Form({
|
|
479
506
|
fields: [
|
|
480
507
|
{
|
|
@@ -520,6 +547,7 @@ const fieldFlow = (req) =>
|
|
|
520
547
|
required: false,
|
|
521
548
|
showIf: { expression_type: "Aggregation" },
|
|
522
549
|
},
|
|
550
|
+
...agg_order_opts,
|
|
523
551
|
{
|
|
524
552
|
name: "model",
|
|
525
553
|
label: req.__("Model"),
|
|
@@ -1129,7 +1157,7 @@ router.post(
|
|
|
1129
1157
|
const jf = table.getField(ref);
|
|
1130
1158
|
const jtable = Table.findOne(jf.reftable_name);
|
|
1131
1159
|
const jrow = await jtable.getRow(
|
|
1132
|
-
{ [jtable.pk_name]: row[ref] },
|
|
1160
|
+
{ [jtable.pk_name]: row[ref]?.[jtable.pk_name] || row[ref] },
|
|
1133
1161
|
{ forUser: req.user, forPublic: !req.user }
|
|
1134
1162
|
);
|
|
1135
1163
|
row[ref] = jrow;
|
|
@@ -1157,7 +1185,7 @@ router.post(
|
|
|
1157
1185
|
else res.send(fv.run(result, req, { row, ...configuration }));
|
|
1158
1186
|
} catch (e) {
|
|
1159
1187
|
console.error("show-calculated error", e);
|
|
1160
|
-
return res.status(
|
|
1188
|
+
return res.status(200).send(``);
|
|
1161
1189
|
}
|
|
1162
1190
|
})
|
|
1163
1191
|
);
|
package/routes/homepage.js
CHANGED
package/routes/index.js
CHANGED
|
@@ -36,6 +36,7 @@ const roleadmin = require("../auth/roleadmin");
|
|
|
36
36
|
const tags = require("./tags");
|
|
37
37
|
const tagentries = require("./tag_entries");
|
|
38
38
|
const diagram = require("./diagram");
|
|
39
|
+
const registry = require("./registry");
|
|
39
40
|
const sync = require("./sync");
|
|
40
41
|
|
|
41
42
|
module.exports =
|
|
@@ -78,5 +79,6 @@ module.exports =
|
|
|
78
79
|
app.use("/tag", tags);
|
|
79
80
|
app.use("/tag-entries", tagentries);
|
|
80
81
|
app.use("/diagram", diagram);
|
|
82
|
+
app.use("/registry-editor", registry);
|
|
81
83
|
app.use("/sync", sync);
|
|
82
84
|
};
|