@saltcorn/server 0.7.3-beta.7 → 0.7.4-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/auth/admin.js +9 -5
- package/auth/routes.js +16 -6
- package/errors.js +51 -48
- package/locales/en.json +29 -1
- package/locales/it.json +2 -1
- package/locales/ru.json +42 -6
- package/locales/zh.json +1 -1
- package/markup/admin.js +4 -3
- 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 +26 -10
- package/public/saltcorn.css +4 -0
- package/public/saltcorn.js +5 -1
- package/routes/admin.js +387 -96
- package/routes/api.js +9 -1
- package/routes/eventlog.js +24 -22
- package/routes/fields.js +11 -13
- package/routes/files.js +5 -5
- package/routes/homepage.js +60 -60
- package/routes/infoarch.js +6 -3
- package/routes/menu.js +65 -4
- package/routes/packs.js +4 -4
- package/routes/page.js +5 -1
- package/routes/pageedit.js +9 -1
- package/routes/plugins.js +187 -123
- package/routes/search.js +4 -2
- package/routes/settings.js +3 -3
- package/routes/tables.js +191 -190
- package/routes/tenant.js +31 -29
- package/routes/utils.js +4 -0
- package/routes/view.js +18 -1
- package/routes/viewedit.js +78 -70
- 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/routes/view.js
CHANGED
|
@@ -20,6 +20,7 @@ const {
|
|
|
20
20
|
} = require("../routes/utils.js");
|
|
21
21
|
const { add_edit_bar } = require("../markup/admin.js");
|
|
22
22
|
const { InvalidConfiguration } = require("@saltcorn/data/utils");
|
|
23
|
+
const { getState } = require("@saltcorn/data/db/state");
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* @type {object}
|
|
@@ -44,8 +45,11 @@ router.get(
|
|
|
44
45
|
const query = { ...req.query };
|
|
45
46
|
const view = await View.findOne({ name: viewname });
|
|
46
47
|
const role = req.user && req.user.id ? req.user.role_id : 10;
|
|
48
|
+
const state = getState();
|
|
49
|
+
state.log(3, `Route /view/${viewname} user=${req.user?.id}`);
|
|
47
50
|
if (!view) {
|
|
48
51
|
req.flash("danger", req.__(`No such view: %s`, text(viewname)));
|
|
52
|
+
state.log(2, `View ${viewname} not found`);
|
|
49
53
|
res.redirect("/");
|
|
50
54
|
return;
|
|
51
55
|
}
|
|
@@ -56,6 +60,7 @@ router.get(
|
|
|
56
60
|
!(await view.authorise_get({ query, req, ...view }))
|
|
57
61
|
) {
|
|
58
62
|
req.flash("danger", req.__("Not authorized"));
|
|
63
|
+
state.log(2, `View ${viewname} not authorized`);
|
|
59
64
|
res.redirect("/");
|
|
60
65
|
return;
|
|
61
66
|
}
|
|
@@ -123,13 +128,21 @@ router.post(
|
|
|
123
128
|
error_catcher(async (req, res) => {
|
|
124
129
|
const { viewname, route } = req.params;
|
|
125
130
|
const role = req.user && req.user.id ? req.user.role_id : 10;
|
|
131
|
+
const state = getState();
|
|
132
|
+
state.log(
|
|
133
|
+
3,
|
|
134
|
+
`Route /view/${viewname} viewroute ${route} user=${req.user?.id}`
|
|
135
|
+
);
|
|
126
136
|
|
|
127
137
|
const view = await View.findOne({ name: viewname });
|
|
128
138
|
if (!view) {
|
|
129
139
|
req.flash("danger", req.__(`No such view: %s`, text(viewname)));
|
|
140
|
+
state.log(2, `View ${viewname} not found`);
|
|
130
141
|
res.redirect("/");
|
|
131
142
|
} else if (role > view.min_role) {
|
|
132
143
|
req.flash("danger", req.__("Not authorized"));
|
|
144
|
+
state.log(2, `View ${viewname} viewroute ${route} not authorized`);
|
|
145
|
+
|
|
133
146
|
res.redirect("/");
|
|
134
147
|
} else {
|
|
135
148
|
await view.runRoute(route, req.body, res, { res, req });
|
|
@@ -150,10 +163,12 @@ router.post(
|
|
|
150
163
|
const { viewname } = req.params;
|
|
151
164
|
const role = req.user && req.user.id ? req.user.role_id : 10;
|
|
152
165
|
const query = { ...req.query };
|
|
153
|
-
|
|
166
|
+
const state = getState();
|
|
167
|
+
state.log(3, `Route /view/${viewname} POST user=${req.user?.id}`);
|
|
154
168
|
const view = await View.findOne({ name: viewname });
|
|
155
169
|
if (!view) {
|
|
156
170
|
req.flash("danger", req.__(`No such view: %s`, text(viewname)));
|
|
171
|
+
state.log(2, `View ${viewname} not found`);
|
|
157
172
|
res.redirect("/");
|
|
158
173
|
return;
|
|
159
174
|
}
|
|
@@ -164,6 +179,8 @@ router.post(
|
|
|
164
179
|
!(await view.authorise_post({ body: req.body, req, ...view }))
|
|
165
180
|
) {
|
|
166
181
|
req.flash("danger", req.__("Not authorized"));
|
|
182
|
+
state.log(2, `View ${viewname} POST not authorized`);
|
|
183
|
+
|
|
167
184
|
res.redirect("/");
|
|
168
185
|
} else if (!view.runPost) {
|
|
169
186
|
throw new InvalidConfiguration(
|
package/routes/viewedit.js
CHANGED
|
@@ -98,6 +98,13 @@ const view_dropdown = (view, req) =>
|
|
|
98
98
|
'<i class="far fa-copy"></i> ' + req.__("Duplicate"),
|
|
99
99
|
req
|
|
100
100
|
),
|
|
101
|
+
a(
|
|
102
|
+
{
|
|
103
|
+
class: "dropdown-item",
|
|
104
|
+
href: `javascript:ajax_modal('/admin/snapshot-restore/view/${view.name}')`,
|
|
105
|
+
},
|
|
106
|
+
'<i class="fas fa-undo-alt"></i> ' + req.__("Restore")
|
|
107
|
+
),
|
|
101
108
|
div({ class: "dropdown-divider" }),
|
|
102
109
|
post_dropdown_item(
|
|
103
110
|
`/viewedit/delete/${view.id}`,
|
|
@@ -138,57 +145,57 @@ router.get(
|
|
|
138
145
|
const viewMarkup =
|
|
139
146
|
views.length > 0
|
|
140
147
|
? mkTable(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
148
|
+
[
|
|
149
|
+
{
|
|
150
|
+
label: req.__("Name"),
|
|
151
|
+
key: (r) => link(`/view/${encodeURIComponent(r.name)}`, r.name),
|
|
152
|
+
sortlink: `javascript:set_state_field('_sortby', 'name')`,
|
|
153
|
+
},
|
|
154
|
+
// description - currently I dont want to show description in view list
|
|
155
|
+
// because description can be long
|
|
156
|
+
/*
|
|
157
|
+
{
|
|
158
|
+
label: req.__("Description"),
|
|
159
|
+
key: "description",
|
|
160
|
+
// this is sorting by column
|
|
161
|
+
sortlink: `javascript:set_state_field('_sortby', 'description')`,
|
|
162
|
+
},
|
|
163
|
+
*/
|
|
164
|
+
// template
|
|
165
|
+
{
|
|
166
|
+
label: req.__("Pattern"),
|
|
167
|
+
key: "viewtemplate",
|
|
168
|
+
sortlink: `javascript:set_state_field('_sortby', 'viewtemplate')`,
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
label: req.__("Table"),
|
|
172
|
+
key: (r) => link(`/table/${r.table}`, r.table),
|
|
173
|
+
sortlink: `javascript:set_state_field('_sortby', 'table')`,
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
label: req.__("Role to access"),
|
|
177
|
+
key: (row) => editViewRoleForm(row, roles, req),
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
label: "",
|
|
181
|
+
key: (r) =>
|
|
182
|
+
link(
|
|
183
|
+
`/viewedit/config/${encodeURIComponent(r.name)}`,
|
|
184
|
+
req.__("Configure")
|
|
185
|
+
),
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
label: "",
|
|
189
|
+
key: (r) => view_dropdown(r, req),
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
views,
|
|
193
|
+
{ hover: true }
|
|
194
|
+
)
|
|
188
195
|
: div(
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
196
|
+
h4(req.__("No views defined")),
|
|
197
|
+
p(req.__("Views define how table rows are displayed to the user"))
|
|
198
|
+
);
|
|
192
199
|
res.sendWrap(req.__(`Views`), {
|
|
193
200
|
above: [
|
|
194
201
|
{
|
|
@@ -203,14 +210,14 @@ router.get(
|
|
|
203
210
|
viewMarkup,
|
|
204
211
|
tables.length > 0
|
|
205
212
|
? a(
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
213
|
+
{ href: `/viewedit/new`, class: "btn btn-primary" },
|
|
214
|
+
req.__("Create view")
|
|
215
|
+
)
|
|
209
216
|
: p(
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
217
|
+
req.__(
|
|
218
|
+
"You must create at least one table before you can create views."
|
|
219
|
+
)
|
|
220
|
+
),
|
|
214
221
|
],
|
|
215
222
|
},
|
|
216
223
|
],
|
|
@@ -263,10 +270,10 @@ const viewForm = async (req, tableOptions, roles, pages, values) => {
|
|
|
263
270
|
),
|
|
264
271
|
}),
|
|
265
272
|
new Field({
|
|
266
|
-
label: req.__("
|
|
273
|
+
label: req.__("View pattern"),
|
|
267
274
|
name: "viewtemplate",
|
|
268
275
|
input_type: "select",
|
|
269
|
-
sublabel: req.__("
|
|
276
|
+
sublabel: req.__("The view pattern sets the foundation of how the view relates to the table and the behaviour of the view"),
|
|
270
277
|
options: Object.keys(getState().viewtemplates),
|
|
271
278
|
attributes: {
|
|
272
279
|
explainers: mapObjectValues(
|
|
@@ -320,15 +327,15 @@ const viewForm = async (req, tableOptions, roles, pages, values) => {
|
|
|
320
327
|
}),
|
|
321
328
|
...(isEdit
|
|
322
329
|
? [
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
330
|
+
new Field({
|
|
331
|
+
name: "viewtemplate",
|
|
332
|
+
input_type: "hidden",
|
|
333
|
+
}),
|
|
334
|
+
new Field({
|
|
335
|
+
name: "table_name",
|
|
336
|
+
input_type: "hidden",
|
|
337
|
+
}),
|
|
338
|
+
]
|
|
332
339
|
: []),
|
|
333
340
|
],
|
|
334
341
|
values,
|
|
@@ -355,7 +362,7 @@ router.get(
|
|
|
355
362
|
}
|
|
356
363
|
const tables = await Table.find_with_external();
|
|
357
364
|
const currentTable = tables.find(
|
|
358
|
-
(t) => t.id === viewrow.table_id || t.name === viewrow.exttable_name
|
|
365
|
+
(t) => (t.id && t.id === viewrow.table_id) || t.name === viewrow.exttable_name
|
|
359
366
|
);
|
|
360
367
|
viewrow.table_name = currentTable && currentTable.name;
|
|
361
368
|
if (viewrow.slug && currentTable) {
|
|
@@ -584,7 +591,7 @@ router.get(
|
|
|
584
591
|
isAdmin,
|
|
585
592
|
error_catcher(async (req, res) => {
|
|
586
593
|
const { name } = req.params;
|
|
587
|
-
|
|
594
|
+
const { step } = req.query;
|
|
588
595
|
const view = await View.findOne({ name });
|
|
589
596
|
if (!view) {
|
|
590
597
|
req.flash("error", `View not found: ${text(name)}`);
|
|
@@ -601,6 +608,7 @@ router.get(
|
|
|
601
608
|
table_id: view.table_id,
|
|
602
609
|
exttable_name: view.exttable_name,
|
|
603
610
|
viewname: name,
|
|
611
|
+
...(step ? { stepName: step } : {}),
|
|
604
612
|
},
|
|
605
613
|
req
|
|
606
614
|
);
|
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
|
+
});
|