@saltcorn/server 0.7.4-beta.2 → 0.7.4
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 +26 -12
- package/auth/routes.js +41 -27
- package/locales/en.json +30 -4
- package/locales/ru.json +60 -7
- package/markup/admin.js +3 -4
- package/markup/expression_blurb.js +1 -1
- package/package.json +7 -7
- package/public/blockly.js +11 -5
- package/public/gridedit.js +7 -2
- package/public/saltcorn-common.js +106 -43
- package/public/saltcorn.css +16 -10
- package/public/saltcorn.js +60 -19
- package/routes/actions.js +15 -5
- package/routes/admin.js +35 -25
- package/routes/api.js +7 -6
- package/routes/diagram.js +460 -0
- package/routes/fields.js +20 -7
- package/routes/index.js +2 -0
- package/routes/pageedit.js +11 -9
- package/routes/plugins.js +15 -15
- package/routes/tables.js +6 -2
- package/routes/tag_entries.js +2 -2
- package/routes/tags.js +30 -26
- package/routes/utils.js +60 -20
- package/routes/viewedit.js +40 -20
- package/tests/page.test.js +9 -0
- package/tests/viewedit.test.js +16 -0
package/routes/fields.js
CHANGED
|
@@ -19,12 +19,13 @@ const {
|
|
|
19
19
|
expressionValidator,
|
|
20
20
|
get_async_expression_function,
|
|
21
21
|
get_expression_function,
|
|
22
|
+
freeVariables,
|
|
22
23
|
} = require("@saltcorn/data/models/expression");
|
|
23
24
|
const db = require("@saltcorn/data/db");
|
|
24
25
|
|
|
25
26
|
const { isAdmin, error_catcher } = require("./utils.js");
|
|
26
27
|
const expressionBlurb = require("../markup/expression_blurb");
|
|
27
|
-
const { readState } = require("@saltcorn/data/plugin-helper");
|
|
28
|
+
const { readState, add_free_variables_to_joinfields } = require("@saltcorn/data/plugin-helper");
|
|
28
29
|
const { wizardCardTitle } = require("../markup/forms.js");
|
|
29
30
|
const FieldRepeat = require("@saltcorn/data/models/fieldrepeat");
|
|
30
31
|
const { applyAsync } = require("@saltcorn/data/utils");
|
|
@@ -621,7 +622,11 @@ router.post(
|
|
|
621
622
|
const { formula, tablename, stored } = req.body;
|
|
622
623
|
const table = await Table.findOne({ name: tablename });
|
|
623
624
|
const fields = await table.getFields();
|
|
624
|
-
const
|
|
625
|
+
const freeVars = freeVariables(formula)
|
|
626
|
+
const joinFields = {}
|
|
627
|
+
if (stored)
|
|
628
|
+
add_free_variables_to_joinfields(freeVars, joinFields, fields)
|
|
629
|
+
const rows = await table.getJoinedRows({ joinFields, orderBy: "RANDOM()", limit: 1 });
|
|
625
630
|
if (rows.length < 1) return "No rows in table";
|
|
626
631
|
let result;
|
|
627
632
|
try {
|
|
@@ -661,8 +666,12 @@ router.post(
|
|
|
661
666
|
return;
|
|
662
667
|
}
|
|
663
668
|
const fields = await table.getFields();
|
|
664
|
-
|
|
665
|
-
|
|
669
|
+
let row = { ...req.body };
|
|
670
|
+
if (!row || Object.keys(row).length === 0) {
|
|
671
|
+
const { id } = req.query
|
|
672
|
+
if (id) row = await table.getRow({ id })
|
|
673
|
+
} else
|
|
674
|
+
readState(row, fields);
|
|
666
675
|
|
|
667
676
|
if (fieldName.includes(".")) {
|
|
668
677
|
//join field
|
|
@@ -694,7 +703,7 @@ router.post(
|
|
|
694
703
|
return;
|
|
695
704
|
}
|
|
696
705
|
} else {
|
|
697
|
-
targetField.type.fieldviews[fieldview];
|
|
706
|
+
fv = targetField.type.fieldviews[fieldview];
|
|
698
707
|
if (!fv)
|
|
699
708
|
fv =
|
|
700
709
|
targetField.type.fieldviews.show ||
|
|
@@ -743,7 +752,9 @@ router.post(
|
|
|
743
752
|
|
|
744
753
|
let result;
|
|
745
754
|
try {
|
|
746
|
-
if (field.
|
|
755
|
+
if (!field.calculated) {
|
|
756
|
+
result = row[field.name]
|
|
757
|
+
} else if (field.stored) {
|
|
747
758
|
const f = get_async_expression_function(formula, fields);
|
|
748
759
|
result = await f(row);
|
|
749
760
|
} else {
|
|
@@ -751,7 +762,9 @@ router.post(
|
|
|
751
762
|
result = f(row);
|
|
752
763
|
}
|
|
753
764
|
const fv = field.type.fieldviews[fieldview];
|
|
754
|
-
|
|
765
|
+
if (!fv)
|
|
766
|
+
res.send(text(result));
|
|
767
|
+
else res.send(fv.run(result));
|
|
755
768
|
} catch (e) {
|
|
756
769
|
return res.status(400).send(`Error: ${e.message}`);
|
|
757
770
|
}
|
package/routes/index.js
CHANGED
|
@@ -72,6 +72,7 @@ const roleadmin = require("../auth/roleadmin");
|
|
|
72
72
|
const scapi = require("./scapi");
|
|
73
73
|
const tags = require("./tags");
|
|
74
74
|
const tagentries = require("./tag_entries");
|
|
75
|
+
const dataDiagram = require("./diagram");
|
|
75
76
|
|
|
76
77
|
module.exports =
|
|
77
78
|
/**
|
|
@@ -110,4 +111,5 @@ module.exports =
|
|
|
110
111
|
app.use("/scapi", scapi);
|
|
111
112
|
app.use("/tag", tags);
|
|
112
113
|
app.use("/tag-entries", tagentries);
|
|
114
|
+
app.use("/diagram", dataDiagram);
|
|
113
115
|
};
|
package/routes/pageedit.js
CHANGED
|
@@ -21,7 +21,7 @@ const { add_to_menu } = require("@saltcorn/admin-models/models/pack");
|
|
|
21
21
|
const db = require("@saltcorn/data/db");
|
|
22
22
|
const { getPageList } = require("./common_lists");
|
|
23
23
|
|
|
24
|
-
const { isAdmin, error_catcher } = require("./utils.js");
|
|
24
|
+
const { isAdmin, error_catcher, addOnDoneRedirect } = require("./utils.js");
|
|
25
25
|
const {
|
|
26
26
|
mkTable,
|
|
27
27
|
renderForm,
|
|
@@ -54,7 +54,7 @@ const pagePropertiesForm = async (req) => {
|
|
|
54
54
|
const roles = await User.get_roles();
|
|
55
55
|
|
|
56
56
|
const form = new Form({
|
|
57
|
-
action: "/pageedit/edit-properties",
|
|
57
|
+
action: addOnDoneRedirect("/pageedit/edit-properties", req),
|
|
58
58
|
fields: [
|
|
59
59
|
new Field({
|
|
60
60
|
label: req.__("Name"),
|
|
@@ -185,9 +185,8 @@ const pageBuilderData = async (req, context) => {
|
|
|
185
185
|
const getRootPageForm = (pages, roles, req) => {
|
|
186
186
|
const form = new Form({
|
|
187
187
|
action: "/pageedit/set_root_page",
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
onChange: "remove_outline(this)",
|
|
188
|
+
noSubmitButton: true,
|
|
189
|
+
onChange: "saveAndContinue(this)",
|
|
191
190
|
blurb: req.__(
|
|
192
191
|
"The root page is the page that is served when the user visits the home location (/). This can be set for each user role."
|
|
193
192
|
),
|
|
@@ -358,7 +357,7 @@ router.post(
|
|
|
358
357
|
if (!pageRow.fixed_states) pageRow.fixed_states = {};
|
|
359
358
|
if (!pageRow.layout) pageRow.layout = {};
|
|
360
359
|
await Page.create(pageRow);
|
|
361
|
-
res.redirect(`/pageedit/edit/${pageRow.name}
|
|
360
|
+
res.redirect(addOnDoneRedirect(`/pageedit/edit/${pageRow.name}`, req));
|
|
362
361
|
}
|
|
363
362
|
}
|
|
364
363
|
})
|
|
@@ -417,20 +416,23 @@ router.post(
|
|
|
417
416
|
error_catcher(async (req, res) => {
|
|
418
417
|
const { pagename } = req.params;
|
|
419
418
|
|
|
419
|
+
let redirectTarget = req.query.on_done_redirect
|
|
420
|
+
? `/${req.query.on_done_redirect}`
|
|
421
|
+
: "/pageedit";
|
|
420
422
|
const page = await Page.findOne({ name: pagename });
|
|
421
423
|
if (!page) {
|
|
422
424
|
req.flash("error", req.__(`Page %s not found`, pagename));
|
|
423
|
-
res.redirect(
|
|
425
|
+
res.redirect(redirectTarget);
|
|
424
426
|
} else if (req.body.layout) {
|
|
425
427
|
await Page.update(page.id, {
|
|
426
428
|
layout: decodeURIComponent(req.body.layout),
|
|
427
429
|
});
|
|
428
430
|
|
|
429
431
|
req.flash("success", req.__(`Page %s saved`, pagename));
|
|
430
|
-
res.redirect(
|
|
432
|
+
res.redirect(redirectTarget);
|
|
431
433
|
} else {
|
|
432
434
|
req.flash("error", req.__(`Error processing page`));
|
|
433
|
-
res.redirect(
|
|
435
|
+
res.redirect(redirectTarget);
|
|
434
436
|
}
|
|
435
437
|
})
|
|
436
438
|
);
|
package/routes/plugins.js
CHANGED
|
@@ -455,7 +455,7 @@ const store_actions_dropdown = (req) =>
|
|
|
455
455
|
{
|
|
456
456
|
class: "dropdown-item",
|
|
457
457
|
href: `/plugins/upgrade`,
|
|
458
|
-
onClick: `notifyAlert('Upgrading modules...', true)`,
|
|
458
|
+
onClick: `notifyAlert('${req.__("Upgrading modules...")}', true)`,
|
|
459
459
|
},
|
|
460
460
|
'<i class="far fa-arrow-alt-circle-up"></i> ' +
|
|
461
461
|
req.__("Upgrade installed modules")
|
|
@@ -551,7 +551,7 @@ router.get(
|
|
|
551
551
|
const { name } = req.params;
|
|
552
552
|
const plugin = await Plugin.findOne({ name: decodeURIComponent(name) });
|
|
553
553
|
if (!plugin) {
|
|
554
|
-
req.flash("warning", "
|
|
554
|
+
req.flash("warning", req.__("Module not found"));
|
|
555
555
|
res.redirect("/plugins");
|
|
556
556
|
return;
|
|
557
557
|
}
|
|
@@ -792,7 +792,7 @@ router.get(
|
|
|
792
792
|
pkgjson = require(path.join(mod.location, "package.json"));
|
|
793
793
|
|
|
794
794
|
if (!plugin_db) {
|
|
795
|
-
req.flash("warning", "
|
|
795
|
+
req.flash("warning", "Module not found");
|
|
796
796
|
res.redirect("/plugins");
|
|
797
797
|
return;
|
|
798
798
|
}
|
|
@@ -817,7 +817,7 @@ router.get(
|
|
|
817
817
|
),
|
|
818
818
|
mod.plugin_module.dependencies
|
|
819
819
|
? tr(
|
|
820
|
-
th(req.__("
|
|
820
|
+
th(req.__("Module dependencies")),
|
|
821
821
|
td(
|
|
822
822
|
mod.plugin_module.dependencies.map((d) =>
|
|
823
823
|
span({ class: "badge bg-primary me-1" }, d)
|
|
@@ -933,7 +933,7 @@ router.get(
|
|
|
933
933
|
|
|
934
934
|
const plugin = await Plugin.findOne({ name });
|
|
935
935
|
await plugin.upgrade_version((p, f) => load_plugins.loadPlugin(p, f));
|
|
936
|
-
req.flash("success", req.__(`
|
|
936
|
+
req.flash("success", req.__(`Module up-to-date`));
|
|
937
937
|
|
|
938
938
|
res.redirect(`/plugins/info/${plugin.name}`);
|
|
939
939
|
})
|
|
@@ -954,7 +954,7 @@ router.post(
|
|
|
954
954
|
if (schema !== db.connectObj.default_schema) {
|
|
955
955
|
req.flash(
|
|
956
956
|
"error",
|
|
957
|
-
req.__(`Only store
|
|
957
|
+
req.__(`Only store modules can be installed on tenant instances`)
|
|
958
958
|
);
|
|
959
959
|
res.redirect(`/plugins`);
|
|
960
960
|
} else {
|
|
@@ -963,12 +963,12 @@ router.post(
|
|
|
963
963
|
plugin,
|
|
964
964
|
schema === db.connectObj.default_schema || plugin.source === "github"
|
|
965
965
|
);
|
|
966
|
-
req.flash("success", req.__(`
|
|
966
|
+
req.flash("success", req.__(`Module %s installed`, plugin.name));
|
|
967
967
|
res.redirect(`/plugins`);
|
|
968
968
|
} catch (e) {
|
|
969
969
|
req.flash("error", `${e.message}`);
|
|
970
970
|
const form = pluginForm(req, plugin);
|
|
971
|
-
res.sendWrap(req.__(`Edit
|
|
971
|
+
res.sendWrap(req.__(`Edit Module`), renderForm(form, req.csrfToken()));
|
|
972
972
|
}
|
|
973
973
|
}
|
|
974
974
|
})
|
|
@@ -988,7 +988,7 @@ router.post(
|
|
|
988
988
|
|
|
989
989
|
const plugin = await Plugin.findOne({ name: decodeURIComponent(name) });
|
|
990
990
|
if (!plugin) {
|
|
991
|
-
req.flash("warning", "
|
|
991
|
+
req.flash("warning", "Module not found");
|
|
992
992
|
res.redirect("/plugins");
|
|
993
993
|
return;
|
|
994
994
|
}
|
|
@@ -998,11 +998,11 @@ router.post(
|
|
|
998
998
|
getState().getConfig("development_mode", false)
|
|
999
999
|
) {
|
|
1000
1000
|
await plugin.delete();
|
|
1001
|
-
req.flash("success", req.__(`
|
|
1001
|
+
req.flash("success", req.__(`Module %s removed.`, plugin.name));
|
|
1002
1002
|
} else {
|
|
1003
1003
|
req.flash(
|
|
1004
1004
|
"error",
|
|
1005
|
-
req.__(`Cannot remove
|
|
1005
|
+
req.__(`Cannot remove module: views %s depend on it`, depviews.join())
|
|
1006
1006
|
);
|
|
1007
1007
|
}
|
|
1008
1008
|
res.redirect(`/plugins`);
|
|
@@ -1025,7 +1025,7 @@ router.post(
|
|
|
1025
1025
|
if (!plugin) {
|
|
1026
1026
|
req.flash(
|
|
1027
1027
|
"error",
|
|
1028
|
-
req.__(`
|
|
1028
|
+
req.__(`Module %s not found`, text(decodeURIComponent(name)))
|
|
1029
1029
|
);
|
|
1030
1030
|
res.redirect(`/plugins`);
|
|
1031
1031
|
return;
|
|
@@ -1034,7 +1034,7 @@ router.post(
|
|
|
1034
1034
|
if (!isRoot && plugin.unsafe) {
|
|
1035
1035
|
req.flash(
|
|
1036
1036
|
"error",
|
|
1037
|
-
req.__("Cannot install unsafe
|
|
1037
|
+
req.__("Cannot install unsafe modules on subdomain tenants")
|
|
1038
1038
|
);
|
|
1039
1039
|
res.redirect(`/plugins`);
|
|
1040
1040
|
return;
|
|
@@ -1047,14 +1047,14 @@ router.post(
|
|
|
1047
1047
|
req.flash(
|
|
1048
1048
|
"success",
|
|
1049
1049
|
req.__(
|
|
1050
|
-
`
|
|
1050
|
+
`Module %s installed, please complete configuration.`,
|
|
1051
1051
|
plugin_db.name
|
|
1052
1052
|
)
|
|
1053
1053
|
);
|
|
1054
1054
|
await sleep(1000); // Allow other workers to load this plugin
|
|
1055
1055
|
res.redirect(`/plugins/configure/${plugin_db.name}`);
|
|
1056
1056
|
} else {
|
|
1057
|
-
req.flash("success", req.__(`
|
|
1057
|
+
req.flash("success", req.__(`Module %s installed`, plugin.name));
|
|
1058
1058
|
res.redirect(`/plugins`);
|
|
1059
1059
|
}
|
|
1060
1060
|
})
|
package/routes/tables.js
CHANGED
|
@@ -1013,7 +1013,6 @@ router.get(
|
|
|
1013
1013
|
const getRole = (rid) => roles.find((r) => r.id === rid).role;
|
|
1014
1014
|
const mainCard = await tablesList(rows, req);
|
|
1015
1015
|
const createCard = div(
|
|
1016
|
-
h5(req.__("Create table")),
|
|
1017
1016
|
a(
|
|
1018
1017
|
{ href: `/table/new`, class: "btn btn-primary mt-1 me-3" },
|
|
1019
1018
|
i({ class: "fas fa-plus-square me-1" }),
|
|
@@ -1029,8 +1028,13 @@ router.get(
|
|
|
1029
1028
|
),
|
|
1030
1029
|
!db.isSQLite &&
|
|
1031
1030
|
a(
|
|
1032
|
-
{
|
|
1031
|
+
{
|
|
1032
|
+
href: `/table/discover`,
|
|
1033
|
+
class: "btn btn-secondary mt-1",
|
|
1034
|
+
title: req.__("Discover tables that are already in the Database, but not known to Saltcorn"),
|
|
1035
|
+
},
|
|
1033
1036
|
i({ class: "fas fa-map-signs me-1" }),
|
|
1037
|
+
|
|
1034
1038
|
req.__("Discover tables")
|
|
1035
1039
|
)
|
|
1036
1040
|
);
|
package/routes/tag_entries.js
CHANGED
|
@@ -105,7 +105,7 @@ router.get(
|
|
|
105
105
|
},
|
|
106
106
|
{
|
|
107
107
|
type: "card",
|
|
108
|
-
title: `Add entries to tag
|
|
108
|
+
title: req.__(`Add entries to tag`),
|
|
109
109
|
contents: buildForm(
|
|
110
110
|
entry_type,
|
|
111
111
|
tag_id,
|
|
@@ -143,7 +143,7 @@ router.post(
|
|
|
143
143
|
const { entry_type, tag_id } = req.params;
|
|
144
144
|
const { ids } = req.body;
|
|
145
145
|
if (!ids) {
|
|
146
|
-
req.flash("error", req.__("Please select at least
|
|
146
|
+
req.flash("error", req.__("Please select at least one item"));
|
|
147
147
|
return res.redirect(`/tag-entries/add/${entry_type}/${tag_id}`);
|
|
148
148
|
}
|
|
149
149
|
const fieldName = idField(entry_type);
|
package/routes/tags.js
CHANGED
|
@@ -6,7 +6,7 @@ const Form = require("@saltcorn/data/models/form");
|
|
|
6
6
|
const User = require("@saltcorn/data/models/user");
|
|
7
7
|
|
|
8
8
|
const { isAdmin, error_catcher, csrfField } = require("./utils");
|
|
9
|
-
const {
|
|
9
|
+
const { send_infoarch_page } = require("../markup/admin");
|
|
10
10
|
|
|
11
11
|
const {
|
|
12
12
|
mkTable,
|
|
@@ -31,34 +31,38 @@ router.get(
|
|
|
31
31
|
isAdmin,
|
|
32
32
|
error_catcher(async (req, res) => {
|
|
33
33
|
const rows = await Tag.find();
|
|
34
|
-
|
|
34
|
+
send_infoarch_page({
|
|
35
35
|
res,
|
|
36
36
|
req,
|
|
37
37
|
active_sub: "Tags",
|
|
38
|
-
contents:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
38
|
+
contents: {
|
|
39
|
+
type: "card",
|
|
40
|
+
title: req.__("Tags"),
|
|
41
|
+
contents: [
|
|
42
|
+
mkTable(
|
|
43
|
+
[
|
|
44
|
+
{
|
|
45
|
+
label: req.__("Tagname"),
|
|
46
|
+
key: (r) =>
|
|
47
|
+
link(`/tag/${r.id || r.name}?show_list=tables`, text(r.name)),
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
label: req.__("Delete"),
|
|
51
|
+
key: (r) => post_delete_btn(`/tag/delete/${r.id}`, req, r.name),
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
rows,
|
|
55
|
+
{}
|
|
56
|
+
),
|
|
57
|
+
a(
|
|
46
58
|
{
|
|
47
|
-
|
|
48
|
-
|
|
59
|
+
href: `/tag/new`,
|
|
60
|
+
class: "btn btn-primary",
|
|
49
61
|
},
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
a(
|
|
55
|
-
{
|
|
56
|
-
href: `/tag/new`,
|
|
57
|
-
class: "btn btn-primary",
|
|
58
|
-
},
|
|
59
|
-
req.__("Create tag")
|
|
60
|
-
),
|
|
61
|
-
],
|
|
62
|
+
req.__("Create tag")
|
|
63
|
+
),
|
|
64
|
+
],
|
|
65
|
+
},
|
|
62
66
|
});
|
|
63
67
|
})
|
|
64
68
|
);
|
|
@@ -199,7 +203,7 @@ router.get(
|
|
|
199
203
|
href: `/tag-entries/add/pages/${tag.id}`,
|
|
200
204
|
class: "btn btn-primary",
|
|
201
205
|
},
|
|
202
|
-
req.__("Add
|
|
206
|
+
req.__("Add pages")
|
|
203
207
|
),
|
|
204
208
|
],
|
|
205
209
|
},
|
|
@@ -222,7 +226,7 @@ router.get(
|
|
|
222
226
|
href: `/tag-entries/add/trigger/${tag.id}`,
|
|
223
227
|
class: "btn btn-primary",
|
|
224
228
|
},
|
|
225
|
-
req.__("Add
|
|
229
|
+
req.__("Add triggers")
|
|
226
230
|
),
|
|
227
231
|
],
|
|
228
232
|
},
|
package/routes/utils.js
CHANGED
|
@@ -113,37 +113,62 @@ const get_tenant_from_req = (req) => {
|
|
|
113
113
|
};
|
|
114
114
|
|
|
115
115
|
/**
|
|
116
|
+
* middleware to extract the tenant domain and call runWithtenant()
|
|
116
117
|
* @param {object} req
|
|
117
118
|
* @param {object} res
|
|
118
119
|
* @param {function} next
|
|
119
120
|
*/
|
|
120
121
|
const setTenant = (req, res, next) => {
|
|
121
122
|
if (db.is_it_multi_tenant()) {
|
|
122
|
-
|
|
123
|
-
if (
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
setLanguage(req, res
|
|
131
|
-
state.log(5, `${req.method} ${req.originalUrl}`);
|
|
123
|
+
// for a saltcorn mobile request use 'req.user.tenant'
|
|
124
|
+
if (req.smr) {
|
|
125
|
+
if (
|
|
126
|
+
req.user?.tenant &&
|
|
127
|
+
req.user.tenant !== db.connectObj.default_schema
|
|
128
|
+
) {
|
|
129
|
+
const state = getTenant(req.user.tenant);
|
|
130
|
+
if (!state) {
|
|
131
|
+
setLanguage(req, res);
|
|
132
132
|
next();
|
|
133
|
-
}
|
|
133
|
+
} else {
|
|
134
|
+
db.runWithTenant(req.user.tenant, () => {
|
|
135
|
+
setLanguage(req, res, state);
|
|
136
|
+
state.log(5, `${req.method} ${req.originalUrl}`);
|
|
137
|
+
next();
|
|
138
|
+
});
|
|
139
|
+
}
|
|
134
140
|
}
|
|
135
|
-
|
|
136
|
-
const ten = get_tenant_from_req(req);
|
|
137
|
-
const state = getTenant(ten);
|
|
138
|
-
if (!state) {
|
|
141
|
+
else {
|
|
139
142
|
setLanguage(req, res);
|
|
140
143
|
next();
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
const other_domain = get_other_domain_tenant(req.hostname);
|
|
147
|
+
if (other_domain) {
|
|
148
|
+
const state = getTenant(other_domain);
|
|
149
|
+
if (!state) {
|
|
150
|
+
setLanguage(req, res);
|
|
151
|
+
next();
|
|
152
|
+
} else {
|
|
153
|
+
db.runWithTenant(other_domain, () => {
|
|
154
|
+
setLanguage(req, res, state);
|
|
155
|
+
state.log(5, `${req.method} ${req.originalUrl}`);
|
|
156
|
+
next();
|
|
157
|
+
});
|
|
158
|
+
}
|
|
141
159
|
} else {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
160
|
+
const ten = get_tenant_from_req(req);
|
|
161
|
+
const state = getTenant(ten);
|
|
162
|
+
if (!state) {
|
|
163
|
+
setLanguage(req, res);
|
|
145
164
|
next();
|
|
146
|
-
}
|
|
165
|
+
} else {
|
|
166
|
+
db.runWithTenant(ten, () => {
|
|
167
|
+
setLanguage(req, res, state);
|
|
168
|
+
state.log(5, `${req.method} ${req.originalUrl}`);
|
|
169
|
+
next();
|
|
170
|
+
});
|
|
171
|
+
}
|
|
147
172
|
}
|
|
148
173
|
}
|
|
149
174
|
} else {
|
|
@@ -232,6 +257,20 @@ const getSessionStore = () => {
|
|
|
232
257
|
}
|
|
233
258
|
};
|
|
234
259
|
|
|
260
|
+
/**
|
|
261
|
+
* appends 'req.query.on_done_redirect' to 'oldPath' if it exists
|
|
262
|
+
* @param {string} oldPath path without 'on_done_redirect'
|
|
263
|
+
* @param {any} req express request
|
|
264
|
+
* @returns a new string with or without on_done_redirect=...
|
|
265
|
+
*/
|
|
266
|
+
const addOnDoneRedirect = (oldPath, req) => {
|
|
267
|
+
const separator = oldPath.indexOf("?") > -1 ? "&" : "?";
|
|
268
|
+
if (req.query.on_done_redirect) {
|
|
269
|
+
return `${oldPath}${separator}on_done_redirect=${req.query.on_done_redirect}`;
|
|
270
|
+
}
|
|
271
|
+
return oldPath;
|
|
272
|
+
};
|
|
273
|
+
|
|
235
274
|
module.exports = {
|
|
236
275
|
sqlsanitize,
|
|
237
276
|
csrfField,
|
|
@@ -243,5 +282,6 @@ module.exports = {
|
|
|
243
282
|
getGitRevision,
|
|
244
283
|
getSessionStore,
|
|
245
284
|
setTenant,
|
|
246
|
-
get_tenant_from_req
|
|
285
|
+
get_tenant_from_req,
|
|
286
|
+
addOnDoneRedirect,
|
|
247
287
|
};
|
package/routes/viewedit.js
CHANGED
|
@@ -16,6 +16,7 @@ const {
|
|
|
16
16
|
post_dropdown_item,
|
|
17
17
|
renderBuilder,
|
|
18
18
|
settingsDropdown,
|
|
19
|
+
alert
|
|
19
20
|
} = require("@saltcorn/markup");
|
|
20
21
|
const {
|
|
21
22
|
//span,
|
|
@@ -30,7 +31,7 @@ const {
|
|
|
30
31
|
} = require("@saltcorn/markup/tags");
|
|
31
32
|
|
|
32
33
|
const { getState } = require("@saltcorn/data/db/state");
|
|
33
|
-
const { isAdmin, error_catcher } = require("./utils.js");
|
|
34
|
+
const { isAdmin, error_catcher, addOnDoneRedirect } = require("./utils.js");
|
|
34
35
|
const { setTableRefs, viewsList } = require("./common_lists");
|
|
35
36
|
const Form = require("@saltcorn/data/models/form");
|
|
36
37
|
const Field = require("@saltcorn/data/models/field");
|
|
@@ -76,6 +77,20 @@ router.get(
|
|
|
76
77
|
|
|
77
78
|
const viewMarkup = await viewsList(views, req);
|
|
78
79
|
const tables = await Table.find();
|
|
80
|
+
const viewAccessWarning = view => {
|
|
81
|
+
const table = tables.find(t => t.name === view.table)
|
|
82
|
+
if (!table) return false
|
|
83
|
+
if (table.ownership_field_id || table.ownership_formula) return false
|
|
84
|
+
|
|
85
|
+
return table.min_role_read < view.min_role
|
|
86
|
+
}
|
|
87
|
+
const hasAccessWarning = views.filter(viewAccessWarning)
|
|
88
|
+
const accessWarning = hasAccessWarning.length > 0
|
|
89
|
+
? alert("danger", `<p>You have views with a role to access lower than the table role to read,
|
|
90
|
+
with no table ownership. In the next version of Saltcorn, this may cause a
|
|
91
|
+
denial of access. Users will need to have table read access to any data displayed.</p>
|
|
92
|
+
Views potentially affected: ${hasAccessWarning.map(v => v.name).join(", ")}`)
|
|
93
|
+
: ''
|
|
79
94
|
res.sendWrap(req.__(`Views`), {
|
|
80
95
|
above: [
|
|
81
96
|
{
|
|
@@ -87,17 +102,18 @@ router.get(
|
|
|
87
102
|
class: "mt-0",
|
|
88
103
|
title: req.__("Your views"),
|
|
89
104
|
contents: [
|
|
105
|
+
accessWarning,
|
|
90
106
|
viewMarkup,
|
|
91
107
|
tables.length > 0
|
|
92
108
|
? a(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
109
|
+
{ href: `/viewedit/new`, class: "btn btn-primary" },
|
|
110
|
+
req.__("Create view")
|
|
111
|
+
)
|
|
96
112
|
: p(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
113
|
+
req.__(
|
|
114
|
+
"You must create at least one table before you can create views."
|
|
115
|
+
)
|
|
116
|
+
),
|
|
101
117
|
],
|
|
102
118
|
},
|
|
103
119
|
],
|
|
@@ -129,7 +145,7 @@ const viewForm = async (req, tableOptions, roles, pages, values) => {
|
|
|
129
145
|
.map(([k, v]) => k);
|
|
130
146
|
const slugOptions = await Table.allSlugOptions();
|
|
131
147
|
return new Form({
|
|
132
|
-
action: "/viewedit/save",
|
|
148
|
+
action: addOnDoneRedirect("/viewedit/save", req),
|
|
133
149
|
submitLabel: req.__("Configure") + " »",
|
|
134
150
|
blurb: req.__("First, please give some basic information about the view."),
|
|
135
151
|
fields: [
|
|
@@ -209,15 +225,15 @@ const viewForm = async (req, tableOptions, roles, pages, values) => {
|
|
|
209
225
|
}),
|
|
210
226
|
...(isEdit
|
|
211
227
|
? [
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
228
|
+
new Field({
|
|
229
|
+
name: "viewtemplate",
|
|
230
|
+
input_type: "hidden",
|
|
231
|
+
}),
|
|
232
|
+
new Field({
|
|
233
|
+
name: "table_name",
|
|
234
|
+
input_type: "hidden",
|
|
235
|
+
}),
|
|
236
|
+
]
|
|
221
237
|
: []),
|
|
222
238
|
],
|
|
223
239
|
values,
|
|
@@ -337,7 +353,6 @@ router.post(
|
|
|
337
353
|
const pages = await Page.find();
|
|
338
354
|
const form = await viewForm(req, tableOptions, roles, pages);
|
|
339
355
|
const result = form.validate(req.body);
|
|
340
|
-
|
|
341
356
|
const sendForm = (form) => {
|
|
342
357
|
res.sendWrap(req.__(`Edit view`), {
|
|
343
358
|
above: [
|
|
@@ -397,7 +412,12 @@ router.post(
|
|
|
397
412
|
else v.configuration = {};
|
|
398
413
|
await View.create(v);
|
|
399
414
|
}
|
|
400
|
-
res.redirect(
|
|
415
|
+
res.redirect(
|
|
416
|
+
addOnDoneRedirect(
|
|
417
|
+
`/viewedit/config/${encodeURIComponent(v.name)}`,
|
|
418
|
+
req
|
|
419
|
+
)
|
|
420
|
+
);
|
|
401
421
|
}
|
|
402
422
|
} else {
|
|
403
423
|
sendForm(form);
|
package/tests/page.test.js
CHANGED
|
@@ -146,6 +146,15 @@ describe("pageedit", () => {
|
|
|
146
146
|
.send("id=1")
|
|
147
147
|
.expect(toRedirect("/pageedit/"));
|
|
148
148
|
});
|
|
149
|
+
it("show builder", async () => {
|
|
150
|
+
const app = await getApp({ disableCsrf: true });
|
|
151
|
+
const loginCookie = await getAdminLoginCookie();
|
|
152
|
+
await request(app)
|
|
153
|
+
.get("/pageedit/edit/a_page")
|
|
154
|
+
.set("Cookie", loginCookie)
|
|
155
|
+
.expect(toInclude("<script>builder.renderBuilder"));
|
|
156
|
+
|
|
157
|
+
});
|
|
149
158
|
|
|
150
159
|
it("sets root page", async () => {
|
|
151
160
|
const app = await getApp({ disableCsrf: true });
|
package/tests/viewedit.test.js
CHANGED
|
@@ -43,6 +43,22 @@ describe("viewedit edit endpoint", () => {
|
|
|
43
43
|
.set("Cookie", loginCookie)
|
|
44
44
|
.expect(toInclude("author"));
|
|
45
45
|
});
|
|
46
|
+
it("show list editor", async () => {
|
|
47
|
+
const loginCookie = await getAdminLoginCookie();
|
|
48
|
+
const app = await getApp({ disableCsrf: true });
|
|
49
|
+
await request(app)
|
|
50
|
+
.get("/viewedit/config/authorlist")
|
|
51
|
+
.set("Cookie", loginCookie)
|
|
52
|
+
.expect(toInclude("author"));
|
|
53
|
+
});
|
|
54
|
+
it("show builder", async () => {
|
|
55
|
+
const loginCookie = await getAdminLoginCookie();
|
|
56
|
+
const app = await getApp({ disableCsrf: true });
|
|
57
|
+
await request(app)
|
|
58
|
+
.get("/viewedit/config/authorshow")
|
|
59
|
+
.set("Cookie", loginCookie)
|
|
60
|
+
.expect(toInclude("<script>builder.renderBuilder"));
|
|
61
|
+
});
|
|
46
62
|
});
|
|
47
63
|
|
|
48
64
|
describe("viewedit new List", () => {
|