@saltcorn/server 0.7.3-beta.3 → 0.7.3-beta.6
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 +4 -1
- package/markup/admin.js +1 -0
- package/package.json +8 -8
- package/public/gridedit.js +6 -0
- package/public/jquery-menu-editor.min.js +5 -2
- package/public/saltcorn-common.js +35 -3
- package/public/saltcorn.js +19 -2
- package/routes/admin.js +119 -10
- package/routes/fields.js +3 -0
- package/routes/files.js +5 -3
- package/routes/homepage.js +7 -7
- package/routes/pageedit.js +1 -0
- package/routes/plugins.js +1 -0
- package/routes/tables.js +7 -6
- package/routes/viewedit.js +5 -0
- package/tests/tenant.test.js +6 -0
package/app.js
CHANGED
|
@@ -227,7 +227,11 @@ const getApp = async (opts = {}) => {
|
|
|
227
227
|
passport.use(
|
|
228
228
|
new JwtStrategy(jwtOpts, (jwt_payload, done) => {
|
|
229
229
|
User.findOne({ email: jwt_payload.sub }).then((u) => {
|
|
230
|
-
if (
|
|
230
|
+
if (
|
|
231
|
+
u &&
|
|
232
|
+
u.last_mobile_login &&
|
|
233
|
+
u.last_mobile_login <= jwt_payload.iat
|
|
234
|
+
) {
|
|
231
235
|
return done(null, {
|
|
232
236
|
email: u.email,
|
|
233
237
|
id: u.id,
|
package/auth/routes.js
CHANGED
|
@@ -203,6 +203,7 @@ const loginWithJwt = async (req, res) => {
|
|
|
203
203
|
const { email, password } = req.query;
|
|
204
204
|
const user = await User.findOne({ email });
|
|
205
205
|
if (user && user.checkPassword(password)) {
|
|
206
|
+
const now = new Date().valueOf();
|
|
206
207
|
const jwt_secret = db.connectObj.jwt_secret;
|
|
207
208
|
const token = jwt.sign(
|
|
208
209
|
{
|
|
@@ -210,9 +211,11 @@ const loginWithJwt = async (req, res) => {
|
|
|
210
211
|
role_id: user.role_id,
|
|
211
212
|
iss: "saltcorn@saltcorn",
|
|
212
213
|
aud: "saltcorn-mobile-app",
|
|
214
|
+
iat: now,
|
|
213
215
|
},
|
|
214
216
|
jwt_secret
|
|
215
217
|
);
|
|
218
|
+
if (!user.last_mobile_login) user.updateLastMobileLogin(now);
|
|
216
219
|
res.json(token);
|
|
217
220
|
}
|
|
218
221
|
};
|
|
@@ -249,18 +252,24 @@ router.get(
|
|
|
249
252
|
* @function
|
|
250
253
|
* @memberof module:auth/routes~routesRouter
|
|
251
254
|
*/
|
|
252
|
-
router.get("/logout", (req, res, next) => {
|
|
253
|
-
req.
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
255
|
+
router.get("/logout", async (req, res, next) => {
|
|
256
|
+
if (req.smr && req.user?.id) {
|
|
257
|
+
const user = await User.findOne({ id: req.user.id });
|
|
258
|
+
await user.updateLastMobileLogin(null);
|
|
259
|
+
res.json({ success: true });
|
|
260
|
+
} else if (req.logout) {
|
|
261
|
+
req.logout();
|
|
262
|
+
if (req.session.destroy)
|
|
263
|
+
req.session.destroy((err) => {
|
|
264
|
+
if (err) return next(err);
|
|
265
|
+
req.logout();
|
|
266
|
+
res.redirect("/auth/login");
|
|
267
|
+
});
|
|
268
|
+
else {
|
|
257
269
|
req.logout();
|
|
270
|
+
req.session = null;
|
|
258
271
|
res.redirect("/auth/login");
|
|
259
|
-
}
|
|
260
|
-
else {
|
|
261
|
-
req.logout();
|
|
262
|
-
req.session = null;
|
|
263
|
-
res.redirect("/auth/login");
|
|
272
|
+
}
|
|
264
273
|
}
|
|
265
274
|
});
|
|
266
275
|
|
|
@@ -978,6 +987,11 @@ router.post(
|
|
|
978
987
|
}
|
|
979
988
|
Trigger.emitEvent("Login", null, req.user);
|
|
980
989
|
req.flash("success", req.__("Welcome, %s!", req.user.email));
|
|
990
|
+
if (req.smr) {
|
|
991
|
+
const dbUser = await User.findOne({ id: req.user.id });
|
|
992
|
+
if (!dbUser.last_mobile_login)
|
|
993
|
+
await dbUser.updateLastMobileLogin(new Date());
|
|
994
|
+
}
|
|
981
995
|
if (getState().get2FApolicy(req.user) === "Mandatory") {
|
|
982
996
|
res.redirect("/auth/twofa/setup/totp");
|
|
983
997
|
} else res.redirect("/");
|
|
@@ -1010,6 +1024,17 @@ router.get(
|
|
|
1010
1024
|
})
|
|
1011
1025
|
);
|
|
1012
1026
|
|
|
1027
|
+
/*
|
|
1028
|
+
returns if 'req.user' is an authenticated user
|
|
1029
|
+
*/
|
|
1030
|
+
router.get(
|
|
1031
|
+
"/authenticated",
|
|
1032
|
+
error_catcher((req, res, next) => {
|
|
1033
|
+
const isAuth = req.user && req.user.id ? true : false;
|
|
1034
|
+
res.json({ authenticated: isAuth });
|
|
1035
|
+
})
|
|
1036
|
+
);
|
|
1037
|
+
|
|
1013
1038
|
/**
|
|
1014
1039
|
* @name post/login-with/:method
|
|
1015
1040
|
* @function
|
|
@@ -1195,6 +1220,7 @@ const userSettings = async ({ req, res, pwform, user }) => {
|
|
|
1195
1220
|
? [
|
|
1196
1221
|
{
|
|
1197
1222
|
type: "card",
|
|
1223
|
+
class: "mt-0",
|
|
1198
1224
|
title: userSetsName,
|
|
1199
1225
|
contents: usersets,
|
|
1200
1226
|
},
|
|
@@ -1203,6 +1229,7 @@ const userSettings = async ({ req, res, pwform, user }) => {
|
|
|
1203
1229
|
{
|
|
1204
1230
|
type: "card",
|
|
1205
1231
|
title: req.__("User"),
|
|
1232
|
+
class: !usersets && "mt-0",
|
|
1206
1233
|
contents: table(
|
|
1207
1234
|
tbody(
|
|
1208
1235
|
tr(
|
package/locales/en.json
CHANGED
|
@@ -916,5 +916,8 @@
|
|
|
916
916
|
"Delete old backup files in this directory after the set number of days": "Delete old backup files in this directory after the set number of days",
|
|
917
917
|
"Mobile app": "Mobile app",
|
|
918
918
|
"Build mobile app": "Build mobile app",
|
|
919
|
-
"Build Result": "Build Result"
|
|
919
|
+
"Build Result": "Build Result",
|
|
920
|
+
"Download automated backup": "Download automated backup",
|
|
921
|
+
"Restoring automated backup": "Restoring automated backup",
|
|
922
|
+
"No errors detected during configuration check": "No errors detected during configuration check"
|
|
920
923
|
}
|
package/markup/admin.js
CHANGED
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/server",
|
|
3
|
-
"version": "0.7.3-beta.
|
|
3
|
+
"version": "0.7.3-beta.6",
|
|
4
4
|
"description": "Server app for Saltcorn, open-source no-code platform",
|
|
5
5
|
"homepage": "https://saltcorn.com",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@saltcorn/base-plugin": "0.7.3-beta.
|
|
10
|
-
"@saltcorn/builder": "0.7.3-beta.
|
|
11
|
-
"@saltcorn/data": "0.7.3-beta.
|
|
12
|
-
"@saltcorn/admin-models": "0.7.3-beta.
|
|
13
|
-
"@saltcorn/markup": "0.7.3-beta.
|
|
14
|
-
"@saltcorn/sbadmin2": "0.7.3-beta.
|
|
9
|
+
"@saltcorn/base-plugin": "0.7.3-beta.6",
|
|
10
|
+
"@saltcorn/builder": "0.7.3-beta.6",
|
|
11
|
+
"@saltcorn/data": "0.7.3-beta.6",
|
|
12
|
+
"@saltcorn/admin-models": "0.7.3-beta.6",
|
|
13
|
+
"@saltcorn/markup": "0.7.3-beta.6",
|
|
14
|
+
"@saltcorn/sbadmin2": "0.7.3-beta.6",
|
|
15
15
|
"@socket.io/cluster-adapter": "^0.1.0",
|
|
16
16
|
"@socket.io/sticky": "^1.0.1",
|
|
17
17
|
"aws-sdk": "^2.1037.0",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"pg": "^8.2.1",
|
|
49
49
|
"pluralize": "^8.0.0",
|
|
50
50
|
"qrcode": "1.5.0",
|
|
51
|
-
"resize-with-sharp-or-jimp": "0.1.
|
|
51
|
+
"resize-with-sharp-or-jimp": "0.1.5",
|
|
52
52
|
"socket.io": "4.2.0",
|
|
53
53
|
"thirty-two": "1.0.2",
|
|
54
54
|
"tmp-promise": "^3.0.2",
|
package/public/gridedit.js
CHANGED
|
@@ -76,6 +76,8 @@ function isoDateTimeFormatter(cell, formatterParams, onRendered) {
|
|
|
76
76
|
function isoDateFormatter(cell, formatterParams, onRendered) {
|
|
77
77
|
const val = cell.getValue();
|
|
78
78
|
if (!val) return "";
|
|
79
|
+
if (formatterParams && formatterParams.format)
|
|
80
|
+
return moment(val).format(formatterParams.format);
|
|
79
81
|
|
|
80
82
|
return new Date(val).toLocaleDateString(window.detected_locale || "en");
|
|
81
83
|
}
|
|
@@ -144,6 +146,10 @@ function add_tabulator_row() {
|
|
|
144
146
|
}
|
|
145
147
|
|
|
146
148
|
function delete_tabulator_row(e, cell) {
|
|
149
|
+
const def = cell.getColumn().getDefinition();
|
|
150
|
+
if (def && def.formatterParams && def.formatterParams.confirm) {
|
|
151
|
+
if (!confirm("Are you sure you want to delete this row?")) return;
|
|
152
|
+
}
|
|
147
153
|
const row = cell.getRow().getData();
|
|
148
154
|
if (!row.id) {
|
|
149
155
|
cell.getRow().delete();
|
|
@@ -117,7 +117,7 @@ function MenuEditor(e, t) {
|
|
|
117
117
|
(n.prev("div").children(".sortableListsOpener").first().remove(),
|
|
118
118
|
n.remove()),
|
|
119
119
|
MenuEditor.updateButtons(s);
|
|
120
|
-
|
|
120
|
+
l.onUpdate();
|
|
121
121
|
}
|
|
122
122
|
}),
|
|
123
123
|
$(document).on("click", ".btnEdit", function (e) {
|
|
@@ -132,6 +132,9 @@ function MenuEditor(e, t) {
|
|
|
132
132
|
"checked",
|
|
133
133
|
true
|
|
134
134
|
);
|
|
135
|
+
} else if (el.prop("tagName") == "SELECT") {
|
|
136
|
+
el.val(t);
|
|
137
|
+
el.attr("data-selected", t);
|
|
135
138
|
} else el.val(t);
|
|
136
139
|
}),
|
|
137
140
|
i.find(".item-menu").first().focus(),
|
|
@@ -164,7 +167,7 @@ function MenuEditor(e, t) {
|
|
|
164
167
|
t.remove()),
|
|
165
168
|
MenuEditor.updateButtons(s),
|
|
166
169
|
s.updateLevels();
|
|
167
|
-
|
|
170
|
+
l.onUpdate();
|
|
168
171
|
}),
|
|
169
172
|
s.on("click", ".btnIn", function (e) {
|
|
170
173
|
e.preventDefault();
|
|
@@ -63,8 +63,8 @@ function apply_showif() {
|
|
|
63
63
|
.val();
|
|
64
64
|
|
|
65
65
|
var options = data[1][val];
|
|
66
|
-
var current = e.attr("data-selected");
|
|
67
|
-
//console.log({
|
|
66
|
+
var current = e.attr("data-selected") || e.val();
|
|
67
|
+
//console.log({ field: e.attr("name"), target: data[0], val, current });
|
|
68
68
|
e.empty();
|
|
69
69
|
(options || []).forEach((o) => {
|
|
70
70
|
if (
|
|
@@ -87,6 +87,39 @@ function apply_showif() {
|
|
|
87
87
|
e.attr("data-selected", ec.target.value);
|
|
88
88
|
});
|
|
89
89
|
});
|
|
90
|
+
$("[data-fetch-options]").each(function (ix, element) {
|
|
91
|
+
const e = $(element);
|
|
92
|
+
const rec = get_form_record(e);
|
|
93
|
+
const dynwhere = JSON.parse(
|
|
94
|
+
decodeURIComponent(e.attr("data-fetch-options"))
|
|
95
|
+
);
|
|
96
|
+
//console.log(dynwhere);
|
|
97
|
+
const qs = Object.entries(dynwhere.whereParsed)
|
|
98
|
+
.map(([k, v]) => `${k}=${v[0] === "$" ? rec[v.substring(1)] : v}`)
|
|
99
|
+
.join("&");
|
|
100
|
+
var current = e.attr("data-selected");
|
|
101
|
+
e.change(function (ec) {
|
|
102
|
+
e.attr("data-selected", ec.target.value);
|
|
103
|
+
});
|
|
104
|
+
$.ajax(`/api/${dynwhere.table}?${qs}`).then((resp) => {
|
|
105
|
+
if (resp.success) {
|
|
106
|
+
e.empty();
|
|
107
|
+
if (!dynwhere.required) e.append($(`<option></option>`));
|
|
108
|
+
resp.success.forEach((r) => {
|
|
109
|
+
e.append(
|
|
110
|
+
$(
|
|
111
|
+
`<option ${
|
|
112
|
+
`${current}` === `${r[dynwhere.refname]}` ? "selected" : ""
|
|
113
|
+
} value="${r[dynwhere.refname]}">${
|
|
114
|
+
r[dynwhere.summary_field]
|
|
115
|
+
}</option>`
|
|
116
|
+
)
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
90
123
|
$("[data-source-url]").each(function (ix, element) {
|
|
91
124
|
const e = $(element);
|
|
92
125
|
const rec = get_form_record(e);
|
|
@@ -631,4 +664,3 @@ function cancel_form(form) {
|
|
|
631
664
|
$(form).append(`<input type="hidden" name="_cancel" value="on">`);
|
|
632
665
|
$(form).submit();
|
|
633
666
|
}
|
|
634
|
-
|
package/public/saltcorn.js
CHANGED
|
@@ -169,7 +169,7 @@ function close_saltcorn_modal() {
|
|
|
169
169
|
if (modal) modal.dispose();
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
function
|
|
172
|
+
function ensure_modal_exists_and_closed() {
|
|
173
173
|
if ($("#scmodal").length === 0) {
|
|
174
174
|
$("body").append(`<div id="scmodal", class="modal">
|
|
175
175
|
<div class="modal-dialog">
|
|
@@ -188,6 +188,19 @@ function ajax_modal(url, opts = {}) {
|
|
|
188
188
|
} else if ($("#scmodal").hasClass("show")) {
|
|
189
189
|
close_saltcorn_modal();
|
|
190
190
|
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function expand_thumbnail(img_id, filename) {
|
|
194
|
+
ensure_modal_exists_and_closed();
|
|
195
|
+
$("#scmodal .modal-body").html(
|
|
196
|
+
`<img src="/files/serve/${img_id}" style="width: 100%">`
|
|
197
|
+
);
|
|
198
|
+
$("#scmodal .modal-title").html(decodeURIComponent(filename));
|
|
199
|
+
new bootstrap.Modal($("#scmodal")).show();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function ajax_modal(url, opts = {}) {
|
|
203
|
+
ensure_modal_exists_and_closed();
|
|
191
204
|
if (opts.submitReload === false) $("#scmodal").addClass("no-submit-reload");
|
|
192
205
|
else $("#scmodal").removeClass("no-submit-reload");
|
|
193
206
|
$.ajax(url, {
|
|
@@ -296,7 +309,11 @@ function ajax_post(url, args) {
|
|
|
296
309
|
"CSRF-Token": _sc_globalCsrf,
|
|
297
310
|
},
|
|
298
311
|
...(args || {}),
|
|
299
|
-
})
|
|
312
|
+
})
|
|
313
|
+
.done(ajax_done)
|
|
314
|
+
.fail((e) =>
|
|
315
|
+
ajax_done(e.responseJSON || { error: "Unknown error: " + e.responseText })
|
|
316
|
+
);
|
|
300
317
|
}
|
|
301
318
|
function ajax_post_btn(e, reload_on_done, reload_delay) {
|
|
302
319
|
var form = $(e).closest("form");
|
package/routes/admin.js
CHANGED
|
@@ -43,6 +43,9 @@ const {
|
|
|
43
43
|
option,
|
|
44
44
|
fieldset,
|
|
45
45
|
legend,
|
|
46
|
+
ul,
|
|
47
|
+
li,
|
|
48
|
+
ol,
|
|
46
49
|
} = require("@saltcorn/markup/tags");
|
|
47
50
|
const db = require("@saltcorn/data/db");
|
|
48
51
|
const {
|
|
@@ -370,7 +373,13 @@ router.get(
|
|
|
370
373
|
? {
|
|
371
374
|
type: "card",
|
|
372
375
|
title: req.__("Automated backup"),
|
|
373
|
-
contents: div(
|
|
376
|
+
contents: div(
|
|
377
|
+
renderForm(backupForm, req.csrfToken()),
|
|
378
|
+
a(
|
|
379
|
+
{ href: "/admin/auto-backup-list" },
|
|
380
|
+
"Restore/download automated backups »"
|
|
381
|
+
)
|
|
382
|
+
),
|
|
374
383
|
}
|
|
375
384
|
: { type: "blank", contents: "" },
|
|
376
385
|
],
|
|
@@ -379,6 +388,93 @@ router.get(
|
|
|
379
388
|
})
|
|
380
389
|
);
|
|
381
390
|
|
|
391
|
+
/**
|
|
392
|
+
* @name get/backup
|
|
393
|
+
* @function
|
|
394
|
+
* @memberof module:routes/admin~routes/adminRouter
|
|
395
|
+
*/
|
|
396
|
+
router.get(
|
|
397
|
+
"/auto-backup-list",
|
|
398
|
+
isAdmin,
|
|
399
|
+
error_catcher(async (req, res) => {
|
|
400
|
+
const isRoot = db.getTenantSchema() === db.connectObj.default_schema;
|
|
401
|
+
if (!isRoot) {
|
|
402
|
+
res.redirect("/admin/backup");
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
const auto_backup_directory = getState().getConfig("auto_backup_directory");
|
|
406
|
+
const fileNms = await fs.promises.readdir(auto_backup_directory);
|
|
407
|
+
const backupFiles = fileNms.filter(
|
|
408
|
+
(fnm) => fnm.startsWith("sc-backup") && fnm.endsWith(".zip")
|
|
409
|
+
);
|
|
410
|
+
send_admin_page({
|
|
411
|
+
res,
|
|
412
|
+
req,
|
|
413
|
+
active_sub: "Backup",
|
|
414
|
+
contents: {
|
|
415
|
+
above: [
|
|
416
|
+
{
|
|
417
|
+
type: "card",
|
|
418
|
+
title: req.__("Download automated backup"),
|
|
419
|
+
contents: div(
|
|
420
|
+
ul(
|
|
421
|
+
backupFiles.map((fnm) =>
|
|
422
|
+
li(
|
|
423
|
+
a(
|
|
424
|
+
{
|
|
425
|
+
href: `/admin/auto-backup-download/${encodeURIComponent(
|
|
426
|
+
fnm
|
|
427
|
+
)}`,
|
|
428
|
+
},
|
|
429
|
+
fnm
|
|
430
|
+
)
|
|
431
|
+
)
|
|
432
|
+
)
|
|
433
|
+
)
|
|
434
|
+
),
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
type: "card",
|
|
438
|
+
title: req.__("Restoring automated backup"),
|
|
439
|
+
contents: div(
|
|
440
|
+
ol(
|
|
441
|
+
li("Download one of the backups above"),
|
|
442
|
+
li(
|
|
443
|
+
a({ href: "/admin/clear-all" }, "Clear this application"),
|
|
444
|
+
" ",
|
|
445
|
+
"(tick all boxes)"
|
|
446
|
+
),
|
|
447
|
+
li(
|
|
448
|
+
"When prompted to create the first user, click the link to restore a backup"
|
|
449
|
+
),
|
|
450
|
+
li("Select the downloaded backup file")
|
|
451
|
+
)
|
|
452
|
+
),
|
|
453
|
+
},
|
|
454
|
+
],
|
|
455
|
+
},
|
|
456
|
+
});
|
|
457
|
+
})
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
router.get(
|
|
461
|
+
"/auto-backup-download/:filename",
|
|
462
|
+
isAdmin,
|
|
463
|
+
error_catcher(async (req, res) => {
|
|
464
|
+
const { filename } = req.params;
|
|
465
|
+
const isRoot = db.getTenantSchema() === db.connectObj.default_schema;
|
|
466
|
+
if (
|
|
467
|
+
!isRoot ||
|
|
468
|
+
!(filename.startsWith("sc-backup") && filename.endsWith(".zip"))
|
|
469
|
+
) {
|
|
470
|
+
res.redirect("/admin/backup");
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
const auto_backup_directory = getState().getConfig("auto_backup_directory");
|
|
474
|
+
res.download(path.join(auto_backup_directory, filename), filename);
|
|
475
|
+
})
|
|
476
|
+
);
|
|
477
|
+
|
|
382
478
|
/**
|
|
383
479
|
* Auto backup Form
|
|
384
480
|
* @param {object} req
|
|
@@ -1141,15 +1237,23 @@ router.post(
|
|
|
1141
1237
|
"error",
|
|
1142
1238
|
req.__("Please select at least one platform (android or iOS).")
|
|
1143
1239
|
);
|
|
1144
|
-
return res.redirect("/admin/
|
|
1240
|
+
return res.redirect("/admin/build-mobile-app");
|
|
1145
1241
|
}
|
|
1146
1242
|
if (!androidPlatform && useDocker) {
|
|
1147
1243
|
req.flash("error", req.__("Only the android build supports docker."));
|
|
1148
|
-
return res.redirect("/admin/
|
|
1244
|
+
return res.redirect("/admin/build-mobile-app");
|
|
1149
1245
|
}
|
|
1150
1246
|
if (appFile && !appFile.endsWith(".apk")) appFile = `${appFile}.apk`;
|
|
1151
1247
|
const appOut = path.join(__dirname, "..", "mobile-app-out");
|
|
1152
|
-
const spawnParams = [
|
|
1248
|
+
const spawnParams = [
|
|
1249
|
+
"build-app",
|
|
1250
|
+
"-v",
|
|
1251
|
+
entryView,
|
|
1252
|
+
"-c",
|
|
1253
|
+
appOut,
|
|
1254
|
+
"-b",
|
|
1255
|
+
"/tmp/mobile_app_build",
|
|
1256
|
+
];
|
|
1153
1257
|
if (useDocker) spawnParams.push("-d");
|
|
1154
1258
|
if (androidPlatform) spawnParams.push("-p", "android");
|
|
1155
1259
|
if (iOSPlatform) spawnParams.push("-p", "ios");
|
|
@@ -1164,8 +1268,8 @@ router.post(
|
|
|
1164
1268
|
// console.log(data.toString());
|
|
1165
1269
|
childOutputs.push(data.toString());
|
|
1166
1270
|
});
|
|
1167
|
-
child.on("exit", async function (
|
|
1168
|
-
if (
|
|
1271
|
+
child.on("exit", async function (exitCode, signal) {
|
|
1272
|
+
if (exitCode === 0) {
|
|
1169
1273
|
const file = await File.from_existing_file(
|
|
1170
1274
|
appOut,
|
|
1171
1275
|
appFile ? appFile : "app-debug.apk",
|
|
@@ -1187,9 +1291,11 @@ router.post(
|
|
|
1187
1291
|
{
|
|
1188
1292
|
type: "card",
|
|
1189
1293
|
title: req.__("Build Result"),
|
|
1190
|
-
contents: div(
|
|
1294
|
+
contents: div(
|
|
1295
|
+
"Unable to build the app:",
|
|
1296
|
+
pre(code(childOutputs.join("<br/>")))
|
|
1297
|
+
),
|
|
1191
1298
|
},
|
|
1192
|
-
childOutputs.join("<br/>"),
|
|
1193
1299
|
],
|
|
1194
1300
|
});
|
|
1195
1301
|
});
|
|
@@ -1201,9 +1307,12 @@ router.post(
|
|
|
1201
1307
|
{
|
|
1202
1308
|
type: "card",
|
|
1203
1309
|
title: req.__("Build Result"),
|
|
1204
|
-
contents: div(
|
|
1310
|
+
contents: div(
|
|
1311
|
+
p("Unable to build the app:"),
|
|
1312
|
+
pre(code(message)),
|
|
1313
|
+
pre(code(stack))
|
|
1314
|
+
),
|
|
1205
1315
|
},
|
|
1206
|
-
`${message} <br/> ${stack}`,
|
|
1207
1316
|
],
|
|
1208
1317
|
});
|
|
1209
1318
|
});
|
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/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,6 +379,7 @@ router.get(
|
|
|
378
379
|
},
|
|
379
380
|
{
|
|
380
381
|
type: "card",
|
|
382
|
+
class: "mt-0",
|
|
381
383
|
title: req.__(`Edit %s view`, viewname),
|
|
382
384
|
contents: renderForm(form, req.csrfToken()),
|
|
383
385
|
},
|
|
@@ -415,6 +417,7 @@ router.get(
|
|
|
415
417
|
},
|
|
416
418
|
{
|
|
417
419
|
type: "card",
|
|
420
|
+
class: "mt-0",
|
|
418
421
|
title: req.__(`Create view`),
|
|
419
422
|
contents: renderForm(form, req.csrfToken()),
|
|
420
423
|
},
|
|
@@ -452,6 +455,7 @@ router.post(
|
|
|
452
455
|
},
|
|
453
456
|
{
|
|
454
457
|
type: "card",
|
|
458
|
+
class: "mt-0",
|
|
455
459
|
title: req.__(`Edit view`),
|
|
456
460
|
contents: renderForm(form, req.csrfToken()),
|
|
457
461
|
},
|
|
@@ -527,6 +531,7 @@ const respondWorkflow = (view, wf, wfres, req, res) => {
|
|
|
527
531
|
},
|
|
528
532
|
{
|
|
529
533
|
type: noCard ? "container" : "card",
|
|
534
|
+
class: !noCard && "mt-0",
|
|
530
535
|
title: wfres.title,
|
|
531
536
|
contents,
|
|
532
537
|
},
|
package/tests/tenant.test.js
CHANGED
|
@@ -10,6 +10,7 @@ const {
|
|
|
10
10
|
toInclude,
|
|
11
11
|
toNotInclude,
|
|
12
12
|
} = require("../auth/testhelp");
|
|
13
|
+
const { getState } = require("@saltcorn/data/db/state");
|
|
13
14
|
|
|
14
15
|
afterAll(db.close);
|
|
15
16
|
|
|
@@ -24,6 +25,8 @@ describe("tenant routes", () => {
|
|
|
24
25
|
if (!db.isSQLite) {
|
|
25
26
|
it("shows create form", async () => {
|
|
26
27
|
db.enable_multi_tenant();
|
|
28
|
+
await getState().setConfig("role_to_create_tenant", "10");
|
|
29
|
+
|
|
27
30
|
const app = await getApp({ disableCsrf: true });
|
|
28
31
|
await request(app).get("/tenant/create").expect(toInclude("subdomain"));
|
|
29
32
|
});
|
|
@@ -37,6 +40,8 @@ describe("tenant routes", () => {
|
|
|
37
40
|
});
|
|
38
41
|
it("creates tenant with capital letter", async () => {
|
|
39
42
|
db.enable_multi_tenant();
|
|
43
|
+
await getState().setConfig("role_to_create_tenant", "10");
|
|
44
|
+
|
|
40
45
|
const app = await getApp({ disableCsrf: true });
|
|
41
46
|
await request(app)
|
|
42
47
|
.post("/tenant/create")
|
|
@@ -46,6 +51,7 @@ describe("tenant routes", () => {
|
|
|
46
51
|
});
|
|
47
52
|
it("rejects existing tenant", async () => {
|
|
48
53
|
db.enable_multi_tenant();
|
|
54
|
+
await getState().setConfig("role_to_create_tenant", "10");
|
|
49
55
|
const app = await getApp({ disableCsrf: true });
|
|
50
56
|
await request(app)
|
|
51
57
|
.post("/tenant/create")
|