@saltcorn/server 0.8.6-beta.1 → 0.8.6-beta.11
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 +3 -3
- package/auth/admin.js +4 -0
- package/auth/roleadmin.js +7 -4
- package/auth/routes.js +1 -1
- package/errors.js +1 -1
- package/locales/en.json +16 -2
- package/locales/pl.json +1 -1
- package/locales/ru.json +38 -2
- package/locales/uk.json +1170 -0
- package/package.json +8 -8
- package/public/dayjs.min.js +1 -0
- package/public/saltcorn-common.js +97 -69
- package/public/saltcorn.css +17 -0
- package/public/saltcorn.js +3 -3
- package/routes/admin.js +26 -0
- package/routes/api.js +27 -48
- package/routes/common_lists.js +4 -1
- package/routes/delete.js +3 -3
- package/routes/edit.js +1 -1
- package/routes/fields.js +32 -12
- package/routes/files.js +7 -7
- package/routes/homepage.js +3 -3
- package/routes/index.js +4 -41
- package/routes/menu.js +1 -1
- package/routes/page.js +2 -2
- package/routes/pageedit.js +2 -2
- package/routes/scapi.js +1 -1
- package/routes/search.js +4 -5
- package/routes/sync.js +84 -0
- package/routes/tables.js +5 -5
- package/routes/tenant.js +74 -27
- package/routes/view.js +3 -3
- package/routes/viewedit.js +43 -15
- package/tests/admin.test.js +8 -8
- package/tests/auth.test.js +13 -13
- package/tests/crud.test.js +2 -2
- package/tests/files.test.js +6 -6
- package/tests/kittens.test.js +11 -11
- package/tests/page.test.js +4 -5
- package/tests/sync.test.js +108 -0
- package/tests/table.test.js +6 -6
- package/tests/tenant.test.js +3 -3
- package/tests/view.test.js +128 -1
- package/tests/viewedit.test.js +4 -4
- package/wrapper.js +3 -2
package/routes/fields.js
CHANGED
|
@@ -83,6 +83,13 @@ const fieldForm = async (req, fkey_opts, existing_names, id, hasData) => {
|
|
|
83
83
|
if (!s || s === "") return req.__("Missing label");
|
|
84
84
|
if (!id && existing_names.includes(Field.labelToName(s)))
|
|
85
85
|
return req.__("Column %s already exists", s);
|
|
86
|
+
if (Field.labelToName(s) === "row")
|
|
87
|
+
return req.__("Not a valid field name");
|
|
88
|
+
try {
|
|
89
|
+
new Function(Field.labelToName(s), "return;");
|
|
90
|
+
} catch {
|
|
91
|
+
return req.__("Not a valid field name");
|
|
92
|
+
}
|
|
86
93
|
},
|
|
87
94
|
}),
|
|
88
95
|
// description
|
|
@@ -464,14 +471,11 @@ const fieldFlow = (req) =>
|
|
|
464
471
|
},
|
|
465
472
|
{
|
|
466
473
|
name: req.__("Default"),
|
|
467
|
-
onlyWhen: async (context) =>
|
|
468
|
-
|
|
469
|
-
|
|
474
|
+
onlyWhen: async (context) => context.required && !context.calculated,
|
|
475
|
+
|
|
476
|
+
form: async (context) => {
|
|
470
477
|
const table = await Table.findOne({ id: context.table_id });
|
|
471
478
|
const nrows = await table.countRows();
|
|
472
|
-
return nrows > 0;
|
|
473
|
-
},
|
|
474
|
-
form: async (context) => {
|
|
475
479
|
const formfield = new Field({
|
|
476
480
|
name: "default",
|
|
477
481
|
label: req.__("Default"),
|
|
@@ -484,12 +488,28 @@ const fieldFlow = (req) =>
|
|
|
484
488
|
},
|
|
485
489
|
});
|
|
486
490
|
await formfield.fill_fkey_options();
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
491
|
+
const defaultOptional = nrows === 0 || context.id;
|
|
492
|
+
if (defaultOptional) formfield.showIf = { set_default: true };
|
|
493
|
+
|
|
494
|
+
const form = new Form({
|
|
495
|
+
blurb: defaultOptional
|
|
496
|
+
? req.__("Set a default value for missing data")
|
|
497
|
+
: req.__(
|
|
498
|
+
"A default value is required when adding required fields to nonempty tables"
|
|
499
|
+
),
|
|
500
|
+
fields: [
|
|
501
|
+
...(defaultOptional
|
|
502
|
+
? [{ name: "set_default", label: "Set Default", type: "Bool" }]
|
|
503
|
+
: []),
|
|
504
|
+
formfield,
|
|
505
|
+
],
|
|
492
506
|
});
|
|
507
|
+
if (
|
|
508
|
+
typeof context.default !== "undefined" &&
|
|
509
|
+
context.default !== null
|
|
510
|
+
)
|
|
511
|
+
form.values.set_default = true;
|
|
512
|
+
return form;
|
|
493
513
|
},
|
|
494
514
|
},
|
|
495
515
|
],
|
|
@@ -717,7 +737,7 @@ router.post(
|
|
|
717
737
|
error_catcher(async (req, res) => {
|
|
718
738
|
const { tableName, fieldName, fieldview } = req.params;
|
|
719
739
|
const table = await Table.findOne({ name: tableName });
|
|
720
|
-
const role = req.user && req.user.id ? req.user.role_id :
|
|
740
|
+
const role = req.user && req.user.id ? req.user.role_id : 100;
|
|
721
741
|
|
|
722
742
|
const fields = await table.getFields();
|
|
723
743
|
let row = { ...req.body };
|
package/routes/files.js
CHANGED
|
@@ -130,7 +130,7 @@ router.get(
|
|
|
130
130
|
router.get(
|
|
131
131
|
"/download/*",
|
|
132
132
|
error_catcher(async (req, res) => {
|
|
133
|
-
const role = req.user && req.user.id ? req.user.role_id :
|
|
133
|
+
const role = req.user && req.user.id ? req.user.role_id : 100;
|
|
134
134
|
const user_id = req.user && req.user.id;
|
|
135
135
|
const serve_path = req.params[0];
|
|
136
136
|
const file = await File.findOne(serve_path);
|
|
@@ -155,7 +155,7 @@ router.post(
|
|
|
155
155
|
isAdmin,
|
|
156
156
|
|
|
157
157
|
error_catcher(async (req, res) => {
|
|
158
|
-
const role = req.user && req.user.id ? req.user.role_id :
|
|
158
|
+
const role = req.user && req.user.id ? req.user.role_id : 100;
|
|
159
159
|
const user_id = req.user && req.user.id;
|
|
160
160
|
const files = req.body.files;
|
|
161
161
|
const location = req.body.location;
|
|
@@ -189,7 +189,7 @@ router.post(
|
|
|
189
189
|
router.get(
|
|
190
190
|
"/serve/*",
|
|
191
191
|
error_catcher(async (req, res) => {
|
|
192
|
-
const role = req.user && req.user.id ? req.user.role_id :
|
|
192
|
+
const role = req.user && req.user.id ? req.user.role_id : 100;
|
|
193
193
|
const user_id = req.user && req.user.id;
|
|
194
194
|
const serve_path = req.params[0];
|
|
195
195
|
//let file;
|
|
@@ -201,7 +201,7 @@ router.get(
|
|
|
201
201
|
(role <= file.min_role_read || (user_id && user_id === file.user_id))
|
|
202
202
|
) {
|
|
203
203
|
res.type(file.mimetype);
|
|
204
|
-
const cacheability = file.min_role_read ===
|
|
204
|
+
const cacheability = file.min_role_read === 100 ? "public" : "private";
|
|
205
205
|
res.set("Cache-Control", `${cacheability}, max-age=86400`);
|
|
206
206
|
if (file.s3_store) s3storage.serveObject(file, res, false);
|
|
207
207
|
else res.sendFile(file.location);
|
|
@@ -228,7 +228,7 @@ router.get(
|
|
|
228
228
|
router.get(
|
|
229
229
|
"/resize/:width_str/:height_str/*",
|
|
230
230
|
error_catcher(async (req, res) => {
|
|
231
|
-
const role = req.user && req.user.id ? req.user.role_id :
|
|
231
|
+
const role = req.user && req.user.id ? req.user.role_id : 100;
|
|
232
232
|
const user_id = req.user && req.user.id;
|
|
233
233
|
const { width_str, height_str } = req.params;
|
|
234
234
|
const serve_path = req.params[0];
|
|
@@ -240,7 +240,7 @@ router.get(
|
|
|
240
240
|
(role <= file.min_role_read || (user_id && user_id === file.user_id))
|
|
241
241
|
) {
|
|
242
242
|
res.type(file.mimetype);
|
|
243
|
-
const cacheability = file.min_role_read ===
|
|
243
|
+
const cacheability = file.min_role_read === 100 ? "public" : "private";
|
|
244
244
|
res.set("Cache-Control", `${cacheability}, max-age=86400`);
|
|
245
245
|
//TODO s3
|
|
246
246
|
if (file.s3_store) s3storage.serveObject(file, res, false);
|
|
@@ -386,7 +386,7 @@ router.post(
|
|
|
386
386
|
let { folder } = req.body;
|
|
387
387
|
let jsonResp = {};
|
|
388
388
|
const min_role_upload = getState().getConfig("min_role_upload", 1);
|
|
389
|
-
const role = req.user && req.user.id ? req.user.role_id :
|
|
389
|
+
const role = req.user && req.user.id ? req.user.role_id : 100;
|
|
390
390
|
let file_for_redirect;
|
|
391
391
|
if (role > +min_role_upload) {
|
|
392
392
|
if (!req.xhr) req.flash("warning", req.__("Not authorized"));
|
package/routes/homepage.js
CHANGED
|
@@ -426,7 +426,7 @@ const welcome_page = async (req) => {
|
|
|
426
426
|
* @returns {Promise<void>}
|
|
427
427
|
*/
|
|
428
428
|
const no_views_logged_in = async (req, res) => {
|
|
429
|
-
const role = req.user && req.user.id ? req.user.role_id :
|
|
429
|
+
const role = req.user && req.user.id ? req.user.role_id : 100;
|
|
430
430
|
if (role > 1 || req.user.tenant !== db.getTenantSchema())
|
|
431
431
|
res.sendWrap(req.__("Hello"), req.__("Welcome to Saltcorn!"));
|
|
432
432
|
else {
|
|
@@ -463,7 +463,7 @@ const no_views_logged_in = async (req, res) => {
|
|
|
463
463
|
const get_config_response = async (role_id, res, req) => {
|
|
464
464
|
const modernCfg = getState().getConfig("home_page_by_role", false);
|
|
465
465
|
// predefined roles
|
|
466
|
-
const legacy_role = {
|
|
466
|
+
const legacy_role = { 100: "public", 80: "user", 40: "staff", 1: "admin" }[
|
|
467
467
|
role_id
|
|
468
468
|
];
|
|
469
469
|
let homeCfg = modernCfg && modernCfg[role_id];
|
|
@@ -497,7 +497,7 @@ module.exports =
|
|
|
497
497
|
*/
|
|
498
498
|
async (req, res) => {
|
|
499
499
|
const isAuth = req.user && req.user.id;
|
|
500
|
-
const role_id = req.user ? req.user.role_id :
|
|
500
|
+
const role_id = req.user ? req.user.role_id : 100;
|
|
501
501
|
const cfgResp = await get_config_response(role_id, res, req);
|
|
502
502
|
if (cfgResp) return;
|
|
503
503
|
|
package/routes/index.js
CHANGED
|
@@ -1,44 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Index is Main Router of App
|
|
3
|
-
* @category server
|
|
4
|
-
* @module routes/index
|
|
5
|
-
* @subcategory routes
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* All files in the routes module.
|
|
10
|
-
* @namespace routes_overview
|
|
11
|
-
* @property {module:routes/actions} actions
|
|
12
|
-
* @property {module:routes/admin} admin
|
|
13
|
-
* @property {module:routes/api} api
|
|
14
|
-
* @property {module:routes/config} config
|
|
15
|
-
* @property {module:routes/crashlog} crashlog
|
|
16
|
-
* @property {module:routes/delete} delete
|
|
17
|
-
* @property {module:routes/edit} edit
|
|
18
|
-
* @property {module:routes/eventlog} eventlog
|
|
19
|
-
* @property {module:routes/events} events
|
|
20
|
-
* @property {module:routes/fields} fields
|
|
21
|
-
* @property {module:routes/files} files
|
|
22
|
-
* @property {module:routes/homepage} homepage
|
|
23
|
-
* @property {module:routes/infoarch} infoarch
|
|
24
|
-
* @property {module:routes/library} library
|
|
25
|
-
* @property {module:routes/list} list
|
|
26
|
-
* @property {module:routes/menu} menu
|
|
27
|
-
* @property {module:routes/packs} packs
|
|
28
|
-
* @property {module:routes/page} page
|
|
29
|
-
* @property {module:routes/pageedit} pageedit
|
|
30
|
-
* @property {module:routes/plugins} plugins
|
|
31
|
-
* @property {module:routes/scapi} scapi
|
|
32
|
-
* @property {module:routes/search} search
|
|
33
|
-
* @property {module:routes/settings} settings
|
|
34
|
-
* @property {module:routes/tables} tables
|
|
35
|
-
* @property {module:routes/tenant} tenant
|
|
36
|
-
* @property {module:routes/utils} utils
|
|
37
|
-
* @property {module:routes/view} view
|
|
38
|
-
* @property {module:routes/viewedit} viewedit
|
|
39
|
-
*
|
|
40
|
-
* @category server
|
|
41
|
-
* @subcategory routes
|
|
42
3
|
*/
|
|
43
4
|
|
|
44
5
|
const table = require("./tables");
|
|
@@ -71,7 +32,8 @@ const useradmin = require("../auth/admin");
|
|
|
71
32
|
const roleadmin = require("../auth/roleadmin");
|
|
72
33
|
const tags = require("./tags");
|
|
73
34
|
const tagentries = require("./tag_entries");
|
|
74
|
-
const
|
|
35
|
+
const diagram = require("./diagram");
|
|
36
|
+
const sync = require("./sync");
|
|
75
37
|
|
|
76
38
|
module.exports =
|
|
77
39
|
/**
|
|
@@ -109,5 +71,6 @@ module.exports =
|
|
|
109
71
|
app.use("/roleadmin", roleadmin);
|
|
110
72
|
app.use("/tag", tags);
|
|
111
73
|
app.use("/tag-entries", tagentries);
|
|
112
|
-
app.use("/diagram",
|
|
74
|
+
app.use("/diagram", diagram);
|
|
75
|
+
app.use("/sync", sync);
|
|
113
76
|
};
|
package/routes/menu.js
CHANGED
|
@@ -454,7 +454,7 @@ router.post(
|
|
|
454
454
|
"/runaction/:name",
|
|
455
455
|
error_catcher(async (req, res) => {
|
|
456
456
|
const { name } = req.params;
|
|
457
|
-
const role = (req.user || {}).role_id ||
|
|
457
|
+
const role = (req.user || {}).role_id || 100;
|
|
458
458
|
const state = getState();
|
|
459
459
|
const menu_items = state.getConfig("menu_items");
|
|
460
460
|
let menu_item;
|
package/routes/page.js
CHANGED
|
@@ -42,7 +42,7 @@ router.get(
|
|
|
42
42
|
state.log(3, `Route /page/${pagename} user=${req.user?.id}`);
|
|
43
43
|
const tic = state.logLevel >= 5 ? new Date() : null;
|
|
44
44
|
|
|
45
|
-
const role = req.user && req.user.id ? req.user.role_id :
|
|
45
|
+
const role = req.user && req.user.id ? req.user.role_id : 100;
|
|
46
46
|
const db_page = await Page.findOne({ name: pagename });
|
|
47
47
|
if (db_page && role <= db_page.min_role) {
|
|
48
48
|
const contents = await db_page.run(req.query, { res, req });
|
|
@@ -104,7 +104,7 @@ router.post(
|
|
|
104
104
|
"/:pagename/action/:rndid",
|
|
105
105
|
error_catcher(async (req, res) => {
|
|
106
106
|
const { pagename, rndid } = req.params;
|
|
107
|
-
const role = req.user && req.user.id ? req.user.role_id :
|
|
107
|
+
const role = req.user && req.user.id ? req.user.role_id : 100;
|
|
108
108
|
const db_page = await Page.findOne({ name: pagename });
|
|
109
109
|
if (db_page && role <= db_page.min_role) {
|
|
110
110
|
let col;
|
package/routes/pageedit.js
CHANGED
|
@@ -387,7 +387,7 @@ router.get(
|
|
|
387
387
|
isAdmin,
|
|
388
388
|
error_catcher(async (req, res) => {
|
|
389
389
|
const { pagename } = req.params;
|
|
390
|
-
const page = await Page.
|
|
390
|
+
const [page] = await Page.find({ name: pagename });
|
|
391
391
|
if (!page) {
|
|
392
392
|
req.flash("error", req.__(`Page %s not found`, pagename));
|
|
393
393
|
res.redirect(`/pageedit`);
|
|
@@ -506,7 +506,7 @@ router.post(
|
|
|
506
506
|
const valres = form.validate(req.body);
|
|
507
507
|
if (valres.success) {
|
|
508
508
|
const home_page_by_role =
|
|
509
|
-
getState().getConfigCopy("home_page_by_role",
|
|
509
|
+
getState().getConfigCopy("home_page_by_role", {}) || {};
|
|
510
510
|
for (const role of roles) {
|
|
511
511
|
home_page_by_role[role.id] = valres.success[role.role];
|
|
512
512
|
}
|
package/routes/scapi.js
CHANGED
package/routes/search.js
CHANGED
|
@@ -165,7 +165,7 @@ const searchForm = () =>
|
|
|
165
165
|
* @returns {Promise<void>}
|
|
166
166
|
*/
|
|
167
167
|
const runSearch = async ({ q, _page, table }, req, res) => {
|
|
168
|
-
const role = (req.user || {}).role_id ||
|
|
168
|
+
const role = (req.user || {}).role_id || 100;
|
|
169
169
|
// globalSearch contains list of pairs: table, view
|
|
170
170
|
const cfg = getState().getConfig("globalSearch");
|
|
171
171
|
const page_size = getState().getConfig("search_page_size");
|
|
@@ -269,10 +269,9 @@ const runSearch = async ({ q, _page, table }, req, res) => {
|
|
|
269
269
|
router.get(
|
|
270
270
|
"/",
|
|
271
271
|
error_catcher(async (req, res) => {
|
|
272
|
-
|
|
273
272
|
const min_role = getState().getConfig("min_role_search");
|
|
274
|
-
const role = (req.user || {}).role_id ||
|
|
275
|
-
if(role>min_role){
|
|
273
|
+
const role = (req.user || {}).role_id || 100;
|
|
274
|
+
if (role > min_role) {
|
|
276
275
|
res.redirect("/"); // silent redirect to home page
|
|
277
276
|
return;
|
|
278
277
|
}
|
|
@@ -283,7 +282,7 @@ router.get(
|
|
|
283
282
|
const cfg = getState().getConfig("globalSearch");
|
|
284
283
|
|
|
285
284
|
if (!cfg) {
|
|
286
|
-
const role = (req.user || {}).role_id ||
|
|
285
|
+
const role = (req.user || {}).role_id || 100;
|
|
287
286
|
|
|
288
287
|
req.flash("warning", req.__("Search not configured"));
|
|
289
288
|
res.redirect(role === 1 ? "/search/config" : "/");
|
package/routes/sync.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
const { error_catcher } = require("./utils.js");
|
|
2
|
+
const Router = require("express-promise-router");
|
|
3
|
+
const db = require("@saltcorn/data/db");
|
|
4
|
+
const { getState } = require("@saltcorn/data/db/state");
|
|
5
|
+
const Table = require("@saltcorn/data/models/table");
|
|
6
|
+
|
|
7
|
+
const router = new Router();
|
|
8
|
+
module.exports = router;
|
|
9
|
+
|
|
10
|
+
const pickFields = (table, row) => {
|
|
11
|
+
const result = {};
|
|
12
|
+
for (const { name, type } of table.getFields()) {
|
|
13
|
+
if (name === "id") continue;
|
|
14
|
+
if (type?.name === "Date") {
|
|
15
|
+
result[name] = row[name] ? new Date(row[name]) : undefined;
|
|
16
|
+
} else {
|
|
17
|
+
result[name] = row[name];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return result;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const allowInsert = (table, user) => {
|
|
24
|
+
const role = user?.role_id || 100;
|
|
25
|
+
return table.min_role_write >= role;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const throwWithCode = (message, code) => {
|
|
29
|
+
const err = new Error(message);
|
|
30
|
+
err.statusCode = code;
|
|
31
|
+
throw err;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* insert the offline data uploaded by the mobile-app
|
|
36
|
+
*/
|
|
37
|
+
router.post(
|
|
38
|
+
"/table_data",
|
|
39
|
+
error_catcher(async (req, res) => {
|
|
40
|
+
// TODO sqlite
|
|
41
|
+
getState().log(
|
|
42
|
+
4,
|
|
43
|
+
`POST /sync/table_data user: '${req.user ? req.user.id : "public"}'`
|
|
44
|
+
);
|
|
45
|
+
let aborted = false;
|
|
46
|
+
req.socket.on("close", () => {
|
|
47
|
+
aborted = true;
|
|
48
|
+
});
|
|
49
|
+
req.socket.on("timeout", () => {
|
|
50
|
+
aborted = true;
|
|
51
|
+
});
|
|
52
|
+
const client = db.isSQLite ? db : await db.getClient();
|
|
53
|
+
try {
|
|
54
|
+
await client.query("BEGIN");
|
|
55
|
+
await client.query("SET CONSTRAINTS ALL DEFERRED");
|
|
56
|
+
for (const [tblName, offlineRows] of Object.entries(req.body.data) ||
|
|
57
|
+
[]) {
|
|
58
|
+
const table = Table.findOne({ name: tblName });
|
|
59
|
+
if (!table) throw new Error(`The table '${tblName}' does not exist.`);
|
|
60
|
+
if (!allowInsert(table, req.user))
|
|
61
|
+
throwWithCode(req.__("Not authorized"), 401);
|
|
62
|
+
if (tblName !== "users") {
|
|
63
|
+
for (const newRow of offlineRows.map((row) =>
|
|
64
|
+
pickFields(table, row)
|
|
65
|
+
)) {
|
|
66
|
+
if (aborted) throw new Error("connection closed by client");
|
|
67
|
+
await db.insert(table.name, newRow, { client: client });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (aborted) throw new Error("connection closed by client");
|
|
72
|
+
await client.query("COMMIT");
|
|
73
|
+
res.json({ success: true });
|
|
74
|
+
} catch (error) {
|
|
75
|
+
await client.query("ROLLBACK");
|
|
76
|
+
getState().log(2, `POST /sync/table_data error: '${error.message}'`);
|
|
77
|
+
res
|
|
78
|
+
.status(error.statusCode || 400)
|
|
79
|
+
.json({ error: error.message || error });
|
|
80
|
+
} finally {
|
|
81
|
+
if (!db.isSQLite) await client.release(true);
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
);
|
package/routes/tables.js
CHANGED
|
@@ -53,6 +53,8 @@ const { getState } = require("@saltcorn/data/db/state");
|
|
|
53
53
|
const { cardHeaderTabs } = require("@saltcorn/markup/layout_utils");
|
|
54
54
|
const { tablesList } = require("./common_lists");
|
|
55
55
|
const { InvalidConfiguration } = require("@saltcorn/data/utils");
|
|
56
|
+
const { sleep } = require("@saltcorn/data/utils");
|
|
57
|
+
|
|
56
58
|
const path = require("path");
|
|
57
59
|
/**
|
|
58
60
|
* @type {object}
|
|
@@ -539,11 +541,7 @@ const attribBadges = (f) => {
|
|
|
539
541
|
let s = "";
|
|
540
542
|
if (f.attributes) {
|
|
541
543
|
Object.entries(f.attributes).forEach(([k, v]) => {
|
|
542
|
-
if (
|
|
543
|
-
["summary_field", "default", "on_delete_cascade", "on_delete"].includes(
|
|
544
|
-
k
|
|
545
|
-
)
|
|
546
|
-
)
|
|
544
|
+
if (["summary_field", "on_delete_cascade", "on_delete"].includes(k))
|
|
547
545
|
return;
|
|
548
546
|
if (v || v === 0) s += badge("secondary", k);
|
|
549
547
|
});
|
|
@@ -913,10 +911,12 @@ router.post(
|
|
|
913
911
|
rest.provider_name !== "Database table"
|
|
914
912
|
) {
|
|
915
913
|
const table = await Table.create(name, rest);
|
|
914
|
+
await sleep(500); // Allow other workers to load this view
|
|
916
915
|
res.redirect(`/table/provider-cfg/${table.id}`);
|
|
917
916
|
} else {
|
|
918
917
|
delete rest.provider_name;
|
|
919
918
|
const table = await Table.create(name, rest);
|
|
919
|
+
await sleep(500); // Allow other workers to load this view
|
|
920
920
|
req.flash("success", req.__(`Table %s created`, name));
|
|
921
921
|
res.redirect(`/table/${table.id}`);
|
|
922
922
|
}
|
package/routes/tenant.js
CHANGED
|
@@ -42,7 +42,7 @@ const {
|
|
|
42
42
|
code,
|
|
43
43
|
} = require("@saltcorn/markup/tags");
|
|
44
44
|
const db = require("@saltcorn/data/db");
|
|
45
|
-
|
|
45
|
+
|
|
46
46
|
const { loadAllPlugins, loadAndSaveNewPlugin } = require("../load_plugins");
|
|
47
47
|
const { isAdmin, error_catcher } = require("./utils.js");
|
|
48
48
|
const User = require("@saltcorn/data/models/user");
|
|
@@ -53,6 +53,7 @@ const {
|
|
|
53
53
|
save_config_from_form,
|
|
54
54
|
} = require("../markup/admin.js");
|
|
55
55
|
const { getConfig } = require("@saltcorn/data/models/config");
|
|
56
|
+
//const {quote} = require("@saltcorn/db-common");
|
|
56
57
|
// todo add button backup / restore for particular tenant (available in admin tenants screens)
|
|
57
58
|
//const {
|
|
58
59
|
// create_backup,
|
|
@@ -75,6 +76,7 @@ const remove_leading_chars = (cs, s) =>
|
|
|
75
76
|
/**
|
|
76
77
|
* Declare Form to create Tenant
|
|
77
78
|
* @param {object} req - Request
|
|
79
|
+
* @param base_url - Base URL
|
|
78
80
|
* @returns {Form} - Saltcorn Form Declaration
|
|
79
81
|
* @category server
|
|
80
82
|
*/
|
|
@@ -109,8 +111,8 @@ const tenant_form = (req, base_url) =>
|
|
|
109
111
|
// TBD To allow few roles to create tenants - currently only one role has such rights simultaneously
|
|
110
112
|
const create_tenant_allowed = (req) => {
|
|
111
113
|
const required_role =
|
|
112
|
-
+getRootState().getConfig("role_to_create_tenant") ||
|
|
113
|
-
const user_role = req.user ? req.user.role_id :
|
|
114
|
+
+getRootState().getConfig("role_to_create_tenant") || 100;
|
|
115
|
+
const user_role = req.user ? req.user.role_id : 100;
|
|
114
116
|
return user_role <= required_role;
|
|
115
117
|
};
|
|
116
118
|
|
|
@@ -227,6 +229,7 @@ router.get(
|
|
|
227
229
|
* Return URL of new Tenant
|
|
228
230
|
* @param {object} req - Request
|
|
229
231
|
* @param {string} subdomain - Tenant Subdomain name string
|
|
232
|
+
* @param base_url - Base URL
|
|
230
233
|
* @returns {string}
|
|
231
234
|
*/
|
|
232
235
|
const getNewURL = (req, subdomain, base_url) => {
|
|
@@ -280,7 +283,7 @@ router.post(
|
|
|
280
283
|
const description = valres.success.description;
|
|
281
284
|
// get list of tenants
|
|
282
285
|
const allTens = await getAllTenants();
|
|
283
|
-
if (allTens.includes(subdomain) || !subdomain) {
|
|
286
|
+
if (allTens.includes(subdomain) || !subdomain || subdomain === "public") {
|
|
284
287
|
form.errors.subdomain = req.__(
|
|
285
288
|
"A site with this subdomain already exists"
|
|
286
289
|
);
|
|
@@ -446,7 +449,7 @@ const tenant_settings_form = (req) =>
|
|
|
446
449
|
"tenant_template",
|
|
447
450
|
"tenant_baseurl",
|
|
448
451
|
"tenant_create_unauth_redirect",
|
|
449
|
-
{ section_header: "Tenant application capabilities" },
|
|
452
|
+
{ section_header: req.__("Tenant application capabilities") },
|
|
450
453
|
"tenants_install_git",
|
|
451
454
|
"tenants_set_npm_modules",
|
|
452
455
|
"tenants_unsafe_plugins",
|
|
@@ -536,8 +539,11 @@ const get_tenant_info = async (subdomain) => {
|
|
|
536
539
|
// get tenant row
|
|
537
540
|
const ten = await Tenant.findOne({ subdomain: saneDomain });
|
|
538
541
|
if (ten) {
|
|
542
|
+
//info.ten = ten;
|
|
539
543
|
info.description = ten.description;
|
|
540
544
|
info.created = ten.created;
|
|
545
|
+
info.template = ten.template;
|
|
546
|
+
info.email = ten.email;
|
|
541
547
|
}
|
|
542
548
|
|
|
543
549
|
// get data from tenant schema
|
|
@@ -547,10 +553,19 @@ const get_tenant_info = async (subdomain) => {
|
|
|
547
553
|
if (firstUser && firstUser.length > 0) {
|
|
548
554
|
info.first_user_email = firstUser[0].email;
|
|
549
555
|
}
|
|
556
|
+
// todo sort in alphabet order
|
|
557
|
+
// config items count
|
|
558
|
+
info.nconfigs = await db.count("_sc_config");
|
|
559
|
+
// error messages count
|
|
560
|
+
info.nerrors = await db.count("_sc_errors");
|
|
561
|
+
// event log
|
|
562
|
+
info.nevent_log = await db.count("_sc_event_log");
|
|
550
563
|
// users count
|
|
551
564
|
info.nusers = await db.count("users");
|
|
552
565
|
// roles count
|
|
553
566
|
info.nroles = await db.count("_sc_roles");
|
|
567
|
+
// table_constraints count
|
|
568
|
+
info.ntable_constraints = await db.count("_sc_table_constraints");
|
|
554
569
|
// tables count
|
|
555
570
|
info.ntables = await db.count("_sc_tables");
|
|
556
571
|
// table fields count
|
|
@@ -561,19 +576,25 @@ const get_tenant_info = async (subdomain) => {
|
|
|
561
576
|
info.nfiles = await db.count("_sc_files");
|
|
562
577
|
// pages count
|
|
563
578
|
info.npages = await db.count("_sc_pages");
|
|
564
|
-
// triggers (actions)
|
|
579
|
+
// triggers (actions) count
|
|
565
580
|
info.nactions = await db.count("_sc_triggers");
|
|
566
|
-
// error messages count
|
|
567
|
-
info.nerrors = await db.count("_sc_errors");
|
|
568
|
-
// config items count
|
|
569
|
-
info.nconfigs = await db.count("_sc_config");
|
|
570
581
|
// plugins count
|
|
571
582
|
info.nplugins = await db.count("_sc_plugins");
|
|
572
583
|
// migration count
|
|
573
584
|
info.nmigrations = await db.count("_sc_migrations");
|
|
574
585
|
// library count
|
|
575
586
|
info.nlibrary = await db.count("_sc_library");
|
|
576
|
-
//
|
|
587
|
+
// notifications
|
|
588
|
+
info.nnotifications = await db.count("_sc_notifications");
|
|
589
|
+
// tags
|
|
590
|
+
info.ntags = await db.count("_sc_tags");
|
|
591
|
+
// tag_entries
|
|
592
|
+
info.ntag_entries = await db.count("_sc_tag_entries");
|
|
593
|
+
// snapshots
|
|
594
|
+
info.nsnapshots = await db.count("_sc_snapshots");
|
|
595
|
+
// session - Only for main app?
|
|
596
|
+
//info.nsession = await db.count("_sc_session");
|
|
597
|
+
|
|
577
598
|
// base url
|
|
578
599
|
info.base_url = await getConfig("base_url");
|
|
579
600
|
return info;
|
|
@@ -628,51 +649,76 @@ router.get(
|
|
|
628
649
|
{ href: "mailto:" + info.first_user_email },
|
|
629
650
|
info.first_user_email
|
|
630
651
|
)
|
|
631
|
-
)
|
|
652
|
+
),
|
|
653
|
+
th(req.__("Template")),
|
|
654
|
+
td(a({ href: info.base_url }, info.template))
|
|
632
655
|
),
|
|
633
656
|
tr(
|
|
634
657
|
th(req.__("Users")),
|
|
635
|
-
td(a({ href: info.base_url + "useradmin" }, info.nusers))
|
|
636
|
-
),
|
|
637
|
-
tr(
|
|
658
|
+
td(a({ href: info.base_url + "useradmin" }, info.nusers)),
|
|
638
659
|
th(req.__("Roles")),
|
|
639
660
|
td(a({ href: info.base_url + "roleadmin" }, info.nroles))
|
|
640
661
|
),
|
|
641
662
|
tr(
|
|
642
663
|
th(req.__("Tables")),
|
|
643
|
-
td(a({ href: info.base_url + "table" }, info.ntables))
|
|
644
|
-
),
|
|
645
|
-
tr(
|
|
664
|
+
td(a({ href: info.base_url + "table" }, info.ntables)),
|
|
646
665
|
th(req.__("Table columns")),
|
|
647
666
|
td(a({ href: info.base_url + "table" }, info.nfields))
|
|
648
667
|
),
|
|
649
668
|
tr(
|
|
650
|
-
th(req.__("
|
|
651
|
-
td(
|
|
669
|
+
th(req.__("Table constraints")),
|
|
670
|
+
td(
|
|
671
|
+
a(
|
|
672
|
+
{ href: info.base_url + "table" },
|
|
673
|
+
info.ntable_constraints
|
|
674
|
+
)
|
|
675
|
+
),
|
|
676
|
+
th(req.__("Library")),
|
|
677
|
+
td(a({ href: info.base_url + "library/list" }, info.nlibrary))
|
|
652
678
|
),
|
|
653
679
|
tr(
|
|
680
|
+
th(req.__("Views")),
|
|
681
|
+
td(a({ href: info.base_url + "viewedit" }, info.nviews)),
|
|
654
682
|
th(req.__("Pages")),
|
|
655
683
|
td(a({ href: info.base_url + "pageedit" }, info.npages))
|
|
656
684
|
),
|
|
657
685
|
tr(
|
|
658
686
|
th(req.__("Files")),
|
|
659
|
-
td(a({ href: info.base_url + "files" }, info.nfiles))
|
|
660
|
-
),
|
|
661
|
-
tr(
|
|
687
|
+
td(a({ href: info.base_url + "files" }, info.nfiles)),
|
|
662
688
|
th(req.__("Actions")),
|
|
663
689
|
td(a({ href: info.base_url + "actions" }, info.nactions))
|
|
664
690
|
),
|
|
665
691
|
tr(
|
|
666
692
|
th(req.__("Modules")),
|
|
667
|
-
td(a({ href: info.base_url + "plugins" }, info.nplugins))
|
|
668
|
-
),
|
|
669
|
-
tr(
|
|
693
|
+
td(a({ href: info.base_url + "plugins" }, info.nplugins)),
|
|
670
694
|
th(req.__("Configuration items")),
|
|
671
695
|
td(a({ href: info.base_url + "admin" }, info.nconfigs))
|
|
672
696
|
),
|
|
673
697
|
tr(
|
|
698
|
+
// Crashlogs only for main site?
|
|
674
699
|
th(req.__("Crashlogs")),
|
|
675
|
-
td(a({ href: info.base_url + "crashlog" }, info.nerrors))
|
|
700
|
+
td(a({ href: info.base_url + "crashlog" }, info.nerrors)),
|
|
701
|
+
//th(req.__("Sessions")),
|
|
702
|
+
//td(a({ href: info.base_url + "crashlog" }, info.nsessions)),
|
|
703
|
+
th(req.__("Event logs")),
|
|
704
|
+
td(a({ href: info.base_url + "eventlog" }, info.nevent_log))
|
|
705
|
+
// Notifications only for main site?
|
|
706
|
+
//th(req.__("Notifications")),
|
|
707
|
+
//td(a({ href: info.base_url + "???" }, info.nnotifications)),
|
|
708
|
+
),
|
|
709
|
+
tr(
|
|
710
|
+
th(req.__("Snapshots")),
|
|
711
|
+
td(
|
|
712
|
+
a({ href: info.base_url + "admin/backup" }, info.nsnapshots)
|
|
713
|
+
),
|
|
714
|
+
th(req.__("Migrations")),
|
|
715
|
+
td(a({ href: info.base_url + "admin" }, info.nmigrations))
|
|
716
|
+
),
|
|
717
|
+
tr(
|
|
718
|
+
th(req.__("Tags")),
|
|
719
|
+
td(a({ href: info.base_url + "tag" }, info.ntags)),
|
|
720
|
+
th(req.__("Tag Entries")),
|
|
721
|
+
td(a({ href: info.base_url + "tag" }, info.ntag_entries))
|
|
676
722
|
)
|
|
677
723
|
),
|
|
678
724
|
],
|
|
@@ -697,6 +743,7 @@ router.get(
|
|
|
697
743
|
name: "description",
|
|
698
744
|
label: req.__("Description"),
|
|
699
745
|
type: "String",
|
|
746
|
+
fieldview: "textarea",
|
|
700
747
|
},
|
|
701
748
|
],
|
|
702
749
|
values: {
|