@saltcorn/server 0.9.6-beta.2 → 0.9.6-beta.20
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 +6 -1
- package/auth/admin.js +55 -53
- package/auth/routes.js +28 -10
- package/auth/testhelp.js +86 -0
- package/help/Field label.tmd +11 -0
- package/help/Field types.tmd +39 -0
- package/help/Ownership field.tmd +76 -0
- package/help/Ownership formula.tmd +75 -0
- package/help/Table roles.tmd +20 -0
- package/help/User groups.tmd +35 -0
- package/load_plugins.js +33 -5
- package/locales/en.json +29 -1
- package/locales/it.json +3 -2
- package/markup/admin.js +1 -0
- package/markup/forms.js +5 -1
- package/package.json +9 -9
- package/public/log_viewer_utils.js +32 -0
- package/public/mermaid.min.js +705 -306
- package/public/saltcorn-builder.css +23 -0
- package/public/saltcorn-common.js +248 -80
- package/public/saltcorn.css +80 -0
- package/public/saltcorn.js +86 -2
- package/restart_watcher.js +1 -0
- package/routes/actions.js +27 -0
- package/routes/admin.js +175 -64
- package/routes/api.js +6 -0
- package/routes/common_lists.js +42 -32
- package/routes/fields.js +70 -42
- package/routes/homepage.js +2 -0
- package/routes/index.js +2 -0
- package/routes/menu.js +69 -4
- package/routes/notifications.js +90 -10
- package/routes/pageedit.js +18 -13
- package/routes/plugins.js +11 -2
- package/routes/registry.js +289 -0
- package/routes/search.js +10 -4
- package/routes/tables.js +51 -27
- package/routes/tenant.js +4 -15
- package/routes/utils.js +25 -8
- package/routes/view.js +1 -1
- package/routes/viewedit.js +11 -7
- package/serve.js +27 -5
- package/tests/edit.test.js +426 -0
- package/tests/fields.test.js +21 -0
- package/tests/filter.test.js +68 -0
- package/tests/page.test.js +2 -2
- package/tests/plugins.test.js +2 -0
- package/tests/sync.test.js +59 -0
- package/wrapper.js +4 -1
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
const Router = require("express-promise-router");
|
|
2
|
+
|
|
3
|
+
const db = require("@saltcorn/data/db");
|
|
4
|
+
const { mkTable, link, post_btn, renderForm } = require("@saltcorn/markup");
|
|
5
|
+
const {
|
|
6
|
+
script,
|
|
7
|
+
domReady,
|
|
8
|
+
a,
|
|
9
|
+
div,
|
|
10
|
+
i,
|
|
11
|
+
text,
|
|
12
|
+
button,
|
|
13
|
+
input,
|
|
14
|
+
label,
|
|
15
|
+
form,
|
|
16
|
+
ul,
|
|
17
|
+
li,
|
|
18
|
+
details,
|
|
19
|
+
summary,
|
|
20
|
+
} = require("@saltcorn/markup/tags");
|
|
21
|
+
const Table = require("@saltcorn/data/models/table");
|
|
22
|
+
const { isAdmin, error_catcher } = require("./utils");
|
|
23
|
+
const { send_infoarch_page } = require("../markup/admin.js");
|
|
24
|
+
const View = require("@saltcorn/data/models/view");
|
|
25
|
+
const Page = require("@saltcorn/data/models/page");
|
|
26
|
+
const Form = require("@saltcorn/data/models/form");
|
|
27
|
+
const {
|
|
28
|
+
table_pack,
|
|
29
|
+
view_pack,
|
|
30
|
+
plugin_pack,
|
|
31
|
+
page_pack,
|
|
32
|
+
page_group_pack,
|
|
33
|
+
role_pack,
|
|
34
|
+
library_pack,
|
|
35
|
+
trigger_pack,
|
|
36
|
+
tag_pack,
|
|
37
|
+
model_pack,
|
|
38
|
+
model_instance_pack,
|
|
39
|
+
event_log_pack,
|
|
40
|
+
install_pack,
|
|
41
|
+
} = require("@saltcorn/admin-models/models/pack");
|
|
42
|
+
const Trigger = require("@saltcorn/data/models/trigger");
|
|
43
|
+
/**
|
|
44
|
+
* @type {object}
|
|
45
|
+
* @const
|
|
46
|
+
* @namespace listRouter
|
|
47
|
+
* @category server
|
|
48
|
+
* @subcategory routes
|
|
49
|
+
*/
|
|
50
|
+
const router = new Router();
|
|
51
|
+
|
|
52
|
+
// export our router to be mounted by the parent application
|
|
53
|
+
module.exports = router;
|
|
54
|
+
|
|
55
|
+
async function asyncFilter(arr, cb) {
|
|
56
|
+
const filtered = [];
|
|
57
|
+
|
|
58
|
+
for (const element of arr) {
|
|
59
|
+
const needAdd = await cb(element);
|
|
60
|
+
|
|
61
|
+
if (needAdd) {
|
|
62
|
+
filtered.push(element);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return filtered;
|
|
67
|
+
}
|
|
68
|
+
router.get(
|
|
69
|
+
"/",
|
|
70
|
+
isAdmin,
|
|
71
|
+
error_catcher(async (req, res) => {
|
|
72
|
+
const { etype, ename, q } = req.query;
|
|
73
|
+
const qlink = q ? `&q=${encodeURIComponent(q)}` : "";
|
|
74
|
+
let edContents = "Choose an entity to edit";
|
|
75
|
+
const all_tables = await Table.find({}, { orderBy: "name", nocase: true });
|
|
76
|
+
const all_views = await View.find({}, { orderBy: "name", nocase: true });
|
|
77
|
+
const all_pages = await Page.find({}, { orderBy: "name", nocase: true });
|
|
78
|
+
const all_triggers = await Trigger.find(
|
|
79
|
+
{},
|
|
80
|
+
{ orderBy: "name", nocase: true }
|
|
81
|
+
);
|
|
82
|
+
let tables, views, pages, triggers;
|
|
83
|
+
if (q) {
|
|
84
|
+
const qlower = q.toLowerCase();
|
|
85
|
+
const includesQ = (s) => s.toLowerCase().includes(qlower);
|
|
86
|
+
|
|
87
|
+
tables = await asyncFilter(all_tables, async (t) => {
|
|
88
|
+
const pack = await table_pack(t);
|
|
89
|
+
return includesQ(JSON.stringify(pack));
|
|
90
|
+
});
|
|
91
|
+
views = await asyncFilter(all_views, async (t) => {
|
|
92
|
+
const pack = await view_pack(t);
|
|
93
|
+
return includesQ(JSON.stringify(pack));
|
|
94
|
+
});
|
|
95
|
+
pages = await asyncFilter(all_pages, async (t) => {
|
|
96
|
+
const pack = await page_pack(t);
|
|
97
|
+
return includesQ(JSON.stringify(pack));
|
|
98
|
+
});
|
|
99
|
+
triggers = await asyncFilter(all_triggers, async (t) => {
|
|
100
|
+
const pack = await trigger_pack(t);
|
|
101
|
+
return includesQ(JSON.stringify(pack));
|
|
102
|
+
});
|
|
103
|
+
} else {
|
|
104
|
+
tables = all_tables;
|
|
105
|
+
views = all_views;
|
|
106
|
+
pages = all_pages;
|
|
107
|
+
triggers = all_triggers;
|
|
108
|
+
}
|
|
109
|
+
const li_link = (etype1, ename1) =>
|
|
110
|
+
li(
|
|
111
|
+
a(
|
|
112
|
+
{
|
|
113
|
+
href: `/registry-editor?etype=${etype1}&ename=${encodeURIComponent(
|
|
114
|
+
ename1
|
|
115
|
+
)}${qlink}`,
|
|
116
|
+
class: etype1 === etype && ename1 === ename ? "fw-bold" : undefined,
|
|
117
|
+
},
|
|
118
|
+
ename1
|
|
119
|
+
)
|
|
120
|
+
);
|
|
121
|
+
const mkForm = (jsonVal) =>
|
|
122
|
+
new Form({
|
|
123
|
+
labelCols: 0,
|
|
124
|
+
action: `/registry-editor?etype=${etype}&ename=${encodeURIComponent(
|
|
125
|
+
ename
|
|
126
|
+
)}${qlink}`,
|
|
127
|
+
|
|
128
|
+
values: { regval: JSON.stringify(jsonVal, null, 2) },
|
|
129
|
+
fields: [
|
|
130
|
+
{
|
|
131
|
+
name: "regval",
|
|
132
|
+
label: "",
|
|
133
|
+
input_type: "code",
|
|
134
|
+
attributes: { mode: "application/json" },
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
});
|
|
138
|
+
switch (etype) {
|
|
139
|
+
case "table":
|
|
140
|
+
const tpack = await table_pack(
|
|
141
|
+
all_tables.find((t) => t.name === ename)
|
|
142
|
+
);
|
|
143
|
+
edContents = renderForm(mkForm(tpack), req.csrfToken());
|
|
144
|
+
break;
|
|
145
|
+
case "view":
|
|
146
|
+
const vpack = await view_pack(all_views.find((v) => v.name === ename));
|
|
147
|
+
edContents = renderForm(mkForm(vpack), req.csrfToken());
|
|
148
|
+
break;
|
|
149
|
+
case "page":
|
|
150
|
+
const ppack = await page_pack(all_pages.find((v) => v.name === ename));
|
|
151
|
+
edContents = renderForm(mkForm(ppack), req.csrfToken());
|
|
152
|
+
break;
|
|
153
|
+
case "trigger":
|
|
154
|
+
const trpack = await trigger_pack(
|
|
155
|
+
all_triggers.find((t) => t.name === ename)
|
|
156
|
+
);
|
|
157
|
+
edContents = renderForm(mkForm(trpack), req.csrfToken());
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
send_infoarch_page({
|
|
161
|
+
res,
|
|
162
|
+
req,
|
|
163
|
+
active_sub: "Registry editor",
|
|
164
|
+
contents: {
|
|
165
|
+
widths: [3, 9],
|
|
166
|
+
besides: [
|
|
167
|
+
{
|
|
168
|
+
type: "card",
|
|
169
|
+
bodyClass: "p-1",
|
|
170
|
+
title: "Entities",
|
|
171
|
+
contents: div(
|
|
172
|
+
form(
|
|
173
|
+
{ method: "GET", action: `/registry-editor` },
|
|
174
|
+
div(
|
|
175
|
+
{ class: "input-group search-bar mb-2" },
|
|
176
|
+
etype &&
|
|
177
|
+
input({ type: "hidden", name: "etype", value: etype }),
|
|
178
|
+
ename &&
|
|
179
|
+
input({ type: "hidden", name: "ename", value: ename }),
|
|
180
|
+
input({
|
|
181
|
+
type: "search",
|
|
182
|
+
class: "form-control search-bar ps-2 hasbl",
|
|
183
|
+
placeholder: "Search",
|
|
184
|
+
name: "q",
|
|
185
|
+
value: q,
|
|
186
|
+
"aria-label": "Search",
|
|
187
|
+
"aria-describedby": "button-search-submit",
|
|
188
|
+
}),
|
|
189
|
+
button(
|
|
190
|
+
{
|
|
191
|
+
class: "btn btn-outline-secondary search-bar",
|
|
192
|
+
type: "submit",
|
|
193
|
+
},
|
|
194
|
+
i({ class: "fas fa-search" })
|
|
195
|
+
)
|
|
196
|
+
)
|
|
197
|
+
),
|
|
198
|
+
// following https://iamkate.com/code/tree-views/
|
|
199
|
+
ul(
|
|
200
|
+
{ class: "katetree ps-2" },
|
|
201
|
+
li(
|
|
202
|
+
details(
|
|
203
|
+
{ open: q || etype === "table" },
|
|
204
|
+
summary("Tables"),
|
|
205
|
+
ul(
|
|
206
|
+
{ class: "ps-3" },
|
|
207
|
+
tables.map((t) => li_link("table", t.name))
|
|
208
|
+
)
|
|
209
|
+
)
|
|
210
|
+
),
|
|
211
|
+
li(
|
|
212
|
+
details(
|
|
213
|
+
{ open: q || etype === "view" },
|
|
214
|
+
summary("Views"),
|
|
215
|
+
ul(
|
|
216
|
+
{ class: "ps-3" },
|
|
217
|
+
views.map((v) => li_link("view", v.name))
|
|
218
|
+
)
|
|
219
|
+
)
|
|
220
|
+
),
|
|
221
|
+
li(
|
|
222
|
+
details(
|
|
223
|
+
{ open: q || etype === "page" }, //
|
|
224
|
+
summary("Pages"),
|
|
225
|
+
ul(
|
|
226
|
+
{ class: "ps-3" },
|
|
227
|
+
pages.map((p) => li_link("page", p.name))
|
|
228
|
+
)
|
|
229
|
+
)
|
|
230
|
+
),
|
|
231
|
+
li(
|
|
232
|
+
details(
|
|
233
|
+
{ open: q || etype === "trigger" }, //
|
|
234
|
+
summary("Triggers"),
|
|
235
|
+
ul(
|
|
236
|
+
{ class: "ps-3" },
|
|
237
|
+
triggers.map((t) => li_link("trigger", t.name))
|
|
238
|
+
)
|
|
239
|
+
)
|
|
240
|
+
)
|
|
241
|
+
)
|
|
242
|
+
),
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
type: "card",
|
|
246
|
+
title:
|
|
247
|
+
ename && etype
|
|
248
|
+
? `Registry editor: ${ename} ${etype}`
|
|
249
|
+
: "Registry editor",
|
|
250
|
+
contents: edContents,
|
|
251
|
+
},
|
|
252
|
+
],
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
})
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
router.post(
|
|
259
|
+
"/",
|
|
260
|
+
isAdmin,
|
|
261
|
+
error_catcher(async (req, res) => {
|
|
262
|
+
const { etype, ename, q } = req.query;
|
|
263
|
+
const qlink = q ? `&q=${encodeURIComponent(q)}` : "";
|
|
264
|
+
|
|
265
|
+
const entVal = JSON.parse(req.body.regval);
|
|
266
|
+
let pack = { plugins: [], tables: [], views: [], pages: [], triggers: [] };
|
|
267
|
+
|
|
268
|
+
switch (etype) {
|
|
269
|
+
case "table":
|
|
270
|
+
pack.tables = [entVal];
|
|
271
|
+
break;
|
|
272
|
+
case "view":
|
|
273
|
+
pack.views = [entVal];
|
|
274
|
+
break;
|
|
275
|
+
case "page":
|
|
276
|
+
pack.pages = [entVal];
|
|
277
|
+
break;
|
|
278
|
+
case "trigger":
|
|
279
|
+
pack.triggers = [entVal];
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
await install_pack(pack);
|
|
283
|
+
res.redirect(
|
|
284
|
+
`/registry-editor?etype=${etype}&ename=${encodeURIComponent(
|
|
285
|
+
ename
|
|
286
|
+
)}${qlink}`
|
|
287
|
+
);
|
|
288
|
+
})
|
|
289
|
+
);
|
package/routes/search.js
CHANGED
|
@@ -202,7 +202,8 @@ const runSearch = async ({ q, _page, table }, req, res) => {
|
|
|
202
202
|
let tablesWithResults = [];
|
|
203
203
|
let tablesConfigured = 0;
|
|
204
204
|
for (const [tableName, viewName] of Object.entries(cfg)) {
|
|
205
|
-
if (!viewName || viewName === "")
|
|
205
|
+
if (!viewName || viewName === "" || viewName === "search_table_description")
|
|
206
|
+
continue;
|
|
206
207
|
tablesConfigured += 1;
|
|
207
208
|
if (table && tableName !== table) continue;
|
|
208
209
|
let sectionHeader = tableName;
|
|
@@ -232,7 +233,7 @@ const runSearch = async ({ q, _page, table }, req, res) => {
|
|
|
232
233
|
}
|
|
233
234
|
|
|
234
235
|
if (vresps.length > 0) {
|
|
235
|
-
tablesWithResults.push(tableName);
|
|
236
|
+
tablesWithResults.push({ tableName, label: sectionHeader });
|
|
236
237
|
resp.push({
|
|
237
238
|
type: "card",
|
|
238
239
|
title: span({ id: tableName }, sectionHeader),
|
|
@@ -273,8 +274,13 @@ const runSearch = async ({ q, _page, table }, req, res) => {
|
|
|
273
274
|
req.__("Show only matches in table:"),
|
|
274
275
|
" ",
|
|
275
276
|
tablesWithResults
|
|
276
|
-
.map((
|
|
277
|
-
a(
|
|
277
|
+
.map(({ tableName, label }) =>
|
|
278
|
+
a(
|
|
279
|
+
{
|
|
280
|
+
href: `javascript:set_state_field('table', '${tableName}')`,
|
|
281
|
+
},
|
|
282
|
+
label
|
|
283
|
+
)
|
|
278
284
|
)
|
|
279
285
|
.join(" | ")
|
|
280
286
|
)
|
package/routes/tables.js
CHANGED
|
@@ -93,14 +93,45 @@ const tableForm = async (table, req) => {
|
|
|
93
93
|
noSubmitButton: true,
|
|
94
94
|
onChange: "saveAndContinue(this)",
|
|
95
95
|
fields: [
|
|
96
|
+
{
|
|
97
|
+
label: req.__("Minimum role to read"),
|
|
98
|
+
sublabel: req.__(
|
|
99
|
+
"User must have this role or higher to read rows from the table, unless they are the owner"
|
|
100
|
+
),
|
|
101
|
+
help: {
|
|
102
|
+
topic: "Table roles",
|
|
103
|
+
context: {},
|
|
104
|
+
},
|
|
105
|
+
name: "min_role_read",
|
|
106
|
+
input_type: "select",
|
|
107
|
+
options: roleOptions,
|
|
108
|
+
attributes: { asideNext: !table.external && !table.provider_name },
|
|
109
|
+
},
|
|
96
110
|
...(!table.external && !table.provider_name
|
|
97
111
|
? [
|
|
112
|
+
{
|
|
113
|
+
label: req.__("Minimum role to write"),
|
|
114
|
+
name: "min_role_write",
|
|
115
|
+
input_type: "select",
|
|
116
|
+
help: {
|
|
117
|
+
topic: "Table roles",
|
|
118
|
+
context: {},
|
|
119
|
+
},
|
|
120
|
+
sublabel: req.__(
|
|
121
|
+
"User must have this role or higher to edit or create new rows in the table, unless they are the owner"
|
|
122
|
+
),
|
|
123
|
+
options: roleOptions,
|
|
124
|
+
},
|
|
98
125
|
{
|
|
99
126
|
label: req.__("Ownership field"),
|
|
100
127
|
name: "ownership_field_id",
|
|
101
128
|
sublabel: req.__(
|
|
102
129
|
"The user referred to in this field will be the owner of the row"
|
|
103
130
|
),
|
|
131
|
+
help: {
|
|
132
|
+
topic: "Ownership field",
|
|
133
|
+
context: {},
|
|
134
|
+
},
|
|
104
135
|
input_type: "select",
|
|
105
136
|
options: [
|
|
106
137
|
{ value: "", label: req.__("None") },
|
|
@@ -114,6 +145,10 @@ const tableForm = async (table, req) => {
|
|
|
114
145
|
validator: expressionValidator,
|
|
115
146
|
type: "String",
|
|
116
147
|
class: "validate-expression",
|
|
148
|
+
help: {
|
|
149
|
+
topic: "Ownership formula",
|
|
150
|
+
context: {},
|
|
151
|
+
},
|
|
117
152
|
sublabel:
|
|
118
153
|
req.__("User is treated as owner if true. In scope: ") +
|
|
119
154
|
["user", ...fields.map((f) => f.name)]
|
|
@@ -126,6 +161,10 @@ const tableForm = async (table, req) => {
|
|
|
126
161
|
sublabel: req.__(
|
|
127
162
|
"Add relations to this table in dropdown options for ownership field"
|
|
128
163
|
),
|
|
164
|
+
help: {
|
|
165
|
+
topic: "User groups",
|
|
166
|
+
context: {},
|
|
167
|
+
},
|
|
129
168
|
name: "is_user_group",
|
|
130
169
|
type: "Bool",
|
|
131
170
|
},
|
|
@@ -141,28 +180,9 @@ const tableForm = async (table, req) => {
|
|
|
141
180
|
),
|
|
142
181
|
//options: roleOptions,
|
|
143
182
|
},
|
|
144
|
-
{
|
|
145
|
-
label: req.__("Minimum role to read"),
|
|
146
|
-
sublabel: req.__(
|
|
147
|
-
"User must have this role or higher to read rows from the table, unless they are the owner"
|
|
148
|
-
),
|
|
149
|
-
name: "min_role_read",
|
|
150
|
-
input_type: "select",
|
|
151
|
-
options: roleOptions,
|
|
152
|
-
attributes: { asideNext: !table.external && !table.provider_name },
|
|
153
|
-
},
|
|
154
183
|
...(table.external || table.provider_name
|
|
155
184
|
? []
|
|
156
185
|
: [
|
|
157
|
-
{
|
|
158
|
-
label: req.__("Minimum role to write"),
|
|
159
|
-
name: "min_role_write",
|
|
160
|
-
input_type: "select",
|
|
161
|
-
sublabel: req.__(
|
|
162
|
-
"User must have this role or higher to edit or create new rows in the table, unless they are the owner"
|
|
163
|
-
),
|
|
164
|
-
options: roleOptions,
|
|
165
|
-
},
|
|
166
186
|
{
|
|
167
187
|
label: req.__("Version history"),
|
|
168
188
|
sublabel: req.__("Track table data changes over time"),
|
|
@@ -681,6 +701,10 @@ const attribBadges = (f) => {
|
|
|
681
701
|
"on_delete_cascade",
|
|
682
702
|
"on_delete",
|
|
683
703
|
"unique_error_msg",
|
|
704
|
+
"ref",
|
|
705
|
+
"table",
|
|
706
|
+
"agg_field",
|
|
707
|
+
"agg_relation",
|
|
684
708
|
].includes(k)
|
|
685
709
|
)
|
|
686
710
|
return;
|
|
@@ -750,6 +774,14 @@ router.get(
|
|
|
750
774
|
r.typename +
|
|
751
775
|
span({ class: "badge bg-danger ms-1" }, "Unknown type"),
|
|
752
776
|
},
|
|
777
|
+
...(table.external
|
|
778
|
+
? []
|
|
779
|
+
: [
|
|
780
|
+
{
|
|
781
|
+
label: req.__("Edit"),
|
|
782
|
+
key: (r) => link(`/field/${r.id}`, req.__("Edit")),
|
|
783
|
+
},
|
|
784
|
+
]),
|
|
753
785
|
{
|
|
754
786
|
label: "",
|
|
755
787
|
key: (r) => typeBadges(r, req),
|
|
@@ -759,14 +791,6 @@ router.get(
|
|
|
759
791
|
key: (r) => attribBadges(r),
|
|
760
792
|
},
|
|
761
793
|
{ label: req.__("Variable name"), key: (t) => code(t.name) },
|
|
762
|
-
...(table.external
|
|
763
|
-
? []
|
|
764
|
-
: [
|
|
765
|
-
{
|
|
766
|
-
label: req.__("Edit"),
|
|
767
|
-
key: (r) => link(`/field/${r.id}`, req.__("Edit")),
|
|
768
|
-
},
|
|
769
|
-
]),
|
|
770
794
|
...(table.external || db.isSQLite
|
|
771
795
|
? []
|
|
772
796
|
: [
|
package/routes/tenant.js
CHANGED
|
@@ -44,7 +44,7 @@ const {
|
|
|
44
44
|
const db = require("@saltcorn/data/db");
|
|
45
45
|
|
|
46
46
|
const { loadAllPlugins, loadAndSaveNewPlugin } = require("../load_plugins");
|
|
47
|
-
const { isAdmin, error_catcher } = require("./utils.js");
|
|
47
|
+
const { isAdmin, error_catcher, is_ip_address } = require("./utils.js");
|
|
48
48
|
const User = require("@saltcorn/data/models/user");
|
|
49
49
|
const File = require("@saltcorn/data/models/file");
|
|
50
50
|
const {
|
|
@@ -117,22 +117,10 @@ const create_tenant_allowed = (req) => {
|
|
|
117
117
|
return user_role <= required_role;
|
|
118
118
|
};
|
|
119
119
|
|
|
120
|
-
/**
|
|
121
|
-
* Check that String is IPv4 address
|
|
122
|
-
* @param {string} hostname
|
|
123
|
-
* @returns {boolean|string[]}
|
|
124
|
-
*/
|
|
125
|
-
// TBD not sure that false is correct return if type of is not string
|
|
126
|
-
// TBD Add IPv6 support
|
|
127
|
-
const is_ip_address = (hostname) => {
|
|
128
|
-
if (typeof hostname !== "string") return false;
|
|
129
|
-
return hostname.split(".").every((s) => +s >= 0 && +s <= 255);
|
|
130
|
-
};
|
|
131
|
-
|
|
132
120
|
const get_cfg_tenant_base_url = (req) =>
|
|
133
121
|
remove_leading_chars(
|
|
134
122
|
".",
|
|
135
|
-
getRootState().getConfig("tenant_baseurl", req.hostname)
|
|
123
|
+
getRootState().getConfig("tenant_baseurl", req.hostname) || req.hostname
|
|
136
124
|
)
|
|
137
125
|
.replace("http://", "")
|
|
138
126
|
.replace("https://", "");
|
|
@@ -268,7 +256,8 @@ router.post(
|
|
|
268
256
|
return;
|
|
269
257
|
}
|
|
270
258
|
// declare ui form
|
|
271
|
-
const
|
|
259
|
+
const base_url = get_cfg_tenant_base_url(req);
|
|
260
|
+
const form = tenant_form(req, base_url);
|
|
272
261
|
// validate ui form
|
|
273
262
|
const valres = form.validate(req.body);
|
|
274
263
|
if (valres.errors)
|
package/routes/utils.js
CHANGED
|
@@ -14,7 +14,7 @@ const {
|
|
|
14
14
|
} = require("@saltcorn/data/db/state");
|
|
15
15
|
const { get_base_url } = require("@saltcorn/data/models/config");
|
|
16
16
|
const { hash } = require("@saltcorn/data/utils");
|
|
17
|
-
const { input, script, domReady } = require("@saltcorn/markup/tags");
|
|
17
|
+
const { input, script, domReady, a } = require("@saltcorn/markup/tags");
|
|
18
18
|
const session = require("express-session");
|
|
19
19
|
const cookieSession = require("cookie-session");
|
|
20
20
|
const is = require("contractis/is");
|
|
@@ -77,11 +77,9 @@ function loggedIn(req, res, next) {
|
|
|
77
77
|
* @returns {void}
|
|
78
78
|
*/
|
|
79
79
|
function isAdmin(req, res, next) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
req.user.tenant === db.getTenantSchema()
|
|
84
|
-
) {
|
|
80
|
+
const cur_tenant = db.getTenantSchema();
|
|
81
|
+
//console.log({ cur_tenant, user: req.user });
|
|
82
|
+
if (req.user && req.user.role_id === 1 && req.user.tenant === cur_tenant) {
|
|
85
83
|
next();
|
|
86
84
|
} else {
|
|
87
85
|
req.flash("danger", req.__("Must be admin"));
|
|
@@ -157,6 +155,8 @@ const get_tenant_from_req = (req, hostPartsOffset) => {
|
|
|
157
155
|
if (req.subdomains && req.subdomains.length == 0)
|
|
158
156
|
return db.connectObj.default_schema;
|
|
159
157
|
if (!req.subdomains && req.headers.host) {
|
|
158
|
+
if (is_ip_address(req.headers.host.split(":")[0]))
|
|
159
|
+
return db.connectObj.default_schema;
|
|
160
160
|
const parts = req.headers.host.split(".");
|
|
161
161
|
if (parts.length < (!hostPartsOffset ? 3 : 3 - hostPartsOffset))
|
|
162
162
|
return db.connectObj.default_schema;
|
|
@@ -299,7 +299,7 @@ const getGitRevision = () => db.connectObj.git_commit;
|
|
|
299
299
|
* Gets session store
|
|
300
300
|
* @returns {session|cookieSession}
|
|
301
301
|
*/
|
|
302
|
-
const getSessionStore = () => {
|
|
302
|
+
const getSessionStore = (pruneInterval) => {
|
|
303
303
|
/*if (getState().getConfig("cookie_sessions", false)) {
|
|
304
304
|
return cookieSession({
|
|
305
305
|
keys: [db.connectObj.session_secret || is.str.generate()],
|
|
@@ -322,6 +322,7 @@ const getSessionStore = () => {
|
|
|
322
322
|
schemaName: db.connectObj.default_schema,
|
|
323
323
|
pool: db.pool,
|
|
324
324
|
tableName: "_sc_session",
|
|
325
|
+
pruneSessionInterval: pruneInterval > 0 ? pruneInterval : false,
|
|
325
326
|
}),
|
|
326
327
|
secret: db.connectObj.session_secret || is.str.generate(),
|
|
327
328
|
resave: false,
|
|
@@ -350,6 +351,18 @@ const is_relative_url = (url) => {
|
|
|
350
351
|
return typeof url === "string" && !url.includes(":/") && !url.includes("//");
|
|
351
352
|
};
|
|
352
353
|
|
|
354
|
+
/**
|
|
355
|
+
* Check that String is IPv4 address
|
|
356
|
+
* @param {string} hostname
|
|
357
|
+
* @returns {boolean|string[]}
|
|
358
|
+
*/
|
|
359
|
+
// TBD not sure that false is correct return if type of is not string
|
|
360
|
+
// TBD Add IPv6 support
|
|
361
|
+
const is_ip_address = (hostname) => {
|
|
362
|
+
if (typeof hostname !== "string") return false;
|
|
363
|
+
return hostname.split(".").every((s) => +s >= 0 && +s <= 255);
|
|
364
|
+
};
|
|
365
|
+
|
|
353
366
|
const admin_config_route = ({
|
|
354
367
|
router,
|
|
355
368
|
path,
|
|
@@ -398,7 +411,10 @@ const admin_config_route = ({
|
|
|
398
411
|
if (restart_required)
|
|
399
412
|
res.json({
|
|
400
413
|
success: "ok",
|
|
401
|
-
notify:
|
|
414
|
+
notify:
|
|
415
|
+
req.__("Restart required for changes to take effect.") +
|
|
416
|
+
" " +
|
|
417
|
+
a({ href: "/admin/system" }, req.__("Restart here")),
|
|
402
418
|
});
|
|
403
419
|
else res.json({ success: "ok" });
|
|
404
420
|
}
|
|
@@ -555,6 +571,7 @@ module.exports = {
|
|
|
555
571
|
get_tenant_from_req,
|
|
556
572
|
addOnDoneRedirect,
|
|
557
573
|
is_relative_url,
|
|
574
|
+
is_ip_address,
|
|
558
575
|
get_sys_info,
|
|
559
576
|
admin_config_route,
|
|
560
577
|
sendHtmlFile,
|
package/routes/view.js
CHANGED
package/routes/viewedit.js
CHANGED
|
@@ -371,6 +371,7 @@ router.get(
|
|
|
371
371
|
const form = await viewForm(req, tableOptions, roles, pages, viewrow);
|
|
372
372
|
const inbound_connected = await viewrow.inbound_connected_objects();
|
|
373
373
|
form.hidden("id");
|
|
374
|
+
form.onChange = `saveAndContinue(this)`;
|
|
374
375
|
res.sendWrap(req.__(`Edit view`), {
|
|
375
376
|
above: [
|
|
376
377
|
{
|
|
@@ -383,6 +384,7 @@ router.get(
|
|
|
383
384
|
{
|
|
384
385
|
type: "card",
|
|
385
386
|
class: "mt-0",
|
|
387
|
+
titleAjaxIndicator: true,
|
|
386
388
|
title: req.__(
|
|
387
389
|
`%s view - %s on %s`,
|
|
388
390
|
viewname,
|
|
@@ -541,12 +543,14 @@ router.post(
|
|
|
541
543
|
//console.log(v);
|
|
542
544
|
await View.create(v);
|
|
543
545
|
}
|
|
544
|
-
res.
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
546
|
+
if (req.xhr) res.json({ success: "ok" });
|
|
547
|
+
else
|
|
548
|
+
res.redirect(
|
|
549
|
+
addOnDoneRedirect(
|
|
550
|
+
`/viewedit/config/${encodeURIComponent(v.name)}`,
|
|
551
|
+
req
|
|
552
|
+
)
|
|
553
|
+
);
|
|
550
554
|
}
|
|
551
555
|
} else {
|
|
552
556
|
sendForm(form);
|
|
@@ -902,7 +906,7 @@ router.post(
|
|
|
902
906
|
? `/${req.query.on_done_redirect}`
|
|
903
907
|
: "/viewedit";
|
|
904
908
|
res.redirect(redirectTarget);
|
|
905
|
-
} else res.json({
|
|
909
|
+
} else res.json({ success: "ok" });
|
|
906
910
|
})
|
|
907
911
|
);
|
|
908
912
|
|