@saltcorn/server 0.7.3 → 0.7.4-beta.2
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/auth/admin.js +2 -1
- package/auth/routes.js +80 -92
- package/errors.js +51 -48
- package/locales/en.json +47 -2
- package/locales/it.json +2 -1
- package/locales/ru.json +42 -6
- package/locales/zh.json +1 -1
- package/markup/admin.js +15 -1
- package/markup/plugin-store.js +5 -5
- package/package.json +7 -7
- package/public/jquery-menu-editor.min.js +1 -1
- package/public/saltcorn-builder.css +75 -0
- package/public/saltcorn-common.js +24 -9
- package/public/saltcorn.css +28 -1
- package/public/saltcorn.js +9 -7
- package/routes/actions.js +2 -39
- package/routes/admin.js +375 -90
- package/routes/api.js +9 -1
- package/routes/common_lists.js +419 -0
- package/routes/fields.js +34 -19
- package/routes/homepage.js +60 -60
- package/routes/index.js +4 -0
- package/routes/menu.js +65 -4
- package/routes/packs.js +4 -4
- package/routes/page.js +5 -1
- package/routes/pageedit.js +13 -98
- package/routes/plugins.js +116 -118
- package/routes/settings.js +3 -3
- package/routes/tables.js +158 -193
- package/routes/tag_entries.js +173 -0
- package/routes/tags.js +266 -0
- package/routes/tenant.js +27 -27
- package/routes/utils.js +4 -0
- package/routes/view.js +18 -1
- package/routes/viewedit.js +22 -132
- package/serve.js +54 -38
- package/tests/admin.test.js +1 -1
- package/tests/api.test.js +17 -0
- package/tests/clientjs.test.js +11 -1
- package/tests/plugins.test.js +1 -1
- package/tests/viewedit.test.js +1 -1
- package/wrapper.js +57 -55
package/serve.js
CHANGED
|
@@ -30,7 +30,7 @@ const { getConfig } = require("@saltcorn/data/models/config");
|
|
|
30
30
|
const { migrate } = require("@saltcorn/data/migrate");
|
|
31
31
|
const socketio = require("socket.io");
|
|
32
32
|
const { createAdapter, setupPrimary } = require("@socket.io/cluster-adapter");
|
|
33
|
-
const { setTenant, getSessionStore } = require("./routes/utils");
|
|
33
|
+
const { setTenant, getSessionStore, get_tenant_from_req } = require("./routes/utils");
|
|
34
34
|
const passport = require("passport");
|
|
35
35
|
const { authenticate } = require("passport");
|
|
36
36
|
const View = require("@saltcorn/data/models/view");
|
|
@@ -44,6 +44,11 @@ const {
|
|
|
44
44
|
getAllTenants,
|
|
45
45
|
} = require("@saltcorn/admin-models/models/tenant");
|
|
46
46
|
const { auto_backup_now } = require("@saltcorn/admin-models/models/backup");
|
|
47
|
+
const Snapshot = require("@saltcorn/admin-models/models/snapshot");
|
|
48
|
+
|
|
49
|
+
const take_snapshot = async () => {
|
|
50
|
+
return await Snapshot.take_if_changed();
|
|
51
|
+
};
|
|
47
52
|
|
|
48
53
|
// helpful https://gist.github.com/jpoehls/2232358
|
|
49
54
|
/**
|
|
@@ -132,32 +137,33 @@ const workerDispatchMsg = ({ tenant, ...msg }) => {
|
|
|
132
137
|
*/
|
|
133
138
|
const onMessageFromWorker =
|
|
134
139
|
(masterState, { port, watchReaper, disableScheduler, pid }) =>
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
140
|
+
(msg) => {
|
|
141
|
+
//console.log("worker msg", typeof msg, msg);
|
|
142
|
+
if (msg === "Start" && !masterState.started) {
|
|
143
|
+
masterState.started = true;
|
|
144
|
+
runScheduler({
|
|
145
|
+
port,
|
|
146
|
+
watchReaper,
|
|
147
|
+
disableScheduler,
|
|
148
|
+
eachTenant,
|
|
149
|
+
auto_backup_now,
|
|
150
|
+
take_snapshot,
|
|
151
|
+
});
|
|
152
|
+
require("./systemd")({ port });
|
|
153
|
+
return true;
|
|
154
|
+
} else if (msg === "RestartServer") {
|
|
155
|
+
process.exit(0);
|
|
156
|
+
return true;
|
|
157
|
+
} else if (msg.tenant || msg.createTenant) {
|
|
158
|
+
///ie from saltcorn
|
|
159
|
+
//broadcast
|
|
160
|
+
Object.entries(cluster.workers).forEach(([wpid, w]) => {
|
|
161
|
+
if (wpid !== pid) w.send(msg);
|
|
162
|
+
});
|
|
163
|
+
workerDispatchMsg(msg); //also master
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
161
167
|
|
|
162
168
|
module.exports =
|
|
163
169
|
/**
|
|
@@ -274,6 +280,7 @@ module.exports =
|
|
|
274
280
|
disableScheduler,
|
|
275
281
|
eachTenant,
|
|
276
282
|
auto_backup_now,
|
|
283
|
+
take_snapshot,
|
|
277
284
|
});
|
|
278
285
|
}
|
|
279
286
|
Trigger.emitEvent("Startup");
|
|
@@ -345,24 +352,33 @@ const setupSocket = (...servers) => {
|
|
|
345
352
|
io.attach(server);
|
|
346
353
|
}
|
|
347
354
|
|
|
348
|
-
io.use(wrap(setTenant));
|
|
355
|
+
//io.use(wrap(setTenant));
|
|
349
356
|
io.use(wrap(getSessionStore()));
|
|
350
357
|
io.use(wrap(passport.initialize()));
|
|
351
358
|
io.use(wrap(passport.authenticate(["jwt", "session"])));
|
|
352
359
|
if (process.send && !cluster.isMaster) io.adapter(createAdapter());
|
|
353
|
-
getState().setRoomEmitter((viewname, room_id, msg) => {
|
|
354
|
-
io.to(`${viewname}_${room_id}`).emit("message", msg);
|
|
360
|
+
getState().setRoomEmitter((tenant, viewname, room_id, msg) => {
|
|
361
|
+
io.to(`${tenant}_${viewname}_${room_id}`).emit("message", msg);
|
|
355
362
|
});
|
|
356
363
|
io.on("connection", (socket) => {
|
|
357
364
|
socket.on("join_room", ([viewname, room_id]) => {
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
.
|
|
362
|
-
.
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
365
|
+
const ten = get_tenant_from_req(socket.request) || "public";
|
|
366
|
+
const f = () => {
|
|
367
|
+
try {
|
|
368
|
+
const view = View.findOne({ name: viewname });
|
|
369
|
+
if (view.viewtemplateObj.authorize_join) {
|
|
370
|
+
view.viewtemplateObj
|
|
371
|
+
.authorize_join(view.configuration, room_id, socket.request.user)
|
|
372
|
+
.then((authorized) => {
|
|
373
|
+
if (authorized) socket.join(`${ten}_${viewname}_${room_id}`);
|
|
374
|
+
});
|
|
375
|
+
} else socket.join(`${ten}_${viewname}_${room_id}`);
|
|
376
|
+
} catch (err) {
|
|
377
|
+
getState().log(1, `Socket join_room error: ${err.stack}`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (ten && ten !== "public") db.runWithTenant(ten, f);
|
|
381
|
+
else f();
|
|
366
382
|
});
|
|
367
383
|
});
|
|
368
384
|
};
|
package/tests/admin.test.js
CHANGED
|
@@ -57,7 +57,7 @@ describe("admin page", () => {
|
|
|
57
57
|
await request(app)
|
|
58
58
|
.get("/settings")
|
|
59
59
|
.set("Cookie", loginCookie)
|
|
60
|
-
.expect(toInclude("
|
|
60
|
+
.expect(toInclude("Module installation and control"));
|
|
61
61
|
});
|
|
62
62
|
it("show admin page", async () => {
|
|
63
63
|
const app = await getApp({ disableCsrf: true });
|
package/tests/api.test.js
CHANGED
|
@@ -84,6 +84,23 @@ describe("API read", () => {
|
|
|
84
84
|
)
|
|
85
85
|
);
|
|
86
86
|
});
|
|
87
|
+
it("should handle fkey args ", async () => {
|
|
88
|
+
const loginCookie = await getAdminLoginCookie();
|
|
89
|
+
const app = await getApp({ disableCsrf: true });
|
|
90
|
+
await request(app)
|
|
91
|
+
.get("/api/patients/?favbook=1")
|
|
92
|
+
.set("Cookie", loginCookie)
|
|
93
|
+
.expect(succeedJsonWith((rows) => rows.length == 1));
|
|
94
|
+
});
|
|
95
|
+
it("should handle fkey args with no value", async () => {
|
|
96
|
+
const loginCookie = await getAdminLoginCookie();
|
|
97
|
+
const app = await getApp({ disableCsrf: true });
|
|
98
|
+
await request(app)
|
|
99
|
+
.get("/api/patients/?favbook=")
|
|
100
|
+
.set("Cookie", loginCookie)
|
|
101
|
+
.expect(succeedJsonWith((rows) => rows.length == 0));
|
|
102
|
+
});
|
|
103
|
+
|
|
87
104
|
it("should get books for public with search and one field", async () => {
|
|
88
105
|
const app = await getApp({ disableCsrf: true });
|
|
89
106
|
await request(app)
|
package/tests/clientjs.test.js
CHANGED
|
@@ -34,8 +34,18 @@ test("updateQueryStringParameter", () => {
|
|
|
34
34
|
expect(removeQueryStringParameter("/foo?name=Bar&age=45", "age")).toBe(
|
|
35
35
|
"/foo?name=Bar"
|
|
36
36
|
);
|
|
37
|
+
expect(
|
|
38
|
+
updateQueryStringParameter("/foo", "publisher.publisher->name", "AK")
|
|
39
|
+
).toBe("/foo?publisher.publisher->name=AK");
|
|
40
|
+
expect(
|
|
41
|
+
updateQueryStringParameter(
|
|
42
|
+
"/foo?publisher.publisher->name=AB",
|
|
43
|
+
"publisher.publisher->name",
|
|
44
|
+
"AK"
|
|
45
|
+
)
|
|
46
|
+
).toBe("/foo?publisher.publisher->name=AK");
|
|
37
47
|
});
|
|
38
|
-
|
|
48
|
+
//publisher.publisher->name
|
|
39
49
|
test("updateQueryStringParameter hash", () => {
|
|
40
50
|
expect(updateQueryStringParameter("/foo#baz", "age", 43)).toBe(
|
|
41
51
|
"/foo?age=43#baz"
|
package/tests/plugins.test.js
CHANGED
package/tests/viewedit.test.js
CHANGED
|
@@ -54,7 +54,7 @@ describe("viewedit new List", () => {
|
|
|
54
54
|
await request(app)
|
|
55
55
|
.get("/viewedit/new")
|
|
56
56
|
.set("Cookie", loginCookie)
|
|
57
|
-
.expect(toInclude("
|
|
57
|
+
.expect(toInclude("View pattern"));
|
|
58
58
|
});
|
|
59
59
|
it("submit new view", async () => {
|
|
60
60
|
const loginCookie = await getAdminLoginCookie();
|
package/wrapper.js
CHANGED
|
@@ -13,7 +13,7 @@ const renderLayout = require("@saltcorn/markup/layout");
|
|
|
13
13
|
* @returns {T[]}
|
|
14
14
|
*/
|
|
15
15
|
const getFlashes = (req) =>
|
|
16
|
-
["error", "success", "danger", "warning","information"]
|
|
16
|
+
["error", "success", "danger", "warning", "information"]
|
|
17
17
|
.map((type) => {
|
|
18
18
|
return { type, msg: req.flash(type) };
|
|
19
19
|
})
|
|
@@ -44,11 +44,13 @@ const get_extra_menu = (role, state, req) => {
|
|
|
44
44
|
link:
|
|
45
45
|
item.type === "Link"
|
|
46
46
|
? item.url
|
|
47
|
-
: item.type === "
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
: item.type === "Action"
|
|
48
|
+
? `javascript:ajax_post_json('/menu/runaction/${item.action_name}')`
|
|
49
|
+
: item.type === "View"
|
|
50
|
+
? `/view/${encodeURIComponent(item.viewname)}`
|
|
51
|
+
: item.type === "Page"
|
|
52
|
+
? `/page/${encodeURIComponent(item.pagename)}`
|
|
53
|
+
: undefined,
|
|
52
54
|
...(item.subitems ? { subitems: transform(item.subitems) } : {}),
|
|
53
55
|
}));
|
|
54
56
|
return transform(cfg);
|
|
@@ -68,41 +70,41 @@ const get_menu = (req) => {
|
|
|
68
70
|
const extra_menu = get_extra_menu(role, state, req);
|
|
69
71
|
const authItems = isAuth
|
|
70
72
|
? [
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
73
|
+
{
|
|
74
|
+
label: req.__("User"),
|
|
75
|
+
icon: "far fa-user",
|
|
76
|
+
isUser: true,
|
|
77
|
+
subitems: [
|
|
78
|
+
{ label: small((req.user.email || "").split("@")[0]) },
|
|
79
|
+
{
|
|
80
|
+
label: req.__("User Settings"),
|
|
81
|
+
icon: "fas fa-user-cog",
|
|
80
82
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
83
|
+
link: "/auth/settings",
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
link: "/auth/logout",
|
|
87
|
+
icon: "fas fa-sign-out-alt",
|
|
88
|
+
label: req.__("Logout"),
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
]
|
|
91
93
|
: [
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
...(allow_signup
|
|
95
|
+
? [{ link: "/auth/signup", label: req.__("Sign up") }]
|
|
96
|
+
: []),
|
|
97
|
+
...(login_menu
|
|
98
|
+
? [{ link: "/auth/login", label: req.__("Login") }]
|
|
99
|
+
: []),
|
|
100
|
+
];
|
|
99
101
|
// const schema = db.getTenantSchema();
|
|
100
102
|
// Admin role id (todo move to common constants)
|
|
101
103
|
const isAdmin = role === 1;
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
104
|
+
/*
|
|
105
|
+
* Admin Menu items
|
|
106
|
+
*
|
|
107
|
+
*/
|
|
106
108
|
const adminItems = [
|
|
107
109
|
{ link: "/table", icon: "fas fa-table", label: req.__("Tables") },
|
|
108
110
|
{ link: "/viewedit", icon: "far fa-eye", label: req.__("Views") },
|
|
@@ -116,7 +118,7 @@ const get_menu = (req) => {
|
|
|
116
118
|
icon: "fas fa-tools",
|
|
117
119
|
label: req.__("About application"),
|
|
118
120
|
},
|
|
119
|
-
{ link: "/plugins", icon: "fas fa-
|
|
121
|
+
{ link: "/plugins", icon: "fas fa-cubes", label: req.__("Modules") },
|
|
120
122
|
{
|
|
121
123
|
link: "/useradmin",
|
|
122
124
|
icon: "fas fa-users-cog",
|
|
@@ -177,17 +179,17 @@ const get_headers = (req, version_tag, description, extras = []) => {
|
|
|
177
179
|
|
|
178
180
|
const iconHeader = favicon
|
|
179
181
|
? [
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
182
|
+
{
|
|
183
|
+
headerTag: `<link rel="icon" type="image/png" href="/files/serve/${favicon}">`,
|
|
184
|
+
},
|
|
185
|
+
]
|
|
184
186
|
: [];
|
|
185
187
|
const meta_description = description
|
|
186
188
|
? [
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
189
|
+
{
|
|
190
|
+
headerTag: `<meta name="description" content="${description}">`,
|
|
191
|
+
},
|
|
192
|
+
]
|
|
191
193
|
: [];
|
|
192
194
|
const stdHeaders = [
|
|
193
195
|
{
|
|
@@ -228,12 +230,12 @@ const get_brand = (state) => {
|
|
|
228
230
|
};
|
|
229
231
|
};
|
|
230
232
|
module.exports = (version_tag) =>
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
233
|
+
/**
|
|
234
|
+
*
|
|
235
|
+
* @param req
|
|
236
|
+
* @param res
|
|
237
|
+
* @param next
|
|
238
|
+
*/
|
|
237
239
|
function (req, res, next) {
|
|
238
240
|
const role = (req.user || {}).role_id || 10;
|
|
239
241
|
|
|
@@ -349,7 +351,7 @@ const defaultRenderToHtml = (s, role) =>
|
|
|
349
351
|
typeof s === "string"
|
|
350
352
|
? s
|
|
351
353
|
: renderLayout({
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
354
|
+
blockDispatch: {},
|
|
355
|
+
role,
|
|
356
|
+
layout: s,
|
|
357
|
+
});
|