@saltcorn/server 0.7.3-beta.2 → 0.7.3-beta.7
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 +5 -1
- package/auth/routes.js +37 -10
- package/locales/en.json +923 -916
- package/locales/zh.json +188 -188
- package/markup/admin.js +5 -1
- package/package.json +8 -8
- package/public/gridedit.js +6 -0
- package/public/jquery-menu-editor.min.js +15 -2
- package/public/saltcorn-common.js +78 -2
- package/public/saltcorn.css +22 -0
- package/public/saltcorn.js +19 -38
- package/routes/admin.js +391 -2
- package/routes/fields.js +3 -0
- package/routes/files.js +5 -3
- package/routes/homepage.js +7 -7
- package/routes/menu.js +13 -1
- package/routes/pageedit.js +1 -0
- package/routes/plugins.js +1 -0
- package/routes/tables.js +7 -6
- package/routes/viewedit.js +11 -1
- package/serve.js +1 -1
- package/tests/tenant.test.js +6 -0
package/routes/admin.js
CHANGED
|
@@ -18,7 +18,7 @@ const { spawn } = require("child_process");
|
|
|
18
18
|
const User = require("@saltcorn/data/models/user");
|
|
19
19
|
const path = require("path");
|
|
20
20
|
const { getAllTenants } = require("@saltcorn/admin-models/models/tenant");
|
|
21
|
-
const { post_btn, renderForm } = require("@saltcorn/markup");
|
|
21
|
+
const { post_btn, renderForm, mkTable, link } = require("@saltcorn/markup");
|
|
22
22
|
const {
|
|
23
23
|
div,
|
|
24
24
|
a,
|
|
@@ -35,6 +35,17 @@ const {
|
|
|
35
35
|
code,
|
|
36
36
|
h5,
|
|
37
37
|
pre,
|
|
38
|
+
button,
|
|
39
|
+
form,
|
|
40
|
+
label,
|
|
41
|
+
input,
|
|
42
|
+
select,
|
|
43
|
+
option,
|
|
44
|
+
fieldset,
|
|
45
|
+
legend,
|
|
46
|
+
ul,
|
|
47
|
+
li,
|
|
48
|
+
ol,
|
|
38
49
|
} = require("@saltcorn/markup/tags");
|
|
39
50
|
const db = require("@saltcorn/data/db");
|
|
40
51
|
const {
|
|
@@ -75,6 +86,7 @@ const {
|
|
|
75
86
|
const moment = require("moment");
|
|
76
87
|
const View = require("@saltcorn/data/models/view");
|
|
77
88
|
const { getConfigFile } = require("@saltcorn/data/db/connect");
|
|
89
|
+
const os = require("os");
|
|
78
90
|
|
|
79
91
|
/**
|
|
80
92
|
* @type {object}
|
|
@@ -136,6 +148,23 @@ const email_form = async (req) => {
|
|
|
136
148
|
return form;
|
|
137
149
|
};
|
|
138
150
|
|
|
151
|
+
const app_files_table = (file, req) =>
|
|
152
|
+
mkTable(
|
|
153
|
+
[
|
|
154
|
+
{
|
|
155
|
+
label: req.__("Filename"),
|
|
156
|
+
key: (r) => div(r.filename),
|
|
157
|
+
},
|
|
158
|
+
{ label: req.__("Size (KiB)"), key: "size_kb", align: "right" },
|
|
159
|
+
{ label: req.__("Media type"), key: (r) => r.mimetype },
|
|
160
|
+
{
|
|
161
|
+
label: req.__("Download"),
|
|
162
|
+
key: (r) => link(`/files/download/${r.id}`, req.__("Download")),
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
[file]
|
|
166
|
+
);
|
|
167
|
+
|
|
139
168
|
/**
|
|
140
169
|
* Router get /
|
|
141
170
|
* @name get
|
|
@@ -345,7 +374,13 @@ router.get(
|
|
|
345
374
|
? {
|
|
346
375
|
type: "card",
|
|
347
376
|
title: req.__("Automated backup"),
|
|
348
|
-
contents: div(
|
|
377
|
+
contents: div(
|
|
378
|
+
renderForm(backupForm, req.csrfToken()),
|
|
379
|
+
a(
|
|
380
|
+
{ href: "/admin/auto-backup-list" },
|
|
381
|
+
"Restore/download automated backups »"
|
|
382
|
+
)
|
|
383
|
+
),
|
|
349
384
|
}
|
|
350
385
|
: { type: "blank", contents: "" },
|
|
351
386
|
],
|
|
@@ -354,6 +389,93 @@ router.get(
|
|
|
354
389
|
})
|
|
355
390
|
);
|
|
356
391
|
|
|
392
|
+
/**
|
|
393
|
+
* @name get/backup
|
|
394
|
+
* @function
|
|
395
|
+
* @memberof module:routes/admin~routes/adminRouter
|
|
396
|
+
*/
|
|
397
|
+
router.get(
|
|
398
|
+
"/auto-backup-list",
|
|
399
|
+
isAdmin,
|
|
400
|
+
error_catcher(async (req, res) => {
|
|
401
|
+
const isRoot = db.getTenantSchema() === db.connectObj.default_schema;
|
|
402
|
+
if (!isRoot) {
|
|
403
|
+
res.redirect("/admin/backup");
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
const auto_backup_directory = getState().getConfig("auto_backup_directory");
|
|
407
|
+
const fileNms = await fs.promises.readdir(auto_backup_directory);
|
|
408
|
+
const backupFiles = fileNms.filter(
|
|
409
|
+
(fnm) => fnm.startsWith("sc-backup") && fnm.endsWith(".zip")
|
|
410
|
+
);
|
|
411
|
+
send_admin_page({
|
|
412
|
+
res,
|
|
413
|
+
req,
|
|
414
|
+
active_sub: "Backup",
|
|
415
|
+
contents: {
|
|
416
|
+
above: [
|
|
417
|
+
{
|
|
418
|
+
type: "card",
|
|
419
|
+
title: req.__("Download automated backup"),
|
|
420
|
+
contents: div(
|
|
421
|
+
ul(
|
|
422
|
+
backupFiles.map((fnm) =>
|
|
423
|
+
li(
|
|
424
|
+
a(
|
|
425
|
+
{
|
|
426
|
+
href: `/admin/auto-backup-download/${encodeURIComponent(
|
|
427
|
+
fnm
|
|
428
|
+
)}`,
|
|
429
|
+
},
|
|
430
|
+
fnm
|
|
431
|
+
)
|
|
432
|
+
)
|
|
433
|
+
)
|
|
434
|
+
)
|
|
435
|
+
),
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
type: "card",
|
|
439
|
+
title: req.__("Restoring automated backup"),
|
|
440
|
+
contents: div(
|
|
441
|
+
ol(
|
|
442
|
+
li("Download one of the backups above"),
|
|
443
|
+
li(
|
|
444
|
+
a({ href: "/admin/clear-all" }, "Clear this application"),
|
|
445
|
+
" ",
|
|
446
|
+
"(tick all boxes)"
|
|
447
|
+
),
|
|
448
|
+
li(
|
|
449
|
+
"When prompted to create the first user, click the link to restore a backup"
|
|
450
|
+
),
|
|
451
|
+
li("Select the downloaded backup file")
|
|
452
|
+
)
|
|
453
|
+
),
|
|
454
|
+
},
|
|
455
|
+
],
|
|
456
|
+
},
|
|
457
|
+
});
|
|
458
|
+
})
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
router.get(
|
|
462
|
+
"/auto-backup-download/:filename",
|
|
463
|
+
isAdmin,
|
|
464
|
+
error_catcher(async (req, res) => {
|
|
465
|
+
const { filename } = req.params;
|
|
466
|
+
const isRoot = db.getTenantSchema() === db.connectObj.default_schema;
|
|
467
|
+
if (
|
|
468
|
+
!isRoot ||
|
|
469
|
+
!(filename.startsWith("sc-backup") && filename.endsWith(".zip"))
|
|
470
|
+
) {
|
|
471
|
+
res.redirect("/admin/backup");
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
const auto_backup_directory = getState().getConfig("auto_backup_directory");
|
|
475
|
+
res.download(path.join(auto_backup_directory, filename), filename);
|
|
476
|
+
})
|
|
477
|
+
);
|
|
478
|
+
|
|
357
479
|
/**
|
|
358
480
|
* Auto backup Form
|
|
359
481
|
* @param {object} req
|
|
@@ -503,6 +625,7 @@ router.get(
|
|
|
503
625
|
" ",
|
|
504
626
|
req.__("Configuration check")
|
|
505
627
|
),
|
|
628
|
+
|
|
506
629
|
hr(),
|
|
507
630
|
|
|
508
631
|
a(
|
|
@@ -935,6 +1058,272 @@ router.get(
|
|
|
935
1058
|
})
|
|
936
1059
|
);
|
|
937
1060
|
|
|
1061
|
+
router.get(
|
|
1062
|
+
"/build-mobile-app",
|
|
1063
|
+
isAdmin,
|
|
1064
|
+
error_catcher(async (req, res) => {
|
|
1065
|
+
const isRoot = db.getTenantSchema() === db.connectObj.default_schema;
|
|
1066
|
+
const views = await View.find();
|
|
1067
|
+
const execBuildMsg =
|
|
1068
|
+
"This is still under development and might run longer.";
|
|
1069
|
+
|
|
1070
|
+
send_admin_page({
|
|
1071
|
+
res,
|
|
1072
|
+
req,
|
|
1073
|
+
active_sub: "Mobile app",
|
|
1074
|
+
contents: {
|
|
1075
|
+
above: [
|
|
1076
|
+
{
|
|
1077
|
+
type: "card",
|
|
1078
|
+
title: req.__("Build mobile app"),
|
|
1079
|
+
contents: form(
|
|
1080
|
+
{
|
|
1081
|
+
action: "/admin/build-mobile-app",
|
|
1082
|
+
method: "post",
|
|
1083
|
+
},
|
|
1084
|
+
|
|
1085
|
+
fieldset(
|
|
1086
|
+
input({
|
|
1087
|
+
type: "hidden",
|
|
1088
|
+
name: "_csrf",
|
|
1089
|
+
value: req.csrfToken(),
|
|
1090
|
+
}),
|
|
1091
|
+
div(
|
|
1092
|
+
{ class: "container ps-2" },
|
|
1093
|
+
div(
|
|
1094
|
+
{ class: "row pb-2" },
|
|
1095
|
+
div({ class: "col-sm-4 fw-bold" }, "Entry view"),
|
|
1096
|
+
div({ class: "col-sm-4 fw-bold" }, "Platform"),
|
|
1097
|
+
div(
|
|
1098
|
+
{
|
|
1099
|
+
class: "col-sm-1 fw-bold d-flex justify-content-center",
|
|
1100
|
+
},
|
|
1101
|
+
"docker"
|
|
1102
|
+
)
|
|
1103
|
+
),
|
|
1104
|
+
div(
|
|
1105
|
+
{ class: "row" },
|
|
1106
|
+
div(
|
|
1107
|
+
{ class: "col-sm-4" },
|
|
1108
|
+
select(
|
|
1109
|
+
{
|
|
1110
|
+
class: "form-control",
|
|
1111
|
+
name: "entryView",
|
|
1112
|
+
id: "entryViewInput",
|
|
1113
|
+
},
|
|
1114
|
+
views
|
|
1115
|
+
.map((view) =>
|
|
1116
|
+
option({ value: view.name }, view.name)
|
|
1117
|
+
)
|
|
1118
|
+
.join(",")
|
|
1119
|
+
)
|
|
1120
|
+
),
|
|
1121
|
+
div(
|
|
1122
|
+
{ class: "col-sm-4" },
|
|
1123
|
+
|
|
1124
|
+
div(
|
|
1125
|
+
{ class: "container ps-0" },
|
|
1126
|
+
div(
|
|
1127
|
+
{ class: "row" },
|
|
1128
|
+
div({ class: "col-sm-8" }, "android"),
|
|
1129
|
+
div(
|
|
1130
|
+
{ class: "col-sm" },
|
|
1131
|
+
input({
|
|
1132
|
+
type: "checkbox",
|
|
1133
|
+
class: "form-check-input",
|
|
1134
|
+
name: "androidPlatform",
|
|
1135
|
+
id: "androidCheckboxId",
|
|
1136
|
+
})
|
|
1137
|
+
)
|
|
1138
|
+
),
|
|
1139
|
+
div(
|
|
1140
|
+
{ class: "row" },
|
|
1141
|
+
div({ class: "col-sm-8" }, "iOS"),
|
|
1142
|
+
div(
|
|
1143
|
+
{ class: "col-sm" },
|
|
1144
|
+
input({
|
|
1145
|
+
type: "checkbox",
|
|
1146
|
+
class: "form-check-input",
|
|
1147
|
+
name: "iOSPlatform",
|
|
1148
|
+
id: "iOSCheckboxId",
|
|
1149
|
+
})
|
|
1150
|
+
)
|
|
1151
|
+
)
|
|
1152
|
+
)
|
|
1153
|
+
),
|
|
1154
|
+
div(
|
|
1155
|
+
{ class: "col-sm-1 d-flex justify-content-center" },
|
|
1156
|
+
input({
|
|
1157
|
+
type: "checkbox",
|
|
1158
|
+
class: "form-check-input",
|
|
1159
|
+
name: "useDocker",
|
|
1160
|
+
id: "dockerCheckboxId",
|
|
1161
|
+
})
|
|
1162
|
+
)
|
|
1163
|
+
),
|
|
1164
|
+
div(
|
|
1165
|
+
{ class: "row pb-2" },
|
|
1166
|
+
div(
|
|
1167
|
+
{ class: "col-sm-8" },
|
|
1168
|
+
label(
|
|
1169
|
+
{
|
|
1170
|
+
for: "appNameInputId",
|
|
1171
|
+
class: "form-label fw-bold",
|
|
1172
|
+
},
|
|
1173
|
+
"App file"
|
|
1174
|
+
),
|
|
1175
|
+
input({
|
|
1176
|
+
type: "text",
|
|
1177
|
+
class: "form-control",
|
|
1178
|
+
name: "appFile",
|
|
1179
|
+
id: "appFileInputId",
|
|
1180
|
+
placeholder: "app-debug",
|
|
1181
|
+
})
|
|
1182
|
+
)
|
|
1183
|
+
),
|
|
1184
|
+
div(
|
|
1185
|
+
{ class: "row pb-3" },
|
|
1186
|
+
div(
|
|
1187
|
+
{ class: "col-sm-8" },
|
|
1188
|
+
label(
|
|
1189
|
+
{
|
|
1190
|
+
for: "serverURLInputId",
|
|
1191
|
+
class: "form-label fw-bold",
|
|
1192
|
+
},
|
|
1193
|
+
"Server URL"
|
|
1194
|
+
),
|
|
1195
|
+
input({
|
|
1196
|
+
type: "text",
|
|
1197
|
+
class: "form-control",
|
|
1198
|
+
name: "serverURL",
|
|
1199
|
+
id: "serverURLInputId",
|
|
1200
|
+
placeholder: "http://10.0.2.2:3000",
|
|
1201
|
+
})
|
|
1202
|
+
)
|
|
1203
|
+
)
|
|
1204
|
+
),
|
|
1205
|
+
button(
|
|
1206
|
+
{
|
|
1207
|
+
type: "submit",
|
|
1208
|
+
onClick: `notifyAlert('${execBuildMsg}'); press_store_button(this);`,
|
|
1209
|
+
class: "btn btn-warning",
|
|
1210
|
+
},
|
|
1211
|
+
i({ class: "fas fa-hammer pe-2" }),
|
|
1212
|
+
|
|
1213
|
+
"Build mobile app"
|
|
1214
|
+
)
|
|
1215
|
+
)
|
|
1216
|
+
),
|
|
1217
|
+
},
|
|
1218
|
+
],
|
|
1219
|
+
},
|
|
1220
|
+
});
|
|
1221
|
+
})
|
|
1222
|
+
);
|
|
1223
|
+
|
|
1224
|
+
router.post(
|
|
1225
|
+
"/build-mobile-app",
|
|
1226
|
+
isAdmin,
|
|
1227
|
+
error_catcher(async (req, res) => {
|
|
1228
|
+
let {
|
|
1229
|
+
entryView,
|
|
1230
|
+
androidPlatform,
|
|
1231
|
+
iOSPlatform,
|
|
1232
|
+
useDocker,
|
|
1233
|
+
appFile,
|
|
1234
|
+
serverURL,
|
|
1235
|
+
} = req.body;
|
|
1236
|
+
if (!androidPlatform && !iOSPlatform) {
|
|
1237
|
+
req.flash(
|
|
1238
|
+
"error",
|
|
1239
|
+
req.__("Please select at least one platform (android or iOS).")
|
|
1240
|
+
);
|
|
1241
|
+
return res.redirect("/admin/build-mobile-app");
|
|
1242
|
+
}
|
|
1243
|
+
if (!androidPlatform && useDocker) {
|
|
1244
|
+
req.flash("error", req.__("Only the android build supports docker."));
|
|
1245
|
+
return res.redirect("/admin/build-mobile-app");
|
|
1246
|
+
}
|
|
1247
|
+
if (appFile && !appFile.endsWith(".apk")) appFile = `${appFile}.apk`;
|
|
1248
|
+
const appOut = path.join(__dirname, "..", "mobile-app-out");
|
|
1249
|
+
const spawnParams = [
|
|
1250
|
+
"build-app",
|
|
1251
|
+
"-v",
|
|
1252
|
+
entryView,
|
|
1253
|
+
"-c",
|
|
1254
|
+
appOut,
|
|
1255
|
+
"-b",
|
|
1256
|
+
`${os.userInfo().homedir}/mobile_app_build`,
|
|
1257
|
+
];
|
|
1258
|
+
if (useDocker) spawnParams.push("-d");
|
|
1259
|
+
if (androidPlatform) spawnParams.push("-p", "android");
|
|
1260
|
+
if (iOSPlatform) spawnParams.push("-p", "ios");
|
|
1261
|
+
if (appFile) spawnParams.push("-a", appFile);
|
|
1262
|
+
if (serverURL) spawnParams.push("-s", serverURL);
|
|
1263
|
+
const child = spawn("saltcorn", spawnParams, {
|
|
1264
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1265
|
+
cwd: ".",
|
|
1266
|
+
});
|
|
1267
|
+
const childOutputs = [];
|
|
1268
|
+
child.stdout.on("data", (data) => {
|
|
1269
|
+
// console.log(data.toString());
|
|
1270
|
+
childOutputs.push(data.toString());
|
|
1271
|
+
});
|
|
1272
|
+
child.stderr.on("data", (data) => {
|
|
1273
|
+
// console.log(data.toString());
|
|
1274
|
+
childOutputs.push(data.toString());
|
|
1275
|
+
});
|
|
1276
|
+
child.on("exit", async function (exitCode, signal) {
|
|
1277
|
+
if (exitCode === 0) {
|
|
1278
|
+
const file = await File.from_existing_file(
|
|
1279
|
+
appOut,
|
|
1280
|
+
appFile ? appFile : "app-debug.apk",
|
|
1281
|
+
req.user.id
|
|
1282
|
+
);
|
|
1283
|
+
res.sendWrap(req.__(`Admin`), {
|
|
1284
|
+
above: [
|
|
1285
|
+
{
|
|
1286
|
+
type: "card",
|
|
1287
|
+
title: req.__("Build Result"),
|
|
1288
|
+
contents: div("The build was successfully"),
|
|
1289
|
+
},
|
|
1290
|
+
app_files_table(file, req),
|
|
1291
|
+
],
|
|
1292
|
+
});
|
|
1293
|
+
} else
|
|
1294
|
+
res.sendWrap(req.__(`Admin`), {
|
|
1295
|
+
above: [
|
|
1296
|
+
{
|
|
1297
|
+
type: "card",
|
|
1298
|
+
title: req.__("Build Result"),
|
|
1299
|
+
contents: div(
|
|
1300
|
+
"Unable to build the app:",
|
|
1301
|
+
pre(code(childOutputs.join("<br/>")))
|
|
1302
|
+
),
|
|
1303
|
+
},
|
|
1304
|
+
],
|
|
1305
|
+
});
|
|
1306
|
+
});
|
|
1307
|
+
child.on("error", function (msg) {
|
|
1308
|
+
const message = msg.message ? msg.message : msg.code;
|
|
1309
|
+
const stack = msg.stack ? msg.stack : "";
|
|
1310
|
+
res.sendWrap(req.__(`Admin`), {
|
|
1311
|
+
above: [
|
|
1312
|
+
{
|
|
1313
|
+
type: "card",
|
|
1314
|
+
title: req.__("Build Result"),
|
|
1315
|
+
contents: div(
|
|
1316
|
+
p("Unable to build the app:"),
|
|
1317
|
+
pre(code(message)),
|
|
1318
|
+
pre(code(stack))
|
|
1319
|
+
),
|
|
1320
|
+
},
|
|
1321
|
+
],
|
|
1322
|
+
});
|
|
1323
|
+
});
|
|
1324
|
+
})
|
|
1325
|
+
);
|
|
1326
|
+
|
|
938
1327
|
/**
|
|
939
1328
|
* @name post/clear-all
|
|
940
1329
|
* @function
|
package/routes/fields.js
CHANGED
|
@@ -488,6 +488,7 @@ router.get(
|
|
|
488
488
|
},
|
|
489
489
|
{
|
|
490
490
|
type: "card",
|
|
491
|
+
class: "mt-0",
|
|
491
492
|
title: wizardCardTitle(field.label, wf, wfres),
|
|
492
493
|
contents: renderForm(wfres.renderForm, req.csrfToken()),
|
|
493
494
|
},
|
|
@@ -524,6 +525,7 @@ router.get(
|
|
|
524
525
|
},
|
|
525
526
|
{
|
|
526
527
|
type: "card",
|
|
528
|
+
class: "mt-0",
|
|
527
529
|
title: wizardCardTitle(req.__(`New field`), wf, wfres),
|
|
528
530
|
contents: renderForm(wfres.renderForm, req.csrfToken()),
|
|
529
531
|
},
|
|
@@ -589,6 +591,7 @@ router.post(
|
|
|
589
591
|
},
|
|
590
592
|
{
|
|
591
593
|
type: "card",
|
|
594
|
+
class: "mt-0",
|
|
592
595
|
title: wizardCardTitle(
|
|
593
596
|
wfres.context.label || req.__("New field"),
|
|
594
597
|
wf,
|
package/routes/files.js
CHANGED
|
@@ -184,11 +184,11 @@ router.get(
|
|
|
184
184
|
* @function
|
|
185
185
|
*/
|
|
186
186
|
router.get(
|
|
187
|
-
"/resize/:id/:width_str",
|
|
187
|
+
"/resize/:id/:width_str/:height_str?",
|
|
188
188
|
error_catcher(async (req, res) => {
|
|
189
189
|
const role = req.user && req.user.id ? req.user.role_id : 10;
|
|
190
190
|
const user_id = req.user && req.user.id;
|
|
191
|
-
const { id, width_str } = req.params;
|
|
191
|
+
const { id, width_str, height_str } = req.params;
|
|
192
192
|
let file;
|
|
193
193
|
if (typeof strictParseInt(id) !== "undefined")
|
|
194
194
|
file = await File.findOne({ id });
|
|
@@ -208,15 +208,17 @@ router.get(
|
|
|
208
208
|
if (file.s3_store) s3storage.serveObject(file, res, false);
|
|
209
209
|
else {
|
|
210
210
|
const width = strictParseInt(width_str);
|
|
211
|
+
const height = height_str ? strictParseInt(height_str) : null;
|
|
211
212
|
if (!width) {
|
|
212
213
|
res.sendFile(file.location);
|
|
213
214
|
return;
|
|
214
215
|
}
|
|
215
|
-
const fnm = `${file.location}_w${width}`;
|
|
216
|
+
const fnm = `${file.location}_w${width}${height ? `_h${height}` : ""}`;
|
|
216
217
|
if (!fs.existsSync(fnm)) {
|
|
217
218
|
await resizer({
|
|
218
219
|
fromFileName: file.location,
|
|
219
220
|
width,
|
|
221
|
+
height,
|
|
220
222
|
toFileName: fnm,
|
|
221
223
|
});
|
|
222
224
|
}
|
package/routes/homepage.js
CHANGED
|
@@ -49,7 +49,7 @@ const tableTable = (tables, req) =>
|
|
|
49
49
|
*/
|
|
50
50
|
const tableCard = (tables, req) => ({
|
|
51
51
|
type: "card",
|
|
52
|
-
class: "welcome-page-entity-list",
|
|
52
|
+
class: "welcome-page-entity-list mt-1",
|
|
53
53
|
title: link("/table", req.__("Tables")),
|
|
54
54
|
contents:
|
|
55
55
|
(tables.length <= 1
|
|
@@ -102,7 +102,7 @@ const viewTable = (views, req) =>
|
|
|
102
102
|
const viewCard = (views, req) => ({
|
|
103
103
|
type: "card",
|
|
104
104
|
title: link("/viewedit", req.__("Views")),
|
|
105
|
-
class: "welcome-page-entity-list",
|
|
105
|
+
class: "welcome-page-entity-list mt-1",
|
|
106
106
|
bodyClass: "py-0 pe-0",
|
|
107
107
|
contents:
|
|
108
108
|
(views.length <= 1
|
|
@@ -156,7 +156,7 @@ const pageTable = (pages, req) =>
|
|
|
156
156
|
const pageCard = (pages, req) => ({
|
|
157
157
|
type: "card",
|
|
158
158
|
title: link("/pageedit", req.__("Pages")),
|
|
159
|
-
class: "welcome-page-entity-list",
|
|
159
|
+
class: "welcome-page-entity-list mt-1",
|
|
160
160
|
contents:
|
|
161
161
|
(pages.length <= 1
|
|
162
162
|
? p(
|
|
@@ -369,9 +369,9 @@ const welcome_page = async (req) => {
|
|
|
369
369
|
above: [
|
|
370
370
|
{
|
|
371
371
|
besides: [
|
|
372
|
-
pageCard(pages, req),
|
|
373
|
-
viewCard(views, req),
|
|
374
372
|
tableCard(tables, req),
|
|
373
|
+
viewCard(views, req),
|
|
374
|
+
pageCard(pages, req),
|
|
375
375
|
],
|
|
376
376
|
},
|
|
377
377
|
{
|
|
@@ -380,7 +380,7 @@ const welcome_page = async (req) => {
|
|
|
380
380
|
type: "card",
|
|
381
381
|
//title: req.__("Install pack"),
|
|
382
382
|
bodyClass: "py-0 pe-0",
|
|
383
|
-
class: "welcome-page-entity-list",
|
|
383
|
+
class: "welcome-page-entity-list mt-2",
|
|
384
384
|
|
|
385
385
|
tabContents:
|
|
386
386
|
triggers.length > 0
|
|
@@ -399,7 +399,7 @@ const welcome_page = async (req) => {
|
|
|
399
399
|
type: "card",
|
|
400
400
|
//title: req.__("Learn"),
|
|
401
401
|
bodyClass: "py-0 pe-0",
|
|
402
|
-
class: "welcome-page-entity-list",
|
|
402
|
+
class: "welcome-page-entity-list mt-2",
|
|
403
403
|
tabContents:
|
|
404
404
|
users.length > 4
|
|
405
405
|
? {
|
package/routes/menu.js
CHANGED
|
@@ -84,7 +84,15 @@ const menuForm = async (req) => {
|
|
|
84
84
|
input_type: "select",
|
|
85
85
|
class: "menutype item-menu",
|
|
86
86
|
required: true,
|
|
87
|
-
options: [
|
|
87
|
+
options: [
|
|
88
|
+
"View",
|
|
89
|
+
"Page",
|
|
90
|
+
"Link",
|
|
91
|
+
"Header",
|
|
92
|
+
"Dynamic",
|
|
93
|
+
"Search",
|
|
94
|
+
"Separator",
|
|
95
|
+
],
|
|
88
96
|
},
|
|
89
97
|
{
|
|
90
98
|
name: "text",
|
|
@@ -92,6 +100,9 @@ const menuForm = async (req) => {
|
|
|
92
100
|
class: "item-menu",
|
|
93
101
|
input_type: "text",
|
|
94
102
|
required: true,
|
|
103
|
+
showIf: {
|
|
104
|
+
type: ["View", "Page", "Link", "Header", "Dynamic", "Search"],
|
|
105
|
+
},
|
|
95
106
|
},
|
|
96
107
|
{
|
|
97
108
|
name: "icon_btn",
|
|
@@ -271,6 +282,7 @@ const menuEditorScript = (menu_items) => `
|
|
|
271
282
|
{
|
|
272
283
|
listOptions: sortableListOptions,
|
|
273
284
|
iconPicker: iconPickerOptions,
|
|
285
|
+
getLabelText: (item) => item?.text || item?.type,
|
|
274
286
|
labelEdit: 'Edit <i class="fas fa-edit clickable"></i>',
|
|
275
287
|
maxLevel: 1 // (Optional) Default is -1 (no level limit)
|
|
276
288
|
// Valid levels are from [0, 1, 2, 3,...N]
|
package/routes/pageedit.js
CHANGED
package/routes/plugins.js
CHANGED
package/routes/tables.js
CHANGED
|
@@ -448,6 +448,7 @@ router.get(
|
|
|
448
448
|
},
|
|
449
449
|
{
|
|
450
450
|
type: "card",
|
|
451
|
+
class: "mt-0",
|
|
451
452
|
title: cardHeaderTabs([
|
|
452
453
|
{ label: req.__("Your tables"), href: "/table" },
|
|
453
454
|
{
|
|
@@ -636,7 +637,9 @@ router.get(
|
|
|
636
637
|
}
|
|
637
638
|
var viewCard;
|
|
638
639
|
if (fields.length > 0) {
|
|
639
|
-
const views = await View.find(
|
|
640
|
+
const views = await View.find(
|
|
641
|
+
table.external ? { exttable_name: table.name } : { table_id: table.id }
|
|
642
|
+
);
|
|
640
643
|
var viewCardContents;
|
|
641
644
|
if (views.length > 0) {
|
|
642
645
|
viewCardContents = mkTable(
|
|
@@ -801,15 +804,12 @@ router.get(
|
|
|
801
804
|
type: "breadcrumbs",
|
|
802
805
|
crumbs: [
|
|
803
806
|
{ text: req.__("Tables"), href: "/table" },
|
|
804
|
-
{ text: table.name },
|
|
807
|
+
{ text: span({ class: "fw-bold text-body" }, table.name) },
|
|
805
808
|
],
|
|
806
809
|
},
|
|
807
|
-
{
|
|
808
|
-
type: "pageHeader",
|
|
809
|
-
title: req.__(`%s table`, table.name),
|
|
810
|
-
},
|
|
811
810
|
{
|
|
812
811
|
type: "card",
|
|
812
|
+
class: "mt-0",
|
|
813
813
|
title: req.__("Fields"),
|
|
814
814
|
contents: fieldCard,
|
|
815
815
|
},
|
|
@@ -1076,6 +1076,7 @@ router.get(
|
|
|
1076
1076
|
},
|
|
1077
1077
|
{
|
|
1078
1078
|
type: "card",
|
|
1079
|
+
class: "mt-0",
|
|
1079
1080
|
title: cardHeaderTabs([
|
|
1080
1081
|
{ label: req.__("Your tables"), href: "/table", active: true },
|
|
1081
1082
|
{
|
package/routes/viewedit.js
CHANGED
|
@@ -197,6 +197,7 @@ router.get(
|
|
|
197
197
|
},
|
|
198
198
|
{
|
|
199
199
|
type: "card",
|
|
200
|
+
class: "mt-0",
|
|
200
201
|
title: req.__("Your views"),
|
|
201
202
|
contents: [
|
|
202
203
|
viewMarkup,
|
|
@@ -378,7 +379,13 @@ router.get(
|
|
|
378
379
|
},
|
|
379
380
|
{
|
|
380
381
|
type: "card",
|
|
381
|
-
|
|
382
|
+
class: "mt-0",
|
|
383
|
+
title: req.__(
|
|
384
|
+
`%s view - %s on %s`,
|
|
385
|
+
viewname,
|
|
386
|
+
viewrow.viewtemplate,
|
|
387
|
+
viewrow.table_name
|
|
388
|
+
),
|
|
382
389
|
contents: renderForm(form, req.csrfToken()),
|
|
383
390
|
},
|
|
384
391
|
],
|
|
@@ -415,6 +422,7 @@ router.get(
|
|
|
415
422
|
},
|
|
416
423
|
{
|
|
417
424
|
type: "card",
|
|
425
|
+
class: "mt-0",
|
|
418
426
|
title: req.__(`Create view`),
|
|
419
427
|
contents: renderForm(form, req.csrfToken()),
|
|
420
428
|
},
|
|
@@ -452,6 +460,7 @@ router.post(
|
|
|
452
460
|
},
|
|
453
461
|
{
|
|
454
462
|
type: "card",
|
|
463
|
+
class: "mt-0",
|
|
455
464
|
title: req.__(`Edit view`),
|
|
456
465
|
contents: renderForm(form, req.csrfToken()),
|
|
457
466
|
},
|
|
@@ -527,6 +536,7 @@ const respondWorkflow = (view, wf, wfres, req, res) => {
|
|
|
527
536
|
},
|
|
528
537
|
{
|
|
529
538
|
type: noCard ? "container" : "card",
|
|
539
|
+
class: !noCard && "mt-0",
|
|
530
540
|
title: wfres.title,
|
|
531
541
|
contents,
|
|
532
542
|
},
|