@betterportal/config-manager 10.0.31 → 10.1.0
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/lib/.bsb/clients/service-betterportal-config-manager.d.ts +1 -1
- package/lib/.bsb/clients/service-betterportal-config-manager.js +1 -1
- package/lib/plugins/service-betterportal-config-manager/adminApi.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/adminApi.js +97 -30
- package/lib/plugins/service-betterportal-config-manager/adminApi.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/bootstrapEndpoint.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/bootstrapEndpoint.js +13 -12
- package/lib/plugins/service-betterportal-config-manager/bootstrapEndpoint.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/auth/_theme.bootstrap1/GET.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/auth/_theme.bootstrap1/GET.js +121 -6
- package/lib/plugins/service-betterportal-config-manager/bp-routes/auth/_theme.bootstrap1/GET.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/routes/_theme.bootstrap1/GET.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/routes/_theme.bootstrap1/GET.js +36 -2
- package/lib/plugins/service-betterportal-config-manager/bp-routes/routes/_theme.bootstrap1/GET.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/routes/route.impl.d.ts +6 -0
- package/lib/plugins/service-betterportal-config-manager/bp-routes/routes/route.impl.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/routes/route.impl.js +3 -0
- package/lib/plugins/service-betterportal-config-manager/bp-routes/routes/route.impl.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/services/_theme.bootstrap1/GET.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/services/_theme.bootstrap1/GET.js +114 -31
- package/lib/plugins/service-betterportal-config-manager/bp-routes/services/_theme.bootstrap1/GET.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/route.impl.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/route.impl.js +9 -4
- package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/route.impl.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/index.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/index.js +28 -8
- package/lib/plugins/service-betterportal-config-manager/index.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/menuEditor.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/menuEditor.js +7 -1
- package/lib/plugins/service-betterportal-config-manager/menuEditor.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/routeMounts.d.ts +8 -0
- package/lib/plugins/service-betterportal-config-manager/routeMounts.d.ts.map +1 -0
- package/lib/plugins/service-betterportal-config-manager/routeMounts.js +22 -0
- package/lib/plugins/service-betterportal-config-manager/routeMounts.js.map +1 -0
- package/lib/plugins/service-betterportal-config-manager/setupTokens.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/setupTokens.js +50 -0
- package/lib/plugins/service-betterportal-config-manager/setupTokens.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/storage/core.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/storage/core.js +25 -0
- package/lib/plugins/service-betterportal-config-manager/storage/core.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/syncApi.d.ts +2 -1
- package/lib/plugins/service-betterportal-config-manager/syncApi.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/syncApi.js +56 -1
- package/lib/plugins/service-betterportal-config-manager/syncApi.js.map +1 -1
- package/lib/schemas/service-betterportal-config-manager.json +1 -1
- package/lib/schemas/service-betterportal-config-manager.plugin.json +1 -1
- package/package.json +3 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated BSB virtual client for service-betterportal-config-manager
|
|
3
3
|
* DO NOT EDIT - Regenerated on every build
|
|
4
|
-
* @version 10.0
|
|
4
|
+
* @version 10.1.0
|
|
5
5
|
*/
|
|
6
6
|
import { ServiceClient, BSBService } from "@bsb/base";
|
|
7
7
|
import type { Observable, BSBServiceClientDefinition, EventInputType } from "@bsb/base";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated BSB virtual client for service-betterportal-config-manager
|
|
3
3
|
* DO NOT EDIT - Regenerated on every build
|
|
4
|
-
* @version 10.0
|
|
4
|
+
* @version 10.1.0
|
|
5
5
|
*/
|
|
6
6
|
import { ServiceClient, bsb, createBroadcastEvent, createEventSchemas } from "@bsb/base";
|
|
7
7
|
const _platformConfigChangedSchema = bsb.object({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adminApi.d.ts","sourceRoot":"","sources":["../../../src/plugins/service-betterportal-config-manager/adminApi.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EAEjB,mBAAmB,EAEpB,MAAM,yBAAyB,CAAC;AAUjC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"adminApi.d.ts","sourceRoot":"","sources":["../../../src/plugins/service-betterportal-config-manager/adminApi.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EAEjB,mBAAmB,EAEpB,MAAM,yBAAyB,CAAC;AAUjC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAmyBzD,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,iBAAiB,EACtB,KAAK,EAAE,mBAAmB,EAC1B,OAAO,EAAE,gBAAgB,GACxB,IAAI,CAgnCN"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { eventHeaders, htmlResponse, jsonResponse, resolveEmbeddedRequestContext, signServiceConfigTicket, uuidv7 } from "@betterportal/framework";
|
|
2
2
|
import { generateApiKey, hashApiKey } from "./storage/index.js";
|
|
3
3
|
import { getManifestCache } from "./syncApi.js";
|
|
4
|
+
import { apiRoutePath, isApiRoute } from "./routeMounts.js";
|
|
4
5
|
const API_BASE = "/.well-known/bp/admin";
|
|
5
6
|
const CONFIG_TICKET_TTL_SECONDS = 5 * 60;
|
|
6
7
|
// Parse-only base for relative request URLs. Never emit this origin.
|
|
@@ -14,6 +15,24 @@ async function readFormBody(event) {
|
|
|
14
15
|
out[k] = v; });
|
|
15
16
|
return out;
|
|
16
17
|
}
|
|
18
|
+
async function readFormBodyWithArrays(event) {
|
|
19
|
+
const fd = await event.req.formData().catch(() => null);
|
|
20
|
+
if (!fd)
|
|
21
|
+
return {};
|
|
22
|
+
const out = {};
|
|
23
|
+
fd.forEach((v, k) => {
|
|
24
|
+
if (typeof v !== "string")
|
|
25
|
+
return;
|
|
26
|
+
const existing = out[k];
|
|
27
|
+
if (Array.isArray(existing))
|
|
28
|
+
existing.push(v);
|
|
29
|
+
else if (typeof existing === "string")
|
|
30
|
+
out[k] = [existing, v];
|
|
31
|
+
else
|
|
32
|
+
out[k] = v;
|
|
33
|
+
});
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
17
36
|
function escapeHtml(s) {
|
|
18
37
|
return s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]));
|
|
19
38
|
}
|
|
@@ -26,7 +45,7 @@ async function readJsonBody(event) {
|
|
|
26
45
|
async function readFormOrJsonBody(event) {
|
|
27
46
|
const contentType = event.req.headers.get("content-type") ?? "";
|
|
28
47
|
if (contentType.includes("application/x-www-form-urlencoded") || contentType.includes("multipart/form-data")) {
|
|
29
|
-
return
|
|
48
|
+
return readFormBodyWithArrays(event);
|
|
30
49
|
}
|
|
31
50
|
return readJsonBody(event);
|
|
32
51
|
}
|
|
@@ -59,6 +78,12 @@ function routeMethodsFromManifest(methods) {
|
|
|
59
78
|
const normalized = (methods ?? []).filter((method) => method === "GET" || method === "POST" || method === "PUT" || method === "PATCH" || method === "DELETE" || method === "OPTIONS");
|
|
60
79
|
return normalized.length ? normalized : ["GET"];
|
|
61
80
|
}
|
|
81
|
+
function hasPageRenderer(view) {
|
|
82
|
+
const renderers = view.html?.themeRenderers;
|
|
83
|
+
if (!renderers)
|
|
84
|
+
return view.renderable !== false;
|
|
85
|
+
return Object.values(renderers).some((renderer) => Array.isArray(renderer.renderModes) && renderer.renderModes.includes("page"));
|
|
86
|
+
}
|
|
62
87
|
function appMatchesTenantUrl(app, tenantUrl) {
|
|
63
88
|
let host = "";
|
|
64
89
|
try {
|
|
@@ -187,7 +212,9 @@ function parseRouteCreateBody(body) {
|
|
|
187
212
|
return { error: viewId.error };
|
|
188
213
|
const manifestView = getManifestCache().get(serviceId.value)?.viewIndex[viewId.value];
|
|
189
214
|
const renderable = manifestView?.renderable !== false;
|
|
190
|
-
const
|
|
215
|
+
const manifest = getManifestCache().get(serviceId.value);
|
|
216
|
+
const serviceRoutePath = manifestView?.path ?? `/${viewId.value}`;
|
|
217
|
+
const path = renderable ? requiredRouteString(body, "path", "Mount path") : { value: apiRoutePath(manifest?.serviceId ?? serviceId.value, serviceRoutePath) };
|
|
191
218
|
if (path.error)
|
|
192
219
|
return { error: path.error };
|
|
193
220
|
if (!path.value.startsWith("/"))
|
|
@@ -196,6 +223,7 @@ function parseRouteCreateBody(body) {
|
|
|
196
223
|
if (title.error)
|
|
197
224
|
return { error: title.error };
|
|
198
225
|
const route = {
|
|
226
|
+
kind: renderable ? "page" : "api",
|
|
199
227
|
path: path.value,
|
|
200
228
|
serviceId: serviceId.value,
|
|
201
229
|
viewId: viewId.value,
|
|
@@ -281,7 +309,8 @@ function addRouteDependencies(appDef, route) {
|
|
|
281
309
|
continue;
|
|
282
310
|
appDef.routes.push({
|
|
283
311
|
id: uuidv7(),
|
|
284
|
-
|
|
312
|
+
kind: "api",
|
|
313
|
+
path: apiRoutePath(manifest.serviceId, dependency.path),
|
|
285
314
|
serviceId: route.serviceId,
|
|
286
315
|
viewId: dependencyViewId,
|
|
287
316
|
targetPath: dependency.path,
|
|
@@ -1424,15 +1453,20 @@ export function registerAdminApiRoutes(app, store, cpState) {
|
|
|
1424
1453
|
return validationError(event, "View is required.");
|
|
1425
1454
|
route.viewId = viewId;
|
|
1426
1455
|
}
|
|
1427
|
-
const
|
|
1456
|
+
const manifest = getManifestCache().get(route.serviceId);
|
|
1457
|
+
const manifestView = manifest?.viewIndex[route.viewId];
|
|
1428
1458
|
if (manifestView) {
|
|
1429
1459
|
route.methods = routeMethodsFromManifest(manifestView.methods);
|
|
1430
1460
|
route.targetPath = manifestView.path;
|
|
1431
1461
|
if (manifestView.renderable === false) {
|
|
1432
|
-
route.
|
|
1462
|
+
route.kind = "api";
|
|
1463
|
+
route.path = apiRoutePath(manifest?.serviceId ?? route.serviceId, manifestView.path);
|
|
1433
1464
|
route.title = manifestView.viewId;
|
|
1434
1465
|
delete route.query;
|
|
1435
1466
|
}
|
|
1467
|
+
else {
|
|
1468
|
+
route.kind = "page";
|
|
1469
|
+
}
|
|
1436
1470
|
}
|
|
1437
1471
|
if (body.targetPath !== undefined) {
|
|
1438
1472
|
const targetPath = trimmedString(body, "targetPath");
|
|
@@ -1443,7 +1477,9 @@ export function registerAdminApiRoutes(app, store, cpState) {
|
|
|
1443
1477
|
}
|
|
1444
1478
|
if (body.query !== undefined) {
|
|
1445
1479
|
const query = trimmedString(body, "query");
|
|
1446
|
-
if (
|
|
1480
|
+
if (isApiRoute(route, manifestView?.renderable))
|
|
1481
|
+
delete route.query;
|
|
1482
|
+
else if (query)
|
|
1447
1483
|
route.query = query.replace(/^\?+/, "");
|
|
1448
1484
|
else
|
|
1449
1485
|
delete route.query;
|
|
@@ -1452,7 +1488,8 @@ export function registerAdminApiRoutes(app, store, cpState) {
|
|
|
1452
1488
|
const title = trimmedString(body, "title");
|
|
1453
1489
|
if (!title)
|
|
1454
1490
|
return validationError(event, "Display title is required.");
|
|
1455
|
-
route
|
|
1491
|
+
if (!isApiRoute(route, manifestView?.renderable))
|
|
1492
|
+
route.title = title;
|
|
1456
1493
|
}
|
|
1457
1494
|
if (body.enabled !== undefined)
|
|
1458
1495
|
route.enabled = body.enabled === true || body.enabled === "true" || body.enabled === "on";
|
|
@@ -1530,7 +1567,7 @@ export function registerAdminApiRoutes(app, store, cpState) {
|
|
|
1530
1567
|
// Browser-mediated: the admin UI fetches the manifest from the service
|
|
1531
1568
|
// (CM cannot reach services) and POSTs the parsed payload here.
|
|
1532
1569
|
app.post(`${API_BASE}/wizard/verify`, async (event) => {
|
|
1533
|
-
const adminApiBase =
|
|
1570
|
+
const adminApiBase = API_BASE;
|
|
1534
1571
|
const form = await readFormBody(event);
|
|
1535
1572
|
const tenantId = form.tenantId ?? "";
|
|
1536
1573
|
const hostname = normalizeHostname(form.hostname);
|
|
@@ -1568,17 +1605,22 @@ export function registerAdminApiRoutes(app, store, cpState) {
|
|
|
1568
1605
|
});
|
|
1569
1606
|
// HTMX wizard: register service
|
|
1570
1607
|
app.post(`${API_BASE}/wizard/register`, async (event) => {
|
|
1571
|
-
const
|
|
1572
|
-
const
|
|
1573
|
-
|
|
1574
|
-
const
|
|
1608
|
+
const body = await readFormOrJsonBody(event);
|
|
1609
|
+
const jsonMode = (event.req.headers.get("accept") ?? "").includes("application/json")
|
|
1610
|
+
|| (event.req.headers.get("content-type") ?? "").includes("application/json");
|
|
1611
|
+
const registerError = (message, status = 400) => jsonMode
|
|
1612
|
+
? jsonResponse({ error: message }, status)
|
|
1613
|
+
: htmlResponse(`<div class="alert alert-danger">${escapeHtml(message)}</div>`, 200, "text/html; mode=fragment");
|
|
1614
|
+
const tenantId = typeof body.tenantId === "string" ? body.tenantId : "";
|
|
1615
|
+
const hostname = (typeof body.hostname === "string" ? body.hostname : "").replace(/\/+$/, "");
|
|
1616
|
+
const title = (typeof body.title === "string" && body.title.trim().length > 0 ? body.title.trim() : hostname);
|
|
1575
1617
|
if (!tenantId || !hostname) {
|
|
1576
|
-
return
|
|
1618
|
+
return registerError("Missing tenantId or hostname");
|
|
1577
1619
|
}
|
|
1578
1620
|
const config = await store.loadConfig();
|
|
1579
1621
|
const tenant = config.tenants.find((t) => t.id === tenantId);
|
|
1580
1622
|
if (!tenant) {
|
|
1581
|
-
return
|
|
1623
|
+
return registerError("Tenant not found", 404);
|
|
1582
1624
|
}
|
|
1583
1625
|
// Schema is fetched by the browser from the service (CM cannot reach services)
|
|
1584
1626
|
// and posted to us as a JSON string in form.schema.
|
|
@@ -1586,19 +1628,27 @@ export function registerAdminApiRoutes(app, store, cpState) {
|
|
|
1586
1628
|
let capabilities = [];
|
|
1587
1629
|
let serviceRoutes = [];
|
|
1588
1630
|
let viewMeta = new Map();
|
|
1589
|
-
|
|
1631
|
+
const schemaText = typeof body.schema === "string" ? body.schema : "";
|
|
1632
|
+
if (schemaText) {
|
|
1590
1633
|
try {
|
|
1591
|
-
const schema = JSON.parse(
|
|
1634
|
+
const schema = JSON.parse(schemaText);
|
|
1592
1635
|
serviceId = schema.manifest?.pluginId;
|
|
1593
1636
|
capabilities = Array.isArray(schema.manifest?.capabilities) ? schema.manifest.capabilities.filter((value) => typeof value === "string") : [];
|
|
1594
1637
|
serviceRoutes = schema.routes ?? [];
|
|
1595
|
-
viewMeta = new Map((schema.manifest?.views ?? []).map((v) => [v.viewId,
|
|
1638
|
+
viewMeta = new Map((schema.manifest?.views ?? []).map((v) => [v.viewId, {
|
|
1639
|
+
title: v.title,
|
|
1640
|
+
renderable: v.renderable !== false,
|
|
1641
|
+
pageRenderable: hasPageRenderer(v),
|
|
1642
|
+
methods: Array.isArray(v.methods)
|
|
1643
|
+
? v.methods.filter((value) => typeof value === "string")
|
|
1644
|
+
: []
|
|
1645
|
+
}]));
|
|
1596
1646
|
}
|
|
1597
1647
|
catch { /* malformed schema - register without routes */ }
|
|
1598
1648
|
}
|
|
1599
1649
|
const duplicate = duplicateTenantService(tenant, hostname, serviceId);
|
|
1600
1650
|
if (duplicate) {
|
|
1601
|
-
return
|
|
1651
|
+
return registerError(duplicate, 409);
|
|
1602
1652
|
}
|
|
1603
1653
|
const newServiceId = uuidv7();
|
|
1604
1654
|
const service = {
|
|
@@ -1621,30 +1671,37 @@ export function registerAdminApiRoutes(app, store, cpState) {
|
|
|
1621
1671
|
const groupChildren = [];
|
|
1622
1672
|
const existingPaths = new Set(appDef.routes.map((r) => r.path));
|
|
1623
1673
|
for (const r of serviceRoutes) {
|
|
1674
|
+
const meta = viewMeta.get(r.viewId);
|
|
1675
|
+
const renderable = meta?.renderable ?? r.renderable !== false;
|
|
1624
1676
|
// Default mounting path is the service's own path; skip collisions
|
|
1625
|
-
const mountPath =
|
|
1677
|
+
const mountPath = renderable
|
|
1678
|
+
? (existingPaths.has(r.path) ? `/${title.toLowerCase().replace(/[^a-z0-9]+/g, "-")}${r.path}` : r.path)
|
|
1679
|
+
: apiRoutePath(serviceId ?? newServiceId, r.path);
|
|
1626
1680
|
if (existingPaths.has(mountPath))
|
|
1627
1681
|
continue;
|
|
1628
1682
|
const routeId = uuidv7();
|
|
1629
|
-
const routeTitle =
|
|
1683
|
+
const routeTitle = meta?.title ?? r.viewId;
|
|
1630
1684
|
appDef.routes.push({
|
|
1631
1685
|
id: routeId,
|
|
1686
|
+
kind: renderable ? "page" : "api",
|
|
1632
1687
|
path: mountPath,
|
|
1633
1688
|
serviceId: newServiceId,
|
|
1634
1689
|
viewId: r.viewId,
|
|
1635
1690
|
targetPath: r.path,
|
|
1636
1691
|
title: routeTitle,
|
|
1637
1692
|
enabled: true,
|
|
1638
|
-
methods:
|
|
1693
|
+
methods: routeMethodsFromManifest(r.methods)
|
|
1639
1694
|
});
|
|
1640
1695
|
existingPaths.add(mountPath);
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1696
|
+
if (meta?.pageRenderable === true && !isApiRoute({ kind: renderable ? "page" : "api", path: mountPath }, renderable)) {
|
|
1697
|
+
groupChildren.push({
|
|
1698
|
+
id: uuidv7(),
|
|
1699
|
+
type: "link",
|
|
1700
|
+
title: routeTitle,
|
|
1701
|
+
routeId,
|
|
1702
|
+
enabled: true
|
|
1703
|
+
});
|
|
1704
|
+
}
|
|
1648
1705
|
}
|
|
1649
1706
|
if (groupChildren.length > 0) {
|
|
1650
1707
|
const menu = (appDef.menu ?? []);
|
|
@@ -1660,7 +1717,17 @@ export function registerAdminApiRoutes(app, store, cpState) {
|
|
|
1660
1717
|
}
|
|
1661
1718
|
}
|
|
1662
1719
|
await store.saveConfig(config);
|
|
1663
|
-
|
|
1720
|
+
if (jsonMode) {
|
|
1721
|
+
return jsonResponse({
|
|
1722
|
+
ok: true,
|
|
1723
|
+
tenantId,
|
|
1724
|
+
serviceInstanceId: newServiceId,
|
|
1725
|
+
serviceUrl: hostname,
|
|
1726
|
+
title,
|
|
1727
|
+
deploymentMode: service.deploymentMode
|
|
1728
|
+
});
|
|
1729
|
+
}
|
|
1730
|
+
const adminApiBase = API_BASE;
|
|
1664
1731
|
return htmlResponse(renderWizardStep3({
|
|
1665
1732
|
apiKey: "",
|
|
1666
1733
|
deploymentMode: service.deploymentMode,
|
|
@@ -1691,7 +1758,7 @@ export function registerAdminApiRoutes(app, store, cpState) {
|
|
|
1691
1758
|
const tenantId = url.searchParams.get("tenantId") ?? undefined;
|
|
1692
1759
|
const hostname = url.searchParams.get("hostname") ?? undefined;
|
|
1693
1760
|
const config = await store.loadConfig();
|
|
1694
|
-
return htmlResponse(renderWizardStep1(config, undefined, { tenantId, hostname },
|
|
1761
|
+
return htmlResponse(renderWizardStep1(config, undefined, { tenantId, hostname }, API_BASE), 200, "text/html; mode=fragment");
|
|
1695
1762
|
});
|
|
1696
1763
|
// HTMX configure: load form
|
|
1697
1764
|
app.get(`${API_BASE}/configure`, async (event) => {
|