@saltcorn/server 0.9.6-beta.17 → 0.9.6-beta.19
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 +6 -1
- package/auth/admin.js +1 -0
- package/help/Field label.tmd +11 -0
- package/help/Field types.tmd +39 -0
- package/help/Ownership field.tmd +76 -0
- package/help/Ownership formula.tmd +75 -0
- package/help/Table roles.tmd +20 -0
- package/help/User groups.tmd +35 -0
- package/locales/en.json +7 -2
- package/package.json +9 -9
- package/public/log_viewer_utils.js +32 -0
- package/public/mermaid.min.js +705 -306
- package/public/saltcorn-builder.css +23 -0
- package/public/saltcorn.css +50 -0
- package/public/saltcorn.js +78 -0
- package/routes/fields.js +8 -0
- package/routes/homepage.js +2 -0
- package/routes/notifications.js +8 -2
- package/routes/tables.js +39 -19
- package/routes/tenant.js +1 -13
- package/routes/utils.js +17 -1
- package/serve.js +27 -5
- package/wrapper.js +4 -1
|
@@ -509,3 +509,26 @@ div.builder-config-field {
|
|
|
509
509
|
border: 1px solid black;
|
|
510
510
|
margin-top: 2px;
|
|
511
511
|
}
|
|
512
|
+
|
|
513
|
+
div.componets-and-library-accordion {
|
|
514
|
+
padding-right: 0 !important;
|
|
515
|
+
}
|
|
516
|
+
.builder-left-shrunk .componets-and-library-accordion {
|
|
517
|
+
min-height: 0 !important;
|
|
518
|
+
padding-right: 0;
|
|
519
|
+
}
|
|
520
|
+
.toolbox-card .builder-layers {
|
|
521
|
+
min-height: 200px;
|
|
522
|
+
overflow-y: auto;
|
|
523
|
+
max-height: max-content !important;
|
|
524
|
+
}
|
|
525
|
+
.toolbox-card:nth-child(2) {
|
|
526
|
+
margin-bottom: 0px;
|
|
527
|
+
}
|
|
528
|
+
#builder-main-canvas {
|
|
529
|
+
height: calc(100dvh - 50px) !important;
|
|
530
|
+
min-height: 600px;
|
|
531
|
+
}
|
|
532
|
+
#builder-main-canvas.h-100 {
|
|
533
|
+
height: 100% !important;
|
|
534
|
+
}
|
package/public/saltcorn.css
CHANGED
|
@@ -536,3 +536,53 @@ button.monospace-copy-btn:hover {
|
|
|
536
536
|
button.monospace-copy-btn {
|
|
537
537
|
position: absolute;
|
|
538
538
|
}
|
|
539
|
+
|
|
540
|
+
#page-inner-content.sbadmin2-theme .table.table-card-rows {
|
|
541
|
+
border-spacing: 0px 8px;
|
|
542
|
+
border-collapse: separate;
|
|
543
|
+
}
|
|
544
|
+
#page-inner-content.sbadmin2-theme .table.table-card-rows tbody {
|
|
545
|
+
transform: translateY(8px);
|
|
546
|
+
}
|
|
547
|
+
#page-inner-content.sbadmin2-theme .table.table-card-rows tr {
|
|
548
|
+
border-radius: 6px;
|
|
549
|
+
}
|
|
550
|
+
#page-inner-content.sbadmin2-theme
|
|
551
|
+
.table-hover.table-card-rows
|
|
552
|
+
> tbody
|
|
553
|
+
> tr:hover
|
|
554
|
+
> * {
|
|
555
|
+
--tblr-table-bg-state: transparent !important;
|
|
556
|
+
--bs-table-accent-bg: transparent !important;
|
|
557
|
+
color: var(--bs-table-hover-color);
|
|
558
|
+
background-color: var(--bs-table-hover-bg);
|
|
559
|
+
}
|
|
560
|
+
#page-inner-content.sbadmin2-theme .table.table-card-rows thead th {
|
|
561
|
+
background-color: #eaecf4;
|
|
562
|
+
border-block: 1px solid #d3d3d3 !important;
|
|
563
|
+
padding: 18px 15px;
|
|
564
|
+
font-weight: 600;
|
|
565
|
+
}
|
|
566
|
+
#page-inner-content.sbadmin2-theme .table.table-card-rows thead th:first-child {
|
|
567
|
+
border-left: 1px solid #d3d3d3 !important;
|
|
568
|
+
border-radius: 6px 0 0 6px;
|
|
569
|
+
}
|
|
570
|
+
#page-inner-content.sbadmin2-theme .table.table-card-rows thead th:last-child {
|
|
571
|
+
border-right: 1px #d3d3d3 !important;
|
|
572
|
+
border-radius: 0 6px 6px 0;
|
|
573
|
+
}
|
|
574
|
+
#page-inner-content.sbadmin2-theme .table.table-card-rows tbody td {
|
|
575
|
+
border-block: 1px solid #e3e6f0 !important;
|
|
576
|
+
position: relative;
|
|
577
|
+
z-index: 11;
|
|
578
|
+
padding: 15px 15px;
|
|
579
|
+
background-color: #fff;
|
|
580
|
+
}
|
|
581
|
+
#page-inner-content.sbadmin2-theme .table.table-card-rows tbody td:first-child {
|
|
582
|
+
border-left: 1px solid #e3e6f0 !important;
|
|
583
|
+
border-radius: 6px 0 0 6px;
|
|
584
|
+
}
|
|
585
|
+
#page-inner-content.sbadmin2-theme .table.table-card-rows tbody td:last-child {
|
|
586
|
+
border-right: 1px #e3e6f0 !important;
|
|
587
|
+
border-radius: 0 6px 6px 0;
|
|
588
|
+
}
|
package/public/saltcorn.js
CHANGED
|
@@ -1034,6 +1034,84 @@ function execLink(path) {
|
|
|
1034
1034
|
window.location.href = `${location.origin}${path}`;
|
|
1035
1035
|
}
|
|
1036
1036
|
|
|
1037
|
+
let defferedPrompt;
|
|
1038
|
+
window.addEventListener("beforeinstallprompt", (e) => {
|
|
1039
|
+
e.preventDefault();
|
|
1040
|
+
defferedPrompt = e;
|
|
1041
|
+
});
|
|
1042
|
+
|
|
1043
|
+
function isAndroidMobile() {
|
|
1044
|
+
const ua = navigator.userAgent || navigator.vendor || window.opera;
|
|
1045
|
+
return /android/i.test(ua) && /mobile/i.test(ua);
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
function validatePWAManifest(manifest) {
|
|
1049
|
+
const errors = [];
|
|
1050
|
+
if (!manifest) errors.push("The manifest.json file is missing. ");
|
|
1051
|
+
else {
|
|
1052
|
+
if (!manifest.icons || manifest.icons.length === 0)
|
|
1053
|
+
errors.push("At least one icon is required");
|
|
1054
|
+
else if (
|
|
1055
|
+
manifest.icons.length > 0 &&
|
|
1056
|
+
!manifest.icons.some((icon) => {
|
|
1057
|
+
const sizes = icon.sizes.split("x");
|
|
1058
|
+
const x = parseInt(sizes[0]);
|
|
1059
|
+
const y = parseInt(sizes[1]);
|
|
1060
|
+
return x === y && x >= 144;
|
|
1061
|
+
})
|
|
1062
|
+
) {
|
|
1063
|
+
errors.push(
|
|
1064
|
+
"At least one square icon of size 144x144 or larger is required"
|
|
1065
|
+
);
|
|
1066
|
+
}
|
|
1067
|
+
if (isAndroidMobile() && manifest.display === "browser") {
|
|
1068
|
+
errors.push(
|
|
1069
|
+
"The display property 'browser' may not work on mobile devices"
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
return errors;
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
function supportsBeforeInstallPrompt() {
|
|
1077
|
+
return "onbeforeinstallprompt" in window;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
function installPWA() {
|
|
1081
|
+
if (defferedPrompt) defferedPrompt.prompt();
|
|
1082
|
+
else if (!supportsBeforeInstallPrompt()) {
|
|
1083
|
+
notifyAlert({
|
|
1084
|
+
type: "danger",
|
|
1085
|
+
text:
|
|
1086
|
+
"It looks like your browser doesn’t support this feature. " +
|
|
1087
|
+
"Please try the standard installation method provided by your browser, or switch to a different browser.",
|
|
1088
|
+
});
|
|
1089
|
+
} else {
|
|
1090
|
+
const manifestUrl = `${window.location.origin}/notifications/manifest.json`;
|
|
1091
|
+
notifyAlert({
|
|
1092
|
+
type: "danger",
|
|
1093
|
+
text:
|
|
1094
|
+
"Unable to install the app. " +
|
|
1095
|
+
"Please check if the app is already installed or " +
|
|
1096
|
+
`inspect your manifest.json <a href="${manifestUrl}?pretty=true" target="_blank">here</a>`,
|
|
1097
|
+
});
|
|
1098
|
+
$.ajax(manifestUrl, {
|
|
1099
|
+
success: (res) => {
|
|
1100
|
+
const errors = validatePWAManifest(res);
|
|
1101
|
+
if (errors.length > 0)
|
|
1102
|
+
notifyAlert({
|
|
1103
|
+
type: "warning",
|
|
1104
|
+
text: `${errors.join(", ")}`,
|
|
1105
|
+
});
|
|
1106
|
+
},
|
|
1107
|
+
error: (res) => {
|
|
1108
|
+
console.log("Error fetching manifest.json");
|
|
1109
|
+
console.log(res);
|
|
1110
|
+
},
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1037
1115
|
(() => {
|
|
1038
1116
|
const e = document.querySelector("[data-sidebar-toggler]");
|
|
1039
1117
|
let closed = localStorage.getItem("sidebarClosed") === "true";
|
package/routes/fields.js
CHANGED
|
@@ -84,6 +84,10 @@ const fieldForm = async (req, fkey_opts, existing_names, id, hasData) => {
|
|
|
84
84
|
sublabel: req.__("Name of the field"),
|
|
85
85
|
type: "String",
|
|
86
86
|
attributes: { autofocus: true },
|
|
87
|
+
help: {
|
|
88
|
+
topic: "Field label",
|
|
89
|
+
context: {},
|
|
90
|
+
},
|
|
87
91
|
validator(s) {
|
|
88
92
|
if (!s || s === "") return req.__("Missing label");
|
|
89
93
|
if (!id && existing_names.includes(Field.labelToName(s)))
|
|
@@ -104,6 +108,10 @@ const fieldForm = async (req, fkey_opts, existing_names, id, hasData) => {
|
|
|
104
108
|
"The type determines the kind of data that can be stored in the field"
|
|
105
109
|
),
|
|
106
110
|
input_type: "select",
|
|
111
|
+
help: {
|
|
112
|
+
topic: "Field types",
|
|
113
|
+
context: {},
|
|
114
|
+
},
|
|
107
115
|
options: isPrimary
|
|
108
116
|
? primaryTypes
|
|
109
117
|
: getState().type_names.concat(fkey_opts || []),
|
package/routes/homepage.js
CHANGED
package/routes/notifications.js
CHANGED
|
@@ -184,8 +184,9 @@ router.post(
|
|
|
184
184
|
);
|
|
185
185
|
|
|
186
186
|
router.get(
|
|
187
|
-
"/manifest.json",
|
|
187
|
+
"/manifest.json:opt_cache_bust?",
|
|
188
188
|
error_catcher(async (req, res) => {
|
|
189
|
+
const { pretty } = req.query;
|
|
189
190
|
const state = getState();
|
|
190
191
|
const manifest = {
|
|
191
192
|
name: state.getConfig("site_name"),
|
|
@@ -216,6 +217,11 @@ router.get(
|
|
|
216
217
|
"red"
|
|
217
218
|
);
|
|
218
219
|
}
|
|
219
|
-
res.json(manifest);
|
|
220
|
+
if (!pretty) res.json(manifest);
|
|
221
|
+
else {
|
|
222
|
+
const prettyJson = JSON.stringify(manifest, null, 2);
|
|
223
|
+
res.setHeader("Content-Type", "application/json");
|
|
224
|
+
res.send(prettyJson);
|
|
225
|
+
}
|
|
220
226
|
})
|
|
221
227
|
);
|
package/routes/tables.js
CHANGED
|
@@ -93,14 +93,45 @@ const tableForm = async (table, req) => {
|
|
|
93
93
|
noSubmitButton: true,
|
|
94
94
|
onChange: "saveAndContinue(this)",
|
|
95
95
|
fields: [
|
|
96
|
+
{
|
|
97
|
+
label: req.__("Minimum role to read"),
|
|
98
|
+
sublabel: req.__(
|
|
99
|
+
"User must have this role or higher to read rows from the table, unless they are the owner"
|
|
100
|
+
),
|
|
101
|
+
help: {
|
|
102
|
+
topic: "Table roles",
|
|
103
|
+
context: {},
|
|
104
|
+
},
|
|
105
|
+
name: "min_role_read",
|
|
106
|
+
input_type: "select",
|
|
107
|
+
options: roleOptions,
|
|
108
|
+
attributes: { asideNext: !table.external && !table.provider_name },
|
|
109
|
+
},
|
|
96
110
|
...(!table.external && !table.provider_name
|
|
97
111
|
? [
|
|
112
|
+
{
|
|
113
|
+
label: req.__("Minimum role to write"),
|
|
114
|
+
name: "min_role_write",
|
|
115
|
+
input_type: "select",
|
|
116
|
+
help: {
|
|
117
|
+
topic: "Table roles",
|
|
118
|
+
context: {},
|
|
119
|
+
},
|
|
120
|
+
sublabel: req.__(
|
|
121
|
+
"User must have this role or higher to edit or create new rows in the table, unless they are the owner"
|
|
122
|
+
),
|
|
123
|
+
options: roleOptions,
|
|
124
|
+
},
|
|
98
125
|
{
|
|
99
126
|
label: req.__("Ownership field"),
|
|
100
127
|
name: "ownership_field_id",
|
|
101
128
|
sublabel: req.__(
|
|
102
129
|
"The user referred to in this field will be the owner of the row"
|
|
103
130
|
),
|
|
131
|
+
help: {
|
|
132
|
+
topic: "Ownership field",
|
|
133
|
+
context: {},
|
|
134
|
+
},
|
|
104
135
|
input_type: "select",
|
|
105
136
|
options: [
|
|
106
137
|
{ value: "", label: req.__("None") },
|
|
@@ -114,6 +145,10 @@ const tableForm = async (table, req) => {
|
|
|
114
145
|
validator: expressionValidator,
|
|
115
146
|
type: "String",
|
|
116
147
|
class: "validate-expression",
|
|
148
|
+
help: {
|
|
149
|
+
topic: "Ownership formula",
|
|
150
|
+
context: {},
|
|
151
|
+
},
|
|
117
152
|
sublabel:
|
|
118
153
|
req.__("User is treated as owner if true. In scope: ") +
|
|
119
154
|
["user", ...fields.map((f) => f.name)]
|
|
@@ -126,6 +161,10 @@ const tableForm = async (table, req) => {
|
|
|
126
161
|
sublabel: req.__(
|
|
127
162
|
"Add relations to this table in dropdown options for ownership field"
|
|
128
163
|
),
|
|
164
|
+
help: {
|
|
165
|
+
topic: "User groups",
|
|
166
|
+
context: {},
|
|
167
|
+
},
|
|
129
168
|
name: "is_user_group",
|
|
130
169
|
type: "Bool",
|
|
131
170
|
},
|
|
@@ -141,28 +180,9 @@ const tableForm = async (table, req) => {
|
|
|
141
180
|
),
|
|
142
181
|
//options: roleOptions,
|
|
143
182
|
},
|
|
144
|
-
{
|
|
145
|
-
label: req.__("Minimum role to read"),
|
|
146
|
-
sublabel: req.__(
|
|
147
|
-
"User must have this role or higher to read rows from the table, unless they are the owner"
|
|
148
|
-
),
|
|
149
|
-
name: "min_role_read",
|
|
150
|
-
input_type: "select",
|
|
151
|
-
options: roleOptions,
|
|
152
|
-
attributes: { asideNext: !table.external && !table.provider_name },
|
|
153
|
-
},
|
|
154
183
|
...(table.external || table.provider_name
|
|
155
184
|
? []
|
|
156
185
|
: [
|
|
157
|
-
{
|
|
158
|
-
label: req.__("Minimum role to write"),
|
|
159
|
-
name: "min_role_write",
|
|
160
|
-
input_type: "select",
|
|
161
|
-
sublabel: req.__(
|
|
162
|
-
"User must have this role or higher to edit or create new rows in the table, unless they are the owner"
|
|
163
|
-
),
|
|
164
|
-
options: roleOptions,
|
|
165
|
-
},
|
|
166
186
|
{
|
|
167
187
|
label: req.__("Version history"),
|
|
168
188
|
sublabel: req.__("Track table data changes over time"),
|
package/routes/tenant.js
CHANGED
|
@@ -44,7 +44,7 @@ const {
|
|
|
44
44
|
const db = require("@saltcorn/data/db");
|
|
45
45
|
|
|
46
46
|
const { loadAllPlugins, loadAndSaveNewPlugin } = require("../load_plugins");
|
|
47
|
-
const { isAdmin, error_catcher } = require("./utils.js");
|
|
47
|
+
const { isAdmin, error_catcher, is_ip_address } = require("./utils.js");
|
|
48
48
|
const User = require("@saltcorn/data/models/user");
|
|
49
49
|
const File = require("@saltcorn/data/models/file");
|
|
50
50
|
const {
|
|
@@ -117,18 +117,6 @@ const create_tenant_allowed = (req) => {
|
|
|
117
117
|
return user_role <= required_role;
|
|
118
118
|
};
|
|
119
119
|
|
|
120
|
-
/**
|
|
121
|
-
* Check that String is IPv4 address
|
|
122
|
-
* @param {string} hostname
|
|
123
|
-
* @returns {boolean|string[]}
|
|
124
|
-
*/
|
|
125
|
-
// TBD not sure that false is correct return if type of is not string
|
|
126
|
-
// TBD Add IPv6 support
|
|
127
|
-
const is_ip_address = (hostname) => {
|
|
128
|
-
if (typeof hostname !== "string") return false;
|
|
129
|
-
return hostname.split(".").every((s) => +s >= 0 && +s <= 255);
|
|
130
|
-
};
|
|
131
|
-
|
|
132
120
|
const get_cfg_tenant_base_url = (req) =>
|
|
133
121
|
remove_leading_chars(
|
|
134
122
|
".",
|
package/routes/utils.js
CHANGED
|
@@ -155,6 +155,8 @@ const get_tenant_from_req = (req, hostPartsOffset) => {
|
|
|
155
155
|
if (req.subdomains && req.subdomains.length == 0)
|
|
156
156
|
return db.connectObj.default_schema;
|
|
157
157
|
if (!req.subdomains && req.headers.host) {
|
|
158
|
+
if (is_ip_address(req.headers.host.split(":")[0]))
|
|
159
|
+
return db.connectObj.default_schema;
|
|
158
160
|
const parts = req.headers.host.split(".");
|
|
159
161
|
if (parts.length < (!hostPartsOffset ? 3 : 3 - hostPartsOffset))
|
|
160
162
|
return db.connectObj.default_schema;
|
|
@@ -297,7 +299,7 @@ const getGitRevision = () => db.connectObj.git_commit;
|
|
|
297
299
|
* Gets session store
|
|
298
300
|
* @returns {session|cookieSession}
|
|
299
301
|
*/
|
|
300
|
-
const getSessionStore = () => {
|
|
302
|
+
const getSessionStore = (pruneInterval) => {
|
|
301
303
|
/*if (getState().getConfig("cookie_sessions", false)) {
|
|
302
304
|
return cookieSession({
|
|
303
305
|
keys: [db.connectObj.session_secret || is.str.generate()],
|
|
@@ -320,6 +322,7 @@ const getSessionStore = () => {
|
|
|
320
322
|
schemaName: db.connectObj.default_schema,
|
|
321
323
|
pool: db.pool,
|
|
322
324
|
tableName: "_sc_session",
|
|
325
|
+
pruneSessionInterval: pruneInterval > 0 ? pruneInterval : false,
|
|
323
326
|
}),
|
|
324
327
|
secret: db.connectObj.session_secret || is.str.generate(),
|
|
325
328
|
resave: false,
|
|
@@ -348,6 +351,18 @@ const is_relative_url = (url) => {
|
|
|
348
351
|
return typeof url === "string" && !url.includes(":/") && !url.includes("//");
|
|
349
352
|
};
|
|
350
353
|
|
|
354
|
+
/**
|
|
355
|
+
* Check that String is IPv4 address
|
|
356
|
+
* @param {string} hostname
|
|
357
|
+
* @returns {boolean|string[]}
|
|
358
|
+
*/
|
|
359
|
+
// TBD not sure that false is correct return if type of is not string
|
|
360
|
+
// TBD Add IPv6 support
|
|
361
|
+
const is_ip_address = (hostname) => {
|
|
362
|
+
if (typeof hostname !== "string") return false;
|
|
363
|
+
return hostname.split(".").every((s) => +s >= 0 && +s <= 255);
|
|
364
|
+
};
|
|
365
|
+
|
|
351
366
|
const admin_config_route = ({
|
|
352
367
|
router,
|
|
353
368
|
path,
|
|
@@ -556,6 +571,7 @@ module.exports = {
|
|
|
556
571
|
get_tenant_from_req,
|
|
557
572
|
addOnDoneRedirect,
|
|
558
573
|
is_relative_url,
|
|
574
|
+
is_ip_address,
|
|
559
575
|
get_sys_info,
|
|
560
576
|
admin_config_route,
|
|
561
577
|
sendHtmlFile,
|
package/serve.js
CHANGED
|
@@ -235,6 +235,10 @@ module.exports =
|
|
|
235
235
|
: defaultNCPUs;
|
|
236
236
|
|
|
237
237
|
const letsEncrypt = await getConfig("letsencrypt", false);
|
|
238
|
+
const pruneSessionInterval = +(await getConfig(
|
|
239
|
+
"prune_session_interval",
|
|
240
|
+
900
|
|
241
|
+
));
|
|
238
242
|
const masterState = {
|
|
239
243
|
started: false,
|
|
240
244
|
listeningTo: new Set([]),
|
|
@@ -287,7 +291,11 @@ module.exports =
|
|
|
287
291
|
})
|
|
288
292
|
.ready((glx) => {
|
|
289
293
|
const httpsServer = glx.httpsServer();
|
|
290
|
-
setupSocket(
|
|
294
|
+
setupSocket(
|
|
295
|
+
appargs?.subdomainOffset,
|
|
296
|
+
pruneSessionInterval,
|
|
297
|
+
httpsServer
|
|
298
|
+
);
|
|
291
299
|
httpsServer.setTimeout(timeout * 1000);
|
|
292
300
|
process.on("message", workerDispatchMsg);
|
|
293
301
|
glx.serveApp(app);
|
|
@@ -344,6 +352,10 @@ const nonGreenlockWorkerSetup = async (appargs, port) => {
|
|
|
344
352
|
const cert = getState().getConfig("custom_ssl_certificate", "");
|
|
345
353
|
const key = getState().getConfig("custom_ssl_private_key", "");
|
|
346
354
|
const timeout = +getState().getConfig("timeout", 120);
|
|
355
|
+
const pruneSessionInterval = +(await getState().getConfig(
|
|
356
|
+
"prune_session_interval",
|
|
357
|
+
900
|
|
358
|
+
));
|
|
347
359
|
// Server with http on port 80 / https on 443
|
|
348
360
|
// todo resolve hardcode
|
|
349
361
|
if (port === 80 && cert && key) {
|
|
@@ -354,7 +366,12 @@ const nonGreenlockWorkerSetup = async (appargs, port) => {
|
|
|
354
366
|
// todo timeout to config
|
|
355
367
|
httpServer.setTimeout(timeout * 1000);
|
|
356
368
|
httpsServer.setTimeout(timeout * 1000);
|
|
357
|
-
setupSocket(
|
|
369
|
+
setupSocket(
|
|
370
|
+
appargs?.subdomainOffset,
|
|
371
|
+
pruneSessionInterval,
|
|
372
|
+
httpServer,
|
|
373
|
+
httpsServer
|
|
374
|
+
);
|
|
358
375
|
httpServer.listen(port, () => {
|
|
359
376
|
console.log("HTTP Server running on port 80");
|
|
360
377
|
});
|
|
@@ -367,7 +384,7 @@ const nonGreenlockWorkerSetup = async (appargs, port) => {
|
|
|
367
384
|
// server with http only
|
|
368
385
|
const http = require("http");
|
|
369
386
|
const httpServer = http.createServer(app);
|
|
370
|
-
setupSocket(appargs?.subdomainOffset, httpServer);
|
|
387
|
+
setupSocket(appargs?.subdomainOffset, pruneSessionInterval, httpServer);
|
|
371
388
|
|
|
372
389
|
// todo timeout to config
|
|
373
390
|
// todo refer in doc to httpserver doc
|
|
@@ -384,7 +401,7 @@ const nonGreenlockWorkerSetup = async (appargs, port) => {
|
|
|
384
401
|
*
|
|
385
402
|
* @param {...*} servers
|
|
386
403
|
*/
|
|
387
|
-
const setupSocket = (subdomainOffset, ...servers) => {
|
|
404
|
+
const setupSocket = (subdomainOffset, pruneSessionInterval, ...servers) => {
|
|
388
405
|
// https://socket.io/docs/v4/middlewares/
|
|
389
406
|
const wrap = (middleware) => (socket, next) =>
|
|
390
407
|
middleware(socket.request, {}, next);
|
|
@@ -395,7 +412,7 @@ const setupSocket = (subdomainOffset, ...servers) => {
|
|
|
395
412
|
}
|
|
396
413
|
|
|
397
414
|
const passportInit = passport.initialize();
|
|
398
|
-
const sessionStore = getSessionStore();
|
|
415
|
+
const sessionStore = getSessionStore(pruneSessionInterval);
|
|
399
416
|
const setupNamespace = (namespace) => {
|
|
400
417
|
//io.of(namespace).use(wrap(setTenant));
|
|
401
418
|
io.of(namespace).use(wrap(sessionStore));
|
|
@@ -452,6 +469,11 @@ const setupSocket = (subdomainOffset, ...servers) => {
|
|
|
452
469
|
socketIds.push(socket.id);
|
|
453
470
|
await getState().setConfig("joined_log_socket_ids", [...socketIds]);
|
|
454
471
|
callback({ status: "ok" });
|
|
472
|
+
setTimeout(() => {
|
|
473
|
+
io.of("/")
|
|
474
|
+
.to(`_logs_${tenant}_`)
|
|
475
|
+
.emit("test_conn_msg", { text: "test message" });
|
|
476
|
+
}, 1000);
|
|
455
477
|
}
|
|
456
478
|
} catch (err) {
|
|
457
479
|
getState().log(1, `Socket join_logs stream: ${err.stack}`);
|
package/wrapper.js
CHANGED
|
@@ -170,6 +170,7 @@ const get_headers = (req, version_tag, description, extras = []) => {
|
|
|
170
170
|
const favicon = state.getConfig("favicon_id", null);
|
|
171
171
|
const notification_in_menu = state.getConfig("notification_in_menu");
|
|
172
172
|
const pwa_enabled = state.getConfig("pwa_enabled");
|
|
173
|
+
const is_root = req.user?.role_id === 1;
|
|
173
174
|
|
|
174
175
|
const iconHeader = favicon
|
|
175
176
|
? [
|
|
@@ -219,7 +220,9 @@ const get_headers = (req, version_tag, description, extras = []) => {
|
|
|
219
220
|
from_cfg.push({ scriptBody: domReady(`check_saltcorn_notifications()`) });
|
|
220
221
|
if (pwa_enabled) {
|
|
221
222
|
from_cfg.push({
|
|
222
|
-
headerTag: `<link rel="manifest" href="/notifications/manifest.json
|
|
223
|
+
headerTag: `<link rel="manifest" href="/notifications/manifest.json${
|
|
224
|
+
is_root ? new Date().valueOf() : ""
|
|
225
|
+
}">`,
|
|
223
226
|
});
|
|
224
227
|
from_cfg.push({
|
|
225
228
|
scriptBody: `if('serviceWorker' in navigator) {
|