@saltcorn/server 0.9.4-beta.9 → 0.9.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 +16 -1
- package/auth/admin.js +19 -3
- package/auth/routes.js +16 -4
- package/auth/testhelp.js +17 -1
- package/load_plugins.js +8 -2
- package/locales/en.json +24 -2
- package/markup/admin.js +22 -18
- package/package.json +10 -9
- package/public/dayjslocales/af.js +1 -0
- package/public/dayjslocales/am.js +1 -0
- package/public/dayjslocales/ar-dz.js +1 -0
- package/public/dayjslocales/ar-iq.js +1 -0
- package/public/dayjslocales/ar-kw.js +1 -0
- package/public/dayjslocales/ar-ly.js +1 -0
- package/public/dayjslocales/ar-ma.js +1 -0
- package/public/dayjslocales/ar-sa.js +1 -0
- package/public/dayjslocales/ar-tn.js +1 -0
- package/public/dayjslocales/ar.js +1 -0
- package/public/dayjslocales/az.js +1 -0
- package/public/dayjslocales/be.js +1 -0
- package/public/dayjslocales/bg.js +1 -0
- package/public/dayjslocales/bi.js +1 -0
- package/public/dayjslocales/bm.js +1 -0
- package/public/dayjslocales/bn-bd.js +1 -0
- package/public/dayjslocales/bn.js +1 -0
- package/public/dayjslocales/bo.js +1 -0
- package/public/dayjslocales/br.js +1 -0
- package/public/dayjslocales/bs.js +1 -0
- package/public/dayjslocales/ca.js +1 -0
- package/public/dayjslocales/cs.js +1 -0
- package/public/dayjslocales/cv.js +1 -0
- package/public/dayjslocales/cy.js +1 -0
- package/public/dayjslocales/da.js +1 -0
- package/public/dayjslocales/de-at.js +1 -0
- package/public/dayjslocales/de-ch.js +1 -0
- package/public/dayjslocales/de.js +1 -0
- package/public/dayjslocales/dv.js +1 -0
- package/public/dayjslocales/el.js +1 -0
- package/public/dayjslocales/en-au.js +1 -0
- package/public/dayjslocales/en-ca.js +1 -0
- package/public/dayjslocales/en-gb.js +1 -0
- package/public/dayjslocales/en-ie.js +1 -0
- package/public/dayjslocales/en-il.js +1 -0
- package/public/dayjslocales/en-in.js +1 -0
- package/public/dayjslocales/en-nz.js +1 -0
- package/public/dayjslocales/en-sg.js +1 -0
- package/public/dayjslocales/en-tt.js +1 -0
- package/public/dayjslocales/en.js +1 -0
- package/public/dayjslocales/eo.js +1 -0
- package/public/dayjslocales/es-do.js +1 -0
- package/public/dayjslocales/es-mx.js +1 -0
- package/public/dayjslocales/es-pr.js +1 -0
- package/public/dayjslocales/es-us.js +1 -0
- package/public/dayjslocales/es.js +1 -0
- package/public/dayjslocales/et.js +1 -0
- package/public/dayjslocales/eu.js +1 -0
- package/public/dayjslocales/fa.js +1 -0
- package/public/dayjslocales/fi.js +1 -0
- package/public/dayjslocales/fo.js +1 -0
- package/public/dayjslocales/fr-ca.js +1 -0
- package/public/dayjslocales/fr-ch.js +1 -0
- package/public/dayjslocales/fr.js +1 -0
- package/public/dayjslocales/fy.js +1 -0
- package/public/dayjslocales/ga.js +1 -0
- package/public/dayjslocales/gd.js +1 -0
- package/public/dayjslocales/gl.js +1 -0
- package/public/dayjslocales/gom-latn.js +1 -0
- package/public/dayjslocales/gu.js +1 -0
- package/public/dayjslocales/he.js +1 -0
- package/public/dayjslocales/hi.js +1 -0
- package/public/dayjslocales/hr.js +1 -0
- package/public/dayjslocales/ht.js +1 -0
- package/public/dayjslocales/hu.js +1 -0
- package/public/dayjslocales/hy-am.js +1 -0
- package/public/dayjslocales/id.js +1 -0
- package/public/dayjslocales/is.js +1 -0
- package/public/dayjslocales/it-ch.js +1 -0
- package/public/dayjslocales/it.js +1 -0
- package/public/dayjslocales/ja.js +1 -0
- package/public/dayjslocales/jv.js +1 -0
- package/public/dayjslocales/ka.js +1 -0
- package/public/dayjslocales/kk.js +1 -0
- package/public/dayjslocales/km.js +1 -0
- package/public/dayjslocales/kn.js +1 -0
- package/public/dayjslocales/ko.js +1 -0
- package/public/dayjslocales/ku.js +1 -0
- package/public/dayjslocales/ky.js +1 -0
- package/public/dayjslocales/lb.js +1 -0
- package/public/dayjslocales/lo.js +1 -0
- package/public/dayjslocales/lt.js +1 -0
- package/public/dayjslocales/lv.js +1 -0
- package/public/dayjslocales/me.js +1 -0
- package/public/dayjslocales/mi.js +1 -0
- package/public/dayjslocales/mk.js +1 -0
- package/public/dayjslocales/ml.js +1 -0
- package/public/dayjslocales/mn.js +1 -0
- package/public/dayjslocales/mr.js +1 -0
- package/public/dayjslocales/ms-my.js +1 -0
- package/public/dayjslocales/ms.js +1 -0
- package/public/dayjslocales/mt.js +1 -0
- package/public/dayjslocales/my.js +1 -0
- package/public/dayjslocales/nb.js +1 -0
- package/public/dayjslocales/ne.js +1 -0
- package/public/dayjslocales/nl-be.js +1 -0
- package/public/dayjslocales/nl.js +1 -0
- package/public/dayjslocales/nn.js +1 -0
- package/public/dayjslocales/oc-lnc.js +1 -0
- package/public/dayjslocales/pa-in.js +1 -0
- package/public/dayjslocales/pl.js +1 -0
- package/public/dayjslocales/pt-br.js +1 -0
- package/public/dayjslocales/pt.js +1 -0
- package/public/dayjslocales/rn.js +1 -0
- package/public/dayjslocales/ro.js +1 -0
- package/public/dayjslocales/ru.js +1 -0
- package/public/dayjslocales/rw.js +1 -0
- package/public/dayjslocales/sd.js +1 -0
- package/public/dayjslocales/se.js +1 -0
- package/public/dayjslocales/si.js +1 -0
- package/public/dayjslocales/sk.js +1 -0
- package/public/dayjslocales/sl.js +1 -0
- package/public/dayjslocales/sq.js +1 -0
- package/public/dayjslocales/sr-cyrl.js +1 -0
- package/public/dayjslocales/sr.js +1 -0
- package/public/dayjslocales/ss.js +1 -0
- package/public/dayjslocales/sv-fi.js +1 -0
- package/public/dayjslocales/sv.js +1 -0
- package/public/dayjslocales/sw.js +1 -0
- package/public/dayjslocales/ta.js +1 -0
- package/public/dayjslocales/te.js +1 -0
- package/public/dayjslocales/tet.js +1 -0
- package/public/dayjslocales/tg.js +1 -0
- package/public/dayjslocales/th.js +1 -0
- package/public/dayjslocales/tk.js +1 -0
- package/public/dayjslocales/tl-ph.js +1 -0
- package/public/dayjslocales/tlh.js +1 -0
- package/public/dayjslocales/tr.js +1 -0
- package/public/dayjslocales/tzl.js +1 -0
- package/public/dayjslocales/tzm-latn.js +1 -0
- package/public/dayjslocales/tzm.js +1 -0
- package/public/dayjslocales/ug-cn.js +1 -0
- package/public/dayjslocales/uk.js +1 -0
- package/public/dayjslocales/ur.js +1 -0
- package/public/dayjslocales/uz-latn.js +1 -0
- package/public/dayjslocales/uz.js +1 -0
- package/public/dayjslocales/vi.js +1 -0
- package/public/dayjslocales/x-pseudo.js +1 -0
- package/public/dayjslocales/yo.js +1 -0
- package/public/dayjslocales/zh-cn.js +1 -0
- package/public/dayjslocales/zh-hk.js +1 -0
- package/public/dayjslocales/zh-tw.js +1 -0
- package/public/dayjslocales/zh.js +1 -0
- package/public/gridedit.js +2 -2
- package/public/log_viewer_utils.js +156 -0
- package/public/saltcorn-builder.css +43 -2
- package/public/saltcorn-common.js +39 -29
- package/public/saltcorn.js +26 -8
- package/public/tabulator_bootstrap5.min.css +1 -0
- package/restart_watcher.js +1 -0
- package/routes/actions.js +158 -16
- package/routes/admin.js +83 -9
- package/routes/common_lists.js +40 -17
- package/routes/fields.js +18 -3
- package/routes/homepage.js +2 -1
- package/routes/page.js +30 -13
- package/routes/page_groupedit.js +104 -83
- package/routes/pageedit.js +4 -3
- package/routes/tables.js +34 -0
- package/routes/tag_entries.js +6 -1
- package/routes/tags.js +4 -0
- package/routes/utils.js +23 -2
- package/routes/view.js +5 -1
- package/routes/viewedit.js +9 -0
- package/serve.js +177 -10
- package/tests/admin.test.js +17 -11
- package/tests/api.test.js +27 -0
- package/tests/fields.test.js +132 -5
- package/tests/help.test.js +37 -0
- package/tests/page_group.test.js +1 -0
- package/tests/plugins.test.js +0 -12
- package/tests/table.test.js +1 -5
- package/tests/view.test.js +12 -0
- package/tests/viewedit.test.js +52 -8
- package/wrapper.js +9 -1
package/routes/page.js
CHANGED
|
@@ -16,6 +16,7 @@ const {
|
|
|
16
16
|
isAdmin,
|
|
17
17
|
sendHtmlFile,
|
|
18
18
|
getEligiblePage,
|
|
19
|
+
getRandomPage,
|
|
19
20
|
} = require("../routes/utils.js");
|
|
20
21
|
const { isTest } = require("@saltcorn/data/utils");
|
|
21
22
|
const { add_edit_bar } = require("../markup/admin.js");
|
|
@@ -88,20 +89,36 @@ const runPage = async (page, req, res, tic) => {
|
|
|
88
89
|
const runPageGroup = async (pageGroup, req, res, tic) => {
|
|
89
90
|
const role = req.user && req.user.id ? req.user.role_id : 100;
|
|
90
91
|
if (role <= pageGroup.min_role) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (!
|
|
92
|
+
if (pageGroup.random_allocation) {
|
|
93
|
+
const page = getRandomPage(pageGroup, req);
|
|
94
|
+
if (typeof page === "string") {
|
|
95
|
+
getState().log(2, page);
|
|
96
|
+
res.status(400).sendWrap(req.__("Internal Error"), page);
|
|
97
|
+
} else if (!page) {
|
|
98
|
+
getState().log(2, `Unable to find a random page in ${pageGroup.name}`);
|
|
99
|
+
res
|
|
100
|
+
.status(404)
|
|
101
|
+
.sendWrap(
|
|
102
|
+
req.__("Internal Error"),
|
|
103
|
+
req.__("Unable to find a random page in %s", pageGroup.name)
|
|
104
|
+
);
|
|
105
|
+
} else await runPage(page, req, res, tic);
|
|
97
106
|
} else {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
.
|
|
101
|
-
.sendWrap(
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
107
|
+
const eligible = await getEligiblePage(pageGroup, req, res);
|
|
108
|
+
if (typeof eligible === "string") {
|
|
109
|
+
getState().log(2, eligible);
|
|
110
|
+
res.status(400).sendWrap(req.__("Internal Error"), eligible);
|
|
111
|
+
} else if (eligible) {
|
|
112
|
+
if (!eligible.isReload) await runPage(eligible, req, res, tic);
|
|
113
|
+
} else {
|
|
114
|
+
getState().log(2, `Pagegroup ${pageGroup.name} has no eligible page`);
|
|
115
|
+
res
|
|
116
|
+
.status(404)
|
|
117
|
+
.sendWrap(
|
|
118
|
+
req.__("Internal Error"),
|
|
119
|
+
req.__("%s has no eligible page", pageGroup.name)
|
|
120
|
+
);
|
|
121
|
+
}
|
|
105
122
|
}
|
|
106
123
|
} else {
|
|
107
124
|
getState().log(2, `Pagegroup ${pageGroup.name} not authorized`);
|
package/routes/page_groupedit.js
CHANGED
|
@@ -29,9 +29,17 @@ const groupPropsForm = async (req, isNew) => {
|
|
|
29
29
|
...(isNew
|
|
30
30
|
? {}
|
|
31
31
|
: {
|
|
32
|
-
onChange: `
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
onChange: `
|
|
33
|
+
saveAndContinue(this, (res) => {
|
|
34
|
+
history.replaceState(null, '', res.responseJSON.row.name);
|
|
35
|
+
const arrowsVisible = $('#upDownArrowsId').length > 0;
|
|
36
|
+
if (
|
|
37
|
+
arrowsVisible && res.responseJSON.row.random_allocation ||
|
|
38
|
+
!arrowsVisible && !res.responseJSON.row.random_allocation
|
|
39
|
+
) {
|
|
40
|
+
window.location.reload();
|
|
41
|
+
}
|
|
42
|
+
});`,
|
|
35
43
|
}),
|
|
36
44
|
noSubmitButton: !isNew,
|
|
37
45
|
fields: [
|
|
@@ -68,65 +76,78 @@ const groupPropsForm = async (req, isNew) => {
|
|
|
68
76
|
input_type: "select",
|
|
69
77
|
options: roles.map((r) => ({ value: r.id, label: r.role })),
|
|
70
78
|
},
|
|
79
|
+
{
|
|
80
|
+
name: "random_allocation",
|
|
81
|
+
label: req.__("Random allocation"),
|
|
82
|
+
type: "Bool",
|
|
83
|
+
sublabel: req.__(
|
|
84
|
+
"Serve a random page, ignoring the eligible formula. " +
|
|
85
|
+
"Within a session, reloads will always deliver the same page. " +
|
|
86
|
+
"This is a basic requirement for A/B testing."
|
|
87
|
+
),
|
|
88
|
+
},
|
|
71
89
|
],
|
|
72
90
|
});
|
|
73
91
|
};
|
|
74
92
|
|
|
75
|
-
const memberForm = async (action, req,
|
|
93
|
+
const memberForm = async (action, req, group, pageValidator) => {
|
|
76
94
|
const pageOptions = (await Page.find()).map((p) => p.name);
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
attributes: {
|
|
88
|
-
options: pageOptions,
|
|
89
|
-
},
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
name: "description",
|
|
93
|
-
label: req.__("Description"),
|
|
94
|
-
type: "String",
|
|
95
|
-
sublabel: req.__("A description of the group member"),
|
|
95
|
+
const fields = [
|
|
96
|
+
{
|
|
97
|
+
name: "page_name",
|
|
98
|
+
label: req.__("Page"),
|
|
99
|
+
sublabel: req.__("Page to be served"),
|
|
100
|
+
type: "String",
|
|
101
|
+
required: true,
|
|
102
|
+
validator: pageValidator,
|
|
103
|
+
attributes: {
|
|
104
|
+
options: pageOptions,
|
|
96
105
|
},
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: "description",
|
|
109
|
+
label: req.__("Description"),
|
|
110
|
+
type: "String",
|
|
111
|
+
sublabel: req.__("A description of the group member"),
|
|
112
|
+
},
|
|
113
|
+
];
|
|
114
|
+
if (!group.random_allocation) {
|
|
115
|
+
fields.push({
|
|
116
|
+
name: "eligible_formula",
|
|
117
|
+
label: req.__("Eligible Formula"),
|
|
118
|
+
sublabel:
|
|
119
|
+
req.__("Formula to determine if this page should be served.") +
|
|
120
|
+
br() +
|
|
121
|
+
span(
|
|
122
|
+
"Variables in scope: ",
|
|
123
|
+
[
|
|
124
|
+
"width",
|
|
125
|
+
"height",
|
|
126
|
+
"innerWidth",
|
|
127
|
+
"innerHeight",
|
|
128
|
+
"user",
|
|
129
|
+
"locale",
|
|
130
|
+
"device",
|
|
131
|
+
]
|
|
132
|
+
.map((f) => code(f))
|
|
133
|
+
.join(", ")
|
|
134
|
+
),
|
|
135
|
+
help: {
|
|
136
|
+
topic: "Eligible Formula",
|
|
123
137
|
},
|
|
124
|
-
|
|
138
|
+
type: "String",
|
|
139
|
+
required: true,
|
|
140
|
+
class: "validate-expression",
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
return new Form({
|
|
144
|
+
action,
|
|
145
|
+
fields,
|
|
125
146
|
additionalButtons: [
|
|
126
147
|
{
|
|
127
148
|
label: req.__("Cancel"),
|
|
128
149
|
class: "btn btn-primary",
|
|
129
|
-
onclick: `cancelMemberEdit('${
|
|
150
|
+
onclick: `cancelMemberEdit('${group.name}');`,
|
|
130
151
|
},
|
|
131
152
|
],
|
|
132
153
|
});
|
|
@@ -142,7 +163,7 @@ const editMemberForm = async (member, req) => {
|
|
|
142
163
|
return await memberForm(
|
|
143
164
|
`/page_groupedit/edit-member/${member.id}`,
|
|
144
165
|
req,
|
|
145
|
-
group
|
|
166
|
+
group,
|
|
146
167
|
validator
|
|
147
168
|
);
|
|
148
169
|
};
|
|
@@ -156,7 +177,7 @@ const addMemberForm = async (group, req) => {
|
|
|
156
177
|
return await memberForm(
|
|
157
178
|
`/page_groupedit/add-member/${group.name}`,
|
|
158
179
|
req,
|
|
159
|
-
group
|
|
180
|
+
group,
|
|
160
181
|
validator
|
|
161
182
|
);
|
|
162
183
|
};
|
|
@@ -224,7 +245,7 @@ const pageGroupMembers = async (pageGroup, req) => {
|
|
|
224
245
|
if (members.length <= 1) return "";
|
|
225
246
|
else
|
|
226
247
|
return div(
|
|
227
|
-
{ class: "container" },
|
|
248
|
+
{ class: "container", id: "upDownArrowsId" },
|
|
228
249
|
div(
|
|
229
250
|
{ class: "row" },
|
|
230
251
|
div(
|
|
@@ -266,38 +287,38 @@ const pageGroupMembers = async (pageGroup, req) => {
|
|
|
266
287
|
)
|
|
267
288
|
);
|
|
268
289
|
};
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
key: (member) =>
|
|
284
|
-
link(`/page_groupedit/edit-member/${member.id}`, req.__("Edit")),
|
|
285
|
-
},
|
|
286
|
-
{
|
|
287
|
-
label: req.__("Delete"),
|
|
288
|
-
key: (member) =>
|
|
289
|
-
post_delete_btn(
|
|
290
|
-
`/page_groupedit/remove-member/${member.id}`,
|
|
291
|
-
req,
|
|
292
|
-
req.__("Member %s", member.sequence)
|
|
293
|
-
),
|
|
294
|
-
},
|
|
295
|
-
],
|
|
296
|
-
members,
|
|
290
|
+
const tblArr = [
|
|
291
|
+
{
|
|
292
|
+
label: req.__("Page"),
|
|
293
|
+
key: (r) =>
|
|
294
|
+
link(`/page/${pageIdToName[r.page_id]}`, pageIdToName[r.page_id]),
|
|
295
|
+
},
|
|
296
|
+
];
|
|
297
|
+
if (!pageGroup.random_allocation) {
|
|
298
|
+
tblArr.push({
|
|
299
|
+
label: "",
|
|
300
|
+
key: (r) => upDownBtns(r, req),
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
tblArr.push(
|
|
297
304
|
{
|
|
298
|
-
|
|
305
|
+
label: req.__("Edit"),
|
|
306
|
+
key: (member) =>
|
|
307
|
+
link(`/page_groupedit/edit-member/${member.id}`, req.__("Edit")),
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
label: req.__("Delete"),
|
|
311
|
+
key: (member) =>
|
|
312
|
+
post_delete_btn(
|
|
313
|
+
`/page_groupedit/remove-member/${member.id}`,
|
|
314
|
+
req,
|
|
315
|
+
req.__("Member %s", member.sequence)
|
|
316
|
+
),
|
|
299
317
|
}
|
|
300
318
|
);
|
|
319
|
+
return mkTable(tblArr, members, {
|
|
320
|
+
hover: true,
|
|
321
|
+
});
|
|
301
322
|
};
|
|
302
323
|
|
|
303
324
|
/**
|
package/routes/pageedit.js
CHANGED
|
@@ -186,7 +186,7 @@ const pageBuilderData = async (req, context) => {
|
|
|
186
186
|
for (const view of views) {
|
|
187
187
|
fixed_state_fields[view.name] = [];
|
|
188
188
|
const table = Table.findOne(view.table_id || view.exttable_name);
|
|
189
|
-
|
|
189
|
+
if (table) view.table_name = table.name;
|
|
190
190
|
const fs = await view.get_state_fields();
|
|
191
191
|
for (const frec of fs) {
|
|
192
192
|
const f = new Field(frec);
|
|
@@ -219,9 +219,10 @@ const pageBuilderData = async (req, context) => {
|
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
221
|
}
|
|
222
|
+
|
|
222
223
|
//console.log(fixed_state_fields.ListTasks);
|
|
223
224
|
return {
|
|
224
|
-
views,
|
|
225
|
+
views: views.map((v) => v.select_option),
|
|
225
226
|
images,
|
|
226
227
|
pages,
|
|
227
228
|
page_groups,
|
|
@@ -383,7 +384,7 @@ const wrap = (contents, noCard, req, page) => ({
|
|
|
383
384
|
crumbs: [
|
|
384
385
|
{ text: req.__("Pages"), href: "/pageedit" },
|
|
385
386
|
page
|
|
386
|
-
? { href: `/page/${page.name}`, text: page.name }
|
|
387
|
+
? { href: `/page/${encodeURIComponent(page.name)}`, text: page.name }
|
|
387
388
|
: { text: req.__("New") },
|
|
388
389
|
],
|
|
389
390
|
},
|
package/routes/tables.js
CHANGED
|
@@ -832,6 +832,7 @@ router.get(
|
|
|
832
832
|
}
|
|
833
833
|
viewCard = {
|
|
834
834
|
type: "card",
|
|
835
|
+
id: "table-views",
|
|
835
836
|
title: req.__("Views of this table"),
|
|
836
837
|
contents:
|
|
837
838
|
viewCardContents +
|
|
@@ -994,6 +995,13 @@ router.get(
|
|
|
994
995
|
req,
|
|
995
996
|
true
|
|
996
997
|
),
|
|
998
|
+
table.name !== "users" &&
|
|
999
|
+
post_dropdown_item(
|
|
1000
|
+
`/table/delete/${table.id}`,
|
|
1001
|
+
'<i class="fas fa-trash"></i> ' + req.__("Delete table"),
|
|
1002
|
+
req,
|
|
1003
|
+
true
|
|
1004
|
+
),
|
|
997
1005
|
])
|
|
998
1006
|
)
|
|
999
1007
|
);
|
|
@@ -1173,6 +1181,32 @@ router.post(
|
|
|
1173
1181
|
res.redirect(`/table`);
|
|
1174
1182
|
return;
|
|
1175
1183
|
}
|
|
1184
|
+
const views = await View.find(
|
|
1185
|
+
t.id ? { table_id: t.id } : { exttable_name: t.name }
|
|
1186
|
+
);
|
|
1187
|
+
if (views.length) {
|
|
1188
|
+
req.flash(
|
|
1189
|
+
"error",
|
|
1190
|
+
`${text(t.name)} has views. Delete these first: <a href="/table/${
|
|
1191
|
+
t.name
|
|
1192
|
+
}#table-views">Views for ${text(t.name)}</a>`
|
|
1193
|
+
);
|
|
1194
|
+
res.redirect(`/table`);
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
if (t.id) {
|
|
1198
|
+
const triggers = await Trigger.find({ table_id: t.id });
|
|
1199
|
+
if (triggers.length) {
|
|
1200
|
+
req.flash(
|
|
1201
|
+
"error",
|
|
1202
|
+
`${text(
|
|
1203
|
+
t.name
|
|
1204
|
+
)} has triggers. Delete these first: <a href="/actions">Trigger list</a>`
|
|
1205
|
+
);
|
|
1206
|
+
res.redirect(`/table`);
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1176
1210
|
try {
|
|
1177
1211
|
await t.delete();
|
|
1178
1212
|
req.flash("success", req.__(`Table %s deleted`, t.name));
|
package/routes/tag_entries.js
CHANGED
|
@@ -30,7 +30,12 @@ const buildFields = (entryType, formOptions, req) => {
|
|
|
30
30
|
div(
|
|
31
31
|
{ class: "col-sm-10" },
|
|
32
32
|
select(
|
|
33
|
-
{
|
|
33
|
+
{
|
|
34
|
+
name: "ids",
|
|
35
|
+
class: "form-control form-select",
|
|
36
|
+
multiple: true,
|
|
37
|
+
size: 20,
|
|
38
|
+
},
|
|
34
39
|
list.map((entry) => {
|
|
35
40
|
return option({ value: entry.id, label: entry.name });
|
|
36
41
|
})
|
package/routes/tags.js
CHANGED
|
@@ -27,6 +27,7 @@ const {
|
|
|
27
27
|
const db = require("@saltcorn/data/db");
|
|
28
28
|
const { getState } = require("@saltcorn/data/db/state");
|
|
29
29
|
const { create_pack_from_tag } = require("@saltcorn/admin-models/models/pack");
|
|
30
|
+
const Table = require("@saltcorn/data/models/table");
|
|
30
31
|
|
|
31
32
|
const router = new Router();
|
|
32
33
|
module.exports = router;
|
|
@@ -161,6 +162,9 @@ router.get(
|
|
|
161
162
|
await setTableRefs(views);
|
|
162
163
|
const pages = await tag.getPages();
|
|
163
164
|
const triggers = await tag.getTriggers();
|
|
165
|
+
triggers.forEach((tr) => {
|
|
166
|
+
if (tr.table_id) tr.table_name = Table.findOne(tr.table_id)?.name;
|
|
167
|
+
});
|
|
164
168
|
const roles = await User.get_roles();
|
|
165
169
|
|
|
166
170
|
const tablesDomId = "tablesListId";
|
package/routes/utils.js
CHANGED
|
@@ -13,6 +13,7 @@ const {
|
|
|
13
13
|
features,
|
|
14
14
|
} = require("@saltcorn/data/db/state");
|
|
15
15
|
const { get_base_url } = require("@saltcorn/data/models/config");
|
|
16
|
+
const { hash } = require("@saltcorn/data/utils");
|
|
16
17
|
const { input, script, domReady } = require("@saltcorn/markup/tags");
|
|
17
18
|
const session = require("express-session");
|
|
18
19
|
const cookieSession = require("cookie-session");
|
|
@@ -21,6 +22,7 @@ const { validateHeaderName, validateHeaderValue } = require("http");
|
|
|
21
22
|
const Crash = require("@saltcorn/data/models/crash");
|
|
22
23
|
const File = require("@saltcorn/data/models/file");
|
|
23
24
|
const User = require("@saltcorn/data/models/user");
|
|
25
|
+
const Page = require("@saltcorn/data/models/page");
|
|
24
26
|
const si = require("systeminformation");
|
|
25
27
|
const {
|
|
26
28
|
config_fields_form,
|
|
@@ -30,6 +32,7 @@ const {
|
|
|
30
32
|
} = require("../markup/admin.js");
|
|
31
33
|
const path = require("path");
|
|
32
34
|
const { UAParser } = require("ua-parser-js");
|
|
35
|
+
const crypto = require("crypto");
|
|
33
36
|
|
|
34
37
|
const get_sys_info = async () => {
|
|
35
38
|
const disks = await si.fsSize();
|
|
@@ -144,9 +147,10 @@ const set_custom_http_headers = (res, req, state) => {
|
|
|
144
147
|
/**
|
|
145
148
|
* Tries to recognize tenant from HTTP Request
|
|
146
149
|
* @param {object} req
|
|
150
|
+
* @param {number|undefined} hostPartsOffset (optional) for socketIO, to get the tenant with localhost
|
|
147
151
|
* @returns {string}
|
|
148
152
|
*/
|
|
149
|
-
const get_tenant_from_req = (req) => {
|
|
153
|
+
const get_tenant_from_req = (req, hostPartsOffset) => {
|
|
150
154
|
if (req.subdomains && req.subdomains.length > 0)
|
|
151
155
|
return req.subdomains[req.subdomains.length - 1];
|
|
152
156
|
|
|
@@ -154,7 +158,8 @@ const get_tenant_from_req = (req) => {
|
|
|
154
158
|
return db.connectObj.default_schema;
|
|
155
159
|
if (!req.subdomains && req.headers.host) {
|
|
156
160
|
const parts = req.headers.host.split(".");
|
|
157
|
-
if (parts.length < 3
|
|
161
|
+
if (parts.length < (!hostPartsOffset ? 3 : 3 - hostPartsOffset))
|
|
162
|
+
return db.connectObj.default_schema;
|
|
158
163
|
else return parts[0];
|
|
159
164
|
}
|
|
160
165
|
};
|
|
@@ -505,6 +510,21 @@ const getEligiblePage = async (pageGroup, req, res) => {
|
|
|
505
510
|
}
|
|
506
511
|
};
|
|
507
512
|
|
|
513
|
+
/**
|
|
514
|
+
* @param {PageGroup} pageGroup
|
|
515
|
+
* @param {any} req
|
|
516
|
+
* @returns the page, null or an error msg
|
|
517
|
+
*/
|
|
518
|
+
const getRandomPage = (pageGroup, req) => {
|
|
519
|
+
if (pageGroup.members.length === 0)
|
|
520
|
+
return req.__("Pagegroup %s has no members", pageGroup.name);
|
|
521
|
+
const hash = crypto.createHash("sha1").update(req.sessionID).digest("hex");
|
|
522
|
+
const idx =
|
|
523
|
+
parseInt(hash.substring(hash.length - 4), 16) % pageGroup.members.length;
|
|
524
|
+
const sessionMember = pageGroup.members[idx];
|
|
525
|
+
return Page.findOne({ id: sessionMember.page_id });
|
|
526
|
+
};
|
|
527
|
+
|
|
508
528
|
module.exports = {
|
|
509
529
|
sqlsanitize,
|
|
510
530
|
csrfField,
|
|
@@ -524,4 +544,5 @@ module.exports = {
|
|
|
524
544
|
sendHtmlFile,
|
|
525
545
|
setRole,
|
|
526
546
|
getEligiblePage,
|
|
547
|
+
getRandomPage,
|
|
527
548
|
};
|
package/routes/view.js
CHANGED
|
@@ -79,6 +79,7 @@ router.get(
|
|
|
79
79
|
if ((title || "").includes("{{")) {
|
|
80
80
|
title = await view.interpolate_title_string(title, query);
|
|
81
81
|
}
|
|
82
|
+
title = { title };
|
|
82
83
|
if (isModal && view.attributes?.popup_width)
|
|
83
84
|
res.set(
|
|
84
85
|
"SaltcornModalWidth",
|
|
@@ -102,7 +103,10 @@ router.get(
|
|
|
102
103
|
if ((description || "").includes("{{")) {
|
|
103
104
|
description = await view.interpolate_title_string(description, query);
|
|
104
105
|
}
|
|
105
|
-
title =
|
|
106
|
+
title.description = description;
|
|
107
|
+
}
|
|
108
|
+
if (view.attributes?.no_menu) {
|
|
109
|
+
title.no_menu = true;
|
|
106
110
|
}
|
|
107
111
|
const tock = new Date();
|
|
108
112
|
const ms = tock.getTime() - tic.getTime();
|
package/routes/viewedit.js
CHANGED
|
@@ -217,6 +217,7 @@ const viewForm = async (req, tableOptions, roles, pages, values) => {
|
|
|
217
217
|
),
|
|
218
218
|
}),
|
|
219
219
|
new Field({
|
|
220
|
+
// legacy
|
|
220
221
|
name: "default_render_page",
|
|
221
222
|
label: req.__("Show on page"),
|
|
222
223
|
sublabel: req.__(
|
|
@@ -243,6 +244,14 @@ const viewForm = async (req, tableOptions, roles, pages, values) => {
|
|
|
243
244
|
},
|
|
244
245
|
showIf: { viewtemplate: hasTable },
|
|
245
246
|
}),
|
|
247
|
+
new Field({
|
|
248
|
+
name: "no_menu",
|
|
249
|
+
label: req.__("No menu"),
|
|
250
|
+
sublabel: req.__("Omit the menu from this view"),
|
|
251
|
+
tab: "View settings",
|
|
252
|
+
parent_field: "attributes",
|
|
253
|
+
type: "Bool",
|
|
254
|
+
}),
|
|
246
255
|
new Field({
|
|
247
256
|
name: "popup_title",
|
|
248
257
|
label: req.__("Title"),
|