@betterportal/config-manager 10.6.30 → 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 +6 -0
- package/lib/plugins/service-betterportal-config-manager/index.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/index.js +83 -23
- 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 +12 -5
- 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 +19 -2
- package/lib/plugins/service-betterportal-config-manager/storage/postgres.d.ts.map +1 -1
- package/lib/plugins/service-betterportal-config-manager/storage/postgres.js +74 -22
- 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
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { htmlResponse, jsonResponse, uuidv7 } from "@betterportal/framework";
|
|
2
|
-
import { getManifestCache } from "./syncApi.js";
|
|
1
|
+
import { eventObservability, menuItemVisible, htmlResponse, jsonResponse, uuidv7 } from "@betterportal/framework";
|
|
2
|
+
import { getManifestCache, injectResolvedServicePaths } from "./syncApi.js";
|
|
3
3
|
import { isApiRoute, isMenuRouteExcluded } from "./routeMounts.js";
|
|
4
|
+
import { editAppRows, readApp } from "./appQueries.js";
|
|
4
5
|
const API_BASE = "/.well-known/bp/admin";
|
|
5
6
|
// Parse-only base for relative request URLs. Never emit this origin.
|
|
6
7
|
const RELATIVE_URL_PARSE_BASE = "http://betterportal.invalid";
|
|
@@ -124,12 +125,12 @@ function titleDisplayHtml(item, route, appId) {
|
|
|
124
125
|
style="border:1px solid var(--bs-border-color); border-radius:0.375rem; padding:0.25rem 0.6rem; background:transparent; cursor:text; text-align:left; min-width:200px; font-weight:500;"
|
|
125
126
|
hx-get="${API_BASE}/menu-editor/item?appId=${encodeURIComponent(appId)}&itemId=${encodeURIComponent(item.id)}&mode=edit-title"
|
|
126
127
|
hx-target="#bp-menu-title-${item.id}"
|
|
127
|
-
hx-swap="outerHTML"
|
|
128
|
+
hx-swap="outerHTML settle:0"
|
|
128
129
|
title="Click to rename">${escapeHtml(titleText)}</button>`;
|
|
129
130
|
}
|
|
130
131
|
function titleEditorHtml(item, route, appId) {
|
|
131
132
|
return `<form id="bp-menu-title-${item.id}" data-bp-menu-editing
|
|
132
|
-
hx-post="${API_BASE}/menu-editor/save-title" hx-target="#bp-menu-title-${item.id}" hx-swap="outerHTML"
|
|
133
|
+
hx-post="${API_BASE}/menu-editor/save-title" hx-target="#bp-menu-title-${item.id}" hx-swap="outerHTML settle:0"
|
|
133
134
|
class="d-flex align-items-center gap-1 flex-grow-1 m-0">
|
|
134
135
|
<input type="hidden" name="appId" value="${escapeHtml(appId)}" />
|
|
135
136
|
<input type="hidden" name="itemId" value="${escapeHtml(item.id)}" />
|
|
@@ -137,7 +138,7 @@ function titleEditorHtml(item, route, appId) {
|
|
|
137
138
|
<button type="submit" class="btn btn-sm btn-success" title="Save">OK</button>
|
|
138
139
|
<button type="button" class="btn btn-sm btn-outline-secondary" title="Cancel"
|
|
139
140
|
hx-get="${API_BASE}/menu-editor/item?appId=${encodeURIComponent(appId)}&itemId=${encodeURIComponent(item.id)}&mode=display-title"
|
|
140
|
-
hx-target="#bp-menu-title-${item.id}" hx-swap="outerHTML">X</button>
|
|
141
|
+
hx-target="#bp-menu-title-${item.id}" hx-swap="outerHTML settle:0">X</button>
|
|
141
142
|
</form>`;
|
|
142
143
|
}
|
|
143
144
|
function subLineHtml(item, route, config, appId) {
|
|
@@ -154,7 +155,7 @@ function subLineHtml(item, route, config, appId) {
|
|
|
154
155
|
<button type="button" class="btn btn-sm btn-link p-0"
|
|
155
156
|
hx-get="${API_BASE}/menu-editor/item?appId=${encodeURIComponent(appId)}&itemId=${encodeURIComponent(item.id)}&mode=edit-external"
|
|
156
157
|
hx-target="#bp-menu-row-${item.id}"
|
|
157
|
-
hx-swap="outerHTML"
|
|
158
|
+
hx-swap="outerHTML settle:0"
|
|
158
159
|
title="Edit URL">Edit</button>
|
|
159
160
|
</div>`;
|
|
160
161
|
}
|
|
@@ -173,12 +174,12 @@ function subLineHtml(item, route, config, appId) {
|
|
|
173
174
|
<button type="button" class="btn btn-sm btn-link p-0 ms-1"
|
|
174
175
|
hx-get="${API_BASE}/menu-editor/item?appId=${encodeURIComponent(appId)}&itemId=${encodeURIComponent(item.id)}&mode=edit-link"
|
|
175
176
|
hx-target="#bp-menu-row-${item.id}"
|
|
176
|
-
hx-swap="outerHTML"
|
|
177
|
+
hx-swap="outerHTML settle:0"
|
|
177
178
|
title="Edit URL & paths">Edit</button>
|
|
178
179
|
</div>`;
|
|
179
180
|
}
|
|
180
181
|
function actionButtons(item, appId) {
|
|
181
|
-
const btn = (action, label, btnClass, title) => `<form hx-post="${API_BASE}/menu-editor/${action}" hx-target="#bp-menu-editor" hx-swap="outerHTML" class="d-inline m-0">
|
|
182
|
+
const btn = (action, label, btnClass, title) => `<form hx-post="${API_BASE}/menu-editor/${action}" hx-target="#bp-menu-editor" hx-swap="outerHTML settle:0" class="d-inline m-0">
|
|
182
183
|
<input type="hidden" name="appId" value="${escapeHtml(appId)}" />
|
|
183
184
|
<input type="hidden" name="itemId" value="${escapeHtml(item.id)}" />
|
|
184
185
|
<button type="submit" class="btn btn-sm ${btnClass}" title="${escapeHtml(title)}">${label}</button>
|
|
@@ -192,13 +193,51 @@ function actionButtons(item, appId) {
|
|
|
192
193
|
${btn("remove", "x", "btn-outline-danger", "Remove")}
|
|
193
194
|
</div>`;
|
|
194
195
|
}
|
|
195
|
-
function
|
|
196
|
+
function visibilityControls(item) {
|
|
197
|
+
const options = [["auto", "Automatic"], ["show", "Everyone"], ["hide-unauthenticated", "Signed in"],
|
|
198
|
+
["show-unauthenticated", "Only signed out"], ["hide-unauthorized", "Has permission"]];
|
|
199
|
+
return `<fieldset class="mb-0"><legend class="small mb-1">Audience</legend>
|
|
200
|
+
<div class="d-flex flex-wrap gap-1">${options.map(([value, label]) => {
|
|
201
|
+
const id = `bp-audience-${item.id}-${value}`;
|
|
202
|
+
return `<input type="radio" class="btn-check" name="authStatus" id="${id}" value="${value}"${(item.authStatus ?? "auto") === value ? " checked" : ""} />
|
|
203
|
+
<label class="btn btn-sm btn-outline-primary" style="--bs-btn-color:var(--bs-body-color)" for="${id}">${label}</label>`;
|
|
204
|
+
}).join("")}</div></fieldset>
|
|
205
|
+
${item.type === "link" ? `<fieldset class="mb-0"><legend class="small mb-1">When service is unavailable</legend>
|
|
206
|
+
${[["show", "Show warning"], ["hide", "Hide"]].map(([value, label]) => `<label class="form-check form-check-inline small mb-0">
|
|
207
|
+
<input type="radio" class="form-check-input" name="serviceStatus" value="${value}"${(item.serviceStatus ?? "show") === value ? " checked" : ""} />${label}</label>`).join("")}</fieldset>` : ""}
|
|
208
|
+
${item.type !== "link" && item.rolesAnyOf?.length ? `<div class="small text-warning">Legacy role restriction: ${escapeHtml(item.rolesAnyOf.join(", "))}.
|
|
209
|
+
<label><input type="checkbox" name="clearRoleRestriction" value="true" /> Remove this restriction</label></div>` : ""}`;
|
|
210
|
+
}
|
|
211
|
+
function accessSummary(config, appDef, item, accessApp) {
|
|
212
|
+
if (item.type !== "link")
|
|
213
|
+
return item.type === "group" ? "Hidden when it has no visible children." : "Audience controls visibility; no linked route permissions.";
|
|
214
|
+
const app = accessApp ?? injectResolvedServicePaths({ tenants: config.tenants, apps: [appDef], managementOrigins: [] }).apps[0];
|
|
215
|
+
const route = app.routes.find(route => route.id === item.routeId);
|
|
216
|
+
if (!route || route.authRequired === undefined || route.menuPermissions === undefined)
|
|
217
|
+
return "Route access unknown until its service syncs. Automatic visibility stays hidden.";
|
|
218
|
+
if (!route.authRequired)
|
|
219
|
+
return "Public route. Access is derived automatically.";
|
|
220
|
+
if (!route.menuPermissions.length)
|
|
221
|
+
return "Requires sign-in. No additional route permissions.";
|
|
222
|
+
const aliases = Object.fromEntries(config.tenants.flatMap((tenant) => tenant.services.map((service) => [service.id, service.serviceId ?? service.id])));
|
|
223
|
+
for (const service of config.platformServices ?? [])
|
|
224
|
+
aliases[service.id] = service.serviceId ?? service.id;
|
|
225
|
+
for (const activation of config.sharedServiceActivations ?? []) {
|
|
226
|
+
const service = config.sharedServiceCatalog?.find((candidate) => candidate.id === activation.sharedServiceId);
|
|
227
|
+
if (service)
|
|
228
|
+
aliases[activation.id] = service.serviceId ?? service.id;
|
|
229
|
+
}
|
|
230
|
+
const roles = (app.auth?.roles ?? []).filter(role => role.id !== "*" && menuItemVisible({ authStatus: "hide-unauthorized" }, route, { status: "authenticated", permissions: role.permissions }, aliases));
|
|
231
|
+
const requirements = route.menuPermissions.map(requirement => `${requirement.viewId}: ${requirement.permissions.join(", ")}`).join("; ");
|
|
232
|
+
return `Route permissions: ${escapeHtml(requirements)}. ${roles.length ? `Roles with access: ${escapeHtml(roles.map(role => role.title).join(", "))}.` : "Access may require a combination of assigned roles."}`;
|
|
233
|
+
}
|
|
234
|
+
function renderRow(item, depth, mode, config, appDef, appId, accessApp) {
|
|
196
235
|
const routes = getRoutes(appDef);
|
|
197
236
|
const route = item.routeId ? routes.find((r) => r.id === item.routeId) ?? null : null;
|
|
198
237
|
const typeBadgeClass = item.type === "group" ? "text-bg-warning" : item.type === "external" ? "text-bg-info" : item.type === "link" ? "text-bg-primary" : "text-bg-secondary";
|
|
199
238
|
if (mode === "edit-external" && item.type === "external") {
|
|
200
239
|
return `<li ${rowAttrs(item, depth, true)} class="list-group-item">
|
|
201
|
-
<form hx-post="${API_BASE}/menu-editor/save-external" hx-target="#bp-menu-row-${item.id}" hx-swap="outerHTML"
|
|
240
|
+
<form hx-post="${API_BASE}/menu-editor/save-external" hx-target="#bp-menu-row-${item.id}" hx-swap="outerHTML settle:0"
|
|
202
241
|
class="d-flex flex-column gap-2">
|
|
203
242
|
<div class="d-flex align-items-center gap-2">
|
|
204
243
|
<span class="badge ${typeBadgeClass}">${escapeHtml(item.type)}</span>
|
|
@@ -208,32 +247,12 @@ function renderRow(item, depth, mode, config, appDef, appId) {
|
|
|
208
247
|
<input type="hidden" name="itemId" value="${escapeHtml(item.id)}" />
|
|
209
248
|
<label class="form-label small mb-0">URL</label>
|
|
210
249
|
<input type="url" name="href" class="form-control form-control-sm" value="${escapeHtml(item.href ?? "")}" placeholder="https://..." required />
|
|
211
|
-
|
|
212
|
-
<div class="col-md-6">
|
|
213
|
-
<label class="form-label small mb-0">Unavailable service</label>
|
|
214
|
-
<select name="serviceStatus" class="form-select form-select-sm">
|
|
215
|
-
<option value="show"${item.serviceStatus !== "hide" ? " selected" : ""}>Show with warning</option>
|
|
216
|
-
<option value="hide"${item.serviceStatus === "hide" ? " selected" : ""}>Hide</option>
|
|
217
|
-
</select>
|
|
218
|
-
</div>
|
|
219
|
-
<div class="col-md-6">
|
|
220
|
-
<label class="form-label small mb-0">Required roles (any, comma separated)</label>
|
|
221
|
-
<input name="rolesAnyOf" class="form-control form-control-sm" value="${escapeHtml((item.rolesAnyOf ?? []).join(", "))}" />
|
|
222
|
-
<label class="form-label small mb-0">Authorization</label>
|
|
223
|
-
<select name="authStatus" class="form-select form-select-sm">
|
|
224
|
-
<option value="auto"${!item.authStatus || item.authStatus === "auto" ? " selected" : ""}>Automatic (route permissions)</option>
|
|
225
|
-
<option value="show"${item.authStatus === "show" ? " selected" : ""}>Always show</option>
|
|
226
|
-
<option value="hide-unauthenticated"${item.authStatus === "hide-unauthenticated" ? " selected" : ""}>Hide when signed out</option>
|
|
227
|
-
<option value="hide-unauthorized"${item.authStatus === "hide-unauthorized" ? " selected" : ""}>Hide when unauthorized</option>
|
|
228
|
-
<option value="show-unauthenticated"${item.authStatus === "show-unauthenticated" ? " selected" : ""}>Show only when signed out</option>
|
|
229
|
-
</select>
|
|
230
|
-
</div>
|
|
231
|
-
</div>
|
|
250
|
+
${visibilityControls(item)}
|
|
232
251
|
<div class="d-flex gap-2 justify-content-end">
|
|
233
252
|
<button type="submit" class="btn btn-sm btn-success">OK Save</button>
|
|
234
253
|
<button type="button" class="btn btn-sm btn-outline-secondary"
|
|
235
254
|
hx-get="${API_BASE}/menu-editor/item?appId=${encodeURIComponent(appId)}&itemId=${encodeURIComponent(item.id)}&mode=display"
|
|
236
|
-
hx-target="#bp-menu-row-${item.id}" hx-swap="outerHTML">X Cancel</button>
|
|
255
|
+
hx-target="#bp-menu-row-${item.id}" hx-swap="outerHTML settle:0">X Cancel</button>
|
|
237
256
|
</div>
|
|
238
257
|
</form>
|
|
239
258
|
</li>`;
|
|
@@ -248,21 +267,13 @@ function renderRow(item, depth, mode, config, appDef, appId) {
|
|
|
248
267
|
${titleDisplayHtml(item, route, appId)}
|
|
249
268
|
</div>
|
|
250
269
|
${subLineHtml(item, route, config, appId)}
|
|
251
|
-
<
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
</select>
|
|
259
|
-
</label>
|
|
260
|
-
<label class="small">Required roles (any, comma separated)
|
|
261
|
-
<input name="rolesAnyOf" class="form-control form-control-sm" value="${escapeHtml((item.rolesAnyOf ?? []).join(", "))}" />
|
|
262
|
-
</label>
|
|
263
|
-
<button type="submit" class="btn btn-sm btn-primary">Save visibility</button>
|
|
264
|
-
</form>
|
|
265
|
-
</details>
|
|
270
|
+
<div class="small text-body-secondary">${accessSummary(config, appDef, item, accessApp)}</div>
|
|
271
|
+
<form hx-post="${API_BASE}/menu-editor/save-visibility" hx-target="#bp-menu-editor" hx-swap="outerHTML settle:0" class="d-flex flex-column gap-2 mt-2">
|
|
272
|
+
<input type="hidden" name="appId" value="${escapeHtml(appId)}" />
|
|
273
|
+
<input type="hidden" name="itemId" value="${escapeHtml(item.id)}" />
|
|
274
|
+
${visibilityControls(item)}
|
|
275
|
+
<div><button type="submit" class="btn btn-sm btn-primary">Save visibility</button></div>
|
|
276
|
+
</form>
|
|
266
277
|
</div>
|
|
267
278
|
${actionButtons(item, appId)}
|
|
268
279
|
</div>
|
|
@@ -296,7 +307,7 @@ async function renderEditLink(item, route, depth, config, appDef, appId) {
|
|
|
296
307
|
].join("");
|
|
297
308
|
const viewOpts = renderViewOptions(views, route?.viewId ?? "");
|
|
298
309
|
return `<li ${rowAttrs(item, depth, true)} class="list-group-item">
|
|
299
|
-
<form hx-post="${API_BASE}/menu-editor/save-link" hx-target="#bp-menu-row-${item.id}" hx-swap="outerHTML"
|
|
310
|
+
<form hx-post="${API_BASE}/menu-editor/save-link" hx-target="#bp-menu-row-${item.id}" hx-swap="outerHTML settle:0"
|
|
300
311
|
class="d-flex flex-column gap-2">
|
|
301
312
|
<div class="d-flex align-items-center gap-2">
|
|
302
313
|
<span class="badge ${typeBadgeClass}">link</span>
|
|
@@ -320,7 +331,7 @@ async function renderEditLink(item, route, depth, config, appDef, appId) {
|
|
|
320
331
|
<select id="bp-views-${item.id}" name="viewId" class="form-select form-select-sm" required
|
|
321
332
|
hx-get="${API_BASE}/menu-editor/default-target"
|
|
322
333
|
hx-target="#bp-paths-${item.id}"
|
|
323
|
-
hx-swap="outerHTML"
|
|
334
|
+
hx-swap="outerHTML settle:0"
|
|
324
335
|
hx-trigger="change"
|
|
325
336
|
hx-include="closest form">${viewOpts}</select>
|
|
326
337
|
</div>
|
|
@@ -338,42 +349,22 @@ async function renderEditLink(item, route, depth, config, appDef, appId) {
|
|
|
338
349
|
</div>
|
|
339
350
|
</div>
|
|
340
351
|
${renderPathFields(item.id, route?.path ?? "", route?.targetPath ?? "")}
|
|
341
|
-
|
|
342
|
-
<div class="col-md-6">
|
|
343
|
-
<label class="form-label small mb-0">Unavailable service</label>
|
|
344
|
-
<select name="serviceStatus" class="form-select form-select-sm">
|
|
345
|
-
<option value="show"${item.serviceStatus !== "hide" ? " selected" : ""}>Show with warning</option>
|
|
346
|
-
<option value="hide"${item.serviceStatus === "hide" ? " selected" : ""}>Hide</option>
|
|
347
|
-
</select>
|
|
348
|
-
</div>
|
|
349
|
-
<div class="col-md-6">
|
|
350
|
-
<label class="form-label small mb-0">Required roles (any, comma separated)</label>
|
|
351
|
-
<input name="rolesAnyOf" class="form-control form-control-sm" value="${escapeHtml((item.rolesAnyOf ?? []).join(", "))}" />
|
|
352
|
-
<label class="form-label small mb-0">Authorization</label>
|
|
353
|
-
<select name="authStatus" class="form-select form-select-sm">
|
|
354
|
-
<option value="auto"${!item.authStatus || item.authStatus === "auto" ? " selected" : ""}>Automatic (route permissions)</option>
|
|
355
|
-
<option value="show"${item.authStatus === "show" ? " selected" : ""}>Always show</option>
|
|
356
|
-
<option value="hide-unauthenticated"${item.authStatus === "hide-unauthenticated" ? " selected" : ""}>Hide when signed out</option>
|
|
357
|
-
<option value="hide-unauthorized"${item.authStatus === "hide-unauthorized" ? " selected" : ""}>Hide when unauthorized</option>
|
|
358
|
-
<option value="show-unauthenticated"${item.authStatus === "show-unauthenticated" ? " selected" : ""}>Show only when signed out</option>
|
|
359
|
-
</select>
|
|
360
|
-
</div>
|
|
361
|
-
</div>
|
|
352
|
+
${visibilityControls(item)}
|
|
362
353
|
<div class="d-flex gap-2 justify-content-end">
|
|
363
354
|
<button type="submit" class="btn btn-sm btn-success">OK Save</button>
|
|
364
355
|
<button type="button" class="btn btn-sm btn-outline-secondary"
|
|
365
356
|
hx-get="${API_BASE}/menu-editor/item?appId=${encodeURIComponent(appId)}&itemId=${encodeURIComponent(item.id)}&mode=display"
|
|
366
|
-
hx-target="#bp-menu-row-${item.id}" hx-swap="outerHTML">X Cancel</button>
|
|
357
|
+
hx-target="#bp-menu-row-${item.id}" hx-swap="outerHTML settle:0">X Cancel</button>
|
|
367
358
|
</div>
|
|
368
359
|
<div class="small text-secondary">Path/target/service/view edits update the underlying route - affects any other menu items referencing it.</div>
|
|
369
360
|
</form>
|
|
370
361
|
</li>`;
|
|
371
362
|
}
|
|
372
|
-
function renderTree(items, depth, config, appDef, appId) {
|
|
363
|
+
function renderTree(items, depth, config, appDef, appId, accessApp = injectResolvedServicePaths({ tenants: config.tenants, apps: [appDef], managementOrigins: [] }).apps[0]) {
|
|
373
364
|
return items.map((item) => {
|
|
374
|
-
const row = renderRow(item, depth, "display", config, appDef, appId);
|
|
365
|
+
const row = renderRow(item, depth, "display", config, appDef, appId, accessApp);
|
|
375
366
|
const children = item.type === "group" && item.children && item.children.length > 0
|
|
376
|
-
? renderTree(item.children, depth + 1, config, appDef, appId)
|
|
367
|
+
? renderTree(item.children, depth + 1, config, appDef, appId, accessApp)
|
|
377
368
|
: "";
|
|
378
369
|
return row + children;
|
|
379
370
|
}).join("");
|
|
@@ -401,7 +392,7 @@ function renderAddForms(appId, routes, groups) {
|
|
|
401
392
|
<div class="card">
|
|
402
393
|
<div class="card-header"><strong>Add View Link</strong></div>
|
|
403
394
|
<div class="card-body">
|
|
404
|
-
<form hx-post="${API_BASE}/menu-editor/add" hx-target="#bp-menu-editor" hx-swap="outerHTML">
|
|
395
|
+
<form hx-post="${API_BASE}/menu-editor/add" hx-target="#bp-menu-editor" hx-swap="outerHTML settle:0">
|
|
405
396
|
<input type="hidden" name="appId" value="${escapeHtml(appId)}" />
|
|
406
397
|
<input type="hidden" name="type" value="link" />
|
|
407
398
|
<div class="mb-2">
|
|
@@ -428,7 +419,7 @@ function renderAddForms(appId, routes, groups) {
|
|
|
428
419
|
<div class="card">
|
|
429
420
|
<div class="card-header"><strong>Add External Link</strong></div>
|
|
430
421
|
<div class="card-body">
|
|
431
|
-
<form hx-post="${API_BASE}/menu-editor/add" hx-target="#bp-menu-editor" hx-swap="outerHTML">
|
|
422
|
+
<form hx-post="${API_BASE}/menu-editor/add" hx-target="#bp-menu-editor" hx-swap="outerHTML settle:0">
|
|
432
423
|
<input type="hidden" name="appId" value="${escapeHtml(appId)}" />
|
|
433
424
|
<input type="hidden" name="type" value="external" />
|
|
434
425
|
<div class="mb-2">
|
|
@@ -452,7 +443,7 @@ function renderAddForms(appId, routes, groups) {
|
|
|
452
443
|
<div class="card">
|
|
453
444
|
<div class="card-header"><strong>Add Group</strong></div>
|
|
454
445
|
<div class="card-body">
|
|
455
|
-
<form hx-post="${API_BASE}/menu-editor/add" hx-target="#bp-menu-editor" hx-swap="outerHTML">
|
|
446
|
+
<form hx-post="${API_BASE}/menu-editor/add" hx-target="#bp-menu-editor" hx-swap="outerHTML settle:0">
|
|
456
447
|
<input type="hidden" name="appId" value="${escapeHtml(appId)}" />
|
|
457
448
|
<input type="hidden" name="type" value="group" />
|
|
458
449
|
<div class="mb-2">
|
|
@@ -481,7 +472,7 @@ function renderEditor(config, appDef, appId) {
|
|
|
481
472
|
<form id="bp-drag-move-form" style="display:none"
|
|
482
473
|
hx-post="${API_BASE}/menu-editor/move-after"
|
|
483
474
|
hx-target="#bp-menu-editor"
|
|
484
|
-
hx-swap="outerHTML">
|
|
475
|
+
hx-swap="outerHTML settle:0">
|
|
485
476
|
<input type="hidden" name="appId" value="${escapeHtml(appId)}" />
|
|
486
477
|
<input type="hidden" name="itemId" />
|
|
487
478
|
<input type="hidden" name="anchorId" />
|
|
@@ -492,9 +483,25 @@ function renderEditor(config, appDef, appId) {
|
|
|
492
483
|
</div>`;
|
|
493
484
|
}
|
|
494
485
|
// -- Endpoint registration --------------------------------------------
|
|
495
|
-
export function registerMenuEditorRoutes(app, store) {
|
|
496
|
-
const
|
|
497
|
-
|
|
486
|
+
export function registerMenuEditorRoutes(app, store, database) {
|
|
487
|
+
const read = async (appId) => {
|
|
488
|
+
if (!database)
|
|
489
|
+
return store.loadConfig();
|
|
490
|
+
return readApp(database, appId);
|
|
491
|
+
};
|
|
492
|
+
const post = (path, action) => {
|
|
493
|
+
app.post(`${API_BASE}/menu-editor/${path}`, async (event) => {
|
|
494
|
+
const form = await readFormBody(event);
|
|
495
|
+
if (database)
|
|
496
|
+
return editAppRows(database, form.appId, eventObservability(event), data => action(form, data));
|
|
497
|
+
const config = await store.loadConfig();
|
|
498
|
+
const response = await action(form, config);
|
|
499
|
+
if (response.ok)
|
|
500
|
+
await store.saveConfig(config);
|
|
501
|
+
return response;
|
|
502
|
+
});
|
|
503
|
+
};
|
|
504
|
+
const respondEditor = async (config, appId) => {
|
|
498
505
|
const appDef = getApp(config, appId);
|
|
499
506
|
if (!appDef)
|
|
500
507
|
return htmlResponse(`<div class="alert alert-danger">App not found</div>`, 200, "text/html; mode=fragment");
|
|
@@ -502,8 +509,7 @@ export function registerMenuEditorRoutes(app, store) {
|
|
|
502
509
|
"HX-Trigger": "bp:menu-changed"
|
|
503
510
|
});
|
|
504
511
|
};
|
|
505
|
-
const respondRow = async (appId, itemId, mode) => {
|
|
506
|
-
const config = await store.loadConfig();
|
|
512
|
+
const respondRow = async (config, appId, itemId, mode) => {
|
|
507
513
|
const appDef = getApp(config, appId);
|
|
508
514
|
if (!appDef)
|
|
509
515
|
return htmlResponse("", 200, "text/html; mode=fragment");
|
|
@@ -547,18 +553,16 @@ export function registerMenuEditorRoutes(app, store) {
|
|
|
547
553
|
const appId = url.searchParams.get("appId") ?? "";
|
|
548
554
|
if (!appId)
|
|
549
555
|
return htmlResponse(`<div class="alert alert-secondary">Select an app</div>`, 200, "text/html; mode=fragment");
|
|
550
|
-
return respondEditor(appId);
|
|
556
|
+
return respondEditor(await read(appId), appId);
|
|
551
557
|
});
|
|
552
558
|
app.get(`${API_BASE}/menu-editor/item`, async (event) => {
|
|
553
559
|
const url = new URL(event.req.url ?? "", RELATIVE_URL_PARSE_BASE);
|
|
554
560
|
const appId = url.searchParams.get("appId") ?? "";
|
|
555
561
|
const itemId = url.searchParams.get("itemId") ?? "";
|
|
556
562
|
const mode = (url.searchParams.get("mode") ?? "display");
|
|
557
|
-
return respondRow(appId, itemId, mode);
|
|
563
|
+
return respondRow(await read(appId), appId, itemId, mode);
|
|
558
564
|
});
|
|
559
|
-
|
|
560
|
-
const f = await readFormBody(event);
|
|
561
|
-
const config = await store.loadConfig();
|
|
565
|
+
post("save-visibility", async (f, config) => {
|
|
562
566
|
const appDef = getApp(config, f.appId);
|
|
563
567
|
if (!appDef)
|
|
564
568
|
return jsonResponse({ error: "App not found" }, 404);
|
|
@@ -569,13 +573,13 @@ export function registerMenuEditorRoutes(app, store) {
|
|
|
569
573
|
return jsonResponse({ error: "Invalid visibility" }, 400);
|
|
570
574
|
}
|
|
571
575
|
found.item.authStatus = f.authStatus;
|
|
572
|
-
found.item.
|
|
573
|
-
|
|
574
|
-
|
|
576
|
+
if (found.item.type === "link")
|
|
577
|
+
found.item.serviceStatus = f.serviceStatus === "hide" ? "hide" : "show";
|
|
578
|
+
if (found.item.type === "link" || f.clearRoleRestriction === "true")
|
|
579
|
+
delete found.item.rolesAnyOf;
|
|
580
|
+
return respondEditor(config, f.appId);
|
|
575
581
|
});
|
|
576
|
-
|
|
577
|
-
const f = await readFormBody(event);
|
|
578
|
-
const config = await store.loadConfig();
|
|
582
|
+
post("save-title", async (f, config) => {
|
|
579
583
|
const appDef = getApp(config, f.appId);
|
|
580
584
|
if (!appDef)
|
|
581
585
|
return jsonResponse({ error: "App not found" }, 404);
|
|
@@ -584,9 +588,8 @@ export function registerMenuEditorRoutes(app, store) {
|
|
|
584
588
|
if (found)
|
|
585
589
|
found.item.title = f.title || undefined;
|
|
586
590
|
appDef.menu = menu;
|
|
587
|
-
await store.saveConfig(config);
|
|
588
591
|
// Return single row + HX-Trigger to refresh sidebar nav
|
|
589
|
-
const config2 =
|
|
592
|
+
const config2 = config;
|
|
590
593
|
const appDef2 = getApp(config2, f.appId);
|
|
591
594
|
const found2 = appDef2 ? locate(getMenu(appDef2), f.itemId) : null;
|
|
592
595
|
if (!appDef2 || !found2)
|
|
@@ -598,9 +601,7 @@ export function registerMenuEditorRoutes(app, store) {
|
|
|
598
601
|
"HX-Trigger": "bp:menu-changed"
|
|
599
602
|
});
|
|
600
603
|
});
|
|
601
|
-
|
|
602
|
-
const f = await readFormBody(event);
|
|
603
|
-
const config = await store.loadConfig();
|
|
604
|
+
post("save-link", async (f, config) => {
|
|
604
605
|
const appDef = getApp(config, f.appId);
|
|
605
606
|
if (!appDef)
|
|
606
607
|
return jsonResponse({ error: "App not found" }, 404);
|
|
@@ -610,7 +611,8 @@ export function registerMenuEditorRoutes(app, store) {
|
|
|
610
611
|
return htmlResponse(`<div class="alert alert-danger">Item not found</div>`, 200, "text/html; mode=fragment");
|
|
611
612
|
}
|
|
612
613
|
found.item.title = f.title || undefined;
|
|
613
|
-
found.item.
|
|
614
|
+
if (found.item.type === "link" || f.clearRoleRestriction === "true")
|
|
615
|
+
delete found.item.rolesAnyOf;
|
|
614
616
|
found.item.serviceStatus = f.serviceStatus === "hide" ? "hide" : "show";
|
|
615
617
|
found.item.authStatus = ["auto", "show", "hide-unauthenticated", "hide-unauthorized", "show-unauthenticated"].includes(f.authStatus) ? f.authStatus : "auto";
|
|
616
618
|
const route = (appDef.routes ?? []).find((r) => r.id === found.item.routeId);
|
|
@@ -628,8 +630,7 @@ export function registerMenuEditorRoutes(app, store) {
|
|
|
628
630
|
return jsonResponse({ error: "Route cannot be added to the menu" }, 400);
|
|
629
631
|
}
|
|
630
632
|
appDef.menu = menu;
|
|
631
|
-
|
|
632
|
-
return respondRow(f.appId, f.itemId, "display").then((r) => {
|
|
633
|
+
return respondRow(config, f.appId, f.itemId, "display").then((r) => {
|
|
633
634
|
const headers = new Headers(r.headers);
|
|
634
635
|
headers.set("HX-Trigger", "bp:menu-changed");
|
|
635
636
|
return new Response(r.body, { status: r.status, headers });
|
|
@@ -656,7 +657,7 @@ export function registerMenuEditorRoutes(app, store) {
|
|
|
656
657
|
if (url.searchParams.get("autoSetPaths") === "true" && serviceId && viewId) {
|
|
657
658
|
const views = lookupServiceViews(serviceId);
|
|
658
659
|
targetPath = views.find((v) => v.viewId === viewId)?.path ?? "";
|
|
659
|
-
const config = await
|
|
660
|
+
const config = await read(appId);
|
|
660
661
|
const appDef = getApp(config, appId);
|
|
661
662
|
const assignedRoute = (appDef?.routes ?? []).find((route) => route.kind !== "api" && route.serviceId === serviceId && route.viewId === viewId);
|
|
662
663
|
if (assignedRoute) {
|
|
@@ -666,16 +667,15 @@ export function registerMenuEditorRoutes(app, store) {
|
|
|
666
667
|
}
|
|
667
668
|
return htmlResponse(renderPathFields(itemId, path, targetPath), 200, "text/html; mode=fragment");
|
|
668
669
|
});
|
|
669
|
-
|
|
670
|
-
const f = await readFormBody(event);
|
|
671
|
-
const config = await store.loadConfig();
|
|
670
|
+
post("save-external", async (f, config) => {
|
|
672
671
|
const appDef = getApp(config, f.appId);
|
|
673
672
|
if (!appDef)
|
|
674
673
|
return jsonResponse({ error: "App not found" }, 404);
|
|
675
674
|
const menu = getMenu(appDef);
|
|
676
675
|
const found = locate(menu, f.itemId);
|
|
677
676
|
if (found) {
|
|
678
|
-
found.item.
|
|
677
|
+
if (found.item.type === "link" || f.clearRoleRestriction === "true")
|
|
678
|
+
delete found.item.rolesAnyOf;
|
|
679
679
|
found.item.authStatus = ["auto", "show", "hide-unauthenticated", "hide-unauthorized", "show-unauthenticated"].includes(f.authStatus) ? f.authStatus : "auto";
|
|
680
680
|
found.item.href = f.href || "";
|
|
681
681
|
if (f.title !== undefined && f.title !== "")
|
|
@@ -685,17 +685,14 @@ export function registerMenuEditorRoutes(app, store) {
|
|
|
685
685
|
return jsonResponse({ error: "Route cannot be added to the menu" }, 400);
|
|
686
686
|
}
|
|
687
687
|
appDef.menu = menu;
|
|
688
|
-
|
|
689
|
-
return respondRow(f.appId, f.itemId, "display").then((r) => {
|
|
688
|
+
return respondRow(config, f.appId, f.itemId, "display").then((r) => {
|
|
690
689
|
// Add HX-Trigger to existing response
|
|
691
690
|
const headers = new Headers(r.headers);
|
|
692
691
|
headers.set("HX-Trigger", "bp:menu-changed");
|
|
693
692
|
return new Response(r.body, { status: r.status, headers });
|
|
694
693
|
});
|
|
695
694
|
});
|
|
696
|
-
|
|
697
|
-
const f = await readFormBody(event);
|
|
698
|
-
const config = await store.loadConfig();
|
|
695
|
+
post("add", async (f, config) => {
|
|
699
696
|
const appDef = getApp(config, f.appId);
|
|
700
697
|
if (!appDef)
|
|
701
698
|
return jsonResponse({ error: "App not found" }, 404);
|
|
@@ -727,12 +724,9 @@ export function registerMenuEditorRoutes(app, store) {
|
|
|
727
724
|
menu.push(newItem);
|
|
728
725
|
}
|
|
729
726
|
appDef.menu = menu;
|
|
730
|
-
|
|
731
|
-
return respondEditor(f.appId);
|
|
727
|
+
return respondEditor(config, f.appId);
|
|
732
728
|
});
|
|
733
|
-
|
|
734
|
-
const f = await readFormBody(event);
|
|
735
|
-
const config = await store.loadConfig();
|
|
729
|
+
post("remove", async (f, config) => {
|
|
736
730
|
const appDef = getApp(config, f.appId);
|
|
737
731
|
if (!appDef)
|
|
738
732
|
return jsonResponse({ error: "App not found" }, 404);
|
|
@@ -741,12 +735,9 @@ export function registerMenuEditorRoutes(app, store) {
|
|
|
741
735
|
if (found)
|
|
742
736
|
found.parent.splice(found.index, 1);
|
|
743
737
|
appDef.menu = menu;
|
|
744
|
-
|
|
745
|
-
return respondEditor(f.appId);
|
|
738
|
+
return respondEditor(config, f.appId);
|
|
746
739
|
});
|
|
747
|
-
|
|
748
|
-
const f = await readFormBody(event);
|
|
749
|
-
const config = await store.loadConfig();
|
|
740
|
+
post("toggle", async (f, config) => {
|
|
750
741
|
const appDef = getApp(config, f.appId);
|
|
751
742
|
if (!appDef)
|
|
752
743
|
return jsonResponse({ error: "App not found" }, 404);
|
|
@@ -755,12 +746,9 @@ export function registerMenuEditorRoutes(app, store) {
|
|
|
755
746
|
if (found)
|
|
756
747
|
found.item.enabled = !found.item.enabled;
|
|
757
748
|
appDef.menu = menu;
|
|
758
|
-
|
|
759
|
-
return respondEditor(f.appId);
|
|
749
|
+
return respondEditor(config, f.appId);
|
|
760
750
|
});
|
|
761
|
-
|
|
762
|
-
const f = await readFormBody(event);
|
|
763
|
-
const config = await store.loadConfig();
|
|
751
|
+
post("toggle-expanded", async (f, config) => {
|
|
764
752
|
const appDef = getApp(config, f.appId);
|
|
765
753
|
if (!appDef)
|
|
766
754
|
return jsonResponse({ error: "App not found" }, 404);
|
|
@@ -770,12 +758,9 @@ export function registerMenuEditorRoutes(app, store) {
|
|
|
770
758
|
found.item.defaultExpanded = !found.item.defaultExpanded;
|
|
771
759
|
}
|
|
772
760
|
appDef.menu = menu;
|
|
773
|
-
|
|
774
|
-
return respondEditor(f.appId);
|
|
761
|
+
return respondEditor(config, f.appId);
|
|
775
762
|
});
|
|
776
|
-
|
|
777
|
-
const f = await readFormBody(event);
|
|
778
|
-
const config = await store.loadConfig();
|
|
763
|
+
post("move-up", async (f, config) => {
|
|
779
764
|
const appDef = getApp(config, f.appId);
|
|
780
765
|
if (!appDef)
|
|
781
766
|
return jsonResponse({ error: "App not found" }, 404);
|
|
@@ -786,12 +771,9 @@ export function registerMenuEditorRoutes(app, store) {
|
|
|
786
771
|
found.parent.splice(found.index - 1, 0, it);
|
|
787
772
|
}
|
|
788
773
|
appDef.menu = menu;
|
|
789
|
-
|
|
790
|
-
return respondEditor(f.appId);
|
|
774
|
+
return respondEditor(config, f.appId);
|
|
791
775
|
});
|
|
792
|
-
|
|
793
|
-
const f = await readFormBody(event);
|
|
794
|
-
const config = await store.loadConfig();
|
|
776
|
+
post("move-down", async (f, config) => {
|
|
795
777
|
const appDef = getApp(config, f.appId);
|
|
796
778
|
if (!appDef)
|
|
797
779
|
return jsonResponse({ error: "App not found" }, 404);
|
|
@@ -802,12 +784,9 @@ export function registerMenuEditorRoutes(app, store) {
|
|
|
802
784
|
found.parent.splice(found.index + 1, 0, it);
|
|
803
785
|
}
|
|
804
786
|
appDef.menu = menu;
|
|
805
|
-
|
|
806
|
-
return respondEditor(f.appId);
|
|
787
|
+
return respondEditor(config, f.appId);
|
|
807
788
|
});
|
|
808
|
-
|
|
809
|
-
const f = await readFormBody(event);
|
|
810
|
-
const config = await store.loadConfig();
|
|
789
|
+
post("move-in", async (f, config) => {
|
|
811
790
|
const appDef = getApp(config, f.appId);
|
|
812
791
|
if (!appDef)
|
|
813
792
|
return jsonResponse({ error: "App not found" }, 404);
|
|
@@ -822,12 +801,9 @@ export function registerMenuEditorRoutes(app, store) {
|
|
|
822
801
|
}
|
|
823
802
|
}
|
|
824
803
|
appDef.menu = menu;
|
|
825
|
-
|
|
826
|
-
return respondEditor(f.appId);
|
|
804
|
+
return respondEditor(config, f.appId);
|
|
827
805
|
});
|
|
828
|
-
|
|
829
|
-
const f = await readFormBody(event);
|
|
830
|
-
const config = await store.loadConfig();
|
|
806
|
+
post("move-after", async (f, config) => {
|
|
831
807
|
const appDef = getApp(config, f.appId);
|
|
832
808
|
if (!appDef)
|
|
833
809
|
return jsonResponse({ error: "App not found" }, 404);
|
|
@@ -836,10 +812,10 @@ export function registerMenuEditorRoutes(app, store) {
|
|
|
836
812
|
const anchorId = f.anchorId || "";
|
|
837
813
|
const targetDepth = Math.max(0, parseInt(f.targetDepth ?? "0", 10) || 0);
|
|
838
814
|
if (!srcId || srcId === anchorId)
|
|
839
|
-
return respondEditor(f.appId);
|
|
815
|
+
return respondEditor(config, f.appId);
|
|
840
816
|
const src = locate(menu, srcId);
|
|
841
817
|
if (!src)
|
|
842
|
-
return respondEditor(f.appId);
|
|
818
|
+
return respondEditor(config, f.appId);
|
|
843
819
|
// Prevent dropping into own descendant
|
|
844
820
|
const isDescendant = (items, id) => {
|
|
845
821
|
for (const it of items) {
|
|
@@ -851,7 +827,7 @@ export function registerMenuEditorRoutes(app, store) {
|
|
|
851
827
|
return false;
|
|
852
828
|
};
|
|
853
829
|
if (anchorId && src.item.type === "group" && src.item.children && isDescendant(src.item.children, anchorId)) {
|
|
854
|
-
return respondEditor(f.appId);
|
|
830
|
+
return respondEditor(config, f.appId);
|
|
855
831
|
}
|
|
856
832
|
// Remove src
|
|
857
833
|
const [srcItem] = src.parent.splice(src.index, 1);
|
|
@@ -859,8 +835,7 @@ export function registerMenuEditorRoutes(app, store) {
|
|
|
859
835
|
// Insert at start of root
|
|
860
836
|
menu.unshift(srcItem);
|
|
861
837
|
appDef.menu = menu;
|
|
862
|
-
|
|
863
|
-
return respondEditor(f.appId);
|
|
838
|
+
return respondEditor(config, f.appId);
|
|
864
839
|
}
|
|
865
840
|
// Build ancestor chain to anchor
|
|
866
841
|
const ancestorChain = (items, id, chain = [], depth = 0) => {
|
|
@@ -879,7 +854,7 @@ export function registerMenuEditorRoutes(app, store) {
|
|
|
879
854
|
const chain = ancestorChain(menu, anchorId);
|
|
880
855
|
if (!chain) {
|
|
881
856
|
src.parent.splice(src.index, 0, srcItem);
|
|
882
|
-
return respondEditor(f.appId);
|
|
857
|
+
return respondEditor(config, f.appId);
|
|
883
858
|
}
|
|
884
859
|
const anchor = chain[chain.length - 1];
|
|
885
860
|
const anchorDepth = anchor.depth;
|
|
@@ -904,12 +879,9 @@ export function registerMenuEditorRoutes(app, store) {
|
|
|
904
879
|
}
|
|
905
880
|
}
|
|
906
881
|
appDef.menu = menu;
|
|
907
|
-
|
|
908
|
-
return respondEditor(f.appId);
|
|
882
|
+
return respondEditor(config, f.appId);
|
|
909
883
|
});
|
|
910
|
-
|
|
911
|
-
const f = await readFormBody(event);
|
|
912
|
-
const config = await store.loadConfig();
|
|
884
|
+
post("move-out", async (f, config) => {
|
|
913
885
|
const appDef = getApp(config, f.appId);
|
|
914
886
|
if (!appDef)
|
|
915
887
|
return jsonResponse({ error: "App not found" }, 404);
|
|
@@ -924,8 +896,7 @@ export function registerMenuEditorRoutes(app, store) {
|
|
|
924
896
|
}
|
|
925
897
|
}
|
|
926
898
|
appDef.menu = menu;
|
|
927
|
-
|
|
928
|
-
return respondEditor(f.appId);
|
|
899
|
+
return respondEditor(config, f.appId);
|
|
929
900
|
});
|
|
930
901
|
}
|
|
931
902
|
//# sourceMappingURL=menuEditor.js.map
|