@betterportal/config-manager 10.1.45 → 10.1.47
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/bp-contracts/org.betterportal.config-manager.json +1 -1
- package/lib/plugins/service-betterportal-config-manager/adminApi.d.ts +12 -0
- package/lib/plugins/service-betterportal-config-manager/adminApi.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/adminApi.js +93 -12
- package/lib/plugins/service-betterportal-config-manager/adminApi.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/bootstrapEndpoint.js +3 -3
- package/lib/plugins/service-betterportal-config-manager/bootstrapEndpoint.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/services/_renderer.bootstrap5/GET.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/services/_renderer.bootstrap5/GET.js +2 -2
- package/lib/plugins/service-betterportal-config-manager/bp-routes/services/_renderer.bootstrap5/GET.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/route.impl.js +3 -3
- package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/route.impl.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/storage/core.d.ts +1 -0
- package/lib/plugins/service-betterportal-config-manager/storage/core.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/storage/core.js +30 -1
- package/lib/plugins/service-betterportal-config-manager/storage/core.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/syncApi.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/syncApi.js +19 -17
- 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.1.
|
|
4
|
+
* @version 10.1.47
|
|
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.1.
|
|
4
|
+
* @version 10.1.47
|
|
5
5
|
*/
|
|
6
6
|
import { ServiceClient, bsb, createBroadcastEvent, createEventSchemas } from "@bsb/base";
|
|
7
7
|
const _platformConfigChangedSchema = bsb.object({
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"pluginId": "org.betterportal.config-manager",
|
|
5
5
|
"title": "BetterPortal Config Manager",
|
|
6
6
|
"description": "Admin-facing BetterPortal service for discovering and managing service config surfaces.",
|
|
7
|
-
"version": "10.1.
|
|
7
|
+
"version": "10.1.47",
|
|
8
8
|
"category": "service",
|
|
9
9
|
"deploymentModes": [
|
|
10
10
|
"self-hosted"
|
|
@@ -197,6 +197,18 @@ declare function automationServiceCatalog(config: BetterPortalConfig, appDef: Be
|
|
|
197
197
|
})[];
|
|
198
198
|
};
|
|
199
199
|
export declare function renderAutomationLlmsApi(catalog: ReturnType<typeof automationServiceCatalog>): string;
|
|
200
|
+
export declare function purgeServiceReferences(config: BetterPortalConfig, tenantId: string, serviceId: string): {
|
|
201
|
+
routesRemoved: number;
|
|
202
|
+
menuItemsRemoved: number;
|
|
203
|
+
slotsRemoved: number;
|
|
204
|
+
fragmentsRemoved: number;
|
|
205
|
+
roleGrantsRemoved: number;
|
|
206
|
+
shellCleared: number;
|
|
207
|
+
authCleared: number;
|
|
208
|
+
m2mBindingsRemoved: number;
|
|
209
|
+
m2mGrantsRemoved: number;
|
|
210
|
+
webhooksRemoved: number;
|
|
211
|
+
};
|
|
200
212
|
export declare function registerAdminApiRoutes(app: BetterPortalH3App, store: PlatformConfigStore, cpState: CpBootstrapState): void;
|
|
201
213
|
export {};
|
|
202
214
|
//# sourceMappingURL=adminApi.d.ts.map
|
|
@@ -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,EACnB,SAAS,EACV,MAAM,yBAAyB,CAAC;AAYjC,OAAO,KAAK,EAAuE,kBAAkB,EAAE,eAAe,EAA0C,MAAM,yBAAyB,CAAC;AAChM,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AA0LzD,iBAAS,wBAAwB,CAAC,MAAM,EAAE,kBAAkB,EAAE,MAAM,EAAE,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiEpF;AAGD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,UAAU,CAAC,OAAO,wBAAwB,CAAC,GAAG,MAAM,CA2CpG;
|
|
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,EACnB,SAAS,EACV,MAAM,yBAAyB,CAAC;AAYjC,OAAO,KAAK,EAAuE,kBAAkB,EAAE,eAAe,EAA0C,MAAM,yBAAyB,CAAC;AAChM,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AA0LzD,iBAAS,wBAAwB,CAAC,MAAM,EAAE,kBAAkB,EAAE,MAAM,EAAE,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiEpF;AAGD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,UAAU,CAAC,OAAO,wBAAwB,CAAC,GAAG,MAAM,CA2CpG;AA8SD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG;IACvG,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;CACzB,CAmEA;AAiPD,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,iBAAiB,EACtB,KAAK,EAAE,mBAAmB,EAC1B,OAAO,EAAE,gBAAgB,GACxB,IAAI,CA48CN"}
|
|
@@ -525,9 +525,19 @@ function collectServiceDeleteBlockers(config, tenantId, serviceId) {
|
|
|
525
525
|
}
|
|
526
526
|
}
|
|
527
527
|
}
|
|
528
|
+
for (const binding of config.m2m.bindings) {
|
|
529
|
+
if (binding.tenantId === tenantId && (binding.sourceServiceId === serviceId || binding.targetServiceId === serviceId)) {
|
|
530
|
+
blockers.push(`M2M binding ${binding.requestId} (${binding.id})`);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
for (const target of config.webhooks.targets) {
|
|
534
|
+
if (target.tenantId === tenantId && target.serviceId === serviceId) {
|
|
535
|
+
blockers.push(`webhook target ${target.eventId} (${target.id})`);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
528
538
|
return blockers;
|
|
529
539
|
}
|
|
530
|
-
function
|
|
540
|
+
export function purgeServiceReferences(config, tenantId, serviceId) {
|
|
531
541
|
const summary = {
|
|
532
542
|
routesRemoved: 0,
|
|
533
543
|
menuItemsRemoved: 0,
|
|
@@ -535,7 +545,10 @@ function purgeTenantServiceReferences(config, tenantId, serviceId) {
|
|
|
535
545
|
fragmentsRemoved: 0,
|
|
536
546
|
roleGrantsRemoved: 0,
|
|
537
547
|
shellCleared: 0,
|
|
538
|
-
authCleared: 0
|
|
548
|
+
authCleared: 0,
|
|
549
|
+
m2mBindingsRemoved: 0,
|
|
550
|
+
m2mGrantsRemoved: 0,
|
|
551
|
+
webhooksRemoved: 0
|
|
539
552
|
};
|
|
540
553
|
for (const app of config.apps.filter((candidate) => candidate.tenantId === tenantId)) {
|
|
541
554
|
if (app.shell?.serviceId === serviceId) {
|
|
@@ -572,6 +585,18 @@ function purgeTenantServiceReferences(config, tenantId, serviceId) {
|
|
|
572
585
|
delete app.fragments[location];
|
|
573
586
|
}
|
|
574
587
|
}
|
|
588
|
+
const bindingIds = new Set(config.m2m.bindings
|
|
589
|
+
.filter((binding) => binding.tenantId === tenantId && (binding.sourceServiceId === serviceId || binding.targetServiceId === serviceId))
|
|
590
|
+
.map((binding) => binding.id));
|
|
591
|
+
const bindingsBefore = config.m2m.bindings.length;
|
|
592
|
+
config.m2m.bindings = config.m2m.bindings.filter((binding) => !bindingIds.has(binding.id));
|
|
593
|
+
summary.m2mBindingsRemoved = bindingsBefore - config.m2m.bindings.length;
|
|
594
|
+
const grantsBefore = config.m2m.grants.length;
|
|
595
|
+
config.m2m.grants = config.m2m.grants.filter((grant) => !bindingIds.has(grant.bindingId));
|
|
596
|
+
summary.m2mGrantsRemoved = grantsBefore - config.m2m.grants.length;
|
|
597
|
+
const webhooksBefore = config.webhooks.targets.length;
|
|
598
|
+
config.webhooks.targets = config.webhooks.targets.filter((target) => target.tenantId !== tenantId || target.serviceId !== serviceId);
|
|
599
|
+
summary.webhooksRemoved = webhooksBefore - config.webhooks.targets.length;
|
|
575
600
|
return summary;
|
|
576
601
|
}
|
|
577
602
|
function sharedServiceIdFor(service, requested) {
|
|
@@ -742,23 +767,23 @@ function migrateTenantServiceToShared(config, tenantId, serviceId, options = {})
|
|
|
742
767
|
removedTenantService: shouldRemove
|
|
743
768
|
};
|
|
744
769
|
}
|
|
745
|
-
function linkedServiceError(blockers) {
|
|
746
|
-
return `Service is still linked and cannot be
|
|
770
|
+
function linkedServiceError(blockers, action = "deleted") {
|
|
771
|
+
return `Service is still linked and cannot be ${action}. Remove these references first: ${blockers.slice(0, 8).join("; ")}${blockers.length > 8 ? `; and ${blockers.length - 8} more` : ""}`;
|
|
747
772
|
}
|
|
748
|
-
function linkedServiceHtmxError(
|
|
773
|
+
function linkedServiceHtmxError(blockers, options) {
|
|
749
774
|
const items = blockers.slice(0, 8).map((blocker) => `<li>${escapeHtml(blocker)}</li>`).join("");
|
|
750
775
|
const more = blockers.length > 8 ? `<li>${blockers.length - 8} more references</li>` : "";
|
|
751
776
|
return htmlResponse(`
|
|
752
777
|
<div class="alert alert-warning">
|
|
753
|
-
<div class="fw-semibold mb-2"
|
|
778
|
+
<div class="fw-semibold mb-2">${escapeHtml(options.heading)}</div>
|
|
754
779
|
<ul class="mb-3">${items}${more}</ul>
|
|
755
780
|
<button
|
|
756
781
|
class="btn btn-sm btn-danger"
|
|
757
|
-
hx-post="${
|
|
782
|
+
hx-post="${escapeHtml(options.actionUrl)}"
|
|
758
783
|
hx-target="#bp-services-alerts"
|
|
759
784
|
hx-swap="innerHTML"
|
|
760
|
-
hx-confirm="
|
|
761
|
-
|
|
785
|
+
hx-confirm="${escapeHtml(options.confirm)}"
|
|
786
|
+
>${escapeHtml(options.buttonLabel)}</button>
|
|
762
787
|
</div>
|
|
763
788
|
`, 409, "text/html; mode=fragment");
|
|
764
789
|
}
|
|
@@ -1101,12 +1126,63 @@ export function registerAdminApiRoutes(app, store, cpState) {
|
|
|
1101
1126
|
if (!tenantId)
|
|
1102
1127
|
return wantsHtmx(event) ? htmxError("tenantId required") : jsonResponse({ error: "tenantId required" }, 400);
|
|
1103
1128
|
const config = await store.loadConfig();
|
|
1129
|
+
const activations = config.sharedServiceActivations.filter((activation) => activation.sharedServiceId === sharedServiceId
|
|
1130
|
+
&& activation.tenantId === tenantId
|
|
1131
|
+
&& activation.appId === appId);
|
|
1132
|
+
if (activations.length === 0) {
|
|
1133
|
+
return wantsHtmx(event) ? htmxError("Shared service activation not found", 404) : jsonResponse({ error: "Shared service activation not found" }, 404);
|
|
1134
|
+
}
|
|
1135
|
+
const blockers = activations.flatMap((activation) => collectServiceDeleteBlockers(config, tenantId, activation.id));
|
|
1136
|
+
if (blockers.length > 0) {
|
|
1137
|
+
const message = linkedServiceError(blockers, "deactivated");
|
|
1138
|
+
if (!wantsHtmx(event))
|
|
1139
|
+
return jsonResponse({ error: message, blockers }, 409);
|
|
1140
|
+
const query = new URLSearchParams({ tenantId });
|
|
1141
|
+
if (appId)
|
|
1142
|
+
query.set("appId", appId);
|
|
1143
|
+
return linkedServiceHtmxError(blockers, {
|
|
1144
|
+
heading: "Service is still linked and cannot be deactivated.",
|
|
1145
|
+
actionUrl: `${API_BASE}/shared-services/${encodeURIComponent(sharedServiceId)}/activations/purge?${query}`,
|
|
1146
|
+
confirm: "Purge all routes, menu entries, fragments, slots, auth links, role grants, M2M connections and webhooks for this activation, then deactivate it?",
|
|
1147
|
+
buttonLabel: "Purge references and deactivate service"
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1104
1150
|
config.sharedServiceActivations = config.sharedServiceActivations.filter((activation) => !(activation.sharedServiceId === sharedServiceId && activation.tenantId === tenantId && activation.appId === appId));
|
|
1105
1151
|
await store.saveConfig(config);
|
|
1106
1152
|
if (wantsHtmx(event))
|
|
1107
1153
|
return htmxReload(`/services?tenantId=${encodeURIComponent(tenantId)}`);
|
|
1108
1154
|
return jsonResponse({ ok: true });
|
|
1109
1155
|
});
|
|
1156
|
+
app.post(`${API_BASE}/shared-services/:sharedServiceId/activations/purge`, async (event) => {
|
|
1157
|
+
const sharedServiceId = getParam(event, "sharedServiceId");
|
|
1158
|
+
if (!sharedServiceId)
|
|
1159
|
+
return jsonResponse({ error: "sharedServiceId required" }, 400);
|
|
1160
|
+
const url = new URL(event.req.url ?? "", RELATIVE_URL_PARSE_BASE);
|
|
1161
|
+
const tenantId = url.searchParams.get("tenantId") ?? "";
|
|
1162
|
+
const appId = url.searchParams.get("appId") ?? undefined;
|
|
1163
|
+
if (!tenantId)
|
|
1164
|
+
return wantsHtmx(event) ? htmxError("tenantId required") : jsonResponse({ error: "tenantId required" }, 400);
|
|
1165
|
+
const config = await store.loadConfig();
|
|
1166
|
+
const activations = config.sharedServiceActivations.filter((activation) => activation.sharedServiceId === sharedServiceId
|
|
1167
|
+
&& activation.tenantId === tenantId
|
|
1168
|
+
&& activation.appId === appId);
|
|
1169
|
+
if (activations.length === 0) {
|
|
1170
|
+
return wantsHtmx(event) ? htmxError("Shared service activation not found", 404) : jsonResponse({ error: "Shared service activation not found" }, 404);
|
|
1171
|
+
}
|
|
1172
|
+
const summaries = activations.map((activation) => purgeServiceReferences(config, tenantId, activation.id));
|
|
1173
|
+
config.sharedServiceActivations = config.sharedServiceActivations.filter((activation) => !activations.includes(activation));
|
|
1174
|
+
await store.saveConfig(config);
|
|
1175
|
+
const removed = (key) => summaries.reduce((total, summary) => total + summary[key], 0);
|
|
1176
|
+
if (wantsHtmx(event)) {
|
|
1177
|
+
return htmlResponse(`
|
|
1178
|
+
<div class="alert alert-success">
|
|
1179
|
+
Shared service deactivated. Purged ${removed("routesRemoved")} route(s), ${removed("slotsRemoved")} slot(s), ${removed("fragmentsRemoved")} fragment(s), ${removed("roleGrantsRemoved")} role grant(s), ${removed("shellCleared")} shell binding(s), ${removed("authCleared")} auth binding(s), ${removed("m2mBindingsRemoved")} M2M connection(s), and ${removed("webhooksRemoved")} webhook(s).
|
|
1180
|
+
</div>
|
|
1181
|
+
<div hx-get="/services?tenantId=${encodeURIComponent(tenantId)}" hx-trigger="load" hx-target="#bp-main" hx-swap="innerHTML"></div>
|
|
1182
|
+
`, 200, "text/html; mode=fragment");
|
|
1183
|
+
}
|
|
1184
|
+
return jsonResponse({ ok: true, summaries });
|
|
1185
|
+
});
|
|
1110
1186
|
// Tenants
|
|
1111
1187
|
app.get(`${API_BASE}/tenants/:tenantId/services`, async (event) => {
|
|
1112
1188
|
const tenantId = getParam(event, "tenantId");
|
|
@@ -1166,7 +1242,12 @@ export function registerAdminApiRoutes(app, store, cpState) {
|
|
|
1166
1242
|
const blockers = collectServiceDeleteBlockers(config, tenantId, serviceId);
|
|
1167
1243
|
if (blockers.length > 0) {
|
|
1168
1244
|
const message = linkedServiceError(blockers);
|
|
1169
|
-
return wantsHtmx(event) ? linkedServiceHtmxError(
|
|
1245
|
+
return wantsHtmx(event) ? linkedServiceHtmxError(blockers, {
|
|
1246
|
+
heading: "Service is still linked and cannot be deleted.",
|
|
1247
|
+
actionUrl: `${API_BASE}/tenants/${encodeURIComponent(tenantId)}/services/${encodeURIComponent(serviceId)}/purge`,
|
|
1248
|
+
confirm: "Purge all routes, menu entries, fragments, slots, auth links, role grants, M2M connections and webhooks for this service, then delete it?",
|
|
1249
|
+
buttonLabel: "Purge references and delete service"
|
|
1250
|
+
}) : jsonResponse({ error: message, blockers }, 409);
|
|
1170
1251
|
}
|
|
1171
1252
|
tenant.services = tenant.services.filter((s) => s.id !== serviceId);
|
|
1172
1253
|
await store.saveConfig(config);
|
|
@@ -1190,13 +1271,13 @@ export function registerAdminApiRoutes(app, store, cpState) {
|
|
|
1190
1271
|
const service = tenant.services.find((candidate) => candidate.id === serviceId);
|
|
1191
1272
|
if (!service)
|
|
1192
1273
|
return wantsHtmx(event) ? htmxError("Service not found", 404) : jsonResponse({ error: "Service not found" }, 404);
|
|
1193
|
-
const summary =
|
|
1274
|
+
const summary = purgeServiceReferences(config, tenantId, serviceId);
|
|
1194
1275
|
tenant.services = tenant.services.filter((candidate) => candidate.id !== serviceId);
|
|
1195
1276
|
await store.saveConfig(config);
|
|
1196
1277
|
if (wantsHtmx(event)) {
|
|
1197
1278
|
return htmlResponse(`
|
|
1198
1279
|
<div class="alert alert-success">
|
|
1199
|
-
Service deleted. Purged ${summary.routesRemoved} route(s), ${summary.slotsRemoved} slot(s), ${summary.fragmentsRemoved} fragment(s), ${summary.roleGrantsRemoved} role grant(s), ${summary.shellCleared} shell binding(s),
|
|
1280
|
+
Service deleted. Purged ${summary.routesRemoved} route(s), ${summary.slotsRemoved} slot(s), ${summary.fragmentsRemoved} fragment(s), ${summary.roleGrantsRemoved} role grant(s), ${summary.shellCleared} shell binding(s), ${summary.authCleared} auth binding(s), ${summary.m2mBindingsRemoved} M2M connection(s), and ${summary.webhooksRemoved} webhook(s).
|
|
1200
1281
|
</div>
|
|
1201
1282
|
<div hx-get="/services?tenantId=${encodeURIComponent(tenantId)}" hx-trigger="load" hx-target="#bp-main" hx-swap="innerHTML"></div>
|
|
1202
1283
|
`, 200, "text/html; mode=fragment");
|