@saltcorn/server 0.7.4 → 0.8.0-beta.0
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 +18 -11
- package/auth/admin.js +173 -74
- package/auth/routes.js +40 -15
- package/locales/en.json +46 -3
- package/locales/es.json +134 -134
- package/locales/ru.json +32 -5
- package/markup/admin.js +40 -38
- package/markup/forms.js +4 -3
- package/package.json +8 -7
- package/public/diagram_utils.js +530 -0
- package/public/gridedit.js +4 -1
- package/public/jquery-menu-editor.min.js +112 -112
- package/public/saltcorn-common.js +31 -8
- package/public/saltcorn.css +11 -0
- package/public/saltcorn.js +211 -70
- package/restart_watcher.js +1 -0
- package/routes/actions.js +5 -1
- package/routes/admin.js +229 -79
- package/routes/api.js +19 -2
- package/routes/common_lists.js +137 -134
- package/routes/diagram.js +43 -117
- package/routes/fields.js +4 -1
- package/routes/files.js +137 -101
- package/routes/homepage.js +2 -2
- package/routes/infoarch.js +2 -2
- package/routes/list.js +4 -4
- package/routes/page.js +16 -3
- package/routes/pageedit.js +13 -8
- package/routes/scapi.js +1 -1
- package/routes/search.js +1 -1
- package/routes/tables.js +4 -5
- package/routes/tag_entries.js +31 -10
- package/routes/tags.js +10 -10
- package/routes/tenant.js +98 -36
- package/routes/utils.js +12 -0
- package/routes/view.js +0 -1
- package/routes/viewedit.js +45 -32
- package/serve.js +5 -0
- package/tests/admin.test.js +2 -0
- package/tests/auth.test.js +20 -0
- package/tests/files.test.js +11 -20
- package/tests/tenant.test.js +4 -2
package/routes/tag_entries.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
const {
|
|
2
|
-
a,
|
|
3
2
|
div,
|
|
4
|
-
text,
|
|
5
3
|
button,
|
|
6
|
-
i,
|
|
7
4
|
form,
|
|
8
5
|
select,
|
|
9
6
|
option,
|
|
@@ -81,10 +78,10 @@ const formOptions = async (type, tag_id) => {
|
|
|
81
78
|
),
|
|
82
79
|
};
|
|
83
80
|
}
|
|
84
|
-
case "
|
|
81
|
+
case "triggers": {
|
|
85
82
|
const ids = await tag.getTriggerIds();
|
|
86
83
|
return {
|
|
87
|
-
|
|
84
|
+
triggers: (Trigger.find()).filter(
|
|
88
85
|
(value) => ids.indexOf(value.id) === -1
|
|
89
86
|
),
|
|
90
87
|
};
|
|
@@ -101,7 +98,7 @@ router.get(
|
|
|
101
98
|
above: [
|
|
102
99
|
{
|
|
103
100
|
type: "breadcrumbs",
|
|
104
|
-
crumbs: [{ text: `Tag entry` }],
|
|
101
|
+
crumbs: [{ text: req.__(`Tag entry`) }],
|
|
105
102
|
},
|
|
106
103
|
{
|
|
107
104
|
type: "card",
|
|
@@ -120,15 +117,19 @@ router.get(
|
|
|
120
117
|
|
|
121
118
|
const idField = (entryType) => {
|
|
122
119
|
switch (entryType) {
|
|
123
|
-
case "tables":
|
|
120
|
+
case "tables":
|
|
121
|
+
case "table": {
|
|
124
122
|
return "table_id";
|
|
125
123
|
}
|
|
126
|
-
case "views":
|
|
124
|
+
case "views":
|
|
125
|
+
case "view": {
|
|
127
126
|
return "view_id";
|
|
128
127
|
}
|
|
129
|
-
case "pages":
|
|
128
|
+
case "pages":
|
|
129
|
+
case "page": {
|
|
130
130
|
return "page_id";
|
|
131
131
|
}
|
|
132
|
+
case "triggers":
|
|
132
133
|
case "trigger": {
|
|
133
134
|
return "trigger_id";
|
|
134
135
|
}
|
|
@@ -136,6 +137,7 @@ const idField = (entryType) => {
|
|
|
136
137
|
return null;
|
|
137
138
|
};
|
|
138
139
|
|
|
140
|
+
// add multiple objects to one tag
|
|
139
141
|
router.post(
|
|
140
142
|
"/add/:entry_type/:tag_id",
|
|
141
143
|
isAdmin,
|
|
@@ -155,6 +157,24 @@ router.post(
|
|
|
155
157
|
})
|
|
156
158
|
);
|
|
157
159
|
|
|
160
|
+
// add one object to multiple tags
|
|
161
|
+
router.post(
|
|
162
|
+
"/add/multiple_tags/:entry_type/:object_id",
|
|
163
|
+
isAdmin,
|
|
164
|
+
error_catcher(async (req, res) => {
|
|
165
|
+
let { entry_type, object_id } = req.params;
|
|
166
|
+
let { tag_ids } = req.body;
|
|
167
|
+
object_id = parseInt(object_id);
|
|
168
|
+
tag_ids = tag_ids.map((id) => parseInt(id));
|
|
169
|
+
const tags = (await Tag.find()).filter((tag) => tag_ids.includes(tag.id));
|
|
170
|
+
const fieldName = idField(entry_type);
|
|
171
|
+
for (const tag of tags) {
|
|
172
|
+
await tag.addEntry({ [fieldName]: object_id });
|
|
173
|
+
}
|
|
174
|
+
res.json({ tags });
|
|
175
|
+
})
|
|
176
|
+
);
|
|
177
|
+
|
|
158
178
|
router.post(
|
|
159
179
|
"/remove/:entry_type/:entry_id/:tag_id",
|
|
160
180
|
isAdmin,
|
|
@@ -168,6 +188,7 @@ router.post(
|
|
|
168
188
|
} else {
|
|
169
189
|
await TagEntry.update(entry.id, { [fieldName]: null });
|
|
170
190
|
}
|
|
171
|
-
res.redirect(`/tag/${tag_id}?show_list=${entry_type}`);
|
|
191
|
+
if (!req.xhr) res.redirect(`/tag/${tag_id}?show_list=${entry_type}`);
|
|
192
|
+
else res.json({ okay: true });
|
|
172
193
|
})
|
|
173
194
|
);
|
package/routes/tags.js
CHANGED
|
@@ -128,18 +128,18 @@ router.get(
|
|
|
128
128
|
const views = await tag.getViews();
|
|
129
129
|
await setTableRefs(views);
|
|
130
130
|
const pages = await tag.getPages();
|
|
131
|
-
const
|
|
131
|
+
const triggers = await tag.getTriggers();
|
|
132
132
|
const roles = await User.get_roles();
|
|
133
133
|
|
|
134
134
|
const tablesDomId = "tablesListId";
|
|
135
135
|
const viewsDomId = "viewsListId";
|
|
136
136
|
const pagesDomId = "pagesDomId";
|
|
137
|
-
const
|
|
137
|
+
const triggersDomId = "triggerDomId";
|
|
138
138
|
res.sendWrap(req.__("%s Tag", tag.name), {
|
|
139
139
|
above: [
|
|
140
140
|
{
|
|
141
141
|
type: "breadcrumbs",
|
|
142
|
-
crumbs: [{ text: `Tag:
|
|
142
|
+
crumbs: [{ text: req.__(`Tag: %s`, tag.name) }],
|
|
143
143
|
},
|
|
144
144
|
{
|
|
145
145
|
type: "card",
|
|
@@ -211,19 +211,19 @@ router.get(
|
|
|
211
211
|
type: "card",
|
|
212
212
|
bodyId: "collapseTriggerCard",
|
|
213
213
|
title: headerWithCollapser(
|
|
214
|
-
req.__("
|
|
215
|
-
|
|
216
|
-
isShowList(show_list, "
|
|
214
|
+
req.__("Triggers"),
|
|
215
|
+
triggersDomId,
|
|
216
|
+
isShowList(show_list, "triggers")
|
|
217
217
|
),
|
|
218
218
|
contents: [
|
|
219
|
-
getTriggerList(
|
|
219
|
+
getTriggerList(triggers, req, {
|
|
220
220
|
tagId: tag.id,
|
|
221
|
-
domId:
|
|
222
|
-
showList: isShowList(show_list, "
|
|
221
|
+
domId: triggersDomId,
|
|
222
|
+
showList: isShowList(show_list, "triggers"),
|
|
223
223
|
}),
|
|
224
224
|
a(
|
|
225
225
|
{
|
|
226
|
-
href: `/tag-entries/add/
|
|
226
|
+
href: `/tag-entries/add/triggers/${tag.id}`,
|
|
227
227
|
class: "btn btn-primary",
|
|
228
228
|
},
|
|
229
229
|
req.__("Add triggers")
|
package/routes/tenant.js
CHANGED
|
@@ -8,13 +8,14 @@
|
|
|
8
8
|
const Router = require("express-promise-router");
|
|
9
9
|
const Form = require("@saltcorn/data/models/form");
|
|
10
10
|
const { getState, add_tenant } = require("@saltcorn/data/db/state");
|
|
11
|
-
const { create_tenant } = require("@saltcorn/admin-models/models/tenant");
|
|
12
11
|
const {
|
|
12
|
+
create_tenant,
|
|
13
13
|
getAllTenants,
|
|
14
14
|
domain_sanitize,
|
|
15
15
|
deleteTenant,
|
|
16
16
|
switchToTenant,
|
|
17
17
|
insertTenant,
|
|
18
|
+
Tenant,
|
|
18
19
|
} = require("@saltcorn/admin-models/models/tenant");
|
|
19
20
|
const {
|
|
20
21
|
renderForm,
|
|
@@ -24,7 +25,6 @@ const {
|
|
|
24
25
|
} = require("@saltcorn/markup");
|
|
25
26
|
const {
|
|
26
27
|
div,
|
|
27
|
-
nbsp,
|
|
28
28
|
p,
|
|
29
29
|
a,
|
|
30
30
|
h4,
|
|
@@ -87,6 +87,11 @@ const tenant_form = (req) =>
|
|
|
87
87
|
input_type: "text",
|
|
88
88
|
postText: text(req.hostname),
|
|
89
89
|
},
|
|
90
|
+
{
|
|
91
|
+
name: "description",
|
|
92
|
+
label: req.__("Description"),
|
|
93
|
+
input_type: "text",
|
|
94
|
+
},
|
|
90
95
|
],
|
|
91
96
|
});
|
|
92
97
|
|
|
@@ -148,37 +153,49 @@ router.get(
|
|
|
148
153
|
"You are trying to create a tenant while connecting via an IP address rather than a domain. This will probably not work."
|
|
149
154
|
)
|
|
150
155
|
);
|
|
151
|
-
let
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
156
|
+
let create_tenant_warning_text = "";
|
|
157
|
+
if (getState().getConfig("create_tenant_warning")) {
|
|
158
|
+
create_tenant_warning_text = getState().getConfig("create_tenant_warning_text");
|
|
159
|
+
if (create_tenant_warning_text && create_tenant_warning_text.length > 0)
|
|
160
|
+
create_tenant_warning_text = div(
|
|
161
|
+
{
|
|
162
|
+
class: "alert alert-warning alert-dismissible fade show mt-5",
|
|
163
|
+
role: "alert",
|
|
164
|
+
},
|
|
165
|
+
h4(req.__("Warning")),
|
|
166
|
+
p( create_tenant_warning_text
|
|
167
|
+
)
|
|
168
|
+
);
|
|
169
|
+
else
|
|
170
|
+
create_tenant_warning_text = div(
|
|
171
|
+
{
|
|
172
|
+
class: "alert alert-warning alert-dismissible fade show mt-5",
|
|
173
|
+
role: "alert",
|
|
174
|
+
},
|
|
175
|
+
h4(req.__("Warning")),
|
|
176
|
+
p(
|
|
177
|
+
req.__(
|
|
178
|
+
"Hosting on this site is provided for free and with no guarantee of availability or security of your application. "
|
|
179
|
+
) +
|
|
180
|
+
" " +
|
|
181
|
+
req.__(
|
|
182
|
+
"This facility is intended solely for you to evaluate the suitability of Saltcorn. "
|
|
183
|
+
) +
|
|
184
|
+
" " +
|
|
185
|
+
req.__(
|
|
186
|
+
"If you would like to store private information that needs to be secure, please use self-hosted Saltcorn. "
|
|
187
|
+
) +
|
|
188
|
+
" " +
|
|
189
|
+
req.__(
|
|
190
|
+
'See <a href="https://github.com/saltcorn/saltcorn">GitHub repository</a> for instructions<p>'
|
|
191
|
+
)
|
|
192
|
+
)
|
|
193
|
+
);
|
|
194
|
+
}
|
|
178
195
|
|
|
179
196
|
res.sendWrap(
|
|
180
197
|
req.__("Create application"),
|
|
181
|
-
|
|
198
|
+
create_tenant_warning_text +
|
|
182
199
|
renderForm(tenant_form(req), req.csrfToken()) +
|
|
183
200
|
p(
|
|
184
201
|
{ class: "mt-2" },
|
|
@@ -246,6 +263,8 @@ router.post(
|
|
|
246
263
|
else {
|
|
247
264
|
// normalize domain name
|
|
248
265
|
const subdomain = domain_sanitize(valres.success.subdomain);
|
|
266
|
+
// get description
|
|
267
|
+
const description = valres.success.description;
|
|
249
268
|
// get list of tenants
|
|
250
269
|
const allTens = await getAllTenants();
|
|
251
270
|
if (allTens.includes(subdomain) || !subdomain) {
|
|
@@ -258,9 +277,15 @@ router.post(
|
|
|
258
277
|
renderForm(form, req.csrfToken())
|
|
259
278
|
);
|
|
260
279
|
} else {
|
|
280
|
+
// tenant url
|
|
261
281
|
const newurl = getNewURL(req, subdomain);
|
|
282
|
+
// tenant template
|
|
262
283
|
const tenant_template = getState().getConfig("tenant_template");
|
|
263
|
-
|
|
284
|
+
// tenant creator
|
|
285
|
+
const user_email = req.user && req.user.email;
|
|
286
|
+
// switch to tenant
|
|
287
|
+
await switchToTenant(await insertTenant(subdomain, user_email, description, tenant_template), newurl);
|
|
288
|
+
// add tenant to global state
|
|
264
289
|
add_tenant(subdomain);
|
|
265
290
|
await create_tenant({
|
|
266
291
|
t: subdomain,
|
|
@@ -348,6 +373,15 @@ router.get(
|
|
|
348
373
|
{
|
|
349
374
|
label: req.__("Description"),
|
|
350
375
|
key: (r) => text(r.description),
|
|
376
|
+
//blurb: req.__("Specify some description for tenant if need"),
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
label: req.__("Creator email"),
|
|
380
|
+
key: (r) => text(r.email),
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
label: req.__("Created"),
|
|
384
|
+
key: (r) => text(r.created),
|
|
351
385
|
},
|
|
352
386
|
{
|
|
353
387
|
label: req.__("Information"),
|
|
@@ -387,6 +421,7 @@ const tenant_settings_form = (req) =>
|
|
|
387
421
|
field_names: [
|
|
388
422
|
"role_to_create_tenant",
|
|
389
423
|
"create_tenant_warning",
|
|
424
|
+
"create_tenant_warning_text",
|
|
390
425
|
"tenant_template",
|
|
391
426
|
],
|
|
392
427
|
action: "/tenant/settings",
|
|
@@ -468,8 +503,19 @@ router.post(
|
|
|
468
503
|
const get_tenant_info = async (subdomain) => {
|
|
469
504
|
const saneDomain = domain_sanitize(subdomain);
|
|
470
505
|
|
|
506
|
+
let info = {};
|
|
507
|
+
|
|
508
|
+
// get tenant row
|
|
509
|
+
const ten = await Tenant.findOne({ subdomain: saneDomain });
|
|
510
|
+
if (ten) {
|
|
511
|
+
info.description = ten.description;
|
|
512
|
+
info.created = ten.created;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
// get data from tenant schema
|
|
471
517
|
return await db.runWithTenant(saneDomain, async () => {
|
|
472
|
-
|
|
518
|
+
|
|
473
519
|
// TBD fix the first user issue because not always firt user by id is creator of tenant
|
|
474
520
|
const firstUser = await User.find({}, { orderBy: "id", limit: 1 });
|
|
475
521
|
if (firstUser && firstUser.length > 0) {
|
|
@@ -497,7 +543,11 @@ const get_tenant_info = async (subdomain) => {
|
|
|
497
543
|
info.nconfigs = await db.count("_sc_config");
|
|
498
544
|
// plugins count
|
|
499
545
|
info.nplugins = await db.count("_sc_plugins");
|
|
500
|
-
//
|
|
546
|
+
// migration count
|
|
547
|
+
info.nmigrations = await db.count("_sc_migrations");
|
|
548
|
+
// library count
|
|
549
|
+
info.nlibrary = await db.count("_sc_library");
|
|
550
|
+
// TBD decide Do we need count tenants, table constraints
|
|
501
551
|
// base url
|
|
502
552
|
info.base_url = await getConfig("base_url");
|
|
503
553
|
return info;
|
|
@@ -525,6 +575,7 @@ router.get(
|
|
|
525
575
|
return;
|
|
526
576
|
}
|
|
527
577
|
const { subdomain } = req.params;
|
|
578
|
+
// get tenant info
|
|
528
579
|
const info = await get_tenant_info(subdomain);
|
|
529
580
|
// get list of files
|
|
530
581
|
let files;
|
|
@@ -545,7 +596,7 @@ router.get(
|
|
|
545
596
|
contents: [
|
|
546
597
|
table(
|
|
547
598
|
tr(
|
|
548
|
-
th(req.__("E-mail")),
|
|
599
|
+
th(req.__("First user E-mail")),
|
|
549
600
|
td(
|
|
550
601
|
a(
|
|
551
602
|
{ href: "mailto:" + info.first_user_email },
|
|
@@ -616,8 +667,13 @@ router.get(
|
|
|
616
667
|
label: req.__("Base URL"),
|
|
617
668
|
type: "String",
|
|
618
669
|
},
|
|
670
|
+
{
|
|
671
|
+
name: "description",
|
|
672
|
+
label: req.__("Description"),
|
|
673
|
+
type: "String",
|
|
674
|
+
},
|
|
619
675
|
],
|
|
620
|
-
values: { base_url: info.base_url },
|
|
676
|
+
values: { base_url: info.base_url, description: info.description },
|
|
621
677
|
}),
|
|
622
678
|
req.csrfToken()
|
|
623
679
|
),
|
|
@@ -673,6 +729,12 @@ router.post(
|
|
|
673
729
|
const { base_url } = req.body;
|
|
674
730
|
const saneDomain = domain_sanitize(subdomain);
|
|
675
731
|
|
|
732
|
+
// save description
|
|
733
|
+
const { description } = req.body;
|
|
734
|
+
await Tenant.update( saneDomain, {description: description});
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
|
|
676
738
|
await db.runWithTenant(saneDomain, async () => {
|
|
677
739
|
await getState().setConfig("base_url", base_url);
|
|
678
740
|
});
|
|
@@ -701,7 +763,7 @@ router.post(
|
|
|
701
763
|
return;
|
|
702
764
|
}
|
|
703
765
|
const { sub } = req.params;
|
|
704
|
-
|
|
766
|
+
// todo warning before deletion
|
|
705
767
|
await deleteTenant(sub);
|
|
706
768
|
res.redirect(`/tenant/list`);
|
|
707
769
|
})
|
package/routes/utils.js
CHANGED
|
@@ -20,6 +20,8 @@ const { validateHeaderName, validateHeaderValue } = require("http");
|
|
|
20
20
|
const Crash = require("@saltcorn/data/models/crash");
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
+
* Checks that user logged or not.
|
|
24
|
+
* If not shows than shows flash and redirects to login
|
|
23
25
|
* @param {object} req
|
|
24
26
|
* @param {object} res
|
|
25
27
|
* @param {function} next
|
|
@@ -35,6 +37,8 @@ function loggedIn(req, res, next) {
|
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
/**
|
|
40
|
+
* Checks that user has admin role or not.
|
|
41
|
+
* If user hasn't admin role shows flash and redirects user to login or totp
|
|
38
42
|
* @param {object} req
|
|
39
43
|
* @param {object} res
|
|
40
44
|
* @param {function} next
|
|
@@ -60,6 +64,7 @@ function isAdmin(req, res, next) {
|
|
|
60
64
|
}
|
|
61
65
|
|
|
62
66
|
/**
|
|
67
|
+
* Sets language for HTTP Request / HTTP Responce
|
|
63
68
|
* @param {object} req
|
|
64
69
|
* @param {object} res
|
|
65
70
|
* @param {string} state
|
|
@@ -73,6 +78,7 @@ const setLanguage = (req, res, state) => {
|
|
|
73
78
|
};
|
|
74
79
|
|
|
75
80
|
/**
|
|
81
|
+
* Sets Custom HTTP headers using data from "custom_http_headers" config variable
|
|
76
82
|
* @param {object} res
|
|
77
83
|
* @param {string} state
|
|
78
84
|
* @returns {void}
|
|
@@ -96,6 +102,7 @@ const set_custom_http_headers = (res, state) => {
|
|
|
96
102
|
};
|
|
97
103
|
|
|
98
104
|
/**
|
|
105
|
+
* Tries to recognize tenant from HTTP Request
|
|
99
106
|
* @param {object} req
|
|
100
107
|
* @returns {string}
|
|
101
108
|
*/
|
|
@@ -179,6 +186,7 @@ const setTenant = (req, res, next) => {
|
|
|
179
186
|
};
|
|
180
187
|
|
|
181
188
|
/**
|
|
189
|
+
* Injects hidden input "_csrf" for CSRF token
|
|
182
190
|
* @param {object} req
|
|
183
191
|
* @returns {input}
|
|
184
192
|
*/
|
|
@@ -190,6 +198,7 @@ const csrfField = (req) =>
|
|
|
190
198
|
});
|
|
191
199
|
|
|
192
200
|
/**
|
|
201
|
+
* Errors catcher
|
|
193
202
|
* @param {function} fn
|
|
194
203
|
* @returns {function}
|
|
195
204
|
*/
|
|
@@ -198,6 +207,7 @@ const error_catcher = (fn) => (request, response, next) => {
|
|
|
198
207
|
};
|
|
199
208
|
|
|
200
209
|
/**
|
|
210
|
+
* Scans for page title from contents
|
|
201
211
|
* @param {string|object} contents
|
|
202
212
|
* @param {string} viewname
|
|
203
213
|
* @returns {string}
|
|
@@ -218,11 +228,13 @@ const scan_for_page_title = (contents, viewname) => {
|
|
|
218
228
|
};
|
|
219
229
|
|
|
220
230
|
/**
|
|
231
|
+
* Gets gir revision
|
|
221
232
|
* @returns {string}
|
|
222
233
|
*/
|
|
223
234
|
const getGitRevision = () => db.connectObj.git_commit;
|
|
224
235
|
|
|
225
236
|
/**
|
|
237
|
+
* Gets session store
|
|
226
238
|
* @returns {session|cookieSession}
|
|
227
239
|
*/
|
|
228
240
|
const getSessionStore = () => {
|
package/routes/view.js
CHANGED
|
@@ -8,7 +8,6 @@ const Router = require("express-promise-router");
|
|
|
8
8
|
|
|
9
9
|
const View = require("@saltcorn/data/models/view");
|
|
10
10
|
const Table = require("@saltcorn/data/models/table");
|
|
11
|
-
const Page = require("@saltcorn/data/models/page");
|
|
12
11
|
|
|
13
12
|
const { div, text, i, a } = require("@saltcorn/markup/tags");
|
|
14
13
|
const { renderForm, link } = require("@saltcorn/markup");
|
package/routes/viewedit.js
CHANGED
|
@@ -16,7 +16,7 @@ const {
|
|
|
16
16
|
post_dropdown_item,
|
|
17
17
|
renderBuilder,
|
|
18
18
|
settingsDropdown,
|
|
19
|
-
alert
|
|
19
|
+
alert,
|
|
20
20
|
} = require("@saltcorn/markup");
|
|
21
21
|
const {
|
|
22
22
|
//span,
|
|
@@ -27,7 +27,9 @@ const {
|
|
|
27
27
|
a,
|
|
28
28
|
div,
|
|
29
29
|
//button,
|
|
30
|
+
script,
|
|
30
31
|
text,
|
|
32
|
+
domReady,
|
|
31
33
|
} = require("@saltcorn/markup/tags");
|
|
32
34
|
|
|
33
35
|
const { getState } = require("@saltcorn/data/db/state");
|
|
@@ -40,7 +42,6 @@ const View = require("@saltcorn/data/models/view");
|
|
|
40
42
|
const Workflow = require("@saltcorn/data/models/workflow");
|
|
41
43
|
const User = require("@saltcorn/data/models/user");
|
|
42
44
|
const Page = require("@saltcorn/data/models/page");
|
|
43
|
-
const Tag = require("@saltcorn/data/models/tag");
|
|
44
45
|
const db = require("@saltcorn/data/db");
|
|
45
46
|
|
|
46
47
|
const { add_to_menu } = require("@saltcorn/admin-models/models/pack");
|
|
@@ -77,20 +78,26 @@ router.get(
|
|
|
77
78
|
|
|
78
79
|
const viewMarkup = await viewsList(views, req);
|
|
79
80
|
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
|
|
81
|
+
const viewAccessWarning = (view) => {
|
|
82
|
+
const table = tables.find((t) => t.name === view.table);
|
|
83
|
+
if (!table) return false;
|
|
84
|
+
if (table.ownership_field_id || table.ownership_formula) return false;
|
|
84
85
|
|
|
85
|
-
return table.min_role_read < view.min_role
|
|
86
|
-
}
|
|
87
|
-
const hasAccessWarning = views.filter(viewAccessWarning)
|
|
88
|
-
const accessWarning =
|
|
89
|
-
|
|
86
|
+
return table.min_role_read < view.min_role;
|
|
87
|
+
};
|
|
88
|
+
const hasAccessWarning = views.filter(viewAccessWarning);
|
|
89
|
+
const accessWarning =
|
|
90
|
+
hasAccessWarning.length > 0
|
|
91
|
+
? alert(
|
|
92
|
+
"danger",
|
|
93
|
+
`<p>You have views with a role to access lower than the table role to read,
|
|
90
94
|
with no table ownership. In the next version of Saltcorn, this may cause a
|
|
91
95
|
denial of access. Users will need to have table read access to any data displayed.</p>
|
|
92
|
-
Views potentially affected: ${hasAccessWarning
|
|
93
|
-
|
|
96
|
+
Views potentially affected: ${hasAccessWarning
|
|
97
|
+
.map((v) => v.name)
|
|
98
|
+
.join(", ")}`
|
|
99
|
+
)
|
|
100
|
+
: "";
|
|
94
101
|
res.sendWrap(req.__(`Views`), {
|
|
95
102
|
above: [
|
|
96
103
|
{
|
|
@@ -225,15 +232,15 @@ const viewForm = async (req, tableOptions, roles, pages, values) => {
|
|
|
225
232
|
}),
|
|
226
233
|
...(isEdit
|
|
227
234
|
? [
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
235
|
+
new Field({
|
|
236
|
+
name: "viewtemplate",
|
|
237
|
+
input_type: "hidden",
|
|
238
|
+
}),
|
|
239
|
+
new Field({
|
|
240
|
+
name: "table_name",
|
|
241
|
+
input_type: "hidden",
|
|
242
|
+
}),
|
|
243
|
+
]
|
|
237
244
|
: []),
|
|
238
245
|
],
|
|
239
246
|
values,
|
|
@@ -434,7 +441,7 @@ router.post(
|
|
|
434
441
|
* @returns {void}
|
|
435
442
|
*/
|
|
436
443
|
const respondWorkflow = (view, wf, wfres, req, res) => {
|
|
437
|
-
const wrap = (contents, noCard) => ({
|
|
444
|
+
const wrap = (contents, noCard, previewURL) => ({
|
|
438
445
|
above: [
|
|
439
446
|
{
|
|
440
447
|
type: "breadcrumbs",
|
|
@@ -450,6 +457,12 @@ const respondWorkflow = (view, wf, wfres, req, res) => {
|
|
|
450
457
|
title: wfres.title,
|
|
451
458
|
contents,
|
|
452
459
|
},
|
|
460
|
+
...previewURL ? [{
|
|
461
|
+
type: "card",
|
|
462
|
+
title: req.__("Preview"),
|
|
463
|
+
contents: div({ id: "viewcfg-preview", "data-preview-url": previewURL },
|
|
464
|
+
script(domReady(`updateViewPreview()`))),
|
|
465
|
+
}] : []
|
|
453
466
|
],
|
|
454
467
|
});
|
|
455
468
|
if (wfres.flash) req.flash(wfres.flash[0], wfres.flash[1]);
|
|
@@ -472,7 +485,7 @@ const respondWorkflow = (view, wf, wfres, req, res) => {
|
|
|
472
485
|
},
|
|
473
486
|
],
|
|
474
487
|
},
|
|
475
|
-
wrap(renderForm(wfres.renderForm, req.csrfToken()))
|
|
488
|
+
wrap(renderForm(wfres.renderForm, req.csrfToken()), false, wfres.previewURL)
|
|
476
489
|
);
|
|
477
490
|
else if (wfres.renderBuilder) {
|
|
478
491
|
wfres.renderBuilder.options.view_id = view.id;
|
|
@@ -686,14 +699,14 @@ router.post(
|
|
|
686
699
|
const view = await View.findOne({ id });
|
|
687
700
|
const roles = await User.get_roles();
|
|
688
701
|
const roleRow = roles.find((r) => r.id === +role);
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
req.__(`Minimum role
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
res.
|
|
702
|
+
const message =
|
|
703
|
+
roleRow && view
|
|
704
|
+
? req.__(`Minimum role for %s updated to %s`, view.name, roleRow.role)
|
|
705
|
+
: req.__(`Minimum role updated`);
|
|
706
|
+
if (!req.xhr) {
|
|
707
|
+
req.flash("success", message);
|
|
708
|
+
res.redirect("/viewedit");
|
|
709
|
+
} else res.json({ okay: true, responseText: message });
|
|
697
710
|
})
|
|
698
711
|
);
|
|
699
712
|
|
package/serve.js
CHANGED
|
@@ -99,11 +99,16 @@ const workerDispatchMsg = ({ tenant, ...msg }) => {
|
|
|
99
99
|
db.runWithTenant(tenant, () => workerDispatchMsg(msg));
|
|
100
100
|
return;
|
|
101
101
|
}
|
|
102
|
+
|
|
102
103
|
if (msg.refresh_plugin_cfg) {
|
|
103
104
|
Plugin.findOne({ name: msg.refresh_plugin_cfg }).then((plugin) => {
|
|
104
105
|
if (plugin) loadPlugin(plugin);
|
|
105
106
|
});
|
|
106
107
|
}
|
|
108
|
+
if (!getState()) {
|
|
109
|
+
console.error("no State for tenant", tenant)
|
|
110
|
+
return
|
|
111
|
+
}
|
|
107
112
|
if (msg.refresh) getState()[`refresh_${msg.refresh}`](true);
|
|
108
113
|
if (msg.createTenant) {
|
|
109
114
|
const tenant_template = getState().getConfig("tenant_template");
|
package/tests/admin.test.js
CHANGED
|
@@ -68,9 +68,11 @@ describe("admin page", () => {
|
|
|
68
68
|
.expect(toInclude("Site identity settings"));
|
|
69
69
|
});
|
|
70
70
|
adminPageContains([
|
|
71
|
+
["/admin", "Site identity"],
|
|
71
72
|
["/admin/backup", "Download a backup"],
|
|
72
73
|
["/admin/email", "Email settings"],
|
|
73
74
|
["/admin/system", "Restart server"],
|
|
75
|
+
["/admin/dev", "Development"],
|
|
74
76
|
]);
|
|
75
77
|
adminPageContains([
|
|
76
78
|
["/useradmin", "Create user"],
|
package/tests/auth.test.js
CHANGED
|
@@ -18,6 +18,7 @@ const { getState } = require("@saltcorn/data/db/state");
|
|
|
18
18
|
const { get_reset_link, generate_email } = require("../auth/resetpw");
|
|
19
19
|
const i18n = require("i18n");
|
|
20
20
|
const path = require("path");
|
|
21
|
+
const fs = require("fs")
|
|
21
22
|
|
|
22
23
|
afterAll(db.close);
|
|
23
24
|
beforeAll(async () => {
|
|
@@ -602,3 +603,22 @@ describe("signup with custom login form", () => {
|
|
|
602
603
|
expect(userrow.height).toBe(15);
|
|
603
604
|
});
|
|
604
605
|
});
|
|
606
|
+
|
|
607
|
+
describe("Locale files", () => {
|
|
608
|
+
it("should be valid JSON", async () => {
|
|
609
|
+
|
|
610
|
+
const localeFiles =
|
|
611
|
+
await fs.promises.readdir(path.join(__dirname, "..", "/locales"));
|
|
612
|
+
expect(localeFiles.length).toBeGreaterThan(3)
|
|
613
|
+
expect(localeFiles).toContain("en.json")
|
|
614
|
+
for (const fnm of localeFiles) {
|
|
615
|
+
const conts = await fs.promises.readFile(
|
|
616
|
+
path.join(__dirname, "..", "/locales", fnm)
|
|
617
|
+
)
|
|
618
|
+
expect(conts.length).toBeGreaterThan(1)
|
|
619
|
+
|
|
620
|
+
const j = JSON.parse(conts)
|
|
621
|
+
expect(Object.keys(j).length).toBeGreaterThan(1)
|
|
622
|
+
}
|
|
623
|
+
})
|
|
624
|
+
})
|