@saltcorn/server 0.7.4 → 0.8.0-beta.1
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 +370 -120
- package/auth/roleadmin.js +5 -23
- package/auth/routes.js +40 -15
- package/locales/de.json +1049 -273
- package/locales/en.json +58 -3
- package/locales/es.json +134 -134
- package/locales/it.json +6 -1
- package/locales/ru.json +44 -7
- package/markup/admin.js +46 -42
- package/markup/forms.js +4 -3
- package/package.json +8 -7
- package/public/blockly.js +19 -31
- 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 +6 -14
- package/routes/admin.js +229 -79
- package/routes/api.js +19 -2
- package/routes/common_lists.js +137 -134
- package/routes/delete.js +6 -5
- package/routes/diagram.js +43 -117
- package/routes/edit.js +5 -10
- package/routes/fields.js +63 -29
- package/routes/files.js +137 -101
- package/routes/homepage.js +2 -2
- package/routes/infoarch.js +2 -2
- package/routes/list.js +12 -13
- 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 +9 -14
- package/routes/tag_entries.js +31 -10
- package/routes/tags.js +10 -10
- package/routes/tenant.js +114 -50
- package/routes/utils.js +12 -0
- package/routes/view.js +3 -4
- package/routes/viewedit.js +57 -55
- package/serve.js +5 -0
- package/tests/admin.test.js +6 -2
- package/tests/auth.test.js +20 -0
- package/tests/fields.test.js +1 -0
- package/tests/files.test.js +11 -20
- package/tests/tenant.test.js +12 -2
- package/tests/viewedit.test.js +15 -1
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,
|
|
@@ -44,7 +44,6 @@ const User = require("@saltcorn/data/models/user");
|
|
|
44
44
|
const File = require("@saltcorn/data/models/file");
|
|
45
45
|
const {
|
|
46
46
|
send_infoarch_page,
|
|
47
|
-
//send_admin_page,
|
|
48
47
|
config_fields_form,
|
|
49
48
|
save_config_from_form,
|
|
50
49
|
} = require("../markup/admin.js");
|
|
@@ -148,45 +147,58 @@ router.get(
|
|
|
148
147
|
"You are trying to create a tenant while connecting via an IP address rather than a domain. This will probably not work."
|
|
149
148
|
)
|
|
150
149
|
);
|
|
151
|
-
let
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
{
|
|
156
|
-
class: "alert alert-warning alert-dismissible fade show mt-5",
|
|
157
|
-
role: "alert",
|
|
158
|
-
},
|
|
159
|
-
h4(req.__("Warning")),
|
|
160
|
-
p(
|
|
161
|
-
req.__(
|
|
162
|
-
"Hosting on this site is provided for free and with no guarantee of availability or security of your application. "
|
|
163
|
-
) +
|
|
164
|
-
" " +
|
|
165
|
-
req.__(
|
|
166
|
-
"This facility is intended solely for you to evaluate the suitability of Saltcorn. "
|
|
167
|
-
) +
|
|
168
|
-
" " +
|
|
169
|
-
req.__(
|
|
170
|
-
"If you would like to store private information that needs to be secure, please use self-hosted Saltcorn. "
|
|
171
|
-
) +
|
|
172
|
-
" " +
|
|
173
|
-
req.__(
|
|
174
|
-
'See <a href="https://github.com/saltcorn/saltcorn">GitHub repository</a> for instructions<p>'
|
|
175
|
-
)
|
|
176
|
-
)
|
|
150
|
+
let create_tenant_warning_text = "";
|
|
151
|
+
if (getState().getConfig("create_tenant_warning")) {
|
|
152
|
+
create_tenant_warning_text = getState().getConfig(
|
|
153
|
+
"create_tenant_warning_text"
|
|
177
154
|
);
|
|
155
|
+
if (create_tenant_warning_text && create_tenant_warning_text.length > 0)
|
|
156
|
+
create_tenant_warning_text = div(
|
|
157
|
+
{
|
|
158
|
+
class: "alert alert-warning alert-dismissible fade show mt-5",
|
|
159
|
+
role: "alert",
|
|
160
|
+
},
|
|
161
|
+
h4(req.__("Warning")),
|
|
162
|
+
p(create_tenant_warning_text)
|
|
163
|
+
);
|
|
164
|
+
else
|
|
165
|
+
create_tenant_warning_text = div(
|
|
166
|
+
{
|
|
167
|
+
class: "alert alert-warning alert-dismissible fade show mt-5",
|
|
168
|
+
role: "alert",
|
|
169
|
+
},
|
|
170
|
+
h4(req.__("Warning")),
|
|
171
|
+
p(
|
|
172
|
+
req.__(
|
|
173
|
+
"Hosting on this site is provided for free and with no guarantee of availability or security of your application. "
|
|
174
|
+
) +
|
|
175
|
+
" " +
|
|
176
|
+
req.__(
|
|
177
|
+
"This facility is intended solely for you to evaluate the suitability of Saltcorn. "
|
|
178
|
+
) +
|
|
179
|
+
" " +
|
|
180
|
+
req.__(
|
|
181
|
+
"If you would like to store private information that needs to be secure, please use self-hosted Saltcorn. "
|
|
182
|
+
) +
|
|
183
|
+
" " +
|
|
184
|
+
req.__(
|
|
185
|
+
'See <a href="https://github.com/saltcorn/saltcorn">GitHub repository</a> for instructions<p>'
|
|
186
|
+
)
|
|
187
|
+
)
|
|
188
|
+
);
|
|
189
|
+
}
|
|
178
190
|
|
|
179
191
|
res.sendWrap(
|
|
180
192
|
req.__("Create application"),
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
193
|
+
create_tenant_warning_text +
|
|
194
|
+
renderForm(tenant_form(req), req.csrfToken()) +
|
|
195
|
+
p(
|
|
196
|
+
{ class: "mt-2" },
|
|
197
|
+
req.__("To login to a previously created application, go to: "),
|
|
198
|
+
code(`${req.protocol}://`) +
|
|
199
|
+
i(req.__("Application name")) +
|
|
200
|
+
code("." + req.hostname)
|
|
201
|
+
)
|
|
190
202
|
);
|
|
191
203
|
})
|
|
192
204
|
);
|
|
@@ -246,6 +258,8 @@ router.post(
|
|
|
246
258
|
else {
|
|
247
259
|
// normalize domain name
|
|
248
260
|
const subdomain = domain_sanitize(valres.success.subdomain);
|
|
261
|
+
// get description
|
|
262
|
+
const description = valres.success.description;
|
|
249
263
|
// get list of tenants
|
|
250
264
|
const allTens = await getAllTenants();
|
|
251
265
|
if (allTens.includes(subdomain) || !subdomain) {
|
|
@@ -258,9 +272,23 @@ router.post(
|
|
|
258
272
|
renderForm(form, req.csrfToken())
|
|
259
273
|
);
|
|
260
274
|
} else {
|
|
275
|
+
// tenant url
|
|
261
276
|
const newurl = getNewURL(req, subdomain);
|
|
277
|
+
// tenant template
|
|
262
278
|
const tenant_template = getState().getConfig("tenant_template");
|
|
263
|
-
|
|
279
|
+
// tenant creator
|
|
280
|
+
const user_email = req.user && req.user.email;
|
|
281
|
+
// switch to tenant
|
|
282
|
+
await switchToTenant(
|
|
283
|
+
await insertTenant(
|
|
284
|
+
subdomain,
|
|
285
|
+
user_email,
|
|
286
|
+
description,
|
|
287
|
+
tenant_template
|
|
288
|
+
),
|
|
289
|
+
newurl
|
|
290
|
+
);
|
|
291
|
+
// add tenant to global state
|
|
264
292
|
add_tenant(subdomain);
|
|
265
293
|
await create_tenant({
|
|
266
294
|
t: subdomain,
|
|
@@ -294,13 +322,13 @@ router.post(
|
|
|
294
322
|
" " +
|
|
295
323
|
hasTemplate
|
|
296
324
|
? req.__(
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
325
|
+
'Use this link: <a href="%s">%s</a> to revisit your application at any time.',
|
|
326
|
+
newurl,
|
|
327
|
+
newurl
|
|
328
|
+
)
|
|
301
329
|
: req.__(
|
|
302
|
-
|
|
303
|
-
|
|
330
|
+
"Use this link to revisit your application at any time."
|
|
331
|
+
)
|
|
304
332
|
)
|
|
305
333
|
)
|
|
306
334
|
);
|
|
@@ -348,6 +376,15 @@ router.get(
|
|
|
348
376
|
{
|
|
349
377
|
label: req.__("Description"),
|
|
350
378
|
key: (r) => text(r.description),
|
|
379
|
+
//blurb: req.__("Specify some description for tenant if need"),
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
label: req.__("Creator email"),
|
|
383
|
+
key: (r) => text(r.email),
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
label: req.__("Created"),
|
|
387
|
+
key: (r) => text(r.created),
|
|
351
388
|
},
|
|
352
389
|
{
|
|
353
390
|
label: req.__("Information"),
|
|
@@ -387,6 +424,7 @@ const tenant_settings_form = (req) =>
|
|
|
387
424
|
field_names: [
|
|
388
425
|
"role_to_create_tenant",
|
|
389
426
|
"create_tenant_warning",
|
|
427
|
+
"create_tenant_warning_text",
|
|
390
428
|
"tenant_template",
|
|
391
429
|
],
|
|
392
430
|
action: "/tenant/settings",
|
|
@@ -468,8 +506,17 @@ router.post(
|
|
|
468
506
|
const get_tenant_info = async (subdomain) => {
|
|
469
507
|
const saneDomain = domain_sanitize(subdomain);
|
|
470
508
|
|
|
509
|
+
let info = {};
|
|
510
|
+
|
|
511
|
+
// get tenant row
|
|
512
|
+
const ten = await Tenant.findOne({ subdomain: saneDomain });
|
|
513
|
+
if (ten) {
|
|
514
|
+
info.description = ten.description;
|
|
515
|
+
info.created = ten.created;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// get data from tenant schema
|
|
471
519
|
return await db.runWithTenant(saneDomain, async () => {
|
|
472
|
-
let info = {};
|
|
473
520
|
// TBD fix the first user issue because not always firt user by id is creator of tenant
|
|
474
521
|
const firstUser = await User.find({}, { orderBy: "id", limit: 1 });
|
|
475
522
|
if (firstUser && firstUser.length > 0) {
|
|
@@ -497,7 +544,11 @@ const get_tenant_info = async (subdomain) => {
|
|
|
497
544
|
info.nconfigs = await db.count("_sc_config");
|
|
498
545
|
// plugins count
|
|
499
546
|
info.nplugins = await db.count("_sc_plugins");
|
|
500
|
-
//
|
|
547
|
+
// migration count
|
|
548
|
+
info.nmigrations = await db.count("_sc_migrations");
|
|
549
|
+
// library count
|
|
550
|
+
info.nlibrary = await db.count("_sc_library");
|
|
551
|
+
// TBD decide Do we need count tenants, table constraints
|
|
501
552
|
// base url
|
|
502
553
|
info.base_url = await getConfig("base_url");
|
|
503
554
|
return info;
|
|
@@ -525,6 +576,7 @@ router.get(
|
|
|
525
576
|
return;
|
|
526
577
|
}
|
|
527
578
|
const { subdomain } = req.params;
|
|
579
|
+
// get tenant info
|
|
528
580
|
const info = await get_tenant_info(subdomain);
|
|
529
581
|
// get list of files
|
|
530
582
|
let files;
|
|
@@ -545,7 +597,7 @@ router.get(
|
|
|
545
597
|
contents: [
|
|
546
598
|
table(
|
|
547
599
|
tr(
|
|
548
|
-
th(req.__("E-mail")),
|
|
600
|
+
th(req.__("First user E-mail")),
|
|
549
601
|
td(
|
|
550
602
|
a(
|
|
551
603
|
{ href: "mailto:" + info.first_user_email },
|
|
@@ -616,8 +668,16 @@ router.get(
|
|
|
616
668
|
label: req.__("Base URL"),
|
|
617
669
|
type: "String",
|
|
618
670
|
},
|
|
671
|
+
{
|
|
672
|
+
name: "description",
|
|
673
|
+
label: req.__("Description"),
|
|
674
|
+
type: "String",
|
|
675
|
+
},
|
|
619
676
|
],
|
|
620
|
-
values: {
|
|
677
|
+
values: {
|
|
678
|
+
base_url: info.base_url,
|
|
679
|
+
description: info.description,
|
|
680
|
+
},
|
|
621
681
|
}),
|
|
622
682
|
req.csrfToken()
|
|
623
683
|
),
|
|
@@ -673,6 +733,10 @@ router.post(
|
|
|
673
733
|
const { base_url } = req.body;
|
|
674
734
|
const saneDomain = domain_sanitize(subdomain);
|
|
675
735
|
|
|
736
|
+
// save description
|
|
737
|
+
const { description } = req.body;
|
|
738
|
+
await Tenant.update(saneDomain, { description: description });
|
|
739
|
+
|
|
676
740
|
await db.runWithTenant(saneDomain, async () => {
|
|
677
741
|
await getState().setConfig("base_url", base_url);
|
|
678
742
|
});
|
|
@@ -701,7 +765,7 @@ router.post(
|
|
|
701
765
|
return;
|
|
702
766
|
}
|
|
703
767
|
const { sub } = req.params;
|
|
704
|
-
|
|
768
|
+
// todo warning before deletion
|
|
705
769
|
await deleteTenant(sub);
|
|
706
770
|
res.redirect(`/tenant/list`);
|
|
707
771
|
})
|
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,10 +8,8 @@ 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
|
-
const {
|
|
14
|
-
const { renderForm, link } = require("@saltcorn/markup");
|
|
12
|
+
const { text } = require("@saltcorn/markup/tags");
|
|
15
13
|
const {
|
|
16
14
|
isAdmin,
|
|
17
15
|
error_catcher,
|
|
@@ -105,7 +103,8 @@ router.post(
|
|
|
105
103
|
if (sf.required && !query[sf.name]) {
|
|
106
104
|
if (!row) {
|
|
107
105
|
if (!table)
|
|
108
|
-
|
|
106
|
+
// todo check after where change
|
|
107
|
+
table = await Table.findOne(view.table_id ? { id : view.table_id } : {name: view.exttable_name});
|
|
109
108
|
row = await table.getRow({});
|
|
110
109
|
}
|
|
111
110
|
if (row) query[sf.name] = row[sf.name];
|
package/routes/viewedit.js
CHANGED
|
@@ -7,28 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
const Router = require("express-promise-router");
|
|
9
9
|
|
|
10
|
-
const {
|
|
11
|
-
|
|
12
|
-
mkTable,
|
|
13
|
-
link,
|
|
14
|
-
//post_btn,
|
|
15
|
-
//post_delete_btn,
|
|
16
|
-
post_dropdown_item,
|
|
17
|
-
renderBuilder,
|
|
18
|
-
settingsDropdown,
|
|
19
|
-
alert
|
|
20
|
-
} = require("@saltcorn/markup");
|
|
21
|
-
const {
|
|
22
|
-
//span,
|
|
23
|
-
//h5,
|
|
24
|
-
h4,
|
|
25
|
-
//nbsp,
|
|
26
|
-
p,
|
|
27
|
-
a,
|
|
28
|
-
div,
|
|
29
|
-
//button,
|
|
30
|
-
text,
|
|
31
|
-
} = require("@saltcorn/markup/tags");
|
|
10
|
+
const { renderForm, renderBuilder, alert } = require("@saltcorn/markup");
|
|
11
|
+
const { p, a, div, script, text, domReady } = require("@saltcorn/markup/tags");
|
|
32
12
|
|
|
33
13
|
const { getState } = require("@saltcorn/data/db/state");
|
|
34
14
|
const { isAdmin, error_catcher, addOnDoneRedirect } = require("./utils.js");
|
|
@@ -40,7 +20,6 @@ const View = require("@saltcorn/data/models/view");
|
|
|
40
20
|
const Workflow = require("@saltcorn/data/models/workflow");
|
|
41
21
|
const User = require("@saltcorn/data/models/user");
|
|
42
22
|
const Page = require("@saltcorn/data/models/page");
|
|
43
|
-
const Tag = require("@saltcorn/data/models/tag");
|
|
44
23
|
const db = require("@saltcorn/data/db");
|
|
45
24
|
|
|
46
25
|
const { add_to_menu } = require("@saltcorn/admin-models/models/pack");
|
|
@@ -77,20 +56,27 @@ router.get(
|
|
|
77
56
|
|
|
78
57
|
const viewMarkup = await viewsList(views, req);
|
|
79
58
|
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
|
|
59
|
+
const viewAccessWarning = (view) => {
|
|
60
|
+
const table = tables.find((t) => t.name === view.table);
|
|
61
|
+
if (!table) return false;
|
|
62
|
+
if (table.ownership_field_id || table.ownership_formula) return false;
|
|
84
63
|
|
|
85
|
-
return table.min_role_read < view.min_role
|
|
86
|
-
}
|
|
87
|
-
const hasAccessWarning = views.filter(viewAccessWarning)
|
|
88
|
-
const accessWarning =
|
|
89
|
-
|
|
64
|
+
return table.min_role_read < view.min_role;
|
|
65
|
+
};
|
|
66
|
+
const hasAccessWarning = views.filter(viewAccessWarning);
|
|
67
|
+
const accessWarning =
|
|
68
|
+
hasAccessWarning.length > 0
|
|
69
|
+
? alert(
|
|
70
|
+
"danger",
|
|
71
|
+
req.__(
|
|
72
|
+
`<p>You have views with a role to access lower than the table role to read,
|
|
90
73
|
with no table ownership. In the next version of Saltcorn, this may cause a
|
|
91
74
|
denial of access. Users will need to have table read access to any data displayed.</p>
|
|
92
|
-
Views potentially affected:
|
|
93
|
-
|
|
75
|
+
Views potentially affected: %s`,
|
|
76
|
+
hasAccessWarning.map((v) => v.name).join(", ")
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
: "";
|
|
94
80
|
res.sendWrap(req.__(`Views`), {
|
|
95
81
|
above: [
|
|
96
82
|
{
|
|
@@ -106,14 +92,14 @@ router.get(
|
|
|
106
92
|
viewMarkup,
|
|
107
93
|
tables.length > 0
|
|
108
94
|
? a(
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
)
|
|
112
|
-
: p(
|
|
113
|
-
req.__(
|
|
114
|
-
"You must create at least one table before you can create views."
|
|
95
|
+
{ href: `/viewedit/new`, class: "btn btn-primary" },
|
|
96
|
+
req.__("Create view")
|
|
115
97
|
)
|
|
116
|
-
|
|
98
|
+
: p(
|
|
99
|
+
req.__(
|
|
100
|
+
"You must create at least one table before you can create views."
|
|
101
|
+
)
|
|
102
|
+
),
|
|
117
103
|
],
|
|
118
104
|
},
|
|
119
105
|
],
|
|
@@ -252,7 +238,7 @@ router.get(
|
|
|
252
238
|
error_catcher(async (req, res) => {
|
|
253
239
|
const { viewname } = req.params;
|
|
254
240
|
|
|
255
|
-
|
|
241
|
+
const viewrow = await View.findOne({ name: viewname });
|
|
256
242
|
if (!viewrow) {
|
|
257
243
|
req.flash("error", `View not found: ${text(viewname)}`);
|
|
258
244
|
res.redirect("/viewedit");
|
|
@@ -380,8 +366,8 @@ router.post(
|
|
|
380
366
|
sendForm(form);
|
|
381
367
|
} else {
|
|
382
368
|
const existing_view = await View.findOne({ name: result.success.name });
|
|
383
|
-
if (
|
|
384
|
-
if (req.body.id
|
|
369
|
+
if (existing_view)
|
|
370
|
+
if (+req.body.id !== existing_view.id) {
|
|
385
371
|
// may be need !== but doesnt work
|
|
386
372
|
form.errors.name = req.__("A view with this name already exists");
|
|
387
373
|
form.hasErrors = true;
|
|
@@ -402,7 +388,7 @@ router.post(
|
|
|
402
388
|
const slug = slugOptions.find((so) => so.label === v.slug);
|
|
403
389
|
v.slug = slug || null;
|
|
404
390
|
}
|
|
405
|
-
const table = await Table.findOne({ name: v.table_name });
|
|
391
|
+
//const table = await Table.findOne({ name: v.table_name });
|
|
406
392
|
delete v.table_name;
|
|
407
393
|
if (req.body.id) {
|
|
408
394
|
await View.update(v, +req.body.id);
|
|
@@ -434,7 +420,7 @@ router.post(
|
|
|
434
420
|
* @returns {void}
|
|
435
421
|
*/
|
|
436
422
|
const respondWorkflow = (view, wf, wfres, req, res) => {
|
|
437
|
-
const wrap = (contents, noCard) => ({
|
|
423
|
+
const wrap = (contents, noCard, previewURL) => ({
|
|
438
424
|
above: [
|
|
439
425
|
{
|
|
440
426
|
type: "breadcrumbs",
|
|
@@ -450,6 +436,18 @@ const respondWorkflow = (view, wf, wfres, req, res) => {
|
|
|
450
436
|
title: wfres.title,
|
|
451
437
|
contents,
|
|
452
438
|
},
|
|
439
|
+
...(previewURL
|
|
440
|
+
? [
|
|
441
|
+
{
|
|
442
|
+
type: "card",
|
|
443
|
+
title: req.__("Preview"),
|
|
444
|
+
contents: div(
|
|
445
|
+
{ id: "viewcfg-preview", "data-preview-url": previewURL },
|
|
446
|
+
script(domReady(`updateViewPreview()`))
|
|
447
|
+
),
|
|
448
|
+
},
|
|
449
|
+
]
|
|
450
|
+
: []),
|
|
453
451
|
],
|
|
454
452
|
});
|
|
455
453
|
if (wfres.flash) req.flash(wfres.flash[0], wfres.flash[1]);
|
|
@@ -472,7 +470,11 @@ const respondWorkflow = (view, wf, wfres, req, res) => {
|
|
|
472
470
|
},
|
|
473
471
|
],
|
|
474
472
|
},
|
|
475
|
-
wrap(
|
|
473
|
+
wrap(
|
|
474
|
+
renderForm(wfres.renderForm, req.csrfToken()),
|
|
475
|
+
false,
|
|
476
|
+
wfres.previewURL
|
|
477
|
+
)
|
|
476
478
|
);
|
|
477
479
|
else if (wfres.renderBuilder) {
|
|
478
480
|
wfres.renderBuilder.options.view_id = view.id;
|
|
@@ -686,14 +688,14 @@ router.post(
|
|
|
686
688
|
const view = await View.findOne({ id });
|
|
687
689
|
const roles = await User.get_roles();
|
|
688
690
|
const roleRow = roles.find((r) => r.id === +role);
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
req.__(`Minimum role
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
res.
|
|
691
|
+
const message =
|
|
692
|
+
roleRow && view
|
|
693
|
+
? req.__(`Minimum role for %s updated to %s`, view.name, roleRow.role)
|
|
694
|
+
: req.__(`Minimum role updated`);
|
|
695
|
+
if (!req.xhr) {
|
|
696
|
+
req.flash("success", message);
|
|
697
|
+
res.redirect("/viewedit");
|
|
698
|
+
} else res.json({ okay: true, responseText: message });
|
|
697
699
|
})
|
|
698
700
|
);
|
|
699
701
|
|
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
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
const request = require("supertest");
|
|
2
2
|
const getApp = require("../app");
|
|
3
3
|
const {
|
|
4
|
-
getStaffLoginCookie,
|
|
4
|
+
//getStaffLoginCookie,
|
|
5
5
|
getAdminLoginCookie,
|
|
6
6
|
toRedirect,
|
|
7
7
|
itShouldRedirectUnauthToLogin,
|
|
8
8
|
toInclude,
|
|
9
9
|
toSucceed,
|
|
10
|
-
toNotInclude,
|
|
10
|
+
//toNotInclude,
|
|
11
11
|
resetToFixtures,
|
|
12
12
|
respondJsonWith,
|
|
13
13
|
} = require("../auth/testhelp");
|
|
@@ -68,15 +68,19 @@ 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"],
|
|
77
79
|
["/roleadmin", "Theme"],
|
|
78
80
|
["/useradmin/settings", "Authentication settings"],
|
|
79
81
|
["/useradmin/ssl", "HTTPS encryption"],
|
|
82
|
+
["/useradmin/http", "HTTP settings"],
|
|
83
|
+
["/useradmin/permissions", "Permissions settings"],
|
|
80
84
|
]);
|
|
81
85
|
adminPageContains([
|
|
82
86
|
["/menu", "jquery-menu-editor"],
|
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
|
+
})
|
package/tests/fields.test.js
CHANGED