@betterportal/config-manager 10.0.31 → 10.1.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.
Files changed (49) hide show
  1. package/lib/.bsb/clients/service-betterportal-config-manager.d.ts +1 -1
  2. package/lib/.bsb/clients/service-betterportal-config-manager.js +1 -1
  3. package/lib/plugins/service-betterportal-config-manager/adminApi.d.ts.map +1 -1
  4. package/lib/plugins/service-betterportal-config-manager/adminApi.js +117 -31
  5. package/lib/plugins/service-betterportal-config-manager/adminApi.js.map +1 -1
  6. package/lib/plugins/service-betterportal-config-manager/bootstrapEndpoint.d.ts.map +1 -1
  7. package/lib/plugins/service-betterportal-config-manager/bootstrapEndpoint.js +15 -13
  8. package/lib/plugins/service-betterportal-config-manager/bootstrapEndpoint.js.map +1 -1
  9. package/lib/plugins/service-betterportal-config-manager/bp-routes/auth/_theme.bootstrap1/GET.d.ts.map +1 -1
  10. package/lib/plugins/service-betterportal-config-manager/bp-routes/auth/_theme.bootstrap1/GET.js +121 -6
  11. package/lib/plugins/service-betterportal-config-manager/bp-routes/auth/_theme.bootstrap1/GET.js.map +1 -1
  12. package/lib/plugins/service-betterportal-config-manager/bp-routes/routes/_theme.bootstrap1/GET.d.ts.map +1 -1
  13. package/lib/plugins/service-betterportal-config-manager/bp-routes/routes/_theme.bootstrap1/GET.js +36 -2
  14. package/lib/plugins/service-betterportal-config-manager/bp-routes/routes/_theme.bootstrap1/GET.js.map +1 -1
  15. package/lib/plugins/service-betterportal-config-manager/bp-routes/routes/route.impl.d.ts +6 -0
  16. package/lib/plugins/service-betterportal-config-manager/bp-routes/routes/route.impl.d.ts.map +1 -1
  17. package/lib/plugins/service-betterportal-config-manager/bp-routes/routes/route.impl.js +3 -0
  18. package/lib/plugins/service-betterportal-config-manager/bp-routes/routes/route.impl.js.map +1 -1
  19. package/lib/plugins/service-betterportal-config-manager/bp-routes/services/_theme.bootstrap1/GET.d.ts.map +1 -1
  20. package/lib/plugins/service-betterportal-config-manager/bp-routes/services/_theme.bootstrap1/GET.js +114 -31
  21. package/lib/plugins/service-betterportal-config-manager/bp-routes/services/_theme.bootstrap1/GET.js.map +1 -1
  22. package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/route.impl.d.ts.map +1 -1
  23. package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/route.impl.js +9 -4
  24. package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/route.impl.js.map +1 -1
  25. package/lib/plugins/service-betterportal-config-manager/index.d.ts +4 -0
  26. package/lib/plugins/service-betterportal-config-manager/index.d.ts.map +1 -1
  27. package/lib/plugins/service-betterportal-config-manager/index.js +45 -9
  28. package/lib/plugins/service-betterportal-config-manager/index.js.map +1 -1
  29. package/lib/plugins/service-betterportal-config-manager/menuEditor.d.ts.map +1 -1
  30. package/lib/plugins/service-betterportal-config-manager/menuEditor.js +7 -1
  31. package/lib/plugins/service-betterportal-config-manager/menuEditor.js.map +1 -1
  32. package/lib/plugins/service-betterportal-config-manager/routeMounts.d.ts +8 -0
  33. package/lib/plugins/service-betterportal-config-manager/routeMounts.d.ts.map +1 -0
  34. package/lib/plugins/service-betterportal-config-manager/routeMounts.js +22 -0
  35. package/lib/plugins/service-betterportal-config-manager/routeMounts.js.map +1 -0
  36. package/lib/plugins/service-betterportal-config-manager/setupTokens.d.ts.map +1 -1
  37. package/lib/plugins/service-betterportal-config-manager/setupTokens.js +50 -0
  38. package/lib/plugins/service-betterportal-config-manager/setupTokens.js.map +1 -1
  39. package/lib/plugins/service-betterportal-config-manager/storage/core.d.ts +2 -0
  40. package/lib/plugins/service-betterportal-config-manager/storage/core.d.ts.map +1 -1
  41. package/lib/plugins/service-betterportal-config-manager/storage/core.js +67 -2
  42. package/lib/plugins/service-betterportal-config-manager/storage/core.js.map +1 -1
  43. package/lib/plugins/service-betterportal-config-manager/syncApi.d.ts +2 -1
  44. package/lib/plugins/service-betterportal-config-manager/syncApi.d.ts.map +1 -1
  45. package/lib/plugins/service-betterportal-config-manager/syncApi.js +56 -1
  46. package/lib/plugins/service-betterportal-config-manager/syncApi.js.map +1 -1
  47. package/lib/schemas/service-betterportal-config-manager.json +1 -1
  48. package/lib/schemas/service-betterportal-config-manager.plugin.json +1 -1
  49. 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.31
4
+ * @version 10.1.1
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.31
4
+ * @version 10.1.1
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;AA4vBzD,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,iBAAiB,EACtB,KAAK,EAAE,mBAAmB,EAC1B,OAAO,EAAE,gBAAgB,GACxB,IAAI,CAwkCN"}
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;AA8yBzD,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,iBAAiB,EACtB,KAAK,EAAE,mBAAmB,EAC1B,OAAO,EAAE,gBAAgB,GACxB,IAAI,CAqnCN"}
@@ -1,10 +1,13 @@
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.
7
8
  const RELATIVE_URL_PARSE_BASE = "http://betterportal.invalid";
9
+ const ROLE_ID_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,63}$/;
10
+ const RESERVED_ROLE_IDS = new Set(["*", "root"]);
8
11
  async function readFormBody(event) {
9
12
  const fd = await event.req.formData().catch(() => null);
10
13
  if (!fd)
@@ -14,6 +17,24 @@ async function readFormBody(event) {
14
17
  out[k] = v; });
15
18
  return out;
16
19
  }
20
+ async function readFormBodyWithArrays(event) {
21
+ const fd = await event.req.formData().catch(() => null);
22
+ if (!fd)
23
+ return {};
24
+ const out = {};
25
+ fd.forEach((v, k) => {
26
+ if (typeof v !== "string")
27
+ return;
28
+ const existing = out[k];
29
+ if (Array.isArray(existing))
30
+ existing.push(v);
31
+ else if (typeof existing === "string")
32
+ out[k] = [existing, v];
33
+ else
34
+ out[k] = v;
35
+ });
36
+ return out;
37
+ }
17
38
  function escapeHtml(s) {
18
39
  return s.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c]));
19
40
  }
@@ -26,7 +47,7 @@ async function readJsonBody(event) {
26
47
  async function readFormOrJsonBody(event) {
27
48
  const contentType = event.req.headers.get("content-type") ?? "";
28
49
  if (contentType.includes("application/x-www-form-urlencoded") || contentType.includes("multipart/form-data")) {
29
- return readFormBody(event);
50
+ return readFormBodyWithArrays(event);
30
51
  }
31
52
  return readJsonBody(event);
32
53
  }
@@ -45,6 +66,18 @@ function htmxError(message, status = 400) {
45
66
  function validationError(event, message) {
46
67
  return wantsHtmx(event) ? htmxError(message, 400) : jsonResponse({ error: message }, 400);
47
68
  }
69
+ function validateRoleId(value) {
70
+ if (typeof value !== "string")
71
+ return undefined;
72
+ const roleId = value.trim();
73
+ if (!roleId)
74
+ return undefined;
75
+ if (!ROLE_ID_PATTERN.test(roleId))
76
+ return undefined;
77
+ if (RESERVED_ROLE_IDS.has(roleId.toLowerCase()))
78
+ return undefined;
79
+ return roleId;
80
+ }
48
81
  function trimmedString(body, key) {
49
82
  const value = body[key];
50
83
  return typeof value === "string" ? value.trim() : undefined;
@@ -59,6 +92,12 @@ function routeMethodsFromManifest(methods) {
59
92
  const normalized = (methods ?? []).filter((method) => method === "GET" || method === "POST" || method === "PUT" || method === "PATCH" || method === "DELETE" || method === "OPTIONS");
60
93
  return normalized.length ? normalized : ["GET"];
61
94
  }
95
+ function hasPageRenderer(view) {
96
+ const renderers = view.html?.themeRenderers;
97
+ if (!renderers)
98
+ return view.renderable !== false;
99
+ return Object.values(renderers).some((renderer) => Array.isArray(renderer.renderModes) && renderer.renderModes.includes("page"));
100
+ }
62
101
  function appMatchesTenantUrl(app, tenantUrl) {
63
102
  let host = "";
64
103
  try {
@@ -187,7 +226,9 @@ function parseRouteCreateBody(body) {
187
226
  return { error: viewId.error };
188
227
  const manifestView = getManifestCache().get(serviceId.value)?.viewIndex[viewId.value];
189
228
  const renderable = manifestView?.renderable !== false;
190
- const path = renderable ? requiredRouteString(body, "path", "Mount path") : { value: manifestView?.path ?? `/${viewId.value}` };
229
+ const manifest = getManifestCache().get(serviceId.value);
230
+ const serviceRoutePath = manifestView?.path ?? `/${viewId.value}`;
231
+ const path = renderable ? requiredRouteString(body, "path", "Mount path") : { value: apiRoutePath(manifest?.serviceId ?? serviceId.value, serviceRoutePath) };
191
232
  if (path.error)
192
233
  return { error: path.error };
193
234
  if (!path.value.startsWith("/"))
@@ -196,6 +237,7 @@ function parseRouteCreateBody(body) {
196
237
  if (title.error)
197
238
  return { error: title.error };
198
239
  const route = {
240
+ kind: renderable ? "page" : "api",
199
241
  path: path.value,
200
242
  serviceId: serviceId.value,
201
243
  viewId: viewId.value,
@@ -281,7 +323,8 @@ function addRouteDependencies(appDef, route) {
281
323
  continue;
282
324
  appDef.routes.push({
283
325
  id: uuidv7(),
284
- path: dependency.path,
326
+ kind: "api",
327
+ path: apiRoutePath(manifest.serviceId, dependency.path),
285
328
  serviceId: route.serviceId,
286
329
  viewId: dependencyViewId,
287
330
  targetPath: dependency.path,
@@ -1269,6 +1312,11 @@ export function registerAdminApiRoutes(app, store, cpState) {
1269
1312
  const title = body.title;
1270
1313
  if (!title)
1271
1314
  return wantsHtmx(event) ? htmxError("title required") : jsonResponse({ error: "title required" }, 400);
1315
+ const roleId = validateRoleId(body.id);
1316
+ if (!roleId) {
1317
+ const message = "role id must start with a letter or number, may contain letters, numbers, dot, underscore, colon, or dash, and cannot be reserved";
1318
+ return wantsHtmx(event) ? htmxError(message) : jsonResponse({ error: message }, 400);
1319
+ }
1272
1320
  const { config, appDef } = await getAppOr404(appId);
1273
1321
  if (!appDef)
1274
1322
  return wantsHtmx(event) ? htmxError("App not found", 404) : jsonResponse({ error: "App not found" }, 404);
@@ -1277,7 +1325,7 @@ export function registerAdminApiRoutes(app, store, cpState) {
1277
1325
  return authResult.response;
1278
1326
  const auth = authResult.auth;
1279
1327
  const role = {
1280
- id: body.id || uuidv7(),
1328
+ id: roleId,
1281
1329
  title,
1282
1330
  description: body.description,
1283
1331
  permissions: Array.isArray(body.permissions) ? body.permissions : []
@@ -1424,15 +1472,20 @@ export function registerAdminApiRoutes(app, store, cpState) {
1424
1472
  return validationError(event, "View is required.");
1425
1473
  route.viewId = viewId;
1426
1474
  }
1427
- const manifestView = getManifestCache().get(route.serviceId)?.viewIndex[route.viewId];
1475
+ const manifest = getManifestCache().get(route.serviceId);
1476
+ const manifestView = manifest?.viewIndex[route.viewId];
1428
1477
  if (manifestView) {
1429
1478
  route.methods = routeMethodsFromManifest(manifestView.methods);
1430
1479
  route.targetPath = manifestView.path;
1431
1480
  if (manifestView.renderable === false) {
1432
- route.path = manifestView.path;
1481
+ route.kind = "api";
1482
+ route.path = apiRoutePath(manifest?.serviceId ?? route.serviceId, manifestView.path);
1433
1483
  route.title = manifestView.viewId;
1434
1484
  delete route.query;
1435
1485
  }
1486
+ else {
1487
+ route.kind = "page";
1488
+ }
1436
1489
  }
1437
1490
  if (body.targetPath !== undefined) {
1438
1491
  const targetPath = trimmedString(body, "targetPath");
@@ -1443,7 +1496,9 @@ export function registerAdminApiRoutes(app, store, cpState) {
1443
1496
  }
1444
1497
  if (body.query !== undefined) {
1445
1498
  const query = trimmedString(body, "query");
1446
- if (query)
1499
+ if (isApiRoute(route, manifestView?.renderable))
1500
+ delete route.query;
1501
+ else if (query)
1447
1502
  route.query = query.replace(/^\?+/, "");
1448
1503
  else
1449
1504
  delete route.query;
@@ -1452,7 +1507,8 @@ export function registerAdminApiRoutes(app, store, cpState) {
1452
1507
  const title = trimmedString(body, "title");
1453
1508
  if (!title)
1454
1509
  return validationError(event, "Display title is required.");
1455
- route.title = title;
1510
+ if (!isApiRoute(route, manifestView?.renderable))
1511
+ route.title = title;
1456
1512
  }
1457
1513
  if (body.enabled !== undefined)
1458
1514
  route.enabled = body.enabled === true || body.enabled === "true" || body.enabled === "on";
@@ -1530,7 +1586,7 @@ export function registerAdminApiRoutes(app, store, cpState) {
1530
1586
  // Browser-mediated: the admin UI fetches the manifest from the service
1531
1587
  // (CM cannot reach services) and POSTs the parsed payload here.
1532
1588
  app.post(`${API_BASE}/wizard/verify`, async (event) => {
1533
- const adminApiBase = adminApiBaseFromControlPlane(cpState);
1589
+ const adminApiBase = API_BASE;
1534
1590
  const form = await readFormBody(event);
1535
1591
  const tenantId = form.tenantId ?? "";
1536
1592
  const hostname = normalizeHostname(form.hostname);
@@ -1568,17 +1624,22 @@ export function registerAdminApiRoutes(app, store, cpState) {
1568
1624
  });
1569
1625
  // HTMX wizard: register service
1570
1626
  app.post(`${API_BASE}/wizard/register`, async (event) => {
1571
- const form = await readFormBody(event);
1572
- const tenantId = form.tenantId ?? "";
1573
- const hostname = (form.hostname ?? "").replace(/\/+$/, "");
1574
- const title = form.title || hostname;
1627
+ const body = await readFormOrJsonBody(event);
1628
+ const jsonMode = (event.req.headers.get("accept") ?? "").includes("application/json")
1629
+ || (event.req.headers.get("content-type") ?? "").includes("application/json");
1630
+ const registerError = (message, status = 400) => jsonMode
1631
+ ? jsonResponse({ error: message }, status)
1632
+ : htmlResponse(`<div class="alert alert-danger">${escapeHtml(message)}</div>`, 200, "text/html; mode=fragment");
1633
+ const tenantId = typeof body.tenantId === "string" ? body.tenantId : "";
1634
+ const hostname = (typeof body.hostname === "string" ? body.hostname : "").replace(/\/+$/, "");
1635
+ const title = (typeof body.title === "string" && body.title.trim().length > 0 ? body.title.trim() : hostname);
1575
1636
  if (!tenantId || !hostname) {
1576
- return htmlResponse(`<div class="alert alert-danger">Missing tenantId or hostname</div>`, 200, "text/html; mode=fragment");
1637
+ return registerError("Missing tenantId or hostname");
1577
1638
  }
1578
1639
  const config = await store.loadConfig();
1579
1640
  const tenant = config.tenants.find((t) => t.id === tenantId);
1580
1641
  if (!tenant) {
1581
- return htmlResponse(`<div class="alert alert-danger">Tenant not found</div>`, 200, "text/html; mode=fragment");
1642
+ return registerError("Tenant not found", 404);
1582
1643
  }
1583
1644
  // Schema is fetched by the browser from the service (CM cannot reach services)
1584
1645
  // and posted to us as a JSON string in form.schema.
@@ -1586,19 +1647,27 @@ export function registerAdminApiRoutes(app, store, cpState) {
1586
1647
  let capabilities = [];
1587
1648
  let serviceRoutes = [];
1588
1649
  let viewMeta = new Map();
1589
- if (form.schema) {
1650
+ const schemaText = typeof body.schema === "string" ? body.schema : "";
1651
+ if (schemaText) {
1590
1652
  try {
1591
- const schema = JSON.parse(form.schema);
1653
+ const schema = JSON.parse(schemaText);
1592
1654
  serviceId = schema.manifest?.pluginId;
1593
1655
  capabilities = Array.isArray(schema.manifest?.capabilities) ? schema.manifest.capabilities.filter((value) => typeof value === "string") : [];
1594
1656
  serviceRoutes = schema.routes ?? [];
1595
- viewMeta = new Map((schema.manifest?.views ?? []).map((v) => [v.viewId, v.title]));
1657
+ viewMeta = new Map((schema.manifest?.views ?? []).map((v) => [v.viewId, {
1658
+ title: v.title,
1659
+ renderable: v.renderable !== false,
1660
+ pageRenderable: hasPageRenderer(v),
1661
+ methods: Array.isArray(v.methods)
1662
+ ? v.methods.filter((value) => typeof value === "string")
1663
+ : []
1664
+ }]));
1596
1665
  }
1597
1666
  catch { /* malformed schema - register without routes */ }
1598
1667
  }
1599
1668
  const duplicate = duplicateTenantService(tenant, hostname, serviceId);
1600
1669
  if (duplicate) {
1601
- return htmlResponse(`<div class="alert alert-warning">${escapeHtml(duplicate)}</div>`, 200, "text/html; mode=fragment");
1670
+ return registerError(duplicate, 409);
1602
1671
  }
1603
1672
  const newServiceId = uuidv7();
1604
1673
  const service = {
@@ -1621,30 +1690,37 @@ export function registerAdminApiRoutes(app, store, cpState) {
1621
1690
  const groupChildren = [];
1622
1691
  const existingPaths = new Set(appDef.routes.map((r) => r.path));
1623
1692
  for (const r of serviceRoutes) {
1693
+ const meta = viewMeta.get(r.viewId);
1694
+ const renderable = meta?.renderable ?? r.renderable !== false;
1624
1695
  // Default mounting path is the service's own path; skip collisions
1625
- const mountPath = existingPaths.has(r.path) ? `/${title.toLowerCase().replace(/[^a-z0-9]+/g, "-")}${r.path}` : r.path;
1696
+ const mountPath = renderable
1697
+ ? (existingPaths.has(r.path) ? `/${title.toLowerCase().replace(/[^a-z0-9]+/g, "-")}${r.path}` : r.path)
1698
+ : apiRoutePath(serviceId ?? newServiceId, r.path);
1626
1699
  if (existingPaths.has(mountPath))
1627
1700
  continue;
1628
1701
  const routeId = uuidv7();
1629
- const routeTitle = viewMeta.get(r.viewId) ?? r.viewId;
1702
+ const routeTitle = meta?.title ?? r.viewId;
1630
1703
  appDef.routes.push({
1631
1704
  id: routeId,
1705
+ kind: renderable ? "page" : "api",
1632
1706
  path: mountPath,
1633
1707
  serviceId: newServiceId,
1634
1708
  viewId: r.viewId,
1635
1709
  targetPath: r.path,
1636
1710
  title: routeTitle,
1637
1711
  enabled: true,
1638
- methods: ["GET"]
1712
+ methods: routeMethodsFromManifest(r.methods)
1639
1713
  });
1640
1714
  existingPaths.add(mountPath);
1641
- groupChildren.push({
1642
- id: uuidv7(),
1643
- type: "link",
1644
- title: routeTitle,
1645
- routeId,
1646
- enabled: true
1647
- });
1715
+ if (meta?.pageRenderable === true && !isApiRoute({ kind: renderable ? "page" : "api", path: mountPath }, renderable)) {
1716
+ groupChildren.push({
1717
+ id: uuidv7(),
1718
+ type: "link",
1719
+ title: routeTitle,
1720
+ routeId,
1721
+ enabled: true
1722
+ });
1723
+ }
1648
1724
  }
1649
1725
  if (groupChildren.length > 0) {
1650
1726
  const menu = (appDef.menu ?? []);
@@ -1660,7 +1736,17 @@ export function registerAdminApiRoutes(app, store, cpState) {
1660
1736
  }
1661
1737
  }
1662
1738
  await store.saveConfig(config);
1663
- const adminApiBase = adminApiBaseFromControlPlane(cpState);
1739
+ if (jsonMode) {
1740
+ return jsonResponse({
1741
+ ok: true,
1742
+ tenantId,
1743
+ serviceInstanceId: newServiceId,
1744
+ serviceUrl: hostname,
1745
+ title,
1746
+ deploymentMode: service.deploymentMode
1747
+ });
1748
+ }
1749
+ const adminApiBase = API_BASE;
1664
1750
  return htmlResponse(renderWizardStep3({
1665
1751
  apiKey: "",
1666
1752
  deploymentMode: service.deploymentMode,
@@ -1691,7 +1777,7 @@ export function registerAdminApiRoutes(app, store, cpState) {
1691
1777
  const tenantId = url.searchParams.get("tenantId") ?? undefined;
1692
1778
  const hostname = url.searchParams.get("hostname") ?? undefined;
1693
1779
  const config = await store.loadConfig();
1694
- return htmlResponse(renderWizardStep1(config, undefined, { tenantId, hostname }, adminApiBaseFromControlPlane(cpState)), 200, "text/html; mode=fragment");
1780
+ return htmlResponse(renderWizardStep1(config, undefined, { tenantId, hostname }, API_BASE), 200, "text/html; mode=fragment");
1695
1781
  });
1696
1782
  // HTMX configure: load form
1697
1783
  app.get(`${API_BASE}/configure`, async (event) => {