@saltcorn/server 0.9.1-beta.0 → 0.9.1-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/auth/admin.js +1 -1
- package/auth/routes.js +4 -6
- package/help/API actions.tmd +20 -1
- package/help/Event types.tmd +78 -0
- package/help/Extra state formula.tmd +12 -0
- package/help/JavaScript action code.tmd +1 -14
- package/locales/en.json +11 -1
- package/package.json +13 -13
- package/public/saltcorn-common.js +135 -54
- package/public/saltcorn.css +1 -0
- package/public/saltcorn.js +2 -11
- package/routes/actions.js +2 -1
- package/routes/api.js +10 -2
- package/routes/fields.js +28 -1
- package/routes/homepage.js +86 -12
- package/routes/page.js +27 -21
- package/routes/pageedit.js +185 -26
- package/routes/utils.js +36 -0
- package/routes/view.js +8 -7
- package/s3storage.js +6 -5
- package/tests/fields.test.js +37 -1
- package/tests/page.test.js +123 -1
- package/tests/table.test.js +5 -1
- package/tests/view.test.js +97 -0
- package/wrapper.js +8 -1
package/routes/fields.js
CHANGED
|
@@ -996,6 +996,30 @@ router.post(
|
|
|
996
996
|
result = row[field.name];
|
|
997
997
|
} else if (field.stored) {
|
|
998
998
|
const f = get_async_expression_function(formula, fields);
|
|
999
|
+
//are there join fields in formula?
|
|
1000
|
+
const joinFields = {};
|
|
1001
|
+
add_free_variables_to_joinfields(
|
|
1002
|
+
freeVariables(formula),
|
|
1003
|
+
joinFields,
|
|
1004
|
+
table.fields
|
|
1005
|
+
);
|
|
1006
|
+
for (const { target, ref, through, rename_object } of Object.values(
|
|
1007
|
+
joinFields
|
|
1008
|
+
)) {
|
|
1009
|
+
const jf = table.getField(ref);
|
|
1010
|
+
const jtable = Table.findOne(jf.reftable_name);
|
|
1011
|
+
const jrow = await jtable.getRow({ [jtable.pk_name]: row[ref] });
|
|
1012
|
+
row[ref] = jrow;
|
|
1013
|
+
if (through) {
|
|
1014
|
+
const jf2 = jtable.getField(through);
|
|
1015
|
+
const jtable2 = Table.findOne(jf2.reftable_name);
|
|
1016
|
+
const jrow2 = await jtable2.getRow({
|
|
1017
|
+
[jtable2.pk_name]: jrow[through],
|
|
1018
|
+
});
|
|
1019
|
+
row[ref][through] = jrow2;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
|
|
999
1023
|
result = await f(row);
|
|
1000
1024
|
} else {
|
|
1001
1025
|
const f = get_expression_function(formula, fields);
|
|
@@ -1145,7 +1169,10 @@ router.post(
|
|
|
1145
1169
|
}
|
|
1146
1170
|
|
|
1147
1171
|
const field = table.getField(fieldName);
|
|
1148
|
-
|
|
1172
|
+
if (!field) {
|
|
1173
|
+
res.send("");
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1149
1176
|
const fieldViewConfigForms = await calcfldViewConfig([field], false, 0);
|
|
1150
1177
|
const formFields = fieldViewConfigForms[field.name][fv_name];
|
|
1151
1178
|
if (!formFields) {
|
package/routes/homepage.js
CHANGED
|
@@ -12,8 +12,9 @@ const View = require("@saltcorn/data/models/view");
|
|
|
12
12
|
const User = require("@saltcorn/data/models/user");
|
|
13
13
|
const File = require("@saltcorn/data/models/file");
|
|
14
14
|
const Page = require("@saltcorn/data/models/page");
|
|
15
|
+
const Plugin = require("@saltcorn/data/models/plugin");
|
|
15
16
|
const { link, mkTable } = require("@saltcorn/markup");
|
|
16
|
-
const { div, a, p, i } = require("@saltcorn/markup/tags");
|
|
17
|
+
const { div, a, p, i, h5, span } = require("@saltcorn/markup/tags");
|
|
17
18
|
const Table = require("@saltcorn/data/models/table");
|
|
18
19
|
const { get_cached_packs } = require("@saltcorn/admin-models/models/pack");
|
|
19
20
|
// const { restore_backup } = require("../markup/admin");
|
|
@@ -21,7 +22,7 @@ const { get_latest_npm_version } = require("@saltcorn/data/models/config");
|
|
|
21
22
|
const packagejson = require("../package.json");
|
|
22
23
|
const Trigger = require("@saltcorn/data/models/trigger");
|
|
23
24
|
const { fileUploadForm } = require("../markup/forms");
|
|
24
|
-
const { get_base_url } = require("./utils.js");
|
|
25
|
+
const { get_base_url, sendHtmlFile } = require("./utils.js");
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
28
|
* Tables List
|
|
@@ -310,10 +311,80 @@ const packTab = (req, packlist) =>
|
|
|
310
311
|
{ noHeader: true }
|
|
311
312
|
),
|
|
312
313
|
a(
|
|
313
|
-
{ href: `/plugins?set=packs`, class: "btn btn-primary" },
|
|
314
|
+
{ href: `/plugins?set=packs`, class: "btn btn-sm btn-primary" },
|
|
314
315
|
req.__("Go to pack store »")
|
|
315
316
|
)
|
|
316
317
|
);
|
|
318
|
+
|
|
319
|
+
const themeCard = (req, roleMap) => {
|
|
320
|
+
const state_layouts = getState().layouts;
|
|
321
|
+
const state_layout_names = Object.keys(state_layouts);
|
|
322
|
+
const layout_by_role = getState().getConfig("layout_by_role");
|
|
323
|
+
const used_layout_by_role = {};
|
|
324
|
+
Object.keys(roleMap).forEach((role_id) => {
|
|
325
|
+
used_layout_by_role[role_id] =
|
|
326
|
+
layout_by_role[role_id] ||
|
|
327
|
+
state_layout_names[state_layout_names.length - 1];
|
|
328
|
+
});
|
|
329
|
+
const themes_available = Plugin.get_cached_plugins().filter(
|
|
330
|
+
(p) => p.has_theme && !state_layout_names.includes(p.name)
|
|
331
|
+
);
|
|
332
|
+
const layouts = Object.entries(getState().layouts)
|
|
333
|
+
.filter(([nm, v]) => nm !== "emergency")
|
|
334
|
+
.map(([name, layout]) => {
|
|
335
|
+
let plugin = getState().plugins[name];
|
|
336
|
+
const for_role = Object.entries(used_layout_by_role)
|
|
337
|
+
.filter(([role, rname]) => rname === name)
|
|
338
|
+
.map(([role, rname]) =>
|
|
339
|
+
span({ class: "badge bg-info" }, roleMap[role])
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
return {
|
|
343
|
+
name,
|
|
344
|
+
layout,
|
|
345
|
+
plugin,
|
|
346
|
+
for_role,
|
|
347
|
+
edit_cfg_link: plugin?.configuration_workflow
|
|
348
|
+
? a(
|
|
349
|
+
{
|
|
350
|
+
href: `/plugins/configure/${encodeURIComponent(name)}`,
|
|
351
|
+
},
|
|
352
|
+
i({ class: "fa fa-cog ms-2" })
|
|
353
|
+
)
|
|
354
|
+
: "",
|
|
355
|
+
};
|
|
356
|
+
});
|
|
357
|
+
const show_installable = themes_available.length > 0 || layouts.length == 1;
|
|
358
|
+
return div(
|
|
359
|
+
{ class: "pb-3 pt-2 pe-4" },
|
|
360
|
+
mkTable(
|
|
361
|
+
[
|
|
362
|
+
{
|
|
363
|
+
label: req.__("Installed theme"),
|
|
364
|
+
key: ({ name, edit_cfg_link }) => `${name}${edit_cfg_link}`,
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
label: req.__("Theme for role"),
|
|
368
|
+
key: ({ for_role }) => for_role.join(" "),
|
|
369
|
+
},
|
|
370
|
+
],
|
|
371
|
+
layouts
|
|
372
|
+
),
|
|
373
|
+
a({ href: "/roleadmin" }, req.__("Set theme for each user role »")),
|
|
374
|
+
show_installable && h5({ class: "mt-2" }, req.__("Available themes")),
|
|
375
|
+
show_installable &&
|
|
376
|
+
div(
|
|
377
|
+
themes_available
|
|
378
|
+
.map((p) => span({ class: "badge bg-secondary" }, p.name))
|
|
379
|
+
.join(" ")
|
|
380
|
+
),
|
|
381
|
+
show_installable &&
|
|
382
|
+
a(
|
|
383
|
+
{ href: `/plugins?set=themes`, class: "mt-2" },
|
|
384
|
+
req.__("Install more themes »")
|
|
385
|
+
)
|
|
386
|
+
);
|
|
387
|
+
};
|
|
317
388
|
/**
|
|
318
389
|
* Help Card
|
|
319
390
|
* @param req
|
|
@@ -412,10 +483,12 @@ const welcome_page = async (req) => {
|
|
|
412
483
|
users.length > 4
|
|
413
484
|
? {
|
|
414
485
|
Users: await usersTab(req, users, roleMap),
|
|
486
|
+
Theme: themeCard(req, roleMap),
|
|
415
487
|
Help: helpCard(req),
|
|
416
488
|
}
|
|
417
489
|
: {
|
|
418
490
|
Help: helpCard(req),
|
|
491
|
+
Theme: themeCard(req, roleMap),
|
|
419
492
|
Users: await usersTab(req, users, roleMap),
|
|
420
493
|
},
|
|
421
494
|
},
|
|
@@ -480,15 +553,16 @@ const get_config_response = async (role_id, res, req) => {
|
|
|
480
553
|
|
|
481
554
|
if (db_page) {
|
|
482
555
|
const contents = await db_page.run(req.query, { res, req });
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
556
|
+
if (contents.html_file) await sendHtmlFile(req, res, contents.html_file);
|
|
557
|
+
else
|
|
558
|
+
res.sendWrap(
|
|
559
|
+
{
|
|
560
|
+
title: db_page.title,
|
|
561
|
+
description: db_page.description,
|
|
562
|
+
bodyClass: "page_" + db.sqlsanitize(homeCfg),
|
|
563
|
+
},
|
|
564
|
+
contents
|
|
565
|
+
);
|
|
492
566
|
} else res.redirect(homeCfg);
|
|
493
567
|
return true;
|
|
494
568
|
}
|
package/routes/page.js
CHANGED
|
@@ -8,12 +8,15 @@ const Router = require("express-promise-router");
|
|
|
8
8
|
|
|
9
9
|
const Page = require("@saltcorn/data/models/page");
|
|
10
10
|
const Trigger = require("@saltcorn/data/models/trigger");
|
|
11
|
+
const File = require("@saltcorn/data/models/file");
|
|
11
12
|
const { getState } = require("@saltcorn/data/db/state");
|
|
12
13
|
const {
|
|
13
14
|
error_catcher,
|
|
14
15
|
scan_for_page_title,
|
|
15
16
|
isAdmin,
|
|
17
|
+
sendHtmlFile,
|
|
16
18
|
} = require("../routes/utils.js");
|
|
19
|
+
const { isTest } = require("@saltcorn/data/utils");
|
|
17
20
|
const { add_edit_bar } = require("../markup/admin.js");
|
|
18
21
|
const { traverseSync } = require("@saltcorn/data/models/layout");
|
|
19
22
|
const { run_action_column } = require("@saltcorn/data/plugin-helper");
|
|
@@ -50,27 +53,30 @@ router.get(
|
|
|
50
53
|
const title = scan_for_page_title(contents, db_page.title);
|
|
51
54
|
const tock = new Date();
|
|
52
55
|
const ms = tock.getTime() - tic.getTime();
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
56
|
+
if (!isTest())
|
|
57
|
+
Trigger.emitEvent("PageLoad", null, req.user, {
|
|
58
|
+
text: req.__("Page '%s' was loaded", pagename),
|
|
59
|
+
type: "page",
|
|
60
|
+
name: pagename,
|
|
61
|
+
render_time: ms,
|
|
62
|
+
});
|
|
63
|
+
if (contents.html_file) await sendHtmlFile(req, res, contents.html_file);
|
|
64
|
+
else
|
|
65
|
+
res.sendWrap(
|
|
66
|
+
{
|
|
67
|
+
title,
|
|
68
|
+
description: db_page.description,
|
|
69
|
+
bodyClass: "page_" + db.sqlsanitize(pagename),
|
|
70
|
+
no_menu: db_page.attributes?.no_menu,
|
|
71
|
+
} || `${pagename} page`,
|
|
72
|
+
add_edit_bar({
|
|
73
|
+
role,
|
|
74
|
+
title: db_page.name,
|
|
75
|
+
what: req.__("Page"),
|
|
76
|
+
url: `/pageedit/edit/${encodeURIComponent(db_page.name)}`,
|
|
77
|
+
contents,
|
|
78
|
+
})
|
|
79
|
+
);
|
|
74
80
|
} else {
|
|
75
81
|
if (db_page && !req.user) {
|
|
76
82
|
res.redirect(`/auth/login?dest=${encodeURIComponent(req.originalUrl)}`);
|
package/routes/pageedit.js
CHANGED
|
@@ -9,7 +9,7 @@ const View = require("@saltcorn/data/models/view");
|
|
|
9
9
|
const Field = require("@saltcorn/data/models/field");
|
|
10
10
|
const Table = require("@saltcorn/data/models/table");
|
|
11
11
|
const Page = require("@saltcorn/data/models/page");
|
|
12
|
-
const { div, a } = require("@saltcorn/markup/tags");
|
|
12
|
+
const { div, a, iframe, script } = require("@saltcorn/markup/tags");
|
|
13
13
|
const { getState } = require("@saltcorn/data/db/state");
|
|
14
14
|
const User = require("@saltcorn/data/models/user");
|
|
15
15
|
const Workflow = require("@saltcorn/data/models/workflow");
|
|
@@ -27,6 +27,7 @@ const {
|
|
|
27
27
|
addOnDoneRedirect,
|
|
28
28
|
is_relative_url,
|
|
29
29
|
} = require("./utils.js");
|
|
30
|
+
const { asyncMap } = require("@saltcorn/data/utils");
|
|
30
31
|
const {
|
|
31
32
|
mkTable,
|
|
32
33
|
renderForm,
|
|
@@ -39,6 +40,8 @@ const {
|
|
|
39
40
|
} = require("@saltcorn/markup");
|
|
40
41
|
const { getActionConfigFields } = require("@saltcorn/data/plugin-helper");
|
|
41
42
|
const Library = require("@saltcorn/data/models/library");
|
|
43
|
+
const path = require("path");
|
|
44
|
+
const fsp = require("fs").promises;
|
|
42
45
|
|
|
43
46
|
/**
|
|
44
47
|
* @type {object}
|
|
@@ -58,6 +61,20 @@ module.exports = router;
|
|
|
58
61
|
const pagePropertiesForm = async (req, isNew) => {
|
|
59
62
|
const roles = await User.get_roles();
|
|
60
63
|
const pages = (await Page.find()).map((p) => p.name);
|
|
64
|
+
const htmlFiles = await File.find(
|
|
65
|
+
{
|
|
66
|
+
mime_super: "text",
|
|
67
|
+
mime_sub: "html",
|
|
68
|
+
},
|
|
69
|
+
{ recursive: true }
|
|
70
|
+
);
|
|
71
|
+
const htmlOptions = await asyncMap(htmlFiles, async (f) => {
|
|
72
|
+
return {
|
|
73
|
+
label: path.join(f.current_folder, f.filename),
|
|
74
|
+
value: File.absPathToServePath(f.location),
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
|
|
61
78
|
const form = new Form({
|
|
62
79
|
action: addOnDoneRedirect("/pageedit/edit-properties", req),
|
|
63
80
|
fields: [
|
|
@@ -92,6 +109,24 @@ const pagePropertiesForm = async (req, isNew) => {
|
|
|
92
109
|
input_type: "select",
|
|
93
110
|
options: roles.map((r) => ({ value: r.id, label: r.role })),
|
|
94
111
|
},
|
|
112
|
+
...(htmlOptions.length > 0
|
|
113
|
+
? [
|
|
114
|
+
{
|
|
115
|
+
name: "html_file",
|
|
116
|
+
label: req.__("HTML file"),
|
|
117
|
+
sublabel: req.__("HTML file to use as page content"),
|
|
118
|
+
input_type: "select",
|
|
119
|
+
|
|
120
|
+
options: [
|
|
121
|
+
{
|
|
122
|
+
label: req.__("None - use drag and drop builder"),
|
|
123
|
+
value: "",
|
|
124
|
+
},
|
|
125
|
+
...htmlOptions,
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
]
|
|
129
|
+
: []),
|
|
95
130
|
{
|
|
96
131
|
name: "no_menu",
|
|
97
132
|
label: req.__("No menu"),
|
|
@@ -367,23 +402,146 @@ router.post(
|
|
|
367
402
|
wrap(renderForm(form, req.csrfToken()), false, req)
|
|
368
403
|
);
|
|
369
404
|
} else {
|
|
370
|
-
const { id, columns, no_menu, ...pageRow } = form.values;
|
|
405
|
+
const { id, columns, no_menu, html_file, ...pageRow } = form.values;
|
|
371
406
|
pageRow.min_role = +pageRow.min_role;
|
|
372
407
|
pageRow.attributes = { no_menu };
|
|
408
|
+
if (html_file) {
|
|
409
|
+
pageRow.layout = {
|
|
410
|
+
html_file: html_file,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
373
413
|
if (+id) {
|
|
414
|
+
const dbPage = Page.findOne({ id: id });
|
|
415
|
+
if (dbPage.layout?.html_file && !html_file) {
|
|
416
|
+
pageRow.layout = {};
|
|
417
|
+
}
|
|
374
418
|
await Page.update(+id, pageRow);
|
|
375
419
|
res.redirect(`/pageedit/`);
|
|
376
420
|
} else {
|
|
377
|
-
if (!pageRow.fixed_states) pageRow.fixed_states = {};
|
|
378
421
|
if (!pageRow.layout) pageRow.layout = {};
|
|
422
|
+
if (!pageRow.fixed_states) pageRow.fixed_states = {};
|
|
379
423
|
await Page.create(pageRow);
|
|
380
|
-
|
|
424
|
+
if (!html_file)
|
|
425
|
+
res.redirect(
|
|
426
|
+
addOnDoneRedirect(`/pageedit/edit/${pageRow.name}`, req)
|
|
427
|
+
);
|
|
428
|
+
else res.redirect(`/pageedit/`);
|
|
381
429
|
}
|
|
382
430
|
}
|
|
383
431
|
})
|
|
384
432
|
);
|
|
385
433
|
|
|
386
434
|
/**
|
|
435
|
+
* open the builder
|
|
436
|
+
* @param {*} req
|
|
437
|
+
* @param {*} res
|
|
438
|
+
* @param {*} page
|
|
439
|
+
*/
|
|
440
|
+
const getEditNormalPage = async (req, res, page) => {
|
|
441
|
+
// set fixed states in page directly for legacy builds
|
|
442
|
+
traverseSync(page.layout, {
|
|
443
|
+
view(s) {
|
|
444
|
+
if (s.state === "fixed" && !s.configuration) {
|
|
445
|
+
const fs = page.fixed_states[s.name];
|
|
446
|
+
if (fs) s.configuration = fs;
|
|
447
|
+
}
|
|
448
|
+
},
|
|
449
|
+
});
|
|
450
|
+
const options = await pageBuilderData(req, page);
|
|
451
|
+
const builderData = {
|
|
452
|
+
options,
|
|
453
|
+
context: page,
|
|
454
|
+
layout: page.layout,
|
|
455
|
+
mode: "page",
|
|
456
|
+
version_tag: db.connectObj.version_tag,
|
|
457
|
+
};
|
|
458
|
+
res.sendWrap(
|
|
459
|
+
req.__(`%s configuration`, page.name),
|
|
460
|
+
wrap(renderBuilder(builderData, req.csrfToken()), true, req, page)
|
|
461
|
+
);
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* open a file editor with an iframe preview
|
|
466
|
+
* @param {*} req
|
|
467
|
+
* @param {*} res
|
|
468
|
+
* @param {*} page
|
|
469
|
+
*/
|
|
470
|
+
const getEditPageWithHtmlFile = async (req, res, page) => {
|
|
471
|
+
const htmlFile = page.html_file;
|
|
472
|
+
const iframeId = "page_preview_iframe";
|
|
473
|
+
const updateBttnId = "addnUpdBtn";
|
|
474
|
+
const file = await File.findOne(htmlFile);
|
|
475
|
+
if (!file) {
|
|
476
|
+
req.flash("error", req.__("File not found"));
|
|
477
|
+
return res.redirect(`/pageedit`);
|
|
478
|
+
}
|
|
479
|
+
const editForm = new Form({
|
|
480
|
+
action: `/pageedit/edit/${encodeURIComponent(page.name)}`,
|
|
481
|
+
fields: [
|
|
482
|
+
{
|
|
483
|
+
name: "code",
|
|
484
|
+
form_name: "code",
|
|
485
|
+
label: "Code",
|
|
486
|
+
input_type: "code",
|
|
487
|
+
attributes: { mode: "text/html" },
|
|
488
|
+
validator(s) {
|
|
489
|
+
return true;
|
|
490
|
+
},
|
|
491
|
+
},
|
|
492
|
+
],
|
|
493
|
+
values: {
|
|
494
|
+
code: await fsp.readFile(file.location, "utf8"),
|
|
495
|
+
},
|
|
496
|
+
onChange: `document.getElementById('${updateBttnId}').disabled = false;`,
|
|
497
|
+
additionalButtons: [
|
|
498
|
+
{
|
|
499
|
+
label: req.__("Update"),
|
|
500
|
+
id: updateBttnId,
|
|
501
|
+
class: "btn btn-primary",
|
|
502
|
+
onclick: `saveAndContinue(this, () => {
|
|
503
|
+
document.getElementById('${iframeId}').contentWindow.location.reload();
|
|
504
|
+
document.getElementById('${updateBttnId}').disabled = true;
|
|
505
|
+
})`,
|
|
506
|
+
disabled: true,
|
|
507
|
+
},
|
|
508
|
+
],
|
|
509
|
+
submitLabel: req.__("Finish") + " »",
|
|
510
|
+
});
|
|
511
|
+
res.sendWrap(req.__("Edit %s", page.title), {
|
|
512
|
+
above: [
|
|
513
|
+
{
|
|
514
|
+
type: "card",
|
|
515
|
+
title: "Edit",
|
|
516
|
+
titleAjaxIndicator: true,
|
|
517
|
+
contents: [renderForm(editForm, req.csrfToken())],
|
|
518
|
+
},
|
|
519
|
+
{
|
|
520
|
+
type: "card",
|
|
521
|
+
title: "Preview",
|
|
522
|
+
contents: [
|
|
523
|
+
iframe({
|
|
524
|
+
id: iframeId,
|
|
525
|
+
src: `/files/serve/${encodeURIComponent(htmlFile)}`,
|
|
526
|
+
}),
|
|
527
|
+
script(`
|
|
528
|
+
const iframe = document.getElementById("${iframeId}");
|
|
529
|
+
iframe.onload = () => {
|
|
530
|
+
const _iframe = document.getElementById("${iframeId}");
|
|
531
|
+
if (_iframe.contentWindow.document.body) {
|
|
532
|
+
_iframe.width = _iframe.contentWindow.document.body.scrollWidth;
|
|
533
|
+
_iframe.height = _iframe.contentWindow.document.body.scrollHeight;
|
|
534
|
+
}
|
|
535
|
+
}`),
|
|
536
|
+
],
|
|
537
|
+
},
|
|
538
|
+
],
|
|
539
|
+
});
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* for normal pages, open the builder
|
|
544
|
+
* for pages with a fixed html file, open a file editor with an iframe preview
|
|
387
545
|
* @name get/edit/:pagename
|
|
388
546
|
* @function
|
|
389
547
|
* @memberof module:routes/pageedit~pageeditRouter
|
|
@@ -399,27 +557,8 @@ router.get(
|
|
|
399
557
|
req.flash("error", req.__(`Page %s not found`, pagename));
|
|
400
558
|
res.redirect(`/pageedit`);
|
|
401
559
|
} else {
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
view(s) {
|
|
405
|
-
if (s.state === "fixed" && !s.configuration) {
|
|
406
|
-
const fs = page.fixed_states[s.name];
|
|
407
|
-
if (fs) s.configuration = fs;
|
|
408
|
-
}
|
|
409
|
-
},
|
|
410
|
-
});
|
|
411
|
-
const options = await pageBuilderData(req, page);
|
|
412
|
-
const builderData = {
|
|
413
|
-
options,
|
|
414
|
-
context: page,
|
|
415
|
-
layout: page.layout,
|
|
416
|
-
mode: "page",
|
|
417
|
-
version_tag: db.connectObj.version_tag,
|
|
418
|
-
};
|
|
419
|
-
res.sendWrap(
|
|
420
|
-
req.__(`%s configuration`, page.name),
|
|
421
|
-
wrap(renderBuilder(builderData, req.csrfToken()), true, req, page)
|
|
422
|
-
);
|
|
560
|
+
if (!page.html_file) await getEditNormalPage(req, res, page);
|
|
561
|
+
else await getEditPageWithHtmlFile(req, res, page);
|
|
423
562
|
}
|
|
424
563
|
})
|
|
425
564
|
);
|
|
@@ -448,13 +587,33 @@ router.post(
|
|
|
448
587
|
await Page.update(page.id, {
|
|
449
588
|
layout: decodeURIComponent(req.body.layout),
|
|
450
589
|
});
|
|
451
|
-
|
|
452
590
|
req.flash("success", req.__(`Page %s saved`, pagename));
|
|
453
591
|
res.redirect(redirectTarget);
|
|
592
|
+
} else if (req.body.code) {
|
|
593
|
+
try {
|
|
594
|
+
if (!page.html_file) throw new Error(req.__("File not found"));
|
|
595
|
+
const file = await File.findOne(page.html_file);
|
|
596
|
+
if (!file) throw new Error(req.__("File not found"));
|
|
597
|
+
await fsp.writeFile(file.location, req.body.code);
|
|
598
|
+
if (!req.xhr) {
|
|
599
|
+
req.flash("success", req.__(`Page %s saved`, pagename));
|
|
600
|
+
res.redirect(redirectTarget);
|
|
601
|
+
} else res.json({ okay: true });
|
|
602
|
+
} catch (error) {
|
|
603
|
+
getState().log(2, `POST /edit/${pagename}: '${error.message}'`);
|
|
604
|
+
req.flash(
|
|
605
|
+
"error",
|
|
606
|
+
`${req.__("Error")}: ${error.message || req.__("An error occurred")}`
|
|
607
|
+
);
|
|
608
|
+
if (!req.xhr) res.redirect(redirectTarget);
|
|
609
|
+
else res.json({ error: error.message });
|
|
610
|
+
}
|
|
454
611
|
} else {
|
|
612
|
+
getState().log(2, `POST /edit/${pagename}: '${req.body}'`);
|
|
455
613
|
req.flash("error", req.__(`Error processing page`));
|
|
456
614
|
res.redirect(redirectTarget);
|
|
457
615
|
}
|
|
616
|
+
getState().log(5, `POST /edit/${pagename}: Success`);
|
|
458
617
|
})
|
|
459
618
|
);
|
|
460
619
|
|
package/routes/utils.js
CHANGED
|
@@ -18,6 +18,7 @@ const cookieSession = require("cookie-session");
|
|
|
18
18
|
const is = require("contractis/is");
|
|
19
19
|
const { validateHeaderName, validateHeaderValue } = require("http");
|
|
20
20
|
const Crash = require("@saltcorn/data/models/crash");
|
|
21
|
+
const File = require("@saltcorn/data/models/file");
|
|
21
22
|
const si = require("systeminformation");
|
|
22
23
|
const {
|
|
23
24
|
config_fields_form,
|
|
@@ -25,6 +26,8 @@ const {
|
|
|
25
26
|
check_if_restart_required,
|
|
26
27
|
flash_restart,
|
|
27
28
|
} = require("../markup/admin.js");
|
|
29
|
+
const path = require("path");
|
|
30
|
+
|
|
28
31
|
const get_sys_info = async () => {
|
|
29
32
|
const disks = await si.fsSize();
|
|
30
33
|
let size = 0;
|
|
@@ -380,6 +383,38 @@ const admin_config_route = ({
|
|
|
380
383
|
);
|
|
381
384
|
};
|
|
382
385
|
|
|
386
|
+
/**
|
|
387
|
+
* Send HTML file to client without any menu
|
|
388
|
+
* @param {any} req
|
|
389
|
+
* @param {any} res
|
|
390
|
+
* @param {string} file
|
|
391
|
+
* @returns
|
|
392
|
+
*/
|
|
393
|
+
const sendHtmlFile = async (req, res, file) => {
|
|
394
|
+
const fullPath = path.join((await File.rootFolder()).location, file);
|
|
395
|
+
const role = req.user && req.user.id ? req.user.role_id : 100;
|
|
396
|
+
try {
|
|
397
|
+
const scFile = await File.from_file_on_disk(
|
|
398
|
+
path.basename(fullPath),
|
|
399
|
+
path.dirname(fullPath)
|
|
400
|
+
);
|
|
401
|
+
if (scFile && role <= scFile.min_role_read) {
|
|
402
|
+
res.sendFile(fullPath);
|
|
403
|
+
} else {
|
|
404
|
+
return res
|
|
405
|
+
.status(404)
|
|
406
|
+
.sendWrap(req.__("An error occurred"), req.__("File not found"));
|
|
407
|
+
}
|
|
408
|
+
} catch (e) {
|
|
409
|
+
return res
|
|
410
|
+
.status(404)
|
|
411
|
+
.sendWrap(
|
|
412
|
+
req.__("An error occurred"),
|
|
413
|
+
e.message || req.__("An error occurred")
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
|
|
383
418
|
module.exports = {
|
|
384
419
|
sqlsanitize,
|
|
385
420
|
csrfField,
|
|
@@ -396,4 +431,5 @@ module.exports = {
|
|
|
396
431
|
is_relative_url,
|
|
397
432
|
get_sys_info,
|
|
398
433
|
admin_config_route,
|
|
434
|
+
sendHtmlFile,
|
|
399
435
|
};
|
package/routes/view.js
CHANGED
|
@@ -18,7 +18,7 @@ const {
|
|
|
18
18
|
setTenant,
|
|
19
19
|
} = require("../routes/utils.js");
|
|
20
20
|
const { add_edit_bar } = require("../markup/admin.js");
|
|
21
|
-
const { InvalidConfiguration } = require("@saltcorn/data/utils");
|
|
21
|
+
const { InvalidConfiguration, isTest } = require("@saltcorn/data/utils");
|
|
22
22
|
const { getState } = require("@saltcorn/data/db/state");
|
|
23
23
|
|
|
24
24
|
/**
|
|
@@ -88,12 +88,13 @@ router.get(
|
|
|
88
88
|
res.set("SaltcornModalLinkOut", `true`);
|
|
89
89
|
const tock = new Date();
|
|
90
90
|
const ms = tock.getTime() - tic.getTime();
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
91
|
+
if (!isTest())
|
|
92
|
+
Trigger.emitEvent("PageLoad", null, req.user, {
|
|
93
|
+
text: req.__("View '%s' was loaded", viewname),
|
|
94
|
+
type: "view",
|
|
95
|
+
name: viewname,
|
|
96
|
+
render_time: ms,
|
|
97
|
+
});
|
|
97
98
|
if (typeof contents === "object" && contents.goto)
|
|
98
99
|
res.redirect(contents.goto);
|
|
99
100
|
else
|
package/s3storage.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
require("aws-sdk/
|
|
2
|
-
const aws = require("aws-sdk");
|
|
1
|
+
const { S3 } = require("@aws-sdk/client-s3");
|
|
3
2
|
const multer = require("multer");
|
|
4
3
|
const multerS3 = require("multer-s3");
|
|
5
4
|
const { getState } = require("@saltcorn/data/db/state");
|
|
@@ -8,9 +7,11 @@ const { v4: uuidv4 } = require("uuid");
|
|
|
8
7
|
const contentDisposition = require("content-disposition");
|
|
9
8
|
const fs = require("fs");
|
|
10
9
|
function createS3Client() {
|
|
11
|
-
return new
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
return new S3({
|
|
11
|
+
credentials: {
|
|
12
|
+
secretAccessKey: getState().getConfig("storage_s3_access_secret"),
|
|
13
|
+
accessKeyId: getState().getConfig("storage_s3_access_key"),
|
|
14
|
+
},
|
|
14
15
|
region: getState().getConfig("storage_s3_region"),
|
|
15
16
|
endpoint: getState().getConfig("storage_s3_endpoint"),
|
|
16
17
|
});
|