@saltcorn/server 0.8.5-beta.8 → 0.8.5-rc.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 +8 -0
- package/locales/en.json +7 -1
- package/package.json +8 -8
- package/public/saltcorn-builder.css +7 -0
- package/public/saltcorn-common.js +24 -4
- package/routes/admin.js +30 -5
- package/routes/pageedit.js +6 -4
- package/routes/tenant.js +38 -18
- package/serve.js +4 -0
- package/wrapper.js +9 -5
package/auth/admin.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// todo refactor to few modules + rename to be in sync with router url
|
|
8
8
|
const Router = require("express-promise-router");
|
|
9
9
|
const { contract, is } = require("contractis");
|
|
10
|
+
const { X509Certificate } = require("crypto");
|
|
10
11
|
const db = require("@saltcorn/data/db");
|
|
11
12
|
const User = require("@saltcorn/data/models/user");
|
|
12
13
|
const View = require("@saltcorn/data/models/view");
|
|
@@ -593,6 +594,12 @@ router.get(
|
|
|
593
594
|
const show_warning =
|
|
594
595
|
!hostname_matches_baseurl(req, getBaseDomain()) &&
|
|
595
596
|
is_hsts_tld(getBaseDomain());
|
|
597
|
+
let expiry = "";
|
|
598
|
+
if (has_custom && X509Certificate) {
|
|
599
|
+
const cert = getState().getConfig("custom_ssl_certificate", "");
|
|
600
|
+
const { validTo } = new X509Certificate(cert);
|
|
601
|
+
expiry = div({ class: "me-2" }, "Expires: ", validTo);
|
|
602
|
+
}
|
|
596
603
|
send_users_page({
|
|
597
604
|
res,
|
|
598
605
|
req,
|
|
@@ -674,6 +681,7 @@ router.get(
|
|
|
674
681
|
? span({ class: "badge bg-primary" }, req.__("Enabled"))
|
|
675
682
|
: span({ class: "badge bg-secondary" }, req.__("Disabled"))
|
|
676
683
|
),
|
|
684
|
+
has_custom && expiry,
|
|
677
685
|
// TBD change to button
|
|
678
686
|
link(
|
|
679
687
|
"/useradmin/ssl/custom",
|
package/locales/en.json
CHANGED
|
@@ -1144,5 +1144,11 @@
|
|
|
1144
1144
|
"Table provider": "Table provider",
|
|
1145
1145
|
"Database table": "Database table",
|
|
1146
1146
|
"Configure provider": "Configure provider",
|
|
1147
|
-
"In scope:": "In scope:"
|
|
1147
|
+
"In scope:": "In scope:",
|
|
1148
|
+
"SSL expiry": "SSL expiry",
|
|
1149
|
+
"A page with this name already exists": "A page with this name already exists",
|
|
1150
|
+
"Tenant Base URL": "Tenant Base URL",
|
|
1151
|
+
"Base hostname for newly created tenants. If unset, defaults to hostname": "Base hostname for newly created tenants. If unset, defaults to hostname",
|
|
1152
|
+
"Redirect unathorized": "Redirect unathorized",
|
|
1153
|
+
"If tenant creation is not authorized, redirect to this URL": "If tenant creation is not authorized, redirect to this URL"
|
|
1148
1154
|
}
|
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/server",
|
|
3
|
-
"version": "0.8.5-
|
|
3
|
+
"version": "0.8.5-rc.2",
|
|
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
|
-
"@saltcorn/base-plugin": "0.8.5-
|
|
10
|
-
"@saltcorn/builder": "0.8.5-
|
|
11
|
-
"@saltcorn/data": "0.8.5-
|
|
12
|
-
"@saltcorn/admin-models": "0.8.5-
|
|
13
|
-
"@saltcorn/filemanager": "0.8.5-
|
|
14
|
-
"@saltcorn/markup": "0.8.5-
|
|
15
|
-
"@saltcorn/sbadmin2": "0.8.5-
|
|
9
|
+
"@saltcorn/base-plugin": "0.8.5-rc.2",
|
|
10
|
+
"@saltcorn/builder": "0.8.5-rc.2",
|
|
11
|
+
"@saltcorn/data": "0.8.5-rc.2",
|
|
12
|
+
"@saltcorn/admin-models": "0.8.5-rc.2",
|
|
13
|
+
"@saltcorn/filemanager": "0.8.5-rc.2",
|
|
14
|
+
"@saltcorn/markup": "0.8.5-rc.2",
|
|
15
|
+
"@saltcorn/sbadmin2": "0.8.5-rc.2",
|
|
16
16
|
"@socket.io/cluster-adapter": "^0.2.1",
|
|
17
17
|
"@socket.io/sticky": "^1.0.1",
|
|
18
18
|
"adm-zip": "0.5.10",
|
|
@@ -141,6 +141,13 @@ div.settings-panel input[type="number"] {
|
|
|
141
141
|
border-width: 1px;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
div.settings-panel .form-control {
|
|
145
|
+
padding: 0.375rem 0.75rem;
|
|
146
|
+
}
|
|
147
|
+
div.settings-panel .form-select {
|
|
148
|
+
padding: 0.375rem 2.25rem 0.375rem 0.75rem;
|
|
149
|
+
}
|
|
150
|
+
|
|
144
151
|
button.btnstylesel {
|
|
145
152
|
padding-left: 5px;
|
|
146
153
|
padding-right: 2px;
|
|
@@ -153,9 +153,15 @@ function apply_showif() {
|
|
|
153
153
|
};
|
|
154
154
|
|
|
155
155
|
const cache = e.prop("data-fetch-options-cache") || {};
|
|
156
|
-
if (cache[qs]) {
|
|
156
|
+
if (cache[qs] === "fetching") {
|
|
157
|
+
// do nothing, this will be activated by someone else
|
|
158
|
+
} else if (cache[qs]) {
|
|
157
159
|
activate(cache[qs], qs);
|
|
158
|
-
} else
|
|
160
|
+
} else {
|
|
161
|
+
e.prop("data-fetch-options-cache", {
|
|
162
|
+
...cache,
|
|
163
|
+
[qs]: "fetching",
|
|
164
|
+
});
|
|
159
165
|
$.ajax(`/api/${dynwhere.table}?${qs}`).then((resp) => {
|
|
160
166
|
if (resp.success) {
|
|
161
167
|
activate(resp.success, qs);
|
|
@@ -164,8 +170,15 @@ function apply_showif() {
|
|
|
164
170
|
...cacheNow,
|
|
165
171
|
[qs]: resp.success,
|
|
166
172
|
});
|
|
173
|
+
} else {
|
|
174
|
+
const cacheNow = e.prop("data-fetch-options-cache") || {};
|
|
175
|
+
e.prop("data-fetch-options-cache", {
|
|
176
|
+
...cacheNow,
|
|
177
|
+
[qs]: undefined,
|
|
178
|
+
});
|
|
167
179
|
}
|
|
168
180
|
});
|
|
181
|
+
}
|
|
169
182
|
});
|
|
170
183
|
$("[data-filter-table]").each(function (ix, element) {
|
|
171
184
|
const e = $(element);
|
|
@@ -271,7 +284,13 @@ function get_form_record(e, select_labels) {
|
|
|
271
284
|
}
|
|
272
285
|
function showIfFormulaInputs(e, fml) {
|
|
273
286
|
const rec = get_form_record(e);
|
|
274
|
-
|
|
287
|
+
try {
|
|
288
|
+
return new Function(`{${Object.keys(rec).join(",")}}`, "return " + fml)(
|
|
289
|
+
rec
|
|
290
|
+
);
|
|
291
|
+
} catch (e) {
|
|
292
|
+
throw new Error(`Error in evaluating showIf formula ${fml}: ${e.message}`);
|
|
293
|
+
}
|
|
275
294
|
}
|
|
276
295
|
|
|
277
296
|
function rep_del(e) {
|
|
@@ -578,7 +597,8 @@ function initialize_page() {
|
|
|
578
597
|
const options = parse(el.attr("locale-date-options"));
|
|
579
598
|
el.text(date.toLocaleDateString(locale, options));
|
|
580
599
|
});
|
|
581
|
-
if ($.fn.historyTabs
|
|
600
|
+
if ($.fn.historyTabs && $.fn.tab)
|
|
601
|
+
$('a[data-bs-toggle="tab"].deeplink').historyTabs();
|
|
582
602
|
init_bs5_dropdowns();
|
|
583
603
|
|
|
584
604
|
// Initialize Sliders - https://stackoverflow.com/a/31083391
|
package/routes/admin.js
CHANGED
|
@@ -19,6 +19,7 @@ const File = require("@saltcorn/data/models/file");
|
|
|
19
19
|
const { spawn } = require("child_process");
|
|
20
20
|
const User = require("@saltcorn/data/models/user");
|
|
21
21
|
const path = require("path");
|
|
22
|
+
const { X509Certificate } = require("crypto");
|
|
22
23
|
const { getAllTenants } = require("@saltcorn/admin-models/models/tenant");
|
|
23
24
|
const {
|
|
24
25
|
post_btn,
|
|
@@ -768,6 +769,26 @@ router.get(
|
|
|
768
769
|
!is_latest && !process.env.SALTCORN_DISABLE_UPGRADE && !git_commit;
|
|
769
770
|
const dbversion = await db.getVersion(true);
|
|
770
771
|
const { memUsage, diskUsage, cpuUsage } = await get_sys_info();
|
|
772
|
+
const custom_ssl_certificate = getRootState().getConfig(
|
|
773
|
+
"custom_ssl_certificate",
|
|
774
|
+
false
|
|
775
|
+
);
|
|
776
|
+
let expiry = "";
|
|
777
|
+
if (custom_ssl_certificate && X509Certificate) {
|
|
778
|
+
const { validTo } = new X509Certificate(custom_ssl_certificate);
|
|
779
|
+
const diffTime = Math.abs(new Date(validTo) - new Date());
|
|
780
|
+
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
781
|
+
expiry = tr(
|
|
782
|
+
th(req.__("SSL expiry")),
|
|
783
|
+
diffDays < 14
|
|
784
|
+
? td(
|
|
785
|
+
{ class: "text-danger fw-bold" },
|
|
786
|
+
moment(new Date(validTo)).fromNow(),
|
|
787
|
+
i({ class: "fas fa-exclamation-triangle ms-1" })
|
|
788
|
+
)
|
|
789
|
+
: td(moment(new Date(validTo)).fromNow())
|
|
790
|
+
);
|
|
791
|
+
}
|
|
771
792
|
send_admin_page({
|
|
772
793
|
res,
|
|
773
794
|
req,
|
|
@@ -871,10 +892,13 @@ router.get(
|
|
|
871
892
|
td(db.isSQLite ? "SQLite " : "PostgreSQL ", dbversion)
|
|
872
893
|
),
|
|
873
894
|
isRoot
|
|
874
|
-
? tr(
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
895
|
+
? tr(
|
|
896
|
+
th(req.__("Database host")),
|
|
897
|
+
td(
|
|
898
|
+
db.connectObj.host +
|
|
899
|
+
(db.connectObj.port ? ":" + db.connectObj.port : "")
|
|
900
|
+
)
|
|
901
|
+
)
|
|
878
902
|
: "",
|
|
879
903
|
isRoot
|
|
880
904
|
? tr(
|
|
@@ -902,7 +926,8 @@ router.get(
|
|
|
902
926
|
: td(diskUsage, "%")
|
|
903
927
|
),
|
|
904
928
|
tr(th(req.__("CPU usage")), td(cpuUsage, "%")),
|
|
905
|
-
tr(th(req.__("Mem usage")), td(memUsage, "%"))
|
|
929
|
+
tr(th(req.__("Mem usage")), td(memUsage, "%")),
|
|
930
|
+
expiry
|
|
906
931
|
)
|
|
907
932
|
),
|
|
908
933
|
p(
|
package/routes/pageedit.js
CHANGED
|
@@ -55,9 +55,9 @@ module.exports = router;
|
|
|
55
55
|
* @param {object} req
|
|
56
56
|
* @returns {Promise<Form>}
|
|
57
57
|
*/
|
|
58
|
-
const pagePropertiesForm = async (req) => {
|
|
58
|
+
const pagePropertiesForm = async (req, isNew) => {
|
|
59
59
|
const roles = await User.get_roles();
|
|
60
|
-
|
|
60
|
+
const pages = (await Page.find()).map((p) => p.name);
|
|
61
61
|
const form = new Form({
|
|
62
62
|
action: addOnDoneRedirect("/pageedit/edit-properties", req),
|
|
63
63
|
fields: [
|
|
@@ -67,6 +67,8 @@ const pagePropertiesForm = async (req) => {
|
|
|
67
67
|
required: true,
|
|
68
68
|
validator(s) {
|
|
69
69
|
if (s.length < 1) return req.__("Missing name");
|
|
70
|
+
if (pages.includes(s) && isNew)
|
|
71
|
+
return req.__("A page with this name already exists");
|
|
70
72
|
},
|
|
71
73
|
sublabel: req.__("A short name that will be in your URL"),
|
|
72
74
|
type: "String",
|
|
@@ -331,7 +333,7 @@ router.get(
|
|
|
331
333
|
"/new",
|
|
332
334
|
isAdmin,
|
|
333
335
|
error_catcher(async (req, res) => {
|
|
334
|
-
const form = await pagePropertiesForm(req);
|
|
336
|
+
const form = await pagePropertiesForm(req, true);
|
|
335
337
|
res.sendWrap(
|
|
336
338
|
req.__(`Page attributes`),
|
|
337
339
|
wrap(renderForm(form, req.csrfToken()), false, req)
|
|
@@ -349,7 +351,7 @@ router.post(
|
|
|
349
351
|
"/edit-properties",
|
|
350
352
|
isAdmin,
|
|
351
353
|
error_catcher(async (req, res) => {
|
|
352
|
-
const form = await pagePropertiesForm(req);
|
|
354
|
+
const form = await pagePropertiesForm(req, !req.body.id);
|
|
353
355
|
form.hidden("id");
|
|
354
356
|
form.validate(req.body);
|
|
355
357
|
if (form.hasErrors) {
|
package/routes/tenant.js
CHANGED
|
@@ -7,7 +7,11 @@
|
|
|
7
7
|
|
|
8
8
|
const Router = require("express-promise-router");
|
|
9
9
|
const Form = require("@saltcorn/data/models/form");
|
|
10
|
-
const {
|
|
10
|
+
const {
|
|
11
|
+
getState,
|
|
12
|
+
add_tenant,
|
|
13
|
+
getRootState,
|
|
14
|
+
} = require("@saltcorn/data/db/state");
|
|
11
15
|
const {
|
|
12
16
|
create_tenant,
|
|
13
17
|
getAllTenants,
|
|
@@ -65,6 +69,9 @@ const { getConfig } = require("@saltcorn/data/models/config");
|
|
|
65
69
|
const router = new Router();
|
|
66
70
|
module.exports = router;
|
|
67
71
|
|
|
72
|
+
const remove_leading_chars = (cs, s) =>
|
|
73
|
+
s.startsWith(cs) ? remove_leading_chars(cs, s.substring(cs.length)) : s;
|
|
74
|
+
|
|
68
75
|
/**
|
|
69
76
|
* Declare Form to create Tenant
|
|
70
77
|
* @param {object} req - Request
|
|
@@ -72,7 +79,8 @@ module.exports = router;
|
|
|
72
79
|
* @category server
|
|
73
80
|
*/
|
|
74
81
|
// TBD add form field email for tenant admin
|
|
75
|
-
|
|
82
|
+
|
|
83
|
+
const tenant_form = (req, base_url) =>
|
|
76
84
|
new Form({
|
|
77
85
|
action: "/tenant/create",
|
|
78
86
|
submitLabel: req.__("Create"),
|
|
@@ -85,7 +93,7 @@ const tenant_form = (req) =>
|
|
|
85
93
|
name: "subdomain",
|
|
86
94
|
label: req.__("Application name"),
|
|
87
95
|
input_type: "text",
|
|
88
|
-
postText: text(
|
|
96
|
+
postText: text("." + base_url),
|
|
89
97
|
},
|
|
90
98
|
],
|
|
91
99
|
});
|
|
@@ -100,7 +108,8 @@ const tenant_form = (req) =>
|
|
|
100
108
|
*/
|
|
101
109
|
// TBD To allow few roles to create tenants - currently only one role has such rights simultaneously
|
|
102
110
|
const create_tenant_allowed = (req) => {
|
|
103
|
-
const required_role =
|
|
111
|
+
const required_role =
|
|
112
|
+
+getRootState().getConfig("role_to_create_tenant") || 10;
|
|
104
113
|
const user_role = req.user ? req.user.role_id : 10;
|
|
105
114
|
return user_role <= required_role;
|
|
106
115
|
};
|
|
@@ -117,6 +126,13 @@ const is_ip_address = (hostname) => {
|
|
|
117
126
|
return hostname.split(".").every((s) => +s >= 0 && +s <= 255);
|
|
118
127
|
};
|
|
119
128
|
|
|
129
|
+
const get_cfg_tenant_base_url = (req) =>
|
|
130
|
+
remove_leading_chars(
|
|
131
|
+
".",
|
|
132
|
+
getRootState().getConfig("tenant_baseurl", req.hostname)
|
|
133
|
+
)
|
|
134
|
+
.replace("http://", "")
|
|
135
|
+
.replace("https://", "");
|
|
120
136
|
/**
|
|
121
137
|
* Create tenant screen runnning
|
|
122
138
|
* @name get/create
|
|
@@ -126,10 +142,7 @@ const is_ip_address = (hostname) => {
|
|
|
126
142
|
router.get(
|
|
127
143
|
"/create",
|
|
128
144
|
error_catcher(async (req, res) => {
|
|
129
|
-
if (
|
|
130
|
-
!db.is_it_multi_tenant() ||
|
|
131
|
-
db.getTenantSchema() !== db.connectObj.default_schema
|
|
132
|
-
) {
|
|
145
|
+
if (!db.is_it_multi_tenant()) {
|
|
133
146
|
res.sendWrap(
|
|
134
147
|
req.__("Create application"),
|
|
135
148
|
req.__("Multi-tenancy not enabled")
|
|
@@ -137,7 +150,13 @@ router.get(
|
|
|
137
150
|
return;
|
|
138
151
|
}
|
|
139
152
|
if (!create_tenant_allowed(req)) {
|
|
140
|
-
|
|
153
|
+
const redir = getState().getConfig("tenant_create_unauth_redirect");
|
|
154
|
+
const redirRoot = getRootState().getConfig(
|
|
155
|
+
"tenant_create_unauth_redirect"
|
|
156
|
+
);
|
|
157
|
+
if (redir) res.redirect(redir);
|
|
158
|
+
else if (redirRoot) res.redirect(redirRoot);
|
|
159
|
+
else res.sendWrap(req.__("Create application"), req.__("Not allowed"));
|
|
141
160
|
return;
|
|
142
161
|
}
|
|
143
162
|
|
|
@@ -149,6 +168,7 @@ router.get(
|
|
|
149
168
|
)
|
|
150
169
|
);
|
|
151
170
|
let create_tenant_warning_text = "";
|
|
171
|
+
const base_url = get_cfg_tenant_base_url(req);
|
|
152
172
|
if (getState().getConfig("create_tenant_warning")) {
|
|
153
173
|
create_tenant_warning_text = getState().getConfig(
|
|
154
174
|
"create_tenant_warning_text"
|
|
@@ -192,13 +212,13 @@ router.get(
|
|
|
192
212
|
res.sendWrap(
|
|
193
213
|
req.__("Create application"),
|
|
194
214
|
create_tenant_warning_text +
|
|
195
|
-
renderForm(tenant_form(req), req.csrfToken()) +
|
|
215
|
+
renderForm(tenant_form(req, base_url), req.csrfToken()) +
|
|
196
216
|
p(
|
|
197
217
|
{ class: "mt-2" },
|
|
198
218
|
req.__("To login to a previously created application, go to: "),
|
|
199
219
|
code(`${req.protocol}://`) +
|
|
200
220
|
i(req.__("Application name")) +
|
|
201
|
-
code("." +
|
|
221
|
+
code("." + base_url)
|
|
202
222
|
)
|
|
203
223
|
);
|
|
204
224
|
})
|
|
@@ -209,14 +229,14 @@ router.get(
|
|
|
209
229
|
* @param {string} subdomain - Tenant Subdomain name string
|
|
210
230
|
* @returns {string}
|
|
211
231
|
*/
|
|
212
|
-
const getNewURL = (req, subdomain) => {
|
|
232
|
+
const getNewURL = (req, subdomain, base_url) => {
|
|
213
233
|
var ports = "";
|
|
214
234
|
const host = req.get("host");
|
|
215
235
|
if (typeof host === "string") {
|
|
216
236
|
const hosts = host.split(":");
|
|
217
237
|
if (hosts.length > 1) ports = `:${hosts[1]}`;
|
|
218
238
|
}
|
|
219
|
-
const hostname = req.hostname;
|
|
239
|
+
const hostname = base_url || req.hostname;
|
|
220
240
|
// return newurl
|
|
221
241
|
return `${req.protocol}://${subdomain}.${hostname}${ports}/`;
|
|
222
242
|
};
|
|
@@ -231,10 +251,7 @@ router.post(
|
|
|
231
251
|
"/create",
|
|
232
252
|
error_catcher(async (req, res) => {
|
|
233
253
|
// check that multi-tenancy is enabled
|
|
234
|
-
if (
|
|
235
|
-
!db.is_it_multi_tenant() ||
|
|
236
|
-
db.getTenantSchema() !== db.connectObj.default_schema
|
|
237
|
-
) {
|
|
254
|
+
if (!db.is_it_multi_tenant()) {
|
|
238
255
|
res.sendWrap(
|
|
239
256
|
req.__("Create application"),
|
|
240
257
|
req.__("Multi-tenancy not enabled")
|
|
@@ -274,7 +291,8 @@ router.post(
|
|
|
274
291
|
);
|
|
275
292
|
} else {
|
|
276
293
|
// tenant url
|
|
277
|
-
const
|
|
294
|
+
const base_url = get_cfg_tenant_base_url(req);
|
|
295
|
+
const newurl = getNewURL(req, subdomain, base_url);
|
|
278
296
|
// tenant template
|
|
279
297
|
const tenant_template = getState().getConfig("tenant_template");
|
|
280
298
|
// tenant creator
|
|
@@ -426,6 +444,8 @@ const tenant_settings_form = (req) =>
|
|
|
426
444
|
"create_tenant_warning",
|
|
427
445
|
"create_tenant_warning_text",
|
|
428
446
|
"tenant_template",
|
|
447
|
+
"tenant_baseurl",
|
|
448
|
+
"tenant_create_unauth_redirect",
|
|
429
449
|
{ section_header: "Tenant application capabilities" },
|
|
430
450
|
"tenants_install_git",
|
|
431
451
|
"tenants_set_npm_modules",
|
package/serve.js
CHANGED
|
@@ -195,6 +195,10 @@ module.exports =
|
|
|
195
195
|
dev,
|
|
196
196
|
...appargs
|
|
197
197
|
} = {}) => {
|
|
198
|
+
process.on("unhandledRejection", (reason, p) => {
|
|
199
|
+
console.error(reason, "Unhandled Rejection at Promise");
|
|
200
|
+
});
|
|
201
|
+
|
|
198
202
|
if (dev && cluster.isMaster) {
|
|
199
203
|
listenForChanges(getRelevantPackages(), await getPluginDirectories());
|
|
200
204
|
}
|
package/wrapper.js
CHANGED
|
@@ -146,11 +146,15 @@ const get_menu = (req) => {
|
|
|
146
146
|
section: req.__("Admin"),
|
|
147
147
|
items: adminItems,
|
|
148
148
|
},
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
149
|
+
...(authItems.length
|
|
150
|
+
? [
|
|
151
|
+
{
|
|
152
|
+
section: req.__("User"),
|
|
153
|
+
isUser: true,
|
|
154
|
+
items: authItems,
|
|
155
|
+
},
|
|
156
|
+
]
|
|
157
|
+
: []),
|
|
154
158
|
].filter((s) => s);
|
|
155
159
|
};
|
|
156
160
|
/**
|