@betterportal/config-manager 10.6.31 → 10.6.32
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 +3 -2
- package/lib/plugins/service-betterportal-config-manager/adminApi.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/adminApi.js +58 -36
- package/lib/plugins/service-betterportal-config-manager/adminApi.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/appQueries.d.ts +976 -0
- package/lib/plugins/service-betterportal-config-manager/appQueries.d.ts.map +1 -0
- package/lib/plugins/service-betterportal-config-manager/appQueries.js +330 -0
- package/lib/plugins/service-betterportal-config-manager/appQueries.js.map +1 -0
- package/lib/plugins/service-betterportal-config-manager/index.d.ts +4 -0
- package/lib/plugins/service-betterportal-config-manager/index.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/index.js +70 -22
- package/lib/plugins/service-betterportal-config-manager/index.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/m2mConnections.d.ts +6 -3
- package/lib/plugins/service-betterportal-config-manager/m2mConnections.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/m2mConnections.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/menuEditor.d.ts +2 -1
- package/lib/plugins/service-betterportal-config-manager/menuEditor.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/menuEditor.js +131 -160
- package/lib/plugins/service-betterportal-config-manager/menuEditor.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/previewEnvironments.d.ts +1 -1
- package/lib/plugins/service-betterportal-config-manager/previewEnvironments.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/previewEnvironments.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/routeContext.d.ts +2 -0
- package/lib/plugins/service-betterportal-config-manager/routeContext.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/routeContext.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/storage/appData.d.ts +1 -1
- package/lib/plugins/service-betterportal-config-manager/storage/appData.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/storage/appData.js +4 -4
- package/lib/plugins/service-betterportal-config-manager/storage/appData.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/storage/core.d.ts +5 -3
- package/lib/plugins/service-betterportal-config-manager/storage/core.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/storage/core.js +1 -1
- package/lib/plugins/service-betterportal-config-manager/storage/core.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/storage/entities.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/storage/entities.js +4 -2
- package/lib/plugins/service-betterportal-config-manager/storage/entities.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/storage/postgres.d.ts +14 -0
- package/lib/plugins/service-betterportal-config-manager/storage/postgres.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/storage/postgres.js +11 -0
- package/lib/plugins/service-betterportal-config-manager/storage/postgres.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/syncApi.d.ts +4 -3
- package/lib/plugins/service-betterportal-config-manager/syncApi.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/syncApi.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/tenantManagement.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/tenantManagement.js +3 -1
- package/lib/plugins/service-betterportal-config-manager/tenantManagement.js.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/webhooks.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/webhooks.js +147 -54
- package/lib/plugins/service-betterportal-config-manager/webhooks.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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"appQueries.d.ts","sourceRoot":"","sources":["../../../src/plugins/service-betterportal-config-manager/appQueries.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from "node:util";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { BetterPortalAppSchema, BetterPortalTenantSchema, BetterPortalConfigSchema, resolveEmbeddedRequestContext } from "@betterportal/framework";
|
|
4
|
+
import { getAvailableServiceInstanceIdsForApp, getServicePluginId, resolveAuthProviderRuntimeMetadata } from "./storage/core.js";
|
|
5
|
+
import { AppDataConflictError, appSettings } from "./storage/appData.js";
|
|
6
|
+
export async function readAppEditor(client, db, appId) {
|
|
7
|
+
const rows = await db.appData.read(client, db.entities, appId);
|
|
8
|
+
const app = rows.entities[0]?.value;
|
|
9
|
+
const empty = { apps: [], tenants: [], platformServices: [], sharedServiceCatalog: [], sharedServiceActivations: [], previewEnvironmentDeployments: [] };
|
|
10
|
+
if (!app)
|
|
11
|
+
return empty;
|
|
12
|
+
db.appData.hydrate([app], rows);
|
|
13
|
+
const result = await client.query(`with tenant as (
|
|
14
|
+
select value from ${db.entities} where scope_id=$1 and kind='tenants' and entity_id=$2
|
|
15
|
+
), activations as (
|
|
16
|
+
select value from ${db.entities} where scope_id=$1 and kind='sharedServiceActivations'
|
|
17
|
+
and value->>'tenantId'=$2 and (value->>'appId' is null or value->>'appId'=$3)
|
|
18
|
+
) select kind, entity_id, value from ${db.entities} where scope_id=$1 and (
|
|
19
|
+
(kind='tenants' and entity_id=$2)
|
|
20
|
+
or (kind='previewEnvironmentDeployments' and value->>'appId'=$3)
|
|
21
|
+
or (kind='tenantServices' and value->>'tenantId'=$2)
|
|
22
|
+
or (kind='platformServices' and entity_id in (select jsonb_array_elements_text(value->'activatedPlatformServices') from tenant))
|
|
23
|
+
or (kind='sharedServiceActivations' and value in (select value from activations))
|
|
24
|
+
or (kind='sharedServiceCatalog' and entity_id in (select value->>'sharedServiceId' from activations))) order by ordinal`, [db.scope, app.tenantId, appId]);
|
|
25
|
+
const values = (kind) => result.rows.filter(row => row.kind === kind).map(row => row.value);
|
|
26
|
+
const tenant = values("tenants")[0];
|
|
27
|
+
if (!tenant)
|
|
28
|
+
throw new Error("App tenant is missing");
|
|
29
|
+
tenant.services = values("tenantServices").map(({ tenantId: _tenantId, ...service }) => service);
|
|
30
|
+
return { apps: [BetterPortalAppSchema.parse(app)], tenants: [BetterPortalTenantSchema.parse(tenant)],
|
|
31
|
+
platformServices: values("platformServices"), sharedServiceCatalog: values("sharedServiceCatalog"),
|
|
32
|
+
sharedServiceActivations: values("sharedServiceActivations"), previewEnvironmentDeployments: values("previewEnvironmentDeployments") };
|
|
33
|
+
}
|
|
34
|
+
/** A short transaction over one app's relational collections. No platform snapshots. */
|
|
35
|
+
export async function editAppRows(db, appId, obs, change) {
|
|
36
|
+
const span = obs?.startSpan("bp.app.edit");
|
|
37
|
+
const waiting = span?.startSpan("bp.app.pool_wait");
|
|
38
|
+
let client;
|
|
39
|
+
try {
|
|
40
|
+
client = await db.pool.connect();
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
span?.end();
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
waiting?.end();
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
await client.query("begin");
|
|
51
|
+
await client.query("set local betterportal.app_data_writer = '3'");
|
|
52
|
+
const lock = span?.startSpan("bp.app.lock");
|
|
53
|
+
try {
|
|
54
|
+
await client.query("select pg_advisory_xact_lock(hashtextextended($1,0))", [JSON.stringify([db.tableName, db.scope, JSON.stringify(["apps", appId])])]);
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
lock?.end();
|
|
58
|
+
}
|
|
59
|
+
const reading = span?.startSpan("bp.app.read");
|
|
60
|
+
let data;
|
|
61
|
+
try {
|
|
62
|
+
data = await readAppEditor(client, db, appId);
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
reading?.end();
|
|
66
|
+
}
|
|
67
|
+
const before = data.apps[0] && structuredClone(data.apps[0]);
|
|
68
|
+
const availableAtRead = before ? getAvailableServiceInstanceIdsForApp(data, before) : new Set();
|
|
69
|
+
const result = await change(data);
|
|
70
|
+
if (result instanceof Response && !result.ok) {
|
|
71
|
+
await client.query("rollback");
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
if (!before) {
|
|
75
|
+
await client.query("rollback");
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
const after = BetterPortalAppSchema.parse(data.apps[0]);
|
|
79
|
+
if (before.auth?.roles.some(role => role.id === "*") && !after.auth?.roles.some(role => role.id === "*")) {
|
|
80
|
+
throw new Error("The reserved platform root role cannot be removed");
|
|
81
|
+
}
|
|
82
|
+
for (const route of after.routes) {
|
|
83
|
+
for (const path of [route.path, route.targetPath, route.servicePathVariant, route.resolvedServicePath]) {
|
|
84
|
+
if (path && /\{[^}]+\}/.test(path))
|
|
85
|
+
throw new Error("Route paths must use :param syntax");
|
|
86
|
+
}
|
|
87
|
+
for (const [name, value] of Object.entries(route.fixedParams ?? {})) {
|
|
88
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name) || value.length < 1 || value.length > 100)
|
|
89
|
+
throw new Error("Invalid fixed route parameter");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (!isDeepStrictEqual(appSettings(before), appSettings(after)))
|
|
93
|
+
throw new Error("App data edit cannot modify app settings");
|
|
94
|
+
if (db.appData.changed(before, after)) {
|
|
95
|
+
const writing = span?.startSpan("bp.app.write");
|
|
96
|
+
try {
|
|
97
|
+
// Existing writers use the same dependency lock names; refresh allocations after acquiring them.
|
|
98
|
+
const refs = [
|
|
99
|
+
...data.tenants.map(value => ["tenants", value.id]),
|
|
100
|
+
...data.tenants.flatMap(tenant => tenant.services.map(value => ["tenantServices", value.id])),
|
|
101
|
+
...data.platformServices.map(value => ["platformServices", value.id]),
|
|
102
|
+
...data.sharedServiceCatalog.map(value => ["sharedServiceCatalog", value.id]),
|
|
103
|
+
...data.sharedServiceActivations.map(value => ["sharedServiceActivations", value.id])
|
|
104
|
+
];
|
|
105
|
+
await client.query(`select pg_advisory_xact_lock_shared(hashtextextended(k,0)) from unnest($1::text[]) k order by k`, [refs.map(ref => JSON.stringify([db.tableName, db.scope, JSON.stringify(ref)])).sort()]);
|
|
106
|
+
const current = await readAppEditor(client, db, appId);
|
|
107
|
+
const allowed = getAvailableServiceInstanceIdsForApp(current, after);
|
|
108
|
+
for (const serviceId of [
|
|
109
|
+
...after.routes.map(route => route.serviceId),
|
|
110
|
+
...(after.auth?.roles ?? []).flatMap(role => role.permissions.map(grant => grant.serviceId)),
|
|
111
|
+
...(after.auth?.serviceId ? [after.auth.serviceId] : []),
|
|
112
|
+
...(after.shell?.serviceId ? [after.shell.serviceId] : []),
|
|
113
|
+
...after.slots.map(slot => slot.serviceId),
|
|
114
|
+
...Object.values(after.fragments).flatMap(fragments => fragments.map(fragment => fragment.serviceId)),
|
|
115
|
+
...Object.values(after.auth?.redirects ?? {}).flatMap(target => target ? [target.serviceId] : [])
|
|
116
|
+
]) {
|
|
117
|
+
// A dependency absent from the original read has no lock/reference in this transaction.
|
|
118
|
+
if (!allowed.has(serviceId))
|
|
119
|
+
throw new Error("App references an unavailable service");
|
|
120
|
+
if (!availableAtRead.has(serviceId))
|
|
121
|
+
throw new AppDataConflictError();
|
|
122
|
+
}
|
|
123
|
+
await db.appData.save(client, appId, before, after);
|
|
124
|
+
const used = new Set();
|
|
125
|
+
const visit = (value) => {
|
|
126
|
+
if (!value || typeof value !== "object")
|
|
127
|
+
return;
|
|
128
|
+
for (const [key, child] of Object.entries(value)) {
|
|
129
|
+
if (key === "serviceId" && typeof child === "string")
|
|
130
|
+
used.add(child);
|
|
131
|
+
else
|
|
132
|
+
visit(child);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
visit(after);
|
|
136
|
+
await client.query(`delete from ${db.references} where scope_id=$1 and kind='apps' and entity_id=$2
|
|
137
|
+
and target_kind in ('tenantServices','platformServices','sharedServiceCatalog','sharedServiceActivations')`, [db.scope, appId]);
|
|
138
|
+
for (const [kind, id] of refs)
|
|
139
|
+
if (used.has(id))
|
|
140
|
+
await client.query(`insert into ${db.references} (scope_id,kind,entity_id,target_kind,target_id) values ($1,'apps',$2,$3,$4) on conflict do nothing`, [db.scope, appId, kind, id]);
|
|
141
|
+
await client.query(`insert into ${db.outbox} (scope_id,id,delivery,event_name,payload)
|
|
142
|
+
values ($1,$2,'broadcast','platform-config.changed',jsonb_build_object('revision',nextval('${db.revisions}'),'appId',$3::text))`, [db.scope, randomUUID(), appId]);
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
writing?.end();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
await client.query("commit");
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
await client.query("rollback").catch(() => undefined);
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
client.release();
|
|
157
|
+
span?.end();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/** Management request resolution reads app settings first, then only the matched app's rows. */
|
|
161
|
+
export async function readRequestConfig(db, headers, obs) {
|
|
162
|
+
const span = obs?.startSpan("bp.request_context.read");
|
|
163
|
+
let client;
|
|
164
|
+
try {
|
|
165
|
+
client = await db.pool.connect();
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
span?.end();
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
await client.query("begin isolation level repeatable read read only");
|
|
173
|
+
const result = await client.query(`with settings as (
|
|
174
|
+
select value from ${db.entities} where scope_id=$1 and kind='settings' and entity_id='platform'
|
|
175
|
+
) select kind,value from ${db.entities} where scope_id=$1 and (
|
|
176
|
+
kind='settings'
|
|
177
|
+
or (kind='apps' and value->>'tenantId'=coalesce((select value->>'adminTenantId' from settings),'betterportal'))
|
|
178
|
+
or (kind='tenants' and entity_id=coalesce((select value->>'adminTenantId' from settings),'betterportal'))) order by ordinal`, [db.scope]);
|
|
179
|
+
const selector = BetterPortalConfigSchema.parse({
|
|
180
|
+
configManagement: result.rows.find(row => row.kind === "settings")?.value,
|
|
181
|
+
apps: result.rows.filter(row => row.kind === "apps").map(row => row.value),
|
|
182
|
+
tenants: result.rows.filter(row => row.kind === "tenants").map(row => row.value)
|
|
183
|
+
});
|
|
184
|
+
const selected = resolveEmbeddedRequestContext(selector, headers);
|
|
185
|
+
if (!selected) {
|
|
186
|
+
await client.query("commit");
|
|
187
|
+
return selector;
|
|
188
|
+
}
|
|
189
|
+
const data = await readAppEditor(client, db, selected.app.id);
|
|
190
|
+
const config = { ...selector, ...data };
|
|
191
|
+
const app = config.apps[0];
|
|
192
|
+
if (app.auth) {
|
|
193
|
+
const provider = resolveAuthProviderRuntimeMetadata(config, app.auth.serviceId);
|
|
194
|
+
if (provider)
|
|
195
|
+
Object.assign(app.auth, { expectedIssuer: provider.issuer, expectedAudience: provider.audience,
|
|
196
|
+
jwksUri: provider.jwksUri, ...(provider.publicKeys ? { publicKeys: provider.publicKeys } : {}) });
|
|
197
|
+
}
|
|
198
|
+
if (app.shell) {
|
|
199
|
+
const activation = data.sharedServiceActivations.find(candidate => candidate.id === app.shell.serviceId);
|
|
200
|
+
const manifests = await client.query(`select value from ${db.entities} where scope_id=$1 and kind='manifestCache'
|
|
201
|
+
and entity_id=any($2::text[]) order by ordinal`, [db.scope, [app.shell.serviceId, activation?.sharedServiceId, getServicePluginId(data, app.shell.serviceId)].filter(Boolean)]);
|
|
202
|
+
config.manifestCache = manifests.rows.map(row => row.value);
|
|
203
|
+
}
|
|
204
|
+
await client.query("commit");
|
|
205
|
+
return config;
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
await client.query("rollback").catch(() => undefined);
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
211
|
+
finally {
|
|
212
|
+
client.release();
|
|
213
|
+
span?.end();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
export async function readAppPage(db, appId, chooseFirst = false) {
|
|
217
|
+
const client = await db.pool.connect();
|
|
218
|
+
try {
|
|
219
|
+
await client.query("begin isolation level repeatable read read only");
|
|
220
|
+
const result = await client.query(`select entity_id as id,value->>'tenantId' as "tenantId",value->>'title' as title from ${db.entities} app
|
|
221
|
+
where scope_id=$1 and kind='apps' and not exists (
|
|
222
|
+
select 1 from ${db.entities} preview where preview.scope_id=app.scope_id and preview.kind='previewEnvironmentDeployments'
|
|
223
|
+
and preview.value->>'appId'=app.entity_id) order by ordinal`, [db.scope]);
|
|
224
|
+
const selected = result.rows.find(app => app.id === appId)?.id ?? (!appId && chooseFirst ? result.rows[0]?.id : undefined);
|
|
225
|
+
const data = await readAppEditor(client, db, selected ?? "");
|
|
226
|
+
await client.query("commit");
|
|
227
|
+
return { ...data, appChoices: result.rows };
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
await client.query("rollback").catch(() => undefined);
|
|
231
|
+
throw error;
|
|
232
|
+
}
|
|
233
|
+
finally {
|
|
234
|
+
client.release();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/** Caches need service manifests and auth grants, never menus, routes or preview snapshots. */
|
|
238
|
+
export async function readCacheData(db) {
|
|
239
|
+
const client = await db.pool.connect();
|
|
240
|
+
try {
|
|
241
|
+
await client.query("begin isolation level repeatable read read only");
|
|
242
|
+
const result = await client.query(`select kind,value from ${db.entities} where scope_id=$1
|
|
243
|
+
and kind in ('apps','tenants','tenantServices','platformServices','sharedServiceCatalog','sharedServiceActivations','settings','manifestCache') order by ordinal`, [db.scope]);
|
|
244
|
+
const values = (kind) => result.rows.filter(row => row.kind === kind).map(row => row.value);
|
|
245
|
+
const apps = values("apps");
|
|
246
|
+
const roles = await client.query(`select app_id,id,title,description,position from ${db.appData.roles} where scope_id=$1`, [db.scope]);
|
|
247
|
+
const grants = await client.query(`select app_id,role_id,service_id,view_id,actions,grant_position from ${db.appData.grants} where scope_id=$1`, [db.scope]);
|
|
248
|
+
db.appData.hydrate(apps, { roles: roles.rows, grants: grants.rows, routes: [], menus: [] });
|
|
249
|
+
const tenants = values("tenants");
|
|
250
|
+
const byId = new Map(tenants.map(tenant => [tenant.id, tenant]));
|
|
251
|
+
for (const { tenantId, ...service } of values("tenantServices"))
|
|
252
|
+
byId.get(tenantId)?.services.push(service);
|
|
253
|
+
const data = { apps: apps.map(app => BetterPortalAppSchema.parse(app)), tenants,
|
|
254
|
+
platformServices: values("platformServices"), sharedServiceCatalog: values("sharedServiceCatalog"),
|
|
255
|
+
sharedServiceActivations: values("sharedServiceActivations"), configManagement: values("settings")[0],
|
|
256
|
+
manifestCache: values("manifestCache") };
|
|
257
|
+
for (const app of data.apps)
|
|
258
|
+
if (app.auth) {
|
|
259
|
+
const provider = resolveAuthProviderRuntimeMetadata(data, app.auth.serviceId);
|
|
260
|
+
if (provider)
|
|
261
|
+
Object.assign(app.auth, { expectedIssuer: provider.issuer, expectedAudience: provider.audience,
|
|
262
|
+
jwksUri: provider.jwksUri, ...(provider.publicKeys ? { publicKeys: provider.publicKeys } : {}) });
|
|
263
|
+
}
|
|
264
|
+
await client.query("commit");
|
|
265
|
+
return data;
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
await client.query("rollback").catch(() => undefined);
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
finally {
|
|
272
|
+
client.release();
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
export async function readApp(db, appId) {
|
|
276
|
+
const client = await db.pool.connect();
|
|
277
|
+
try {
|
|
278
|
+
await client.query("begin isolation level repeatable read read only");
|
|
279
|
+
const data = await readAppEditor(client, db, appId);
|
|
280
|
+
await client.query("commit");
|
|
281
|
+
return data;
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
await client.query("rollback").catch(() => undefined);
|
|
285
|
+
throw error;
|
|
286
|
+
}
|
|
287
|
+
finally {
|
|
288
|
+
client.release();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/** Service/settings pages need registration summaries, not app operational collections. */
|
|
292
|
+
export async function readDirectory(db) {
|
|
293
|
+
const result = await db.pool.query(`with previews as (
|
|
294
|
+
select value->>'tenantId' as tenant_id,value->>'appId' as app_id from ${db.entities}
|
|
295
|
+
where scope_id=$1 and kind='previewEnvironmentDeployments'
|
|
296
|
+
) select kind,value from ${db.entities} e where scope_id=$1
|
|
297
|
+
and kind in ('settings','apps','tenants','tenantServices','platformServices','sharedServiceCatalog','sharedServiceActivations','bindings','grants')
|
|
298
|
+
and not exists (select 1 from previews p where
|
|
299
|
+
(e.kind='apps' and e.entity_id=p.app_id) or (e.kind='tenants' and e.entity_id=p.tenant_id)
|
|
300
|
+
or (e.kind in ('tenantServices','sharedServiceActivations','bindings','grants') and e.value->>'tenantId'=p.tenant_id))
|
|
301
|
+
order by ordinal`, [db.scope]);
|
|
302
|
+
const values = (kind) => result.rows.filter(row => row.kind === kind).map(row => row.value);
|
|
303
|
+
const tenants = values("tenants");
|
|
304
|
+
const byId = new Map(tenants.map(tenant => [tenant.id, tenant]));
|
|
305
|
+
for (const { tenantId, ...service } of values("tenantServices"))
|
|
306
|
+
byId.get(tenantId)?.services.push(service);
|
|
307
|
+
const activity = await db.pool.query(`select service_id,last_seen_at,last_sync_at from ${db.activity} where scope_id=$1`, [db.scope]);
|
|
308
|
+
const activityById = new Map(activity.rows.map(row => [row.service_id, row]));
|
|
309
|
+
for (const tenant of tenants)
|
|
310
|
+
for (const service of tenant.services) {
|
|
311
|
+
const row = activityById.get(service.id);
|
|
312
|
+
if (row?.last_seen_at)
|
|
313
|
+
service.lastSeenAt = row.last_seen_at.toISOString();
|
|
314
|
+
if (row?.last_sync_at)
|
|
315
|
+
service.lastSyncAt = row.last_sync_at.toISOString();
|
|
316
|
+
}
|
|
317
|
+
return { apps: values("apps").map(app => BetterPortalAppSchema.parse(app)), tenants,
|
|
318
|
+
platformServices: values("platformServices"), sharedServiceCatalog: values("sharedServiceCatalog"),
|
|
319
|
+
sharedServiceActivations: values("sharedServiceActivations"), configManagement: values("settings")[0],
|
|
320
|
+
m2m: { bindings: values("bindings"), grants: values("grants") } };
|
|
321
|
+
}
|
|
322
|
+
/** Tenant editor needs route choices/counts, but no route options, menus or grants. */
|
|
323
|
+
export async function readTenantPage(db) {
|
|
324
|
+
const data = await readDirectory(db);
|
|
325
|
+
const routes = await db.pool.query(`select app_id,id,kind,path,service_id,view_id,title,icon,enabled,operations,position
|
|
326
|
+
from ${db.appData.routes} where scope_id=$1 and app_id=any($2::text[])`, [db.scope, data.apps.map(app => app.id)]);
|
|
327
|
+
db.appData.hydrate(data.apps, { roles: [], grants: [], routes: routes.rows, menus: [] });
|
|
328
|
+
return data;
|
|
329
|
+
}
|
|
330
|
+
//# sourceMappingURL=appQueries.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"appQueries.js","sourceRoot":"","sources":["../../../src/plugins/service-betterportal-config-manager/appQueries.ts"],"names":[],"mappings":"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"}
|
|
@@ -178,6 +178,9 @@ export declare class Plugin extends BPService<InstanceType<typeof Config>, typeo
|
|
|
178
178
|
private warmAuthCache;
|
|
179
179
|
private refreshAuthCache;
|
|
180
180
|
private loadRequestConfig;
|
|
181
|
+
private readonly contextConfigs;
|
|
182
|
+
private loadContextConfig;
|
|
183
|
+
private loadDirectory;
|
|
181
184
|
protected resolveRequestContext(event: BetterPortalEvent): Promise<BetterPortalResolvedRequestContext | null>;
|
|
182
185
|
protected describeCorsContextFailure(event: BetterPortalEvent): Promise<{
|
|
183
186
|
candidateHosts: string;
|
|
@@ -190,6 +193,7 @@ export declare class Plugin extends BPService<InstanceType<typeof Config>, typeo
|
|
|
190
193
|
private startOutbox;
|
|
191
194
|
private populateConfigAdminContext;
|
|
192
195
|
private populateServicesContext;
|
|
196
|
+
private loadEditorPage;
|
|
193
197
|
private populateRoutesContext;
|
|
194
198
|
private populateMenuContext;
|
|
195
199
|
private populateFragmentsContext;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/service-betterportal-config-manager/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,qBAAqB,EAK1B,KAAK,UAAU,EAChB,MAAM,WAAW,CAAC;AACnB,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,SAAS,EAA4B,MAAM,0BAA0B,CAAC;AAG/E,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/service-betterportal-config-manager/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,qBAAqB,EAK1B,KAAK,UAAU,EAChB,MAAM,WAAW,CAAC;AACnB,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,SAAS,EAA4B,MAAM,0BAA0B,CAAC;AAG/E,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAuB1E,OAAO,EAKL,KAAK,iBAAiB,EACtB,KAAK,kCAAkC,EACvC,KAAK,oBAAoB,EAG1B,MAAM,yBAAyB,CAAC;AA0EjC,QAAA,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;IAdV,iGAAiG;;IAEjG,4GAA4G;;IAE5G,sDAAsD;;GAmBvD,CAAC;AAEF,QAAA,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;CAiBhB,CAAC;AAEH,qBAAa,MAAO,SAAQ,SAAS,CAAC,YAAY,CAAC,OAAO,MAAM,CAAC,EAAE,OAAO,YAAY,CAAC;IACrF,MAAM,CAAC,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;QA7Cb,iGAAiG;;QAEjG,4GAA4G;;QAE5G,sDAAsD;;QAyC/B;IACvB,MAAM,CAAC,YAAY;;;;;;;;;;;;;;;;;;;;;MAAgB;IACnC,SAAS,CAAC,QAAQ,CAAC,+BAA+B,SAAS;IAC3D,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAmF;IAClH,OAAO,CAAC,cAAc,CAAC,CAA0D;IACjF,OAAO,CAAC,kBAAkB,CAAC,CAAiB;IAC5C,OAAO,CAAC,WAAW,CAAC,CAAiB;IACrC,OAAO,CAAC,WAAW,CAAC,CAAgB;IACpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAY;IACrC,OAAO,CAAC,kBAAkB,CAAK;IAC/B,OAAO,CAAC,eAAe,CAAC,CAAkB;IAC1C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkC;IAC7D,2EAA2E;IAC3E,OAAO,CAAC,OAAO,CAAoB;IACnC,uFAAuF;IACvF,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAqK;IACrM,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAa;IAC5C,OAAO,CAAC,mBAAmB,CAAK;gBAEpB,GAAG,EAAE,qBAAqB,CAAC,YAAY,CAAC,OAAO,MAAM,CAAC,EAAE,OAAO,YAAY,CAAC;IAKxF,SAAS,CAAC,UAAU;;;;;;;;;;IAWpB;;;OAGG;IACH,SAAS,CAAC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAItF,SAAS,CAAC,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAIlF,SAAS,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS;IAOnF,SAAS,CAAC,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS;IAItH;;;;;;OAMG;IACH,OAAO,CAAC,kBAAkB;IAU1B;;;;;;OAMG;YACW,mBAAmB;IAUjC;;;;;;;OAOG;YACW,aAAa;YAqCb,gBAAgB;IAmC9B,OAAO,CAAC,iBAAiB;IAYzB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA0E;IACzG,OAAO,CAAC,iBAAiB;YAWX,aAAa;cAQF,qBAAqB,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,kCAAkC,GAAG,IAAI,CAAC;cAe5G,0BAA0B,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,kBAAkB,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;cASjI,YAAY,CAAC,QAAQ,EAAE,oBAAoB,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;YAmI/E,qBAAqB;IAyBpB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAOvC,OAAO,CAAC,oBAAoB;IA4B5B,OAAO,CAAC,WAAW;YAiCL,0BAA0B;YA4D1B,uBAAuB;YAyGvB,cAAc;YAWd,qBAAqB;YAmHrB,mBAAmB;YA8BnB,wBAAwB;YAcxB,sBAAsB;YAuCtB,wBAAwB;YAoIxB,uBAAuB;CAwDtC;AAED,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC"}
|
|
@@ -7,6 +7,7 @@ import { cpBootstrap } from "./cpBootstrap.js";
|
|
|
7
7
|
import { registerSetupEndpoints } from "./setupTokens.js";
|
|
8
8
|
import { registerBootstrapEndpoint } from "./bootstrapEndpoint.js";
|
|
9
9
|
import { registerAdminApiRoutes } from "./adminApi.js";
|
|
10
|
+
import { readAppPage, readCacheData, readDirectory, readRequestConfig } from "./appQueries.js";
|
|
10
11
|
import { registerMenuEditorRoutes } from "./menuEditor.js";
|
|
11
12
|
import { registerFragmentsEditorRoutes } from "./fragmentsEditor.js";
|
|
12
13
|
import { registerWebhookRoutes } from "./webhooks.js";
|
|
@@ -167,7 +168,7 @@ export class Plugin extends BPService {
|
|
|
167
168
|
async refreshConfigCaches(obs) {
|
|
168
169
|
const generation = ++this.authCacheGeneration;
|
|
169
170
|
this.storage.invalidate();
|
|
170
|
-
const config = await this.storage.loadConfig();
|
|
171
|
+
const config = (this.postgresStorage ? await readCacheData(this.postgresStorage.database) : await this.storage.loadConfig());
|
|
171
172
|
if (generation !== this.authCacheGeneration)
|
|
172
173
|
return;
|
|
173
174
|
hydrateManifestCache(config);
|
|
@@ -185,7 +186,7 @@ export class Plugin extends BPService {
|
|
|
185
186
|
async warmAuthCache(obs, snapshot) {
|
|
186
187
|
const generation = this.authCacheGeneration;
|
|
187
188
|
try {
|
|
188
|
-
const config = snapshot ?? await this.storage.loadConfig();
|
|
189
|
+
const config = snapshot ?? (this.postgresStorage ? await readCacheData(this.postgresStorage.database) : await this.storage.loadConfig());
|
|
189
190
|
if (generation !== this.authCacheGeneration)
|
|
190
191
|
return;
|
|
191
192
|
const next = new Map();
|
|
@@ -226,7 +227,7 @@ export class Plugin extends BPService {
|
|
|
226
227
|
const key = `${tenantId}::${appId}`;
|
|
227
228
|
const generation = this.authCacheGeneration;
|
|
228
229
|
try {
|
|
229
|
-
const config = await this.storage.loadConfig();
|
|
230
|
+
const config = (this.postgresStorage ? await readCacheData(this.postgresStorage.database) : await this.storage.loadConfig());
|
|
230
231
|
if (generation !== this.authCacheGeneration)
|
|
231
232
|
return;
|
|
232
233
|
const app = config.apps.find((a) => a.id === appId && a.tenantId === tenantId);
|
|
@@ -272,8 +273,30 @@ export class Plugin extends BPService {
|
|
|
272
273
|
}
|
|
273
274
|
return pending;
|
|
274
275
|
}
|
|
276
|
+
contextConfigs = new WeakMap();
|
|
277
|
+
loadContextConfig(event) {
|
|
278
|
+
if (!this.postgresStorage)
|
|
279
|
+
return this.loadRequestConfig(event);
|
|
280
|
+
let pending = this.contextConfigs.get(event);
|
|
281
|
+
if (!pending) {
|
|
282
|
+
pending = readRequestConfig(this.postgresStorage.database, eventHeaders(event), eventObservability(event));
|
|
283
|
+
this.contextConfigs.set(event, pending);
|
|
284
|
+
void pending.catch(() => this.contextConfigs.delete(event));
|
|
285
|
+
}
|
|
286
|
+
return pending;
|
|
287
|
+
}
|
|
288
|
+
async loadDirectory(event) {
|
|
289
|
+
const span = eventObservability(event)?.startSpan("bp.directory.read");
|
|
290
|
+
try {
|
|
291
|
+
return this.postgresStorage ? await readDirectory(this.postgresStorage.database)
|
|
292
|
+
: visibleAdminConfig(await this.loadRequestConfig(event));
|
|
293
|
+
}
|
|
294
|
+
finally {
|
|
295
|
+
span?.end();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
275
298
|
async resolveRequestContext(event) {
|
|
276
|
-
const config = await this.
|
|
299
|
+
const config = await this.loadContextConfig(event);
|
|
277
300
|
const context = resolveEmbeddedRequestContext(config, eventHeaders(event));
|
|
278
301
|
if (!context) {
|
|
279
302
|
return null;
|
|
@@ -285,7 +308,7 @@ export class Plugin extends BPService {
|
|
|
285
308
|
return context;
|
|
286
309
|
}
|
|
287
310
|
async describeCorsContextFailure(event) {
|
|
288
|
-
const config = await this.
|
|
311
|
+
const config = await this.loadContextConfig(event);
|
|
289
312
|
const details = describeEmbeddedContextResolution(config, eventHeaders(event));
|
|
290
313
|
return {
|
|
291
314
|
candidateHosts: details.candidates.join(","),
|
|
@@ -306,7 +329,7 @@ export class Plugin extends BPService {
|
|
|
306
329
|
this.storage = this.withChangeBroadcasts(resolvedStorage.store, _obs, {
|
|
307
330
|
backend: resolvedStorage.backend
|
|
308
331
|
});
|
|
309
|
-
hydrateManifestCache(await this.storage.loadConfig());
|
|
332
|
+
hydrateManifestCache(this.postgresStorage ? await readCacheData(this.postgresStorage.database) : await this.storage.loadConfig());
|
|
310
333
|
// Initialize CP keypair + JWKS (P7).
|
|
311
334
|
this.cpState = await cpBootstrap({
|
|
312
335
|
keyStorePath: resolve(cwd, this.config.cpKeyStorePath ?? "./.bp-cp-state/keys.json"),
|
|
@@ -337,6 +360,7 @@ export class Plugin extends BPService {
|
|
|
337
360
|
});
|
|
338
361
|
setConfigManagerRouteContext({
|
|
339
362
|
storage: this.storage,
|
|
363
|
+
database: this.postgresStorage?.database,
|
|
340
364
|
cpState: this.cpState,
|
|
341
365
|
serviceBaseUrl: this.cpState.issuer
|
|
342
366
|
});
|
|
@@ -354,8 +378,8 @@ export class Plugin extends BPService {
|
|
|
354
378
|
this.app.use("/auth", (event) => this.populateAdminAuthContext(event));
|
|
355
379
|
this.app.use("/settings", (event) => this.populateSettingsContext(event));
|
|
356
380
|
this.app.use("/.well-known/bp", (event) => applyWellKnownCors(event));
|
|
357
|
-
registerAdminApiRoutes(this.app, this.storage, this.cpState);
|
|
358
|
-
registerMenuEditorRoutes(this.app, this.storage);
|
|
381
|
+
registerAdminApiRoutes(this.app, this.storage, this.cpState, this.postgresStorage?.database);
|
|
382
|
+
registerMenuEditorRoutes(this.app, this.storage, this.postgresStorage?.database);
|
|
359
383
|
registerFragmentsEditorRoutes(this.app, this.storage);
|
|
360
384
|
this.webhookRuntime = registerWebhookRoutes(this.app, this.storage, this.postgresStorage, this.workerId);
|
|
361
385
|
registerPreviewDeploymentApi({
|
|
@@ -415,6 +439,16 @@ export class Plugin extends BPService {
|
|
|
415
439
|
}
|
|
416
440
|
async removeExpiredPreviews() {
|
|
417
441
|
const cleanup = async () => {
|
|
442
|
+
if (this.postgresStorage) {
|
|
443
|
+
const { pool, entities, scope } = this.postgresStorage.database;
|
|
444
|
+
const due = await pool.query(`select 1 from ${entities} where scope_id=$1 and kind='previewEnvironmentDeployments'
|
|
445
|
+
and ((value->>'expiresAt')::timestamptz <= now()
|
|
446
|
+
or (value->'credentialReplay'->>'expiresAt')::timestamptz <= now()) limit 1`, [scope]);
|
|
447
|
+
if (!due.rows.length) {
|
|
448
|
+
await this.postgresStorage.cleanupExpiredActions();
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
418
452
|
const config = await this.storage.loadConfig();
|
|
419
453
|
let changed = deleteExpiredPreviewDeployments(config).length > 0;
|
|
420
454
|
for (const deployment of config.previewEnvironmentDeployments) {
|
|
@@ -495,8 +529,8 @@ export class Plugin extends BPService {
|
|
|
495
529
|
this.outboxTimer.unref();
|
|
496
530
|
}
|
|
497
531
|
async populateConfigAdminContext(event) {
|
|
498
|
-
const portalConfig =
|
|
499
|
-
const requestContext = resolveEmbeddedRequestContext(
|
|
532
|
+
const portalConfig = await this.loadDirectory(event);
|
|
533
|
+
const requestContext = resolveEmbeddedRequestContext(await this.loadContextConfig(event), eventHeaders(event));
|
|
500
534
|
if (requestContext) {
|
|
501
535
|
const tenant = requestContext.tenant;
|
|
502
536
|
const allServices = [
|
|
@@ -552,7 +586,7 @@ export class Plugin extends BPService {
|
|
|
552
586
|
}
|
|
553
587
|
}
|
|
554
588
|
async populateServicesContext(event) {
|
|
555
|
-
const config =
|
|
589
|
+
const config = await this.loadDirectory(event);
|
|
556
590
|
const url = new URL(event.req.url ?? "", RELATIVE_URL_PARSE_BASE);
|
|
557
591
|
const requestedTenantId = url.searchParams.get("tenantId") ?? undefined;
|
|
558
592
|
const selectedTenantId = config.tenants.some((tenant) => tenant.id === requestedTenantId)
|
|
@@ -648,8 +682,22 @@ export class Plugin extends BPService {
|
|
|
648
682
|
serviceBaseUrl: this.cpState.issuer
|
|
649
683
|
};
|
|
650
684
|
}
|
|
651
|
-
async
|
|
685
|
+
async loadEditorPage(event, chooseFirst = false) {
|
|
686
|
+
if (this.postgresStorage) {
|
|
687
|
+
const appId = new URL(event.req.url, RELATIVE_URL_PARSE_BASE).searchParams.get("appId") ?? undefined;
|
|
688
|
+
const span = eventObservability(event)?.startSpan("bp.app.read");
|
|
689
|
+
try {
|
|
690
|
+
return await readAppPage(this.postgresStorage.database, appId, chooseFirst);
|
|
691
|
+
}
|
|
692
|
+
finally {
|
|
693
|
+
span?.end();
|
|
694
|
+
}
|
|
695
|
+
}
|
|
652
696
|
const config = visibleAdminConfig(await this.loadRequestConfig(event));
|
|
697
|
+
return { ...config, appChoices: config.apps };
|
|
698
|
+
}
|
|
699
|
+
async populateRoutesContext(event) {
|
|
700
|
+
const config = await this.loadEditorPage(event);
|
|
653
701
|
const url = new URL(event.req.url ?? "", RELATIVE_URL_PARSE_BASE);
|
|
654
702
|
const selectedAppId = url.searchParams.get("appId") ?? undefined;
|
|
655
703
|
const openApiServiceId = url.searchParams.get("apiServiceId") ?? undefined;
|
|
@@ -744,7 +792,7 @@ export class Plugin extends BPService {
|
|
|
744
792
|
});
|
|
745
793
|
event.__bpResponseModel = {
|
|
746
794
|
title: "Route Designer",
|
|
747
|
-
apps: config.
|
|
795
|
+
apps: config.appChoices.map((a) => ({ id: a.id, title: a.title, tenantId: a.tenantId })),
|
|
748
796
|
selectedAppId,
|
|
749
797
|
openApiServiceId,
|
|
750
798
|
routes: routeModel,
|
|
@@ -755,13 +803,13 @@ export class Plugin extends BPService {
|
|
|
755
803
|
};
|
|
756
804
|
}
|
|
757
805
|
async populateMenuContext(event) {
|
|
758
|
-
const config =
|
|
806
|
+
const config = await this.loadEditorPage(event);
|
|
759
807
|
const url = new URL(event.req.url ?? "", RELATIVE_URL_PARSE_BASE);
|
|
760
808
|
const selectedAppId = url.searchParams.get("appId") ?? undefined;
|
|
761
809
|
const selectedApp = selectedAppId ? config.apps.find((a) => a.id === selectedAppId) : undefined;
|
|
762
810
|
event.__bpResponseModel = {
|
|
763
811
|
title: "Menu Designer",
|
|
764
|
-
apps: config.
|
|
812
|
+
apps: config.appChoices.map((a) => ({ id: a.id, title: a.title, tenantId: a.tenantId })),
|
|
765
813
|
selectedAppId,
|
|
766
814
|
menu: (selectedApp?.menu ?? []).map((m) => ({
|
|
767
815
|
id: m.id, type: m.type, title: m.title,
|
|
@@ -781,19 +829,19 @@ export class Plugin extends BPService {
|
|
|
781
829
|
};
|
|
782
830
|
}
|
|
783
831
|
async populateFragmentsContext(event) {
|
|
784
|
-
const config =
|
|
832
|
+
const config = await this.loadEditorPage(event);
|
|
785
833
|
const url = new URL(event.req.url ?? "", RELATIVE_URL_PARSE_BASE);
|
|
786
834
|
const selectedAppId = url.searchParams.get("appId") ?? undefined;
|
|
787
835
|
event.__bpResponseModel = {
|
|
788
836
|
title: "Fragments",
|
|
789
|
-
apps: config.
|
|
837
|
+
apps: config.appChoices.map((a) => ({ id: a.id, title: a.title, tenantId: a.tenantId })),
|
|
790
838
|
selectedAppId,
|
|
791
839
|
adminApiBase: "/.well-known/bp/admin",
|
|
792
840
|
serviceBaseUrl: this.cpState.issuer
|
|
793
841
|
};
|
|
794
842
|
}
|
|
795
843
|
async populatePreviewContext(event) {
|
|
796
|
-
const config =
|
|
844
|
+
const config = await this.loadDirectory(event);
|
|
797
845
|
const services = [];
|
|
798
846
|
const allServiceHostnames = [
|
|
799
847
|
...config.tenants.flatMap((t) => t.services.filter((s) => s.enabled).map((s) => ({
|
|
@@ -820,7 +868,7 @@ export class Plugin extends BPService {
|
|
|
820
868
|
};
|
|
821
869
|
}
|
|
822
870
|
async populateAdminAuthContext(event) {
|
|
823
|
-
const config =
|
|
871
|
+
const config = await this.loadEditorPage(event, true);
|
|
824
872
|
const url = new URL(event.req.url ?? "", RELATIVE_URL_PARSE_BASE);
|
|
825
873
|
const selectedAppId = url.searchParams.get("appId") ?? undefined;
|
|
826
874
|
const selectedApp = selectedAppId
|
|
@@ -918,7 +966,7 @@ export class Plugin extends BPService {
|
|
|
918
966
|
: undefined;
|
|
919
967
|
event.__bpResponseModel = {
|
|
920
968
|
title: "Permission Manager",
|
|
921
|
-
apps: config.
|
|
969
|
+
apps: config.appChoices.map((a) => ({ id: a.id, tenantId: a.tenantId, title: a.title })),
|
|
922
970
|
selectedAppId: selectedApp?.id,
|
|
923
971
|
selectedTenantId,
|
|
924
972
|
authConfigured,
|
|
@@ -932,9 +980,9 @@ export class Plugin extends BPService {
|
|
|
932
980
|
};
|
|
933
981
|
}
|
|
934
982
|
async populateSettingsContext(event) {
|
|
935
|
-
const config =
|
|
983
|
+
const config = await this.loadDirectory(event);
|
|
936
984
|
const url = new URL(event.req.url ?? "", RELATIVE_URL_PARSE_BASE);
|
|
937
|
-
const requestContext = resolveEmbeddedRequestContext(
|
|
985
|
+
const requestContext = resolveEmbeddedRequestContext(await this.loadContextConfig(event), eventHeaders(event));
|
|
938
986
|
const requestedAppId = url.searchParams.get("appId") ?? undefined;
|
|
939
987
|
const app = requestContext?.app
|
|
940
988
|
?? (requestedAppId ? config.apps.find((candidate) => candidate.id === requestedAppId) : undefined)
|