@betterportal/config-manager 10.6.31 → 10.6.33

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 (65) 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/bp-contracts/org.betterportal.config-manager.json +69 -1
  4. package/lib/plugins/service-betterportal-config-manager/.bp-generated/registry.d.ts +32 -0
  5. package/lib/plugins/service-betterportal-config-manager/.bp-generated/registry.d.ts.map +1 -1
  6. package/lib/plugins/service-betterportal-config-manager/adminApi.d.ts +3 -2
  7. package/lib/plugins/service-betterportal-config-manager/adminApi.d.ts.map +1 -1
  8. package/lib/plugins/service-betterportal-config-manager/adminApi.js +58 -36
  9. package/lib/plugins/service-betterportal-config-manager/adminApi.js.map +1 -1
  10. package/lib/plugins/service-betterportal-config-manager/appQueries.d.ts +1096 -0
  11. package/lib/plugins/service-betterportal-config-manager/appQueries.d.ts.map +1 -0
  12. package/lib/plugins/service-betterportal-config-manager/appQueries.js +330 -0
  13. package/lib/plugins/service-betterportal-config-manager/appQueries.js.map +1 -0
  14. package/lib/plugins/service-betterportal-config-manager/bp-routes/preview-environments/_renderer.bootstrap5/GET.d.ts.map +1 -1
  15. package/lib/plugins/service-betterportal-config-manager/bp-routes/preview-environments/_renderer.bootstrap5/GET.js +35 -5
  16. package/lib/plugins/service-betterportal-config-manager/bp-routes/preview-environments/_renderer.bootstrap5/GET.js.map +1 -1
  17. package/lib/plugins/service-betterportal-config-manager/index.d.ts +4 -0
  18. package/lib/plugins/service-betterportal-config-manager/index.d.ts.map +1 -1
  19. package/lib/plugins/service-betterportal-config-manager/index.js +70 -22
  20. package/lib/plugins/service-betterportal-config-manager/index.js.map +1 -1
  21. package/lib/plugins/service-betterportal-config-manager/m2mConnections.d.ts +6 -3
  22. package/lib/plugins/service-betterportal-config-manager/m2mConnections.d.ts.map +1 -1
  23. package/lib/plugins/service-betterportal-config-manager/m2mConnections.js.map +1 -1
  24. package/lib/plugins/service-betterportal-config-manager/menuEditor.d.ts +2 -1
  25. package/lib/plugins/service-betterportal-config-manager/menuEditor.d.ts.map +1 -1
  26. package/lib/plugins/service-betterportal-config-manager/menuEditor.js +165 -176
  27. package/lib/plugins/service-betterportal-config-manager/menuEditor.js.map +1 -1
  28. package/lib/plugins/service-betterportal-config-manager/previewEnvironmentManagement.d.ts +10 -0
  29. package/lib/plugins/service-betterportal-config-manager/previewEnvironmentManagement.d.ts.map +1 -1
  30. package/lib/plugins/service-betterportal-config-manager/previewEnvironmentManagement.js +90 -1
  31. package/lib/plugins/service-betterportal-config-manager/previewEnvironmentManagement.js.map +1 -1
  32. package/lib/plugins/service-betterportal-config-manager/previewEnvironments.d.ts +1 -1
  33. package/lib/plugins/service-betterportal-config-manager/previewEnvironments.d.ts.map +1 -1
  34. package/lib/plugins/service-betterportal-config-manager/previewEnvironments.js +133 -23
  35. package/lib/plugins/service-betterportal-config-manager/previewEnvironments.js.map +1 -1
  36. package/lib/plugins/service-betterportal-config-manager/routeContext.d.ts +2 -0
  37. package/lib/plugins/service-betterportal-config-manager/routeContext.d.ts.map +1 -1
  38. package/lib/plugins/service-betterportal-config-manager/routeContext.js.map +1 -1
  39. package/lib/plugins/service-betterportal-config-manager/storage/appData.d.ts +1 -1
  40. package/lib/plugins/service-betterportal-config-manager/storage/appData.d.ts.map +1 -1
  41. package/lib/plugins/service-betterportal-config-manager/storage/appData.js +4 -4
  42. package/lib/plugins/service-betterportal-config-manager/storage/appData.js.map +1 -1
  43. package/lib/plugins/service-betterportal-config-manager/storage/core.d.ts +5 -3
  44. package/lib/plugins/service-betterportal-config-manager/storage/core.d.ts.map +1 -1
  45. package/lib/plugins/service-betterportal-config-manager/storage/core.js +1 -1
  46. package/lib/plugins/service-betterportal-config-manager/storage/core.js.map +1 -1
  47. package/lib/plugins/service-betterportal-config-manager/storage/entities.d.ts.map +1 -1
  48. package/lib/plugins/service-betterportal-config-manager/storage/entities.js +4 -2
  49. package/lib/plugins/service-betterportal-config-manager/storage/entities.js.map +1 -1
  50. package/lib/plugins/service-betterportal-config-manager/storage/postgres.d.ts +14 -0
  51. package/lib/plugins/service-betterportal-config-manager/storage/postgres.d.ts.map +1 -1
  52. package/lib/plugins/service-betterportal-config-manager/storage/postgres.js +11 -0
  53. package/lib/plugins/service-betterportal-config-manager/storage/postgres.js.map +1 -1
  54. package/lib/plugins/service-betterportal-config-manager/syncApi.d.ts +4 -3
  55. package/lib/plugins/service-betterportal-config-manager/syncApi.d.ts.map +1 -1
  56. package/lib/plugins/service-betterportal-config-manager/syncApi.js.map +1 -1
  57. package/lib/plugins/service-betterportal-config-manager/tenantManagement.d.ts.map +1 -1
  58. package/lib/plugins/service-betterportal-config-manager/tenantManagement.js +3 -1
  59. package/lib/plugins/service-betterportal-config-manager/tenantManagement.js.map +1 -1
  60. package/lib/plugins/service-betterportal-config-manager/webhooks.d.ts.map +1 -1
  61. package/lib/plugins/service-betterportal-config-manager/webhooks.js +147 -54
  62. package/lib/plugins/service-betterportal-config-manager/webhooks.js.map +1 -1
  63. package/lib/schemas/service-betterportal-config-manager.json +1 -1
  64. package/lib/schemas/service-betterportal-config-manager.plugin.json +1 -1
  65. 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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"}
@@ -1 +1 @@
1
- {"version":3,"file":"GET.d.ts","sourceRoot":"","sources":["../../../../../../src/plugins/service-betterportal-config-manager/bp-routes/preview-environments/_renderer.bootstrap5/GET.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0CAA0C,CAAC;AAmC7E,wBAAgB,kBAAkB,IAAI,cAAc,CAqPnD;AAiED,wBAAgB,YAAY,CAAC,KAAK,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,cAAc,CAyDnH;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,YAAY,GAAG,cAAc,CAmLzD"}
1
+ {"version":3,"file":"GET.d.ts","sourceRoot":"","sources":["../../../../../../src/plugins/service-betterportal-config-manager/bp-routes/preview-environments/_renderer.bootstrap5/GET.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0CAA0C,CAAC;AAmC7E,wBAAgB,kBAAkB,IAAI,cAAc,CAmRnD;AAiED,wBAAgB,YAAY,CAAC,KAAK,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,cAAc,CAsFnH;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,YAAY,GAAG,cAAc,CAuLzD"}
@@ -35,6 +35,36 @@ function pageScript() {
35
35
  }
36
36
  export function configEditorScript() {
37
37
  return js(`(() => {
38
+ document.querySelectorAll("[data-bp-schema-form]:not([data-bp-ready])").forEach((form) => {
39
+ form.dataset.bpReady = "true";
40
+ const fileInput = form.querySelector("[data-bp-schema-file]");
41
+ const text = form.querySelector("[name=schemas]");
42
+ const status = form.querySelector("[data-bp-schema-status]");
43
+ const submit = form.querySelector("[type=submit]");
44
+ text.addEventListener("input", () => {
45
+ fileInput.value = "";
46
+ fileInput.setCustomValidity("");
47
+ status.textContent = "";
48
+ });
49
+ fileInput.addEventListener("change", async () => {
50
+ const file = fileInput.files[0];
51
+ if (!file) return;
52
+ submit.disabled = true;
53
+ try {
54
+ if (file.size > 1024 * 1024) throw new Error("BP schema upload exceeds 1 MiB");
55
+ const contents = await file.text();
56
+ JSON.parse(contents);
57
+ text.value = contents;
58
+ fileInput.setCustomValidity("");
59
+ status.textContent = file.name;
60
+ } catch (error) {
61
+ fileInput.setCustomValidity(error.message || String(error));
62
+ status.textContent = error.message || String(error);
63
+ } finally {
64
+ submit.disabled = false;
65
+ }
66
+ });
67
+ });
38
68
  const decode64 = (value) => {
39
69
  const base64 = value.replace(/-/g, "+").replace(/_/g, "/") + "===".slice((value.length + 3) % 4);
40
70
  return Uint8Array.from(atob(base64), (char) => char.charCodeAt(0));
@@ -315,12 +345,12 @@ export function configEditor(group, path, ticketUrl) {
315
345
  }
316
346
  return _jsx("input", { class: "form-control", type: field.secret ? "password" : field.control === "email" || field.control === "url" || field.control === "number" ? field.control : "text", maxlength: "255", value: field.secret ? "" : value, autocomplete: field.secret ? "new-password" : "off", "data-bp-config-field": "", "data-service-id": service.serviceId, "data-scope": field.scope, "data-key": field.key, "data-title": field.title, "data-secret": field.secret ? "true" : "false", "data-default-value": field.defaultValue === undefined ? undefined : JSON.stringify(field.defaultValue), disabled: field.secret, required: field.required && !field.secret });
317
347
  };
318
- return (_jsxs("details", { class: "border rounded p-3 mt-3", children: [_jsx("summary", { class: "fw-semibold", children: "Encrypted service configuration" }), !hasFields ? _jsx("div", { class: "alert alert-secondary mt-3 mb-0", children: "No synced service config schemas are available for this group." }) : (_jsxs("form", { class: "mt-3", "data-bp-preview-config-form": "", "data-group-id": group.id, "data-path": path, "data-ticket-url": ticketUrl, "data-source-tenant-id": group.sourceTenantId, "data-source-app-id": group.sourceAppId, children: [_jsxs("div", { class: "alert alert-warning small", children: ["Use preview-only values. The key and decrypted secrets stay in this browser; BetterPortal stores only ", _jsx("code", { children: "encrypted:" }), " envelopes."] }), _jsx("label", { class: "form-label", for: `bp-preview-key-${group.id}`, children: "Preview config key" }), _jsxs("div", { class: "input-group mb-2", children: [_jsx("input", { id: `bp-preview-key-${group.id}`, class: "form-control font-monospace", type: "password", autocomplete: "off", "data-bp-preview-key": "" }), _jsx("button", { class: "btn btn-outline-secondary", type: "button", "data-bp-generate-key": "", children: "Generate" }), _jsx("button", { class: "btn btn-outline-secondary", type: "button", "data-bp-copy-target": `bp-preview-key-${group.id}`, children: "Copy" })] }), _jsxs("div", { class: "form-text mb-3", children: ["Template changes apply to newly created previews. Existing previews keep their own settings. Set this exact value as ", _jsx("code", { children: "BP_PREVIEW_CONFIG_KEY" }), " on every configured preview service. Keep it outside BetterPortal."] }), _jsx("button", { class: "btn btn-sm btn-outline-secondary mb-3", type: "button", "data-bp-decrypt-config": "", children: "Decrypt stored secrets" }), group.services.map((service) => (_jsxs("fieldset", { class: "border rounded p-3 mb-3", children: [_jsx("legend", { class: "float-none w-auto px-2 fs-6", children: service.title }), _jsx("span", { hidden: true, "data-bp-existing-config": "", "data-service-id": service.serviceId, "data-tenant": service.encryptedTenantConfig, "data-app": service.encryptedAppConfig, "data-source-instance-id": service.source?.instanceId, "data-source-hostname": service.source?.hostname }), service.fields.length === 0 ? _jsx("div", { class: "alert alert-warning mb-0", children: "This service has not synced a config schema yet. Its existing preview config will be preserved." }) : null, ["tenant", "app"].map((scope) => {
319
- const fields = service.fields.filter((field) => field.scope === scope);
320
- return fields.length === 0 ? null : _jsxs("div", { class: "mb-3", children: [_jsxs("div", { class: "text-uppercase text-secondary small fw-semibold mb-2", children: [scope, " config"] }), _jsx("div", { class: "row g-3", children: fields.map((field) => _jsxs("div", { class: "col-12 col-lg-6", children: [_jsxs("label", { class: "form-label", children: [field.title, field.required ? " *" : ""] }), fieldControl(service, field), _jsxs("div", { class: "form-text", children: [field.description, field.secret ? " Unlock with a generated key or decrypt the stored values first." : ""] }), field.secret && !field.required ? _jsxs("div", { class: "form-check mt-1", children: [_jsx("input", { class: "form-check-input", type: "checkbox", disabled: true, "data-bp-clear-secret": "", "data-service-id": service.serviceId, "data-scope": field.scope, "data-key": field.key, id: `bp-clear-${group.id}-${service.serviceId}-${field.scope}-${field.key}` }), _jsx("label", { class: "form-check-label small", for: `bp-clear-${group.id}-${service.serviceId}-${field.scope}-${field.key}`, children: "Clear stored value" })] }) : null] })) })] });
321
- })] }))), _jsx("div", { class: "alert alert-secondary py-2", "data-bp-preview-config-status": "", role: "status", children: "Key is never submitted." }), _jsxs("div", { class: "d-flex flex-wrap gap-2", children: [_jsx("button", { class: "btn btn-primary", type: "submit", children: "Encrypt and save config" }), _jsx("button", { class: "btn btn-outline-secondary", type: "button", "data-bp-sync-prod": "", children: "Sync from prod" })] }), _jsxs("dialog", { class: "border-0 rounded shadow p-0", style: "width:min(92vw,72rem);max-height:90vh", "data-bp-sync-dialog": "", children: [_jsxs("div", { class: "p-3 border-bottom d-flex justify-content-between align-items-center", children: [_jsx("h3", { class: "h5 mb-0", children: "Production config diff" }), _jsx("button", { class: "btn-close", type: "button", "aria-label": "Close", "data-bp-sync-close": "" })] }), _jsx("div", { class: "p-3 overflow-auto", style: "max-height:70vh", "data-bp-sync-body": "" }), _jsxs("div", { class: "p-3 border-top d-flex justify-content-end gap-2", children: [_jsx("button", { class: "btn btn-outline-secondary", type: "button", "data-bp-sync-close": "", children: "Cancel" }), _jsx("button", { class: "btn btn-primary", type: "button", "data-bp-sync-apply": "", children: "Apply selected and save" })] })] })] }))] }));
348
+ return (_jsxs("div", { children: [_jsxs("section", { class: "mt-3", "aria-labelledby": `bp-plugin-definitions-${group.id}`, children: [_jsx("h4", { class: "h6", id: `bp-plugin-definitions-${group.id}`, children: "BP plugins" }), _jsxs("form", { class: "d-flex flex-wrap gap-2 mb-3", "hx-post": path, "hx-target": "#bp-main", "hx-swap": "innerHTML", children: [_jsx("input", { type: "hidden", name: "action", value: "save-service" }), _jsx("input", { type: "hidden", name: "groupId", value: group.id }), _jsx("label", { class: "visually-hidden", for: `bp-plugin-id-${group.id}`, children: "BP plugin ID" }), _jsx("input", { id: `bp-plugin-id-${group.id}`, class: "form-control font-monospace", style: "flex:1 1 16rem;min-width:0", name: "serviceId", placeholder: "org.example.service", required: true }), _jsx("button", { class: "btn btn-outline-primary", type: "submit", children: "Add BP plugin" })] }), group.services.map((service) => (_jsxs("details", { class: "border-bottom py-2", children: [_jsxs("summary", { children: [_jsx("code", { class: "text-break", children: service.serviceId }), " ", _jsx("span", { class: "small text-secondary", children: service.schemaSource === "uploaded" ? "Uploaded schema" : service.schemaSource === "synced" ? "Synced schema" : "No schema" })] }), service.schemaWarning ? _jsx("div", { class: "alert alert-warning mt-2", role: "status", children: service.schemaWarning }) : null, _jsxs("form", { class: "mt-2", "data-bp-schema-form": "", "hx-post": path, "hx-target": "#bp-main", "hx-swap": "innerHTML", children: [_jsx("input", { type: "hidden", name: "action", value: "save-service" }), _jsx("input", { type: "hidden", name: "groupId", value: group.id }), _jsx("input", { type: "hidden", name: "serviceId", value: service.serviceId }), _jsx("label", { class: "form-label", for: `bp-schema-file-${group.id}-${service.serviceId}`, children: "BP config schema file" }), _jsx("input", { id: `bp-schema-file-${group.id}-${service.serviceId}`, class: "form-control mb-2", type: "file", accept: ".json,application/json", "data-bp-schema-file": "" }), _jsx("label", { class: "form-label", for: `bp-schema-json-${group.id}-${service.serviceId}`, children: "BP configSchemas JSON" }), _jsx("textarea", { id: `bp-schema-json-${group.id}-${service.serviceId}`, class: "form-control font-monospace mb-2", name: "schemas", rows: 4, maxlength: 1048576, required: true }), _jsx("div", { class: "small text-secondary mb-2", "data-bp-schema-status": "", role: "status" }), _jsx("button", { class: "btn btn-sm btn-outline-primary", type: "submit", children: "Save schema" })] })] })))] }), _jsxs("details", { class: "border rounded p-3 mt-3", children: [_jsx("summary", { class: "fw-semibold", children: "Encrypted service configuration" }), !hasFields ? _jsx("div", { class: "alert alert-secondary mt-3 mb-0", children: "No BP config fields are available." }) : (_jsxs("form", { class: "mt-3", "data-bp-preview-config-form": "", "data-group-id": group.id, "data-path": path, "data-ticket-url": ticketUrl, "data-source-tenant-id": group.sourceTenantId, "data-source-app-id": group.sourceAppId, children: [_jsxs("div", { class: "alert alert-warning small", children: ["Use preview-only values. The key and decrypted secrets stay in this browser; BetterPortal stores only ", _jsx("code", { children: "encrypted:" }), " envelopes."] }), _jsx("label", { class: "form-label", for: `bp-preview-key-${group.id}`, children: "Preview config key" }), _jsxs("div", { class: "input-group mb-2", children: [_jsx("input", { id: `bp-preview-key-${group.id}`, class: "form-control font-monospace", type: "password", autocomplete: "off", "data-bp-preview-key": "" }), _jsx("button", { class: "btn btn-outline-secondary", type: "button", "data-bp-generate-key": "", children: "Generate" }), _jsx("button", { class: "btn btn-outline-secondary", type: "button", "data-bp-copy-target": `bp-preview-key-${group.id}`, children: "Copy" })] }), _jsxs("div", { class: "form-text mb-3", children: ["Template changes apply to newly created previews. Existing previews keep their own settings. Set this exact value as ", _jsx("code", { children: "BP_PREVIEW_CONFIG_KEY" }), " on every configured preview service. Keep it outside BetterPortal."] }), _jsx("button", { class: "btn btn-sm btn-outline-secondary mb-3", type: "button", "data-bp-decrypt-config": "", children: "Decrypt stored secrets" }), group.services.map((service) => (_jsxs("fieldset", { class: "border rounded p-3 mb-3", children: [_jsx("legend", { class: "float-none w-auto px-2 fs-6", children: service.title }), _jsx("span", { hidden: true, "data-bp-existing-config": "", "data-service-id": service.serviceId, "data-tenant": service.encryptedTenantConfig, "data-app": service.encryptedAppConfig, "data-source-instance-id": service.source?.instanceId, "data-source-hostname": service.source?.hostname }), service.fields.length === 0 ? _jsx("div", { class: "alert alert-warning mb-0", children: "This service has not synced a config schema yet. Its existing preview config will be preserved." }) : null, ["tenant", "app"].map((scope) => {
349
+ const fields = service.fields.filter((field) => field.scope === scope);
350
+ return fields.length === 0 ? null : _jsxs("div", { class: "mb-3", children: [_jsxs("div", { class: "text-uppercase text-secondary small fw-semibold mb-2", children: [scope, " config"] }), _jsx("div", { class: "row g-3", children: fields.map((field) => _jsxs("div", { class: "col-12 col-lg-6", children: [_jsxs("label", { class: "form-label", children: [field.title, field.required ? " *" : ""] }), fieldControl(service, field), _jsxs("div", { class: "form-text", children: [field.description, field.secret ? " Unlock with a generated key or decrypt the stored values first." : ""] }), field.secret && !field.required ? _jsxs("div", { class: "form-check mt-1", children: [_jsx("input", { class: "form-check-input", type: "checkbox", disabled: true, "data-bp-clear-secret": "", "data-service-id": service.serviceId, "data-scope": field.scope, "data-key": field.key, id: `bp-clear-${group.id}-${service.serviceId}-${field.scope}-${field.key}` }), _jsx("label", { class: "form-check-label small", for: `bp-clear-${group.id}-${service.serviceId}-${field.scope}-${field.key}`, children: "Clear stored value" })] }) : null] })) })] });
351
+ })] }))), _jsx("div", { class: "alert alert-secondary py-2", "data-bp-preview-config-status": "", role: "status", children: "Key is never submitted." }), _jsxs("div", { class: "d-flex flex-wrap gap-2", children: [_jsx("button", { class: "btn btn-primary", type: "submit", children: "Encrypt and save config" }), _jsx("button", { class: "btn btn-outline-secondary", type: "button", "data-bp-sync-prod": "", children: "Sync from prod" })] }), _jsxs("dialog", { class: "border-0 rounded shadow p-0", style: "width:min(92vw,72rem);max-height:90vh", "data-bp-sync-dialog": "", children: [_jsxs("div", { class: "p-3 border-bottom d-flex justify-content-between align-items-center", children: [_jsx("h3", { class: "h5 mb-0", children: "Production config diff" }), _jsx("button", { class: "btn-close", type: "button", "aria-label": "Close", "data-bp-sync-close": "" })] }), _jsx("div", { class: "p-3 overflow-auto", style: "max-height:70vh", "data-bp-sync-body": "" }), _jsxs("div", { class: "p-3 border-top d-flex justify-content-end gap-2", children: [_jsx("button", { class: "btn btn-outline-secondary", type: "button", "data-bp-sync-close": "", children: "Cancel" }), _jsx("button", { class: "btn btn-primary", type: "button", "data-bp-sync-apply": "", children: "Apply selected and save" })] })] })] }))] })] }));
322
352
  }
323
353
  export function render(data) {
324
- return (_jsxs("div", { class: "container-fluid px-0", children: [_jsxs("div", { class: "d-flex flex-wrap justify-content-between align-items-start gap-3 mb-4", children: [_jsxs("div", { children: [_jsx("h2", { class: "mb-1", children: data.title }), _jsx("p", { class: "text-secondary mb-0", children: "Clone an existing tenant/app configuration while CI owns where each service runs." })] }), _jsx("button", { class: "btn btn-primary", "data-bs-toggle": "offcanvas", "data-bs-target": "#bp-create-preview-group", children: "Create group" })] }), data.notice ? _jsx("div", { class: "alert alert-success", role: "status", children: data.notice }) : null, data.error ? _jsx("div", { class: "alert alert-danger", role: "alert", children: data.error }) : null, credentials(data), _jsxs("div", { class: "alert alert-info d-flex gap-2 align-items-start", children: [_jsx("span", { "aria-hidden": "true", children: "i" }), _jsxs("div", { children: [_jsx("strong", { children: "Pull setup only." }), " Services receive credentials from CI, publish their manifest on first sync, and rebuild preview routes/menu automatically."] })] }), data.groups.length === 0 ? (_jsx("div", { class: "card border-0 shadow-sm", children: _jsx("div", { class: "card-body py-5 text-center text-secondary", children: "No preview groups configured." }) })) : data.groups.map((group) => (_jsx("section", { class: "card border-0 shadow-sm mb-4", "aria-labelledby": `bp-group-${group.id}`, children: _jsxs("div", { class: "card-body", children: [_jsxs("div", { class: "d-flex flex-wrap justify-content-between align-items-start gap-3 mb-3", children: [_jsxs("div", { children: [_jsx("h3", { class: "h5 mb-1", id: `bp-group-${group.id}`, children: group.name }), _jsx("div", { class: "small text-secondary", children: group.sourceLabel }), _jsx("code", { class: "small", children: group.id })] }), _jsxs("div", { class: "d-flex flex-wrap gap-2", children: [_jsx("button", { class: "btn btn-sm btn-outline-primary", "data-bs-toggle": "collapse", "data-bs-target": `#bp-new-${group.id}`, children: "New preview" }), _jsx("button", { class: "btn btn-sm btn-outline-secondary", "data-bs-toggle": "collapse", "data-bs-target": `#bp-settings-${group.id}`, "hx-get": `${data.previewPath}?_c=config&groupId=${encodeURIComponent(group.id)}`, "hx-trigger": `click[!document.getElementById('bp-config-${group.id}').querySelector('form')]`, "hx-target": `#bp-config-${group.id}`, "hx-swap": "innerHTML", children: "Settings" }), _jsx("button", { class: "btn btn-sm btn-outline-danger", "hx-delete": `${data.previewPath}?entity=group&id=${encodeURIComponent(group.id)}`, "hx-confirm": `Delete ${group.name} and all of its previews?`, "hx-target": "#bp-main", "hx-swap": "innerHTML", children: "Delete" })] })] }), _jsxs("div", { class: "row g-3 mb-3", children: [_jsxs("div", { class: "col-12 col-xl-7", children: [_jsx("div", { class: "small text-secondary mb-1", children: "CI endpoint" }), _jsxs("code", { class: "d-block text-break", children: ["POST ", data.deploymentApiBase, "/", group.id, "/deployments/<key>"] })] }), _jsxs("div", { class: "col-12 col-xl-5", children: [_jsx("div", { class: "small text-secondary mb-1", children: "Discovered services" }), _jsx("div", { class: "d-flex flex-wrap gap-1", children: group.services.length ? group.services.map((service) => _jsx("span", { class: "badge text-bg-secondary", children: service.serviceId })) : _jsx("span", { class: "small text-secondary", children: "Added by the first preview create request." }) })] })] }), _jsx("div", { class: "collapse mb-4", id: `bp-new-${group.id}`, children: _jsxs("div", { class: "border rounded p-3 bg-body-tertiary", children: [_jsx("h4", { class: "h6", children: "Create preview manually" }), _jsxs("form", { "hx-post": data.previewPath, "hx-target": "#bp-main", "hx-swap": "innerHTML", children: [_jsx("input", { type: "hidden", name: "action", value: "create-deployment" }), _jsx("input", { type: "hidden", name: "groupId", value: group.id }), _jsxs("div", { class: "row g-3", children: [_jsxs("div", { class: "col-12 col-md-4", children: [_jsx("label", { class: "form-label", children: "Key" }), _jsx("input", { class: "form-control font-monospace", name: "key", maxlength: "255", pattern: "[A-Za-z0-9][A-Za-z0-9._:-]*", required: true })] }), _jsxs("div", { class: "col-12 col-md-4", children: [_jsx("label", { class: "form-label", children: "Name" }), _jsx("input", { class: "form-control", name: "name" })] }), _jsxs("div", { class: "col-12 col-md-4", children: [_jsx("label", { class: "form-label", children: "Expiry" }), expiryInput(undefined, true)] }), _jsxs("div", { class: "col-12", children: [_jsx("label", { class: "form-label", children: "App hostname or origin" }), _jsx("input", { class: "form-control font-monospace", name: "hostname", placeholder: "pr-123.preview.example.com", required: true })] }), _jsxs("div", { class: "col-12", children: [_jsx("label", { class: "form-label", for: `bp-preview-services-${group.id}`, children: "Services" }), _jsx("textarea", { id: `bp-preview-services-${group.id}`, class: "form-control font-monospace", name: "services", rows: Math.max(4, group.services.length + 2), required: true, children: JSON.stringify(Object.fromEntries(group.services.map((service) => [service.serviceId, ""])), null, 2) }), _jsx("div", { class: "form-text", children: "JSON object mapping service IDs to public HTTPS URLs." })] })] }), _jsx("button", { class: "btn btn-primary mt-3", type: "submit", children: "Create preview" })] })] }) }), _jsx("div", { class: "collapse mb-4", id: `bp-settings-${group.id}`, children: _jsxs("div", { class: "border rounded p-3", children: [_jsxs("form", { "hx-put": data.previewPath, "hx-target": "#bp-main", "hx-swap": "innerHTML", children: [_jsx("input", { type: "hidden", name: "action", value: "update-group" }), _jsx("input", { type: "hidden", name: "groupId", value: group.id }), _jsxs("div", { class: "row g-3 mb-3", children: [_jsxs("div", { class: "col-12 col-md-8", children: [_jsx("label", { class: "form-label", children: "Group name" }), _jsx("input", { class: "form-control", name: "name", value: group.name, required: true })] }), _jsxs("div", { class: "col-12 col-md-4", children: [_jsx("label", { class: "form-label", children: "Maximum/default expiry" }), expiryInput(group.expiresInDays)] })] }), _jsxs("div", { class: "mb-3", children: [_jsx("label", { class: "form-label", for: `bp-elevated-roles-${group.id}`, children: "Preview admin role IDs" }), _jsx("textarea", { id: `bp-elevated-roles-${group.id}`, class: "form-control", name: "elevatedRoleIds", rows: "3", maxlength: "12900", "aria-describedby": `bp-elevated-roles-help-${group.id}`, children: (group.elevatedRoleIds ?? []).join("\n") }), _jsx("div", { class: "form-text", id: `bp-elevated-roles-help-${group.id}`, children: "Defaults for newly created previews; existing previews keep their own role settings. One exact role ID per line (or comma-separated), e.g. admin, staff, client. Users with any listed role get full access within this group's previews only. Empty keeps normal permissions. Changes apply to existing previews on config sync." })] }), oidcFields(`bp-group-${group.id}`, group.oidc), _jsx("button", { class: "btn btn-primary", type: "submit", children: "Save settings" })] }), _jsx("hr", {}), _jsxs("form", { "hx-post": data.previewPath, "hx-target": "#bp-main", "hx-swap": "innerHTML", children: [_jsx("input", { type: "hidden", name: "action", value: "rotate-key" }), _jsx("input", { type: "hidden", name: "groupId", value: group.id }), _jsx("button", { class: "btn btn-outline-warning", type: "submit", "hx-confirm": "Rotate this group API key? Existing CI credentials will stop working.", children: "Rotate API key" })] }), _jsx("div", { id: `bp-config-${group.id}`, children: _jsx("div", { class: "text-secondary small mt-3", role: "status", children: "Open settings to load service configuration." }) })] }) }), group.deployments.length === 0 ? _jsx("div", { class: "text-secondary small", children: "No active previews." }) : (_jsx("div", { class: "table-responsive", children: _jsxs("table", { class: "table table-sm align-middle mb-0", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "Preview" }), _jsx("th", { children: "App" }), _jsx("th", { children: "Services" }), _jsx("th", { children: "Expiry" }), _jsx("th", {})] }) }), _jsx("tbody", { children: group.deployments.map((deployment) => (_jsxs("tr", { children: [_jsxs("td", { children: [_jsx("div", { class: "fw-semibold", children: deployment.name }), _jsx("code", { class: "small", children: deployment.key }), _jsx("div", { children: _jsx("span", { class: `badge ${deployment.ready ? "text-bg-success" : "text-bg-warning"}`, children: deployment.ready ? "Configured" : "Needs attention" }) })] }), _jsx("td", { children: _jsx("a", { href: deployment.hostname.startsWith("http") ? deployment.hostname : `https://${deployment.hostname}`, target: "_blank", rel: "noreferrer", children: deployment.hostname }) }), _jsx("td", { children: deployment.services.map((service) => _jsxs("div", { class: "small", children: [_jsx("span", { class: `badge ${service.ready ? "text-bg-success" : "text-bg-warning"}`, children: service.status.state }), " ", _jsx("span", { class: "font-monospace", children: service.serviceId })] })) }), _jsxs("td", { children: [_jsxs("form", { class: "d-flex gap-2", "hx-put": data.previewPath, "hx-target": "#bp-main", "hx-swap": "innerHTML", children: [_jsx("input", { type: "hidden", name: "action", value: "update-expiry" }), _jsx("input", { type: "hidden", name: "deploymentId", value: deployment.id }), expiryInput(deployment.expiresInDays, false, `Expiry for ${deployment.name}`), _jsx("button", { class: "btn btn-sm btn-outline-secondary", type: "submit", children: "Save" })] }), _jsx("div", { class: "small text-secondary mt-1", children: deployment.expiresAt ? new Date(deployment.expiresAt).toLocaleString() : "Never expires" })] }), _jsxs("td", { children: [_jsx("button", { type: "button", class: "btn btn-sm btn-outline-secondary me-2", "data-bs-toggle": "offcanvas", "data-bs-target": "#bp-preview-debug", "hx-get": `${data.previewPath}?_c=debug&deploymentId=${encodeURIComponent(deployment.id)}`, "hx-target": "#bp-preview-debug-body", "hx-swap": "innerHTML", children: "Debug" }), _jsx("button", { class: "btn btn-sm btn-outline-danger", "hx-delete": `${data.previewPath}?entity=deployment&id=${encodeURIComponent(deployment.id)}`, "hx-confirm": `Delete preview ${deployment.name}?`, "hx-target": "#bp-main", "hx-swap": "innerHTML", children: "Delete" })] })] }))) })] }) }))] }) }))), _jsxs("aside", { class: "offcanvas offcanvas-end", tabindex: -1, id: "bp-preview-debug", "aria-labelledby": "bp-preview-debug-title", style: "width:min(95vw,52rem)", children: [_jsxs("div", { class: "offcanvas-header", children: [_jsx("h3", { class: "offcanvas-title h5", id: "bp-preview-debug-title", children: "Preview diagnostics" }), _jsx("button", { type: "button", class: "btn-close", "data-bs-dismiss": "offcanvas", "aria-label": "Close" })] }), _jsx("div", { class: "offcanvas-body", id: "bp-preview-debug-body", "aria-live": "polite", children: "Select a deployment to load diagnostics." })] }), _jsxs("aside", { class: "offcanvas offcanvas-end", tabindex: -1, id: "bp-create-preview-group", "aria-labelledby": "bp-create-preview-group-title", children: [_jsxs("div", { class: "offcanvas-header", children: [_jsx("h3", { class: "offcanvas-title h5", id: "bp-create-preview-group-title", children: "Create preview group" }), _jsx("button", { type: "button", class: "btn-close", "data-bs-dismiss": "offcanvas", "aria-label": "Close" })] }), _jsx("div", { class: "offcanvas-body", children: _jsxs("form", { "hx-post": data.previewPath, "hx-target": "#bp-main", "hx-swap": "innerHTML", children: [_jsx("input", { type: "hidden", name: "action", value: "create-group" }), _jsxs("div", { class: "mb-3", children: [_jsx("label", { class: "form-label", for: "bp-preview-group-name", children: "Name" }), _jsx("input", { id: "bp-preview-group-name", class: "form-control", name: "name", maxlength: "120", required: true })] }), _jsxs("div", { class: "mb-3", children: [_jsx("label", { class: "form-label", for: "bp-preview-source-tenant", children: "Source tenant" }), _jsxs("select", { id: "bp-preview-source-tenant", class: "form-select", name: "sourceTenantId", required: true, children: [_jsx("option", { value: "", children: "Select tenant" }), data.sourceTenants.map((tenant) => _jsx("option", { value: tenant.id, children: tenant.title }))] })] }), _jsxs("div", { class: "mb-3", children: [_jsx("label", { class: "form-label", for: "bp-preview-source-app", children: "Source app" }), _jsxs("select", { id: "bp-preview-source-app", class: "form-select", name: "sourceAppId", required: true, children: [_jsx("option", { value: "", children: "Select app" }), data.sourceApps.map((app) => _jsx("option", { value: app.id, "data-tenant-id": app.tenantId, children: app.title }))] })] }), _jsxs("div", { class: "mb-3", children: [_jsx("label", { class: "form-label", children: "Maximum/default expiry" }), expiryInput(30)] }), oidcFields("bp-new-group"), _jsxs("div", { class: "mb-3", children: [_jsx("label", { class: "form-label", for: "bp-new-elevated-roles", children: "Preview admin role IDs" }), _jsx("textarea", { id: "bp-new-elevated-roles", class: "form-control", name: "elevatedRoleIds", rows: "3", maxlength: "12900", placeholder: "admin, staff, client", "aria-describedby": "bp-new-elevated-roles-help" }), _jsx("div", { class: "form-text", id: "bp-new-elevated-roles-help", children: "One exact role ID per line or comma-separated. Grants full access only inside this group's previews; it does not assign roles to users or grant platform-admin access. Empty keeps normal permissions." })] }), _jsx("button", { class: "btn btn-primary w-100", type: "submit", children: "Create group" })] }) })] }), _jsx("script", { children: pageScript() })] }));
354
+ return (_jsxs("div", { class: "container-fluid px-0", children: [_jsxs("div", { class: "d-flex flex-wrap justify-content-between align-items-start gap-3 mb-4", children: [_jsxs("div", { children: [_jsx("h2", { class: "mb-1", children: data.title }), _jsx("p", { class: "text-secondary mb-0", children: "Clone an existing tenant/app configuration while CI owns where each service runs." })] }), _jsx("button", { class: "btn btn-primary", "data-bs-toggle": "offcanvas", "data-bs-target": "#bp-create-preview-group", children: "Create group" })] }), data.notice ? _jsx("div", { class: "alert alert-success", role: "status", children: data.notice }) : null, data.error ? _jsx("div", { class: "alert alert-danger", role: "alert", children: data.error }) : null, credentials(data), _jsxs("div", { class: "alert alert-info d-flex gap-2 align-items-start", children: [_jsx("span", { "aria-hidden": "true", children: "i" }), _jsxs("div", { children: [_jsx("strong", { children: "Pull setup only." }), " Services receive credentials from CI, publish their manifest on first sync, and rebuild preview routes/menu automatically."] })] }), data.groups.length === 0 ? (_jsx("div", { class: "card border-0 shadow-sm", children: _jsx("div", { class: "card-body py-5 text-center text-secondary", children: "No preview groups configured." }) })) : data.groups.map((group) => (_jsx("section", { class: "card border-0 shadow-sm mb-4", "aria-labelledby": `bp-group-${group.id}`, children: _jsxs("div", { class: "card-body", children: [_jsxs("div", { class: "d-flex flex-wrap justify-content-between align-items-start gap-3 mb-3", children: [_jsxs("div", { children: [_jsx("h3", { class: "h5 mb-1", id: `bp-group-${group.id}`, children: group.name }), _jsx("div", { class: "small text-secondary", children: group.sourceLabel }), _jsx("code", { class: "small", children: group.id })] }), _jsxs("div", { class: "d-flex flex-wrap gap-2", children: [_jsx("button", { class: "btn btn-sm btn-outline-primary", "data-bs-toggle": "collapse", "data-bs-target": `#bp-new-${group.id}`, children: "New preview" }), _jsx("button", { class: "btn btn-sm btn-outline-secondary", "data-bs-toggle": "collapse", "data-bs-target": `#bp-settings-${group.id}`, "hx-get": `${data.previewPath}?_c=config&groupId=${encodeURIComponent(group.id)}`, "hx-trigger": `click[!document.getElementById('bp-config-${group.id}').querySelector('form')]`, "hx-target": `#bp-config-${group.id}`, "hx-swap": "innerHTML", children: "Settings" }), _jsx("button", { class: "btn btn-sm btn-outline-danger", "hx-delete": `${data.previewPath}?entity=group&id=${encodeURIComponent(group.id)}`, "hx-confirm": `Delete ${group.name} and all of its previews?`, "hx-target": "#bp-main", "hx-swap": "innerHTML", children: "Delete" })] })] }), _jsxs("div", { class: "row g-3 mb-3", children: [_jsxs("div", { class: "col-12 col-xl-7", children: [_jsx("div", { class: "small text-secondary mb-1", children: "CI endpoint" }), _jsxs("code", { class: "d-block text-break", children: ["POST ", data.deploymentApiBase, "/", group.id, "/deployments/<key>"] })] }), _jsxs("div", { class: "col-12 col-xl-5", children: [_jsx("div", { class: "small text-secondary mb-1", children: "BP plugins" }), _jsx("div", { class: "d-flex flex-wrap gap-1", children: group.services.length ? group.services.map((service) => _jsx("span", { class: "badge text-bg-secondary", children: service.serviceId })) : _jsx("span", { class: "small text-secondary", children: "Added by the first preview create request." }) })] })] }), _jsx("div", { class: "collapse mb-4", id: `bp-new-${group.id}`, children: _jsxs("div", { class: "border rounded p-3 bg-body-tertiary", children: [_jsx("h4", { class: "h6", children: "Create preview manually" }), _jsxs("form", { "hx-post": data.previewPath, "hx-target": "#bp-main", "hx-swap": "innerHTML", children: [_jsx("input", { type: "hidden", name: "action", value: "create-deployment" }), _jsx("input", { type: "hidden", name: "groupId", value: group.id }), _jsxs("div", { class: "row g-3", children: [_jsxs("div", { class: "col-12 col-md-4", children: [_jsx("label", { class: "form-label", children: "Key" }), _jsx("input", { class: "form-control font-monospace", name: "key", maxlength: "255", pattern: "[A-Za-z0-9][A-Za-z0-9._:-]*", required: true })] }), _jsxs("div", { class: "col-12 col-md-4", children: [_jsx("label", { class: "form-label", children: "Name" }), _jsx("input", { class: "form-control", name: "name" })] }), _jsxs("div", { class: "col-12 col-md-4", children: [_jsx("label", { class: "form-label", children: "Expiry" }), expiryInput(undefined, true)] }), _jsxs("div", { class: "col-12", children: [_jsx("label", { class: "form-label", children: "App hostname or origin" }), _jsx("input", { class: "form-control font-monospace", name: "hostname", placeholder: "pr-123.preview.example.com", required: true })] }), _jsxs("div", { class: "col-12", children: [_jsx("label", { class: "form-label", for: `bp-preview-services-${group.id}`, children: "Services" }), _jsx("textarea", { id: `bp-preview-services-${group.id}`, class: "form-control font-monospace", name: "services", rows: Math.max(4, group.services.length + 2), required: true, children: JSON.stringify(Object.fromEntries(group.services.map((service) => [service.serviceId, ""])), null, 2) }), _jsx("div", { class: "form-text", children: "JSON object mapping service IDs to public HTTPS URLs." })] })] }), _jsx("button", { class: "btn btn-primary mt-3", type: "submit", children: "Create preview" })] })] }) }), _jsx("div", { class: "collapse mb-4", id: `bp-settings-${group.id}`, children: _jsxs("div", { class: "border rounded p-3", children: [_jsxs("form", { "hx-put": data.previewPath, "hx-target": "#bp-main", "hx-swap": "innerHTML", children: [_jsx("input", { type: "hidden", name: "action", value: "update-group" }), _jsx("input", { type: "hidden", name: "groupId", value: group.id }), _jsxs("div", { class: "row g-3 mb-3", children: [_jsxs("div", { class: "col-12 col-md-8", children: [_jsx("label", { class: "form-label", children: "Group name" }), _jsx("input", { class: "form-control", name: "name", value: group.name, required: true })] }), _jsxs("div", { class: "col-12 col-md-4", children: [_jsx("label", { class: "form-label", children: "Maximum/default expiry" }), expiryInput(group.expiresInDays)] })] }), _jsxs("div", { class: "mb-3", children: [_jsx("label", { class: "form-label", for: `bp-elevated-roles-${group.id}`, children: "Preview admin role IDs" }), _jsx("textarea", { id: `bp-elevated-roles-${group.id}`, class: "form-control", name: "elevatedRoleIds", rows: "3", maxlength: "12900", "aria-describedby": `bp-elevated-roles-help-${group.id}`, children: (group.elevatedRoleIds ?? []).join("\n") }), _jsx("div", { class: "form-text", id: `bp-elevated-roles-help-${group.id}`, children: "Defaults for newly created previews; existing previews keep their own role settings. One exact role ID per line (or comma-separated), e.g. admin, staff, client. Users with any listed role get full access within this group's previews only. Empty keeps normal permissions. Changes apply to existing previews on config sync." })] }), oidcFields(`bp-group-${group.id}`, group.oidc), _jsx("button", { class: "btn btn-primary", type: "submit", children: "Save settings" })] }), _jsx("hr", {}), _jsxs("form", { "hx-post": data.previewPath, "hx-target": "#bp-main", "hx-swap": "innerHTML", children: [_jsx("input", { type: "hidden", name: "action", value: "rotate-key" }), _jsx("input", { type: "hidden", name: "groupId", value: group.id }), _jsx("button", { class: "btn btn-outline-warning", type: "submit", "hx-confirm": "Rotate this group API key? Existing CI credentials will stop working.", children: "Rotate API key" })] }), _jsx("div", { id: `bp-config-${group.id}`, children: _jsx("div", { class: "text-secondary small mt-3", role: "status", children: "Open settings to load service configuration." }) })] }) }), group.deployments.length === 0 ? _jsx("div", { class: "text-secondary small", children: "No active previews." }) : (_jsx("div", { class: "table-responsive", children: _jsxs("table", { class: "table table-sm align-middle mb-0", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "Preview" }), _jsx("th", { children: "App" }), _jsx("th", { children: "Services" }), _jsx("th", { children: "Expiry" }), _jsx("th", {})] }) }), _jsx("tbody", { children: group.deployments.map((deployment) => (_jsxs("tr", { children: [_jsxs("td", { children: [_jsx("div", { class: "fw-semibold", children: deployment.name }), _jsx("code", { class: "small", children: deployment.key }), _jsx("div", { children: _jsx("span", { class: `badge ${deployment.ready ? "text-bg-success" : "text-bg-warning"}`, children: deployment.ready ? "Configured" : "Needs attention" }) })] }), _jsx("td", { children: _jsx("a", { href: deployment.hostname.startsWith("http") ? deployment.hostname : `https://${deployment.hostname}`, target: "_blank", rel: "noreferrer", children: deployment.hostname }) }), _jsx("td", { children: deployment.services.map((service) => _jsxs("div", { class: "small", children: [_jsx("span", { class: `badge ${service.ready ? "text-bg-success" : "text-bg-warning"}`, children: service.status.state }), " ", _jsx("span", { class: "font-monospace", children: service.serviceId })] })) }), _jsxs("td", { children: [_jsxs("form", { class: "d-flex gap-2", "hx-put": data.previewPath, "hx-target": "#bp-main", "hx-swap": "innerHTML", children: [_jsx("input", { type: "hidden", name: "action", value: "update-expiry" }), _jsx("input", { type: "hidden", name: "deploymentId", value: deployment.id }), expiryInput(deployment.expiresInDays, false, `Expiry for ${deployment.name}`), _jsx("button", { class: "btn btn-sm btn-outline-secondary", type: "submit", children: "Save" })] }), _jsx("div", { class: "small text-secondary mt-1", children: deployment.expiresAt ? new Date(deployment.expiresAt).toLocaleString() : "Never expires" })] }), _jsxs("td", { children: [_jsx("button", { type: "button", class: "btn btn-sm btn-outline-secondary me-2", "data-bs-toggle": "offcanvas", "data-bs-target": "#bp-preview-debug", "hx-get": `${data.previewPath}?_c=debug&deploymentId=${encodeURIComponent(deployment.id)}`, "hx-target": "#bp-preview-debug-body", "hx-swap": "innerHTML", children: "Debug" }), _jsx("button", { class: "btn btn-sm btn-outline-danger", "hx-delete": `${data.previewPath}?entity=deployment&id=${encodeURIComponent(deployment.id)}`, "hx-confirm": `Delete preview ${deployment.name}?`, "hx-target": "#bp-main", "hx-swap": "innerHTML", children: "Delete" })] })] }))) })] }) }))] }) }))), _jsxs("aside", { class: "offcanvas offcanvas-end", tabindex: -1, id: "bp-preview-debug", "aria-labelledby": "bp-preview-debug-title", style: "width:min(95vw,52rem)", children: [_jsxs("div", { class: "offcanvas-header", children: [_jsx("h3", { class: "offcanvas-title h5", id: "bp-preview-debug-title", children: "Preview diagnostics" }), _jsx("button", { type: "button", class: "btn-close", "data-bs-dismiss": "offcanvas", "aria-label": "Close" })] }), _jsx("div", { class: "offcanvas-body", id: "bp-preview-debug-body", "aria-live": "polite", children: "Select a deployment to load diagnostics." })] }), _jsxs("aside", { class: "offcanvas offcanvas-end", tabindex: -1, id: "bp-create-preview-group", "aria-labelledby": "bp-create-preview-group-title", children: [_jsxs("div", { class: "offcanvas-header", children: [_jsx("h3", { class: "offcanvas-title h5", id: "bp-create-preview-group-title", children: "Create preview group" }), _jsx("button", { type: "button", class: "btn-close", "data-bs-dismiss": "offcanvas", "aria-label": "Close" })] }), _jsx("div", { class: "offcanvas-body", children: _jsxs("form", { "hx-post": data.previewPath, "hx-target": "#bp-main", "hx-swap": "innerHTML", children: [_jsx("input", { type: "hidden", name: "action", value: "create-group" }), _jsxs("div", { class: "mb-3", children: [_jsx("label", { class: "form-label", for: "bp-preview-group-name", children: "Name" }), _jsx("input", { id: "bp-preview-group-name", class: "form-control", name: "name", maxlength: "120", required: true })] }), _jsxs("div", { class: "mb-3", children: [_jsx("label", { class: "form-label", for: "bp-preview-source-tenant", children: "Source tenant" }), _jsxs("select", { id: "bp-preview-source-tenant", class: "form-select", name: "sourceTenantId", required: true, children: [_jsx("option", { value: "", children: "Select tenant" }), data.sourceTenants.map((tenant) => _jsx("option", { value: tenant.id, children: tenant.title }))] })] }), _jsxs("div", { class: "mb-3", children: [_jsx("label", { class: "form-label", for: "bp-preview-source-app", children: "Source app" }), _jsxs("select", { id: "bp-preview-source-app", class: "form-select", name: "sourceAppId", required: true, children: [_jsx("option", { value: "", children: "Select app" }), data.sourceApps.map((app) => _jsx("option", { value: app.id, "data-tenant-id": app.tenantId, children: app.title }))] })] }), _jsxs("div", { class: "mb-3", children: [_jsx("label", { class: "form-label", children: "Maximum/default expiry" }), expiryInput(30)] }), _jsxs("div", { class: "mb-3", children: [_jsx("label", { class: "form-label", for: "bp-new-plugin-ids", children: "BP plugin IDs" }), _jsx("textarea", { id: "bp-new-plugin-ids", class: "form-control font-monospace", name: "pluginIds", rows: 3, placeholder: "org.example.service" })] }), oidcFields("bp-new-group"), _jsxs("div", { class: "mb-3", children: [_jsx("label", { class: "form-label", for: "bp-new-elevated-roles", children: "Preview admin role IDs" }), _jsx("textarea", { id: "bp-new-elevated-roles", class: "form-control", name: "elevatedRoleIds", rows: "3", maxlength: "12900", placeholder: "admin, staff, client", "aria-describedby": "bp-new-elevated-roles-help" }), _jsx("div", { class: "form-text", id: "bp-new-elevated-roles-help", children: "One exact role ID per line or comma-separated. Grants full access only inside this group's previews; it does not assign roles to users or grant platform-admin access. Empty keeps normal permissions." })] }), _jsx("button", { class: "btn btn-primary w-100", type: "submit", children: "Create group" })] }) })] }), _jsx("script", { children: pageScript() })] }));
325
355
  }
326
356
  //# sourceMappingURL=GET.js.map