@heswell/vuu-portal 2.1.2 → 2.2.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/195.js +141 -10
- package/README.md +30 -3
- package/bin.js +7 -1
- package/config/application.conf +69 -0
- package/config/default-modules.yaml +26 -0
- package/index.js +1 -1
- package/package.json +3 -2
- package/src/PortalApplication.d.ts +3 -2
- package/src/index.d.ts +1 -0
- package/src/modules/ModuleDiscovery/ModuleStore.d.ts +2 -36
- package/src/resolvePortalConfig.d.ts +31 -0
package/195.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { MODULE_ADMIN_RPC, executeModuleAdminRpc, managedModuleColumnValues, managedModulePermissionValues, splitNotificationTopics, validateModuleConfig } from "@heswell/module-admin";
|
|
2
|
+
import { ConfigFactory, EditSessionRpcHandler, ModuleFactory, NotificationModule, NullProvider, PermissionFilter, Provider, TableDef, VUU_DEFAULT_COLUMNS, ViewPortDef, createConfiguredAuthProviders, createVuuServerApplication, getLogger } from "@heswell/vuu-server";
|
|
3
3
|
import node_fs from "node:fs";
|
|
4
4
|
import node_path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
5
6
|
function moduleClientIdentifier(name) {
|
|
6
7
|
const kebab = name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
|
|
7
8
|
return kebab.startsWith("vuu-") ? kebab : `vuu-${kebab}`;
|
|
@@ -30,7 +31,8 @@ function readModules(table) {
|
|
|
30
31
|
mfComponent: stringValue(table, row, "mfComponent"),
|
|
31
32
|
mfScope: stringValue(table, row, "mfScope"),
|
|
32
33
|
mfUrl: stringValue(table, row, "mfUrl"),
|
|
33
|
-
...navIcon(table, row)
|
|
34
|
+
...navIcon(table, row),
|
|
35
|
+
...navMetadata(table, row)
|
|
34
36
|
};
|
|
35
37
|
});
|
|
36
38
|
}
|
|
@@ -40,6 +42,23 @@ function navIcon(table, row) {
|
|
|
40
42
|
navIconUrl
|
|
41
43
|
} : {};
|
|
42
44
|
}
|
|
45
|
+
function navMetadata(table, row) {
|
|
46
|
+
const metadata = {};
|
|
47
|
+
const navOrder = optionalValue(table, row, "navOrder");
|
|
48
|
+
if ("number" == typeof navOrder && Number.isFinite(navOrder)) metadata.navOrder = navOrder;
|
|
49
|
+
const navBadge = optionalValue(table, row, "navBadge");
|
|
50
|
+
if ("string" == typeof navBadge && navBadge) metadata.navBadge = navBadge;
|
|
51
|
+
const topics = optionalValue(table, row, "notificationTopics");
|
|
52
|
+
if ("string" == typeof topics) {
|
|
53
|
+
const notificationTopics = splitNotificationTopics(topics);
|
|
54
|
+
if (notificationTopics.length) metadata.notificationTopics = notificationTopics;
|
|
55
|
+
}
|
|
56
|
+
return metadata;
|
|
57
|
+
}
|
|
58
|
+
function optionalValue(table, row, column) {
|
|
59
|
+
const index = table.tableDef.columns.findIndex(({ name })=>name === column);
|
|
60
|
+
return -1 === index ? void 0 : row[index];
|
|
61
|
+
}
|
|
43
62
|
function readModulePermissions(table) {
|
|
44
63
|
return table.rows.map((row)=>({
|
|
45
64
|
moduleId: numberValue(table, row, "module_id"),
|
|
@@ -69,7 +88,13 @@ function selectModules(modules, modulePermissions, authorizations) {
|
|
|
69
88
|
...latestByName.values()
|
|
70
89
|
];
|
|
71
90
|
const permittedIds = new Set(permittedModules.map(({ id })=>id));
|
|
72
|
-
return permittedModules.filter(({ parentModuleId })=>0 === parentModuleId || permittedIds.has(parentModuleId)).map(({ parentModuleId: _parentModuleId, ...module })=>module).sort(
|
|
91
|
+
return permittedModules.filter(({ parentModuleId })=>0 === parentModuleId || permittedIds.has(parentModuleId)).map(({ parentModuleId: _parentModuleId, ...module })=>module).sort(compareModules);
|
|
92
|
+
}
|
|
93
|
+
function compareModules(left, right) {
|
|
94
|
+
const leftOrder = left.navOrder ?? 1 / 0;
|
|
95
|
+
const rightOrder = right.navOrder ?? 1 / 0;
|
|
96
|
+
if (leftOrder !== rightOrder) return leftOrder < rightOrder ? -1 : 1;
|
|
97
|
+
return left.name.localeCompare(right.name);
|
|
73
98
|
}
|
|
74
99
|
function accessRoleForModule(module, moduleRoles, permittedModuleRoles) {
|
|
75
100
|
const ownRole = permittedModuleRoles.get(module.id);
|
|
@@ -151,6 +176,18 @@ const modulesTable = TableDef({
|
|
|
151
176
|
name: "navIconUrl",
|
|
152
177
|
dataType: "string"
|
|
153
178
|
},
|
|
179
|
+
{
|
|
180
|
+
name: "navOrder",
|
|
181
|
+
dataType: "double"
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
name: "navBadge",
|
|
185
|
+
dataType: "string"
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
name: "notificationTopics",
|
|
189
|
+
dataType: "string"
|
|
190
|
+
},
|
|
154
191
|
...VUU_DEFAULT_COLUMNS
|
|
155
192
|
],
|
|
156
193
|
keyField: "id",
|
|
@@ -331,9 +368,12 @@ function readModulesFile(filePath) {
|
|
|
331
368
|
return parsed.modules;
|
|
332
369
|
}
|
|
333
370
|
function cloneModules(modules) {
|
|
334
|
-
return modules.map((module)=>(
|
|
335
|
-
|
|
336
|
-
|
|
371
|
+
return modules.map((module)=>Object.fromEntries(Object.entries(module).filter(([, value])=>void 0 !== value).map(([key, value])=>[
|
|
372
|
+
key,
|
|
373
|
+
Array.isArray(value) ? [
|
|
374
|
+
...value
|
|
375
|
+
] : value
|
|
376
|
+
])));
|
|
337
377
|
}
|
|
338
378
|
const managedModuleKeys = [
|
|
339
379
|
"id",
|
|
@@ -353,6 +393,11 @@ const managedModuleKeys = [
|
|
|
353
393
|
"navIconUrl",
|
|
354
394
|
"accessRole"
|
|
355
395
|
];
|
|
396
|
+
const optionalModuleKeys = [
|
|
397
|
+
"navOrder",
|
|
398
|
+
"navBadge",
|
|
399
|
+
"notificationTopics"
|
|
400
|
+
];
|
|
356
401
|
const retiredModuleKeys = [
|
|
357
402
|
"vuuConnectionId",
|
|
358
403
|
"vuuWebsocketUrl",
|
|
@@ -360,7 +405,7 @@ const retiredModuleKeys = [
|
|
|
360
405
|
];
|
|
361
406
|
function parseManagedModule(value, index) {
|
|
362
407
|
if (!isRecord(value)) throw new Error(`Modules file entry ${index} must be an object`);
|
|
363
|
-
for (const key of Object.keys(value))if (!managedModuleKeys.includes(key) && !retiredModuleKeys.includes(key)) throw new Error(`Modules file entry ${index} contains unknown field '${key}'`);
|
|
408
|
+
for (const key of Object.keys(value))if (!managedModuleKeys.includes(key) && !optionalModuleKeys.includes(key) && !retiredModuleKeys.includes(key)) throw new Error(`Modules file entry ${index} contains unknown field '${key}'`);
|
|
364
409
|
const module = {};
|
|
365
410
|
for (const key of managedModuleKeys){
|
|
366
411
|
const fieldValue = value[key];
|
|
@@ -376,6 +421,18 @@ function parseManagedModule(value, index) {
|
|
|
376
421
|
if ("number" === expected && !Number.isInteger(fieldValue)) throw new Error(`Modules file entry ${index} field '${key}' must be an integer`);
|
|
377
422
|
module[key] = fieldValue;
|
|
378
423
|
}
|
|
424
|
+
for (const key of optionalModuleKeys){
|
|
425
|
+
const fieldValue = value[key];
|
|
426
|
+
if (null == fieldValue) continue;
|
|
427
|
+
const valid = "navOrder" === key ? "number" == typeof fieldValue : "navBadge" === key ? "string" == typeof fieldValue : Array.isArray(fieldValue) && fieldValue.every((topic)=>"string" == typeof topic);
|
|
428
|
+
if (!valid) {
|
|
429
|
+
const expected = "navOrder" === key ? "a number" : "navBadge" === key ? "a string" : "a list of strings";
|
|
430
|
+
throw new Error(`Modules file entry ${index} field '${key}' must be ${expected}`);
|
|
431
|
+
}
|
|
432
|
+
module[key] = Array.isArray(fieldValue) ? [
|
|
433
|
+
...fieldValue
|
|
434
|
+
] : fieldValue;
|
|
435
|
+
}
|
|
379
436
|
return module;
|
|
380
437
|
}
|
|
381
438
|
function assertValidManagedModules(modules, filePath) {
|
|
@@ -424,11 +481,69 @@ function columnsFor(table) {
|
|
|
424
481
|
function valuesToRow(table, values) {
|
|
425
482
|
return table.columns.map(({ name })=>values[name] ?? "");
|
|
426
483
|
}
|
|
484
|
+
const here = node_path.dirname(fileURLToPath(import.meta.url));
|
|
485
|
+
const BUNDLED_CONFIG_FILE = [
|
|
486
|
+
node_path.resolve(here, "../config/application.conf"),
|
|
487
|
+
node_path.resolve(here, "config/application.conf")
|
|
488
|
+
].find((candidate)=>node_fs.existsSync(candidate));
|
|
489
|
+
const existingFile = (file, source)=>{
|
|
490
|
+
if (!node_fs.existsSync(file)) throw new Error(`Config file not found at '${file}' (from ${source})`);
|
|
491
|
+
return file;
|
|
492
|
+
};
|
|
493
|
+
function resolvePortalConfig({ configFile, cwd = process.cwd(), env = process.env } = {}) {
|
|
494
|
+
if (configFile) return {
|
|
495
|
+
configFile: existingFile(node_path.resolve(cwd, configFile), "--config"),
|
|
496
|
+
source: "--config"
|
|
497
|
+
};
|
|
498
|
+
const envFile = env.VUU_CONFIG_FILE?.trim();
|
|
499
|
+
if (envFile) return {
|
|
500
|
+
configFile: existingFile(node_path.resolve(cwd, envFile), "VUU_CONFIG_FILE"),
|
|
501
|
+
source: "VUU_CONFIG_FILE"
|
|
502
|
+
};
|
|
503
|
+
const appName = env.VUU_APP?.trim();
|
|
504
|
+
if (appName) return {
|
|
505
|
+
configFile: existingFile(node_path.resolve(cwd, "packages", appName, "application.conf"), "VUU_APP"),
|
|
506
|
+
source: "VUU_APP"
|
|
507
|
+
};
|
|
508
|
+
const cwdFile = node_path.resolve(cwd, "application.conf");
|
|
509
|
+
if (node_fs.existsSync(cwdFile)) return {
|
|
510
|
+
configFile: cwdFile,
|
|
511
|
+
source: "cwd"
|
|
512
|
+
};
|
|
513
|
+
if (!BUNDLED_CONFIG_FILE) throw new Error("Unable to locate application.conf: pass --config, or set VUU_CONFIG_FILE or VUU_APP.");
|
|
514
|
+
const overrides = {
|
|
515
|
+
"vuu.portal.modulesFile": node_path.resolve(cwd, env.VUU_PORTAL_MODULES_FILE?.trim() || "modules.yaml")
|
|
516
|
+
};
|
|
517
|
+
for (const [key, envName] of BUNDLED_PATH_ENV){
|
|
518
|
+
const value = env[envName]?.trim();
|
|
519
|
+
if (value) overrides[key] = node_path.resolve(cwd, value);
|
|
520
|
+
}
|
|
521
|
+
return {
|
|
522
|
+
configFile: BUNDLED_CONFIG_FILE,
|
|
523
|
+
source: "bundled",
|
|
524
|
+
overrides
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
const BUNDLED_PATH_ENV = [
|
|
528
|
+
[
|
|
529
|
+
"vuu.certPath",
|
|
530
|
+
"VUU_CERT_PATH"
|
|
531
|
+
],
|
|
532
|
+
[
|
|
533
|
+
"vuu.keyPath",
|
|
534
|
+
"VUU_KEY_PATH"
|
|
535
|
+
],
|
|
536
|
+
[
|
|
537
|
+
"vuu.portal.defaultModulesFile",
|
|
538
|
+
"VUU_PORTAL_DEFAULT_MODULES_FILE"
|
|
539
|
+
]
|
|
540
|
+
];
|
|
541
|
+
const log = getLogger("vuu.portal");
|
|
427
542
|
const PORTAL_DEFAULT_HTTPS_PORT = 8443;
|
|
428
543
|
const PORTAL_DEFAULT_WEBSOCKET_PATH = "/websocket-portal";
|
|
429
544
|
const PORTAL_DEFAULT_WEBSOCKET_PORT = 8091;
|
|
430
545
|
function createPortalApplication({ config, configFile } = {}) {
|
|
431
|
-
const portalConfig = config ??
|
|
546
|
+
const portalConfig = config ?? loadPortalConfig(configFile);
|
|
432
547
|
return createVuuServerApplication({
|
|
433
548
|
authProviders: createConfiguredAuthProviders(portalConfig),
|
|
434
549
|
config: portalConfig,
|
|
@@ -439,10 +554,26 @@ function createPortalApplication({ config, configFile } = {}) {
|
|
|
439
554
|
moduleRegistry: createModuleRegistry(tableContainer, user)
|
|
440
555
|
}),
|
|
441
556
|
modules: [
|
|
557
|
+
NotificationModule((table)=>new NullProvider(table), (viewport)=>PermissionFilter("audience", new Set([
|
|
558
|
+
"all",
|
|
559
|
+
viewport.user.name
|
|
560
|
+
])), (table)=>ViewPortDef["default"](table.tableDef.columns)),
|
|
442
561
|
ModuleDiscoveryModule(loadModuleState(portalConfig), {
|
|
443
562
|
adminRole: loadModuleAdminRole(portalConfig)
|
|
444
563
|
})
|
|
445
564
|
]
|
|
446
565
|
});
|
|
447
566
|
}
|
|
448
|
-
|
|
567
|
+
function loadPortalConfig(configFile) {
|
|
568
|
+
const resolved = resolvePortalConfig({
|
|
569
|
+
configFile
|
|
570
|
+
});
|
|
571
|
+
const config = ConfigFactory.load(resolved.configFile, {
|
|
572
|
+
overrides: resolved.overrides
|
|
573
|
+
});
|
|
574
|
+
log.info(`using ${resolved.configFile} (from ${resolved.source})`);
|
|
575
|
+
if (config.getBoolean("vuu.ssl", false) && !(config.has("vuu.certPath") && config.has("vuu.keyPath"))) throw new Error(`vuu.ssl=true needs vuu.certPath and vuu.keyPath (set VUU_CERT_PATH and VUU_KEY_PATH, or set them in ${resolved.configFile})`);
|
|
576
|
+
if ("bundled" === resolved.source) log.warn(`no application.conf given: using the bundled default (vuu.auth.mode=${config.getString("vuu.auth.mode", "")}, vuu.ssl=${config.getBoolean("vuu.ssl", false)}). Pass --config or set VUU_CONFIG_FILE for anything but local development. Module catalogue: ${config.getPath("vuu.portal.modulesFile")}`);
|
|
577
|
+
return config;
|
|
578
|
+
}
|
|
579
|
+
export { BUNDLED_CONFIG_FILE, DEFAULT_MODULE_ADMIN_ROLE, InMemoryModuleStore, ModuleDiscoveryModule, ModuleState, PORTAL_DEFAULT_WEBSOCKET_PATH, YamlModuleStore, createModuleRegistry, createModuleState, createPortalApplication, loadDefaultModules, loadModuleAdminRole, loadModuleState, moduleClientIdentifier, resolvePortalConfig };
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @heswell/vuu-portal
|
|
2
2
|
|
|
3
3
|
Vuu portal server: a [`@heswell/vuu-server`](../vuu-server) application that
|
|
4
|
-
hosts the `MODULE_DISCOVERY`
|
|
4
|
+
hosts the `MODULE_DISCOVERY` and `NOTIFICATIONS` modules and returns each user's module registry in
|
|
5
5
|
the login response. Requires the [Bun](https://bun.sh) runtime to run the
|
|
6
6
|
server. The library exports can be imported under Node.js too.
|
|
7
7
|
|
|
@@ -49,11 +49,13 @@ Root exports:
|
|
|
49
49
|
|
|
50
50
|
## Starting the portal
|
|
51
51
|
|
|
52
|
-
Everything is configured in `application.conf`.
|
|
53
|
-
|
|
52
|
+
Everything is configured in `application.conf`. With no conf, the portal starts
|
|
53
|
+
from the default bundled with the package, for local development. Point it at
|
|
54
|
+
your own conf to run the stock entry unchanged:
|
|
54
55
|
|
|
55
56
|
```sh
|
|
56
57
|
# bin
|
|
58
|
+
bunx vuu-portal # bundled default conf
|
|
57
59
|
bunx vuu-portal --config ./path/to/application.conf
|
|
58
60
|
VUU_CONFIG_FILE=./path/to/application.conf bunx vuu-portal
|
|
59
61
|
|
|
@@ -70,6 +72,31 @@ The conf file is located in this order:
|
|
|
70
72
|
3. `$VUU_APP`, which selects `packages/$VUU_APP/application.conf` under the
|
|
71
73
|
current directory.
|
|
72
74
|
4. `./application.conf`.
|
|
75
|
+
5. The conf bundled with the package,
|
|
76
|
+
[`config/application.conf`](./config/application.conf).
|
|
77
|
+
|
|
78
|
+
The portal logs which conf it used (logger `vuu.portal`).
|
|
79
|
+
|
|
80
|
+
### The bundled default conf
|
|
81
|
+
|
|
82
|
+
`config/application.conf` is meant for zero-config local development, and the
|
|
83
|
+
portal logs a warning when it is used:
|
|
84
|
+
|
|
85
|
+
- `vuu.auth.mode="permissive"`: any username and password may log in.
|
|
86
|
+
- `vuu.ssl=false`: the websocket is `ws://` and the REST server `http://`. No
|
|
87
|
+
certificate or key is shipped; for TLS set `VUU_SSL=true`, `VUU_CERT_PATH`
|
|
88
|
+
and `VUU_KEY_PATH`. `vuu.ssl=true` without both paths fails at startup.
|
|
89
|
+
- Websocket on 8091 (`/websocket-portal`), REST on 8443.
|
|
90
|
+
- No Keycloak secret is shipped; set `VUU_KEYCLOAK_CLIENT_SECRET` (and
|
|
91
|
+
`VUU_KEYCLOAK_URL`) with `VUU_AUTH_MODE=keycloak`.
|
|
92
|
+
- The seed catalogue, [`config/default-modules.yaml`](./config/default-modules.yaml),
|
|
93
|
+
is empty. The writable `modules.yaml` is kept in the current directory (or at
|
|
94
|
+
`$VUU_PORTAL_MODULES_FILE`), never in the installed package.
|
|
95
|
+
`VUU_PORTAL_MODULES_FILE`, `VUU_PORTAL_DEFAULT_MODULES_FILE`,
|
|
96
|
+
`VUU_CERT_PATH` and `VUU_KEY_PATH` resolve against the current directory.
|
|
97
|
+
|
|
98
|
+
Every setting has a `VUU_*` override; see the comments in the file, or
|
|
99
|
+
`vuu-portal --help`. For anything else, copy it and pass `--config`.
|
|
73
100
|
|
|
74
101
|
In this repository, `npm run start:portal` starts it with
|
|
75
102
|
`VUU_APP=vuu-portal`, which uses [`application.conf`](./application.conf).
|
package/bin.js
CHANGED
|
@@ -6,7 +6,13 @@ Starts the Vuu portal server. All settings (ports, websocket path, auth,
|
|
|
6
6
|
module catalogue files, module admin role) come from application.conf.
|
|
7
7
|
|
|
8
8
|
Without --config the conf is located via $VUU_CONFIG_FILE, then $VUU_APP
|
|
9
|
-
(packages/<app>/application.conf), then ./application.conf
|
|
9
|
+
(packages/<app>/application.conf), then ./application.conf, then the
|
|
10
|
+
conf bundled with the package. The bundled conf is for local development:
|
|
11
|
+
permissive auth, no TLS (ws:// and http://), websocket on 8091 and REST on
|
|
12
|
+
8443, and modules.yaml in the current directory. Override it with
|
|
13
|
+
VUU_AUTH_MODE, VUU_SSL, VUU_CERT_PATH, VUU_KEY_PATH, VUU_WEBSOCKET_PORT,
|
|
14
|
+
VUU_HTTPS_PORT, VUU_WEBSOCKET_PATH, VUU_PORTAL_MODULES_FILE,
|
|
15
|
+
VUU_PORTAL_DEFAULT_MODULES_FILE, VUU_CORS_ALLOWED_ORIGINS, VUU_LOG_LEVEL.`;
|
|
10
16
|
const parseConfigFile = (args)=>{
|
|
11
17
|
let configFile;
|
|
12
18
|
for(let index = 0; index < args.length; index++){
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Default vuu-portal configuration, bundled with the package. It is used when
|
|
2
|
+
# no conf is given: no --config, VUU_CONFIG_FILE or VUU_APP, and no
|
|
3
|
+
# ./application.conf in the current directory.
|
|
4
|
+
#
|
|
5
|
+
# Local development only: any username and password may log in, and the
|
|
6
|
+
# websocket and REST servers run without TLS. Every setting can be changed
|
|
7
|
+
# with the VUU_* environment variable next to it, or copy this file, edit it
|
|
8
|
+
# and pass --config.
|
|
9
|
+
#
|
|
10
|
+
# Relative paths in this file resolve against this directory (inside the
|
|
11
|
+
# installed package). The writable module catalogue (modules.yaml) is kept in
|
|
12
|
+
# the current directory instead, and VUU_PORTAL_MODULES_FILE, VUU_CERT_PATH,
|
|
13
|
+
# VUU_KEY_PATH and VUU_PORTAL_DEFAULT_MODULES_FILE resolve against the
|
|
14
|
+
# current directory.
|
|
15
|
+
|
|
16
|
+
vuu.auth.mode="permissive"
|
|
17
|
+
vuu.auth.mode=${?VUU_AUTH_MODE}
|
|
18
|
+
vuu.auth.path="/api/authn"
|
|
19
|
+
# Comma-separated username:password entries (or a bare username, for a user
|
|
20
|
+
# that may use any password). Empty allows any username with any password.
|
|
21
|
+
vuu.auth.permissive.users=""
|
|
22
|
+
vuu.auth.permissive.users=${?VUU_AUTH_PERMISSIVE_USERS}
|
|
23
|
+
|
|
24
|
+
# TLS is off by default: ws:// and http://. No certificate is shipped. To use
|
|
25
|
+
# TLS set VUU_SSL=true, VUU_CERT_PATH and VUU_KEY_PATH. vuu.ssl=false is only
|
|
26
|
+
# allowed with vuu.auth.mode="permissive".
|
|
27
|
+
vuu.ssl=false
|
|
28
|
+
vuu.ssl=${?VUU_SSL}
|
|
29
|
+
vuu.certPath=${?VUU_CERT_PATH}
|
|
30
|
+
vuu.keyPath=${?VUU_KEY_PATH}
|
|
31
|
+
|
|
32
|
+
vuu.websocket.path="/websocket-portal"
|
|
33
|
+
vuu.websocket.path=${?VUU_WEBSOCKET_PATH}
|
|
34
|
+
vuu.websocket.port=8091
|
|
35
|
+
vuu.websocket.port=${?VUU_WEBSOCKET_PORT}
|
|
36
|
+
vuu.https.port=8443
|
|
37
|
+
vuu.https.port=${?VUU_HTTPS_PORT}
|
|
38
|
+
|
|
39
|
+
# Browser origins allowed to call the REST auth endpoint (CORS).
|
|
40
|
+
# VUU_CORS_ALLOWED_ORIGINS replaces the list with a comma-separated list.
|
|
41
|
+
vuu.auth.cors.allowedOrigins=["http://localhost:5173", "http://127.0.0.1:5173", "http://localhost:5001", "http://127.0.0.1:5001"]
|
|
42
|
+
vuu.auth.cors.allowedOrigins=${?VUU_CORS_ALLOWED_ORIGINS}
|
|
43
|
+
|
|
44
|
+
# Keycloak, used only with VUU_AUTH_MODE=keycloak. No secret is shipped.
|
|
45
|
+
vuu.keycloak.url=${?VUU_KEYCLOAK_URL}
|
|
46
|
+
vuu.keycloak.realm="vuu"
|
|
47
|
+
vuu.keycloak.realm=${?VUU_KEYCLOAK_REALM}
|
|
48
|
+
vuu.keycloak.allowSelfSignedCert=false
|
|
49
|
+
vuu.keycloak.allowSelfSignedCert=${?VUU_KEYCLOAK_ALLOW_SELF_SIGNED_CERT}
|
|
50
|
+
vuu.auth.keycloak.clientId="vuu-portal-server"
|
|
51
|
+
vuu.auth.keycloak.clientId=${?VUU_KEYCLOAK_CLIENT_ID}
|
|
52
|
+
vuu.auth.keycloak.audience="vuu-portal-server"
|
|
53
|
+
vuu.auth.keycloak.audience=${?VUU_KEYCLOAK_AUDIENCE}
|
|
54
|
+
vuu.auth.keycloak.audiencePolicy="require-audience"
|
|
55
|
+
vuu.auth.keycloak.authorizationClientId="vuu-portal"
|
|
56
|
+
vuu.auth.keycloak.expectedAuthorizedParty="vuu-portal"
|
|
57
|
+
vuu.auth.keycloak.tokenExchangeEnabled=false
|
|
58
|
+
vuu.auth.keycloak.clientSecret=${?VUU_KEYCLOAK_CLIENT_SECRET}
|
|
59
|
+
|
|
60
|
+
# The module catalogue. defaultModulesFile (read-only, in this directory)
|
|
61
|
+
# seeds modulesFile on first startup; runtime edits are saved to modulesFile.
|
|
62
|
+
vuu.portal.defaultModulesFile="default-modules.yaml"
|
|
63
|
+
vuu.portal.modulesFile="modules.yaml"
|
|
64
|
+
vuu.portal.moduleAdminRole="module-admin-access"
|
|
65
|
+
vuu.portal.moduleAdminRole=${?VUU_PORTAL_MODULE_ADMIN_ROLE}
|
|
66
|
+
|
|
67
|
+
# Log level: trace, debug, info, warn, error or silent.
|
|
68
|
+
vuu.log.level="info"
|
|
69
|
+
vuu.log.level=${?VUU_LOG_LEVEL}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Default portal modules for the bundled application.conf. Seeds modules.yaml
|
|
2
|
+
# (in the current directory) on first startup; add modules there, or with the
|
|
3
|
+
# module admin service. To ship your own defaults, point
|
|
4
|
+
# VUU_PORTAL_DEFAULT_MODULES_FILE at a file like this one.
|
|
5
|
+
#
|
|
6
|
+
# Example entry:
|
|
7
|
+
#
|
|
8
|
+
# - id: 1
|
|
9
|
+
# version: 1
|
|
10
|
+
# parentModuleId: 0
|
|
11
|
+
# name: "moduleAdmin"
|
|
12
|
+
# title: "Manage remote modules"
|
|
13
|
+
# description: "Create new remote module, update existing modules"
|
|
14
|
+
# enabled: true
|
|
15
|
+
# location: "/Modules/Manage Modules"
|
|
16
|
+
# path: "/modules/admin"
|
|
17
|
+
# mfComponent: "ModuleAdmin"
|
|
18
|
+
# mfScope: "moduleAdmin"
|
|
19
|
+
# mfUrl: "http://localhost:5002"
|
|
20
|
+
# navIconUrl: ""
|
|
21
|
+
# accessRole: "module-admin-access"
|
|
22
|
+
# # Optional nav metadata:
|
|
23
|
+
# navOrder: 1
|
|
24
|
+
# navBadge: "modules.pending"
|
|
25
|
+
# notificationTopics: ["modules"]
|
|
26
|
+
modules: []
|
package/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
var src_PORTAL_DEFAULT_HTTPS_PORT = 8443;
|
|
2
2
|
var src_PORTAL_DEFAULT_WEBSOCKET_PORT = 8091;
|
|
3
|
-
export { DEFAULT_MODULE_ADMIN_ROLE, InMemoryModuleStore, ModuleDiscoveryModule, ModuleState, PORTAL_DEFAULT_WEBSOCKET_PATH, YamlModuleStore, createModuleRegistry, createModuleState, createPortalApplication, loadDefaultModules, loadModuleAdminRole, loadModuleState, moduleClientIdentifier } from "./195.js";
|
|
3
|
+
export { BUNDLED_CONFIG_FILE, DEFAULT_MODULE_ADMIN_ROLE, InMemoryModuleStore, ModuleDiscoveryModule, ModuleState, PORTAL_DEFAULT_WEBSOCKET_PATH, YamlModuleStore, createModuleRegistry, createModuleState, createPortalApplication, loadDefaultModules, loadModuleAdminRole, loadModuleState, moduleClientIdentifier, resolvePortalConfig } from "./195.js";
|
|
4
4
|
export { src_PORTAL_DEFAULT_HTTPS_PORT as PORTAL_DEFAULT_HTTPS_PORT, src_PORTAL_DEFAULT_WEBSOCKET_PORT as PORTAL_DEFAULT_WEBSOCKET_PORT };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heswell/vuu-portal",
|
|
3
|
-
"version": "2.1
|
|
3
|
+
"version": "2.2.1",
|
|
4
4
|
"description": "VUU portal server with remote module discovery",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.js",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"files": [
|
|
52
52
|
"*.js",
|
|
53
53
|
"src/**/*.d.ts",
|
|
54
|
-
"README.md"
|
|
54
|
+
"README.md",
|
|
55
|
+
"config"
|
|
55
56
|
]
|
|
56
57
|
}
|
|
@@ -7,8 +7,9 @@ export type PortalApplicationOptions = {
|
|
|
7
7
|
config?: Config;
|
|
8
8
|
/**
|
|
9
9
|
* Path to the portal's application.conf, resolved against the current
|
|
10
|
-
* directory. Defaults to
|
|
11
|
-
*
|
|
10
|
+
* directory. Defaults to $VUU_CONFIG_FILE, then $VUU_APP
|
|
11
|
+
* (packages/<app>/application.conf), then ./application.conf, then the
|
|
12
|
+
* conf bundled with the package (see resolvePortalConfig).
|
|
12
13
|
*/
|
|
13
14
|
configFile?: string;
|
|
14
15
|
};
|
package/src/index.d.ts
CHANGED
|
@@ -3,3 +3,4 @@ export { loadModuleAdminRole, loadModuleState, ModuleDiscoveryModule, ModuleStat
|
|
|
3
3
|
export { createModuleState, InMemoryModuleStore, loadDefaultModules, type ModuleStore, YamlModuleStore, } from "./modules/ModuleDiscovery/ModuleStore.js";
|
|
4
4
|
export { DEFAULT_MODULE_ADMIN_ROLE } from "./modules/ModuleDiscovery/ModuleAdminService.js";
|
|
5
5
|
export { createPortalApplication, PORTAL_DEFAULT_HTTPS_PORT, PORTAL_DEFAULT_WEBSOCKET_PATH, PORTAL_DEFAULT_WEBSOCKET_PORT, type PortalApplicationOptions, } from "./PortalApplication.js";
|
|
6
|
+
export { BUNDLED_CONFIG_FILE, type PortalConfigSource, type ResolvedPortalConfig, type ResolvePortalConfigOptions, resolvePortalConfig, } from "./resolvePortalConfig.js";
|
|
@@ -6,48 +6,14 @@ export interface ModuleStore {
|
|
|
6
6
|
export declare class InMemoryModuleStore implements ModuleStore {
|
|
7
7
|
private modules;
|
|
8
8
|
constructor(modules?: readonly ManagedModule[]);
|
|
9
|
-
load():
|
|
10
|
-
parentModuleId: number;
|
|
11
|
-
name: string;
|
|
12
|
-
title: string;
|
|
13
|
-
description: string;
|
|
14
|
-
enabled: boolean;
|
|
15
|
-
location: string;
|
|
16
|
-
path: string;
|
|
17
|
-
mfComponent: string;
|
|
18
|
-
mfScope: string;
|
|
19
|
-
mfUrl: string;
|
|
20
|
-
navIconUrl: string;
|
|
21
|
-
accessRole: string;
|
|
22
|
-
id: number;
|
|
23
|
-
version: number;
|
|
24
|
-
created: number;
|
|
25
|
-
updated: number;
|
|
26
|
-
}[];
|
|
9
|
+
load(): ManagedModule[];
|
|
27
10
|
save(modules: readonly ManagedModule[]): void;
|
|
28
11
|
}
|
|
29
12
|
export declare class ModuleState {
|
|
30
13
|
#private;
|
|
31
14
|
private readonly store;
|
|
32
15
|
constructor(store: ModuleStore, modules: readonly ManagedModule[]);
|
|
33
|
-
get modules():
|
|
34
|
-
parentModuleId: number;
|
|
35
|
-
name: string;
|
|
36
|
-
title: string;
|
|
37
|
-
description: string;
|
|
38
|
-
enabled: boolean;
|
|
39
|
-
location: string;
|
|
40
|
-
path: string;
|
|
41
|
-
mfComponent: string;
|
|
42
|
-
mfScope: string;
|
|
43
|
-
mfUrl: string;
|
|
44
|
-
navIconUrl: string;
|
|
45
|
-
accessRole: string;
|
|
46
|
-
id: number;
|
|
47
|
-
version: number;
|
|
48
|
-
created: number;
|
|
49
|
-
updated: number;
|
|
50
|
-
}[];
|
|
16
|
+
get modules(): ManagedModule[];
|
|
51
17
|
replaceAndSave(modules: readonly ManagedModule[]): void;
|
|
52
18
|
}
|
|
53
19
|
export declare class YamlModuleStore implements ModuleStore {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ConfigValue } from "@heswell/vuu-server";
|
|
2
|
+
export type PortalConfigSource = "--config" | "VUU_CONFIG_FILE" | "VUU_APP" | "cwd" | "bundled";
|
|
3
|
+
export type ResolvedPortalConfig = {
|
|
4
|
+
/** Absolute path of the application.conf to load. */
|
|
5
|
+
configFile: string;
|
|
6
|
+
source: PortalConfigSource;
|
|
7
|
+
/**
|
|
8
|
+
* Config overrides. Only set for the bundled conf: the writable module
|
|
9
|
+
* catalogue (vuu.portal.modulesFile) goes in the current directory, or at
|
|
10
|
+
* $VUU_PORTAL_MODULES_FILE, never inside the installed package; and
|
|
11
|
+
* $VUU_CERT_PATH, $VUU_KEY_PATH and $VUU_PORTAL_DEFAULT_MODULES_FILE
|
|
12
|
+
* resolve against the current directory.
|
|
13
|
+
*/
|
|
14
|
+
overrides?: Record<string, ConfigValue>;
|
|
15
|
+
};
|
|
16
|
+
export type ResolvePortalConfigOptions = {
|
|
17
|
+
configFile?: string;
|
|
18
|
+
cwd?: string;
|
|
19
|
+
env?: Record<string, string | undefined>;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* The application.conf shipped in the package's config/ directory. Source
|
|
23
|
+
* modules live in src/, the built entries at the package root.
|
|
24
|
+
*/
|
|
25
|
+
export declare const BUNDLED_CONFIG_FILE: string;
|
|
26
|
+
/**
|
|
27
|
+
* Locates the portal's application.conf: --config, then $VUU_CONFIG_FILE,
|
|
28
|
+
* then $VUU_APP (packages/<app>/application.conf), then ./application.conf,
|
|
29
|
+
* then the conf bundled with the package.
|
|
30
|
+
*/
|
|
31
|
+
export declare function resolvePortalConfig({ configFile, cwd, env, }?: ResolvePortalConfigOptions): ResolvedPortalConfig;
|