@saltcorn/server 0.9.4-beta.12 → 0.9.4-beta.13
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 +13 -1
- package/auth/admin.js +17 -2
- package/auth/routes.js +3 -2
- package/locales/en.json +11 -2
- package/package.json +9 -9
- package/public/log_viewer_utils.js +156 -0
- package/routes/actions.js +1 -6
- package/routes/admin.js +68 -4
- package/routes/common_lists.js +2 -1
- package/routes/utils.js +4 -2
- package/serve.js +51 -4
- package/tests/admin.test.js +7 -1
package/app.js
CHANGED
|
@@ -125,7 +125,19 @@ const getApp = async (opts = {}) => {
|
|
|
125
125
|
|
|
126
126
|
// https://www.npmjs.com/package/helmet
|
|
127
127
|
// helmet is secure app by adding HTTP headers
|
|
128
|
-
|
|
128
|
+
|
|
129
|
+
const cross_domain_iframe = getState().getConfig(
|
|
130
|
+
"cross_domain_iframe",
|
|
131
|
+
false
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const helmetOptions = {
|
|
135
|
+
contentSecurityPolicy: false,
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
if (cross_domain_iframe) helmetOptions.xFrameOptions = false;
|
|
139
|
+
app.use(helmet(helmetOptions));
|
|
140
|
+
|
|
129
141
|
// TODO ch find a better solution
|
|
130
142
|
app.use(cors());
|
|
131
143
|
const bodyLimit = getState().getConfig("body_limit");
|
package/auth/admin.js
CHANGED
|
@@ -42,6 +42,7 @@ const {
|
|
|
42
42
|
getBaseDomain,
|
|
43
43
|
hostname_matches_baseurl,
|
|
44
44
|
is_hsts_tld,
|
|
45
|
+
check_if_restart_required,
|
|
45
46
|
} = require("../markup/admin");
|
|
46
47
|
const { send_verification_email } = require("@saltcorn/data/models/email");
|
|
47
48
|
const { expressionValidator } = require("@saltcorn/data/models/expression");
|
|
@@ -354,6 +355,7 @@ const auth_settings_form = async (req) =>
|
|
|
354
355
|
"signup_form",
|
|
355
356
|
"user_settings_form",
|
|
356
357
|
"verification_view",
|
|
358
|
+
"logout_url",
|
|
357
359
|
"elevate_verified",
|
|
358
360
|
"email_mask",
|
|
359
361
|
],
|
|
@@ -376,6 +378,7 @@ const http_settings_form = async (req) =>
|
|
|
376
378
|
//"cookie_sessions",
|
|
377
379
|
"public_cache_maxage",
|
|
378
380
|
"custom_http_headers",
|
|
381
|
+
"cross_domain_iframe",
|
|
379
382
|
"body_limit",
|
|
380
383
|
"url_encoded_limit",
|
|
381
384
|
],
|
|
@@ -508,12 +511,21 @@ router.post(
|
|
|
508
511
|
},
|
|
509
512
|
});
|
|
510
513
|
} else {
|
|
514
|
+
const restart_required = check_if_restart_required(form, req);
|
|
515
|
+
|
|
511
516
|
await save_config_from_form(form);
|
|
512
517
|
|
|
513
518
|
if (!req.xhr) {
|
|
514
519
|
req.flash("success", req.__("HTTP settings updated"));
|
|
515
520
|
res.redirect("/useradmin/http");
|
|
516
|
-
} else
|
|
521
|
+
} else {
|
|
522
|
+
if (restart_required)
|
|
523
|
+
res.json({
|
|
524
|
+
success: "ok",
|
|
525
|
+
notify: req.__("Restart required for changes to take effect."),
|
|
526
|
+
});
|
|
527
|
+
else res.json({ success: "ok" });
|
|
528
|
+
}
|
|
517
529
|
}
|
|
518
530
|
})
|
|
519
531
|
);
|
|
@@ -1017,7 +1029,10 @@ router.post(
|
|
|
1017
1029
|
? req.__(` with password %s`, code(password))
|
|
1018
1030
|
: "";
|
|
1019
1031
|
|
|
1020
|
-
req.flash(
|
|
1032
|
+
req.flash(
|
|
1033
|
+
pwflash ? "warning" : "success",
|
|
1034
|
+
req.__(`User %s created`, email) + pwflash
|
|
1035
|
+
);
|
|
1021
1036
|
|
|
1022
1037
|
if (rnd_password && send_pwreset_email)
|
|
1023
1038
|
await send_reset_email(u, req, { creating: true });
|
package/auth/routes.js
CHANGED
|
@@ -332,17 +332,18 @@ router.get("/logout", async (req, res, next) => {
|
|
|
332
332
|
res.json({ success: true });
|
|
333
333
|
} else if (req.logout) {
|
|
334
334
|
req.logout(function (err) {
|
|
335
|
+
const destination = getState().getConfig("logout_url", "/auth/login");
|
|
335
336
|
if (req.session.destroy)
|
|
336
337
|
req.session.destroy((err) => {
|
|
337
338
|
if (err) return next(err);
|
|
338
339
|
req.logout(() => {
|
|
339
|
-
res.redirect(
|
|
340
|
+
res.redirect(destination);
|
|
340
341
|
});
|
|
341
342
|
});
|
|
342
343
|
else {
|
|
343
344
|
req.logout(function (err) {
|
|
344
345
|
req.session = null;
|
|
345
|
-
res.redirect(
|
|
346
|
+
res.redirect(destination);
|
|
346
347
|
});
|
|
347
348
|
}
|
|
348
349
|
});
|
package/locales/en.json
CHANGED
|
@@ -1366,5 +1366,14 @@
|
|
|
1366
1366
|
"Serve a random page, ignoring the eligible formula. Within a session, reloads will always deliver the same page. This is a basic requirement for A/B testing.": "Serve a random page, ignoring the eligible formula. Within a session, reloads will always deliver the same page. This is a basic requirement for A/B testing.",
|
|
1367
1367
|
"Pagegroup %s not found": "Pagegroup %s not found",
|
|
1368
1368
|
"Create trigger": "Create trigger",
|
|
1369
|
-
"Omit the menu from this view": "Omit the menu from this view"
|
|
1370
|
-
|
|
1369
|
+
"Omit the menu from this view": "Omit the menu from this view",
|
|
1370
|
+
"Cross-domain iframe": "Cross-domain iframe",
|
|
1371
|
+
"Allow embedding in iframe on different domains. Unsets the X-Frame-Options header": "Allow embedding in iframe on different domains. Unsets the X-Frame-Options header",
|
|
1372
|
+
"Logout URL": "Logout URL",
|
|
1373
|
+
"The URL to direct to after logout": "The URL to direct to after logout",
|
|
1374
|
+
"Runtime informations": "Runtime informations",
|
|
1375
|
+
"open logs viewer": "open logs viewer",
|
|
1376
|
+
"Server logs": "Server logs",
|
|
1377
|
+
"Timestamp": "Timestamp",
|
|
1378
|
+
"Message": "Message"
|
|
1379
|
+
}
|
package/package.json
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/server",
|
|
3
|
-
"version": "0.9.4-beta.
|
|
3
|
+
"version": "0.9.4-beta.13",
|
|
4
4
|
"description": "Server app for Saltcorn, open-source no-code platform",
|
|
5
5
|
"homepage": "https://saltcorn.com",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@aws-sdk/client-s3": "^3.451.0",
|
|
10
|
-
"@saltcorn/base-plugin": "0.9.4-beta.
|
|
11
|
-
"@saltcorn/builder": "0.9.4-beta.
|
|
12
|
-
"@saltcorn/data": "0.9.4-beta.
|
|
13
|
-
"@saltcorn/admin-models": "0.9.4-beta.
|
|
14
|
-
"@saltcorn/filemanager": "0.9.4-beta.
|
|
15
|
-
"@saltcorn/markup": "0.9.4-beta.
|
|
16
|
-
"@saltcorn/sbadmin2": "0.9.4-beta.
|
|
10
|
+
"@saltcorn/base-plugin": "0.9.4-beta.13",
|
|
11
|
+
"@saltcorn/builder": "0.9.4-beta.13",
|
|
12
|
+
"@saltcorn/data": "0.9.4-beta.13",
|
|
13
|
+
"@saltcorn/admin-models": "0.9.4-beta.13",
|
|
14
|
+
"@saltcorn/filemanager": "0.9.4-beta.13",
|
|
15
|
+
"@saltcorn/markup": "0.9.4-beta.13",
|
|
16
|
+
"@saltcorn/sbadmin2": "0.9.4-beta.13",
|
|
17
17
|
"@socket.io/cluster-adapter": "^0.2.1",
|
|
18
18
|
"@socket.io/sticky": "^1.0.1",
|
|
19
19
|
"adm-zip": "0.5.10",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"express-session": "^1.17.1",
|
|
34
34
|
"greenlock": "^4.0.4",
|
|
35
35
|
"greenlock-express": "^4.0.3",
|
|
36
|
-
"helmet": "^
|
|
36
|
+
"helmet": "^7.1.0",
|
|
37
37
|
"i18n": "^0.15.1",
|
|
38
38
|
"imapflow": "1.0.123",
|
|
39
39
|
"jsonwebtoken": "^9.0.0",
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
var logViewerHelpers = (() => {
|
|
2
|
+
const messages = [];
|
|
3
|
+
let currentPage = 1;
|
|
4
|
+
const rowsPerPage = 20;
|
|
5
|
+
const dateOptions = {
|
|
6
|
+
month: "short",
|
|
7
|
+
day: "numeric",
|
|
8
|
+
hour: "2-digit",
|
|
9
|
+
minute: "2-digit",
|
|
10
|
+
second: "2-digit",
|
|
11
|
+
};
|
|
12
|
+
let lostConnection = false;
|
|
13
|
+
|
|
14
|
+
const logLevelColor = (level) => {
|
|
15
|
+
switch (parseInt(level)) {
|
|
16
|
+
case 4:
|
|
17
|
+
return "text-primary";
|
|
18
|
+
case 3:
|
|
19
|
+
return "text-warning";
|
|
20
|
+
case 2:
|
|
21
|
+
return "text-info";
|
|
22
|
+
case 1:
|
|
23
|
+
return "text-danger";
|
|
24
|
+
}
|
|
25
|
+
return "";
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const buildLogRow = (date, level, text) => {
|
|
29
|
+
return `
|
|
30
|
+
<tr>
|
|
31
|
+
<td>
|
|
32
|
+
${new Date(date).toLocaleDateString(
|
|
33
|
+
window.detected_locale || "en",
|
|
34
|
+
dateOptions
|
|
35
|
+
)}
|
|
36
|
+
</td>
|
|
37
|
+
<td class="${logLevelColor(level)} fw-bold">
|
|
38
|
+
${text}
|
|
39
|
+
</td>
|
|
40
|
+
</tr>
|
|
41
|
+
`;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const buildPaginationItem = (n) => `
|
|
45
|
+
<li class="page-item">
|
|
46
|
+
<span
|
|
47
|
+
class="page-link link-style"
|
|
48
|
+
onclick="logViewerHelpers.goToLogsPage(${n})"
|
|
49
|
+
role="link">
|
|
50
|
+
${n}
|
|
51
|
+
</span>
|
|
52
|
+
</li>
|
|
53
|
+
`;
|
|
54
|
+
|
|
55
|
+
const handleLogMsg = (msg) => {
|
|
56
|
+
messages.push(msg);
|
|
57
|
+
const tbl = $("#_sc_logs_tbl_id_");
|
|
58
|
+
if (currentPage === 1) {
|
|
59
|
+
const tbody = tbl.find("tbody");
|
|
60
|
+
const allTblRows = tbody.find("tr");
|
|
61
|
+
if (allTblRows.length >= rowsPerPage)
|
|
62
|
+
allTblRows[allTblRows.length - 1].remove();
|
|
63
|
+
tbody.prepend(buildLogRow(msg.time, msg.level, msg.text));
|
|
64
|
+
}
|
|
65
|
+
if (messages.length % rowsPerPage === 1) {
|
|
66
|
+
const pagination = tbl.find(".pagination");
|
|
67
|
+
pagination.append(
|
|
68
|
+
buildPaginationItem(Math.trunc(messages.length / rowsPerPage + 1))
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
logViewerHelpers.goToLogsPage(currentPage);
|
|
72
|
+
};
|
|
73
|
+
const startTrackingMsg = () => {
|
|
74
|
+
const startedMsg = {
|
|
75
|
+
time: new Date().toLocaleDateString("de-DE", dateOptions),
|
|
76
|
+
level: 5,
|
|
77
|
+
text: "tracking started",
|
|
78
|
+
};
|
|
79
|
+
messages.push(startedMsg);
|
|
80
|
+
const tbl = $("#_sc_logs_tbl_id_");
|
|
81
|
+
tbl
|
|
82
|
+
.find("tbody")
|
|
83
|
+
.append(buildLogRow(startedMsg.time, startedMsg.level, startedMsg.text));
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const handleDisconnect = () => {
|
|
87
|
+
lostConnection = true;
|
|
88
|
+
$("#server-logs-card-id")
|
|
89
|
+
.find(".sc-error-indicator")
|
|
90
|
+
.css("display", "inline");
|
|
91
|
+
notifyAlert({
|
|
92
|
+
type: "danger",
|
|
93
|
+
text: "lost the server connection",
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const handleConnect = (socket) => {
|
|
98
|
+
socket.emit("join_log_room", (ack) => {
|
|
99
|
+
if (ack) {
|
|
100
|
+
if (ack.status === "ok") {
|
|
101
|
+
$("#server-logs-card-id")
|
|
102
|
+
.find(".sc-error-indicator")
|
|
103
|
+
.css("display", "none");
|
|
104
|
+
if (lostConnection) {
|
|
105
|
+
lostConnection = false;
|
|
106
|
+
emptyAlerts();
|
|
107
|
+
notifyAlert({
|
|
108
|
+
type: "success",
|
|
109
|
+
text: "You are connected again",
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
} else if (ack.status === "error" && ack.msg) {
|
|
113
|
+
notifyAlert({
|
|
114
|
+
type: "danger",
|
|
115
|
+
text: `Unable to join the log room: ${ack.msg}`,
|
|
116
|
+
});
|
|
117
|
+
} else {
|
|
118
|
+
notifyAlert({
|
|
119
|
+
type: "danger",
|
|
120
|
+
text: "Unable to join the log room: Unknow error",
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
notifyAlert({
|
|
125
|
+
type: "danger",
|
|
126
|
+
text: "Unable to join the log room",
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
init_log_socket: () => {
|
|
134
|
+
const socket = io({ transports: ["websocket"] });
|
|
135
|
+
startTrackingMsg();
|
|
136
|
+
socket.on("connect", () => handleConnect(socket));
|
|
137
|
+
socket.on("disconnect", handleDisconnect);
|
|
138
|
+
socket.on("log_msg", handleLogMsg);
|
|
139
|
+
},
|
|
140
|
+
goToLogsPage: (n) => {
|
|
141
|
+
currentPage = n;
|
|
142
|
+
$(".page-item").removeClass("active");
|
|
143
|
+
$(`.page-item:nth-child(${n})`).addClass("active");
|
|
144
|
+
let end = messages.length - rowsPerPage * n;
|
|
145
|
+
let start = end + rowsPerPage;
|
|
146
|
+
const tbl = $("#_sc_logs_tbl_id_");
|
|
147
|
+
const tbody = tbl.find("tbody");
|
|
148
|
+
tbody.empty();
|
|
149
|
+
for (let i = start; i > end; i--) {
|
|
150
|
+
if (i < 1) break;
|
|
151
|
+
const msg = messages[i - 1];
|
|
152
|
+
tbody.append(buildLogRow(msg.time, msg.level, msg.text));
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
})();
|
package/routes/actions.js
CHANGED
|
@@ -11,6 +11,7 @@ const {
|
|
|
11
11
|
addOnDoneRedirect,
|
|
12
12
|
is_relative_url,
|
|
13
13
|
} = require("./utils.js");
|
|
14
|
+
const { ppVal } = require("@saltcorn/data/utils");
|
|
14
15
|
const { getState } = require("@saltcorn/data/db/state");
|
|
15
16
|
const Trigger = require("@saltcorn/data/models/trigger");
|
|
16
17
|
const { getTriggerList } = require("./common_lists");
|
|
@@ -631,12 +632,6 @@ router.get(
|
|
|
631
632
|
const { id } = req.params;
|
|
632
633
|
const trigger = await Trigger.findOne({ id });
|
|
633
634
|
const output = [];
|
|
634
|
-
const ppVal = (x) =>
|
|
635
|
-
typeof x === "string"
|
|
636
|
-
? x
|
|
637
|
-
: typeof x === "function"
|
|
638
|
-
? x.toString()
|
|
639
|
-
: JSON.stringify(x, null, 2);
|
|
640
635
|
const fakeConsole = {
|
|
641
636
|
log(...s) {
|
|
642
637
|
console.log(...s);
|
package/routes/admin.js
CHANGED
|
@@ -2541,6 +2541,52 @@ router.post(
|
|
|
2541
2541
|
}
|
|
2542
2542
|
})
|
|
2543
2543
|
);
|
|
2544
|
+
|
|
2545
|
+
router.get(
|
|
2546
|
+
"/dev/logs_viewer",
|
|
2547
|
+
isAdmin,
|
|
2548
|
+
error_catcher(async (req, res) => {
|
|
2549
|
+
return send_admin_page({
|
|
2550
|
+
res,
|
|
2551
|
+
req,
|
|
2552
|
+
active_sub: "Development",
|
|
2553
|
+
contents: {
|
|
2554
|
+
above: [
|
|
2555
|
+
{
|
|
2556
|
+
type: "card",
|
|
2557
|
+
id: "server-logs-card-id",
|
|
2558
|
+
title: req.__("Server logs"),
|
|
2559
|
+
titleErrorInidicator: true,
|
|
2560
|
+
contents: mkTable(
|
|
2561
|
+
[
|
|
2562
|
+
{
|
|
2563
|
+
label: req.__("Timestamp"),
|
|
2564
|
+
width: "15%",
|
|
2565
|
+
},
|
|
2566
|
+
{ label: req.__("Message") },
|
|
2567
|
+
],
|
|
2568
|
+
[],
|
|
2569
|
+
{
|
|
2570
|
+
pagination: {
|
|
2571
|
+
current_page: 1,
|
|
2572
|
+
pages: 1,
|
|
2573
|
+
get_page_link: () => "logViewerHelpers.goToLogsPage(1)",
|
|
2574
|
+
},
|
|
2575
|
+
tableId: "_sc_logs_tbl_id_",
|
|
2576
|
+
}
|
|
2577
|
+
),
|
|
2578
|
+
},
|
|
2579
|
+
script({
|
|
2580
|
+
src: `/static_assets/${db.connectObj.version_tag}/socket.io.min.js`,
|
|
2581
|
+
}),
|
|
2582
|
+
script({ src: "/log_viewer_utils.js" }),
|
|
2583
|
+
script(domReady(`logViewerHelpers.init_log_socket();`)),
|
|
2584
|
+
],
|
|
2585
|
+
},
|
|
2586
|
+
});
|
|
2587
|
+
})
|
|
2588
|
+
);
|
|
2589
|
+
|
|
2544
2590
|
/**
|
|
2545
2591
|
* Dev / Admin
|
|
2546
2592
|
*/
|
|
@@ -2574,10 +2620,28 @@ admin_config_route({
|
|
|
2574
2620
|
req,
|
|
2575
2621
|
active_sub: "Development",
|
|
2576
2622
|
contents: {
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2623
|
+
above: [
|
|
2624
|
+
{
|
|
2625
|
+
type: "card",
|
|
2626
|
+
title: req.__("Development settings"),
|
|
2627
|
+
titleAjaxIndicator: true,
|
|
2628
|
+
contents: [renderForm(form, req.csrfToken())],
|
|
2629
|
+
},
|
|
2630
|
+
{
|
|
2631
|
+
type: "card",
|
|
2632
|
+
title: req.__("Runtime informations"),
|
|
2633
|
+
contents: [
|
|
2634
|
+
div(
|
|
2635
|
+
{ class: "row form-group" },
|
|
2636
|
+
a(
|
|
2637
|
+
{ class: "d-block", href: "dev/logs_viewer" },
|
|
2638
|
+
req.__("open logs viewer")
|
|
2639
|
+
),
|
|
2640
|
+
i("Open a log viewer for the current server messages")
|
|
2641
|
+
),
|
|
2642
|
+
],
|
|
2643
|
+
},
|
|
2644
|
+
],
|
|
2581
2645
|
},
|
|
2582
2646
|
});
|
|
2583
2647
|
},
|
package/routes/common_lists.js
CHANGED
package/routes/utils.js
CHANGED
|
@@ -147,9 +147,10 @@ const set_custom_http_headers = (res, req, state) => {
|
|
|
147
147
|
/**
|
|
148
148
|
* Tries to recognize tenant from HTTP Request
|
|
149
149
|
* @param {object} req
|
|
150
|
+
* @param {number|undefined} hostPartsOffset (optional) for socketIO, to get the tenant with localhost
|
|
150
151
|
* @returns {string}
|
|
151
152
|
*/
|
|
152
|
-
const get_tenant_from_req = (req) => {
|
|
153
|
+
const get_tenant_from_req = (req, hostPartsOffset) => {
|
|
153
154
|
if (req.subdomains && req.subdomains.length > 0)
|
|
154
155
|
return req.subdomains[req.subdomains.length - 1];
|
|
155
156
|
|
|
@@ -157,7 +158,8 @@ const get_tenant_from_req = (req) => {
|
|
|
157
158
|
return db.connectObj.default_schema;
|
|
158
159
|
if (!req.subdomains && req.headers.host) {
|
|
159
160
|
const parts = req.headers.host.split(".");
|
|
160
|
-
if (parts.length < 3
|
|
161
|
+
if (parts.length < (!hostPartsOffset ? 3 : 3 - hostPartsOffset))
|
|
162
|
+
return db.connectObj.default_schema;
|
|
161
163
|
else return parts[0];
|
|
162
164
|
}
|
|
163
165
|
};
|
package/serve.js
CHANGED
|
@@ -112,6 +112,10 @@ const initMaster = async ({ disableMigrate }, useClusterAdaptor = true) => {
|
|
|
112
112
|
const tenants = await getAllTenants();
|
|
113
113
|
await init_multi_tenant(loadAllPlugins, disableMigrate, tenants);
|
|
114
114
|
}
|
|
115
|
+
eachTenant(async () => {
|
|
116
|
+
const state = getState();
|
|
117
|
+
if (state) await state.setConfig("joined_log_socket_ids", []);
|
|
118
|
+
});
|
|
115
119
|
if (useClusterAdaptor) setupPrimary();
|
|
116
120
|
};
|
|
117
121
|
|
|
@@ -283,7 +287,7 @@ module.exports =
|
|
|
283
287
|
})
|
|
284
288
|
.ready((glx) => {
|
|
285
289
|
const httpsServer = glx.httpsServer();
|
|
286
|
-
setupSocket(httpsServer);
|
|
290
|
+
setupSocket(appargs?.subdomainOffset, httpsServer);
|
|
287
291
|
httpsServer.setTimeout(timeout * 1000);
|
|
288
292
|
process.on("message", workerDispatchMsg);
|
|
289
293
|
glx.serveApp(app);
|
|
@@ -350,7 +354,7 @@ const nonGreenlockWorkerSetup = async (appargs, port) => {
|
|
|
350
354
|
// todo timeout to config
|
|
351
355
|
httpServer.setTimeout(timeout * 1000);
|
|
352
356
|
httpsServer.setTimeout(timeout * 1000);
|
|
353
|
-
setupSocket(httpServer, httpsServer);
|
|
357
|
+
setupSocket(appargs?.subdomainOffset, httpServer, httpsServer);
|
|
354
358
|
httpServer.listen(port, () => {
|
|
355
359
|
console.log("HTTP Server running on port 80");
|
|
356
360
|
});
|
|
@@ -363,7 +367,7 @@ const nonGreenlockWorkerSetup = async (appargs, port) => {
|
|
|
363
367
|
// server with http only
|
|
364
368
|
const http = require("http");
|
|
365
369
|
const httpServer = http.createServer(app);
|
|
366
|
-
setupSocket(httpServer);
|
|
370
|
+
setupSocket(appargs?.subdomainOffset, httpServer);
|
|
367
371
|
|
|
368
372
|
// todo timeout to config
|
|
369
373
|
// todo refer in doc to httpserver doc
|
|
@@ -380,7 +384,7 @@ const nonGreenlockWorkerSetup = async (appargs, port) => {
|
|
|
380
384
|
*
|
|
381
385
|
* @param {...*} servers
|
|
382
386
|
*/
|
|
383
|
-
const setupSocket = (...servers) => {
|
|
387
|
+
const setupSocket = (subdomainOffset, ...servers) => {
|
|
384
388
|
// https://socket.io/docs/v4/middlewares/
|
|
385
389
|
const wrap = (middleware) => (socket, next) =>
|
|
386
390
|
middleware(socket.request, {}, next);
|
|
@@ -398,6 +402,12 @@ const setupSocket = (...servers) => {
|
|
|
398
402
|
getState().setRoomEmitter((tenant, viewname, room_id, msg) => {
|
|
399
403
|
io.to(`${tenant}_${viewname}_${room_id}`).emit("message", msg);
|
|
400
404
|
});
|
|
405
|
+
|
|
406
|
+
getState().setLogEmitter((tenant, level, msg) => {
|
|
407
|
+
const time = new Date().valueOf();
|
|
408
|
+
io.to(`_logs_${tenant}_`).emit("log_msg", { text: msg, time, level });
|
|
409
|
+
});
|
|
410
|
+
|
|
401
411
|
io.on("connection", (socket) => {
|
|
402
412
|
socket.on("join_room", ([viewname, room_id]) => {
|
|
403
413
|
const ten = get_tenant_from_req(socket.request) || "public";
|
|
@@ -418,5 +428,42 @@ const setupSocket = (...servers) => {
|
|
|
418
428
|
if (ten && ten !== "public") db.runWithTenant(ten, f);
|
|
419
429
|
else f();
|
|
420
430
|
});
|
|
431
|
+
|
|
432
|
+
socket.on("join_log_room", async (callback) => {
|
|
433
|
+
const tenant =
|
|
434
|
+
get_tenant_from_req(socket.request, subdomainOffset) || "public";
|
|
435
|
+
const f = async () => {
|
|
436
|
+
try {
|
|
437
|
+
const user = socket.request.user;
|
|
438
|
+
if (!user || user.role_id !== 1) throw new Error("Not authorized");
|
|
439
|
+
else {
|
|
440
|
+
socket.join(`_logs_${tenant}_`);
|
|
441
|
+
const socketIds = await getState().getConfig(
|
|
442
|
+
"joined_log_socket_ids"
|
|
443
|
+
);
|
|
444
|
+
socketIds.push(socket.id);
|
|
445
|
+
await getState().setConfig("joined_log_socket_ids", [...socketIds]);
|
|
446
|
+
callback({ status: "ok" });
|
|
447
|
+
}
|
|
448
|
+
} catch (err) {
|
|
449
|
+
getState().log(1, `Socket join_logs stream: ${err.stack}`);
|
|
450
|
+
callback({ status: "error", msg: err.message || "unknown error" });
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
if (tenant && tenant !== "public") db.runWithTenant(tenant, f);
|
|
454
|
+
else await f();
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
socket.on("disconnect", async () => {
|
|
458
|
+
const tenant =
|
|
459
|
+
get_tenant_from_req(socket.request, subdomainOffset) || "public";
|
|
460
|
+
const f = async () => {
|
|
461
|
+
const socketIds = await getState().getConfig("joined_log_socket_ids");
|
|
462
|
+
const newSocketIds = socketIds.filter((id) => id !== socket.id);
|
|
463
|
+
await getState().setConfig("joined_log_socket_ids", newSocketIds);
|
|
464
|
+
};
|
|
465
|
+
if (tenant && tenant !== "public") db.runWithTenant(tenant, f);
|
|
466
|
+
else f();
|
|
467
|
+
});
|
|
421
468
|
});
|
|
422
469
|
};
|
package/tests/admin.test.js
CHANGED
|
@@ -555,7 +555,7 @@ describe("diagram", () => {
|
|
|
555
555
|
});
|
|
556
556
|
|
|
557
557
|
/**
|
|
558
|
-
*
|
|
558
|
+
* Tags tests
|
|
559
559
|
*/
|
|
560
560
|
describe("tags", () => {
|
|
561
561
|
itShouldRedirectUnauthToLogin("/tag");
|
|
@@ -604,6 +604,12 @@ describe("tags", () => {
|
|
|
604
604
|
.expect(toRedirect("/tag"));
|
|
605
605
|
});
|
|
606
606
|
});
|
|
607
|
+
|
|
608
|
+
describe("server logs viewer", () => {
|
|
609
|
+
itShouldRedirectUnauthToLogin("/admin/dev/logs_viewer");
|
|
610
|
+
itShouldIncludeTextForAdmin("/admin/dev/logs_viewer", "Server logs");
|
|
611
|
+
});
|
|
612
|
+
|
|
607
613
|
/**
|
|
608
614
|
* Clear all tests
|
|
609
615
|
*/
|